mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
* refactor(errors): require a reason for every analysis cancellation Add cancelledBy, the one constructor for AnalysisCancelledError. Its reason argument is required: an aborted signal, an earlier cancellation, a reason carried from an inner failure, or an OwnerCancellation when REA closed the work's owner itself. Closed adb, apktool, firmware, sqlite, gdb, web-bundle analyzer and binary-session record owners now report OwnerCancellation.ownerClosed instead of an absent reason. Firmware's predecessor wait returns its cancellation as a Result, removing an unreachable reason-less fallback. An architecture guard rejects new direct constructions, with a counted per-file baseline that can only shrink. * refactor(errors): share one aborted-signal predicate with cancelledBy * refactor(process): return a departing caller's reason from a shared startup wait * refactor(providers): build decompiler-provider cancellations with a required reason (#10587) Migrate every AnalysisCancelledError construction under src/ghidra, src/ida, src/cutter, src/jeb and src/kuna to cancelledBy and lower the guard baseline from 671 to 572. Closed Kuna target commands report OwnerCancellation.ownerClosed instead of an absent reason, and Kuna and JEB close abort their lifetimes with that typed reason. * fix(errors): read the reason of any signal-shaped cancellation source * refactor(native): build debugger and native cancellations with a required reason Migrate every AnalysisCancelledError construction under src/native, src/gdb and src/rizin to cancelledBy and delete Rizin's local rizinCancellation constructor; its reason choice becomes a CancellationReason. GDB close aborts active startups with OwnerCancellation.ownerClosed. * test(ci): cover the counted-baseline comparison behind the architecture guard * refactor(native): drop an unused cancellation import from native UI observation
244 lines
8.6 KiB
TypeScript
244 lines
8.6 KiB
TypeScript
import type { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
|
|
import { DarwinProcessOwnershipInspectionError } from "../process/DarwinProcessRunTokenReader.js";
|
|
import {
|
|
ProcessOwnershipInspectionCancelled,
|
|
ProcessOwnershipIdentityBatchFailure,
|
|
} from "../process/ProcessInspectionCancellation.js";
|
|
import {
|
|
retainProcessInspectionCleanup,
|
|
processInspectionCancellationError,
|
|
} from "../providers/OwnedCommandCleanup.js";
|
|
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
|
import {
|
|
AnalysisCapabilityUnavailableError,
|
|
AnalysisResourceConstraintError,
|
|
AnalysisTimeoutError,
|
|
cancelledBy,
|
|
carriedReason,
|
|
} from "../domain/analysisErrorCore.js";
|
|
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
|
|
import { NATIVE_MACOS_PROVIDER_IDENTITY as IDENTITY } from "./NativeMacosProviderIdentity.js";
|
|
import {
|
|
DEFAULT_NATIVE_COMMAND_TIMEOUT_MS,
|
|
fixedNativeAllowance,
|
|
nativeDeadlineRecovery,
|
|
} from "../domain/native/nativeDeadlines.js";
|
|
import {
|
|
NATIVE_COMMAND_OUTPUT_BUDGET_BYTES,
|
|
NATIVE_FAILURE_DIAGNOSTIC_BYTES,
|
|
type NativeCommandFailure,
|
|
} from "./CommandRunner.js";
|
|
|
|
/** Operation-owned text for a command deadline or unavailable requirement. */
|
|
export interface NativeCommandFailurePresentation {
|
|
readonly unavailableMessage?: string;
|
|
readonly timeoutRecovery?: {
|
|
readonly userMessage: string;
|
|
readonly remediationAction: string;
|
|
};
|
|
}
|
|
|
|
/** Translate the command owner's facts once, preserving capture and cleanup authority. */
|
|
export const translateNativeCommandFailure = (
|
|
operation: string,
|
|
failure: NativeCommandFailure,
|
|
resources: ArtifactResourceScope,
|
|
timeoutMs = DEFAULT_NATIVE_COMMAND_TIMEOUT_MS,
|
|
presentation: NativeCommandFailurePresentation = {},
|
|
): AnalysisError => {
|
|
const cleanupOwners = [
|
|
...failure.cleanupFailures,
|
|
...(failure.cleanupOwner === undefined
|
|
? []
|
|
: [
|
|
{
|
|
owner: failure.cleanupOwner,
|
|
marker: failure.cleanupMarker,
|
|
reason:
|
|
failure.cleanupFailure ??
|
|
"native command cleanup remains incomplete",
|
|
resources: failure.cleanupResources,
|
|
},
|
|
]),
|
|
];
|
|
const markers = cleanupOwners.flatMap(({ marker }) =>
|
|
marker === undefined ? [] : [marker],
|
|
);
|
|
const bindCleanup = (error: AnalysisError): AnalysisError =>
|
|
cleanupOwners.length === 0 ||
|
|
cleanupOwners.some(({ marker }) => marker === undefined)
|
|
? error
|
|
: error.bindCleanupDependencies([...new Set(markers)]);
|
|
const preparation = failure.preparationFailure;
|
|
if (preparation !== undefined) {
|
|
const darwin =
|
|
preparation instanceof DarwinProcessOwnershipInspectionError
|
|
? preparation
|
|
: undefined;
|
|
const compiler = darwin?.compilerOutcome;
|
|
const options = {
|
|
cause: failure,
|
|
...(darwin?.capturedOutput === undefined
|
|
? {}
|
|
: { capturedOutput: darwin?.capturedOutput }),
|
|
diagnostics: {
|
|
phase: "process-preparation",
|
|
tool: failure.tool,
|
|
reason: failure.reason,
|
|
detail: preparation.message,
|
|
...(compiler === undefined
|
|
? {}
|
|
: {
|
|
compiler_outcome: {
|
|
outcome: compiler.outcome,
|
|
code: compiler.code,
|
|
signal: compiler.signal,
|
|
...(compiler.timeoutMs === undefined
|
|
? {}
|
|
: { timeout_ms: compiler.timeoutMs }),
|
|
},
|
|
}),
|
|
},
|
|
};
|
|
const cancelled = processInspectionCancellationError(
|
|
preparation,
|
|
operation,
|
|
);
|
|
const cancellationPrimary =
|
|
preparation instanceof ProcessOwnershipInspectionCancelled ||
|
|
(preparation instanceof ProcessOwnershipIdentityBatchFailure &&
|
|
preparation.origin === "cancelled") ||
|
|
(darwin !== undefined && failure.reason === "cancelled");
|
|
const error =
|
|
failure.reason === "timeout"
|
|
? new AnalysisTimeoutError(operation, timeoutMs, {
|
|
...options,
|
|
...(presentation.timeoutRecovery ??
|
|
nativeDeadlineRecovery({
|
|
timeoutMs,
|
|
setting: "native_command_timeout_ms",
|
|
cliFlag: "--native-command-timeout-ms",
|
|
defaultMs: DEFAULT_NATIVE_COMMAND_TIMEOUT_MS,
|
|
phase: "macOS native command deadline",
|
|
unchanged: `The ${String(NATIVE_COMMAND_OUTPUT_BUDGET_BYTES)}-byte command output allowance and the ${String(NATIVE_FAILURE_DIAGNOSTIC_BYTES)}-byte failure-diagnostic excerpt have no runtime override.`,
|
|
})),
|
|
})
|
|
: cancellationPrimary && cancelled !== undefined
|
|
? cancelled
|
|
: // The ownership helper's compiler deadline is a retryable timeout
|
|
// with its own setting, not a provider failure.
|
|
darwin?.compileTimeout !== undefined
|
|
? new AnalysisTimeoutError(
|
|
operation,
|
|
darwin.compileTimeout.timeoutMs,
|
|
{
|
|
...options,
|
|
userMessage: darwin.compileTimeout.userMessage,
|
|
remediationAction: darwin.compileTimeout.remediationAction,
|
|
},
|
|
)
|
|
: new ProviderAdapterError(IDENTITY.id, operation, options);
|
|
return retainProcessInspectionCleanup(
|
|
preparation,
|
|
bindCleanup(error),
|
|
resources,
|
|
operation,
|
|
);
|
|
}
|
|
const capturedOutput =
|
|
failure.capture === null
|
|
? undefined
|
|
: {
|
|
stdout: failure.capture.stdout,
|
|
stderr: failure.capture.stderr,
|
|
truncated: failure.capture.truncated,
|
|
stdout_bytes: failure.capture.stdoutBytes,
|
|
stderr_bytes: failure.capture.stderrBytes,
|
|
exit_code: failure.capture.exitCode,
|
|
signal: failure.capture.signal,
|
|
};
|
|
const cleanup =
|
|
failure.cleanupFailure === null
|
|
? undefined
|
|
: {
|
|
reason: failure.cleanupFailure,
|
|
resources: [...failure.cleanupResources],
|
|
};
|
|
const errorOptionsWithoutCause = {
|
|
...(capturedOutput === undefined ? {} : { capturedOutput }),
|
|
...(cleanup === undefined ? {} : { cleanup }),
|
|
};
|
|
const errorOptions = { cause: failure, ...errorOptionsWithoutCause };
|
|
if (failure.reason === "unavailable") {
|
|
const reason =
|
|
presentation.unavailableMessage ??
|
|
`${failure.tool} is unavailable through xcrun.`;
|
|
return bindCleanup(
|
|
new AnalysisCapabilityUnavailableError(IDENTITY.id, operation, reason, {
|
|
...errorOptions,
|
|
userMessage: reason,
|
|
}),
|
|
);
|
|
}
|
|
if (failure.reason === "cancelled")
|
|
return bindCleanup(
|
|
cancelledBy(
|
|
operation,
|
|
carriedReason(
|
|
failure.cancellation === undefined
|
|
? failure
|
|
: failure.cancellation.reason,
|
|
),
|
|
{
|
|
...errorOptionsWithoutCause,
|
|
},
|
|
),
|
|
);
|
|
if (failure.reason === "timeout")
|
|
return bindCleanup(
|
|
new AnalysisTimeoutError(operation, timeoutMs, {
|
|
...errorOptions,
|
|
...(presentation.timeoutRecovery ??
|
|
nativeDeadlineRecovery({
|
|
timeoutMs,
|
|
setting: "native_command_timeout_ms",
|
|
cliFlag: "--native-command-timeout-ms",
|
|
defaultMs: DEFAULT_NATIVE_COMMAND_TIMEOUT_MS,
|
|
phase: "macOS native command deadline",
|
|
unchanged: `The ${String(NATIVE_COMMAND_OUTPUT_BUDGET_BYTES)}-byte command output allowance and the ${String(NATIVE_FAILURE_DIAGNOSTIC_BYTES)}-byte failure-diagnostic excerpt have no runtime override.`,
|
|
})),
|
|
}),
|
|
);
|
|
if (failure.reason === "output-limit")
|
|
return bindCleanup(
|
|
new AnalysisResourceConstraintError(
|
|
operation,
|
|
"memory",
|
|
`Native command output exceeded ${String(NATIVE_COMMAND_OUTPUT_BUDGET_BYTES)} bytes. Complete output is required for analysis. This release has no runtime override. native_command_timeout_ms does not raise this allowance.`,
|
|
{ max_output_bytes: NATIVE_COMMAND_OUTPUT_BUDGET_BYTES },
|
|
{
|
|
...errorOptions,
|
|
...fixedNativeAllowance(
|
|
"The native command output capture allowance",
|
|
"capture",
|
|
`${String(NATIVE_COMMAND_OUTPUT_BUDGET_BYTES)} bytes`,
|
|
),
|
|
},
|
|
),
|
|
);
|
|
return bindCleanup(
|
|
new ProviderAdapterError(IDENTITY.id, operation, {
|
|
...errorOptions,
|
|
diagnostics: {
|
|
tool: failure.tool,
|
|
reason: failure.reason,
|
|
exit_code: failure.capture?.exitCode ?? failure.exitCode,
|
|
signal: failure.capture?.signal ?? null,
|
|
stdout_bytes: failure.capture?.stdoutBytes ?? 0,
|
|
stderr_bytes: failure.capture?.stderrBytes ?? 0,
|
|
...(failure.detail === null ? {} : { detail: failure.detail }),
|
|
},
|
|
}),
|
|
);
|
|
};
|