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:
Kaoru0822andClaude Opus 5.5 authored and GitHub committed 2026-10-15 03:42:03 -07:00
1 parent b6529bf1af
commit 30ba5e99f9
5 files changed
+72 -21

No files matched your search

+2 -1
View File
@@ -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" }),
]),
},
});