mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
fix(native): reject mixed dyld refusal diagnostics (#5778)
* fix(native): classify confirmed dSYM dyld refusals * style(native): format target refusal handling * fix(native): confirm precise Mach-O layout refusals before export fallback * test(cli): observe cancellation at stable source resolution boundary * fix(native): reject contradictory dyld layout refusals * fix(native): preserve mixed dyld diagnostic failures * fix(native): retain the evidence behind contradictory layout refusals When the imports and exports refusals disagree, or a refusal disagrees with the parsed Mach-O header, inspect_macho returned a generic output error that kept only one dyld_info capture. It dropped the header file type and the other refusal, so the failure could not show why the admitted diagnostic was rejected. The contradiction now has its own error. Its diagnostics keep the parsed file type and every admitted refusal (mode, admitted layout, exit code and output), and the captured output is still the conflicting command. The test checks the reason and the retained diagnostics for each contradiction case. * test(native): allow time for real Mach-O CLI and MCP controls
This commit is contained in:
2 files changed
+134
-9
No files matched your search
@@ -17,24 +17,34 @@ const dyldFailureDiagnostics = (
|
||||
: outputs.flatMap((output) =>
|
||||
withoutEchoedPathHeader(output, capture.arguments.at(-1))
|
||||
.split(/\r?\n/u)
|
||||
.map((line) => line.trim().replace(/^(?:dyld_info:\s*)/u, "")),
|
||||
.map((line) => line.trim().replace(/^(?:dyld_info:\s*)/u, ""))
|
||||
.filter(Boolean),
|
||||
);
|
||||
|
||||
/** Recognize only dyld_info's target-specific missing-UUID refusal. */
|
||||
/** Admit the observed refusal only when it is the sole settled diagnostic. */
|
||||
const dyldFailureDiagnostic = (
|
||||
capture: NativeCommandCapture,
|
||||
): string | null => {
|
||||
const diagnostics = dyldFailureDiagnostics(capture, [
|
||||
capture.stdout,
|
||||
capture.stderr,
|
||||
]);
|
||||
return diagnostics.length === 1 ? (diagnostics[0] ?? null) : null;
|
||||
};
|
||||
|
||||
/** Keep missing-UUID admission limited to its observed stdout-only refusal. */
|
||||
export const isMissingUuidDyldFailure = (
|
||||
capture: NativeCommandCapture,
|
||||
): boolean =>
|
||||
dyldFailureDiagnostics(capture, [capture.stdout]).includes(
|
||||
MISSING_UUID_DIAGNOSTIC,
|
||||
);
|
||||
capture.stderr.trim().length === 0 &&
|
||||
dyldFailureDiagnostic(capture) === MISSING_UUID_DIAGNOSTIC;
|
||||
|
||||
/** The narrowly observed dyld refusal for an unbacked dSYM text section. */
|
||||
/** Admit the observed dSYM refusal only when no other diagnostic is present. */
|
||||
export const isUnbackedTextDyldFailure = (
|
||||
capture: NativeCommandCapture,
|
||||
): boolean =>
|
||||
dyldFailureDiagnostics(capture, [capture.stdout, capture.stderr]).includes(
|
||||
"section '__TEXT/__text' has offset=0 but is not a zero-fill section type",
|
||||
);
|
||||
dyldFailureDiagnostic(capture) ===
|
||||
"section '__TEXT/__text' has offset=0 but is not a zero-fill section type";
|
||||
|
||||
/** Reported CORE/FVMLIB refusals still require an independently matching header. */
|
||||
export const parseDyldFileTypeRefusal = (
|
||||
|
||||
@@ -451,3 +451,118 @@ it.each(["10", "DSYM", "2"])(
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe("dSYM diagnostic admission", () => {
|
||||
const diagnostic =
|
||||
"section '__TEXT/__text' has offset=0 but is not a zero-fill section type";
|
||||
|
||||
it.each([
|
||||
{ name: "stdout refusal", stdout: diagnostic, stderr: "", accepted: true },
|
||||
{ name: "stderr refusal", stdout: "", stderr: diagnostic, accepted: true },
|
||||
{
|
||||
name: "echoed path and prefixed refusal",
|
||||
stdout: `/fixture/debug-companion:\n\ndyld_info: ${diagnostic}\n`,
|
||||
stderr: "",
|
||||
accepted: true,
|
||||
},
|
||||
{
|
||||
name: "unrelated stdout after refusal",
|
||||
stdout: `${diagnostic}\nmalformed load command`,
|
||||
stderr: "",
|
||||
accepted: false,
|
||||
},
|
||||
{
|
||||
name: "unrelated stdout before refusal",
|
||||
stdout: `malformed load command\n${diagnostic}`,
|
||||
stderr: "",
|
||||
accepted: false,
|
||||
},
|
||||
{
|
||||
name: "unrelated stderr beside stdout refusal",
|
||||
stdout: diagnostic,
|
||||
stderr: "malformed load command",
|
||||
accepted: false,
|
||||
},
|
||||
{
|
||||
name: "unrelated stdout beside stderr refusal",
|
||||
stdout: "malformed load command",
|
||||
stderr: diagnostic,
|
||||
accepted: false,
|
||||
},
|
||||
{
|
||||
name: "unrelated stderr after refusal",
|
||||
stdout: "",
|
||||
stderr: `${diagnostic}\nmalformed load command`,
|
||||
accepted: false,
|
||||
},
|
||||
{
|
||||
name: "missing UUID before dSYM refusal",
|
||||
stdout: `missing LC_UUID load command\n${diagnostic}`,
|
||||
stderr: "",
|
||||
accepted: false,
|
||||
},
|
||||
{
|
||||
name: "missing UUID beside stderr dSYM refusal",
|
||||
stdout: "missing LC_UUID load command",
|
||||
stderr: diagnostic,
|
||||
accepted: false,
|
||||
},
|
||||
{
|
||||
name: "duplicate refusal across streams",
|
||||
stdout: diagnostic,
|
||||
stderr: diagnostic,
|
||||
accepted: false,
|
||||
},
|
||||
])("preserves $name", async ({ stdout, stderr, accepted }) => {
|
||||
const runner = new NativeFixtureRunner({
|
||||
otool: [
|
||||
"Mach header",
|
||||
"magic cputype cpusubtype caps filetype ncmds sizeofcmds flags",
|
||||
"0xfeedfacf 16777228 0 0x00 10 0 0 0x0",
|
||||
].join("\n"),
|
||||
});
|
||||
const result = await inspectNativeMacho({
|
||||
target: nativeMachoTarget("/fixture/debug-companion"),
|
||||
run: async (tool, args, _signal, acceptNonZeroIf) => {
|
||||
const captured = await runner.run(tool, args);
|
||||
if (!captured.ok || tool !== "dyld_info") return captured;
|
||||
const refused = {
|
||||
...captured.value,
|
||||
stdout,
|
||||
stderr,
|
||||
stdoutBytes: Buffer.byteLength(stdout),
|
||||
stderrBytes: Buffer.byteLength(stderr),
|
||||
exitCode: 1,
|
||||
};
|
||||
return acceptNonZeroIf?.(refused)
|
||||
? ok(refused)
|
||||
: err(
|
||||
new AnalysisOutputError("inspect_macho", "native refusal", {
|
||||
capturedOutput: {
|
||||
stdout,
|
||||
stderr,
|
||||
truncated: false,
|
||||
stdout_bytes: refused.stdoutBytes,
|
||||
stderr_bytes: refused.stderrBytes,
|
||||
exit_code: 1,
|
||||
signal: null,
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
invocation: () => {
|
||||
throw new Error("Refused target does not produce provenance");
|
||||
},
|
||||
});
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) return;
|
||||
expect(result.error._tag).toBe(
|
||||
accepted ? "AnalysisUnsupportedTargetError" : "AnalysisOutputError",
|
||||
);
|
||||
expect(result.error.capturedOutput).toMatchObject({
|
||||
stdout,
|
||||
stderr,
|
||||
exit_code: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user