mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
1 parent
0c99194b8c
commit
f28c0fdfb0
4 files changed
+94
-2
No files matched your search
+6
-1
@@ -24,6 +24,9 @@ requires an absolute filesystem path and preserves that exact selection:
|
||||
|
||||
The inline Evidence result contains the artifact path, SHA-256, and byte length.
|
||||
`chunks` lists every IFF chunk name, file range, and payload SHA-256.
|
||||
Repeated names retain separate chunk occurrences. Decoded tables and the Code
|
||||
header use the last occurrence of each name, matching OTP's loader; the chunk
|
||||
inventory still exposes earlier payloads and their source ranges.
|
||||
|
||||
When `AtU8` or `Atom` is present, `atoms` is the 1-based name table and
|
||||
`module` is atom 1. Both the pre-OTP 28 table and the OTP 28+ compact table
|
||||
@@ -43,4 +46,6 @@ import, and lambda rows together. Malformed `FOR1`/`BEAM` headers, truncated
|
||||
chunks, and `AtU8` names that are not valid UTF-8 fail without partial success.
|
||||
|
||||
This workflow does not execute bytecode, inflate `LitT` literals, or decode
|
||||
`Attr`/`CInf` external-term payloads.
|
||||
`Attr`/`CInf` external-term payloads. Complete coverage describes this static
|
||||
chunk and table report; it does not establish that an Erlang runtime can load
|
||||
or execute the module.
|
||||
@@ -79,6 +79,52 @@ it("reads atoms, exports, imports, lambdas and the Code header", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("selects the last table and Code payload for every repeated chunk name", () => {
|
||||
const original = erlangBeamFixture("selected");
|
||||
const decoy = erlangBeamFixture("decoy");
|
||||
const changedFields = new Map([
|
||||
["Code", 16],
|
||||
["ExpT", 8],
|
||||
["ImpT", 12],
|
||||
["FunT", 8],
|
||||
]);
|
||||
for (let at = 12; at < decoy.length;) {
|
||||
const name = decoy.toString("latin1", at, at + 4);
|
||||
const size = decoy.readUInt32BE(at + 4);
|
||||
const field = changedFields.get(name);
|
||||
if (field !== undefined) decoy.writeUInt32BE(9, at + 8 + field);
|
||||
at += 8 + size + ((4 - (size % 4)) % 4);
|
||||
}
|
||||
const repeated = Buffer.concat([
|
||||
original.subarray(0, 12),
|
||||
decoy.subarray(12),
|
||||
original.subarray(12),
|
||||
]);
|
||||
repeated.writeUInt32BE(repeated.length - 8, 4);
|
||||
const report = parse(repeated);
|
||||
const control = parse(original);
|
||||
expect(report.atoms).toEqual(control.atoms);
|
||||
expect(report.exports).toEqual(control.exports);
|
||||
expect(report.imports).toEqual(control.imports);
|
||||
expect(report.lambdas).toEqual(control.lambdas);
|
||||
expect(report.code).toEqual({
|
||||
...control.code,
|
||||
instructions: {
|
||||
...control.code!.instructions,
|
||||
offset: control.code!.instructions.offset + decoy.length - 12,
|
||||
},
|
||||
});
|
||||
expect(report.chunks).toHaveLength(control.chunks.length * 2);
|
||||
});
|
||||
|
||||
it("rejects a malformed selected atom table instead of using an earlier table", () => {
|
||||
const original = erlangBeamFixture();
|
||||
const invalid = singleChunkBeam("AtU8", Buffer.alloc(0));
|
||||
const repeated = Buffer.concat([original, invalid.subarray(12)]);
|
||||
repeated.writeUInt32BE(repeated.length - 8, 4);
|
||||
expect(() => parse(repeated)).toThrow(/truncated/);
|
||||
});
|
||||
|
||||
// Expected values come from beam_lib:chunks/2 on the same file under OTP 29.
|
||||
it("reads a compiler-produced OTP 29 BEAM with the compact atom table", () => {
|
||||
const report = parse(erlangBeamOtp29Fixture());
|
||||
|
||||
@@ -256,7 +256,9 @@ export const parseErlangBeam = (
|
||||
sha256: sha256(payload),
|
||||
},
|
||||
});
|
||||
if (!payloads.has(name)) payloads.set(name, { payload, offset: at });
|
||||
// OTP's loader replaces the selected payload on each matching chunk.
|
||||
// Keep every occurrence above; decode the last occurrence of each name.
|
||||
payloads.set(name, { payload, offset: at });
|
||||
at = padded;
|
||||
}
|
||||
const atomChunk = payloads.get("AtU8") ?? payloads.get("Atom");
|
||||
|
||||
@@ -72,6 +72,45 @@ cliTest("reports BEAM exports through the public CLI", async ({ cli }) => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
cliTest(
|
||||
"decodes the last atom table while retaining every chunk occurrence",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-beam-repeated-atoms-");
|
||||
const original = erlangBeamOtp29Fixture();
|
||||
const decoy = oneAtomBeam(0).subarray(12);
|
||||
const repeated = Buffer.concat([
|
||||
original.subarray(0, 12),
|
||||
decoy,
|
||||
original.subarray(12),
|
||||
]);
|
||||
repeated.writeUInt32BE(repeated.length - 8, 4);
|
||||
const path = join(root, "repeated.beam");
|
||||
await writeFile(path, repeated);
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-erlang-beam", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
const report = toolContract("inspect_erlang_beam").outputSchema.parse(
|
||||
result.json,
|
||||
).normalized_result;
|
||||
expect(report.module).toBe("rea_beam_fixture");
|
||||
expect(report.exports).toContainEqual({
|
||||
function_index: 2,
|
||||
function: "hello",
|
||||
arity: 1,
|
||||
label: 2,
|
||||
});
|
||||
const atomChunks = report.chunks.filter(({ name }) => name === "AtU8");
|
||||
expect(atomChunks).toHaveLength(2);
|
||||
expect(atomChunks[0]?.payload.sha256).not.toBe(
|
||||
atomChunks[1]?.payload.sha256,
|
||||
);
|
||||
expect(atomChunks[0]?.payload.location.offset).toBe(20);
|
||||
expect(atomChunks[1]?.payload.location.offset).toBe(20 + decoy.length);
|
||||
expect(report.coverage.status).toBe("complete");
|
||||
},
|
||||
);
|
||||
|
||||
cliTest(
|
||||
"inspects an OTP 29 BEAM with an extended compact atom length and preserves source identity",
|
||||
async ({ cli }) => {
|
||||
|
||||
Reference in new issue
Block a user