mirror of
https://github.com/morluto/rea
synced 2026-10-28 06:46:47 -04:00
fix(managed): unify metadata row and coded-index ownership
Share Field scalar facts and use one coded-index tag registry for widths and decoding. Preserve reserved constructor tags, generic-instantiation restrictions, heap failure order and selected-field signature admission. Derive native import member kind from the validated MemberForwarded coordinate independently of name lookup, retaining known field or method identity when its display name is unreadable. Validation: 368 focused source, CLI and MCP tests passed; five lost-kind regressions reproduced on the previous commit. Heap-width, coded-width, reserved-tag and selection boundaries are covered. Typecheck and lint passed.
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import type { ManagedMetadataLayout } from "./ManagedMetadataLayout.js";
|
||||
import { metadataRowCursor, metadataToken } from "./ManagedMetadataHeaps.js";
|
||||
|
||||
/** Scalar Field facts; consumers own heap admission and field selection. */
|
||||
export const readFieldDefinitionRow = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
row: number,
|
||||
): {
|
||||
readonly token: string;
|
||||
readonly rowOffset: number;
|
||||
readonly flags: number;
|
||||
readonly nameIndex: number;
|
||||
readonly signatureIndex: number;
|
||||
} => {
|
||||
// Layout admission bounds the complete row independently of its heap values.
|
||||
const cursor = metadataRowCursor(bytes, layout, 4, row);
|
||||
return {
|
||||
token: metadataToken(4, row),
|
||||
rowOffset: cursor.start,
|
||||
flags: cursor.readUInt16(),
|
||||
nameIndex: cursor.readIndex(layout.stringIndexSize),
|
||||
signatureIndex: cursor.readIndex(layout.blobIndexSize),
|
||||
};
|
||||
};
|
||||
@@ -2863,3 +2863,74 @@ describe("managed member method paging", () => {
|
||||
).toThrow(RangeError);
|
||||
});
|
||||
});
|
||||
|
||||
it("retains names of unselected fields without admitting their malformed signatures", () => {
|
||||
const bytes = buildManagedNestedTypeFixture({ topLevelTypes: true });
|
||||
const pe = readManagedPeLayout(bytes);
|
||||
if (pe.cli === null) throw new Error("Expected CLI fixture");
|
||||
const { rva, size } = pe.cli.metadata;
|
||||
const layout = readManagedMetadataLayout(
|
||||
bytes,
|
||||
pe.rvaToOffset(rva, size, "cli.metadata"),
|
||||
size,
|
||||
);
|
||||
const field = metadataRowOffset(layout, 4, 1);
|
||||
bytes.writeUIntLE(
|
||||
layout.blobIndexSize === 2 ? 0xffff : 0xffffffff,
|
||||
field + 2 + layout.stringIndexSize,
|
||||
layout.blobIndexSize,
|
||||
);
|
||||
const method = metadataRowOffset(layout, 6, 2);
|
||||
const methodRva = bytes.readUInt32LE(method);
|
||||
const body = tokenBody([[ldsfld, 0x04000001]]);
|
||||
bytes.set(body, pe.rvaToOffset(methodRva, body.length, "fixture CIL"));
|
||||
const result = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
{
|
||||
typeNames: ["OtherInner"],
|
||||
},
|
||||
);
|
||||
expect(result.fields).toMatchObject([{ token: "0x04000002", name: "F" }]);
|
||||
expect(result.methods).toMatchObject([{ token: "0x06000002", name: "M" }]);
|
||||
expect(result.field_accesses).toMatchObject([
|
||||
{
|
||||
field_token: "0x04000001",
|
||||
field_name: "F",
|
||||
field_declaring_type: "Inner",
|
||||
},
|
||||
]);
|
||||
expect(
|
||||
result.coverage.issues.filter(
|
||||
({ scope }) =>
|
||||
scope === "metadata.#Blob" || scope === "Field:0x04000001.signature",
|
||||
),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it.each([false, true])(
|
||||
"keeps ordinary TypeSpec operands distinct from GENERICINST heads (generic=%s)",
|
||||
(generic) => {
|
||||
const bytes = buildManagedPeFixture({
|
||||
typeSpecSignatures: [Buffer.from([0x15, 0x12, 0x05, 0x01, 0x0e])],
|
||||
fieldSignature: Buffer.from(
|
||||
generic ? [0x06, 0x15, 0x12, 0x06, 0x01, 0x0e] : [0x06, 0x12, 0x06],
|
||||
),
|
||||
});
|
||||
const result = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
expect(result.fields[0]?.signature).toMatchObject({
|
||||
kind: "field",
|
||||
parse_status: generic ? "malformed" : "decoded",
|
||||
field_type: generic ? null : "class:6",
|
||||
});
|
||||
const issues = result.coverage.issues.filter(
|
||||
({ scope }) => scope === "Field:0x04000001.signature",
|
||||
);
|
||||
expect(issues).toEqual(
|
||||
generic ? [expect.objectContaining({ code: "invalid-row" })] : [],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -18,9 +18,10 @@ import {
|
||||
readManagedTypeNesting,
|
||||
type ManagedTypeNesting,
|
||||
} from "./ManagedTypeNesting.js";
|
||||
import type {
|
||||
ManagedMetadataLayout,
|
||||
MetadataTableLayout,
|
||||
import {
|
||||
MANAGED_CODED_INDEXES,
|
||||
type ManagedMetadataLayout,
|
||||
type MetadataTableLayout,
|
||||
} from "./ManagedMetadataLayout.js";
|
||||
import {
|
||||
readManagedCompressedUnsigned,
|
||||
@@ -262,8 +263,8 @@ export const parseTypes = (
|
||||
);
|
||||
const extendsIndex = decodeMetadataCodedIndex(
|
||||
extendsRaw,
|
||||
2,
|
||||
[2, 1, 27],
|
||||
MANAGED_CODED_INDEXES.TypeDefOrRef.bits,
|
||||
MANAGED_CODED_INDEXES.TypeDefOrRef.tables,
|
||||
layout.rowCounts,
|
||||
);
|
||||
if (extendsIndex.kind === "invalid")
|
||||
@@ -518,18 +519,22 @@ const POSITION_ELEMENTS: ReadonlyMap<number, readonly SignaturePosition[]> =
|
||||
[0x10, ["return", "parameter", "local", "field"]],
|
||||
]);
|
||||
|
||||
/** GENERICINST names a TypeDef or TypeRef, excluding the family's TypeSpec alternative. */
|
||||
const GENERIC_TYPE_DEFINITION_TABLES: readonly number[] =
|
||||
MANAGED_CODED_INDEXES.TypeDefOrRef.tables.slice(0, 2);
|
||||
|
||||
/** Decode a type operand once; spec callers also validate its metadata coordinate. */
|
||||
const readTypeOperand = (
|
||||
blob: Buffer,
|
||||
offset: number,
|
||||
layout?: ManagedMetadataLayout,
|
||||
tables: readonly number[] = [2, 1, 27],
|
||||
tables: readonly number[] = MANAGED_CODED_INDEXES.TypeDefOrRef.tables,
|
||||
): Omit<TypeOperand, "offset"> => {
|
||||
const token = readCompressed(blob, offset);
|
||||
if (layout !== undefined) {
|
||||
const decoded = decodeMetadataCodedIndex(
|
||||
token.value,
|
||||
2,
|
||||
MANAGED_CODED_INDEXES.TypeDefOrRef.bits,
|
||||
tables,
|
||||
layout.rowCounts,
|
||||
);
|
||||
@@ -542,7 +547,11 @@ const readTypeOperand = (
|
||||
);
|
||||
return { ...token, coordinate: { table: decoded.table, row: decoded.row } };
|
||||
}
|
||||
const { table, row } = metadataCodedIndexCoordinate(token.value, 2, tables);
|
||||
const { table, row } = metadataCodedIndexCoordinate(
|
||||
token.value,
|
||||
MANAGED_CODED_INDEXES.TypeDefOrRef.bits,
|
||||
tables,
|
||||
);
|
||||
return { ...token, coordinate: { table, row } };
|
||||
};
|
||||
|
||||
@@ -695,7 +704,12 @@ const readTypeSignature = (
|
||||
"generic instantiation does not name a class or value type",
|
||||
cursor + 1,
|
||||
);
|
||||
const token = readTypeOperand(blob, cursor + 2, layout, [2, 1]);
|
||||
const token = readTypeOperand(
|
||||
blob,
|
||||
cursor + 2,
|
||||
layout,
|
||||
GENERIC_TYPE_DEFINITION_TABLES,
|
||||
);
|
||||
if (
|
||||
cursor === offset &&
|
||||
(token.coordinate.table === 1 || token.coordinate.table === 2)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
MEMBER_REF_PARENT_CODED_INDEX,
|
||||
MANAGED_CODED_INDEXES,
|
||||
type ManagedMetadataLayout,
|
||||
} from "./ManagedMetadataLayout.js";
|
||||
import {
|
||||
@@ -23,8 +23,8 @@ export const readMemberReferenceRowFacts = (
|
||||
parentRaw,
|
||||
parent: decodeMetadataCodedIndex(
|
||||
parentRaw,
|
||||
MEMBER_REF_PARENT_CODED_INDEX.bits,
|
||||
MEMBER_REF_PARENT_CODED_INDEX.tables,
|
||||
MANAGED_CODED_INDEXES.MemberRefParent.bits,
|
||||
MANAGED_CODED_INDEXES.MemberRefParent.tables,
|
||||
layout.rowCounts,
|
||||
),
|
||||
nameIndex: cursor.readIndex(layout.stringIndexSize),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { readFieldDefinitionRow } from "./ManagedFieldDefinitionRows.js";
|
||||
import { readMemberReferenceRowFacts } from "./ManagedMemberReferenceRows.js";
|
||||
import { readMethodDefinitionRow } from "./ManagedMethodDefinitionRows.js";
|
||||
import { jsonStringByteLength } from "../domain/jsonStringByteLength.js";
|
||||
@@ -9,7 +10,10 @@ import {
|
||||
} from "./ManagedDecodeBudget.js";
|
||||
import type { ManagedPeLayout } from "./ManagedPeReader.js";
|
||||
import type { ManagedParseIssue } from "../domain/managed/managedArtifact.js";
|
||||
import type { ManagedMetadataLayout } from "./ManagedMetadataLayout.js";
|
||||
import {
|
||||
MANAGED_CODED_INDEXES,
|
||||
type ManagedMetadataLayout,
|
||||
} from "./ManagedMetadataLayout.js";
|
||||
import {
|
||||
metadataSignature,
|
||||
memberTargetIdentity,
|
||||
@@ -83,12 +87,12 @@ export const parseFields = (
|
||||
);
|
||||
for (let row = 1; row <= (table?.rowCount ?? 0); row += 1) {
|
||||
readManagedValue(() => {
|
||||
const cursor = metadataRowCursor(bytes, layout, 4, row);
|
||||
const flags = cursor.readUInt16();
|
||||
const { token, rowOffset, flags, nameIndex, signatureIndex } =
|
||||
readFieldDefinitionRow(bytes, layout, row);
|
||||
const name = readMetadataString(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
nameIndex,
|
||||
layout.strings.size,
|
||||
);
|
||||
const owner =
|
||||
@@ -96,7 +100,6 @@ export const parseFields = (
|
||||
? 0
|
||||
: (readManagedValue(() => ownerRow(row), issues) ?? 0);
|
||||
const declared = ranges[owner - 1];
|
||||
const token = metadataToken(4, row);
|
||||
core.set(token, {
|
||||
token,
|
||||
name,
|
||||
@@ -107,7 +110,7 @@ export const parseFields = (
|
||||
const sig = readMetadataBlob(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.blobIndexSize),
|
||||
signatureIndex,
|
||||
layout.blob?.size ?? 0,
|
||||
);
|
||||
const decoded = budgetedSignature(
|
||||
@@ -131,7 +134,7 @@ export const parseFields = (
|
||||
});
|
||||
fields.push({
|
||||
token,
|
||||
row_offset: cursor.start,
|
||||
row_offset: rowOffset,
|
||||
declaring_type_token: declared?.token ?? null,
|
||||
declaring_type: declared?.fullName ?? null,
|
||||
name,
|
||||
@@ -180,8 +183,8 @@ export const parseExportedTypes = (
|
||||
const token = metadataToken(39, row);
|
||||
const implementation = decodeMetadataCodedIndex(
|
||||
implementationRaw,
|
||||
2,
|
||||
[38, 35, 39],
|
||||
MANAGED_CODED_INDEXES.Implementation.bits,
|
||||
MANAGED_CODED_INDEXES.Implementation.tables,
|
||||
layout.rowCounts,
|
||||
);
|
||||
if (implementation.kind !== "valid")
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
ManagedParseIssue,
|
||||
} from "../domain/managed/managedArtifact.js";
|
||||
import {
|
||||
MANAGED_CODED_INDEXES,
|
||||
metadataRowOffset,
|
||||
type ManagedMetadataLayout,
|
||||
} from "./ManagedMetadataLayout.js";
|
||||
@@ -36,13 +37,6 @@ type AssemblyReference = ManagedArtifactInspection["references"][number];
|
||||
type ManagedResource = ManagedArtifactInspection["resources"][number];
|
||||
type CustomAttribute = ManagedArtifactInspection["attributes"][number];
|
||||
|
||||
const HAS_CUSTOM_ATTRIBUTE_TABLES = [
|
||||
6, 4, 1, 2, 8, 9, 10, 0, 14, 23, 20, 17, 26, 27, 32, 35, 38, 39, 40, 42, 44,
|
||||
43,
|
||||
] as const;
|
||||
|
||||
const CUSTOM_ATTRIBUTE_TYPE_TABLES = [undefined, undefined, 6, 10] as const;
|
||||
|
||||
const publicKeyIdentity = (
|
||||
bytes: Buffer,
|
||||
kind: "public-key" | "public-key-token",
|
||||
@@ -475,8 +469,8 @@ export const readCustomAttribute = (
|
||||
);
|
||||
const parent = decodeMetadataCodedIndex(
|
||||
parentRaw,
|
||||
5,
|
||||
HAS_CUSTOM_ATTRIBUTE_TABLES,
|
||||
MANAGED_CODED_INDEXES.HasCustomAttribute.bits,
|
||||
MANAGED_CODED_INDEXES.HasCustomAttribute.tables,
|
||||
layout.rowCounts,
|
||||
);
|
||||
if (parent.kind !== "valid") {
|
||||
@@ -489,8 +483,8 @@ export const readCustomAttribute = (
|
||||
}
|
||||
const constructor = decodeMetadataCodedIndex(
|
||||
typeRaw,
|
||||
3,
|
||||
CUSTOM_ATTRIBUTE_TYPE_TABLES,
|
||||
MANAGED_CODED_INDEXES.CustomAttributeType.bits,
|
||||
MANAGED_CODED_INDEXES.CustomAttributeType.tables,
|
||||
layout.rowCounts,
|
||||
);
|
||||
if (constructor.kind !== "valid") {
|
||||
@@ -557,8 +551,8 @@ export const readResource = ({
|
||||
);
|
||||
const implementation = decodeMetadataCodedIndex(
|
||||
implementationRaw,
|
||||
2,
|
||||
[38, 35, 39],
|
||||
MANAGED_CODED_INDEXES.Implementation.bits,
|
||||
MANAGED_CODED_INDEXES.Implementation.tables,
|
||||
layout.rowCounts,
|
||||
);
|
||||
if (implementation.kind === "invalid")
|
||||
|
||||
@@ -94,9 +94,10 @@ export interface ManagedMetadataLayout {
|
||||
codedIndexSize(name: CodedIndexName): 2 | 4;
|
||||
}
|
||||
|
||||
export type CodedIndexName = keyof typeof CODED_INDEXES;
|
||||
export type CodedIndexName = keyof typeof MANAGED_CODED_INDEXES;
|
||||
|
||||
const CODED_INDEXES = {
|
||||
/** ECMA-335 coded-index tag positions, shared by width and coordinate interpretation. */
|
||||
export const MANAGED_CODED_INDEXES = {
|
||||
TypeDefOrRef: { bits: 2, tables: [2, 1, 27] },
|
||||
HasConstant: { bits: 2, tables: [4, 8, 23] },
|
||||
HasCustomAttribute: {
|
||||
@@ -113,14 +114,11 @@ const CODED_INDEXES = {
|
||||
MethodDefOrRef: { bits: 1, tables: [6, 10] },
|
||||
MemberForwarded: { bits: 1, tables: [4, 6] },
|
||||
Implementation: { bits: 2, tables: [38, 35, 39] },
|
||||
CustomAttributeType: { bits: 3, tables: [6, 10] },
|
||||
CustomAttributeType: { bits: 3, tables: [undefined, undefined, 6, 10] },
|
||||
ResolutionScope: { bits: 2, tables: [0, 26, 35, 1] },
|
||||
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`. */
|
||||
@@ -207,10 +205,12 @@ const codedSize = (
|
||||
name: CodedIndexName,
|
||||
rowCounts: readonly number[],
|
||||
): 2 | 4 => {
|
||||
const descriptor = CODED_INDEXES[name];
|
||||
const descriptor = MANAGED_CODED_INDEXES[name];
|
||||
let maximum = 0;
|
||||
for (const table of descriptor.tables)
|
||||
for (const table of descriptor.tables) {
|
||||
if (table === undefined) continue;
|
||||
maximum = Math.max(maximum, rowCounts[table] ?? 0);
|
||||
}
|
||||
return maximum < 2 ** (16 - descriptor.bits) ? 2 : 4;
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,10 @@ import {
|
||||
type DecodedMethod,
|
||||
type MemberCore,
|
||||
} from "./ManagedMemberInspectorCore.js";
|
||||
import type { ManagedMetadataLayout } from "./ManagedMetadataLayout.js";
|
||||
import {
|
||||
MANAGED_CODED_INDEXES,
|
||||
type ManagedMetadataLayout,
|
||||
} from "./ManagedMetadataLayout.js";
|
||||
import {
|
||||
decodeMetadataCodedIndex,
|
||||
type MetadataTokenFact,
|
||||
@@ -84,8 +87,8 @@ const readMethodSpec = (
|
||||
const instantiation = cursor.readIndex(layout.blobIndexSize);
|
||||
const method = decodeMetadataCodedIndex(
|
||||
methodRaw,
|
||||
1,
|
||||
[6, 10],
|
||||
MANAGED_CODED_INDEXES.MethodDefOrRef.bits,
|
||||
MANAGED_CODED_INDEXES.MethodDefOrRef.tables,
|
||||
layout.rowCounts,
|
||||
);
|
||||
if (method.kind !== "valid")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { readFieldDefinitionRow } from "./ManagedFieldDefinitionRows.js";
|
||||
import {
|
||||
readMethodDefinitionRow,
|
||||
type ManagedMethodImplementationFlags,
|
||||
@@ -12,7 +13,10 @@ import {
|
||||
type ManagedNativeBoundaryInspection,
|
||||
type ManagedParseIssue,
|
||||
} from "../domain/managed/managedArtifact.js";
|
||||
import type { ManagedMetadataLayout } from "./ManagedMetadataLayout.js";
|
||||
import {
|
||||
MANAGED_CODED_INDEXES,
|
||||
type ManagedMetadataLayout,
|
||||
} from "./ManagedMetadataLayout.js";
|
||||
import {
|
||||
metadataRowCursor,
|
||||
decodeMetadataCodedIndex,
|
||||
@@ -92,19 +96,15 @@ export const parseFields = (
|
||||
const table = layout.table(4);
|
||||
for (let row = 1; row <= (table?.rowCount ?? 0); row += 1) {
|
||||
readManagedValue(() => {
|
||||
const cursor = metadataRowCursor(bytes, layout, 4, row);
|
||||
const flags = cursor.readUInt16();
|
||||
const name = readMetadataString(
|
||||
const { token, rowOffset, flags, nameIndex } = readFieldDefinitionRow(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
heapExtent,
|
||||
row,
|
||||
);
|
||||
cursor.readIndex(layout.blobIndexSize);
|
||||
const token = metadataToken(4, row);
|
||||
const name = readMetadataString(bytes, layout, nameIndex, heapExtent);
|
||||
fields.set(token, {
|
||||
token,
|
||||
rowOffset: cursor.start,
|
||||
rowOffset,
|
||||
kind: "field",
|
||||
name,
|
||||
flags,
|
||||
@@ -181,8 +181,8 @@ export const parseImplMaps = ({
|
||||
);
|
||||
const memberForwarded = decodeMetadataCodedIndex(
|
||||
memberForwardedRaw,
|
||||
1,
|
||||
[4, 6],
|
||||
MANAGED_CODED_INDEXES.MemberForwarded.bits,
|
||||
MANAGED_CODED_INDEXES.MemberForwarded.tables,
|
||||
layout.rowCounts,
|
||||
);
|
||||
if (memberForwarded.kind !== "valid")
|
||||
@@ -226,7 +226,12 @@ export const parseImplMaps = ({
|
||||
mapping_flags: mappingFlags,
|
||||
mapping_flags_hex: flagsHex(mappingFlags),
|
||||
member_token: memberToken,
|
||||
member_kind: member?.kind ?? "unknown",
|
||||
member_kind:
|
||||
memberForwarded.kind !== "valid"
|
||||
? "unknown"
|
||||
: memberForwarded.table === 4
|
||||
? "field"
|
||||
: "method",
|
||||
member_name: member?.name ?? null,
|
||||
import_name: importName,
|
||||
import_scope_token: importScopeToken,
|
||||
|
||||
@@ -598,3 +598,185 @@ it.each([
|
||||
expect(members.methods[0]?.body.il_sha256).toEqual(expect.any(String));
|
||||
},
|
||||
);
|
||||
|
||||
it.each(
|
||||
[2, 4].flatMap((stringWidth) =>
|
||||
[2, 4].flatMap((blobWidth) =>
|
||||
[false, true].map((badName) => ({ stringWidth, blobWidth, badName })),
|
||||
),
|
||||
),
|
||||
)(
|
||||
"keeps Field heap admission consumer-specific with string=$stringWidth/blob=$blobWidth/badName=$badName",
|
||||
({ stringWidth, blobWidth, badName }) => {
|
||||
const bytes = buildManagedPeFixture({
|
||||
pinvoke: { memberForwardedRaw: 2 },
|
||||
methodFlags: 0x0016,
|
||||
...(stringWidth === 4 ? { assemblyName: "A".repeat(65536) } : {}),
|
||||
...(blobWidth === 4
|
||||
? { standaloneSignatures: [Buffer.alloc(65536)] }
|
||||
: {}),
|
||||
});
|
||||
const pe = readManagedPeLayout(bytes);
|
||||
if (pe.cli === null) throw new Error("Expected CLI fixture");
|
||||
const { rva, size } = pe.cli.metadata;
|
||||
const layout = readManagedMetadataLayout(
|
||||
bytes,
|
||||
pe.rvaToOffset(rva, size, "cli.metadata"),
|
||||
size,
|
||||
);
|
||||
expect(layout.stringIndexSize).toBe(stringWidth);
|
||||
expect(layout.blobIndexSize).toBe(blobWidth);
|
||||
const row = metadataRowOffset(layout, 4, 1);
|
||||
if (badName)
|
||||
bytes.writeUIntLE(
|
||||
stringWidth === 2 ? 0xffff : 0xffffffff,
|
||||
row + 2,
|
||||
stringWidth,
|
||||
);
|
||||
bytes.writeUIntLE(
|
||||
blobWidth === 2 ? 0xffff : 0xffffffff,
|
||||
row + 2 + stringWidth,
|
||||
blobWidth,
|
||||
);
|
||||
const target = managedPeFixtureTarget(bytes);
|
||||
const members = inspectManagedMembersBytes(bytes, target);
|
||||
const native = inspectManagedNativeBoundariesBytes(bytes, target);
|
||||
expect(members.fields).toEqual([]);
|
||||
expect(
|
||||
members.coverage.issues.filter(
|
||||
({ scope }) =>
|
||||
scope === "metadata.#Strings" || scope === "metadata.#Blob",
|
||||
),
|
||||
).toEqual([
|
||||
expect.objectContaining({
|
||||
code: "invalid-heap-index",
|
||||
scope: badName ? "metadata.#Strings" : "metadata.#Blob",
|
||||
}),
|
||||
]);
|
||||
expect(native.pinvoke_imports).toMatchObject([
|
||||
{
|
||||
member_kind: "field",
|
||||
member_token: "0x04000001",
|
||||
member_name: badName ? null : "counter",
|
||||
},
|
||||
]);
|
||||
expect(
|
||||
native.coverage.issues.filter(
|
||||
({ scope }) =>
|
||||
scope === "metadata.#Strings" || scope === "metadata.#Blob",
|
||||
),
|
||||
).toEqual(
|
||||
badName
|
||||
? [
|
||||
expect.objectContaining({
|
||||
code: "invalid-heap-index",
|
||||
scope: "metadata.#Strings",
|
||||
}),
|
||||
]
|
||||
: [],
|
||||
);
|
||||
if (!badName)
|
||||
expect(members.field_accesses).toContainEqual(
|
||||
expect.objectContaining({
|
||||
field_token: "0x04000001",
|
||||
field_name: "counter",
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([8, 9])(
|
||||
"keeps reserved CustomAttributeType tag %i distinct from a valid constructor",
|
||||
(constructor) => {
|
||||
const value = Buffer.from([1, 0, 0, 0, 0]);
|
||||
const bytes = buildManagedPeFixture({
|
||||
customAttributeTypeRaw: constructor,
|
||||
customAttributeValue: value,
|
||||
});
|
||||
const result = inspectManagedArtifactBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
expect(result.attributes).toMatchObject([
|
||||
{
|
||||
parent_token: "0x20000001",
|
||||
constructor_token: null,
|
||||
type_name: null,
|
||||
value_length: value.length,
|
||||
value_sha256: createHash("sha256").update(value).digest("hex"),
|
||||
},
|
||||
]);
|
||||
expect(result.coverage.issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
code: "invalid-row",
|
||||
scope: "metadata.CustomAttribute:0x0c000001",
|
||||
detail: expect.stringContaining(
|
||||
`coded index tag ${constructor - 8} is not defined`,
|
||||
),
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{ options: { methodRowCount: 8191 }, table: 6, count: 8191, width: 2 },
|
||||
{ options: { methodRowCount: 8192 }, table: 6, count: 8192, width: 4 },
|
||||
{ options: { memberRefRowCount: 8191 }, table: 10, count: 8191, width: 2 },
|
||||
{ options: { memberRefRowCount: 8192 }, table: 10, count: 8192, width: 4 },
|
||||
{ options: { moduleRowCount: 8192 }, table: 0, count: 8192, width: 2 },
|
||||
])(
|
||||
"sizes CustomAttributeType from real target-table $table count $count",
|
||||
({ options, table, count, width }) => {
|
||||
const bytes = buildManagedPeFixture(options);
|
||||
const pe = readManagedPeLayout(bytes);
|
||||
if (pe.cli === null) throw new Error("Expected CLI fixture");
|
||||
const { rva, size } = pe.cli.metadata;
|
||||
const layout = readManagedMetadataLayout(
|
||||
bytes,
|
||||
pe.rvaToOffset(rva, size, "cli.metadata"),
|
||||
size,
|
||||
);
|
||||
expect(layout.table(table)?.rowCount).toBe(count);
|
||||
expect(layout.codedIndexSize("CustomAttributeType")).toBe(width);
|
||||
},
|
||||
);
|
||||
|
||||
it("retains an ImplMap method identity independently of its unreadable name", () => {
|
||||
const bytes = buildManagedPeFixture({ pinvoke: {} });
|
||||
const pe = readManagedPeLayout(bytes);
|
||||
if (pe.cli === null) throw new Error("Expected CLI fixture");
|
||||
const { rva, size } = pe.cli.metadata;
|
||||
const layout = readManagedMetadataLayout(
|
||||
bytes,
|
||||
pe.rvaToOffset(rva, size, "cli.metadata"),
|
||||
size,
|
||||
);
|
||||
const row = metadataRowOffset(layout, 6, 1);
|
||||
bytes.writeUIntLE(
|
||||
layout.stringIndexSize === 2 ? 0xffff : 0xffffffff,
|
||||
row + 8,
|
||||
layout.stringIndexSize,
|
||||
);
|
||||
const result = inspectManagedNativeBoundariesBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
expect(result.pinvoke_imports).toMatchObject([
|
||||
{
|
||||
member_token: "0x06000001",
|
||||
member_kind: "method",
|
||||
member_name: null,
|
||||
import_name: "MessageBoxW",
|
||||
import_scope_name: "user32.dll",
|
||||
},
|
||||
]);
|
||||
expect(result.coverage).toMatchObject({
|
||||
state: "partial",
|
||||
issues: [
|
||||
expect.objectContaining({
|
||||
code: "invalid-heap-index",
|
||||
scope: "metadata.#Strings",
|
||||
}),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,10 @@ import {
|
||||
readMetadataString,
|
||||
} from "./ManagedMetadataHeaps.js";
|
||||
import { readManagedValue } from "./ManagedReaderFailure.js";
|
||||
import { metadataRowOffset } from "./ManagedMetadataLayout.js";
|
||||
import {
|
||||
MANAGED_CODED_INDEXES,
|
||||
metadataRowOffset,
|
||||
} from "./ManagedMetadataLayout.js";
|
||||
import type { ManagedParseIssue } from "../domain/managed/managedArtifact.js";
|
||||
|
||||
export interface ManagedTypeReference {
|
||||
@@ -48,8 +51,8 @@ export const managedTypeReferences = (
|
||||
const raw = cursor.readIndex(layout.codedIndexSize("ResolutionScope"));
|
||||
const scope = decodeMetadataCodedIndex(
|
||||
raw,
|
||||
2,
|
||||
[0, 26, 35, 1],
|
||||
MANAGED_CODED_INDEXES.ResolutionScope.bits,
|
||||
MANAGED_CODED_INDEXES.ResolutionScope.tables,
|
||||
layout.rowCounts,
|
||||
);
|
||||
if (scope.kind === "invalid")
|
||||
|
||||
Reference in New Issue
Block a user