mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 16:57:19 +00:00
* feat(managed): name the declaring type of call and field targets
Call edges named external targets by member name alone (Join, ToJson,
WriteAllText), and method-spec edges had no name at all, so an analyst could
not tell which library a method calls or find every use of a type without a
decompiler. Field accesses through MemberRefs had no name either.
Name each TypeDef, TypeRef, and generic-instantiation TypeSpec once, in the
namespace-qualified form TypeDef full_name uses, and add member_refs
parent_name, call_edges target_declaring_type, and field_accesses
field_declaring_type. A method-spec target reports the generic method it
instantiates. Fields, member references, methods, and method specs now
resolve through one token-keyed MemberCore lookup instead of three maps.
Evidence recorded before these fields reads them as null.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* 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>
* fix(managed): validate MethodSpec arity and report unsupported spec blobs
- A MethodSpec now checks that its method signature is generic and that
the instantiation supplies as many type arguments as it declares. A
non-generic target, a field reference, or a count mismatch is reported
as invalid-row; the readable method keeps its name.
- Unsupported TypeSpec and MethodSpec forms raise an owner-scoped
unsupported-signature issue instead of reading as an intentional
unknown.
- MethodSpec reading moves to ManagedMethodSpecs.ts, and its tests get
their own describe block.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(managed): reject non-method signature headers and invalid TypeRef scopes
The method signature header reader now owns the calling-convention rule
that only FNPTR applied: FIELD, LOCAL_SIG, PROPERTY, and GENERICINST
headers, the reserved bit, and EXPLICITTHIS without HASTHIS are malformed.
A MethodSpec whose method has a PROPERTY header is no longer accepted as a
generic method, and member signatures with such headers no longer decode
as methods.
The type-name lookup validates a TypeRef's ResolutionScope coded index and
reports an out-of-range scope as invalid-row, keeping the readable name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(managed): hold member rows to MethodDefSig and FieldSig headers
Method signature headers are now checked in context. MethodDef and
MemberRef rows accept only the MethodDefSig/MethodRefSig headers: DEFAULT,
VARARG, or GENERIC with DEFAULT. FNPTR keeps the unmanaged conventions
compilers emit for function pointers. A field signature must be exactly
FIELD, so a flagged FIELD header is malformed and never names a field.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(managed): accept MethodSpec arity only from a fully decoded signature
The arity check read only the target's signature header, so a truncated
generic method signature passed. Under type selection a MethodDef target
outside the selection is never decoded elsewhere, so coverage stayed
complete. The arity now comes from a transient full decode, and only a
decoded method signature supplies it. signature() keeps its raw length
and digest around the shared decodeSignature.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* perf(managed): decode each MethodSpec target signature once
edgeTargets caches the generic arity, including a failed decode, by
target method token, so many instantiations of one generic method share
a single signature decode. Unity.InputSystem has 472 distinct MethodSpecs
over about 133 target methods.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(managed): validate spec type operands and transitive reference scopes
* fix(managed): retain readable names when a scope row is unreadable
* test(managed): pin owner-scoped spec operand and scope-chain checks
e59217c83 validates spec type operands inside specSignature, so a
malformed generic TypeSpec base is now invalid-row under its owning
TypeSpec row; the three stale expectations follow that. New cases cover
a TypeSpec argument and a MethodSpec argument that name a missing TypeRef,
and a TypeRef whose ResolutionScope names itself. The scope cases move to
their own describe block.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(managed): keep field targets from naming MethodSpecs
A MethodSpec over a field-signature MemberRef inherited kind "field", so
an ldsfld on that MethodSpec published a field name and declaring type
for malformed IL. A MethodSpec now borrows its target's name only when
the target is not a field; the arity check still reports it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(managed): reuse owner-decoded signatures for MethodSpec arity
MemberRef signatures, and MethodDef signatures in the type selection, are
already fully decoded, but the MethodSpec arity check decoded them again
and admitted a second transient allocation, which could exhaust the
budget. MemberCore now carries the owner's genericArity, and only a target
nothing decoded, such as an unselected MethodDef, falls back to the raw
signature. signatureGenericArity is the one rule for both paths.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
155 lines
5.0 KiB
TypeScript
155 lines
5.0 KiB
TypeScript
import type { ManagedParseIssue } from "../domain/managed/managedArtifact.js";
|
|
import {
|
|
methodGenericArity,
|
|
specSignature,
|
|
type ManagedMethod,
|
|
type MemberCore,
|
|
} from "./ManagedMemberInspectorCore.js";
|
|
import type { ManagedMetadataLayout } from "./ManagedMetadataLayout.js";
|
|
import {
|
|
metadataCodedToken,
|
|
metadataCodedTokenInvalidReason,
|
|
metadataRowCursor,
|
|
readMetadataBlob,
|
|
} from "./ManagedMetadataHeaps.js";
|
|
import { managedFailure, readManagedValue } from "./ManagedReaderFailure.js";
|
|
|
|
/**
|
|
* Index fields, member references, and methods by token, then name each
|
|
* MethodSpec an inspected body calls after the generic method it instantiates,
|
|
* so every edge target resolves through one lookup.
|
|
*/
|
|
export const edgeTargets = (
|
|
bytes: Buffer,
|
|
layout: ManagedMetadataLayout,
|
|
methods: readonly ManagedMethod[],
|
|
cores: readonly ReadonlyMap<string, MemberCore>[],
|
|
): {
|
|
readonly members: ReadonlyMap<string, MemberCore>;
|
|
readonly issues: readonly ManagedParseIssue[];
|
|
} => {
|
|
const members = new Map(cores.flatMap((core) => [...core]));
|
|
const issues: ManagedParseIssue[] = [];
|
|
const seen = new Set<string>();
|
|
// Generic arity by method token, so instantiations of one method decode it once.
|
|
const arities = new Map<string, number | null>();
|
|
for (const method of methods)
|
|
for (const { operand } of method.body.anchors) {
|
|
if (operand?.startsWith("0x2b") !== true || seen.has(operand)) continue;
|
|
seen.add(operand);
|
|
readManagedValue(
|
|
() =>
|
|
readMethodSpec({ bytes, layout, members, issues, arities }, operand),
|
|
issues,
|
|
);
|
|
}
|
|
return { members, issues };
|
|
};
|
|
|
|
/**
|
|
* Map one MethodSpec to its generic method, then check its instantiation
|
|
* decodes and matches that method's generic arity. A readable Method column
|
|
* keeps its name when the rest is malformed; the failure makes coverage partial.
|
|
*/
|
|
const readMethodSpec = (
|
|
{
|
|
bytes,
|
|
layout,
|
|
members,
|
|
issues,
|
|
arities,
|
|
}: {
|
|
readonly bytes: Buffer;
|
|
readonly layout: ManagedMetadataLayout;
|
|
readonly members: Map<string, MemberCore>;
|
|
readonly issues: ManagedParseIssue[];
|
|
readonly arities: Map<string, number | null>;
|
|
},
|
|
token: string,
|
|
): void => {
|
|
const scope = `metadata.MethodSpec:${token}`;
|
|
const cursor = metadataRowCursor(
|
|
bytes,
|
|
layout,
|
|
0x2b,
|
|
Number.parseInt(token.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(methodRaw, 1, [6, 10], layout.rowCounts);
|
|
const target = generic === null ? undefined : members.get(generic);
|
|
// A MethodSpec instantiates a method, so a field target never lends it a
|
|
// name, even to a field operand.
|
|
if (target !== undefined && target.kind !== "field")
|
|
members.set(token, { ...target, token });
|
|
const types = specSignature(
|
|
layout,
|
|
readMetadataBlob(bytes, layout, instantiation, layout.blob.size),
|
|
{ form: "instantiation", scope, offset: cursor.start },
|
|
);
|
|
if (generic === null) return;
|
|
// Prefer the arity the target's owner decoded; read the raw signature only
|
|
// for a target nothing decoded, such as a MethodDef outside the selection.
|
|
if (target?.genericArity === undefined && !arities.has(generic))
|
|
arities.set(
|
|
generic,
|
|
methodGenericArity(layout, methodSignatureBlob(bytes, layout, generic)),
|
|
);
|
|
const arity =
|
|
target?.genericArity === undefined
|
|
? (arities.get(generic) ?? null)
|
|
: target.genericArity;
|
|
if (arity !== types.length)
|
|
throw managedFailure(
|
|
"invalid-row",
|
|
scope,
|
|
arity === null
|
|
? "MethodSpec method does not have a decodable method signature"
|
|
: `MethodSpec supplies ${String(types.length)} type argument(s), but its method declares ${String(arity)} generic parameter(s)`,
|
|
cursor.start,
|
|
);
|
|
};
|
|
|
|
/** The Signature column of a MethodDef or MemberRef row. */
|
|
const methodSignatureBlob = (
|
|
bytes: Buffer,
|
|
layout: ManagedMetadataLayout,
|
|
token: string,
|
|
): Buffer => {
|
|
const table = Number.parseInt(token.slice(2, 4), 16);
|
|
const cursor = metadataRowCursor(
|
|
bytes,
|
|
layout,
|
|
table,
|
|
Number.parseInt(token.slice(4), 16),
|
|
);
|
|
if (table === 6) {
|
|
cursor.readUInt32();
|
|
cursor.readUInt16();
|
|
cursor.readUInt16();
|
|
} else cursor.readIndex(layout.codedIndexSize("MemberRefParent"));
|
|
cursor.readIndex(layout.stringIndexSize);
|
|
return readMetadataBlob(
|
|
bytes,
|
|
layout,
|
|
cursor.readIndex(layout.blobIndexSize),
|
|
layout.blob.size,
|
|
);
|
|
};
|