mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
refactor(managed): centralize MemberRef row facts
This commit is contained in:
1 parent
939009f6f9
commit
c09fa01b37
7 files changed
+179
-26
No files matched your search
@@ -583,6 +583,35 @@ describe("managed artifact coded indexes", () => {
|
||||
}, 30_000);
|
||||
});
|
||||
|
||||
it.each([false, true])(
|
||||
"resolves an attribute type without reading malformed constructor heaps (bad name %s)",
|
||||
(badName) => {
|
||||
const bytes = buildManagedPeFixture();
|
||||
const layout = metadataLayoutFor(bytes);
|
||||
const row = layout.table(10)!;
|
||||
const nameColumn = row.offset + layout.codedIndexSize("MemberRefParent");
|
||||
const signatureColumn = nameColumn + layout.stringIndexSize;
|
||||
if (badName) {
|
||||
if (layout.stringIndexSize === 2) bytes.writeUInt16LE(0xffff, nameColumn);
|
||||
else bytes.writeUInt32LE(0xffffffff, nameColumn);
|
||||
}
|
||||
if (layout.blobIndexSize === 2)
|
||||
bytes.writeUInt16LE(0xffff, signatureColumn);
|
||||
else bytes.writeUInt32LE(0xffffffff, signatureColumn);
|
||||
|
||||
const result = inspectManagedArtifactBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
expect(result.attributes[0]).toMatchObject({
|
||||
constructor_token: "0x0a000001",
|
||||
type_name: "System.Runtime.Versioning.TargetFrameworkAttribute",
|
||||
decoded_fixed_string: ".NETCoreApp,Version=v8.0",
|
||||
});
|
||||
expect(result.coverage).toEqual({ state: "complete", issues: [] });
|
||||
},
|
||||
);
|
||||
|
||||
describe("managed artifact reference inventory", () => {
|
||||
it("qualifies local custom-attribute names with their enclosing types", () => {
|
||||
const bytes = buildManagedPeFixture({
|
||||
|
||||
@@ -571,6 +571,56 @@ const genericRef = Buffer.from([0x30, 0x01, 0x00, 0x01]);
|
||||
// GENERIC, one generic parameter, no parameters, void.
|
||||
const genericDef = Buffer.from([0x10, 0x01, 0x00, 0x01]);
|
||||
|
||||
it.each([
|
||||
{ badName: true, parent: 0, expectedHeap: "#Strings" },
|
||||
{ badName: false, parent: 0, expectedHeap: "#Blob" },
|
||||
{ badName: true, parent: 17, expectedHeap: "#Strings" },
|
||||
{ badName: false, parent: 17, expectedHeap: "#Blob" },
|
||||
])(
|
||||
"reports $expectedHeap before invalid MemberRef parent $parent (bad name $badName)",
|
||||
({ badName, parent, expectedHeap }) => {
|
||||
const bytes = buildManagedPeFixture({ memberRefParentRaw: parent });
|
||||
const pe = readManagedPeLayout(bytes);
|
||||
if (pe.cli === null) throw new Error("fixture has no CLI header");
|
||||
const { rva, size } = pe.cli.metadata;
|
||||
const layout = readManagedMetadataLayout(
|
||||
bytes,
|
||||
pe.rvaToOffset(rva, size, "metadata"),
|
||||
size,
|
||||
);
|
||||
const nameColumn =
|
||||
metadataRowOffset(layout, 10, 1) +
|
||||
layout.codedIndexSize("MemberRefParent");
|
||||
if (badName) {
|
||||
if (layout.stringIndexSize === 2) bytes.writeUInt16LE(0xffff, nameColumn);
|
||||
else bytes.writeUInt32LE(0xffffffff, nameColumn);
|
||||
}
|
||||
const signatureColumn = nameColumn + layout.stringIndexSize;
|
||||
if (layout.blobIndexSize === 2)
|
||||
bytes.writeUInt16LE(0xffff, signatureColumn);
|
||||
else bytes.writeUInt32LE(0xffffffff, signatureColumn);
|
||||
const result = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
expect(result.member_refs).toEqual([]);
|
||||
expect(result.coverage.state).toBe("partial");
|
||||
expect(
|
||||
result.coverage.issues.filter(
|
||||
({ scope }) =>
|
||||
scope === "metadata.#Strings" ||
|
||||
scope === "metadata.#Blob" ||
|
||||
scope === "metadata.MemberRef:0x0a000001",
|
||||
),
|
||||
).toEqual([
|
||||
expect.objectContaining({
|
||||
code: "invalid-heap-index",
|
||||
scope: `metadata.${expectedHeap}`,
|
||||
}),
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
describe("managed TypeSpec declaring names", () => {
|
||||
it("names a generic TypeSpec parent by its generic type and leaves other TypeSpecs unknown", () => {
|
||||
// GENERICINST CLASS TypeRef(1) <string>
|
||||
@@ -956,6 +1006,55 @@ describe("managed MethodSpec call targets", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("reads MethodSpec arity from an unnamed MemberRef independently of parent admission", () => {
|
||||
const bytes = buildManagedPeFixture({
|
||||
memberRefSignature: genericRef,
|
||||
memberRefParentRaw: 17,
|
||||
methodSpecMethods: [(1 << 1) | 1],
|
||||
methodSpecInstantiation: Buffer.from([0x0a, 0x01, 0x0e]),
|
||||
ilBody: tokenBody([[call, 0x2b00_0001]]),
|
||||
});
|
||||
const pe = readManagedPeLayout(bytes);
|
||||
if (pe.cli === null) throw new Error("fixture has no CLI header");
|
||||
const { rva, size } = pe.cli.metadata;
|
||||
const layout = readManagedMetadataLayout(
|
||||
bytes,
|
||||
pe.rvaToOffset(rva, size, "metadata"),
|
||||
size,
|
||||
);
|
||||
const nameColumn =
|
||||
metadataRowOffset(layout, 10, 1) + layout.codedIndexSize("MemberRefParent");
|
||||
if (layout.stringIndexSize === 2) bytes.writeUInt16LE(0xffff, nameColumn);
|
||||
else bytes.writeUInt32LE(0xffffffff, nameColumn);
|
||||
const result = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
expect(result.member_refs).toEqual([]);
|
||||
expect(result.call_edges).toMatchObject([
|
||||
{
|
||||
target_token: "0x2b000001",
|
||||
target_kind: "method-spec",
|
||||
target_name: null,
|
||||
target_declaring_type: null,
|
||||
},
|
||||
]);
|
||||
expect(result.coverage).toMatchObject({
|
||||
state: "partial",
|
||||
issues: [
|
||||
expect.objectContaining({
|
||||
code: "invalid-heap-index",
|
||||
scope: "metadata.#Strings",
|
||||
}),
|
||||
],
|
||||
});
|
||||
expect(
|
||||
result.coverage.issues.some(({ scope }) =>
|
||||
scope.startsWith("metadata.MethodSpec:"),
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
describe("managed MethodDef heap admission", () => {
|
||||
it("keeps names from unpaged methods without reading their signatures or bodies", () => {
|
||||
const bytes = buildMultiMethodManagedPeFixture({
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
MEMBER_REF_PARENT_CODED_INDEX,
|
||||
type ManagedMetadataLayout,
|
||||
} from "./ManagedMetadataLayout.js";
|
||||
import {
|
||||
decodeMetadataCodedIndex,
|
||||
metadataRowCursor,
|
||||
metadataToken,
|
||||
} from "./ManagedMetadataHeaps.js";
|
||||
|
||||
/** Scalar MemberRef facts; consumers own heap admission and parent diagnostics. */
|
||||
export const readMemberReferenceRowFacts = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
row: number,
|
||||
) => {
|
||||
// Layout admission bounds the complete row independently of its heap values.
|
||||
const cursor = metadataRowCursor(bytes, layout, 10, row);
|
||||
const parentRaw = cursor.readIndex(layout.codedIndexSize("MemberRefParent"));
|
||||
return {
|
||||
token: metadataToken(10, row),
|
||||
rowOffset: cursor.start,
|
||||
parentRaw,
|
||||
parent: decodeMetadataCodedIndex(
|
||||
parentRaw,
|
||||
MEMBER_REF_PARENT_CODED_INDEX.bits,
|
||||
MEMBER_REF_PARENT_CODED_INDEX.tables,
|
||||
layout.rowCounts,
|
||||
),
|
||||
nameIndex: cursor.readIndex(layout.stringIndexSize),
|
||||
signatureIndex: cursor.readIndex(layout.blobIndexSize),
|
||||
};
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
import { readMemberReferenceRowFacts } from "./ManagedMemberReferenceRows.js";
|
||||
import { readMethodDefinitionRow } from "./ManagedMethodDefinitionRows.js";
|
||||
import { jsonStringByteLength } from "../domain/jsonStringByteLength.js";
|
||||
import { isManagedMemberReferenceInstruction } from "../domain/managed/managedMemberInstructionFacts.js";
|
||||
@@ -234,34 +235,25 @@ export const parseMemberRefs = (
|
||||
const table = layout.table(10);
|
||||
for (let row = 1; row <= (table?.rowCount ?? 0); row += 1) {
|
||||
readManagedValue(() => {
|
||||
const cursor = metadataRowCursor(bytes, layout, 10, row);
|
||||
const parentRaw = cursor.readIndex(
|
||||
layout.codedIndexSize("MemberRefParent"),
|
||||
);
|
||||
const { token, rowOffset, parentRaw, parent, nameIndex, signatureIndex } =
|
||||
readMemberReferenceRowFacts(bytes, layout, row);
|
||||
const name = readMetadataString(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
nameIndex,
|
||||
layout.strings.size,
|
||||
);
|
||||
const sig = readMetadataBlob(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.blobIndexSize),
|
||||
signatureIndex,
|
||||
layout.blob?.size ?? 0,
|
||||
);
|
||||
const token = metadataToken(10, row);
|
||||
const parent = decodeMetadataCodedIndex(
|
||||
parentRaw,
|
||||
3,
|
||||
[2, 1, 26, 6, 27],
|
||||
layout.rowCounts,
|
||||
);
|
||||
if (parent.kind !== "valid")
|
||||
issues.push({
|
||||
code: "invalid-row",
|
||||
scope: `metadata.MemberRef:${token}`,
|
||||
offset: cursor.start,
|
||||
offset: rowOffset,
|
||||
detail:
|
||||
parent.kind === "null"
|
||||
? "MemberRef parent coded index 0x0 is null, but the Class column must reference a row"
|
||||
@@ -278,7 +270,7 @@ export const parseMemberRefs = (
|
||||
);
|
||||
refs.push({
|
||||
token,
|
||||
row_offset: cursor.start,
|
||||
row_offset: rowOffset,
|
||||
parent_token: parentToken,
|
||||
parent_name: parentName,
|
||||
name,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { readMemberReferenceRowFacts } from "./ManagedMemberReferenceRows.js";
|
||||
import { readTypeDefinitionRowFacts } from "./ManagedTypeDefinitionRows.js";
|
||||
import { managedDecodeBudget } from "./ManagedDecodeBudget.js";
|
||||
import { managedTypeReferences } from "./ManagedTypeReferences.js";
|
||||
@@ -408,13 +409,7 @@ const attributeTypeName = (
|
||||
const { table, row } = constructor;
|
||||
if (table === 6)
|
||||
return declaringTypeForMethod(bytes, layout, row, memberLists, typeName);
|
||||
const cursor = metadataRowCursor(bytes, layout, 10, row);
|
||||
const parent = decodeMetadataCodedIndex(
|
||||
cursor.readIndex(layout.codedIndexSize("MemberRefParent")),
|
||||
3,
|
||||
[2, 1, 26, 6, 27],
|
||||
layout.rowCounts,
|
||||
);
|
||||
const { parent } = readMemberReferenceRowFacts(bytes, layout, row);
|
||||
if (parent.kind === "null") return null;
|
||||
if (parent.table === 2 || parent.table === 1)
|
||||
return typeName(parent.table, parent.row);
|
||||
|
||||
@@ -118,6 +118,9 @@ const CODED_INDEXES = {
|
||||
TypeOrMethodDef: { bits: 1, tables: [2, 6] },
|
||||
} as const;
|
||||
|
||||
/** Shared MemberRef parent tag mapping, also used to select its column width. */
|
||||
export const MEMBER_REF_PARENT_CODED_INDEX = CODED_INDEXES.MemberRefParent;
|
||||
|
||||
const SUPPORTED_TABLE_MASK = (1n << BigInt(METADATA_TABLE_NAMES.length)) - 1n;
|
||||
|
||||
/** Check a range within `bytes`, which start at artifact file offset `base`. */
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { readMemberReferenceRowFacts } from "./ManagedMemberReferenceRows.js";
|
||||
import { readMethodDefinitionRow } from "./ManagedMethodDefinitionRows.js";
|
||||
import { createHash } from "node:crypto";
|
||||
import type { ManagedParseIssue } from "../domain/managed/managedArtifact.js";
|
||||
@@ -163,10 +164,11 @@ const methodSignatureBlob = (
|
||||
if (table === 6) {
|
||||
signatureIndex = readMethodDefinitionRow(bytes, layout, row).signatureIndex;
|
||||
} else {
|
||||
const cursor = metadataRowCursor(bytes, layout, table, row);
|
||||
cursor.readIndex(layout.codedIndexSize("MemberRefParent"));
|
||||
cursor.readIndex(layout.stringIndexSize);
|
||||
signatureIndex = cursor.readIndex(layout.blobIndexSize);
|
||||
signatureIndex = readMemberReferenceRowFacts(
|
||||
bytes,
|
||||
layout,
|
||||
row,
|
||||
).signatureIndex;
|
||||
}
|
||||
return readMetadataBlob(
|
||||
bytes,
|
||||
|
||||
Reference in new issue
Block a user