Files
rea/tests/acceptance/managedCliOutput.test.ts
T
morluto 87fa1b613d fix(managed): retain nested names and ReadyToRun observations
Resolve attribute display names from validated nesting and parsed member facts, admit composed names before allocation, and preserve unknown malformed chains. Keep ReadyToRun classification ahead of metadata hints while retaining each observed hint.

Fixes #6815
Fixes #6810
2026-10-24 00:11:41 +08:00

522 lines
17 KiB
TypeScript

import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect } from "vitest";
import { elf, thinMach } from "../../src/domain/binaryTarget.fixture.js";
import { buildManagedPeFixture } from "../../src/dotnet/ManagedPe.fixture.js";
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
import { cliTest } from "../support/cli/cliFixture.js";
cliTest(
"managed CLI preserves malformed prefix evidence without a complete CIL fingerprint",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-managed-cil-prefix-");
for (const [name, il, status, coverage] of [
["dangling", [0xfe, 0x13], "malformed", "partial"],
["complete", [0x00, 0x2a], "present", "complete"],
] as const) {
const path = join(root, `${name}.dll`);
await writeFile(
path,
buildManagedPeFixture({
ilBody: Buffer.from([(il.length << 2) | 2, ...il]),
}),
);
const result = await cli.run({
arguments: ["inspect-managed-members", path, "--json"],
});
expect(result.exitCode).toBe(0);
expect(result.json).toMatchObject({
operation: "inspect_managed_members",
subject: { local_path: path },
normalized_result: {
coverage: { state: coverage },
user_strings: { state: coverage },
methods: [
expect.objectContaining({
body: expect.objectContaining({
status,
il_sha256: expect.any(String),
normalized_il_sha256:
status === "malformed" ? null : expect.any(String),
issue:
status === "malformed"
? "CIL prefix chain at IL offset 0 has no following instruction"
: null,
}),
}),
],
},
});
}
},
);
cliTest(
"managed CLI retains metadata and CIL after repeated local constructor owner lookups",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-managed-row-lookup-");
const path = join(root, "local-attributes.dll");
await writeFile(
path,
buildManagedPeFixture({
additionalEmptyTypes: 512,
customAttributeRowCount: 512,
customAttributeTypeRaw: (1 << 3) | 2,
typeName: "LookupAttribute",
extendsRaw: 0,
methodName: ".ctor",
methodFlags: 0x1886,
methodSignature: Buffer.from([0x20, 0x01, 0x01, 0x0e]),
ilBody: Buffer.from([0x06, 0x2a]),
}),
);
const artifact = await cli.run({
arguments: ["inspect-managed-artifact", path, "--json"],
});
expect(artifact.exitCode).toBe(0);
expect(artifact.json).toMatchObject({
normalized_result: {
coverage: { state: "complete", issues: [] },
attributes: expect.arrayContaining([
expect.objectContaining({
type_name: "Fixture.LookupAttribute",
constructor_token: "0x06000001",
}),
]),
},
});
expect(artifact.json).toHaveProperty(
"normalized_result.attributes.length",
512,
);
const members = await cli.run({
arguments: ["inspect-managed-members", path, "--json"],
});
expect(members.exitCode).toBe(0);
expect(members.json).toMatchObject({
normalized_result: {
coverage: { state: "complete", issues: [] },
types: expect.arrayContaining([
expect.objectContaining({ full_name: "Fixture.Empty0" }),
expect.objectContaining({ full_name: "Fixture.Empty511" }),
expect.objectContaining({ full_name: "Fixture.LookupAttribute" }),
]),
methods: [
expect.objectContaining({
name: ".ctor",
declaring_type: "Fixture.LookupAttribute",
body: expect.objectContaining({
status: "present",
instruction_count: 1,
}),
}),
],
},
});
expect(members.json).toHaveProperty("normalized_result.types.length", 513);
},
);
cliTest(
"managed CLI separates .NET Standard API targets from runtime evidence",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-managed-runtime-family-");
for (const [name, targetFramework, readyToRun, runtimeFamily] of [
["standard", ".NETStandard,Version=v2.0", false, "unknown"],
["core", ".NETCoreApp,Version=v8.0", false, "modern-dotnet"],
["standard-r2r", ".NETStandard,Version=v2.0", true, "modern-dotnet"],
] as const) {
const path = join(root, `${name}.dll`);
await writeFile(
path,
buildManagedPeFixture({ targetFramework, readyToRun }),
);
const result = await cli.run({
arguments: ["inspect-managed-artifact", path, "--json"],
});
expect(result.exitCode).toBe(0);
expect(result.json).toMatchObject({
operation: "inspect_managed_artifact",
subject: { local_path: path },
normalized_result: {
classification: {
status: "managed",
runtime_family: runtimeFamily,
implementation: readyToRun ? "cil-and-ready-to-run" : "cil",
evidence: expect.arrayContaining([
expect.objectContaining({
code: readyToRun
? "ready-to-run-signature-observed"
: "target-framework-observed",
detail: expect.stringContaining(
readyToRun
? "ReadyToRun signature"
: name === "standard"
? "does not identify a runtime family"
: "names modern .NET",
),
file_offset: expect.any(Number),
}),
]),
},
target_frameworks: [targetFramework],
coverage: { state: "complete", issues: [] },
},
});
}
const hintedPath = join(root, "ready-to-run-with-runtime-hints.dll");
const hintedTarget = ".NETFramework,Version=v4.8";
await writeFile(
hintedPath,
buildManagedPeFixture({
readyToRun: true,
targetFramework: hintedTarget,
references: ["UnityEngine.CoreModule"],
}),
);
const hinted = await cli.run({
arguments: ["inspect-managed-artifact", hintedPath, "--json"],
});
expect(hinted.exitCode).toBe(0);
expect(hinted.json).toMatchObject({
normalized_result: {
classification: {
runtime_family: "modern-dotnet",
implementation: "cil-and-ready-to-run",
evidence: expect.arrayContaining([
expect.objectContaining({
code: "ready-to-run-signature-observed",
}),
expect.objectContaining({ code: "unity-reference-observed" }),
expect.objectContaining({ code: "target-framework-observed" }),
]),
},
target_frameworks: [hintedTarget],
},
});
},
);
cliTest(
"managed CLI qualifies a local nested custom-attribute type name",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-managed-nested-attribute-");
const path = join(root, "nested-attribute.dll");
await writeFile(
path,
buildManagedPeFixture({
additionalEmptyTypes: 1,
customAttributeTypeRaw: (1 << 3) | 2,
nestedTypes: [[2, 1]],
typeName: "InnerAttribute",
typeNamespace: "",
}),
);
const result = await cli.run({
arguments: ["inspect-managed-artifact", path, "--json"],
});
expect(result.exitCode).toBe(0);
expect(result.json).toMatchObject({
normalized_result: {
attributes: [
expect.objectContaining({
type_name: "Empty0+InnerAttribute",
}),
],
coverage: { state: "complete", issues: [] },
},
});
},
);
cliTest(
"managed CLI preserves evidence, filters, envelopes, and bounded token counts",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-managed-cli-output-");
const path = join(root, "fixture.dll");
await writeFile(
path,
buildManagedPeFixture({
ilBody: Buffer.from([0x0e, 0x17, 0x26, 0x2a]),
}),
);
const argv = ["inspect-managed-members", path, "--json"];
const result = await cli.run({ arguments: argv });
expect(result.exitCode).toBe(0);
expect(result.json).toMatchObject({
operation: "inspect_managed_members",
provider: { id: "rea-dotnet-static" },
subject: { local_path: path },
normalized_result: {
methods: [
expect.objectContaining({
token: expect.any(String),
body: expect.objectContaining({
status: "present",
decoded_instruction_count: 3,
anchors: [
{
il_offset: 0,
opcode: "ldc.i4.1",
operand_kind: "none",
operand: null,
},
{
il_offset: 1,
opcode: "pop",
operand_kind: "none",
operand: null,
},
{
il_offset: 2,
opcode: "ret",
operand_kind: "none",
operand: null,
},
],
}),
}),
],
},
});
const filtered = await cli.run({
arguments: [...argv, "--filter-output", "operation", "--full-output"],
});
expect(filtered.exitCode).toBe(0);
expect(filtered.json).toMatchObject({
ok: true,
data: "inspect_managed_members",
meta: {
command: "inspect-managed-members",
duration: expect.stringMatching(/^\d+ms$/u),
},
});
const count = await cli.run({
arguments: [...argv, "--filter-output", "operation", "--token-count"],
});
expect(count.exitCode).toBe(0);
expect(count.stdout).toMatch(/^\d+\n$/u);
const omittedCount = await cli.run({
arguments: [...argv, "--filter-output", "toString", "--token-count"],
});
expect(omittedCount.exitCode).toBe(0);
expect(omittedCount.stdout).toBe("0\n");
const jsonl = await cli.run({
arguments: [
"inspect-managed-members",
path,
"--format",
"jsonl",
"--filter-output",
"operation",
],
});
expect(jsonl.exitCode).toBe(0);
expect(jsonl.json).toBe("inspect_managed_members");
},
);
cliTest(
"managed CLI rejects repeated or empty type selections as invalid input",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-managed-cli-types-");
const path = join(root, "fixture.dll");
await writeFile(path, buildManagedPeFixture());
for (const [types, message] of [
[["Fixture.Empty0", "Fixture.Empty0"], "must not repeat a name"],
[[""], ">=1 characters"],
] as const) {
const result = await cli.run({
arguments: [
"inspect-managed-members",
path,
...types.flatMap((type) => ["--type", type]),
"--json",
],
});
expect(result.exitCode).toBe(1);
expect(result.json).toMatchObject({
code: "VALIDATION_ERROR",
fieldErrors: [
expect.objectContaining({
path: expect.stringMatching(/^type/u),
message: expect.stringContaining(message),
}),
],
});
}
},
);
cliTest(
"managed CLI bounds CIL claims by section virtual ranges",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-managed-virtual-size-");
for (const virtualSectionSize of [0, 0x800, 0x801, 0x802]) {
// The two-byte tiny method starts at RVA 0x2800. Its header alone fits
// at size 0x801, and its full CIL fits exactly at size 0x802.
const bytes = buildManagedPeFixture({
virtualSectionSize,
ilBody: Buffer.from([0x06, 0x2a]),
});
const path = join(root, `virtual-${virtualSectionSize}.dll`);
await writeFile(path, bytes);
const result = await cli.run({
arguments: ["inspect-managed-members", path, "--json"],
});
expect(result.exitCode).toBe(0);
expect(result.json).toMatchObject({
normalized_result:
virtualSectionSize === 0
? {
methods: [],
metadata: { status: "malformed" },
coverage: { state: "unavailable" },
}
: {
methods: [
{
rva: 0x2800,
body:
virtualSectionSize === 0x802
? {
status: "present",
normalized_il_sha256: expect.any(String),
}
: {
status: "malformed",
normalized_il_sha256: null,
},
},
],
coverage: {
state: virtualSectionSize === 0x802 ? "complete" : "partial",
},
},
});
}
},
);
cliTest(
"managed CLI distinguishes raw padding from overlapping virtual sections",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-managed-section-identity-");
for (const virtualSectionSize of [0x800, 0x900]) {
const bytes = buildManagedPeFixture({ virtualSectionSize });
const coff = bytes.readUInt32LE(0x3c) + 4;
const firstSection = coff + 20 + bytes.readUInt16LE(coff + 16);
const secondSection = firstSection + 40;
bytes.writeUInt16LE(2, coff + 2);
bytes.write(".il", secondSection, "ascii");
bytes.writeUInt32LE(0x100, secondSection + 8);
bytes.writeUInt32LE(0x2800, secondSection + 12);
bytes.writeUInt32LE(0x100, secondSection + 16);
bytes.writeUInt32LE(0x0a00, secondSection + 20);
const path = join(root, `sections-${virtualSectionSize}.dll`);
await writeFile(path, bytes);
const result = await cli.run({
arguments: ["inspect-managed-members", path, "--json"],
});
expect(result.exitCode).toBe(0);
expect(result.json).toMatchObject({
normalized_result: {
methods: [
{
rva: 0x2800,
body:
virtualSectionSize === 0x800
? {
status: "present",
file_offset: 0x0a00,
normalized_il_sha256: expect.any(String),
}
: {
status: "malformed",
normalized_il_sha256: null,
issue: expect.stringContaining("ambiguous overlapping"),
},
},
],
coverage: {
state: virtualSectionSize === 0x800 ? "complete" : "partial",
},
},
});
}
},
);
cliTest(
"managed CLI reports a section table past EOF as invalid input",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-managed-cli-sections-");
const path = join(root, "fixture.dll");
const bytes = buildManagedPeFixture();
const pe = bytes.readUInt32LE(0x3c);
const sections = pe + 24 + bytes.readUInt16LE(pe + 20);
bytes.writeUInt32LE(bytes.length + 0x1000, sections + 20);
await writeFile(path, bytes);
for (const command of [
"inspect-managed-artifact",
"inspect-managed-members",
"inspect-managed-native-boundaries",
]) {
const result = await cli.run({ arguments: [command, path, "--json"] });
expect(result.exitCode).toBe(1);
expect(result.json).toMatchObject({
code: "invalid_request",
details: {
issues: [
{
path: ["path"],
reason: "invalid_format",
message: expect.stringContaining("pe.section-data"),
},
],
},
});
}
},
);
cliTest(
"managed CLI refuses non-PE executables as unsupported targets",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-managed-non-pe-");
for (const [name, bytes, format] of [
["native.elf", elf(2, 1, 0x3e), "elf"],
["native.macho", thinMach(0xfeedfacf, 0x01000007), "mach-o"],
] as const) {
const path = join(root, name);
await writeFile(path, bytes);
for (const command of [
"inspect-managed-artifact",
"inspect-managed-members",
"inspect-managed-native-boundaries",
]) {
const result = await cli.run({ arguments: [command, path, "--json"] });
expect(result.exitCode).not.toBe(0);
expect(result.json).toMatchObject({
code: "unsupported_target",
category: "unsupported_target",
retryable: false,
remediation: {
action:
"Select a target supported by this operation or choose an operation supporting the reported target format.",
},
details: {
operation: command.replaceAll("-", "_"),
path,
reason: `Managed static triage requires a PE target; observed ${format}.`,
},
});
}
}
},
);