mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(native): distinguish undecoded Swift spelling adjustments
Recognize known undecoded producer spellings without treating them as successful demangles. Preserve actual producer output and derive symbol types from the domain contract instead of duplicating its validation schema. Compare retained output with the real demangler through MCP, while preserving CLI malformed-input and line-ending coverage. Refs: #4215, #4287 Co-authored-by: Vinícius DS Andrade <95101505+viniciusdsandrade@users.noreply.github.com>
This commit is contained in:
1 parent
2f6b9ecdc6
commit
808339f0b1
3 files changed
+89
-21
No files matched your search
@@ -59,6 +59,43 @@ describe("parseDemangledSymbols", () => {
|
||||
).toEqual([...Array<string>(8).fill("invalid"), "unchanged"]);
|
||||
});
|
||||
|
||||
it("keeps the demangler's adjusted spelling of an undecoded name out of demangled", () => {
|
||||
const inputs = [
|
||||
"__T0Bogus",
|
||||
"__$sBogus",
|
||||
"___stack_chk_fail",
|
||||
"strlen",
|
||||
"Symbol",
|
||||
"entry",
|
||||
"__T04main3FooV",
|
||||
"s4main3FooV",
|
||||
];
|
||||
const output = [
|
||||
"_T0Bogus",
|
||||
"_$sBogus",
|
||||
"__stack_chk_fail",
|
||||
"$strlen",
|
||||
"$Symbol",
|
||||
"$entry",
|
||||
"main.Foo",
|
||||
"main.Foo",
|
||||
].join("\n");
|
||||
expect(parseDemangledSymbols(inputs, `${output}\n`)).toEqual([
|
||||
{ input: "__T0Bogus", output: "_T0Bogus", status: "invalid" },
|
||||
{ input: "__$sBogus", output: "_$sBogus", status: "invalid" },
|
||||
{
|
||||
input: "___stack_chk_fail",
|
||||
output: "__stack_chk_fail",
|
||||
status: "unchanged",
|
||||
},
|
||||
{ input: "strlen", output: "$strlen", status: "unchanged" },
|
||||
{ input: "Symbol", output: "$Symbol", status: "unchanged" },
|
||||
{ input: "entry", output: "$entry", status: "unchanged" },
|
||||
{ input: "__T04main3FooV", output: "main.Foo", status: "demangled" },
|
||||
{ input: "s4main3FooV", output: "main.Foo", status: "demangled" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("parses mixed batches preserving ordering", () => {
|
||||
const inputs = ["$s4main4testyyF", "_printf", "$sInvalidMangle"];
|
||||
const output = "main.test() -> ()\n_printf\n$sInvalidMangle\n";
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import { z } from "zod";
|
||||
import type { z } from "zod";
|
||||
|
||||
import { isSwiftMangledSymbolName } from "../../domain/swiftSymbolClassification.js";
|
||||
import type { demangleSwiftSchema } from "../../domain/native/nativeInspection.js";
|
||||
|
||||
const resultSchema = z.object({
|
||||
input: z.string(),
|
||||
output: z.string(),
|
||||
status: z.enum(["demangled", "unchanged", "invalid"]),
|
||||
});
|
||||
type DemangledSymbol = z.infer<typeof demangleSwiftSchema>["symbols"][number];
|
||||
|
||||
export type DemangledSymbol = z.infer<typeof resultSchema>;
|
||||
/**
|
||||
* swift-demangle removes one underscore from a name starting with `__` before
|
||||
* demangling, and prefixes `$` to an argument starting with S, s or e. A name
|
||||
* it cannot decode is printed in that adjusted spelling.
|
||||
*/
|
||||
const linkageName = (input: string): string =>
|
||||
input.startsWith("__") ? input.slice(1) : input;
|
||||
const undecodedSpelling = (input: string): string =>
|
||||
/^[Sse]/u.test(input) ? `$${input}` : linkageName(input);
|
||||
|
||||
/**
|
||||
* Preserve input ordering while parsing `swift-demangle --compact` lines.
|
||||
@@ -26,19 +31,21 @@ export const parseDemangledSymbols = (
|
||||
const lines = output.replace(/\n$/u, "").split("\n");
|
||||
if (lines.length !== inputs.length)
|
||||
throw new TypeError("swift-demangle output count does not match input");
|
||||
return inputs.map((input, index) => {
|
||||
return inputs.map<DemangledSymbol>((input, index) => {
|
||||
const value = lines[index];
|
||||
if (value === undefined)
|
||||
throw new TypeError("swift-demangle omitted an output line");
|
||||
return resultSchema.parse({
|
||||
const decoded = value !== input && value !== undecodedSpelling(input);
|
||||
return {
|
||||
input,
|
||||
// Status describes decoding; retain the producer's exact spelling even
|
||||
// when it adjusted an identifier that it could not decode.
|
||||
output: value,
|
||||
status:
|
||||
value === input
|
||||
? isSwiftMangledSymbolName(input)
|
||||
? "invalid"
|
||||
: "unchanged"
|
||||
: "demangled",
|
||||
});
|
||||
status: decoded
|
||||
? "demangled"
|
||||
: isSwiftMangledSymbolName(linkageName(input))
|
||||
? "invalid"
|
||||
: "unchanged",
|
||||
};
|
||||
});
|
||||
};
|
||||
@@ -1,5 +1,7 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { copyFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
import { describe, expect } from "vitest";
|
||||
|
||||
@@ -11,6 +13,7 @@ import { mcpTest } from "../../support/mcp/mcpFixture.js";
|
||||
import { openProductionTarget } from "../../support/mcp/productionSession.js";
|
||||
|
||||
const demangledSchema = evidenceResultOf(demangleSwiftSchema);
|
||||
const execute = promisify(execFile);
|
||||
|
||||
describe.skipIf(process.platform !== "darwin")(
|
||||
"native Swift demangling through MCP",
|
||||
@@ -86,19 +89,40 @@ describe.skipIf(process.platform !== "darwin")(
|
||||
...decodable.map((prefix) => `${prefix}4main3FooV`),
|
||||
...prefixes.map((prefix) => `${prefix}Bogus`),
|
||||
"_Tprintf",
|
||||
// Current swift-demangle prints these undecoded names as
|
||||
// _T0Bogus, __stack_chk_fail and $strlen.
|
||||
"__T0Bogus",
|
||||
"___stack_chk_fail",
|
||||
"strlen",
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(called.isError, JSON.stringify(called.content)).not.toBe(true);
|
||||
expect(
|
||||
demangledSchema
|
||||
.parse(called.structuredContent)
|
||||
.normalized_result.symbols.map(({ status }) => status),
|
||||
).toEqual([
|
||||
const result = demangledSchema.parse(
|
||||
called.structuredContent,
|
||||
).normalized_result;
|
||||
const { symbols } = result;
|
||||
expect(symbols.map(({ status }) => status)).toEqual([
|
||||
...decodable.map(() => "demangled"),
|
||||
...prefixes.map(() => "invalid"),
|
||||
"unchanged",
|
||||
"invalid",
|
||||
"unchanged",
|
||||
"unchanged",
|
||||
]);
|
||||
// Toolchain versions can differ in their undecoded spelling. Compare
|
||||
// retained Evidence with the actual producer instead of rewriting it
|
||||
// to match the input or requiring one toolchain's adjustment.
|
||||
const command = result.provenance.find(
|
||||
({ tool }) => tool === "swift-demangle",
|
||||
)?.command;
|
||||
const executable = command?.[0];
|
||||
if (executable === undefined || command === undefined)
|
||||
throw new Error("Missing demangler command provenance");
|
||||
const { stdout } = await execute(executable, command.slice(1));
|
||||
expect(symbols.map(({ output }) => output)).toEqual(
|
||||
stdout.replace(/\n$/u, "").split("\n"),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user