mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(native): keep a dylib's own install name out of its dependencies (#4050)
inspect-macho listed the LC_ID_DYLIB command of a dylib as a dependency, so every dylib reported a self-dependency and an inflated exhaustive count, while trace-dylib-resolution correctly treats that name as the image identity. The dependency collector now skips LC_ID_DYLIB; the command, with its install name, remains in the raw load commands. Fixes #3872 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b6529bf1af
commit
30ba5e99f9
5 files changed
+72
-21
No files matched your search
@@ -407,7 +407,8 @@ const collectDependency = (
|
||||
fields: Readonly<Record<string, string | number | null>>,
|
||||
state: ParsedLoadCommands,
|
||||
): void => {
|
||||
if (!DYLIB_COMMANDS.has(kind)) return;
|
||||
// LC_ID_DYLIB names this image itself; it stays a raw load command only.
|
||||
if (!DYLIB_COMMANDS.has(kind) || kind === "LC_ID_DYLIB") return;
|
||||
state.dependencies.push({
|
||||
path: stripOffsetSuffix(stringField(fields, "name")),
|
||||
kind,
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { join } from "node:path";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
|
||||
import { describe, expect } from "vitest";
|
||||
import { z } from "zod";
|
||||
|
||||
import { inspectMachoSchema } from "../../../src/domain/native/nativeInspection.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
const evidenceSchema = z.object({ normalized_result: inspectMachoSchema });
|
||||
|
||||
describe.skipIf(process.platform !== "darwin")(
|
||||
"native Mach-O CLI dylib identity",
|
||||
() => {
|
||||
cliTest(
|
||||
"keeps a dylib's own install name out of its dependencies",
|
||||
async ({ cli, processes }) => {
|
||||
const directory = await createTestTempDirectory("rea-dylib-identity-");
|
||||
const source = join(directory, "library.c");
|
||||
const library = join(directory, "libprobe.dylib");
|
||||
await writeFile(source, "int exported_value(void) { return 42; }\n");
|
||||
const compiled = await processes.run(
|
||||
"/usr/bin/xcrun",
|
||||
[
|
||||
"clang",
|
||||
"-dynamiclib",
|
||||
source,
|
||||
"-Wl,-install_name,@rpath/libprobe.dylib",
|
||||
"-o",
|
||||
library,
|
||||
],
|
||||
{ timeoutMs: 30_000 },
|
||||
);
|
||||
expect(compiled.exitCode, compiled.stderr).toBe(0);
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-macho", library, "--json"],
|
||||
environment: { REA_LOG_LEVEL: "silent" },
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
const inspected = evidenceSchema.parse(result.json).normalized_result;
|
||||
expect(inspected.dependencies).toMatchObject({
|
||||
items: [
|
||||
{ path: "/usr/lib/libSystem.B.dylib", kind: "LC_LOAD_DYLIB" },
|
||||
],
|
||||
total: 1,
|
||||
exhaustive: true,
|
||||
});
|
||||
expect(
|
||||
inspected.load_commands.items.find(
|
||||
({ kind }) => kind === "LC_ID_DYLIB",
|
||||
)?.fields.name,
|
||||
).toBe("@rpath/libprobe.dylib (offset 24)");
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -464,14 +464,8 @@ Load command 1
|
||||
{ index: 1, kind: "LC_UUID" },
|
||||
],
|
||||
);
|
||||
expect(parsed.dependencies).toEqual([
|
||||
{
|
||||
path: installName,
|
||||
kind: "LC_ID_DYLIB",
|
||||
current_version: "3.2.1",
|
||||
compatibility_version: "1.0.0",
|
||||
},
|
||||
]);
|
||||
expect(parsed.commands[0]?.fields.name).toBe(`${installName} (offset 24)`);
|
||||
expect(parsed.dependencies).toEqual([]);
|
||||
expect(parsed.uuid).toBe("01234567-89AB-CDEF-0123-456789ABCDEF");
|
||||
},
|
||||
);
|
||||
|
||||
@@ -16,12 +16,10 @@ it.each([
|
||||
await nativeFixture(`load-command-text-values/${slug}.txt`),
|
||||
),
|
||||
);
|
||||
expect(load.dependencies).toContainEqual({
|
||||
path: name,
|
||||
kind: "LC_ID_DYLIB",
|
||||
current_version: "0.0.0",
|
||||
compatibility_version: "0.0.0",
|
||||
});
|
||||
// The image's own install name is identity, not a dependency.
|
||||
expect(load.dependencies.map(({ kind }) => kind)).not.toContain(
|
||||
"LC_ID_DYLIB",
|
||||
);
|
||||
expect(
|
||||
load.commands.find((command) => command.kind === "LC_ID_DYLIB")?.fields
|
||||
.name,
|
||||
|
||||
@@ -22,20 +22,21 @@ it("retains native re-export dependencies alongside ordinary and identity comman
|
||||
expect(result.value.result).toMatchObject({
|
||||
dependencies: {
|
||||
exhaustive: true,
|
||||
total: 3,
|
||||
items: expect.arrayContaining([
|
||||
total: 2,
|
||||
items: [
|
||||
expect.objectContaining({
|
||||
kind: "LC_REEXPORT_DYLIB",
|
||||
path: "@rpath/libLeaf.dylib",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
kind: "LC_ID_DYLIB",
|
||||
path: "/owned/libOuter.dylib",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
kind: "LC_LOAD_DYLIB",
|
||||
path: "/usr/lib/libSystem.B.dylib",
|
||||
}),
|
||||
],
|
||||
},
|
||||
load_commands: {
|
||||
items: expect.arrayContaining([
|
||||
expect.objectContaining({ kind: "LC_ID_DYLIB" }),
|
||||
]),
|
||||
},
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user