Files
rea/tests/boundary/cli/managedMemberSummaryCli.test.ts
morluto 667402846c fix(managed): validate metadata operands and locals declarations
Classify MemberRef operands from parsed signatures, validate signature table references and LocalVarSig blobs, and compare observed locals declaration presence. Keep partial CIL facts and transient-budget ownership at the method reader.

Fixes #6578
Fixes #6572
Fixes #6558
Fixes #6513
2026-10-23 18:30:45 +08:00

580 lines
19 KiB
TypeScript

import { pathToFileURL } from "node:url";
import {
mkdir,
readFile,
readdir,
realpath,
unlink,
writeFile,
} from "node:fs/promises";
import { join, resolve } from "node:path";
import { expect } from "vitest";
import { inspectAnalysisView } from "../../../src/application/analysisView/AnalysisViewService.js";
import {
analysisCliErrorEnvelopeSchema,
analysisErrorProjectionSchema,
} from "../../../src/contracts/errorSchemas.js";
import {
analysisViewResultSchema,
managedMemberSummaryViewResultSchema,
} from "../../../src/domain/analysisView/analysisView.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { parseEvidenceBundle } from "../../../src/domain/evidenceBundle.js";
import { managedMemberInspectionSchema } from "../../../src/domain/managed/managedArtifact.js";
import { buildManagedPeFixture } from "../../../src/dotnet/ManagedPe.fixture.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
const memberFixture = (): Buffer =>
buildManagedPeFixture({
methodRowCount: 2,
memberRefRowCount: 3,
// ldarg.0; ldfld field; call member ref; ldstr "ok"; pop; ret
ilBody: Buffer.from([
(18 << 2) | 2,
0x02,
0x7b,
1,
0,
0,
0x04,
0x28,
1,
0,
0,
0x0a,
0x72,
1,
0,
0,
0x70,
0x26,
0x2a,
]),
userStringHeap: Buffer.from([0, 5, 0x6f, 0, 0x6b, 0, 0]),
exportedTypes: [
{
namespace: "System",
name: "Object",
flags: 0x0020_0001,
implementationRaw: (1 << 2) | 1,
},
],
});
cliTest(
"advertises durable summary and portable paging options in CLI help",
async ({ cli }) => {
for (const command of [
"inspect-managed-members",
"inspect-analysis-view",
]) {
const result = await cli.run({ arguments: [command, "--help"] });
expect(result.exitCode).toBe(0);
expect(result.stdout).toContain("--evidence-bundle");
if (command === "inspect-managed-members") {
expect(result.stdout).toContain("--detail");
expect(result.stdout).toContain("summary");
expect(result.stdout).toContain("complete");
}
}
},
);
cliTest(
"classifies a field MemberRef loaded by ldtoken in the CLI inspection",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-managed-ldtoken-cli-");
const path = join(root, "field-token.dll");
const il = Buffer.from([0xd0, 1, 0, 0, 0x0a, 0x26, 0x2a]);
await writeFile(
path,
buildManagedPeFixture({
targetFramework: null,
memberRefName: "Counter",
memberRefParentRaw: 8,
memberRefSignature: Buffer.from([0x06, 0x08]),
ilBody: Buffer.concat([Buffer.from([(il.length << 2) | 2]), il]),
}),
);
const inspected = await cli.run({
arguments: ["inspect-managed-members", path, "--json"],
});
expect(inspected.exitCode).toBe(0);
const evidence = parseEvidence(inspected.json);
const result = managedMemberInspectionSchema.parse(
evidence.normalized_result,
);
expect(result.member_refs[0]?.signature.kind).toBe("field");
expect(result.methods[0]?.body.anchors[0]).toMatchObject({
opcode: "ldtoken",
operand_kind: "field",
operand: "0x0a000001",
});
},
);
cliTest(
"retains complete member Evidence for exact CLI pages after process exit and target removal",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-member-summary-cli-");
const path = join(root, "fixture.dll");
const bundlePath = join(root, "members.json");
await writeFile(path, memberFixture());
const typeNames = ["Fixture.Program", "Missing.Type"];
const arguments_ = [
"inspect-managed-members",
path,
...typeNames.flatMap((type) => ["--type", type]),
"--json",
];
const omitted = await cli.run({ arguments: arguments_ });
expect(omitted.exitCode).toBe(0);
const complete = parseEvidence(omitted.json);
const completeBundlePath = join(root, "complete.json");
const explicit = await cli.run({
arguments: [
...arguments_,
"--detail",
"complete",
"--evidence-bundle",
completeBundlePath,
],
});
expect(explicit.exitCode).toBe(0);
expect(parseEvidence(explicit.json).evidence_id).toBe(complete.evidence_id);
expect(
parseEvidenceBundle(
JSON.parse(await readFile(completeBundlePath, "utf8")),
).records[0]?.evidence_id,
).toBe(complete.evidence_id);
const reply = await cli.run({
cwd: root,
arguments: [
...arguments_,
"--detail",
"summary",
"--evidence-bundle",
"members.json",
],
});
expect(reply.exitCode).toBe(0);
const receipt = parseEvidence(reply.json);
const summary = managedMemberSummaryViewResultSchema.parse(
receipt.normalized_result,
);
const originalBytes = await readFile(bundlePath, "utf8");
const bundle = parseEvidenceBundle(JSON.parse(originalBytes));
expect(bundle.records).toHaveLength(1);
const parent = bundle.records[0]!;
expect(parent.evidence_id).toBe(complete.evidence_id);
expect(parent.normalized_result).toEqual(complete.normalized_result);
expect(parent.raw_result).toEqual(complete.raw_result);
expect(parent.subject).toEqual(complete.subject);
expect(parent.locations).toEqual(complete.locations);
expect(parent.parameters).toEqual({ type_names: typeNames });
expect(receipt.evidence_links).toEqual([parent.evidence_id]);
const portable = await inspectAnalysisView({
source: { kind: "inline", evidence: parent },
view: { kind: "summary" },
});
if (!portable.ok) throw portable.error;
expect(receipt.evidence_id).toBe(portable.value.evidence_id);
expect(receipt.normalized_result).toEqual(portable.value.normalized_result);
const report = managedMemberInspectionSchema.parse(
parent.normalized_result,
);
expect(summary.summary.selection).toEqual({
type_names: typeNames,
unmatched_type_names: ["Missing.Type"],
});
expect(summary.summary.coverage).toEqual(report.coverage);
expect(summary.summary.identity_scope).toEqual(report.identity_scope);
expect(summary.summary).not.toHaveProperty("member_refs");
expect(summary.summary).not.toHaveProperty("methods");
await unlink(path);
const otherDirectory = join(root, "later-process");
await mkdir(otherDirectory);
for (const collection of [
"types",
"exported_types",
"fields",
"methods",
"member_refs",
"call_edges",
"field_accesses",
"user_strings",
] as const) {
const rows =
collection === "user_strings"
? report.user_strings?.literals
: report[collection];
if (rows == null) throw new Error(`${collection} was not examined`);
expect(rows.length, collection).toBeGreaterThan(0);
expect(summary.summary.reported_counts[collection]).toBe(rows.length);
const paged: unknown[] = [];
for (let offset: number | null = 0; offset !== null;) {
const response = await cli.run({
cwd: otherDirectory,
arguments: [
"inspect-analysis-view",
JSON.stringify({
source: {
kind: "retained-evidence",
evidence_id: parent.evidence_id,
},
view: { kind: "managed-members", collection, offset, limit: 1 },
}),
"--evidence-bundle",
bundlePath,
"--json",
],
});
expect(response.exitCode).toBe(0);
const evidence = parseEvidence(response.json);
expect(evidence.evidence_links).toEqual([parent.evidence_id]);
expect(evidence.subject).toEqual(parent.subject);
const page = analysisViewResultSchema.parse(evidence.normalized_result);
if (page.kind !== "managed-members") throw new Error("Wrong page kind");
expect(page.page.collection).toBe(collection);
expect(page.selection).toEqual(report.selection);
expect(page.source_coverage).toEqual(report.coverage);
expect(page.source_metadata_status).toBe(report.metadata.status);
expect(page.coverage.total).toBe(rows.length);
paged.push(...page.page.rows);
offset = page.coverage.next_offset;
}
expect(paged).toEqual(rows);
}
expect(await readFile(bundlePath, "utf8")).toBe(originalBytes);
},
180_000,
);
cliTest(
"refuses summary without a durable destination before opening the target",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-member-summary-missing-");
const result = await cli.run({
arguments: [
"inspect-managed-members",
join(root, "absent.dll"),
"--detail",
"summary",
"--json",
],
});
expect(result.exitCode).toBe(1);
expect(result.json).toMatchObject({
code: "invalid_request",
details: {
issues: [
expect.objectContaining({
path: ["evidence_bundle"],
message: expect.stringContaining("summary"),
}),
],
},
});
expect(result.json).not.toHaveProperty("evidence_id");
},
);
cliTest(
"does not replace caller files or report successful summary when bundle publication fails",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-member-summary-write-");
const path = join(root, "fixture.dll");
const bytes = buildManagedPeFixture();
await writeFile(path, bytes);
const complete = await cli.run({
arguments: ["inspect-managed-members", path, "--json"],
});
expect(complete.exitCode).toBe(0);
const parent = parseEvidence(complete.json);
for (const destination of [path, join(root, "absent", "bundle.json")]) {
const result = await cli.run({
arguments: [
"inspect-managed-members",
path,
"--detail",
"summary",
"--evidence-bundle",
destination,
"--json",
],
});
expect(result.exitCode).toBe(1);
expect(result.json).toMatchObject({
code: "invalid_request",
details: {
path: destination,
reason: destination === path ? "exists" : "missing",
},
});
const {
error: _label,
input_path: _inputPath,
input_reason: _inputReason,
...projection
} = analysisCliErrorEnvelopeSchema.parse(result.json);
const failure = analysisErrorProjectionSchema.parse(projection);
if (failure.code !== "invalid_request")
throw new Error(`Unexpected publication failure: ${failure.code}`);
const retained = parseEvidence(failure.details?.partial_observation);
expect(retained.evidence_id).toBe(parent.evidence_id);
expect(retained.normalized_result).toEqual(parent.normalized_result);
expect(result.json).not.toHaveProperty("evidence_id");
expect(result.json).not.toHaveProperty("normalized_result");
}
expect(await readFile(path)).toEqual(bytes);
expect(await readdir(root)).toEqual(["fixture.dll"]);
},
);
cliTest(
"preserves provider refusal without publishing an Evidence bundle",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-member-summary-provider-");
const path = join(root, "absent.dll");
const destination = join(root, "bundle.json");
const complete = await cli.run({
arguments: ["inspect-managed-members", path, "--json"],
});
const summary = await cli.run({
arguments: [
"inspect-managed-members",
path,
"--detail",
"summary",
"--evidence-bundle",
destination,
"--json",
],
});
expect(complete.exitCode).toBe(1);
expect(summary.exitCode).toBe(1);
expect(summary.json).toEqual(complete.json);
await expect(readFile(destination)).rejects.toMatchObject({
code: "ENOENT",
});
},
);
cliTest(
"keeps unavailable managed member coverage explicit in saved summary Evidence",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-member-summary-unknown-");
const path = join(root, "malformed.dll");
const destination = join(root, "bundle.json");
await writeFile(path, buildManagedPeFixture({ virtualSectionSize: 0 }));
const result = await cli.run({
arguments: [
"inspect-managed-members",
path,
"--detail",
"summary",
"--evidence-bundle",
destination,
"--json",
],
});
expect(result.exitCode).toBe(0);
const summary = managedMemberSummaryViewResultSchema.parse(
parseEvidence(result.json).normalized_result,
);
expect(summary.summary).toMatchObject({
metadata: { status: "malformed" },
coverage: { state: "unavailable" },
reported_counts: { methods: 0 },
});
expect(summary.unknowns.length).toBeGreaterThan(0);
const bundle = parseEvidenceBundle(
JSON.parse(await readFile(destination, "utf8")),
);
const report = managedMemberInspectionSchema.parse(
bundle.records[0]!.normalized_result,
);
expect(summary.summary.coverage).toEqual(report.coverage);
expect(summary.limitations).toEqual(report.limitations);
},
);
cliTest(
"preserves decoded CIL and reports invalid LocalVarSigTok through CLI Evidence",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-member-locals-token-");
const path = join(root, "fixture.dll");
const body = Buffer.alloc(13);
body.writeUInt16LE(0x3003, 0);
body.writeUInt16LE(8, 2);
body.writeUInt32LE(1, 4);
body.writeUInt32LE(0x06000001, 8);
body[12] = 0x2a;
await writeFile(path, buildManagedPeFixture({ ilBody: body }));
const result = await cli.run({
arguments: ["inspect-managed-members", path, "--json"],
});
expect(result.exitCode).toBe(0);
const evidence = parseEvidence(result.json);
const inspection = managedMemberInspectionSchema.parse(
evidence.normalized_result,
);
expect(inspection.methods[0]?.body).toMatchObject({
status: "malformed",
local_var_sig_token: "0x06000001",
anchors: [expect.objectContaining({ opcode: "ret" })],
});
expect(inspection.coverage).toMatchObject({
state: "partial",
issues: [expect.objectContaining({ code: "invalid-row" })],
});
},
);
cliTest.for(
(process.platform === "win32"
? (["event"] as const)
: (["event", "os"] as const)
).flatMap((delivery) => [
{ signal: "SIGINT" as const, status: 130, delivery },
{ signal: "SIGTERM" as const, status: 143, delivery },
]),
)(
"keeps a committed receipt when $signal ($delivery) is queued during final cleanup",
async ({ signal, status, delivery }, { cli }) => {
const root = await createTestTempDirectory("rea-managed-queued-signal-");
const target = join(root, "fixture.dll");
const destination = join(root, "parent.json");
const marker = join(root, "signal.json");
const preload = resolve("tests/fixtures/evidencePublicationPreload.mjs");
const prefix = join(await realpath(root), ".rea-write-");
await writeFile(target, memberFixture());
const reply = await cli.run({
cwd: root,
arguments: [
"inspect-managed-members",
target,
"--type",
"Fixture.Program",
"--detail",
"summary",
"--evidence-bundle",
destination,
"--json",
],
environment: {
HOME: root,
REA_LOG_LEVEL: "silent",
REA_TEST_EVIDENCE_PUBLICATION: JSON.stringify({
prefix,
marker,
signal,
delivery,
}),
NODE_OPTIONS: `--max-old-space-size=2048 --import=${pathToFileURL(preload).href}`,
},
});
expect(reply.exitCode).toBe(status);
const { error: _label, ...projection } =
analysisCliErrorEnvelopeSchema.parse(reply.json);
const error = analysisErrorProjectionSchema.parse(projection);
expect(error.code).toBe("cancelled");
expect(error.details?.operation).toBe("inspect_managed_members");
const bytes = await readFile(destination);
const parent = parseEvidenceBundle(JSON.parse(bytes.toString()))
.records[0]!;
expect(error.details).toMatchObject({
diagnostics: {
evidence_bundle: { path: destination, bytes: bytes.length },
},
});
const partial = parseEvidence(error.details?.partial_observation);
expect(partial.evidence_links).toEqual([parent.evidence_id]);
expect(partial.normalized_result).toMatchObject({
kind: "summary",
parent_evidence_id: parent.evidence_id,
});
expect(JSON.parse(await readFile(marker, "utf8"))).toMatchObject({
signal,
listeners: expect.any(Number),
});
expect(
JSON.parse(await readFile(marker, "utf8")).listeners,
).toBeGreaterThan(0);
expect(
(await readdir(root)).some((name) => name.startsWith(".rea-write-")),
).toBe(false);
},
);
cliTest.for(
(process.platform === "win32"
? (["event"] as const)
: (["event", "os"] as const)
).flatMap((delivery) => [
{ signal: "SIGINT" as const, status: 130, delivery },
{ signal: "SIGTERM" as const, status: 143, delivery },
]),
)(
"retains complete default Evidence when $signal ($delivery) is queued during sealing",
async ({ signal, status, delivery }, { cli }) => {
const root = await createTestTempDirectory("rea-managed-complete-signal-");
const target = join(root, "fixture.dll"),
marker = join(root, "signal.json");
await writeFile(target, memberFixture());
const preload = resolve(
"tests/fixtures/evidenceCompletionSignalPreload.mjs",
);
const reply = await cli.run({
arguments: [
"inspect-managed-members",
target,
"--type",
"Fixture.Program",
"--json",
],
environment: {
HOME: root,
REA_LOG_LEVEL: "silent",
REA_TEST_EVIDENCE_COMPLETION: JSON.stringify({
operation: "inspect_managed_members",
target_path: target,
marker,
signal,
delivery,
}),
NODE_OPTIONS: `--max-old-space-size=2048 --import=${pathToFileURL(preload).href}`,
},
});
expect(reply.exitCode).toBe(status);
const { error: _label, ...projection } =
analysisCliErrorEnvelopeSchema.parse(reply.json);
const error = analysisErrorProjectionSchema.parse(projection);
expect(error.code).toBe("cancelled");
expect(error.details?.operation).toBe("inspect_managed_members");
const partial = parseEvidence(error.details?.partial_observation);
expect(partial.operation).toBe("inspect_managed_members");
expect(partial.subject?.local_path).toBe(target);
expect(
managedMemberInspectionSchema.parse(partial.normalized_result).methods,
).toHaveLength(2);
const proof = JSON.parse(await readFile(marker, "utf8"));
expect(proof).toMatchObject({
evidence_id: partial.evidence_id,
signal,
delivery,
});
expect(proof.listeners).toBeGreaterThan(0);
expect((await readdir(root)).sort()).toEqual([
"fixture.dll",
"signal.json",
]);
},
);