mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
fix(managed): retain declaration kind and runtime implementation facts
This commit is contained in:
@@ -504,6 +504,13 @@ characters. These candidates follow [Microsoft's native library loading rules](h
|
||||
These filename rules compare declared and observed identities; they do not
|
||||
establish which library the runtime loaded.
|
||||
|
||||
Signatures retain their decoded fields, but a Field or MethodDef row whose
|
||||
signature has the other member kind is malformed and cannot establish an
|
||||
unchanged signature. Runtime-provided methods are counted separately in
|
||||
`native_implementations.runtime_provided`; they have no file-backed native body
|
||||
for this workflow to map and do not increase `native_body_unresolved`. Historical
|
||||
verification Evidence without this separate count leaves it unknown (`null`).
|
||||
|
||||
An empty declared import name remains unchanged in verification Evidence and
|
||||
produces no native-name candidates. With a supplied PE native context and
|
||||
`no_mangle: false`, an unspecified charset includes the ANSI `A` candidate;
|
||||
|
||||
@@ -542,7 +542,8 @@ export const buildVerificationResult = ({
|
||||
> => {
|
||||
const counts = countStatuses(pinvokeImports);
|
||||
const nativeBodyUnresolved = managed.native_implementations.filter(
|
||||
({ boundary_kind: kind }) => kind !== "pinvoke",
|
||||
({ boundary_kind: kind }) =>
|
||||
kind !== "pinvoke" && kind !== "runtime-provided",
|
||||
).length;
|
||||
const coverage: ManagedNativeVerificationResult["coverage"] = {
|
||||
status:
|
||||
@@ -578,6 +579,9 @@ export const buildVerificationResult = ({
|
||||
},
|
||||
pinvoke_imports: [...pinvokeImports],
|
||||
native_implementations: {
|
||||
runtime_provided: managed.native_implementations.filter(
|
||||
({ boundary_kind }) => boundary_kind === "runtime-provided",
|
||||
).length,
|
||||
unresolved: nativeBodyUnresolved,
|
||||
reason:
|
||||
"Managed metadata tokens and RVAs are not translated to native provider addresses by this workflow; C++/CLI, ReadyToRun, and native-body mappings require explicit provider-supported bridge evidence.",
|
||||
|
||||
@@ -169,6 +169,7 @@ export const managedNativeVerificationResultSchema = z
|
||||
}),
|
||||
pinvoke_imports: z.array(pinvokeVerificationSchema),
|
||||
native_implementations: z.strictObject({
|
||||
runtime_provided: z.int().nonnegative().nullable().default(null),
|
||||
unresolved: z.int().min(0),
|
||||
reason: textSchema,
|
||||
}),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import * as inventoryProducer from "./ManagedMetadataInventory.js";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { inspectManagedArtifactBytes } from "./ManagedArtifactInspector.js";
|
||||
import { inspectManagedMembersBytes } from "./ManagedMemberInspector.js";
|
||||
@@ -10,6 +11,39 @@ import {
|
||||
managedPeFixtureTarget,
|
||||
} from "./ManagedPe.fixture.js";
|
||||
|
||||
it("preserves a readable native declaration when inventory issue count exceeds call-argument capacity", () => {
|
||||
const original = inventoryProducer.readManagedMetadataInventory;
|
||||
const issue = {
|
||||
code: "invalid-row" as const,
|
||||
scope: "metadata.CustomAttribute:fixture",
|
||||
offset: 0,
|
||||
detail: "Controlled invalid constructor",
|
||||
};
|
||||
const producer = vi
|
||||
.spyOn(inventoryProducer, "readManagedMetadataInventory")
|
||||
.mockImplementation((...args) => ({
|
||||
...original(...args),
|
||||
issues: Array.from({ length: 150_000 }, () => issue),
|
||||
}));
|
||||
try {
|
||||
const bytes = buildManagedPeFixture({
|
||||
targetFramework: null,
|
||||
pinvoke: { importName: "Observe" },
|
||||
});
|
||||
const result = inspectManagedNativeBoundariesBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
expect(result.pinvoke_imports[0]?.import_name).toBe("Observe");
|
||||
expect(result.coverage).toMatchObject({
|
||||
state: "partial",
|
||||
issues: [issue],
|
||||
});
|
||||
} finally {
|
||||
producer.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
describe("managed artifact failure classification", () => {
|
||||
it("distinguishes native, malformed, and unsupported metadata", () => {
|
||||
const nativeBytes = buildNativePeFixture();
|
||||
|
||||
@@ -46,6 +46,42 @@ const ldstrBody = (tokens: readonly string[]): Buffer => {
|
||||
return Buffer.concat([Buffer.from([(il.length << 2) | 2]), il]);
|
||||
};
|
||||
|
||||
it.each([
|
||||
{
|
||||
options: { methodSignature: Buffer.from([0x06, 0x08]) },
|
||||
collection: "methods",
|
||||
expected: "method",
|
||||
},
|
||||
{
|
||||
options: { fieldSignature: Buffer.from([0x00, 0x00, 0x01]) },
|
||||
collection: "fields",
|
||||
expected: "field",
|
||||
},
|
||||
] as const)(
|
||||
"rejects a wrong-kind $expected signature at its metadata owner",
|
||||
({ options, collection, expected }) => {
|
||||
const bytes = buildManagedPeFixture(options);
|
||||
const result = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
expect(result[collection][0]?.signature).toMatchObject({
|
||||
parse_status: "malformed",
|
||||
resolved_sha256: null,
|
||||
issue: expect.stringContaining(`Expected ${expected} signature`),
|
||||
});
|
||||
expect(result.coverage).toMatchObject({
|
||||
state: "partial",
|
||||
issues: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
code: "invalid-blob",
|
||||
detail: expect.stringContaining(`Expected ${expected} signature`),
|
||||
}),
|
||||
]),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe("managed compressed integer framing", () => {
|
||||
it.each([[0x80], [0xc0], [0xc0, 1], [0xc0, 1, 2]])(
|
||||
"keeps an incomplete width %j distinct from a reserved prefix",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
import {
|
||||
appendManagedParseIssues,
|
||||
managedFailure,
|
||||
ManagedReaderFailure,
|
||||
readManagedValue,
|
||||
@@ -190,7 +191,7 @@ export const readTypeDefinitions = (
|
||||
issues: ManagedParseIssue[],
|
||||
memberLists: TypeMemberListFacts,
|
||||
): readonly TypeDefinition[] => {
|
||||
issues.push(...memberLists.issues);
|
||||
appendManagedParseIssues(issues, memberLists.issues);
|
||||
const typeTable = layout.table(2);
|
||||
const definitions: TypeDefinition[] = [];
|
||||
for (let row = 1; row <= (typeTable?.rowCount ?? 0); row += 1)
|
||||
@@ -1119,21 +1120,32 @@ export const metadataSignature = (
|
||||
layout,
|
||||
issues,
|
||||
scope,
|
||||
expectedKind,
|
||||
}: {
|
||||
readonly rawSha256: string;
|
||||
readonly typeName: SignatureTypeName;
|
||||
readonly layout: ManagedMetadataLayout;
|
||||
readonly issues: ManagedParseIssue[];
|
||||
readonly scope: string;
|
||||
readonly expectedKind?: "field" | "method";
|
||||
},
|
||||
): ManagedSignature => {
|
||||
const operands: TypeOperand[] = [];
|
||||
const decoded = decodeSignature(
|
||||
let decoded = decodeSignature(
|
||||
blob,
|
||||
layout,
|
||||
operands,
|
||||
(issue) => issues.push(issue),
|
||||
scope,
|
||||
);
|
||||
if (
|
||||
expectedKind !== undefined &&
|
||||
decoded.kind !== "unknown" &&
|
||||
decoded.kind !== expectedKind
|
||||
) {
|
||||
const detail = `Expected ${expectedKind} signature; decoded ${decoded.kind}${decoded.issue === null ? "" : `: ${decoded.issue}`}`;
|
||||
issues.push({ code: "invalid-blob", scope, offset: null, detail });
|
||||
decoded = { ...decoded, parse_status: "malformed", issue: detail };
|
||||
}
|
||||
return projectSignature(blob, rawSha256, typeName, decoded, operands);
|
||||
};
|
||||
|
||||
@@ -109,7 +109,7 @@ export const parseFields = (
|
||||
layout,
|
||||
signatureTypeName,
|
||||
issues,
|
||||
`Field:${token}.signature`,
|
||||
{ scope: `Field:${token}.signature`, kind: "field" },
|
||||
);
|
||||
core.set(token, {
|
||||
token,
|
||||
@@ -269,7 +269,7 @@ export const parseMemberRefs = (
|
||||
layout,
|
||||
signatureTypeName,
|
||||
issues,
|
||||
`MemberRef:${token}.signature`,
|
||||
{ scope: `MemberRef:${token}.signature` },
|
||||
);
|
||||
refs.push({
|
||||
token,
|
||||
@@ -391,7 +391,7 @@ export const parseMethods = ({
|
||||
layout,
|
||||
signatureTypeName,
|
||||
issues,
|
||||
`MethodDef:${token}.signature`,
|
||||
{ scope: `MethodDef:${token}.signature`, kind: "method" },
|
||||
);
|
||||
core.set(token, {
|
||||
...member,
|
||||
@@ -543,7 +543,7 @@ const budgetedSignature = (
|
||||
layout: ManagedMetadataLayout,
|
||||
signatureTypeName: SignatureTypeName,
|
||||
issues: ManagedParseIssue[],
|
||||
scope: string,
|
||||
owner: { readonly scope: string; readonly kind?: "field" | "method" },
|
||||
): ReturnType<typeof metadataSignature> => {
|
||||
const budget = managedDecodeBudget(layout);
|
||||
budget.admitTransient(
|
||||
@@ -557,7 +557,8 @@ const budgetedSignature = (
|
||||
typeName: signatureTypeName,
|
||||
layout,
|
||||
issues,
|
||||
scope,
|
||||
scope: owner.scope,
|
||||
...(owner.kind === undefined ? {} : { expectedKind: owner.kind }),
|
||||
}),
|
||||
budget,
|
||||
);
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import { metadataToken } from "./ManagedMetadataHeaps.js";
|
||||
import type { TypeMemberListFacts } from "./ManagedTypeMemberLists.js";
|
||||
import {
|
||||
appendManagedParseIssues,
|
||||
readManagedValue,
|
||||
uniqueManagedParseIssues,
|
||||
} from "./ManagedReaderFailure.js";
|
||||
@@ -214,7 +215,7 @@ export const readManagedMetadataInventory = (
|
||||
typeNames?: ManagedMetadataTypeNameLookup,
|
||||
): ManagedMetadataInventory => {
|
||||
const issues: ManagedParseIssue[] = [...memberLists.issues];
|
||||
issues.push(...identity.issues);
|
||||
appendManagedParseIssues(issues, identity.issues);
|
||||
const { module } = identity;
|
||||
const assembly =
|
||||
readManagedValue(
|
||||
|
||||
@@ -19,7 +19,10 @@ import {
|
||||
type ManagedCliHeader,
|
||||
type ManagedPeLayout,
|
||||
} from "./ManagedPeReader.js";
|
||||
import { ManagedReaderFailure } from "./ManagedReaderFailure.js";
|
||||
import {
|
||||
appendManagedParseIssues,
|
||||
ManagedReaderFailure,
|
||||
} from "./ManagedReaderFailure.js";
|
||||
import {
|
||||
buildNativeBoundaryInspection,
|
||||
cliNative,
|
||||
@@ -148,7 +151,7 @@ export const inspectManagedNativeBoundariesBytes = (
|
||||
}
|
||||
try {
|
||||
const heapExtent = Math.max(layout.strings.size, layout.blob?.size ?? 0);
|
||||
issues.push(...inventory.issues);
|
||||
appendManagedParseIssues(issues, inventory.issues);
|
||||
const moduleRefs = parseModuleRefs(bytes, layout, heapExtent, issues);
|
||||
const members = new Map([
|
||||
...parseFields(bytes, layout, heapExtent, issues),
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import { readManagedPeLayout } from "../../../../src/dotnet/ManagedPeReader.js";
|
||||
import {
|
||||
metadataRowOffset,
|
||||
readManagedMetadataLayout,
|
||||
} from "../../../../src/dotnet/ManagedMetadataLayout.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildManagedPeFixture,
|
||||
@@ -142,6 +147,40 @@ const inputWithImportScope = (scope: string | null) => {
|
||||
};
|
||||
};
|
||||
|
||||
it("separates CLR runtime-provided methods from unresolved file-backed native bodies", () => {
|
||||
const bytes = buildManagedPeFixture({ targetFramework: null });
|
||||
const pe = readManagedPeLayout(bytes);
|
||||
if (pe.cli === null) throw new Error("Missing fixture CLI");
|
||||
const layout = readManagedMetadataLayout(
|
||||
bytes,
|
||||
pe.rvaToOffset(pe.cli.metadata.rva, pe.cli.metadata.size, "fixture"),
|
||||
pe.cli.metadata.size,
|
||||
);
|
||||
bytes.writeUInt16LE(3, metadataRowOffset(layout, 6, 1) + 4);
|
||||
const target = managedPeFixtureTarget(bytes);
|
||||
const inspection = inspectManagedNativeBoundariesBytes(bytes, target);
|
||||
expect(inspection.native_implementations[0]?.boundary_kind).toBe(
|
||||
"runtime-provided",
|
||||
);
|
||||
const original = exampleInput().managed_boundaries;
|
||||
const managed = createEvidence(target, original.provider, {
|
||||
operation: "inspect_managed_native_boundaries",
|
||||
parameters: {},
|
||||
result: inspection,
|
||||
});
|
||||
const result = verifyManagedNativeBoundaries({
|
||||
managed_boundaries: managed,
|
||||
native_observations: [
|
||||
nativeFunctionEvidence("/fixtures/nativehelper.dll", "pe"),
|
||||
],
|
||||
});
|
||||
expect(result.summary.native_body_unresolved).toBe(0);
|
||||
expect(result.native_implementations).toMatchObject({
|
||||
unresolved: 0,
|
||||
runtime_provided: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ flags: 0x200, symbol: "ExampleA", format: "pe", status: "inferred" },
|
||||
{ flags: 0x202, symbol: "ExampleA", format: "pe", status: "inferred" },
|
||||
|
||||
Reference in New Issue
Block a user