mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
refactor(managed): centralize method implementation flag facts
Have the MethodDef row owner derive code type, managed kind and the P/Invoke flag for both body decoding and native-boundary inventory. Remove duplicate masks and unreachable fallbacks while preserving raw flags, zero-RVA precedence and ImplMap-specific declaration policy. Validation: 280 focused source, CLI and MCP tests passed, including cross-workflow flag combinations and declaration/body policy boundaries. Typecheck and lint passed.
This commit is contained in:
1 parent
8ced814b96
commit
1f5c5c31c1
5 files changed
+250
-60
No files matched your search
@@ -366,6 +366,7 @@ export const parseMethods = ({
|
||||
flags,
|
||||
nameIndex,
|
||||
signatureIndex,
|
||||
implementation,
|
||||
} = readMethodDefinitionRow(bytes, layout, row);
|
||||
const name = readMetadataString(
|
||||
bytes,
|
||||
@@ -408,7 +409,7 @@ export const parseMethods = ({
|
||||
),
|
||||
});
|
||||
const read = methodBody(bytes, pe, rva, {
|
||||
implementation: { implFlags, flags },
|
||||
implementation,
|
||||
budget: managedDecodeBudget(layout),
|
||||
metadataLayout: layout,
|
||||
methodToken: token,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ManagedMethodImplementationFlags } from "./ManagedMethodDefinitionRows.js";
|
||||
import {
|
||||
admitManagedProjection,
|
||||
type ManagedDecodeBudget,
|
||||
@@ -84,10 +85,7 @@ const readMethodBodyHeader = (
|
||||
};
|
||||
|
||||
export interface MethodBodyContext {
|
||||
readonly implementation: {
|
||||
readonly implFlags: number;
|
||||
readonly flags: number;
|
||||
};
|
||||
readonly implementation: ManagedMethodImplementationFlags;
|
||||
readonly budget: ManagedDecodeBudget;
|
||||
readonly metadataLayout: ManagedMetadataLayout;
|
||||
readonly methodToken: string;
|
||||
@@ -122,8 +120,9 @@ export const methodBody = (
|
||||
issues,
|
||||
};
|
||||
const native =
|
||||
(context.implementation.implFlags & 7) !== 0 ||
|
||||
(context.implementation.flags & 0x2000) !== 0;
|
||||
context.implementation.codeType !== "il" ||
|
||||
context.implementation.managedKind !== "managed" ||
|
||||
context.implementation.pinvokeFlag;
|
||||
if (native) {
|
||||
const issue =
|
||||
"CIL decoding is unavailable for native, runtime, unmanaged, or P/Invoke implementations";
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import type { ManagedMetadataLayout } from "./ManagedMetadataLayout.js";
|
||||
import { metadataRowCursor, metadataToken } from "./ManagedMetadataHeaps.js";
|
||||
|
||||
/** Meaning of the MethodDef implementation bits, independent of RVA or ImplMap evidence. */
|
||||
export interface ManagedMethodImplementationFlags {
|
||||
readonly codeType: "il" | "native" | "optil" | "runtime";
|
||||
readonly managedKind: "managed" | "unmanaged";
|
||||
readonly pinvokeFlag: boolean;
|
||||
}
|
||||
|
||||
const METHOD_CODE_TYPES = ["il", "native", "optil", "runtime"] as const;
|
||||
|
||||
/** Scalar MethodDef facts; consumers own heap admission and method selection. */
|
||||
export const readMethodDefinitionRow = (
|
||||
bytes: Buffer,
|
||||
@@ -14,16 +23,25 @@ export const readMethodDefinitionRow = (
|
||||
readonly flags: number;
|
||||
readonly nameIndex: number;
|
||||
readonly signatureIndex: number;
|
||||
readonly implementation: ManagedMethodImplementationFlags;
|
||||
} => {
|
||||
// Layout admission bounds the whole fixed-width row before a cursor is created.
|
||||
const cursor = metadataRowCursor(bytes, layout, 6, row);
|
||||
const rva = cursor.readUInt32();
|
||||
const implFlags = cursor.readUInt16();
|
||||
const flags = cursor.readUInt16();
|
||||
return {
|
||||
token: metadataToken(6, row),
|
||||
rowOffset: cursor.start,
|
||||
rva: cursor.readUInt32(),
|
||||
implFlags: cursor.readUInt16(),
|
||||
flags: cursor.readUInt16(),
|
||||
rva,
|
||||
implFlags,
|
||||
flags,
|
||||
nameIndex: cursor.readIndex(layout.stringIndexSize),
|
||||
signatureIndex: cursor.readIndex(layout.blobIndexSize),
|
||||
implementation: {
|
||||
codeType: METHOD_CODE_TYPES[(implFlags & 0x0003) as 0 | 1 | 2 | 3],
|
||||
managedKind: (implFlags & 0x0004) === 0 ? "managed" : "unmanaged",
|
||||
pinvokeFlag: (flags & 0x2000) !== 0,
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,4 +1,7 @@
|
||||
import { readMethodDefinitionRow } from "./ManagedMethodDefinitionRows.js";
|
||||
import {
|
||||
readMethodDefinitionRow,
|
||||
type ManagedMethodImplementationFlags,
|
||||
} from "./ManagedMethodDefinitionRows.js";
|
||||
import {
|
||||
admitManagedProjection,
|
||||
managedDecodeBudget,
|
||||
@@ -31,15 +34,24 @@ type NativeImport = ManagedNativeBoundaryInspection["pinvoke_imports"][number];
|
||||
type NativeImplementation =
|
||||
ManagedNativeBoundaryInspection["native_implementations"][number];
|
||||
|
||||
interface MemberCore {
|
||||
type MemberCore = {
|
||||
readonly token: string;
|
||||
readonly rowOffset: number;
|
||||
readonly kind: NativeImport["member_kind"];
|
||||
readonly name: string;
|
||||
readonly flags: number;
|
||||
readonly implFlags: number | null;
|
||||
readonly rva: number | null;
|
||||
}
|
||||
} & (
|
||||
| {
|
||||
readonly kind: "field";
|
||||
readonly implFlags: null;
|
||||
readonly rva: null;
|
||||
}
|
||||
| {
|
||||
readonly kind: "method";
|
||||
readonly implFlags: number;
|
||||
readonly rva: number;
|
||||
readonly implementation: ManagedMethodImplementationFlags;
|
||||
}
|
||||
);
|
||||
|
||||
const flagsHex = (value: number): string =>
|
||||
`0x${value.toString(16).padStart(4, "0")}`;
|
||||
@@ -114,8 +126,15 @@ export const parseMethods = (
|
||||
const table = layout.table(6);
|
||||
for (let row = 1; row <= (table?.rowCount ?? 0); row += 1) {
|
||||
readManagedValue(() => {
|
||||
const { token, rowOffset, rva, implFlags, flags, nameIndex } =
|
||||
readMethodDefinitionRow(bytes, layout, row);
|
||||
const {
|
||||
token,
|
||||
rowOffset,
|
||||
rva,
|
||||
implFlags,
|
||||
flags,
|
||||
nameIndex,
|
||||
implementation,
|
||||
} = readMethodDefinitionRow(bytes, layout, row);
|
||||
const name = readMetadataString(bytes, layout, nameIndex, heapExtent);
|
||||
methods.set(token, {
|
||||
token,
|
||||
@@ -125,6 +144,7 @@ export const parseMethods = (
|
||||
flags,
|
||||
implFlags,
|
||||
rva,
|
||||
implementation,
|
||||
});
|
||||
}, issues);
|
||||
}
|
||||
@@ -296,67 +316,32 @@ export const nativeImplementations = (
|
||||
const implementations: NativeImplementation[] = [];
|
||||
for (const method of methods) {
|
||||
if (method.kind !== "method") continue;
|
||||
const implFlags = method.implFlags ?? 0;
|
||||
const codeType = codeTypeFor(implFlags);
|
||||
const managedKind = managedKindFor(implFlags);
|
||||
const pinvokeDeclared =
|
||||
pinvokeTokens.has(method.token) || (method.flags & 0x2000) !== 0;
|
||||
const { codeType, managedKind, pinvokeFlag } = method.implementation;
|
||||
const pinvokeDeclared = pinvokeTokens.has(method.token) || pinvokeFlag;
|
||||
if (!pinvokeDeclared && codeType === "il" && managedKind === "managed")
|
||||
continue;
|
||||
implementations.push({
|
||||
token: method.token,
|
||||
row_offset: method.rowOffset,
|
||||
name: method.name,
|
||||
rva: method.rva ?? 0,
|
||||
rva: method.rva,
|
||||
flags: method.flags,
|
||||
impl_flags: implFlags,
|
||||
impl_flags: method.implFlags,
|
||||
code_type: codeType,
|
||||
managed_kind: managedKind,
|
||||
pinvoke_declared: pinvokeDeclared,
|
||||
boundary_kind: boundaryKind(pinvokeDeclared, codeType, managedKind),
|
||||
body_interpretation:
|
||||
codeType === "il" && managedKind === "managed" && !pinvokeDeclared
|
||||
? "managed-cil"
|
||||
: method.rva === 0
|
||||
? "not-file-backed"
|
||||
: "native-or-runtime",
|
||||
method.rva === 0 ? "not-file-backed" : "native-or-runtime",
|
||||
});
|
||||
}
|
||||
return implementations;
|
||||
};
|
||||
|
||||
const codeTypeFor = (implFlags: number): NativeImplementation["code_type"] => {
|
||||
switch (implFlags & 0x0003) {
|
||||
case 0x0000:
|
||||
return "il";
|
||||
case 0x0001:
|
||||
return "native";
|
||||
case 0x0002:
|
||||
return "optil";
|
||||
case 0x0003:
|
||||
return "runtime";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
};
|
||||
|
||||
const managedKindFor = (
|
||||
implFlags: number,
|
||||
): NativeImplementation["managed_kind"] => {
|
||||
switch (implFlags & 0x0004) {
|
||||
case 0x0000:
|
||||
return "managed";
|
||||
case 0x0004:
|
||||
return "unmanaged";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
};
|
||||
|
||||
const boundaryKind = (
|
||||
pinvokeDeclared: boolean,
|
||||
codeType: NativeImplementation["code_type"],
|
||||
managedKind: NativeImplementation["managed_kind"],
|
||||
codeType: ManagedMethodImplementationFlags["codeType"],
|
||||
managedKind: ManagedMethodImplementationFlags["managedKind"],
|
||||
): NativeImplementation["boundary_kind"] => {
|
||||
if (pinvokeDeclared) return "pinvoke";
|
||||
if (codeType === "native") return "native-body";
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
metadataRowOffset,
|
||||
readManagedMetadataLayout,
|
||||
} from "./ManagedMetadataLayout.js";
|
||||
import { readManagedPeLayout } from "./ManagedPeReader.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { createHash } from "node:crypto";
|
||||
@@ -411,3 +416,185 @@ describe("managed PE fixture layout", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const inspectImplementationFixture = ({
|
||||
implFlags,
|
||||
methodFlags = 0x0016,
|
||||
zeroRva = false,
|
||||
implMap = false,
|
||||
}: {
|
||||
implFlags: number;
|
||||
methodFlags?: number;
|
||||
zeroRva?: boolean;
|
||||
implMap?: boolean;
|
||||
}) => {
|
||||
const bytes = buildManagedPeFixture({
|
||||
methodFlags,
|
||||
...(implMap ? { pinvoke: {} } : {}),
|
||||
});
|
||||
const pe = readManagedPeLayout(bytes);
|
||||
if (pe.cli === null) throw new Error("Expected CLI fixture");
|
||||
const { rva, size } = pe.cli.metadata;
|
||||
const layout = readManagedMetadataLayout(
|
||||
bytes,
|
||||
pe.rvaToOffset(rva, size, "cli.metadata"),
|
||||
size,
|
||||
);
|
||||
const row = metadataRowOffset(layout, 6, 1);
|
||||
bytes.writeUInt16LE(implFlags, row + 4);
|
||||
if (zeroRva) bytes.writeUInt32LE(0, row);
|
||||
const target = managedPeFixtureTarget(bytes);
|
||||
return {
|
||||
members: inspectManagedMembersBytes(bytes, target),
|
||||
boundaries: inspectManagedNativeBoundariesBytes(bytes, target),
|
||||
};
|
||||
};
|
||||
|
||||
it.each([
|
||||
{ bits: 0, codeType: "il", managedKind: "managed", boundary: null },
|
||||
{
|
||||
bits: 1,
|
||||
codeType: "native",
|
||||
managedKind: "managed",
|
||||
boundary: "native-body",
|
||||
},
|
||||
{
|
||||
bits: 2,
|
||||
codeType: "optil",
|
||||
managedKind: "managed",
|
||||
boundary: "mixed-or-unknown",
|
||||
},
|
||||
{
|
||||
bits: 3,
|
||||
codeType: "runtime",
|
||||
managedKind: "managed",
|
||||
boundary: "runtime-provided",
|
||||
},
|
||||
{
|
||||
bits: 4,
|
||||
codeType: "il",
|
||||
managedKind: "unmanaged",
|
||||
boundary: "unmanaged-method",
|
||||
},
|
||||
{
|
||||
bits: 5,
|
||||
codeType: "native",
|
||||
managedKind: "unmanaged",
|
||||
boundary: "native-body",
|
||||
},
|
||||
{
|
||||
bits: 6,
|
||||
codeType: "optil",
|
||||
managedKind: "unmanaged",
|
||||
boundary: "unmanaged-method",
|
||||
},
|
||||
{
|
||||
bits: 7,
|
||||
codeType: "runtime",
|
||||
managedKind: "unmanaged",
|
||||
boundary: "runtime-provided",
|
||||
},
|
||||
])(
|
||||
"publishes implementation facts consistently with body admission for low flags $bits",
|
||||
({ bits, codeType, managedKind, boundary }) => {
|
||||
// NoInlining, Synchronized and PreserveSig do not change code type or managed kind.
|
||||
for (const highBits of [0, 0x00a8]) {
|
||||
const implFlags = bits | highBits;
|
||||
const { members, boundaries } = inspectImplementationFixture({
|
||||
implFlags,
|
||||
});
|
||||
expect(members.methods[0]?.body.status).toBe(
|
||||
boundary === null ? "present" : "partial",
|
||||
);
|
||||
if (boundary === null) {
|
||||
expect(boundaries.native_implementations).toEqual([]);
|
||||
expect(members.methods[0]?.body.il_sha256).toEqual(expect.any(String));
|
||||
} else {
|
||||
expect(boundaries.native_implementations).toMatchObject([
|
||||
{
|
||||
token: members.methods[0]?.token,
|
||||
impl_flags: implFlags,
|
||||
code_type: codeType,
|
||||
managed_kind: managedKind,
|
||||
pinvoke_declared: false,
|
||||
boundary_kind: boundary,
|
||||
body_interpretation: "native-or-runtime",
|
||||
},
|
||||
]);
|
||||
expect(members.methods[0]?.body.il_sha256).toBeNull();
|
||||
expect(members.methods[0]?.body.issue).toMatch(/CIL/u);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "PInvoke flag on managed IL",
|
||||
implFlags: 0,
|
||||
methodFlags: 0x2016,
|
||||
zeroRva: false,
|
||||
implMap: false,
|
||||
status: "partial",
|
||||
codeType: "il",
|
||||
managedKind: "managed",
|
||||
},
|
||||
{
|
||||
name: "PInvoke flag on unmanaged OPTIL",
|
||||
implFlags: 6,
|
||||
methodFlags: 0x2016,
|
||||
zeroRva: false,
|
||||
implMap: false,
|
||||
status: "partial",
|
||||
codeType: "optil",
|
||||
managedKind: "unmanaged",
|
||||
},
|
||||
{
|
||||
name: "absent RVA before PInvoke/runtime classification",
|
||||
implFlags: 7,
|
||||
methodFlags: 0x2016,
|
||||
zeroRva: true,
|
||||
implMap: false,
|
||||
status: "absent",
|
||||
codeType: "runtime",
|
||||
managedKind: "unmanaged",
|
||||
},
|
||||
{
|
||||
name: "ImplMap without the PInvoke flag",
|
||||
implFlags: 0,
|
||||
methodFlags: 0x0016,
|
||||
zeroRva: false,
|
||||
implMap: true,
|
||||
status: "present",
|
||||
codeType: "il",
|
||||
managedKind: "managed",
|
||||
},
|
||||
])(
|
||||
"retains distinct native declaration and body policies: $name",
|
||||
(scenario) => {
|
||||
const { members, boundaries } = inspectImplementationFixture(scenario);
|
||||
expect(boundaries.native_implementations).toMatchObject([
|
||||
{
|
||||
token: members.methods[0]?.token,
|
||||
flags: scenario.methodFlags,
|
||||
code_type: scenario.codeType,
|
||||
managed_kind: scenario.managedKind,
|
||||
pinvoke_declared: true,
|
||||
boundary_kind: "pinvoke",
|
||||
body_interpretation: scenario.zeroRva
|
||||
? "not-file-backed"
|
||||
: "native-or-runtime",
|
||||
},
|
||||
]);
|
||||
expect(boundaries.pinvoke_imports).toHaveLength(scenario.implMap ? 1 : 0);
|
||||
expect(members.methods[0]?.body.status).toBe(scenario.status);
|
||||
if (scenario.zeroRva)
|
||||
expect(members.methods[0]?.body).toMatchObject({
|
||||
rva: 0,
|
||||
issue: null,
|
||||
il_sha256: null,
|
||||
});
|
||||
if (scenario.status === "present")
|
||||
expect(members.methods[0]?.body.il_sha256).toEqual(expect.any(String));
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user