diff --git a/src/domain/native/objcSwiftMetadata.test.ts b/src/domain/native/objcSwiftMetadata.test.ts index c8a3492480..2babf2161c 100644 --- a/src/domain/native/objcSwiftMetadata.test.ts +++ b/src/domain/native/objcSwiftMetadata.test.ts @@ -38,4 +38,40 @@ describe("ObjC/Swift metadata", () => { }), ); }); + + it("projects every accepted Swift mangling prefix and counts it", () => { + const names = [ + "$s5Store7launchyyF", + "_$s5Store7launchyyF", + "$S5Store7launchyyF", + "_$S5Store7launchyyF", + "_T05Store7launchyyF", + "@__swiftmacro_5Store7launchfMf_", + "$e5Store7launchyyF", + "_$e5Store7launchyyF", + "_TtC5Store11StoreHandle", + "_Tprintf", + ]; + const result = inspectNativeDispatchMetadata( + names.map((name, index) => ({ + address: `ram:0x${(0x1000 + index * 0x100).toString(16)}`, + name, + })), + names.length, + ); + + expect(result.swift_symbols.map((entry) => entry.mangled_name)).toEqual( + names.slice(0, -1), + ); + expect( + result.swift_symbols.every((entry) => entry.decode.status === "partial"), + ).toBe(true); + expect(result.coverage).toContainEqual( + expect.objectContaining({ + facet: "swift_declarations", + examined: 9, + decoded: 0, + }), + ); + }); }); diff --git a/src/domain/native/objcSwiftMetadata.ts b/src/domain/native/objcSwiftMetadata.ts index a360c32029..851b9f1b7b 100644 --- a/src/domain/native/objcSwiftMetadata.ts +++ b/src/domain/native/objcSwiftMetadata.ts @@ -1,5 +1,6 @@ import { z } from "zod"; import { providerSchema } from "../evidence.js"; +import { isSwiftMangledSymbolName } from "../swiftSymbolClassification.js"; import { compareUnicodeCodePoints, @@ -368,7 +369,7 @@ export const inspectNativeDispatchMetadata = ( classNames.add(methodMatch[2]); } - if (/^(?:_?\$s|_Tt)/u.test(entry.name)) + if (isSwiftMangledSymbolName(entry.name) || entry.name.startsWith("_Tt")) swiftSymbols.push({ mangled_name: entry.name, address: entry.address, diff --git a/src/domain/swiftSymbolClassification.ts b/src/domain/swiftSymbolClassification.ts index 58b9b0d261..04910e5c58 100644 --- a/src/domain/swiftSymbolClassification.ts +++ b/src/domain/swiftSymbolClassification.ts @@ -55,6 +55,26 @@ export const classifySwiftSymbol = ( return { category }; }; +/** + * Prefixes accepted by Swift's demangler as manglings. This is deliberately + * narrower than visible Swift runtime class names (`_Tt…`): those names are + * useful metadata symbols but are not themselves demangler manglings. + */ +const SWIFT_MANGLING_PREFIXES = [ + "_T0", + "$S", + "_$S", + "$s", + "_$s", + "@__swiftmacro_", + "$e", + "_$e", +] as const; + +/** Recognize names the Swift demangler accepts as mangled symbols. */ +export const isSwiftMangledSymbolName = (name: string): boolean => + SWIFT_MANGLING_PREFIXES.some((prefix) => name.startsWith(prefix)); + const identifierEnd = ( mangling: string, offset: number, diff --git a/src/native/parsers/demangle.ts b/src/native/parsers/demangle.ts index 6f1e42d61d..3c036d37b7 100644 --- a/src/native/parsers/demangle.ts +++ b/src/native/parsers/demangle.ts @@ -1,5 +1,7 @@ import { z } from "zod"; +import { isSwiftMangledSymbolName } from "../../domain/swiftSymbolClassification.js"; + const resultSchema = z.object({ input: z.string(), output: z.string(), @@ -8,22 +10,6 @@ const resultSchema = z.object({ export type DemangledSymbol = z.infer; -/** - * Prefixes the Swift demangler accepts as mangled names (Swift 4, 4.x, 5+, - * macro file names and Embedded Swift). Legacy `_T` is omitted because it is - * also an ordinary C symbol spelling such as `_Tprintf`. - */ -const SWIFT_MANGLING_PREFIXES = [ - "_T0", - "$S", - "_$S", - "$s", - "_$s", - "@__swiftmacro_", - "$e", - "_$e", -] as const; - /** * Preserve input ordering while parsing `swift-demangle --compact` lines. * The demangler ends each record with LF, so a carriage return belongs to the @@ -49,7 +35,7 @@ export const parseDemangledSymbols = ( output: value, status: value === input - ? SWIFT_MANGLING_PREFIXES.some((prefix) => input.startsWith(prefix)) + ? isSwiftMangledSymbolName(input) ? "invalid" : "unchanged" : "demangled",