fix(managed): validate spec blobs and member kinds before naming targets
Address review findings on declaring-type naming: - A generic instantiation must name a TypeDef or TypeRef, so a TypeSpec base is reported as malformed instead of followed. Naming no longer recurses. - TypeSpec and MethodSpec instantiation blobs are decoded with the member-signature grammar. Malformed blobs and out-of-range TypeDef rows are reported against their owning row and make coverage partial. - A MethodSpec reads both columns. An invalid Method coded index is reported; a readable Method keeps its name beside a malformed instantiation. - MemberCore carries its signature kind, so a field operand is named only by a field or field-signature MemberRef. This replaces the token-table guard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8c1cc9055b
commit
ace5cd00be
6 files changed
+261
-69
No files matched your search
@@ -99,10 +99,13 @@ assembly. Older member Evidence without `selection` remains readable.
|
||||
target. TypeDef and TypeRef names use the namespace-qualified `full_name`
|
||||
form, and nested types keep bare names. A generic-instantiation TypeSpec
|
||||
parent names its generic type, such as ``System.Collections.Generic.List`1``.
|
||||
Other TypeSpec forms, ModuleRef and MethodDef parents, and unreadable rows stay
|
||||
Other well-formed TypeSpec forms and ModuleRef or MethodDef parents stay
|
||||
`null`. A `method-spec` call edge reports the name and declaring type of the
|
||||
generic method it instantiates. Older member Evidence without these fields
|
||||
reads them as `null`: unknown.
|
||||
generic method it instantiates. A field access is named only by a field or a
|
||||
field-signature MemberRef. TypeSpec and MethodSpec blobs are decoded with the
|
||||
member-signature grammar; malformed rows and blobs stay `null` and make
|
||||
coverage partial, while a readable MethodSpec method column keeps its name.
|
||||
Older member Evidence without these fields reads them as `null`: unknown.
|
||||
|
||||
## Analysis objective
|
||||
|
||||
|
||||
@@ -212,34 +212,91 @@ describe("managed edge target declaring types", () => {
|
||||
expect(array.coverage).toEqual({ state: "complete", issues: [] });
|
||||
});
|
||||
|
||||
it("keeps self-referential and out-of-range generic TypeSpec parents unknown", () => {
|
||||
const cyclic = memberRefOnTypeSpec(
|
||||
Buffer.from([0x15, 0x12, (1 << 2) | 2, 0x01, 0x0e]),
|
||||
);
|
||||
expect(cyclic.member_refs[0]?.parent_name).toBeNull();
|
||||
it.each([
|
||||
[
|
||||
"names a TypeSpec as its generic type",
|
||||
[0x15, 0x12, (1 << 2) | 2, 0x01, 0x0e],
|
||||
"invalid-blob",
|
||||
"metadata.TypeSpec:0x1b000001",
|
||||
],
|
||||
[
|
||||
"has no type arguments",
|
||||
[0x15, 0x12, (1 << 2) | 1],
|
||||
"invalid-blob",
|
||||
"metadata.TypeSpec:0x1b000001",
|
||||
],
|
||||
[
|
||||
"names a missing TypeRef",
|
||||
[0x15, 0x12, (9 << 2) | 1, 0x01, 0x0e],
|
||||
"invalid-row",
|
||||
"metadata.TypeRef",
|
||||
],
|
||||
[
|
||||
"names a missing TypeDef",
|
||||
[0x15, 0x12, (9 << 2) | 0, 0x01, 0x0e],
|
||||
"invalid-row",
|
||||
"metadata.TypeDef",
|
||||
],
|
||||
])(
|
||||
"reports a generic TypeSpec parent that %s and leaves it unknown",
|
||||
(_case, signature, code, scope) => {
|
||||
const result = memberRefOnTypeSpec(Buffer.from(signature));
|
||||
|
||||
const missing = memberRefOnTypeSpec(
|
||||
Buffer.from([0x15, 0x12, (9 << 2) | 1, 0x01, 0x0e]),
|
||||
);
|
||||
expect(missing.member_refs[0]?.parent_name).toBeNull();
|
||||
expect(missing.coverage).toMatchObject({
|
||||
state: "partial",
|
||||
issues: [{ code: "invalid-row", scope: "metadata.TypeRef" }],
|
||||
expect(result.member_refs[0]?.parent_name).toBeNull();
|
||||
expect(result.coverage).toMatchObject({
|
||||
state: "partial",
|
||||
issues: [{ code, scope }],
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps a readable MethodSpec method beside a malformed instantiation", () => {
|
||||
const result = inspect({
|
||||
methodSpecMethods: [(1 << 1) | 1, (9 << 1) | 1],
|
||||
methodSpecInstantiation: Buffer.from([0x0a, 0x00]),
|
||||
ilBody: tokenBody([
|
||||
[call, 0x2b00_0001],
|
||||
[call, 0x2b00_0002],
|
||||
]),
|
||||
});
|
||||
|
||||
expect(
|
||||
result.call_edges.map(({ target_name, target_declaring_type }) => [
|
||||
target_name,
|
||||
target_declaring_type,
|
||||
]),
|
||||
).toEqual([
|
||||
[".ctor", attribute],
|
||||
[null, null],
|
||||
]);
|
||||
expect(result.coverage.state).toBe("partial");
|
||||
expect(
|
||||
result.coverage.issues.map(({ code, scope }) => [code, scope]),
|
||||
).toEqual([
|
||||
["invalid-blob", "metadata.MethodSpec:0x2b000001"],
|
||||
["invalid-row", "metadata.MethodSpec:0x2b000002"],
|
||||
["invalid-blob", "metadata.MethodSpec:0x2b000002"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("names external field accesses and leaves mismatched operand tables unnamed", () => {
|
||||
it("names field MemberRefs as fields and leaves mismatched member kinds unnamed", () => {
|
||||
const result = inspect({
|
||||
memberRefName: "Empty",
|
||||
// FIELD string
|
||||
memberRefSignature: Buffer.from([0x06, 0x0e]),
|
||||
ilBody: tokenBody([
|
||||
[ldsfld, 0x0a00_0001],
|
||||
[call, 0x0400_0001],
|
||||
]),
|
||||
});
|
||||
const methodRef = inspect({ ilBody: tokenBody([[ldsfld, 0x0a00_0001]]) });
|
||||
|
||||
expect(result.field_accesses).toMatchObject([
|
||||
{ field_name: "Empty", field_declaring_type: attribute },
|
||||
]);
|
||||
expect(methodRef.field_accesses).toMatchObject([
|
||||
{ field_name: null, field_declaring_type: null },
|
||||
]);
|
||||
expect(result.call_edges).toMatchObject([
|
||||
{
|
||||
target_kind: "unknown",
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
ManagedMemberInspection,
|
||||
ManagedParseIssue,
|
||||
} from "../domain/managed/managedArtifact.js";
|
||||
import { managedDecodeBudget } from "./ManagedDecodeBudget.js";
|
||||
import type {
|
||||
ManagedMetadataLayout,
|
||||
MetadataTableLayout,
|
||||
@@ -57,6 +58,8 @@ export interface MemberCore {
|
||||
readonly token: string;
|
||||
readonly name: string;
|
||||
readonly declaringType: string | null;
|
||||
/** Fields and methods by table; member references by decoded signature. */
|
||||
readonly kind: ManagedSignature["kind"];
|
||||
}
|
||||
|
||||
export interface ParsedInstruction {
|
||||
@@ -596,6 +599,61 @@ const callingConvention = (value: number): string => {
|
||||
return flags.length === 0 ? name : `${name} ${flags.join(" ")}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode a whole TypeSpec blob, or a MethodSpec instantiation (GENERICINST,
|
||||
* argument count, Types), with the member-signature grammar. Returns the
|
||||
* rendered types, or null for a well-formed form the grammar does not
|
||||
* support. A malformed blob throws invalid-blob under the owning row.
|
||||
*/
|
||||
export const specSignature = (
|
||||
layout: ManagedMetadataLayout,
|
||||
blob: Buffer,
|
||||
owner: {
|
||||
readonly form: "type" | "instantiation";
|
||||
readonly scope: string;
|
||||
readonly offset: number;
|
||||
},
|
||||
): readonly string[] | null => {
|
||||
const budget = managedDecodeBudget(layout);
|
||||
// Rendering is transient, so charge it as member signatures are charged.
|
||||
budget.admitTransient(blob.length * 256, owner.offset);
|
||||
try {
|
||||
let cursor = 0;
|
||||
let count = 1;
|
||||
if (owner.form === "instantiation") {
|
||||
if (blob[0] !== 0x0a)
|
||||
malformedSignature("MethodSpec instantiation is not GENERICINST", 0);
|
||||
const header = readCompressed(blob, 1);
|
||||
if (header.value === 0)
|
||||
malformedSignature("MethodSpec instantiation has no type arguments", 1);
|
||||
count = header.value;
|
||||
cursor = header.next;
|
||||
}
|
||||
const types: string[] = [];
|
||||
// Each Type consumes at least one byte, so a false count fails on truncation.
|
||||
for (let index = 0; index < count; index += 1) {
|
||||
const type = readTypeSignature(blob, cursor, "type");
|
||||
types.push(type.value);
|
||||
cursor = type.next;
|
||||
}
|
||||
if (cursor !== blob.length)
|
||||
malformedSignature("Trailing signature data", cursor);
|
||||
return types;
|
||||
} catch (cause: unknown) {
|
||||
if (!(cause instanceof ManagedReaderFailure)) throw cause;
|
||||
if (cause.issue.code === "unsupported-signature") return null;
|
||||
if (cause.issue.code !== "invalid-blob") throw cause;
|
||||
throw managedFailure(
|
||||
"invalid-blob",
|
||||
owner.scope,
|
||||
cause.issue.detail,
|
||||
owner.offset,
|
||||
);
|
||||
} finally {
|
||||
budget.releaseTransient();
|
||||
}
|
||||
};
|
||||
|
||||
export const signature = (
|
||||
blob: Buffer,
|
||||
rawSha256?: string,
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { ManagedParseIssue } from "../domain/managed/managedArtifact.js";
|
||||
import type { ManagedMetadataLayout } from "./ManagedMetadataLayout.js";
|
||||
import {
|
||||
signature,
|
||||
specSignature,
|
||||
type ManagedCallEdge,
|
||||
type ManagedExportedType,
|
||||
type ManagedField,
|
||||
@@ -80,6 +81,7 @@ export const parseFields = (
|
||||
token,
|
||||
name,
|
||||
declaringType: declared?.fullName ?? null,
|
||||
kind: "field",
|
||||
});
|
||||
if (!isSelected(selected, declared)) return;
|
||||
const sig = readMetadataBlob(
|
||||
@@ -228,15 +230,21 @@ export const parseMemberRefs = (
|
||||
layout.rowCounts,
|
||||
);
|
||||
const parentName = typeName(parentToken);
|
||||
const decoded = budgetedSignature(sig, layout);
|
||||
refs.push({
|
||||
token,
|
||||
row_offset: cursor.start,
|
||||
parent_token: parentToken,
|
||||
parent_name: parentName,
|
||||
name,
|
||||
signature: budgetedSignature(sig, layout),
|
||||
signature: decoded,
|
||||
});
|
||||
core.set(token, {
|
||||
token,
|
||||
name,
|
||||
declaringType: parentName,
|
||||
kind: decoded.kind,
|
||||
});
|
||||
core.set(token, { token, name, declaringType: parentName });
|
||||
}, issues);
|
||||
}
|
||||
return { refs, core, issues };
|
||||
@@ -291,6 +299,7 @@ export const parseMethods = ({
|
||||
token,
|
||||
name,
|
||||
declaringType: declared?.fullName ?? null,
|
||||
kind: "method",
|
||||
});
|
||||
if (!isSelected(selected, declared)) return;
|
||||
const sig = readMetadataBlob(
|
||||
@@ -339,34 +348,66 @@ export const edgeTargets = (
|
||||
} => {
|
||||
const members = new Map(cores.flatMap((core) => [...core]));
|
||||
const issues: ManagedParseIssue[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const method of methods)
|
||||
for (const { operand } of method.body.anchors) {
|
||||
if (operand?.startsWith("0x2b") !== true || members.has(operand))
|
||||
continue;
|
||||
if (operand?.startsWith("0x2b") !== true || seen.has(operand)) continue;
|
||||
seen.add(operand);
|
||||
readManagedValue(() => {
|
||||
const scope = `metadata.MethodSpec:${operand}`;
|
||||
const cursor = metadataRowCursor(
|
||||
bytes,
|
||||
layout,
|
||||
0x2b,
|
||||
Number.parseInt(operand.slice(4), 16),
|
||||
);
|
||||
const methodRaw = cursor.readIndex(
|
||||
layout.codedIndexSize("MethodDefOrRef"),
|
||||
);
|
||||
const instantiation = cursor.readIndex(layout.blobIndexSize);
|
||||
const methodReason = metadataCodedTokenInvalidReason(
|
||||
methodRaw,
|
||||
1,
|
||||
[6, 10],
|
||||
layout.rowCounts,
|
||||
);
|
||||
if (methodReason !== null || methodRaw === 0)
|
||||
issues.push({
|
||||
code: "invalid-row",
|
||||
scope,
|
||||
offset: cursor.start,
|
||||
detail:
|
||||
methodReason === 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}`,
|
||||
});
|
||||
const generic = metadataCodedToken(
|
||||
cursor.readIndex(layout.codedIndexSize("MethodDefOrRef")),
|
||||
methodRaw,
|
||||
1,
|
||||
[6, 10],
|
||||
layout.rowCounts,
|
||||
);
|
||||
const target = generic === null ? undefined : members.get(generic);
|
||||
// A readable Method column stays a fact even if the instantiation is not.
|
||||
if (target !== undefined)
|
||||
members.set(operand, { ...target, token: operand });
|
||||
specSignature(
|
||||
layout,
|
||||
readMetadataBlob(bytes, layout, instantiation, layout.blob.size),
|
||||
{ form: "instantiation", scope, offset: cursor.start },
|
||||
);
|
||||
}, issues);
|
||||
}
|
||||
return { members, issues };
|
||||
};
|
||||
|
||||
const TARGET_TABLES = new Map<string, ReadonlySet<string>>([
|
||||
["method", new Set(["0x06", "0x0a", "0x2b"])],
|
||||
["field", new Set(["0x04", "0x0a"])],
|
||||
/**
|
||||
* Member kinds each operand kind may name. A MemberRef whose signature does
|
||||
* not decode keeps its name on calls, as before, but is never named a field.
|
||||
*/
|
||||
const NAMEABLE_KINDS = new Map<string, ReadonlySet<MemberCore["kind"]>>([
|
||||
["method", new Set(["method", "unknown"])],
|
||||
["field", new Set(["field"])],
|
||||
]);
|
||||
|
||||
const targetKind = (token: string): ManagedCallEdge["target_kind"] =>
|
||||
@@ -378,6 +419,19 @@ const targetKind = (token: string): ManagedCallEdge["target_kind"] =>
|
||||
? "method-spec"
|
||||
: "unknown";
|
||||
|
||||
/** The member an operand names, if the operand kind can name that member's kind. */
|
||||
const namedTarget = (
|
||||
members: ReadonlyMap<string, MemberCore>,
|
||||
operandKind: string,
|
||||
operand: string,
|
||||
): MemberCore | undefined => {
|
||||
const member = members.get(operand);
|
||||
return member !== undefined &&
|
||||
NAMEABLE_KINDS.get(operandKind)?.has(member.kind) === true
|
||||
? member
|
||||
: undefined;
|
||||
};
|
||||
|
||||
export const edges = (
|
||||
methods: readonly ManagedMethod[],
|
||||
members: ReadonlyMap<string, MemberCore>,
|
||||
@@ -402,12 +456,7 @@ export const edges = (
|
||||
if (anchor.opcode === "ldtoken") continue;
|
||||
const { operand } = anchor;
|
||||
if (operand === null) continue;
|
||||
// A token from a table the operand kind cannot name stays unnamed.
|
||||
const target = TARGET_TABLES.get(anchor.operand_kind)?.has(
|
||||
operand.slice(0, 4),
|
||||
)
|
||||
? members.get(operand)
|
||||
: undefined;
|
||||
const target = namedTarget(members, anchor.operand_kind, operand);
|
||||
if (anchor.operand_kind === "method") {
|
||||
// Each derived record is admitted before the graph grows further.
|
||||
callEdges.push(
|
||||
|
||||
@@ -30,8 +30,11 @@ interface ManagedPeFixtureOptions {
|
||||
readonly assemblyRowCount?: number;
|
||||
readonly memberRefParentRaw?: number;
|
||||
readonly memberRefName?: string;
|
||||
readonly memberRefSignature?: Buffer;
|
||||
/** Raw MethodDefOrRef coded values, one MethodSpec row each. */
|
||||
readonly methodSpecMethods?: readonly number[];
|
||||
/** Instantiation blob shared by every MethodSpec row. */
|
||||
readonly methodSpecInstantiation?: Buffer;
|
||||
/** TypeSpec signature blobs, one TypeSpec row each. */
|
||||
readonly typeSpecSignatures?: readonly Buffer[];
|
||||
readonly mvid?: Buffer;
|
||||
@@ -836,22 +839,27 @@ const fixtureMetadataRows = ({
|
||||
return rows;
|
||||
};
|
||||
|
||||
/** Blob indexes for optional TypeSpec and MethodSpec rows; defaults leave the heap unchanged. */
|
||||
const specBlobIndexes = (
|
||||
/** Blob indexes for the MemberRef and optional spec rows; defaults leave the heap unchanged. */
|
||||
const referenceBlobIndexes = (
|
||||
blobs: BlobHeap,
|
||||
options: ManagedPeFixtureOptions,
|
||||
): Pick<
|
||||
FixtureMetadataRowContext["indexes"],
|
||||
"typeSpecSignatures" | "methodSpecInstantiation"
|
||||
"constructorSignature" | "typeSpecSignatures" | "methodSpecInstantiation"
|
||||
> => ({
|
||||
constructorSignature: blobs.add(
|
||||
options.memberRefSignature ?? Buffer.from([0x20, 0x01, 0x01, 0x0e]),
|
||||
),
|
||||
typeSpecSignatures: (options.typeSpecSignatures ?? []).map((signature) =>
|
||||
blobs.add(signature),
|
||||
),
|
||||
// GENERICINST with one string type argument.
|
||||
// Defaults to GENERICINST with one string type argument.
|
||||
methodSpecInstantiation:
|
||||
options.methodSpecMethods === undefined
|
||||
? 0
|
||||
: blobs.add(Buffer.from([0x0a, 0x01, 0x0e])),
|
||||
: blobs.add(
|
||||
options.methodSpecInstantiation ?? Buffer.from([0x0a, 0x01, 0x0e]),
|
||||
),
|
||||
});
|
||||
|
||||
const buildManagedFixtureMetadata = (
|
||||
@@ -899,8 +907,7 @@ const buildManagedFixtureMetadata = (
|
||||
const methodSignature = blobs.add(
|
||||
options.methodSignature ?? Buffer.from([0x00, 0x00, 0x01]),
|
||||
);
|
||||
const constructorSignature = blobs.add(Buffer.from([0x20, 0x01, 0x01, 0x0e]));
|
||||
const specBlobs = specBlobIndexes(blobs, options);
|
||||
const referenceBlobs = referenceBlobIndexes(blobs, options);
|
||||
const attributeBlob = blobs.add(
|
||||
fixedStringAttributeBlob(
|
||||
options.targetFramework === null
|
||||
@@ -933,9 +940,8 @@ const buildManagedFixtureMetadata = (
|
||||
tokenBlob,
|
||||
fieldSignature,
|
||||
methodSignature,
|
||||
constructorSignature,
|
||||
attributeBlob,
|
||||
...specBlobs,
|
||||
...referenceBlobs,
|
||||
},
|
||||
heapSizes: { strings: stringSize, blobs: blobSize, guids: guidSize },
|
||||
});
|
||||
|
||||
@@ -2,21 +2,26 @@ import type { ManagedParseIssue } from "../domain/managed/managedArtifact.js";
|
||||
import {
|
||||
fullName,
|
||||
readCompressed,
|
||||
specSignature,
|
||||
type TypeRange,
|
||||
} from "./ManagedMemberInspectorCore.js";
|
||||
import type { ManagedMetadataLayout } from "./ManagedMetadataLayout.js";
|
||||
import {
|
||||
metadataRowOffset,
|
||||
type ManagedMetadataLayout,
|
||||
} from "./ManagedMetadataLayout.js";
|
||||
import {
|
||||
metadataRowCursor,
|
||||
metadataToken,
|
||||
readMetadataBlob,
|
||||
readMetadataString,
|
||||
} from "./ManagedMetadataHeaps.js";
|
||||
import { readManagedValue } from "./ManagedReaderFailure.js";
|
||||
import { managedFailure, readManagedValue } from "./ManagedReaderFailure.js";
|
||||
|
||||
/**
|
||||
* Name the type a TypeDef, TypeRef, or generic-instantiation TypeSpec token
|
||||
* denotes, in the namespace-qualified form TypeDef `full_name` uses. Other
|
||||
* TypeSpec forms and unreadable rows stay null: the type is unknown.
|
||||
* denotes, in the namespace-qualified form TypeDef `full_name` uses, once per
|
||||
* token. Other well-formed TypeSpecs stay null: the type is unknown. Malformed
|
||||
* rows and blobs are reported to `issues` and also stay null.
|
||||
*/
|
||||
export const typeNameLookup = (
|
||||
bytes: Buffer,
|
||||
@@ -25,19 +30,21 @@ export const typeNameLookup = (
|
||||
issues: ManagedParseIssue[],
|
||||
): ((token: string | null) => string | null) => {
|
||||
const names = new Map<string, string | null>();
|
||||
const resolve = (table: number, row: number): string | null => {
|
||||
if (table === 2) return ranges[row - 1]?.fullName ?? null;
|
||||
if (table === 1) {
|
||||
const cursor = metadataRowCursor(bytes, layout, 1, row);
|
||||
cursor.readIndex(layout.codedIndexSize("ResolutionScope"));
|
||||
const name = cursor.readIndex(layout.stringIndexSize);
|
||||
const namespace = cursor.readIndex(layout.stringIndexSize);
|
||||
return fullName(
|
||||
readMetadataString(bytes, layout, namespace, layout.strings.size),
|
||||
readMetadataString(bytes, layout, name, layout.strings.size),
|
||||
);
|
||||
const definitionName = (table: 1 | 2, row: number): string | null => {
|
||||
if (table === 2) {
|
||||
metadataRowOffset(layout, 2, row);
|
||||
return ranges[row - 1]?.fullName ?? null;
|
||||
}
|
||||
if (table !== 27) return null;
|
||||
const cursor = metadataRowCursor(bytes, layout, 1, row);
|
||||
cursor.readIndex(layout.codedIndexSize("ResolutionScope"));
|
||||
const name = cursor.readIndex(layout.stringIndexSize);
|
||||
const namespace = cursor.readIndex(layout.stringIndexSize);
|
||||
return fullName(
|
||||
readMetadataString(bytes, layout, namespace, layout.strings.size),
|
||||
readMetadataString(bytes, layout, name, layout.strings.size),
|
||||
);
|
||||
};
|
||||
const specName = (row: number): string | null => {
|
||||
const cursor = metadataRowCursor(bytes, layout, 27, row);
|
||||
const spec = readMetadataBlob(
|
||||
bytes,
|
||||
@@ -45,30 +52,42 @@ export const typeNameLookup = (
|
||||
cursor.readIndex(layout.blobIndexSize),
|
||||
layout.blob.size,
|
||||
);
|
||||
// GENERICINST (CLASS | VALUETYPE) TypeDefOrRefOrSpecEncoded ...
|
||||
if (spec[0] !== 0x15 || (spec[1] !== 0x12 && spec[1] !== 0x11)) return null;
|
||||
const owner = {
|
||||
form: "type",
|
||||
scope: `metadata.TypeSpec:${metadataToken(27, row)}`,
|
||||
offset: cursor.start,
|
||||
} as const;
|
||||
if (specSignature(layout, spec, owner) === null || spec[0] !== 0x15)
|
||||
return null;
|
||||
// GENERICINST (CLASS | VALUETYPE) TypeDefOrRefEncoded: never a TypeSpec,
|
||||
// so naming never recurses.
|
||||
const encoded = readCompressed(spec, 2).value;
|
||||
const generic = [2, 1, 27][encoded & 3];
|
||||
return generic === undefined
|
||||
? null
|
||||
: lookup(metadataToken(generic, Math.floor(encoded / 4)));
|
||||
const table = ([2, 1] as const)[encoded & 3];
|
||||
if (table === undefined)
|
||||
throw managedFailure(
|
||||
"invalid-blob",
|
||||
owner.scope,
|
||||
"Generic instantiation does not name a TypeDef or TypeRef",
|
||||
owner.offset,
|
||||
);
|
||||
return definitionName(table, Math.floor(encoded / 4));
|
||||
};
|
||||
const lookup = (token: string | null): string | null => {
|
||||
return (token) => {
|
||||
if (token === null) return null;
|
||||
if (names.has(token)) return names.get(token) ?? null;
|
||||
// Reserve the entry first so a self-referential TypeSpec ends as unknown.
|
||||
names.set(token, null);
|
||||
const table = Number.parseInt(token.slice(2, 4), 16);
|
||||
const row = Number.parseInt(token.slice(4), 16);
|
||||
const name =
|
||||
readManagedValue(
|
||||
() =>
|
||||
resolve(
|
||||
Number.parseInt(token.slice(2, 4), 16),
|
||||
Number.parseInt(token.slice(4), 16),
|
||||
),
|
||||
table === 27
|
||||
? specName(row)
|
||||
: table === 1 || table === 2
|
||||
? definitionName(table, row)
|
||||
: null,
|
||||
issues,
|
||||
) ?? null;
|
||||
names.set(token, name);
|
||||
return name;
|
||||
};
|
||||
return lookup;
|
||||
};
|
||||
Reference in new issue
Block a user