Files
reverse-engineering-anything/src/dotnet/ManagedMemberInspector.ts
T
Kaoru0822andClaude Opus 5.5 1f531fc257 fix(managed): decode generic, array, fnptr, and modifier signatures (#1835)
* fix(managed): decode generic, array, fnptr, and modifier signatures

Member signatures using GENERICINST, ARRAY, FNPTR, CMOD_REQD/CMOD_OPT,
or TYPEDBYREF were reported as unsupported, leaving their types unknown
and turning otherwise complete inspections partial. Decode them with an
explicit stack so nested signatures stay recursion-free, keep encoded
TypeDefOrRefOrSpec values, and report malformed shapes as malformed.

Fixes #1832

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(managed): bound array rank before rendering dimensions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(managed): keep modifier order and reject non-method fnptr headers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(managed): reject corrupt fnptr flags and misplaced typed references

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(managed): decode signature elements by grammar position

Track whether each element is a field type, return type, parameter,
nested Type, or pointee. VOID is valid only as a return type or
pointee, TYPEDBYREF only as a return type or parameter, and BYREF only
at the top of a return type, parameter, or ref field; other placements
are malformed. Function pointer items are parsed as a return type then
parameters, and generic arguments as Types.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 07:43:48 -07:00

201 lines
6.4 KiB
TypeScript

import {
admitManagedProjection,
managedDecodeBudget,
} from "./ManagedDecodeBudget.js";
import type { BinaryTarget } from "../domain/binaryTargetTypes.js";
import {
managedMemberInspectionSchema,
type ManagedMemberInspection,
type ManagedParseIssue,
} from "../domain/managed/managedArtifact.js";
import {
readManagedPeLayout,
type ManagedPeLayout,
} from "./ManagedPeReader.js";
import {
readManagedResourceDirectory,
managedTableRowCounts,
readManagedMetadataInventory,
} from "./ManagedMetadataInventory.js";
import { readManagedMetadataLayout } from "./ManagedMetadataLayout.js";
import {
ManagedReaderFailure,
uniqueManagedParseIssues,
} from "./ManagedReaderFailure.js";
import { parseTypes, typeRanges } from "./ManagedMemberInspectorCore.js";
import {
edges,
parseExportedTypes,
parseFields,
parseMemberRefs,
parseMethods,
} from "./ManagedMemberRows.js";
const unavailable = (
target: BinaryTarget,
bytes: Buffer,
issue: ManagedParseIssue | null,
): ManagedMemberInspection =>
managedMemberInspectionSchema.parse(
admitManagedProjection({
artifact: {
path: target.path,
sha256: target.sha256,
byte_length: bytes.length,
format: "pe",
},
module: null,
metadata: {
status:
issue === null
? "absent"
: issue.code === "resource-limit"
? "partial"
: "malformed",
version: null,
table_row_counts: {},
},
identity_scope: {
token_identity: "build-local",
requires_artifact_sha256: target.sha256,
requires_mvid: null,
},
types: [],
exported_types: null,
fields: [],
methods: [],
member_refs: [],
call_edges: [],
field_accesses: [],
coverage: {
state: "unavailable",
issues: issue === null ? [] : [issue],
},
limitations: [
issue === null
? "The PE has no admitted CLI metadata; managed member inspection is unavailable."
: "The CLI metadata could not be admitted; managed member inspection is unavailable.",
],
}),
);
const readMemberInventory = (
bytes: Buffer,
pe: ManagedPeLayout,
issues: ManagedParseIssue[],
) => {
const cli = pe.cli;
if (cli === null)
throw new TypeError("Managed inventory requires CLI metadata");
const rootOffset = pe.rvaToOffset(
cli.metadata.rva,
cli.metadata.size,
"cli.metadata",
);
const layout = readManagedMetadataLayout(
bytes,
rootOffset,
cli.metadata.size,
);
const inventory = readManagedMetadataInventory(
bytes,
layout,
readManagedResourceDirectory(pe, issues),
);
return { layout, inventory };
};
/** Inspect metadata members and method bodies without loading target code. */
export const inspectManagedMembersBytes = (
bytes: Buffer,
target: BinaryTarget,
): ManagedMemberInspection => {
const pe = readManagedPeLayout(bytes);
if (pe.cli === null) return unavailable(target, bytes, pe.cliIssue);
const issues: ManagedParseIssue[] = [];
try {
const { layout, inventory } = readMemberInventory(bytes, pe, issues);
issues.push(...inventory.issues);
const ranges = typeRanges(bytes, layout, issues);
const parsedTypes = parseTypes(bytes, layout, ranges);
issues.push(...parsedTypes.issues);
const exportedTypes = parseExportedTypes(bytes, layout);
issues.push(...exportedTypes.issues);
const fields = parseFields(bytes, layout, ranges);
const memberRefs = parseMemberRefs(bytes, layout);
issues.push(...memberRefs.issues);
const methods = parseMethods({
bytes,
layout,
pe,
ranges,
});
issues.push(...fields.issues, ...methods.issues);
const coverageIssues = uniqueManagedParseIssues(issues);
const incompleteMembers =
[...fields.fields, ...memberRefs.refs, ...methods.methods].some(
(member) => member.signature.parse_status !== "decoded",
) ||
methods.methods.some(
(method) =>
method.body.status === "partial" ||
method.body.status === "malformed",
);
const related = edges(
methods.methods,
methods.core,
fields.core,
memberRefs.core,
managedDecodeBudget(layout),
);
return managedMemberInspectionSchema.parse(
admitManagedProjection(
{
artifact: {
path: target.path,
sha256: target.sha256,
byte_length: bytes.length,
format: "pe",
},
module: inventory.module,
metadata: {
status: issues.length === 0 ? "complete" : "partial",
version: layout.version,
table_row_counts: managedTableRowCounts(layout),
},
identity_scope: {
token_identity: "build-local",
requires_artifact_sha256: target.sha256,
requires_mvid: inventory.module?.mvid ?? null,
},
types: parsedTypes.types,
exported_types: exportedTypes.exportedTypes,
fields: fields.fields,
methods: methods.methods,
member_refs: memberRefs.refs,
call_edges: related.callEdges,
field_accesses: related.fieldAccesses,
coverage: {
state:
issues.length === 0 && !incompleteMembers
? "complete"
: "partial",
issues: coverageIssues,
},
limitations: [
"Metadata tokens are build-local coordinates and are only meaningful with the reported artifact SHA-256 and MVID.",
"CIL instruction anchors are decoded from file-backed method bodies only; no target assembly is loaded or executed.",
"Exported types are reported from ExportedType rows as declared; forwarded types are not resolved in their implementing assemblies.",
"Member signatures decode ECMA-335 primitive, class, valuetype, generic instantiation, generic variable, pointer, byref, single-dimension and general array, function pointer, custom modifier, and typed reference forms; class and valuetype operands keep their encoded TypeDefOrRefOrSpec values, and unsupported forms such as vararg sentinels retain raw signature hashes.",
],
},
managedDecodeBudget(layout),
),
);
} catch (cause: unknown) {
if (cause instanceof ManagedReaderFailure)
return unavailable(target, bytes, cause.issue);
throw cause;
}
};