mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(apple): explain present rejected dylib candidates
Derive unresolved findings from stored candidate paths and outcomes while preserving weak, lazy and conditional load consequences. Document not-loadable and platform-mismatch outcomes and later-path resolution. Validation: 126 resolver/CLI/MCP tests, check:ci, docs:check, and seven compiled CLI plus SDK-validated stdio MCP fixture cases. Source-built and mutated inert Mach-O inputs retained identical hashes; no target execution or dyld runtime comparison. Fixes #5747 Fixes #5694
This commit is contained in:
1 parent
f1126ff7bf
commit
3b67b362aa
3 files changed
+63
-3
No files matched your search
@@ -213,8 +213,16 @@ Resolution follows dyld:
|
||||
Each edge lists every candidate path tried, with its outcome: `resolved`,
|
||||
`absent`, `not-mach-o`, `malformed`, `unsupported` (recognized Mach-O outside
|
||||
parser coverage, such as big-endian images), `architecture-missing`,
|
||||
`outside-target`, `escapes-target`, or `undetermined`. Slices follow dyld's
|
||||
graded architectures, so an `x86_64h` process also loads `x86_64`. The edge's `resolution` is one of:
|
||||
`not-loadable`, `platform-mismatch`, `outside-target`, `escapes-target`, or
|
||||
`undetermined`. `not-loadable` means a
|
||||
matching-architecture Mach-O image was found but it is not a dylib and cannot
|
||||
satisfy a dylib dependency. `platform-mismatch` means a matching-architecture
|
||||
dylib was found but its observed platform is incompatible with the process.
|
||||
These outcomes reject that candidate; dyld search may continue and resolve a
|
||||
later candidate. Candidate outcomes describe individual search paths, while
|
||||
`resolution.status` describes the final edge. Slices follow dyld's graded
|
||||
architectures, so an `x86_64h` process also loads `x86_64`. The edge's
|
||||
`resolution` is one of:
|
||||
|
||||
| Status | Meaning |
|
||||
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -398,6 +398,9 @@ describe("dyld resolution outcomes: checking candidate format and compatibility"
|
||||
({ outcome }) => outcome,
|
||||
),
|
||||
).toEqual(["not-mach-o", "malformed", "architecture-missing"]);
|
||||
expect(trace.findings[0]?.explanation).toContain(
|
||||
"No usable candidate for @rpath/lib.dylib exists in the analyzed root; found Contents/MacOS/a/lib.dylib (not a Mach-O image), Contents/MacOS/b/lib.dylib (malformed), Contents/MacOS/c/lib.dylib (missing the process architecture)",
|
||||
);
|
||||
expect(trace.coverage).toEqual({
|
||||
status: "partial",
|
||||
unparsed_images: ["Contents/MacOS/b/lib.dylib"],
|
||||
|
||||
@@ -70,7 +70,10 @@ const unresolvedFinding = (edge: Edge, index: number): Finding => {
|
||||
basis: "derived",
|
||||
} as const;
|
||||
const lazy = isLazyLoadCommand(edge.command);
|
||||
const missing = `No candidate for ${dependencyPhrase(edge, lazy)} exists in the analyzed root`;
|
||||
const missing = missingCandidateExplanation(
|
||||
edge,
|
||||
dependencyPhrase(edge, lazy),
|
||||
);
|
||||
// Below a conditional fallback, every consequence applies only if it loads.
|
||||
const when = edge.loader_conditional
|
||||
? `. ${edge.loader} loads only conditionally; if it loads, `
|
||||
@@ -94,6 +97,52 @@ const unresolvedFinding = (edge: Edge, index: number): Finding => {
|
||||
};
|
||||
};
|
||||
|
||||
/** Use the resolver's physical-candidate facts without rechecking source paths. */
|
||||
const missingCandidateExplanation = (
|
||||
edge: Edge,
|
||||
dependency: string,
|
||||
): string => {
|
||||
const present = edge.candidates.flatMap(({ outcome, resolved_path }) =>
|
||||
resolved_path === null ? [] : [{ outcome, path: resolved_path }],
|
||||
);
|
||||
if (present.length === 0)
|
||||
return `No candidate for ${dependency} exists in the analyzed root`;
|
||||
const descriptions = present.map(
|
||||
({ outcome, path }) => `${path} (${candidateRejection(outcome)})`,
|
||||
);
|
||||
return `No usable candidate for ${dependency} exists in the analyzed root; found ${descriptions.join(", ")}`;
|
||||
};
|
||||
|
||||
/** Explain a found candidate from the resolver's outcome, not by reopening it. */
|
||||
const candidateRejection = (
|
||||
outcome: Edge["candidates"][number]["outcome"],
|
||||
): string => {
|
||||
switch (outcome) {
|
||||
case "not-mach-o":
|
||||
return "not a Mach-O image";
|
||||
case "malformed":
|
||||
return "malformed";
|
||||
case "architecture-missing":
|
||||
return "missing the process architecture";
|
||||
case "not-loadable":
|
||||
return "not a loadable dylib";
|
||||
case "platform-mismatch":
|
||||
return "incompatible with the process platform";
|
||||
case "unsupported":
|
||||
return "unsupported by the parser";
|
||||
case "resolved":
|
||||
return "loadable";
|
||||
case "absent":
|
||||
return "absent";
|
||||
case "outside-target":
|
||||
return "outside the analyzed root";
|
||||
case "escapes-target":
|
||||
return "escapes the analyzed root";
|
||||
case "undetermined":
|
||||
return "of undetermined loadability";
|
||||
}
|
||||
};
|
||||
|
||||
/** Name the dependency the way the finding's consequence treats it. */
|
||||
const dependencyPhrase = (edge: Edge, lazy: boolean): string => {
|
||||
if (lazy && edge.weak)
|
||||
|
||||
Reference in new issue
Block a user