mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 01:13:39 +00:00
* 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>
201 lines
6.4 KiB
TypeScript
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;
|
|
}
|
|
};
|