mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
refactor(managed): centralize TypeDef row facts across consumers
This commit is contained in:
1 parent
8423c5c6d0
commit
2cdc8cb92e
5 files changed
+173
-44
No files matched your search
@@ -23,7 +23,6 @@ import type {
|
||||
MetadataTableLayout,
|
||||
} from "./ManagedMetadataLayout.js";
|
||||
import {
|
||||
metadataRowCursor,
|
||||
readManagedCompressedUnsigned,
|
||||
decodeMetadataCodedIndex,
|
||||
metadataCodedIndexCoordinate,
|
||||
@@ -34,7 +33,11 @@ import {
|
||||
sha256Bytes,
|
||||
} from "./ManagedMetadataHeaps.js";
|
||||
import { memberListTable } from "./ManagedMemberPointers.js";
|
||||
import type { TypeMemberListFacts } from "./ManagedTypeMemberLists.js";
|
||||
import type {
|
||||
TypeMemberListEntry,
|
||||
TypeMemberListFacts,
|
||||
} from "./ManagedTypeMemberLists.js";
|
||||
import type { TypeDefinitionRowFacts } from "./ManagedTypeDefinitionRows.js";
|
||||
|
||||
export type ManagedType = ManagedMemberInspection["types"][number];
|
||||
/** A renderable TypeDef projection paired with its original source row. */
|
||||
@@ -72,6 +75,7 @@ export interface TypeRange {
|
||||
|
||||
/** One TypeDef row's names and coordinates, including unreadable declarations. */
|
||||
interface TypeDefinition extends TypeRange {
|
||||
readonly source: TypeDefinitionRowFacts;
|
||||
readonly row: number;
|
||||
readonly name: string | undefined;
|
||||
readonly namespace: string | undefined;
|
||||
@@ -173,12 +177,10 @@ const readTypeDefinition = (
|
||||
layout: ManagedMetadataLayout,
|
||||
row: number,
|
||||
issues: ManagedParseIssue[],
|
||||
memberLists: TypeMemberListFacts,
|
||||
current: TypeMemberListEntry,
|
||||
): TypeDefinition => {
|
||||
const cursor = metadataRowCursor(bytes, layout, 2, row);
|
||||
cursor.readUInt32();
|
||||
const nameIndex = cursor.readIndex(layout.stringIndexSize);
|
||||
const namespaceIndex = cursor.readIndex(layout.stringIndexSize);
|
||||
const { source } = current;
|
||||
const { nameIndex, namespaceIndex } = source;
|
||||
// Keep both independent name failures, but decode each heap coordinate once.
|
||||
const namespace = readManagedValue(
|
||||
() =>
|
||||
@@ -189,9 +191,8 @@ const readTypeDefinition = (
|
||||
() => readMetadataString(bytes, layout, nameIndex, layout.strings.size),
|
||||
issues,
|
||||
);
|
||||
cursor.readIndex(layout.codedIndexSize("TypeDefOrRef"));
|
||||
const current = memberLists.entries[row - 1];
|
||||
return {
|
||||
source,
|
||||
row,
|
||||
name,
|
||||
namespace,
|
||||
@@ -200,10 +201,10 @@ const readTypeDefinition = (
|
||||
namespace === undefined || name === undefined
|
||||
? null
|
||||
: fullName(namespace, name),
|
||||
fieldStart: current?.field.start.value ?? null,
|
||||
fieldEnd: current?.field.end ?? null,
|
||||
methodStart: current?.method.start.value ?? null,
|
||||
methodEnd: current?.method.end ?? null,
|
||||
fieldStart: current.field.start.value,
|
||||
fieldEnd: current.field.end,
|
||||
methodStart: current.method.start.value,
|
||||
methodEnd: current.method.end,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -214,11 +215,10 @@ export const readTypeDefinitions = (
|
||||
memberLists: TypeMemberListFacts,
|
||||
): readonly TypeDefinition[] => {
|
||||
appendManagedParseIssues(issues, memberLists.issues);
|
||||
const typeTable = layout.table(2);
|
||||
const definitions: TypeDefinition[] = [];
|
||||
for (let row = 1; row <= (typeTable?.rowCount ?? 0); row += 1)
|
||||
for (const [index, entry] of memberLists.entries.entries())
|
||||
definitions.push(
|
||||
readTypeDefinition(bytes, layout, row, issues, memberLists),
|
||||
readTypeDefinition(bytes, layout, index + 1, issues, entry),
|
||||
);
|
||||
return definitions;
|
||||
};
|
||||
@@ -249,12 +249,7 @@ export const parseTypes = (
|
||||
definition.fullName === null
|
||||
)
|
||||
continue;
|
||||
const cursor = metadataRowCursor(bytes, layout, 2, row);
|
||||
const flags = cursor.readUInt32();
|
||||
cursor.readIndex(layout.stringIndexSize);
|
||||
cursor.readIndex(layout.stringIndexSize);
|
||||
const extendsOffset = cursor.offset;
|
||||
const extendsRaw = cursor.readIndex(layout.codedIndexSize("TypeDefOrRef"));
|
||||
const { flags, extendsRaw, extendsOffset, rowOffset } = definition.source;
|
||||
const fieldRange = rowRange(
|
||||
fields,
|
||||
definition.fieldStart,
|
||||
@@ -279,12 +274,12 @@ export const parseTypes = (
|
||||
detail: `TypeDef Extends coded index 0x${extendsRaw.toString(16)} is invalid: ${extendsIndex.reason}`,
|
||||
});
|
||||
const enclosingRow = nesting.enclosing.get(row);
|
||||
managedDecodeBudget(layout).reserve(128, cursor.start);
|
||||
managedDecodeBudget(layout).reserve(128, rowOffset);
|
||||
items.push({
|
||||
row,
|
||||
type: {
|
||||
token,
|
||||
row_offset: cursor.start,
|
||||
row_offset: rowOffset,
|
||||
namespace,
|
||||
name,
|
||||
full_name: definition.fullName,
|
||||
|
||||
@@ -295,3 +295,84 @@ it("keeps later type identity, member ownership and constructor names at their o
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps independent name failures ahead of conditional Extends diagnostics", () => {
|
||||
const bytes = buildManagedPeFixture({ extendsRaw: 12 });
|
||||
const readable = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
const row = readable.types[0]?.row_offset;
|
||||
if (row === undefined) throw new Error("Expected TypeDef fixture");
|
||||
expect(readable.coverage.issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
code: "invalid-row",
|
||||
scope: "metadata.TypeDef:0x02000001",
|
||||
offset: row + 8,
|
||||
detail: expect.stringContaining("Extends"),
|
||||
}),
|
||||
);
|
||||
bytes.writeUInt16LE(0xffff, row + 4);
|
||||
bytes.writeUInt16LE(0xfffe, row + 6);
|
||||
const partial = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
expect(partial.types).toEqual([]);
|
||||
expect(partial.fields).toHaveLength(1);
|
||||
expect(partial.methods).toHaveLength(1);
|
||||
for (const member of [...partial.fields, ...partial.methods])
|
||||
expect(member).toMatchObject({
|
||||
declaring_type_token: "0x02000001",
|
||||
declaring_type: null,
|
||||
});
|
||||
expect(partial.module).toEqual(readable.module);
|
||||
expect(partial.coverage).toMatchObject({
|
||||
state: "partial",
|
||||
issues: [
|
||||
expect.objectContaining({
|
||||
code: "invalid-heap-index",
|
||||
scope: "metadata.#Strings",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
code: "invalid-heap-index",
|
||||
scope: "metadata.#Strings",
|
||||
}),
|
||||
],
|
||||
});
|
||||
expect(
|
||||
new Set(partial.coverage.issues.map(({ offset }) => offset)).size,
|
||||
).toBe(2);
|
||||
});
|
||||
|
||||
it("reads inventory names on demand without admitting TypeDef member lists or Extends", () => {
|
||||
const bytes = buildManagedPeFixture({
|
||||
additionalEmptyTypes: 1,
|
||||
typeName: "Lookup",
|
||||
});
|
||||
const pe = readManagedPeLayout(bytes);
|
||||
if (pe.cli === null) throw new Error("Expected CLI fixture");
|
||||
const layout = readManagedMetadataLayout(
|
||||
bytes,
|
||||
pe.rvaToOffset(pe.cli.metadata.rva, pe.cli.metadata.size, "cli.metadata"),
|
||||
pe.cli.metadata.size,
|
||||
);
|
||||
expect(layout.stringIndexSize).toBe(2);
|
||||
const earlier = metadataRowOffset(layout, 2, 1);
|
||||
bytes.writeUInt16LE(0xffff, earlier + 4);
|
||||
bytes.writeUInt16LE(0xfffe, earlier + 6);
|
||||
const selected = metadataRowOffset(layout, 2, 2);
|
||||
bytes.writeUInt16LE(0xffff, selected + 8);
|
||||
bytes.writeUInt16LE(0, selected + 10);
|
||||
bytes.writeUInt16LE(0, selected + 12);
|
||||
const issues: ManagedParseIssue[] = [];
|
||||
const names = managedMetadataTypeNames(bytes, layout, issues);
|
||||
expect(names(2, 2)).toBe("Fixture.Lookup");
|
||||
expect(issues).toEqual([]);
|
||||
expect(readTypeMemberListFacts(bytes, layout).issues).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ code: "invalid-row", offset: selected + 10 }),
|
||||
expect.objectContaining({ code: "invalid-row", offset: selected + 12 }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
import { readTypeDefinitionRowFacts } from "./ManagedTypeDefinitionRows.js";
|
||||
import { managedDecodeBudget } from "./ManagedDecodeBudget.js";
|
||||
import { managedTypeReferences } from "./ManagedTypeReferences.js";
|
||||
import { MAX_TYPE_NESTING_DEPTH } from "./ManagedTypeNameDigests.js";
|
||||
@@ -315,18 +316,18 @@ export const managedMetadataTypeNames = (
|
||||
namespace = parsed.namespace;
|
||||
flags = parsed.flags;
|
||||
} else {
|
||||
const cursor = metadataRowCursor(bytes, layout, table, row);
|
||||
flags = cursor.readUInt32();
|
||||
const source = readTypeDefinitionRowFacts(bytes, layout, row);
|
||||
flags = source.flags;
|
||||
name = readMetadataString(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
source.nameIndex,
|
||||
layout.strings.size,
|
||||
);
|
||||
namespace = readMetadataString(
|
||||
bytes,
|
||||
layout,
|
||||
cursor.readIndex(layout.stringIndexSize),
|
||||
source.namespaceIndex,
|
||||
layout.strings.size,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import type { ManagedMetadataLayout } from "./ManagedMetadataLayout.js";
|
||||
import { metadataRowCursor } from "./ManagedMetadataHeaps.js";
|
||||
import { memberListIndexSize } from "./ManagedMemberPointers.js";
|
||||
|
||||
/** Scalar TypeDef source facts; consumers own heap and coded-index admission. */
|
||||
export interface TypeDefinitionRowFacts {
|
||||
readonly rowOffset: number;
|
||||
readonly flags: number;
|
||||
readonly nameIndex: number;
|
||||
readonly namespaceIndex: number;
|
||||
readonly extendsRaw: number;
|
||||
readonly extendsOffset: number;
|
||||
readonly fieldListRaw: number;
|
||||
readonly fieldListOffset: number;
|
||||
readonly methodListRaw: number;
|
||||
readonly methodListOffset: number;
|
||||
}
|
||||
|
||||
export const readTypeDefinitionRowFacts = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
row: number,
|
||||
): TypeDefinitionRowFacts => {
|
||||
const cursor = metadataRowCursor(bytes, layout, 2, row);
|
||||
const flags = cursor.readUInt32();
|
||||
const nameIndex = cursor.readIndex(layout.stringIndexSize);
|
||||
const namespaceIndex = cursor.readIndex(layout.stringIndexSize);
|
||||
const extendsOffset = cursor.offset;
|
||||
const extendsRaw = cursor.readIndex(layout.codedIndexSize("TypeDefOrRef"));
|
||||
const fieldListOffset = cursor.offset;
|
||||
const fieldListRaw = cursor.readIndex(memberListIndexSize(layout, 4));
|
||||
const methodListOffset = cursor.offset;
|
||||
const methodListRaw = cursor.readIndex(memberListIndexSize(layout, 6));
|
||||
return {
|
||||
rowOffset: cursor.start,
|
||||
flags,
|
||||
nameIndex,
|
||||
namespaceIndex,
|
||||
extendsRaw,
|
||||
extendsOffset,
|
||||
fieldListRaw,
|
||||
fieldListOffset,
|
||||
methodListRaw,
|
||||
methodListOffset,
|
||||
};
|
||||
};
|
||||
@@ -1,13 +1,17 @@
|
||||
import type { ManagedParseIssue } from "../domain/managed/managedArtifact.js";
|
||||
import { managedDecodeBudget } from "./ManagedDecodeBudget.js";
|
||||
import type { ManagedMetadataLayout } from "./ManagedMetadataLayout.js";
|
||||
import { metadataRowCursor, metadataToken } from "./ManagedMetadataHeaps.js";
|
||||
import { metadataToken } from "./ManagedMetadataHeaps.js";
|
||||
import {
|
||||
memberListIndexSize,
|
||||
memberListTable,
|
||||
validatedMemberListStart,
|
||||
} from "./ManagedMemberPointers.js";
|
||||
|
||||
import {
|
||||
readTypeDefinitionRowFacts,
|
||||
type TypeDefinitionRowFacts,
|
||||
} from "./ManagedTypeDefinitionRows.js";
|
||||
|
||||
export type MemberDefinitionTable = 4 | 6;
|
||||
|
||||
export interface TypeMemberListStart {
|
||||
@@ -23,6 +27,7 @@ export interface TypeMemberListRange {
|
||||
}
|
||||
|
||||
export interface TypeMemberListEntry {
|
||||
readonly source: TypeDefinitionRowFacts;
|
||||
readonly field: TypeMemberListRange;
|
||||
readonly method: TypeMemberListRange;
|
||||
}
|
||||
@@ -39,33 +44,33 @@ export const readTypeMemberListFacts = (
|
||||
): TypeMemberListFacts => {
|
||||
const fields = memberListTable(layout, 4);
|
||||
const methods = memberListTable(layout, 6);
|
||||
const rows: { field: TypeMemberListStart; method: TypeMemberListStart }[] =
|
||||
[];
|
||||
const rows: {
|
||||
source: TypeDefinitionRowFacts;
|
||||
field: TypeMemberListStart;
|
||||
method: TypeMemberListStart;
|
||||
}[] = [];
|
||||
const issues: ManagedParseIssue[] = [];
|
||||
const types = layout.table(2);
|
||||
const typeCount = types?.rowCount ?? 0;
|
||||
// Peak retained shape includes both the parsed-start and range arrays, their
|
||||
// nested records, plus each eventual JSON object/key projection. Malformed
|
||||
// Peak retained shape includes shared scalar source facts, the parsed-start
|
||||
// and range arrays, plus each eventual JSON object/key projection. Malformed
|
||||
// row diagnostics are admitted separately before they are allocated.
|
||||
managedDecodeBudget(layout).reserve(
|
||||
typeCount * 512 + 256,
|
||||
typeCount * 640 + 256,
|
||||
types?.offset ?? null,
|
||||
);
|
||||
for (let row = 1; row <= typeCount; row += 1) {
|
||||
const cursor = metadataRowCursor(bytes, layout, 2, row);
|
||||
cursor.readUInt32();
|
||||
cursor.readIndex(layout.stringIndexSize);
|
||||
cursor.readIndex(layout.stringIndexSize);
|
||||
cursor.readIndex(layout.codedIndexSize("TypeDefOrRef"));
|
||||
const source = readTypeDefinitionRowFacts(bytes, layout, row);
|
||||
const field = {
|
||||
offset: cursor.offset,
|
||||
raw: cursor.readIndex(memberListIndexSize(layout, 4)),
|
||||
offset: source.fieldListOffset,
|
||||
raw: source.fieldListRaw,
|
||||
};
|
||||
const method = {
|
||||
offset: cursor.offset,
|
||||
raw: cursor.readIndex(memberListIndexSize(layout, 6)),
|
||||
offset: source.methodListOffset,
|
||||
raw: source.methodListRaw,
|
||||
};
|
||||
const entry = {
|
||||
source,
|
||||
field: { ...field, value: validatedMemberListStart(field.raw, fields) },
|
||||
method: {
|
||||
...method,
|
||||
@@ -116,6 +121,7 @@ export const readTypeMemberListFacts = (
|
||||
const entries: TypeMemberListEntry[] = rows.map((entry, index) => {
|
||||
const next = rows[index + 1];
|
||||
return {
|
||||
source: entry.source,
|
||||
field: {
|
||||
start: {
|
||||
...entry.field,
|
||||
|
||||
Reference in new issue
Block a user