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 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822 2026-10-14 02:07:40 +00:00
parent 70771523c5
commit a3cea599fb
3 changed files with 68 additions and 16 deletions

View File

@ -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<string>(8).fill("invalid"), "unchanged"]);
});
it("parses mixed batches preserving ordering", () => {

View File

@ -8,6 +8,22 @@ const resultSchema = z.object({
export type DemangledSymbol = z.infer<typeof resultSchema>;
/**
* 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",

View File

@ -68,5 +68,39 @@ 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);
const prefixes = ["_T0", "$S", "_$S", "$e", "_$e"];
const called = await client.callTool({
name: "demangle_swift",
arguments: {
symbols: [
...prefixes.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([
...prefixes.map(() => "demangled"),
...prefixes.map(() => "invalid"),
"unchanged",
]);
},
);
},
);