fix(native): retain unclassified accepted Swift manglings

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: 吴佳浩 <wujiahao@supremind.com>
This commit is contained in:
morluto
2026-10-14 15:20:04 +08:00
parent 74afb04f32
commit 9d00d7d7fa
2 changed files with 60 additions and 3 deletions
+42
View File
@@ -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([]);
});
});
+18 -3
View File
@@ -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<string, string[]>();
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);