Files
rea/src/dotnet/ManagedTypeReferences.ts
T
morluto ce3c27fe92 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.
2026-10-24 04:18:07 +08:00

141 lines
4.5 KiB
TypeScript

import { managedDecodeBudget } from "./ManagedDecodeBudget.js";
import type { ManagedMetadataLayout } from "./ManagedMetadataLayout.js";
import {
decodeMetadataCodedIndex,
metadataRowCursor,
metadataToken,
readMetadataString,
} from "./ManagedMetadataHeaps.js";
import { readManagedValue } from "./ManagedReaderFailure.js";
import { metadataRowOffset } from "./ManagedMetadataLayout.js";
import type { ManagedParseIssue } from "../domain/managed/managedArtifact.js";
export interface ManagedTypeReference {
readonly namespace: string;
readonly name: string;
readonly enclosingRow: number | null;
readonly scopeValid: boolean;
}
export type ManagedTypeReferenceLookup = (
row: number,
) => ManagedTypeReference | null;
interface TypeReferenceRow {
readonly nameIndex: number;
readonly namespaceIndex: number;
readonly enclosingRow: number | null;
readonly scopeValid: boolean;
}
/** Own each TypeRef source tuple and scope validation once per inspection. */
export const managedTypeReferences = (
bytes: Buffer,
layout: ManagedMetadataLayout,
issues: ManagedParseIssue[],
): ManagedTypeReferenceLookup => {
const rows = new Map<number, TypeReferenceRow | null>();
const names = new Map<number, ManagedTypeReference | null>();
const checked = new Map<number, boolean>();
const read = (row: number): TypeReferenceRow | null => {
const cached = rows.get(row);
if (cached !== undefined) return cached;
// Admit the parsed row, validation entry and bounded walk bookkeeping.
managedDecodeBudget(layout).reserve(384, null);
const value =
readManagedValue(() => {
const cursor = metadataRowCursor(bytes, layout, 1, row);
const raw = cursor.readIndex(layout.codedIndexSize("ResolutionScope"));
const scope = decodeMetadataCodedIndex(
raw,
2,
[0, 26, 35, 1],
layout.rowCounts,
);
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: ${scope.reason}`,
});
return {
nameIndex: cursor.readIndex(layout.stringIndexSize),
namespaceIndex: cursor.readIndex(layout.stringIndexSize),
enclosingRow:
scope.kind !== "null" && scope.table === 1 ? scope.row : null,
scopeValid: scope.kind !== "invalid",
};
}, issues) ?? null;
rows.set(row, value);
return value;
};
const validate = (row: number): boolean => {
const prior = checked.get(row);
if (prior !== undefined) return prior;
const path: number[] = [];
const seen = new Set<number>();
let current: number | null = row;
let valid = true;
while (current !== null) {
const known = checked.get(current);
if (known !== undefined) {
valid = known;
break;
}
if (seen.has(current)) {
issues.push({
code: "invalid-row",
scope: `metadata.TypeRef:${metadataToken(1, current)}`,
offset: metadataRowOffset(layout, 1, current),
detail: "TypeRef ResolutionScope chain contains a cycle",
});
valid = false;
break;
}
seen.add(current);
path.push(current);
const value = read(current);
if (value === null || !value.scopeValid) {
valid = false;
break;
}
current = value.enclosingRow;
}
for (const visited of path) checked.set(visited, valid);
return valid;
};
return (row) => {
const cached = names.get(row);
if (cached !== undefined) return cached;
const value = read(row);
if (value === null) return null;
const scopeValid = validate(row);
// Scope validation loads only scalar row facts. The digest owner's nesting
// cap determines which names are requested, rather than preloading parents.
managedDecodeBudget(layout).reserve(128, null);
const named =
readManagedValue(
() => ({
namespace: readMetadataString(
bytes,
layout,
value.namespaceIndex,
layout.strings.size,
),
name: readMetadataString(
bytes,
layout,
value.nameIndex,
layout.strings.size,
),
enclosingRow: value.enclosingRow,
scopeValid,
}),
issues,
) ?? null;
names.set(row, named);
return named;
};
};