fix(dotnet): name the instruction when a CIL operand leaves the method body (#2697)
The decoder already reports "Instruction X at IL offset N leaves method
body" when an operand's computed end passes the IL body, but only after
the operand was read. Operand readers use Buffer reads, so an operand
cut off by the body end threw a RangeError first. The catch-all stored
Node's message ("Attempt to access memory outside buffer bounds" or an
offset RangeError) as body.issue, dropping the instruction and offset.
A body ending in a lone 0xFE two-byte opcode prefix failed the same way.
Treat a RangeError from the operand read as the operand leaving the body
so the existing diagnostic applies, and check the second opcode byte
before reading it. Malformed status, partial coverage and earlier
decoded instructions are unchanged.
Fixes #2677
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
cdc41fc86c
commit
97dc6581f4
|
|
@ -510,3 +510,41 @@ describe("managed exception section decoding", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("managed CIL truncated operands", () => {
|
||||
const tinyBody = (il: readonly number[]) => {
|
||||
const bytes = buildManagedPeFixture({
|
||||
ilBody: Buffer.from([(il.length << 2) | 0x02, ...il]),
|
||||
});
|
||||
const inspection = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
return { body: inspection.methods[0]?.body, coverage: inspection.coverage };
|
||||
};
|
||||
|
||||
it.each([
|
||||
[[0x20], 0, "Instruction ldc.i4 at IL offset 0 leaves method body"],
|
||||
[
|
||||
[0x00, 0x20, 0x01],
|
||||
1,
|
||||
"Instruction ldc.i4 at IL offset 1 leaves method body",
|
||||
],
|
||||
[[0x2b], 0, "Instruction br.s at IL offset 0 leaves method body"],
|
||||
[[0x45, 0x02], 0, "Instruction switch at IL offset 0 leaves method body"],
|
||||
[
|
||||
[0xfe, 0x09, 0x00],
|
||||
0,
|
||||
"Instruction ldarg at IL offset 0 leaves method body",
|
||||
],
|
||||
[[0x00, 0xfe], 1, "Two-byte CIL opcode at IL offset 1 leaves method body"],
|
||||
])("names the instruction and IL offset for IL %j", (il, decoded, issue) => {
|
||||
const { body, coverage } = tinyBody(il);
|
||||
expect(body).toMatchObject({
|
||||
status: "malformed",
|
||||
decoded_instruction_count: decoded,
|
||||
issue,
|
||||
});
|
||||
expect(coverage.state).toBe("partial");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -384,6 +384,22 @@ const readOperand = (
|
|||
return { next: offset, anchorKind: "unknown", value: null };
|
||||
};
|
||||
|
||||
/** An operand cut off by the end of the IL body has no decodable value. */
|
||||
const readOperandWithin = (
|
||||
il: Buffer,
|
||||
offset: number,
|
||||
kind: string,
|
||||
): OperandResult | undefined => {
|
||||
try {
|
||||
return readOperand(il, offset, kind);
|
||||
} catch (cause: unknown) {
|
||||
// Buffer reads past the IL body throw RangeError; report it as the
|
||||
// instruction leaving the method body rather than as a Buffer message.
|
||||
if (cause instanceof RangeError) return undefined;
|
||||
throw cause;
|
||||
}
|
||||
};
|
||||
|
||||
export const decodeInstructions = (
|
||||
il: Buffer,
|
||||
admitInstruction?: (offset: number) => void,
|
||||
|
|
@ -404,14 +420,22 @@ export const decodeInstructions = (
|
|||
const start = offset;
|
||||
const first = il.readUInt8(offset);
|
||||
offset += 1;
|
||||
if (first === 0xfe && offset >= il.length) {
|
||||
issue = `Two-byte CIL opcode at IL offset ${String(start)} leaves method body`;
|
||||
break;
|
||||
}
|
||||
const code = first === 0xfe ? 0xfe00 + il.readUInt8(offset++) : first;
|
||||
const descriptor = OPCODES.get(code);
|
||||
if (descriptor === undefined) {
|
||||
issue = `Unsupported CIL opcode 0x${code.toString(16)} at IL offset ${String(start)}`;
|
||||
break;
|
||||
}
|
||||
const operand = readOperand(il, offset, descriptor.operand);
|
||||
if (operand.next < offset || operand.next > il.length) {
|
||||
const operand = readOperandWithin(il, offset, descriptor.operand);
|
||||
if (
|
||||
operand === undefined ||
|
||||
operand.next < offset ||
|
||||
operand.next > il.length
|
||||
) {
|
||||
issue = `Instruction ${descriptor.name} at IL offset ${String(start)} leaves method body`;
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue