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:
Jeff authored and GitHub committed 2026-10-27 21:32:13 +08:00
1 parent cbeeeda97b
commit d47efe1a54
2 files changed
+134 -9

No files matched your search

+19 -9
View File
@@ -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 = (
+115
View File
@@ -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,
});
});
});