From f4df6ab280ce30395816159a05ee3c89ff47ff0f Mon Sep 17 00:00:00 2001 From: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com> Date: Tue, 13 Oct 2026 21:46:25 -0700 Subject: [PATCH] fix(native): classify every Swift mangling prefix as invalid when undecodable (#3089) * fix(native): classify every Swift mangling prefix as invalid when undecodable demangle_swift reported "invalid" only for $s and _$s inputs that swift-demangle left unchanged. Undecodable symbols carrying the other prefixes the Swift demangler accepts (_T0, $S, _$S, @__swiftmacro_, $e, _$e) were reported as "unchanged", i.e. as non-Swift names. Legacy _T stays "unchanged" because it is also an ordinary C symbol spelling. Co-Authored-By: Claude Opus 5.5 * test(native): expect only toolchain-portable Swift manglings to demangle The macOS 15 runner's swift-demangle predates Embedded Swift, so valid $e symbols stay unchanged there. Keep $e and _$e in the undecodable half only. Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Opus 5.5 --- src/native/parsers/demangle.test.ts | 32 +++++++++-------- src/native/parsers/demangle.ts | 18 +++++++++- tests/boundary/mcp/nativeDemangleMcp.test.ts | 37 ++++++++++++++++++++ 3 files changed, 71 insertions(+), 16 deletions(-) diff --git a/src/native/parsers/demangle.test.ts b/src/native/parsers/demangle.test.ts index 5b15229bd3..dd17e34aad 100644 --- a/src/native/parsers/demangle.test.ts +++ b/src/native/parsers/demangle.test.ts @@ -40,21 +40,23 @@ describe("parseDemangledSymbols", () => { ]); }); - it("identifies invalid Swift symbols that fail demangling", () => { - const inputs = ["$sInvalidMangle", "_$sAlsoInvalid"]; - const output = "$sInvalidMangle\n_$sAlsoInvalid\n"; - expect(parseDemangledSymbols(inputs, output)).toEqual([ - { - input: "$sInvalidMangle", - output: "$sInvalidMangle", - status: "invalid", - }, - { - input: "_$sAlsoInvalid", - output: "_$sAlsoInvalid", - status: "invalid", - }, - ]); + it("identifies invalid Swift symbols for every demangler mangling prefix", () => { + const inputs = [ + "_T0Bogus", + "$SBogus", + "_$SBogus", + "$sBogus", + "_$sBogus", + "@__swiftmacro_Bogus", + "$eBogus", + "_$eBogus", + "_Tprintf", + ]; + expect( + parseDemangledSymbols(inputs, `${inputs.join("\n")}\n`).map( + ({ status }) => status, + ), + ).toEqual([...Array(8).fill("invalid"), "unchanged"]); }); it("parses mixed batches preserving ordering", () => { diff --git a/src/native/parsers/demangle.ts b/src/native/parsers/demangle.ts index afcb30b624..6f1e42d61d 100644 --- a/src/native/parsers/demangle.ts +++ b/src/native/parsers/demangle.ts @@ -8,6 +8,22 @@ 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 @@ -33,7 +49,7 @@ export const parseDemangledSymbols = ( output: value, status: value === input - ? input.startsWith("$s") || input.startsWith("_$s") + ? SWIFT_MANGLING_PREFIXES.some((prefix) => input.startsWith(prefix)) ? "invalid" : "unchanged" : "demangled", diff --git a/tests/boundary/mcp/nativeDemangleMcp.test.ts b/tests/boundary/mcp/nativeDemangleMcp.test.ts index 8e66a27fa6..7bff5fa037 100644 --- a/tests/boundary/mcp/nativeDemangleMcp.test.ts +++ b/tests/boundary/mcp/nativeDemangleMcp.test.ts @@ -68,5 +68,42 @@ describe.skipIf(process.platform !== "darwin")( ); }, ); + + mcpTest( + "reports undecodable symbols with any Swift mangling prefix as invalid", + async ({ mcp, onTestFinished }) => { + const target = join( + await createTestTempDirectory("rea-demangle-mcp-"), + "tool", + ); + await copyFile("/usr/bin/true", target); + const client = await openProductionTarget(mcp, onTestFinished, target); + // Embedded Swift ($e) decoding depends on the installed toolchain, so + // only manglings every supported demangler decodes are expected valid. + const decodable = ["_T0", "$S", "_$S"]; + const prefixes = [...decodable, "$e", "_$e"]; + + const called = await client.callTool({ + name: "demangle_swift", + arguments: { + symbols: [ + ...decodable.map((prefix) => `${prefix}4main3FooV`), + ...prefixes.map((prefix) => `${prefix}Bogus`), + "_Tprintf", + ], + }, + }); + expect(called.isError, JSON.stringify(called.content)).not.toBe(true); + expect( + demangledSchema + .parse(called.structuredContent) + .normalized_result.symbols.map(({ status }) => status), + ).toEqual([ + ...decodable.map(() => "demangled"), + ...prefixes.map(() => "invalid"), + "unchanged", + ]); + }, + ); }, );