mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
fix(beam): distinguish empty optional chunk payloads
This commit is contained in:
+7
-1
@@ -37,9 +37,15 @@ When `AtU8` or `Atom` is present, `atoms` is the 1-based name table and
|
||||
those names when the table is present; unresolved indices keep `function` or
|
||||
`module` null.
|
||||
|
||||
A zero-byte `AtU8` chunk does not override a nonempty historical `Atom` table.
|
||||
The chunk inventory retains both occurrences. A nonempty malformed `AtU8`
|
||||
still fails instead of falling back to an older table.
|
||||
|
||||
`code` reports the Code chunk instruction-set, opcode maximum, label count,
|
||||
function count, and the instruction stream's file range. Missing optional
|
||||
tables are null, not empty-everywhere.
|
||||
tables are null, not empty-everywhere. A zero-byte optional `FunT` has no decoded
|
||||
lambda table (`lambdas: null`); a four-byte zero count is an observed empty table
|
||||
(`lambdas: []`). Both chunks remain in the inventory.
|
||||
|
||||
## Limits and failures
|
||||
|
||||
|
||||
@@ -243,6 +243,52 @@ it("keeps a latin1 atom byte unchanged", () => {
|
||||
expect(report.module).toBe("ÿ");
|
||||
});
|
||||
|
||||
it("uses a nonempty legacy atom table when the selected AtU8 chunk is empty", () => {
|
||||
const legacy = singleChunkBeam("Atom", Buffer.from([0, 0, 0, 1, 1, 0xff]));
|
||||
const empty = singleChunkBeam("AtU8", Buffer.alloc(0));
|
||||
for (const chunks of [
|
||||
[legacy, empty],
|
||||
[empty, legacy],
|
||||
]) {
|
||||
const bytes = Buffer.concat([chunks[0]!, chunks[1]!.subarray(12)]);
|
||||
bytes.writeUInt32BE(bytes.length - 8, 4);
|
||||
expect(parse(bytes)).toMatchObject({
|
||||
atom_encoding: "latin1",
|
||||
module: "ÿ",
|
||||
chunks: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
name: "AtU8",
|
||||
payload: expect.objectContaining({ bytes: 0 }),
|
||||
}),
|
||||
]),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("retains empty optional FunT chunks without discarding compiler module facts", () => {
|
||||
const original = erlangBeamOtp29Fixture();
|
||||
const control = parse(original);
|
||||
for (const payload of [Buffer.alloc(0), Buffer.alloc(4), Buffer.alloc(1)]) {
|
||||
const bytes = Buffer.concat([
|
||||
original,
|
||||
singleChunkBeam("FunT", payload).subarray(12),
|
||||
]);
|
||||
bytes.writeUInt32BE(bytes.length - 8, 4);
|
||||
if (payload.length === 1) {
|
||||
expect(() => parse(bytes)).toThrow(/lambda table is truncated/);
|
||||
continue;
|
||||
}
|
||||
const report = parse(bytes);
|
||||
expect(report.module).toBe(control.module);
|
||||
expect(report.exports).toEqual(control.exports);
|
||||
expect(report.lambdas).toEqual(payload.length === 0 ? null : []);
|
||||
expect(report.chunks.at(-1)).toMatchObject({
|
||||
name: "FunT",
|
||||
payload: { bytes: payload.length },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects combined table rows before allocating them", () => {
|
||||
const below = Math.floor(MAX_INSPECT_ERLANG_BEAM_TABLE_ROWS / 2);
|
||||
const overflow = MAX_INSPECT_ERLANG_BEAM_TABLE_ROWS - below * 2 + 1;
|
||||
|
||||
@@ -256,12 +256,18 @@ export const parseErlangBeam = (
|
||||
payloads.set(name, { payload, offset: at });
|
||||
at = padded;
|
||||
}
|
||||
const atomChunk = payloads.get("AtU8") ?? payloads.get("Atom");
|
||||
const atomEncoding = payloads.has("AtU8")
|
||||
? "utf8"
|
||||
: payloads.has("Atom")
|
||||
? "latin1"
|
||||
: null;
|
||||
const utf8Atoms = payloads.get("AtU8");
|
||||
const legacyAtoms = payloads.get("Atom");
|
||||
const useLegacyAtoms =
|
||||
(utf8Atoms?.payload.length ?? 0) === 0 &&
|
||||
(legacyAtoms?.payload.length ?? 0) > 0;
|
||||
const atomChunk = useLegacyAtoms ? legacyAtoms : (utf8Atoms ?? legacyAtoms);
|
||||
const atomEncoding =
|
||||
atomChunk === undefined
|
||||
? null
|
||||
: useLegacyAtoms || utf8Atoms === undefined
|
||||
? "latin1"
|
||||
: "utf8";
|
||||
const atoms =
|
||||
atomChunk === undefined || atomEncoding === null
|
||||
? null
|
||||
@@ -300,10 +306,11 @@ export const parseErlangBeam = (
|
||||
function: atomAt(atoms, row[1]!),
|
||||
arity: row[2]!,
|
||||
}));
|
||||
const lambdaPayload = payloads.get("FunT")?.payload;
|
||||
const lambdas =
|
||||
payloads.get("FunT") === undefined
|
||||
lambdaPayload === undefined || lambdaPayload.length === 0
|
||||
? null
|
||||
: parseRows(payloads.get("FunT")!.payload, 6, "lambda").map((row) => ({
|
||||
: parseRows(lambdaPayload, 6, "lambda").map((row) => ({
|
||||
function_index: row[0]!,
|
||||
function: atomAt(atoms, row[0]!),
|
||||
arity: row[1]!,
|
||||
|
||||
Reference in New Issue
Block a user