mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
fix(managed): report member signature issues at artifact offsets (#7879)
Field, MethodDef and MemberRef signature issues reported the invalid operand's offset inside its blob, while local signature issues in the same coverage array used artifact file offsets. Translate both through one helper that adds the blob's artifact base. Fixes #7385
This commit is contained in:
3 files changed
+129
-13
No files matched your search
@@ -432,6 +432,102 @@ it.each([
|
||||
},
|
||||
);
|
||||
|
||||
/** Artifact offset of row 1's signature blob, read with the production heap reader. */
|
||||
const memberSignatureBlobOffset = (
|
||||
bytes: Buffer,
|
||||
table: 4 | 6 | 10,
|
||||
): number => {
|
||||
const pe = readManagedPeLayout(bytes);
|
||||
if (pe.cli === null) throw new Error("fixture has no CLI header");
|
||||
const layout = readManagedMetadataLayout(
|
||||
bytes,
|
||||
pe.rvaToOffset(pe.cli.metadata.rva, pe.cli.metadata.size, "metadata"),
|
||||
pe.cli.metadata.size,
|
||||
);
|
||||
const row = metadataRowCursor(bytes, layout, table, 1);
|
||||
// ECMA-335 II.22.15 Field, II.22.26 MethodDef and II.22.25 MemberRef
|
||||
// columns before Name.
|
||||
if (table === 4) row.readUInt16();
|
||||
if (table === 6) {
|
||||
row.readUInt32();
|
||||
row.readUInt16();
|
||||
row.readUInt16();
|
||||
}
|
||||
if (table === 10) row.readIndex(layout.codedIndexSize("MemberRefParent"));
|
||||
row.readIndex(layout.stringIndexSize);
|
||||
const blob = readMetadataBlob(
|
||||
bytes,
|
||||
layout,
|
||||
row.readIndex(layout.blobIndexSize),
|
||||
layout.blob?.size ?? 0,
|
||||
);
|
||||
return blob.byteOffset - bytes.byteOffset;
|
||||
};
|
||||
|
||||
// CLASS (0x12) followed by TypeDefOrRefOrSpec 0x07, whose tag 3 is undefined.
|
||||
const INVALID_CLASS_OPERAND = [0x12, 0x07] as const;
|
||||
|
||||
it.each([
|
||||
{
|
||||
member: "field",
|
||||
table: 4,
|
||||
scope: "Field:0x04000001.signature",
|
||||
signature: {
|
||||
fieldSignature: Buffer.from([0x06, ...INVALID_CLASS_OPERAND]),
|
||||
},
|
||||
operandOffset: 2,
|
||||
},
|
||||
{
|
||||
member: "method parameter",
|
||||
table: 6,
|
||||
scope: "MethodDef:0x06000001.signature",
|
||||
signature: {
|
||||
methodSignature: Buffer.from([
|
||||
0x00,
|
||||
0x01,
|
||||
0x01,
|
||||
...INVALID_CLASS_OPERAND,
|
||||
]),
|
||||
},
|
||||
operandOffset: 4,
|
||||
},
|
||||
{
|
||||
member: "member reference parameter",
|
||||
table: 10,
|
||||
scope: "MemberRef:0x0a000001.signature",
|
||||
signature: {
|
||||
memberRefSignature: Buffer.from([
|
||||
0x20,
|
||||
0x01,
|
||||
0x01,
|
||||
...INVALID_CLASS_OPERAND,
|
||||
]),
|
||||
},
|
||||
operandOffset: 4,
|
||||
},
|
||||
] as const)(
|
||||
"reports an invalid $member signature operand at its artifact offset",
|
||||
({ table, scope, signature, operandOffset }) => {
|
||||
const bytes = buildManagedPeFixture(signature);
|
||||
const blobOffset = memberSignatureBlobOffset(bytes, table);
|
||||
expect(bytes[blobOffset + operandOffset]).toBe(0x07);
|
||||
|
||||
const result = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
|
||||
expect(result.coverage.state).toBe("partial");
|
||||
expect(result.coverage.issues).toContainEqual({
|
||||
code: "invalid-row",
|
||||
scope,
|
||||
offset: blobOffset + operandOffset,
|
||||
detail:
|
||||
"TypeDefOrRefOrSpec operand 0x7 is invalid: coded index tag 3 is not defined",
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe("managed member inspection", () => {
|
||||
it("keeps metadata handles distinct from calls and field accesses", () => {
|
||||
const il = Buffer.from([
|
||||
|
||||
@@ -793,6 +793,20 @@ const readTypeSignature = (
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Signature decoders report blob-relative offsets; coverage issues use
|
||||
* artifact file offsets.
|
||||
*/
|
||||
const artifactSignatureIssue = (
|
||||
issue: ManagedParseIssue,
|
||||
scope: string,
|
||||
blobOffset: number,
|
||||
): ManagedParseIssue => ({
|
||||
...issue,
|
||||
scope,
|
||||
offset: issue.offset === null ? blobOffset : blobOffset + issue.offset,
|
||||
});
|
||||
|
||||
/** Read validated LocalVarSig identity facts without projecting local type identities. */
|
||||
export const readLocalSignatureFacts = (
|
||||
layout: ManagedMetadataLayout,
|
||||
@@ -843,14 +857,7 @@ export const readLocalSignatureFacts = (
|
||||
cause.issue.code !== "unsupported-signature")
|
||||
)
|
||||
throw cause;
|
||||
return err({
|
||||
...cause.issue,
|
||||
scope,
|
||||
offset:
|
||||
cause.issue.offset === null
|
||||
? blobOffset
|
||||
: blobOffset + cause.issue.offset,
|
||||
});
|
||||
return err(artifactSignatureIssue(cause.issue, scope, blobOffset));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1178,6 +1185,7 @@ export const metadataSignature = (
|
||||
layout,
|
||||
issues,
|
||||
scope,
|
||||
blobOffset,
|
||||
expectedKind,
|
||||
}: {
|
||||
readonly rawSha256: string;
|
||||
@@ -1185,6 +1193,8 @@ export const metadataSignature = (
|
||||
readonly layout: ManagedMetadataLayout;
|
||||
readonly issues: ManagedParseIssue[];
|
||||
readonly scope: string;
|
||||
/** Artifact file offset of the blob's first byte. */
|
||||
readonly blobOffset: number;
|
||||
readonly expectedKind?: "field" | "method";
|
||||
},
|
||||
): ManagedSignature => {
|
||||
@@ -1193,7 +1203,7 @@ export const metadataSignature = (
|
||||
blob,
|
||||
layout,
|
||||
operands,
|
||||
(issue) => issues.push(issue),
|
||||
(issue) => issues.push(artifactSignatureIssue(issue, scope, blobOffset)),
|
||||
scope,
|
||||
);
|
||||
if (
|
||||
|
||||
@@ -118,7 +118,7 @@ export const parseFields = (
|
||||
layout,
|
||||
signatureTypeName,
|
||||
issues,
|
||||
{ scope: `Field:${token}.signature`, kind: "field" },
|
||||
{ scope: `Field:${token}.signature`, kind: "field", artifact: bytes },
|
||||
);
|
||||
core.set(token, {
|
||||
token,
|
||||
@@ -269,7 +269,7 @@ export const parseMemberRefs = (
|
||||
layout,
|
||||
signatureTypeName,
|
||||
issues,
|
||||
{ scope: `MemberRef:${token}.signature` },
|
||||
{ scope: `MemberRef:${token}.signature`, artifact: bytes },
|
||||
);
|
||||
refs.push({
|
||||
token,
|
||||
@@ -397,7 +397,11 @@ export const parseMethods = ({
|
||||
layout,
|
||||
signatureTypeName,
|
||||
issues,
|
||||
{ scope: `MethodDef:${token}.signature`, kind: "method" },
|
||||
{
|
||||
scope: `MethodDef:${token}.signature`,
|
||||
kind: "method",
|
||||
artifact: bytes,
|
||||
},
|
||||
);
|
||||
core.set(token, {
|
||||
...member,
|
||||
@@ -565,7 +569,12 @@ const budgetedSignature = (
|
||||
layout: ManagedMetadataLayout,
|
||||
signatureTypeName: SignatureTypeName,
|
||||
issues: ManagedParseIssue[],
|
||||
owner: { readonly scope: string; readonly kind?: "field" | "method" },
|
||||
owner: {
|
||||
readonly scope: string;
|
||||
readonly kind?: "field" | "method";
|
||||
/** The artifact the blob was read from, for artifact issue offsets. */
|
||||
readonly artifact: Buffer;
|
||||
},
|
||||
): ReturnType<typeof metadataSignature> => {
|
||||
const budget = managedDecodeBudget(layout);
|
||||
budget.admitTransient(
|
||||
@@ -580,6 +589,7 @@ const budgetedSignature = (
|
||||
layout,
|
||||
issues,
|
||||
scope: owner.scope,
|
||||
blobOffset: bytes.byteOffset - owner.artifact.byteOffset,
|
||||
...(owner.kind === undefined ? {} : { expectedKind: owner.kind }),
|
||||
}),
|
||||
budget,
|
||||
|
||||
Reference in new issue
Block a user