mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
refactor(managed): retain TypeDef row ownership through projections
This commit is contained in:
@@ -209,16 +209,12 @@ export const inspectManagedMembersBytes = (
|
||||
const { operand: signatureTypeName, declaration: declarationTypeName } =
|
||||
managedTypeNameDigests(layout, issues, parsedTypes, typeReferences);
|
||||
const types = parsedTypes.types
|
||||
.filter((type) => selected === null || selected.has(type.token))
|
||||
.map((type) => {
|
||||
.filter(({ type }) => selected === null || selected.has(type.token))
|
||||
.map(({ row, type }) => {
|
||||
managedDecodeBudget(layout).reserve(512, type.row_offset);
|
||||
return {
|
||||
...type,
|
||||
name_sha256:
|
||||
declarationTypeName(
|
||||
2,
|
||||
Number.parseInt(type.token.slice(4), 16),
|
||||
)?.toString("hex") ?? null,
|
||||
name_sha256: declarationTypeName(2, row)?.toString("hex") ?? null,
|
||||
};
|
||||
});
|
||||
const envelope = managedPeInspectionEnvelope(
|
||||
|
||||
@@ -36,6 +36,12 @@ import { memberListTable } from "./ManagedMemberPointers.js";
|
||||
import type { TypeMemberListFacts } from "./ManagedTypeMemberLists.js";
|
||||
|
||||
export type ManagedType = ManagedMemberInspection["types"][number];
|
||||
/** A renderable TypeDef projection paired with its original source row. */
|
||||
export interface DecodedTypeDefinition {
|
||||
readonly row: number;
|
||||
readonly type: ManagedType;
|
||||
}
|
||||
|
||||
export type ManagedExportedType = NonNullable<
|
||||
ManagedMemberInspection["exported_types"]
|
||||
>[number];
|
||||
@@ -225,13 +231,13 @@ export const parseTypes = (
|
||||
layout: ManagedMetadataLayout,
|
||||
definitions: readonly TypeDefinition[],
|
||||
): {
|
||||
readonly types: readonly ManagedType[];
|
||||
readonly types: readonly DecodedTypeDefinition[];
|
||||
readonly issues: readonly ManagedParseIssue[];
|
||||
readonly nesting: ManagedTypeNesting;
|
||||
} => {
|
||||
const fields = memberListTable(layout, 4);
|
||||
const methods = memberListTable(layout, 6);
|
||||
const items: ManagedType[] = [];
|
||||
const items: DecodedTypeDefinition[] = [];
|
||||
const issues: ManagedParseIssue[] = [];
|
||||
const nesting = readManagedTypeNesting(bytes, layout, issues);
|
||||
for (const definition of definitions) {
|
||||
@@ -272,26 +278,31 @@ 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);
|
||||
items.push({
|
||||
token,
|
||||
row_offset: cursor.start,
|
||||
namespace,
|
||||
name,
|
||||
full_name: definition.fullName,
|
||||
flags,
|
||||
extends_token: extendsIndex.kind === "valid" ? extendsIndex.token : null,
|
||||
enclosing_type_token:
|
||||
enclosingRow === undefined ? null : metadataToken(2, enclosingRow),
|
||||
name_sha256: null,
|
||||
field_list: {
|
||||
first_row: fieldRange.first,
|
||||
last_row: fieldRange.last,
|
||||
count: fieldRange.count,
|
||||
},
|
||||
method_list: {
|
||||
first_row: methodRange.first,
|
||||
last_row: methodRange.last,
|
||||
count: methodRange.count,
|
||||
row,
|
||||
type: {
|
||||
token,
|
||||
row_offset: cursor.start,
|
||||
namespace,
|
||||
name,
|
||||
full_name: definition.fullName,
|
||||
flags,
|
||||
extends_token:
|
||||
extendsIndex.kind === "valid" ? extendsIndex.token : null,
|
||||
enclosing_type_token:
|
||||
enclosingRow === undefined ? null : metadataToken(2, enclosingRow),
|
||||
name_sha256: null,
|
||||
field_list: {
|
||||
first_row: fieldRange.first,
|
||||
last_row: fieldRange.last,
|
||||
count: fieldRange.count,
|
||||
},
|
||||
method_list: {
|
||||
first_row: methodRange.first,
|
||||
last_row: methodRange.last,
|
||||
count: methodRange.count,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,6 +7,17 @@ import {
|
||||
} from "./ManagedPe.fixture.js";
|
||||
import { buildManagedMemberCapacityFixture } from "../../tests/fixtures/managedMemberCapacity.js";
|
||||
import { readManagedPeLayout } from "./ManagedPeReader.js";
|
||||
import type { ManagedParseIssue } from "../domain/managed/managedArtifact.js";
|
||||
import {
|
||||
parseTypes,
|
||||
readTypeDefinitions,
|
||||
} from "./ManagedMemberInspectorCore.js";
|
||||
import {
|
||||
readManagedMetadataInventory,
|
||||
readManagedResourceDirectory,
|
||||
} from "./ManagedMetadataInventory.js";
|
||||
import { managedMetadataTypeNames } from "./ManagedMetadataInventoryRows.js";
|
||||
import { readTypeMemberListFacts } from "./ManagedTypeMemberLists.js";
|
||||
import {
|
||||
metadataRowOffset,
|
||||
readManagedMetadataLayout,
|
||||
@@ -206,3 +217,81 @@ it("does not turn an unreadable requested type name into observed absence during
|
||||
]);
|
||||
expect(result.limitations.join(" ")).toContain("absence is not observed");
|
||||
});
|
||||
|
||||
it("keeps later type identity, member ownership and constructor names at their original row", () => {
|
||||
const bytes = buildManagedPeFixture({
|
||||
additionalEmptyTypes: 1,
|
||||
customAttributeTypeRaw: (1 << 3) | 2,
|
||||
typeName: "LookupAttribute",
|
||||
extendsRaw: 0,
|
||||
methodName: ".ctor",
|
||||
methodFlags: 0x1886,
|
||||
methodSignature: Buffer.from([0x20, 0x01, 0x01, 0x0e]),
|
||||
});
|
||||
const healthy = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
const owner = healthy.types.find(({ token }) => token === "0x02000002");
|
||||
expect(owner?.full_name).toBe("Fixture.LookupAttribute");
|
||||
expect(owner?.name_sha256).toMatch(/^[0-9a-f]{64}$/u);
|
||||
expect(healthy.fields).toHaveLength(1);
|
||||
expect(healthy.methods).toHaveLength(1);
|
||||
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);
|
||||
// Only the preceding empty type's name is unreadable; row 2 still owns
|
||||
// FieldDef 1 and MethodDef 1, including the local attribute constructor.
|
||||
bytes.writeUInt16LE(0xffff, metadataRowOffset(layout, 2, 1) + 4);
|
||||
const target = managedPeFixtureTarget(bytes);
|
||||
const result = inspectManagedMembersBytes(bytes, target, {
|
||||
typeNames: ["Fixture.LookupAttribute", "Fixture.Empty0"],
|
||||
});
|
||||
|
||||
expect(result.types).toEqual([owner]);
|
||||
expect(result.types[0]).toMatchObject({
|
||||
token: "0x02000002",
|
||||
row_offset: metadataRowOffset(layout, 2, 2),
|
||||
name_sha256: owner?.name_sha256,
|
||||
field_list: { first_row: 1, last_row: 1, count: 1 },
|
||||
method_list: { first_row: 1, last_row: 1, count: 1 },
|
||||
});
|
||||
expect(result.fields).toEqual(healthy.fields);
|
||||
expect(result.methods).toEqual(healthy.methods);
|
||||
for (const member of [...result.fields, ...result.methods])
|
||||
expect(member).toMatchObject({
|
||||
declaring_type_token: "0x02000002",
|
||||
declaring_type: "Fixture.LookupAttribute",
|
||||
});
|
||||
expect(result.artifact.sha256).toBe(target.sha256);
|
||||
expect(result.coverage.state).toBe("partial");
|
||||
expect(result.selection?.unmatched_type_names).toEqual(["Fixture.Empty0"]);
|
||||
expect(result.limitations.join(" ")).toContain("absence is not observed");
|
||||
|
||||
// Member inspection uses the parsed-type name owner for its metadata
|
||||
// inventory, whose attribute names are not included in the member report.
|
||||
const issues: ManagedParseIssue[] = [];
|
||||
const memberLists = readTypeMemberListFacts(bytes, layout);
|
||||
const definitions = readTypeDefinitions(bytes, layout, issues, memberLists);
|
||||
const parsedTypes = parseTypes(bytes, layout, definitions);
|
||||
const names = managedMetadataTypeNames(bytes, layout, issues, parsedTypes);
|
||||
const inventory = readManagedMetadataInventory(
|
||||
bytes,
|
||||
layout,
|
||||
readManagedResourceDirectory(pe, issues),
|
||||
memberLists,
|
||||
undefined,
|
||||
names,
|
||||
);
|
||||
expect(inventory.attributes).toMatchObject([
|
||||
{
|
||||
constructor_token: "0x06000001",
|
||||
type_name: "Fixture.LookupAttribute",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -2,7 +2,6 @@ import { managedDecodeBudget } from "./ManagedDecodeBudget.js";
|
||||
import { managedTypeReferences } from "./ManagedTypeReferences.js";
|
||||
import { MAX_TYPE_NESTING_DEPTH } from "./ManagedTypeNameDigests.js";
|
||||
import { readManagedTypeNesting } from "./ManagedTypeNesting.js";
|
||||
import type { ManagedTypeNesting } from "./ManagedTypeNesting.js";
|
||||
import { memberDeclaringTypeRowLookup } from "./ManagedMemberOwnership.js";
|
||||
import type { TypeMemberListFacts } from "./ManagedTypeMemberLists.js";
|
||||
|
||||
@@ -27,7 +26,7 @@ import {
|
||||
import { managedFailure, readManagedValue } from "./ManagedReaderFailure.js";
|
||||
import type { ManagedTypeReferenceLookup } from "./ManagedTypeReferences.js";
|
||||
import type { ManagedResourceDirectory } from "./ManagedMetadataInventory.js";
|
||||
import type { ManagedType } from "./ManagedMemberInspectorCore.js";
|
||||
import type { ManagedType, parseTypes } from "./ManagedMemberInspectorCore.js";
|
||||
|
||||
type ModuleIdentity = NonNullable<ManagedArtifactInspection["module"]>;
|
||||
type AssemblyIdentity = NonNullable<ManagedArtifactInspection["assembly"]>;
|
||||
@@ -246,10 +245,7 @@ export const managedMetadataTypeNames = (
|
||||
bytes: Buffer,
|
||||
layout: ManagedMetadataLayout,
|
||||
issues: ManagedParseIssue[],
|
||||
parsedTypes?: {
|
||||
readonly types: readonly ManagedType[];
|
||||
readonly nesting: ManagedTypeNesting;
|
||||
},
|
||||
parsedTypes?: Pick<ReturnType<typeof parseTypes>, "types" | "nesting">,
|
||||
parsedReferences?: ManagedTypeReferenceLookup,
|
||||
): ManagedMetadataTypeNameLookup => {
|
||||
const nesting =
|
||||
@@ -259,8 +255,7 @@ export const managedMetadataTypeNames = (
|
||||
const definitions = new Map<number, ManagedType>();
|
||||
if (parsedTypes !== undefined) {
|
||||
managedDecodeBudget(layout).reserve(parsedTypes.types.length * 128, null);
|
||||
for (const type of parsedTypes.types)
|
||||
definitions.set(Number.parseInt(type.token.slice(4), 16), type);
|
||||
for (const { row, type } of parsedTypes.types) definitions.set(row, type);
|
||||
}
|
||||
const cache = new Map<
|
||||
string,
|
||||
|
||||
@@ -4,10 +4,10 @@ import { managedDecodeBudget } from "./ManagedDecodeBudget.js";
|
||||
import type { ManagedParseIssue } from "../domain/managed/managedArtifact.js";
|
||||
import type {
|
||||
parseTypes,
|
||||
ManagedType,
|
||||
SignatureTypeName,
|
||||
DeclarationTypeName,
|
||||
} from "./ManagedMemberInspectorCore.js";
|
||||
import { metadataToken } from "./ManagedMetadataHeaps.js";
|
||||
import type { ManagedTypeReferenceLookup } from "./ManagedTypeReferences.js";
|
||||
import type { ManagedMetadataLayout } from "./ManagedMetadataLayout.js";
|
||||
import { managedFailure, readManagedValue } from "./ManagedReaderFailure.js";
|
||||
@@ -153,11 +153,11 @@ export const managedTypeNameDigests = (
|
||||
} => {
|
||||
const retain = () => managedDecodeBudget(layout).reserve(128, null);
|
||||
managedDecodeBudget(layout).reserve(parsedTypes.types.length * 128, null);
|
||||
const types = new Map<string, (typeof parsedTypes.types)[number]>();
|
||||
for (const type of parsedTypes.types) types.set(type.token, type);
|
||||
const types = new Map<number, ManagedType>();
|
||||
for (const { row, type } of parsedTypes.types) types.set(row, type);
|
||||
const definitions = nestedTypeDigests((row) => {
|
||||
if (!parsedTypes.nesting.complete) return null;
|
||||
const type = types.get(metadataToken(TYPE_DEF, row));
|
||||
const type = types.get(row);
|
||||
if (
|
||||
type === undefined &&
|
||||
(row < 1 || row > (layout.table(TYPE_DEF)?.rowCount ?? 0))
|
||||
|
||||
Reference in New Issue
Block a user