From 8defd220a9f3fa9f200dd47104bc1e1de313dbba Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sat, 24 Oct 2026 09:13:28 +0800 Subject: [PATCH] refactor(objc): retire obsolete class discovery paths Remove the test-only selection/projection facade and its unused synthesized binding scan. Keep lexical and identity controls at their active owners, with combined behavior covered through EnhancedTools. --- .../EnhancedTools.objcClasses.test.ts | 53 ++++++++++++- src/domain/symbolAnalysis.objcClasses.test.ts | 75 ++++--------------- src/domain/symbolAnalysis.ts | 22 +----- 3 files changed, 67 insertions(+), 83 deletions(-) diff --git a/src/application/EnhancedTools.objcClasses.test.ts b/src/application/EnhancedTools.objcClasses.test.ts index a6b954892a..f6371b9eb6 100644 --- a/src/application/EnhancedTools.objcClasses.test.ts +++ b/src/application/EnhancedTools.objcClasses.test.ts @@ -183,6 +183,29 @@ it.each([ }, ); +it("reports no applicable identity scan when names contain no owned class labels", async () => { + const execute = vi.fn( + createPort({ + names: [ + { address: "0x1000", value: "__objc_class_HuntRoot_class" }, + { address: "0x2000", value: "PTR__OBJC_CLASS_$_NSAlert_100028668" }, + { address: "0x3000", value: "s__OBJC_CLASS_$_NSAlert_10002ed0d" }, + ], + }).execute, + ); + const tools = new EnhancedTools({ execute }); + await expect(tools.execute("get_objc_classes", {})).resolves.toEqual({ + ok: true, + value: { + count: 0, + classes: [], + identity_coverage: { status: "not_applicable", reason: null }, + }, + }); + expect(execute).toHaveBeenCalledTimes(1); + expect(execute.mock.calls[0]?.[0]).toBe("list_names"); +}); + describe("Objective-C class import identity composition", () => { it("joins only exact same-target bind slots and preserves literal filter selection", async () => { const tools = new EnhancedTools(createPort()); @@ -209,7 +232,22 @@ describe("Objective-C class import identity composition", () => { ok: true, value: { count: 1, - classes: [{ address: "0x1000", name: "OBJC_CLASS_$_Document$GOT" }], + classes: [ + { + address: "0x1000", + name: "OBJC_CLASS_$_Document$GOT", + binding_identity: { + symbol: "_OBJC_CLASS_$_Document", + binding_source: "chained", + library_ordinal: 1, + library: "/System/Library/Frameworks/AppKit.framework/AppKit", + weak: false, + addend: "0", + slot_addresses: ["0x1000"], + }, + }, + ], + identity_coverage: { status: "complete", reason: null }, }, }); }); @@ -274,6 +312,12 @@ describe("Objective-C class import identity composition", () => { it("keeps names with unknown identity when binding inspection is unavailable", async () => { const tools = new EnhancedTools( createPort({ + names: [ + { address: "0x1000", value: "OBJC_CLASS_$_Document$GOT" }, + { address: "0x1000", value: "OBJC_CLASS_$_Document" }, + { address: "0x2000", value: "OBJC_CLASS_$_Document" }, + { address: "0x2000", value: "OBJC_CLASS_$_Document" }, + ], metadataFailure: new AnalysisCapabilityUnavailableError( provider.id, "inspect_native_dispatch_metadata", @@ -284,7 +328,12 @@ describe("Objective-C class import identity composition", () => { await expect(tools.execute("get_objc_classes", {})).resolves.toMatchObject({ ok: true, value: { - count: 2, + count: 3, + classes: [ + { address: "0x1000", name: "OBJC_CLASS_$_Document$GOT" }, + { address: "0x1000", name: "OBJC_CLASS_$_Document" }, + { address: "0x2000", name: "OBJC_CLASS_$_Document" }, + ], identity_coverage: { status: "unknown", reason: "No static Mach-O reader", diff --git a/src/domain/symbolAnalysis.objcClasses.test.ts b/src/domain/symbolAnalysis.objcClasses.test.ts index 28ba29bf89..c44855b6c7 100644 --- a/src/domain/symbolAnalysis.objcClasses.test.ts +++ b/src/domain/symbolAnalysis.objcClasses.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import { - discoverObjcClasses, + selectObjcClassCandidates, projectObjcClassCandidates, } from "./symbolAnalysis.js"; @@ -19,15 +19,11 @@ const classImport = { describe("Objective-C class label discovery", () => { it("does not reinterpret a provider-specific label without an owner tag", () => { expect( - discoverObjcClasses( + selectObjcClassCandidates( [{ address: "0x1", name: "__objc_class_HuntRoot_class" }], "Hunt", ), - ).toEqual({ - count: 0, - classes: [], - identity_coverage: { status: "not_applicable", reason: null }, - }); + ).toEqual([]); }); it("uses provider-tagged stripped class labels and filters their exact text", () => { @@ -51,25 +47,10 @@ describe("Objective-C class label discovery", () => { : {}), })); - expect(discoverObjcClasses(names, "Hunt")).toEqual({ - count: 6, - classes: names.slice(0, 6).map(({ address, name }) => ({ - address, - name, - })), - identity_coverage: { - status: "unknown", - reason: "Exact class-import binding identity is unavailable.", - }, - }); - expect(discoverObjcClasses(names, "__objc_class_HuntRoot_class")).toEqual({ - count: 1, - classes: [{ address: "0x1", name: "__objc_class_HuntRoot_class" }], - identity_coverage: { - status: "unknown", - reason: "Exact class-import binding identity is unavailable.", - }, - }); + expect(selectObjcClassCandidates(names, "Hunt")).toEqual(names.slice(0, 6)); + expect( + selectObjcClassCandidates(names, "__objc_class_HuntRoot_class"), + ).toEqual([names[0]]); }); it("coalesces only selected rows whose own addresses prove the same import", () => { @@ -102,24 +83,6 @@ describe("Objective-C class label discovery", () => { ], identity_coverage: completeCoverage, }); - expect(discoverObjcClasses([alias, canonical], "$GOT", scan)).toEqual({ - count: 1, - classes: [ - { - ...alias, - binding_identity: { - symbol: classImport.symbol, - binding_source: classImport.binding_source, - library_ordinal: classImport.library_ordinal, - library: classImport.library, - weak: false, - addend: "0", - slot_addresses: ["0x1000"], - }, - }, - ], - identity_coverage: completeCoverage, - }); }); it("does not join same-name rows without exact slot evidence", () => { @@ -153,20 +116,13 @@ describe("Objective-C class label discovery", () => { ).toMatchObject({ count: 2, classes: rows }); }); - it("keeps same-named candidates at distinct addresses until bind identity is known", () => { + it("deduplicates exact class labels while keeping distinct addresses", () => { const rows = [ { address: "0x1000", name: "OBJC_CLASS_$_Document" }, { address: "0x1000", name: "OBJC_CLASS_$_Document" }, { address: "0x2000", name: "OBJC_CLASS_$_Document" }, ]; - expect(discoverObjcClasses(rows, "")).toEqual({ - count: 2, - classes: [rows[0], rows[2]], - identity_coverage: { - status: "unknown", - reason: "Exact class-import binding identity is unavailable.", - }, - }); + expect(selectObjcClassCandidates(rows, "")).toEqual([rows[0], rows[2]]); }); }); @@ -176,7 +132,7 @@ it("keeps equal symbols separate when only their lookup ordinals differ", () => { address: "0x2000", name: "OBJC_CLASS_$_Document$GOT" }, ]; expect( - discoverObjcClasses(rows, "", { + projectObjcClassCandidates(rows, { bindings: [ { ...classImport, library_ordinal: -1, library: null }, { @@ -203,7 +159,7 @@ it("keeps regular self-scope and weak rebinding records separate", () => { { address: "0x2000", name: "OBJC_CLASS_$_Document$GOT" }, ]; expect( - discoverObjcClasses(rows, "", { + projectObjcClassCandidates(rows, { bindings: [ { ...classImport, library_ordinal: 0, library: null }, { @@ -226,7 +182,7 @@ it("keeps regular self-scope and weak rebinding records separate", () => { }); it("excludes pointer and string labels that only contain a class marker", () => { - const result = discoverObjcClasses( + const result = selectObjcClassCandidates( [ { address: "0x1", name: "PTR__OBJC_CLASS_$_NSAlert_100028668" }, { address: "0x2", name: "s__OBJC_CLASS_$_NSAlert_10002ed0d" }, @@ -234,8 +190,7 @@ it("excludes pointer and string labels that only contain a class marker", () => ], "", ); - expect(result).toMatchObject({ - count: 1, - classes: [{ address: "0x3", name: "AppKit::_OBJC_CLASS_$_NSAlert" }], - }); + expect(result).toEqual([ + { address: "0x3", name: "AppKit::_OBJC_CLASS_$_NSAlert" }, + ]); }); diff --git a/src/domain/symbolAnalysis.ts b/src/domain/symbolAnalysis.ts index f1517305a2..e3d197e0fb 100644 --- a/src/domain/symbolAnalysis.ts +++ b/src/domain/symbolAnalysis.ts @@ -54,16 +54,7 @@ export const selectObjcClassCandidates = ( /** Project selected class labels, coalescing only exact proven import identities. */ export const projectObjcClassCandidates = ( candidates: readonly AddressedName[], - bindingScan: ObjcClassImportBindingScan = { - bindings: [], - coverage: { - status: candidates.length === 0 ? "not_applicable" : "unknown", - reason: - candidates.length === 0 - ? null - : "Exact class-import binding identity is unavailable.", - }, - }, + bindingScan: ObjcClassImportBindingScan, ): JsonValue => { const classes = coalesceBoundClassAliases(candidates, bindingScan.bindings); return { @@ -76,17 +67,6 @@ export const projectObjcClassCandidates = ( }; }; -/** Select, then project Objective-C class labels for provider-neutral callers. */ -export const discoverObjcClasses = ( - names: readonly AddressedName[], - pattern: string, - bindingScan?: ObjcClassImportBindingScan, -): JsonValue => - projectObjcClassCandidates( - selectObjcClassCandidates(names, pattern), - bindingScan, - ); - /** Select and deduplicate Objective-C and Swift protocol labels. */ export const discoverObjcProtocols = ( names: readonly AddressedName[],