refactor(managed): unify coded-index identity and validation

Decode metadata coded indexes once into null, valid or invalid facts. Migrate
all existing consumers and remove parallel token and failure-reason decoders.
Keep required-null policy with each column, preserving tokens, reason text,
offsets, issue ordering and readable partial metadata.

Retain known table/row coordinates even when invalid; TypeRef regressions
preserve row-zero and out-of-range enclosing facts without granting validity.
Attribute naming consumes the same validated constructor coordinate.

Validation: metadata/member/native-boundary/signature source suites and public
CLI/MCP recovery workflows passed; static, docs and duplication checks passed.
This commit is contained in:
morluto
2026-10-24 04:08:09 +08:00
parent 09ba16873c
commit ce3c27fe92
8 changed files with 153 additions and 145 deletions
+34
View File
@@ -17,6 +17,8 @@ import { edges } from "./ManagedMemberRows.js";
import { parseUserStrings } from "./ManagedUserStrings.js";
import { signature, type ManagedMethod } from "./ManagedMemberInspectorCore.js";
import { ManagedReaderFailure } from "./ManagedReaderFailure.js";
import { managedTypeReferences } from "./ManagedTypeReferences.js";
import type { ManagedParseIssue } from "../domain/managed/managedArtifact.js";
import { managedMemberInspectionSchema } from "../domain/managed/managedArtifact.js";
import { comparisonLimitations } from "../domain/managed/managedMemberComparisonCoverage.js";
import { buildManagedPeFixture as buildMultiMethodManagedPeFixture } from "../../scripts/fixtures/managed/pe.mjs";
@@ -637,6 +639,38 @@ describe("managed edge target declaring types", () => {
});
describe("managed TypeRef resolution scopes", () => {
it.each([0, 9])(
"retains invalid enclosing TypeRef row %s without granting scope validity",
(enclosingRow) => {
const bytes = buildManagedPeFixture({
typeRefScopeRaw: enclosingRow * 4 + 3,
});
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 issues: ManagedParseIssue[] = [];
const reference = managedTypeReferences(bytes, layout, issues)(1);
expect(reference).toMatchObject({ enclosingRow, scopeValid: false });
expect(issues).toMatchObject([
{
code: "invalid-row",
scope: "metadata.TypeRef:0x01000001",
detail: expect.stringContaining(
enclosingRow === 0
? "coded index selects row zero in table 1"
: "coded index selects row 9 in table 1, which has 1 rows",
),
},
]);
},
);
it.each([
// AssemblyRef 9 does not exist.
["an invalid", (9 << 2) | 2, "is invalid"],
+13 -22
View File
@@ -23,8 +23,7 @@ import type {
} from "./ManagedMetadataLayout.js";
import {
metadataRowCursor,
metadataCodedToken,
metadataCodedTokenInvalidReason,
decodeMetadataCodedIndex,
metadataToken,
readMetadataString,
sha256Bytes,
@@ -242,18 +241,18 @@ export const parseTypes = (
definition.methodStart,
definition.methodEnd,
);
const extendsReason = metadataCodedTokenInvalidReason(
const extendsIndex = decodeMetadataCodedIndex(
extendsRaw,
2,
[2, 1, 27],
layout.rowCounts,
);
if (extendsReason !== null)
if (extendsIndex.kind === "invalid")
issues.push({
code: "invalid-row",
scope: `metadata.TypeDef:${token}`,
offset: extendsOffset,
detail: `TypeDef Extends coded index 0x${extendsRaw.toString(16)} is invalid: ${extendsReason}`,
detail: `TypeDef Extends coded index 0x${extendsRaw.toString(16)} is invalid: ${extendsIndex.reason}`,
});
const enclosingRow = nesting.enclosing.get(row);
items.push({
@@ -263,12 +262,7 @@ export const parseTypes = (
name,
full_name: definition.fullName,
flags,
extends_token: metadataCodedToken(
extendsRaw,
2,
[2, 1, 27],
layout.rowCounts,
),
extends_token: extendsIndex.kind === "valid" ? extendsIndex.token : null,
enclosing_type_token:
enclosingRow === undefined ? null : metadataToken(2, enclosingRow),
name_sha256: null,
@@ -530,20 +524,17 @@ const readTypeOperand = (
): ReturnType<typeof readCompressed> => {
const token = readCompressed(blob, offset);
if (layout !== undefined) {
const reason =
token.value === 0
? "type operand is null"
: metadataCodedTokenInvalidReason(
token.value,
2,
tables,
layout.rowCounts,
);
if (reason !== null)
const decoded = decodeMetadataCodedIndex(
token.value,
2,
tables,
layout.rowCounts,
);
if (decoded.kind !== "valid")
throw managedFailure(
"invalid-row",
"signature",
`TypeDefOrRefOrSpec operand 0x${token.value.toString(16)} is invalid: ${reason}`,
`TypeDefOrRefOrSpec operand 0x${token.value.toString(16)} is invalid: ${decoded.kind === "null" ? "type operand is null" : decoded.reason}`,
offset,
);
}
+12 -22
View File
@@ -25,8 +25,7 @@ import {
import {
metadataToken,
metadataRowCursor,
metadataCodedToken,
metadataCodedTokenInvalidReason,
decodeMetadataCodedIndex,
readMetadataBlob,
readMetadataString,
} from "./ManagedMetadataHeaps.js";
@@ -172,21 +171,21 @@ export const parseExportedTypes = (
layout.codedIndexSize("Implementation"),
);
const token = metadataToken(39, row);
const implementationReason = metadataCodedTokenInvalidReason(
const implementation = decodeMetadataCodedIndex(
implementationRaw,
2,
[38, 35, 39],
layout.rowCounts,
);
if (implementationReason !== null || implementationRaw === 0)
if (implementation.kind !== "valid")
issues.push({
code: "invalid-row",
scope: `metadata.ExportedType:${token}`,
offset: implementationOffset,
detail:
implementationReason === null
implementation.kind === "null"
? "ExportedType implementation coded index 0x0 is null, but the Implementation column must reference a row"
: `ExportedType implementation coded index 0x${implementationRaw.toString(16)} is invalid: ${implementationReason}`,
: `ExportedType implementation coded index 0x${implementationRaw.toString(16)} is invalid: ${implementation.reason}`,
});
exportedTypes.push({
token,
@@ -196,12 +195,8 @@ export const parseExportedTypes = (
flags,
forwarder: (flags & TYPE_FORWARDER_FLAG) !== 0,
type_def_id: typeDefId,
implementation_token: metadataCodedToken(
implementationRaw,
2,
[38, 35, 39],
layout.rowCounts,
),
implementation_token:
implementation.kind === "valid" ? implementation.token : null,
});
}, issues);
}
@@ -241,28 +236,23 @@ export const parseMemberRefs = (
layout.blob?.size ?? 0,
);
const token = metadataToken(10, row);
const parentReason = metadataCodedTokenInvalidReason(
const parent = decodeMetadataCodedIndex(
parentRaw,
3,
[2, 1, 26, 6, 27],
layout.rowCounts,
);
if (parentReason !== null || parentRaw === 0)
if (parent.kind !== "valid")
issues.push({
code: "invalid-row",
scope: `metadata.MemberRef:${token}`,
offset: cursor.start,
detail:
parentReason === null
parent.kind === "null"
? "MemberRef parent coded index 0x0 is null, but the Class column must reference a row"
: `MemberRef parent coded index 0x${parentRaw.toString(16)} is invalid: ${parentReason}`,
: `MemberRef parent coded index 0x${parentRaw.toString(16)} is invalid: ${parent.reason}`,
});
const parentToken = metadataCodedToken(
parentRaw,
3,
[2, 1, 26, 6, 27],
layout.rowCounts,
);
const parentToken = parent.kind === "valid" ? parent.token : null;
const parentName = typeName(parentToken);
const decoded = budgetedSignature(
sig,
+40 -24
View File
@@ -392,37 +392,53 @@ export const metadataRowCursor = (
);
};
/** Decode a metadata coded index without signed 32-bit coercion. */
export const metadataCodedToken = (
raw: number,
bits: number,
tables: readonly (number | undefined)[],
rowCounts: readonly number[],
): string | null => {
if (raw === 0) return null;
const tag = raw & (2 ** bits - 1);
const row = Math.floor(raw / 2 ** bits);
const table = tables[tag];
return table === undefined || row === 0 || row > (rowCounts[table] ?? 0)
? null
: metadataToken(table, row);
};
export type ManagedCodedIndex =
| { readonly kind: "null" }
| {
readonly kind: "valid";
readonly token: string;
readonly table: number;
readonly row: number;
}
| {
readonly kind: "invalid";
readonly reason: string;
readonly table: number | undefined;
readonly row: number;
};
/** Explain why a non-null coded index cannot name an admitted metadata row. */
export const metadataCodedTokenInvalidReason = (
/** Decode and validate a metadata coded index without signed row coercion. */
export const decodeMetadataCodedIndex = (
raw: number,
bits: number,
tables: readonly (number | undefined)[],
rowCounts: readonly number[],
): string | null => {
if (raw === 0) return null;
): ManagedCodedIndex => {
if (raw === 0) return { kind: "null" };
const tag = raw & (2 ** bits - 1);
const row = Math.floor(raw / 2 ** bits);
const table = tables[tag];
if (table === undefined) return `coded index tag ${tag} is not defined`;
if (row === 0) return `coded index selects row zero in table ${table}`;
if (table === undefined)
return {
kind: "invalid",
reason: `coded index tag ${tag} is not defined`,
table,
row,
};
if (row === 0)
return {
kind: "invalid",
reason: `coded index selects row zero in table ${table}`,
table,
row,
};
const count = rowCounts[table] ?? 0;
return row > count
? `coded index selects row ${row} in table ${table}, which has ${count} rows`
: null;
if (row > count)
return {
kind: "invalid",
reason: `coded index selects row ${row} in table ${table}, which has ${count} rows`,
table,
row,
};
return { kind: "valid", token: metadataToken(table, row), table, row };
};
+35 -52
View File
@@ -16,8 +16,8 @@ import {
} from "./ManagedMetadataLayout.js";
import {
metadataRowCursor,
metadataCodedToken,
metadataCodedTokenInvalidReason,
decodeMetadataCodedIndex,
type ManagedCodedIndex,
metadataToken,
readMetadataBlob,
readMetadataGuid,
@@ -404,29 +404,28 @@ const declaringTypeForMethod = (
const attributeTypeName = (
bytes: Buffer,
layout: ManagedMetadataLayout,
rawType: number,
constructor: Extract<ManagedCodedIndex, { readonly kind: "valid" }>,
memberLists: TypeMemberListFacts,
typeName: ManagedMetadataTypeNameLookup,
): string | null => {
const tag = rawType & 7;
const row = Math.floor(rawType / 8);
if (row === 0) return null;
if (tag === 2)
const { table, row } = constructor;
if (table === 6)
return declaringTypeForMethod(bytes, layout, row, memberLists, typeName);
if (tag !== 3) return null;
const memberRefs = layout.table(10);
if (memberRefs === undefined || row > memberRefs.rowCount) return null;
const cursor = metadataRowCursor(bytes, layout, 10, row);
const parent = cursor.readIndex(layout.codedIndexSize("MemberRefParent"));
const parentTag = parent & 7;
const parentRow = Math.floor(parent / 8);
if (parentTag === 0) return typeName(2, parentRow);
if (parentTag === 1) return typeName(1, parentRow);
if (parentTag === 3)
const parent = decodeMetadataCodedIndex(
cursor.readIndex(layout.codedIndexSize("MemberRefParent")),
3,
[2, 1, 26, 6, 27],
layout.rowCounts,
);
if (parent.kind === "null") return null;
if (parent.table === 2 || parent.table === 1)
return typeName(parent.table, parent.row);
if (parent.table === 6)
return declaringTypeForMethod(
bytes,
layout,
parentRow,
parent.row,
memberLists,
typeName,
);
@@ -510,59 +509,48 @@ export const readCustomAttribute = (
cursor.readIndex(layout.blobIndexSize),
heapExtent,
);
const parent = metadataCodedToken(
const parent = decodeMetadataCodedIndex(
parentRaw,
5,
HAS_CUSTOM_ATTRIBUTE_TABLES,
layout.rowCounts,
);
if (parent === null) {
const parentReason = metadataCodedTokenInvalidReason(
parentRaw,
5,
HAS_CUSTOM_ATTRIBUTE_TABLES,
layout.rowCounts,
);
if (parent.kind !== "valid") {
throw managedFailure(
"invalid-row",
`metadata.CustomAttribute:${metadataToken(12, row)}`,
`CustomAttribute parent coded index 0x${parentRaw.toString(16)} is invalid${parentReason === null ? " because the required parent is null" : `: ${parentReason}`}`,
`CustomAttribute parent coded index 0x${parentRaw.toString(16)} is invalid${parent.kind === "null" ? " because the required parent is null" : `: ${parent.reason}`}`,
cursor.start,
);
}
const constructor = metadataCodedToken(
const constructor = decodeMetadataCodedIndex(
typeRaw,
3,
CUSTOM_ATTRIBUTE_TYPE_TABLES,
layout.rowCounts,
);
if (constructor === null) {
const typeReason = metadataCodedTokenInvalidReason(
typeRaw,
3,
CUSTOM_ATTRIBUTE_TYPE_TABLES,
layout.rowCounts,
);
if (constructor.kind !== "valid") {
issues.push({
code: "invalid-row",
scope: `metadata.CustomAttribute:${metadataToken(12, row)}`,
offset: typeOffset,
detail:
typeReason === null
constructor.kind === "null"
? "CustomAttribute constructor coded index 0x0 is null, but Type must reference a MethodDef or MemberRef row"
: `CustomAttribute constructor coded index 0x${typeRaw.toString(16)} is invalid: ${typeReason}`,
: `CustomAttribute constructor coded index 0x${typeRaw.toString(16)} is invalid: ${constructor.reason}`,
});
}
let typeName: string | null = null;
if (constructor !== null)
if (constructor.kind === "valid")
typeName =
readManagedValue(
() => attributeTypeName(bytes, layout, typeRaw, memberLists, typeNames),
() =>
attributeTypeName(bytes, layout, constructor, memberLists, typeNames),
issues,
) ?? null;
return {
parent_token: parent,
constructor_token: constructor,
parent_token: parent.token,
constructor_token: constructor.kind === "valid" ? constructor.token : null,
type_name: typeName,
value_length: value.length,
value_sha256: managedDecodeBudget(layout).digest(value, "sha256"),
@@ -603,28 +591,22 @@ export const readResource = ({
const implementationRaw = cursor.readIndex(
layout.codedIndexSize("Implementation"),
);
const implementationToken = metadataCodedToken(
const implementation = decodeMetadataCodedIndex(
implementationRaw,
2,
[38, 35, 39],
layout.rowCounts,
);
const implementationReason = metadataCodedTokenInvalidReason(
implementationRaw,
2,
[38, 35, 39],
layout.rowCounts,
);
if (implementationReason !== null)
if (implementation.kind === "invalid")
issues.push({
code: "invalid-row",
scope: `metadata.ManifestResource:${metadataToken(40, row)}`,
offset: implementationOffset,
detail: `ManifestResource implementation coded index 0x${implementationRaw.toString(16)} is invalid: ${implementationReason}`,
detail: `ManifestResource implementation coded index 0x${implementationRaw.toString(16)} is invalid: ${implementation.reason}`,
});
let dataLength: number | null = null;
let dataSha256: string | null = null;
if (implementationRaw === 0) {
if (implementation.kind === "null") {
if (directory === null || declaredOffset > directory.size - 4) {
issues.push({
code: "invalid-resource",
@@ -655,8 +637,9 @@ export const readResource = ({
flags,
visibility:
(flags & 7) === 1 ? "public" : (flags & 7) === 2 ? "private" : "unknown",
implementation_token: implementationToken,
embedded: implementationRaw === 0,
implementation_token:
implementation.kind === "valid" ? implementation.token : null,
embedded: implementation.kind === "null",
declared_offset: declaredOffset,
data_length: dataLength,
data_sha256: dataSha256,
+6 -7
View File
@@ -9,8 +9,7 @@ import {
} from "./ManagedMemberInspectorCore.js";
import type { ManagedMetadataLayout } from "./ManagedMetadataLayout.js";
import {
metadataCodedToken,
metadataCodedTokenInvalidReason,
decodeMetadataCodedIndex,
metadataRowCursor,
readMetadataBlob,
} from "./ManagedMetadataHeaps.js";
@@ -84,23 +83,23 @@ const readMethodSpec = (
);
const methodRaw = cursor.readIndex(layout.codedIndexSize("MethodDefOrRef"));
const instantiation = cursor.readIndex(layout.blobIndexSize);
const methodReason = metadataCodedTokenInvalidReason(
const method = decodeMetadataCodedIndex(
methodRaw,
1,
[6, 10],
layout.rowCounts,
);
if (methodReason !== null || methodRaw === 0)
if (method.kind !== "valid")
issues.push({
code: "invalid-row",
scope,
offset: cursor.start,
detail:
methodReason === null
method.kind === "null"
? "MethodSpec method coded index 0x0 is null, but the Method column must reference a row"
: `MethodSpec method coded index 0x${methodRaw.toString(16)} is invalid: ${methodReason}`,
: `MethodSpec method coded index 0x${methodRaw.toString(16)} is invalid: ${method.reason}`,
});
const generic = metadataCodedToken(methodRaw, 1, [6, 10], layout.rowCounts);
const generic = method.kind === "valid" ? method.token : null;
const target = generic === null ? undefined : members.get(generic);
// A MethodSpec instantiates a method, so a field target never lends it a
// name, even to a field operand.
+7 -12
View File
@@ -11,8 +11,7 @@ import {
import type { ManagedMetadataLayout } from "./ManagedMetadataLayout.js";
import {
metadataRowCursor,
metadataCodedToken,
metadataCodedTokenInvalidReason,
decodeMetadataCodedIndex,
metadataToken,
readMetadataString,
} from "./ManagedMetadataHeaps.js";
@@ -169,28 +168,24 @@ export const parseImplMaps = ({
const memberForwardedRaw = cursor.readIndex(
layout.codedIndexSize("MemberForwarded"),
);
const memberForwardedReason = metadataCodedTokenInvalidReason(
const memberForwarded = decodeMetadataCodedIndex(
memberForwardedRaw,
1,
[4, 6],
layout.rowCounts,
);
if (memberForwardedReason !== null || memberForwardedRaw === 0)
if (memberForwarded.kind !== "valid")
issues.push({
code: "invalid-row",
scope: `metadata.ImplMap:${metadataToken(28, row)}`,
offset: memberForwardedOffset,
detail:
memberForwardedReason === null
memberForwarded.kind === "null"
? "ImplMap MemberForwarded coded index 0x0 is null, but the column must reference a Field or MethodDef row"
: `ImplMap MemberForwarded coded index 0x${memberForwardedRaw.toString(16)} is invalid: ${memberForwardedReason}`,
: `ImplMap MemberForwarded coded index 0x${memberForwardedRaw.toString(16)} is invalid: ${memberForwarded.reason}`,
});
const memberToken = metadataCodedToken(
memberForwardedRaw,
1,
[4, 6],
layout.rowCounts,
);
const memberToken =
memberForwarded.kind === "valid" ? memberForwarded.token : null;
const importName = readMetadataString(
bytes,
layout,
+6 -6
View File
@@ -1,7 +1,7 @@
import { managedDecodeBudget } from "./ManagedDecodeBudget.js";
import type { ManagedMetadataLayout } from "./ManagedMetadataLayout.js";
import {
metadataCodedTokenInvalidReason,
decodeMetadataCodedIndex,
metadataRowCursor,
metadataToken,
readMetadataString,
@@ -46,25 +46,25 @@ export const managedTypeReferences = (
readManagedValue(() => {
const cursor = metadataRowCursor(bytes, layout, 1, row);
const raw = cursor.readIndex(layout.codedIndexSize("ResolutionScope"));
const reason = metadataCodedTokenInvalidReason(
const scope = decodeMetadataCodedIndex(
raw,
2,
[0, 26, 35, 1],
layout.rowCounts,
);
if (reason !== null)
if (scope.kind === "invalid")
issues.push({
code: "invalid-row",
scope: `metadata.TypeRef:${metadataToken(1, row)}`,
offset: cursor.start,
detail: `TypeRef ResolutionScope coded index 0x${raw.toString(16)} is invalid: ${reason}`,
detail: `TypeRef ResolutionScope coded index 0x${raw.toString(16)} is invalid: ${scope.reason}`,
});
return {
nameIndex: cursor.readIndex(layout.stringIndexSize),
namespaceIndex: cursor.readIndex(layout.stringIndexSize),
enclosingRow:
raw !== 0 && (raw & 3) === 3 ? Math.floor(raw / 4) : null,
scopeValid: reason === null,
scope.kind !== "null" && scope.table === 1 ? scope.row : null,
scopeValid: scope.kind !== "invalid",
};
}, issues) ?? null;
rows.set(row, value);