From 41bd808c1f80cebbd49bbe41005aec99da80d836 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Wed, 11 Nov 2026 11:49:01 +0800 Subject: [PATCH] fix(symbols): match exact Objective-C protocol labels (#9938) --- src/domain/objcClassSymbol.ts | 9 +++++++-- src/domain/symbolAnalysis.test.ts | 20 ++++++++++++++++++++ src/domain/symbolAnalysis.ts | 11 ++++------- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/domain/objcClassSymbol.ts b/src/domain/objcClassSymbol.ts index 07dca28743..b6b11ee0c4 100644 --- a/src/domain/objcClassSymbol.ts +++ b/src/domain/objcClassSymbol.ts @@ -8,8 +8,7 @@ export const objcClassSymbol = ( readonly metaclass: boolean; } | undefined => { - const namespace = name.lastIndexOf("::"); - const leaf = namespace < 0 ? name : name.slice(namespace + 2); + const leaf = objcSymbolLeaf(name); const match = /^_?(OBJC_(META)?CLASS_\$_([^\s]+))$/u.exec(leaf); if (match?.[1] === undefined || match[3] === undefined) return undefined; return { @@ -18,3 +17,9 @@ export const objcClassSymbol = ( metaclass: match[2] !== undefined, }; }; + +/** Preserve the exact runtime symbol leaf after a provider namespace. */ +export const objcSymbolLeaf = (name: string): string => { + const namespace = name.lastIndexOf("::"); + return namespace < 0 ? name : name.slice(namespace + 2); +}; diff --git a/src/domain/symbolAnalysis.test.ts b/src/domain/symbolAnalysis.test.ts index 468a587d42..8f68910a72 100644 --- a/src/domain/symbolAnalysis.test.ts +++ b/src/domain/symbolAnalysis.test.ts @@ -282,3 +282,23 @@ it("distinguishes protocol identity from protocol member contexts", () => { unclassified: [], }); }); + +it("selects exact Objective-C protocol symbol leaves", () => { + const declarations = [ + "OBJC_PROTOCOL_$_Runnable", + "_OBJC_PROTOCOL_$_Runnable", + "__OBJC_PROTOCOL_$_Runnable", + "Framework::__OBJC_PROTOCOL_$_Runnable", + ].map((name, index) => ({ address: `0x${index}`, name })); + const otherNames = [ + "s__OBJC_PROTOCOL_$_Runnable_1000", + "PTR__OBJC_PROTOCOL_$_Runnable_1000", + "-[Example useOBJC_PROTOCOL_$_Runnable]", + "__OBJC_PROTOCOL_REFERENCE_$_Runnable", + "__OBJC_PROTOCOL_$_", + ].map((name, index) => ({ address: `0x${index + 4}`, name })); + expect(discoverObjcProtocols([...otherNames, ...declarations])).toEqual({ + count: declarations.length, + protocols: declarations, + }); +}); diff --git a/src/domain/symbolAnalysis.ts b/src/domain/symbolAnalysis.ts index 039d04e17d..cc4a455d92 100644 --- a/src/domain/symbolAnalysis.ts +++ b/src/domain/symbolAnalysis.ts @@ -1,4 +1,4 @@ -import { objcClassSymbol } from "./objcClassSymbol.js"; +import { objcClassSymbol, objcSymbolLeaf } from "./objcClassSymbol.js"; import type { JsonValue } from "./jsonValue.js"; import type { AddressedName } from "./native/nativeInventory.js"; import type { @@ -14,11 +14,8 @@ import { /** Local class records emitted by Apple's fragile Objective-C ABI. */ const LEGACY_OBJC_CLASS_PREFIX = "l_OBJC_CLASS_" as const; -/** - * Objective-C protocol declaration marker. `PROTOCOL_REFERENCE` slots - * share the substring but are uses, not definitions. - */ -const OBJC_PROTOCOL_MARKER = "OBJC_PROTOCOL_$_" as const; +/** Objective-C protocol declaration labels with their object-file prefixes. */ +const OBJC_PROTOCOL_SYMBOL = /^_{0,2}OBJC_PROTOCOL_\$_[^\s]+$/u; export interface ObjcClassImportBindingScan { readonly bindings: readonly MachoImportBinding[]; @@ -66,7 +63,7 @@ export const discoverObjcProtocols = ( const protocols = uniqueByName( names.filter( ({ name }) => - name.includes(OBJC_PROTOCOL_MARKER) || + OBJC_PROTOCOL_SYMBOL.test(objcSymbolLeaf(name)) || classifySwiftSymbol(name)?.protocolIdentity === true, ), );