From 416f16e2ede8b31ec3bbb8d02efbf8c62074c8aa Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Tue, 27 Oct 2026 02:46:25 +0800 Subject: [PATCH] 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. --- src/dotnet/ManagedFieldDefinitionRows.ts | 25 +++ src/dotnet/ManagedMemberInspector.test.ts | 71 +++++++ src/dotnet/ManagedMemberInspectorCore.ts | 32 ++- src/dotnet/ManagedMemberReferenceRows.ts | 6 +- src/dotnet/ManagedMemberRows.ts | 21 +- src/dotnet/ManagedMetadataInventoryRows.ts | 20 +- src/dotnet/ManagedMetadataLayout.ts | 16 +- src/dotnet/ManagedMethodSpecs.ts | 9 +- src/dotnet/ManagedNativeBoundaryHelpers.ts | 29 +-- .../ManagedNativeBoundaryInspector.test.ts | 182 ++++++++++++++++++ src/dotnet/ManagedTypeReferences.ts | 9 +- 11 files changed, 360 insertions(+), 60 deletions(-) create mode 100644 src/dotnet/ManagedFieldDefinitionRows.ts diff --git a/src/dotnet/ManagedFieldDefinitionRows.ts b/src/dotnet/ManagedFieldDefinitionRows.ts new file mode 100644 index 0000000000..169e9c042e --- /dev/null +++ b/src/dotnet/ManagedFieldDefinitionRows.ts @@ -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), + }; +}; diff --git a/src/dotnet/ManagedMemberInspector.test.ts b/src/dotnet/ManagedMemberInspector.test.ts index 5262170d55..ea2ab11636 100644 --- a/src/dotnet/ManagedMemberInspector.test.ts +++ b/src/dotnet/ManagedMemberInspector.test.ts @@ -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" })] : [], + ); + }, +); diff --git a/src/dotnet/ManagedMemberInspectorCore.ts b/src/dotnet/ManagedMemberInspectorCore.ts index 3df701b7f2..a2851adc45 100644 --- a/src/dotnet/ManagedMemberInspectorCore.ts +++ b/src/dotnet/ManagedMemberInspectorCore.ts @@ -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 = [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 => { 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) diff --git a/src/dotnet/ManagedMemberReferenceRows.ts b/src/dotnet/ManagedMemberReferenceRows.ts index 129c190c7e..5610b70cd8 100644 --- a/src/dotnet/ManagedMemberReferenceRows.ts +++ b/src/dotnet/ManagedMemberReferenceRows.ts @@ -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), diff --git a/src/dotnet/ManagedMemberRows.ts b/src/dotnet/ManagedMemberRows.ts index 8c32b894d0..22ab5fad17 100644 --- a/src/dotnet/ManagedMemberRows.ts +++ b/src/dotnet/ManagedMemberRows.ts @@ -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") diff --git a/src/dotnet/ManagedMetadataInventoryRows.ts b/src/dotnet/ManagedMetadataInventoryRows.ts index bc2e13370a..7c0f06d05c 100644 --- a/src/dotnet/ManagedMetadataInventoryRows.ts +++ b/src/dotnet/ManagedMetadataInventoryRows.ts @@ -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") diff --git a/src/dotnet/ManagedMetadataLayout.ts b/src/dotnet/ManagedMetadataLayout.ts index 148d4f89e9..cf4025b6f4 100644 --- a/src/dotnet/ManagedMetadataLayout.ts +++ b/src/dotnet/ManagedMetadataLayout.ts @@ -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; }; diff --git a/src/dotnet/ManagedMethodSpecs.ts b/src/dotnet/ManagedMethodSpecs.ts index ab71be8e33..8b8705bba3 100644 --- a/src/dotnet/ManagedMethodSpecs.ts +++ b/src/dotnet/ManagedMethodSpecs.ts @@ -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") diff --git a/src/dotnet/ManagedNativeBoundaryHelpers.ts b/src/dotnet/ManagedNativeBoundaryHelpers.ts index ca08b469e1..b38612d6d7 100644 --- a/src/dotnet/ManagedNativeBoundaryHelpers.ts +++ b/src/dotnet/ManagedNativeBoundaryHelpers.ts @@ -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, diff --git a/src/dotnet/ManagedNativeBoundaryInspector.test.ts b/src/dotnet/ManagedNativeBoundaryInspector.test.ts index c5d44abb81..9788ef5eab 100644 --- a/src/dotnet/ManagedNativeBoundaryInspector.test.ts +++ b/src/dotnet/ManagedNativeBoundaryInspector.test.ts @@ -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", + }), + ], + }); +}); diff --git a/src/dotnet/ManagedTypeReferences.ts b/src/dotnet/ManagedTypeReferences.ts index 340a0bc681..fb7648ab4d 100644 --- a/src/dotnet/ManagedTypeReferences.ts +++ b/src/dotnet/ManagedTypeReferences.ts @@ -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")