Files
reverse-engineering-anything/src/application/AnalysisSettings.test.ts
T

220 lines
7.3 KiB
TypeScript

import { expect, it } from "vitest";
import { AnalysisSettings } from "./AnalysisSettings.js";
import {
runtimeAnalysisConfig,
runtimeAsarHeaderAdmission,
} from "../config/runtimeAnalysisSettings.js";
import { parseConfig } from "../config/parseConfig.js";
it.each([
["inspect_native_api", [64, 7]],
["trace_native_ui_action", []],
] as const)(
"reports the decompiler budgets consumed by %s",
(operation, values) => {
const store = new AnalysisSettings({
REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS: "7",
REA_GHIDRA_DECOMPILE_MAX_PAYLOAD_MIB: "64",
});
expect(
store
.inspect({
provider: "ghidra",
operation,
settings: [
"ghidra.decompile_max_payload_mib",
"ghidra.decompile_timeout_seconds",
],
})
.settings.map(({ value }) => value),
).toEqual(values);
},
);
it("updates atomically, restores launch values, and refuses stale or invalid writes", () => {
const store = new AnalysisSettings({ REA_ASAR_MAX_HEADER_BYTES: "20000000" });
const inspect = () => store.inspect({ settings: ["asar.max_header_bytes"] });
expect(inspect().settings[0]).toMatchObject({
value: 20000000,
source: "setting",
});
expect(
store.update({
changes: [
{ setting: "asar.max_header_bytes", action: "set", value: 30000000 },
],
expected_revision: 0,
}).ok,
).toBe(true);
expect(inspect()).toMatchObject({
revision: 1,
settings: [{ value: 30000000, source: "runtime" }],
});
expect(
store.update({
changes: [
{ setting: "asar.max_header_bytes", action: "set", value: 30000000 },
],
}),
).toMatchObject({ ok: true, value: { changed: false, revision: 1 } });
for (const changes of [
[
{ setting: "asar.max_header_bytes", action: "set", value: 100 },
{ setting: "wasm.command_timeout_ms", action: "set", value: 0 },
],
[
{ setting: "asar.max_header_bytes", action: "reset" },
{ setting: "asar.max_header_bytes", action: "set", value: 100 },
],
[{ setting: "NODE_OPTIONS", action: "set", value: 1 }],
])
expect(store.update({ changes }).ok).toBe(false);
expect(
store.update({
changes: [{ setting: "asar.max_header_bytes", action: "reset" }],
expected_revision: 0,
}).ok,
).toBe(false);
expect(inspect().revision).toBe(1);
expect(
store.update({
changes: [{ setting: "asar.max_header_bytes", action: "reset" }],
}),
).toMatchObject({
ok: true,
value: { revision: 2, settings: [{ value: 20000000, source: "setting" }] },
});
expect(new AnalysisSettings({}).inspect().revision).toBe(0);
});
it("keeps accepted operations and their typed provider views stable across concurrent updates", async () => {
const store = new AnalysisSettings({});
const config = parseConfig({});
if (!config.ok) throw config.error;
const live = runtimeAnalysisConfig(config.value, store.read);
const asar = runtimeAsarHeaderAdmission({}, store.read);
let release!: () => void;
const barrier = new Promise<void>((resolve) => {
release = resolve;
});
const pending = store.run(async () => {
await barrier;
expect(asar.maximumBytes).toBe(16777216);
expect(asar.listing?.maximumBytes).toBe(67108864);
expect(live.ghidraValueFlowMaxOperations).toBe(3000);
expect(live.ghidraValueFlowMaxInputsPerOperation).toBe(64);
expect(live.ghidraValueFlowMaxEdges).toBe(12000);
expect(live.ghidraStartupTimeoutMs).toBe(330000);
expect(live.regexMatchingDeadline.deadlineMs).toBe(5000);
expect(store.read().revision).toBe(0);
});
try {
const changed = store.update({
changes: [
{ setting: "asar.max_header_bytes", action: "set", value: 33554432 },
{ setting: "asar.max_listing_bytes", action: "set", value: 134217728 },
{
setting: "ghidra.value_flow_max_operations",
action: "set",
value: 6000,
},
{
setting: "ghidra.value_flow_max_inputs_per_operation",
action: "set",
value: 128,
},
{ setting: "ghidra.value_flow_max_edges", action: "set", value: 24000 },
{ setting: "ghidra.startup_timeout_ms", action: "set", value: 999999 },
{ setting: "regex.matching_deadline_ms", action: "set", value: 10000 },
],
});
expect(changed.ok).toBe(true);
} finally {
release();
await Promise.allSettled([pending]);
}
await pending;
store.run(() => {
expect(asar.maximumBytes).toBe(33554432);
expect(asar.listing?.maximumBytes).toBe(134217728);
expect(live.ghidraValueFlowMaxOperations).toBe(6000);
expect(live.ghidraValueFlowMaxInputsPerOperation).toBe(128);
expect(live.ghidraValueFlowMaxEdges).toBe(24000);
expect(live.ghidraStartupTimeoutMs).toBe(999999);
expect(live.regexMatchingDeadline.deadlineMs).toBe(10000);
});
});
it("exposes shared provider controls and preserves the explicit unbounded decompile value", () => {
const environment = {
REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS: "7",
REA_GHIDRA_DECOMPILE_MAX_PAYLOAD_MIB: "64",
};
const store = new AnalysisSettings(environment);
const parsed = parseConfig(environment);
if (!parsed.ok) throw parsed.error;
const config = runtimeAnalysisConfig(parsed.value, store.read);
expect(config.ghidraDecompileMaxPayloadMiB).toBe(64);
expect(
store
.inspect({ provider: "ghidra", operation: "search_strings" })
.settings.map(({ setting }) => setting),
).toEqual(["ghidra.startup_timeout_ms"]);
expect(
store
.inspect({ provider: "ida", operation: "search_strings" })
.settings.map(({ setting }) => setting),
).toEqual(["regex.matching_deadline_ms", "regex.worker_heap_request_mib"]);
expect(
store
.inspect({ operation: "inventory_artifact" })
.settings.map(({ setting }) => setting),
).toEqual(["asar.max_listing_bytes", "asar.max_header_bytes"]);
expect(
store.update({
changes: [
{
setting: "ghidra.decompile_timeout_seconds",
action: "set",
value: null,
},
{
setting: "ghidra.decompile_max_payload_mib",
action: "set",
value: null,
},
],
}).ok,
).toBe(true);
expect(config.ghidraDecompileTimeoutSeconds).toBeUndefined();
expect(config.ghidraDecompileMaxPayloadMiB).toBeUndefined();
expect(
store.inspect({ settings: ["ghidra.decompile_timeout_seconds"] })
.settings[0],
).toMatchObject({ value: null, launch_value: 7, source: "runtime" });
for (const [setting, value] of [
["asar.max_header_bytes", null],
["asar.max_listing_bytes", 0],
["asar.max_listing_bytes", Number.MAX_SAFE_INTEGER + 1],
["ghidra.value_flow_max_operations", 0],
["ghidra.value_flow_max_inputs_per_operation", 2147483648],
["ghidra.decompile_max_payload_mib", 2048],
["asar.max_header_bytes", 268435457],
["javascript.max_heap_mb", 127],
["regex.matching_deadline_ms", 1.5],
]) {
expect(
store.update({ changes: [{ setting, action: "set", value }] }).ok,
).toBe(false);
}
expect(store.inspect().revision).toBe(1);
expect(
store.update({
changes: [
{ setting: "ghidra.decompile_timeout_seconds", action: "reset" },
],
}).ok,
).toBe(true);
expect(config.ghidraDecompileTimeoutSeconds).toBe(7);
});