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);
|
}, 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", () => {
|
describe("managed artifact reference inventory", () => {
|
||||||
it("qualifies local custom-attribute names with their enclosing types", () => {
|
it("qualifies local custom-attribute names with their enclosing types", () => {
|
||||||
const bytes = buildManagedPeFixture({
|
const bytes = buildManagedPeFixture({
|
||||||
|
|||||||
@@ -571,6 +571,56 @@ const genericRef = Buffer.from([0x30, 0x01, 0x00, 0x01]);
|
|||||||
// GENERIC, one generic parameter, no parameters, void.
|
// GENERIC, one generic parameter, no parameters, void.
|
||||||
const genericDef = Buffer.from([0x10, 0x01, 0x00, 0x01]);
|
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", () => {
|
describe("managed TypeSpec declaring names", () => {
|
||||||
it("names a generic TypeSpec parent by its generic type and leaves other TypeSpecs unknown", () => {
|
it("names a generic TypeSpec parent by its generic type and leaves other TypeSpecs unknown", () => {
|
||||||
// GENERICINST CLASS TypeRef(1) <string>
|
// 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", () => {
|
describe("managed MethodDef heap admission", () => {
|
||||||
it("keeps names from unpaged methods without reading their signatures or bodies", () => {
|
it("keeps names from unpaged methods without reading their signatures or bodies", () => {
|
||||||
const bytes = buildMultiMethodManagedPeFixture({
|
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 { readMethodDefinitionRow } from "./ManagedMethodDefinitionRows.js";
|
||||||
import { jsonStringByteLength } from "../domain/jsonStringByteLength.js";
|
import { jsonStringByteLength } from "../domain/jsonStringByteLength.js";
|
||||||
import { isManagedMemberReferenceInstruction } from "../domain/managed/managedMemberInstructionFacts.js";
|
import { isManagedMemberReferenceInstruction } from "../domain/managed/managedMemberInstructionFacts.js";
|
||||||
@@ -234,34 +235,25 @@ export const parseMemberRefs = (
|
|||||||
const table = layout.table(10);
|
const table = layout.table(10);
|
||||||
for (let row = 1; row <= (table?.rowCount ?? 0); row += 1) {
|
for (let row = 1; row <= (table?.rowCount ?? 0); row += 1) {
|
||||||
readManagedValue(() => {
|
readManagedValue(() => {
|
||||||
const cursor = metadataRowCursor(bytes, layout, 10, row);
|
const { token, rowOffset, parentRaw, parent, nameIndex, signatureIndex } =
|
||||||
const parentRaw = cursor.readIndex(
|
readMemberReferenceRowFacts(bytes, layout, row);
|
||||||
layout.codedIndexSize("MemberRefParent"),
|
|
||||||
);
|
|
||||||
const name = readMetadataString(
|
const name = readMetadataString(
|
||||||
bytes,
|
bytes,
|
||||||
layout,
|
layout,
|
||||||
cursor.readIndex(layout.stringIndexSize),
|
nameIndex,
|
||||||
layout.strings.size,
|
layout.strings.size,
|
||||||
);
|
);
|
||||||
const sig = readMetadataBlob(
|
const sig = readMetadataBlob(
|
||||||
bytes,
|
bytes,
|
||||||
layout,
|
layout,
|
||||||
cursor.readIndex(layout.blobIndexSize),
|
signatureIndex,
|
||||||
layout.blob?.size ?? 0,
|
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")
|
if (parent.kind !== "valid")
|
||||||
issues.push({
|
issues.push({
|
||||||
code: "invalid-row",
|
code: "invalid-row",
|
||||||
scope: `metadata.MemberRef:${token}`,
|
scope: `metadata.MemberRef:${token}`,
|
||||||
offset: cursor.start,
|
offset: rowOffset,
|
||||||
detail:
|
detail:
|
||||||
parent.kind === "null"
|
parent.kind === "null"
|
||||||
? "MemberRef parent coded index 0x0 is null, but the Class column must reference a row"
|
? "MemberRef parent coded index 0x0 is null, but the Class column must reference a row"
|
||||||
@@ -278,7 +270,7 @@ export const parseMemberRefs = (
|
|||||||
);
|
);
|
||||||
refs.push({
|
refs.push({
|
||||||
token,
|
token,
|
||||||
row_offset: cursor.start,
|
row_offset: rowOffset,
|
||||||
parent_token: parentToken,
|
parent_token: parentToken,
|
||||||
parent_name: parentName,
|
parent_name: parentName,
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { readMemberReferenceRowFacts } from "./ManagedMemberReferenceRows.js";
|
||||||
import { readTypeDefinitionRowFacts } from "./ManagedTypeDefinitionRows.js";
|
import { readTypeDefinitionRowFacts } from "./ManagedTypeDefinitionRows.js";
|
||||||
import { managedDecodeBudget } from "./ManagedDecodeBudget.js";
|
import { managedDecodeBudget } from "./ManagedDecodeBudget.js";
|
||||||
import { managedTypeReferences } from "./ManagedTypeReferences.js";
|
import { managedTypeReferences } from "./ManagedTypeReferences.js";
|
||||||
@@ -408,13 +409,7 @@ const attributeTypeName = (
|
|||||||
const { table, row } = constructor;
|
const { table, row } = constructor;
|
||||||
if (table === 6)
|
if (table === 6)
|
||||||
return declaringTypeForMethod(bytes, layout, row, memberLists, typeName);
|
return declaringTypeForMethod(bytes, layout, row, memberLists, typeName);
|
||||||
const cursor = metadataRowCursor(bytes, layout, 10, row);
|
const { parent } = readMemberReferenceRowFacts(bytes, layout, row);
|
||||||
const parent = decodeMetadataCodedIndex(
|
|
||||||
cursor.readIndex(layout.codedIndexSize("MemberRefParent")),
|
|
||||||
3,
|
|
||||||
[2, 1, 26, 6, 27],
|
|
||||||
layout.rowCounts,
|
|
||||||
);
|
|
||||||
if (parent.kind === "null") return null;
|
if (parent.kind === "null") return null;
|
||||||
if (parent.table === 2 || parent.table === 1)
|
if (parent.table === 2 || parent.table === 1)
|
||||||
return typeName(parent.table, parent.row);
|
return typeName(parent.table, parent.row);
|
||||||
|
|||||||
@@ -118,6 +118,9 @@ const CODED_INDEXES = {
|
|||||||
TypeOrMethodDef: { bits: 1, tables: [2, 6] },
|
TypeOrMethodDef: { bits: 1, tables: [2, 6] },
|
||||||
} as const;
|
} 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;
|
const SUPPORTED_TABLE_MASK = (1n << BigInt(METADATA_TABLE_NAMES.length)) - 1n;
|
||||||
|
|
||||||
/** Check a range within `bytes`, which start at artifact file offset `base`. */
|
/** 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 { readMethodDefinitionRow } from "./ManagedMethodDefinitionRows.js";
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
import type { ManagedParseIssue } from "../domain/managed/managedArtifact.js";
|
import type { ManagedParseIssue } from "../domain/managed/managedArtifact.js";
|
||||||
@@ -163,10 +164,11 @@ const methodSignatureBlob = (
|
|||||||
if (table === 6) {
|
if (table === 6) {
|
||||||
signatureIndex = readMethodDefinitionRow(bytes, layout, row).signatureIndex;
|
signatureIndex = readMethodDefinitionRow(bytes, layout, row).signatureIndex;
|
||||||
} else {
|
} else {
|
||||||
const cursor = metadataRowCursor(bytes, layout, table, row);
|
signatureIndex = readMemberReferenceRowFacts(
|
||||||
cursor.readIndex(layout.codedIndexSize("MemberRefParent"));
|
bytes,
|
||||||
cursor.readIndex(layout.stringIndexSize);
|
layout,
|
||||||
signatureIndex = cursor.readIndex(layout.blobIndexSize);
|
row,
|
||||||
|
).signatureIndex;
|
||||||
}
|
}
|
||||||
return readMetadataBlob(
|
return readMetadataBlob(
|
||||||
bytes,
|
bytes,
|
||||||
|
|||||||
Reference in new issue
Block a user