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:
RRFZ authored and GitHub committed 2026-10-29 08:23:04 +08:00
1 parent 3a46047aa7
commit 30936d7596
3 files changed
+129 -13

No files matched your search

+96
View File
@@ -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([
+19 -9
View File
@@ -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 (
+14 -4
View File
@@ -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,