From 9d00d7d7fabb643d1d11c71c77ae4fc704ef6f69 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Wed, 14 Oct 2026 15:20:04 +0800 Subject: [PATCH] fix(native): retain unclassified accepted Swift manglings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the shared Swift prefix owner for admission while preserving legacy classified spellings. Keep unresolved symbols and aliases without letting them override decoded category votes. Co-authored-by: 吴佳浩 --- src/domain/symbolAnalysis.test.ts | 42 +++++++++++++++++++++++++++++++ src/domain/symbolAnalysis.ts | 21 +++++++++++++--- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/domain/symbolAnalysis.test.ts b/src/domain/symbolAnalysis.test.ts index c43bbfb764..2afa88bf29 100644 --- a/src/domain/symbolAnalysis.test.ts +++ b/src/domain/symbolAnalysis.test.ts @@ -94,4 +94,46 @@ describe("Swift alias ambiguity", () => { ], }); }); + + it("retains manglings whose prefix the demangler accepts", () => { + const names = [ + "$e4main7ServiceCMa", + "_$e4main7ServiceCMa", + "@__swiftmacro_4main7ServiceCMa", + ]; + const result = categorizeSwiftTypes( + names.map((name, index) => ({ address: `0x${String(index)}`, name })), + ); + expect(result.categories.other?.items.map((item) => item.name)).toEqual( + names, + ); + expect(result.unclassified.map((item) => item.name)).toEqual(names); + }); + + it("keeps legacy names with an optional leading underscore", () => { + expect( + categorizeSwiftTypes([{ address: "0x1", name: "__T04main4PairVMa" }], { + category: "structs", + }), + ).toMatchObject({ + total: 1, + categories: { structs: { count: 1 } }, + unclassified: [], + }); + }); + + it("does not let a demangler-only alias erase a decoded category", () => { + const result = categorizeSwiftTypes( + [{ address: "0x1", name: "$s4main7ServiceCMa" }], + {}, + [{ address: "0x1", name: "$e4main7ServiceCMa" }], + ); + expect(result.categories.classes?.items).toMatchObject([ + { + name: "$s4main7ServiceCMa", + mangled_names: ["$s4main7ServiceCMa", "$e4main7ServiceCMa"], + }, + ]); + expect(result.categories.other?.items).toEqual([]); + }); }); diff --git a/src/domain/symbolAnalysis.ts b/src/domain/symbolAnalysis.ts index 6667afcc6d..173dd99f9e 100644 --- a/src/domain/symbolAnalysis.ts +++ b/src/domain/symbolAnalysis.ts @@ -2,6 +2,7 @@ import type { JsonValue } from "./jsonValue.js"; import type { AddressedName } from "./hopperValues.js"; import { classifySwiftSymbol, + isSwiftMangledSymbolName, type SwiftTypeCategory, } from "./swiftSymbolClassification.js"; @@ -58,6 +59,16 @@ export const discoverObjcProtocols = ( }; }; +/** + * A Swift symbol carries either a demangler mangling or a name the category + * decoder already accepts, including visible runtime class names (`_Tt…`) and + * the optional leading underscore of legacy encodings (`__T0…`). Whether the + * nominal category can be decoded is a separate question, so it must not decide + * whether the symbol is Swift at all. + */ +const isSwiftSymbolName = (name: string): boolean => + isSwiftMangledSymbolName(name) || classifySwiftSymbol(name) !== undefined; + /** Categorize procedure names using Swift manglings at their exact addresses. */ export const categorizeSwiftTypes = ( procedures: readonly AddressedName[], @@ -94,7 +105,7 @@ export const categorizeSwiftTypes = ( if (seen.has(entry.name)) continue; const mangledNames = [ ...new Set([ - ...(classifySwiftSymbol(entry.name) === undefined ? [] : [entry.name]), + ...(isSwiftSymbolName(entry.name) ? [entry.name] : []), ...(aliases.get(entry.address) ?? []), ]), ]; @@ -104,8 +115,12 @@ export const categorizeSwiftTypes = ( ![entry.name, ...mangledNames].some((name) => name.includes(pattern)) ) continue; + // Only decoded names vote. Unresolved aliases remain evidence without + // erasing category evidence from a name the decoder did recognize. const kinds = new Set( - mangledNames.map((name) => classifySwiftSymbol(name)?.category ?? null), + mangledNames + .filter((name) => classifySwiftSymbol(name) !== undefined) + .map((name) => classifySwiftSymbol(name)?.category ?? null), ); const category = kinds.size === 1 ? ([...kinds][0] ?? null) : null; const item = { @@ -154,7 +169,7 @@ export const categorizeSwiftTypes = ( const swiftAliasesByAddress = (symbols: readonly AddressedName[]) => { const aliases = new Map(); for (const { address, name } of symbols) { - if (classifySwiftSymbol(name) === undefined) continue; + if (!isSwiftSymbolName(name)) continue; const existing = aliases.get(address); if (existing === undefined) aliases.set(address, [name]); else existing.push(name);