fix(native): distinguish symbol definitions from metadata
This commit is contained in:
1 parent
890bb66b35
commit
70bad53847
6 files changed
+81
-9
No files matched your search
@@ -143,7 +143,7 @@ describe("function comparison normalized identity", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["sub_deadbeef", "fcn.00401000"])(
|
||||
it.each(["sub_deadbeef", "fcn.00401000", "FUN_00401000", "FUN_deadBEEF"])(
|
||||
"keeps %s as an address-derived name",
|
||||
(name) => {
|
||||
const generated = (base: "0x1000" | "0x2000") =>
|
||||
@@ -169,6 +169,8 @@ describe("function comparison normalized identity", () => {
|
||||
it.each([
|
||||
{ name: "sub_deallocate", status: "unchanged" },
|
||||
{ name: "sub_deadbeef", status: "unknown" },
|
||||
{ name: "FUN_00401000", status: "unknown" },
|
||||
{ name: "FUN_initialize", status: "unchanged" },
|
||||
])("preserves the calls dimension for callee $name", ({ name, status }) => {
|
||||
const calling = (base: "0x1000" | "0x2000") =>
|
||||
functionDossierSchema.parse({
|
||||
|
||||
@@ -182,8 +182,9 @@ export const sorted = (values: readonly unknown[]): readonly unknown[] =>
|
||||
.sort((left, right) => compareUnicodeCodePoints(left.json, right.json))
|
||||
.map(({ value }) => value);
|
||||
|
||||
/** Recognize address-derived procedure names without excluding named FUN_ symbols. */
|
||||
export const isAutoName = (name: string): boolean =>
|
||||
/^(?:sub_[0-9a-f]+|fcn\.[0-9a-f]+)$/iu.test(name);
|
||||
/^(?:sub_[0-9a-f]+|fcn\.[0-9a-f]+|FUN_[0-9a-f]+)$/iu.test(name);
|
||||
|
||||
const parseAddress = (value: string): bigint | null =>
|
||||
/^0x[0-9a-f]+$/iu.test(value) ? BigInt(value) : null;
|
||||
|
||||
@@ -42,7 +42,11 @@ export const classifySwiftSymbol = (
|
||||
const kind = mangling.charAt(end);
|
||||
const nominal = NOMINAL_CATEGORIES.get(kind);
|
||||
if (nominal === undefined) {
|
||||
if (kind === "E" && category !== null) category = "extensions";
|
||||
// Current swiftc emits protocol descriptors as `…<Nominal>Mp` without
|
||||
// the nominal `P` marker (e.g. `_$s4main7ScoringMp`). Decode that exact
|
||||
// suffix; unknown substitution encodings stay unclassified, never guessed.
|
||||
if (mangling.slice(end) === "Mp") category = "protocols";
|
||||
else if (kind === "E" && category !== null) category = "extensions";
|
||||
break;
|
||||
}
|
||||
category = nominal;
|
||||
|
||||
@@ -12,6 +12,7 @@ describe("symbol analysis", () => {
|
||||
{ address: "0x1", name: "_OBJC_CLASS_$_App" },
|
||||
{ address: "0x2", name: "_OBJC_CLASS_$_App" },
|
||||
{ address: "0x3", name: "_OBJC_PROTOCOL_$_Delegate" },
|
||||
{ address: "0x4", name: "__OBJC_PROTOCOL_REFERENCE_$_Delegate" },
|
||||
{ address: "0x4", name: "_OBJC_PROTOCOL_$_Delegate" },
|
||||
];
|
||||
expect(discoverObjcClasses(names, "App")).toMatchObject({ count: 1 });
|
||||
@@ -36,6 +37,39 @@ describe("symbol analysis", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("recognizes fragile Apple Objective-C class records without alias duplication", () => {
|
||||
// Apple clang -target i386-apple-macosx10.5
|
||||
// -fobjc-runtime=macosx-fragile-10.5 emits these local class records.
|
||||
// The `.objc_class_name_*` absolute symbols alias the same class and are
|
||||
// deliberately not selected.
|
||||
const names = [
|
||||
{ address: "0x1ac", name: "l_OBJC_CLASS_ProbeClass" },
|
||||
{ address: "0x0", name: ".objc_class_name_ProbeClass" },
|
||||
// A class named `NAME_` produces this exact legacy local symbol.
|
||||
{ address: "0x0", name: "l_OBJC_CLASS_NAME_" },
|
||||
];
|
||||
expect(discoverObjcClasses(names, "")).toEqual({
|
||||
count: 2,
|
||||
classes: [
|
||||
{ address: "0x1ac", name: "l_OBJC_CLASS_ProbeClass" },
|
||||
{ address: "0x0", name: "l_OBJC_CLASS_NAME_" },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("discovers modern Swift protocol descriptors without treating methods as declarations", () => {
|
||||
// Current swiftc emits `…Mp` descriptors without the nominal `P` marker.
|
||||
const names = [
|
||||
{ address: "0xf0", name: "_$s4main7ScoringMp" },
|
||||
{ address: "0x100", name: "_$s4main7ScoringTL" },
|
||||
{ address: "0x110", name: "_$s4main7ScoringP5scoreSiyFTq" },
|
||||
];
|
||||
expect(discoverObjcProtocols(names)).toEqual({
|
||||
count: 1,
|
||||
protocols: [{ address: "0xf0", name: "_$s4main7ScoringMp" }],
|
||||
});
|
||||
});
|
||||
|
||||
it("categorizes every Swift mangling family and deduplicates names", () => {
|
||||
const result = categorizeSwiftTypes([
|
||||
{ address: "1", name: "_TtCClass" },
|
||||
|
||||
@@ -5,6 +5,18 @@ import {
|
||||
type SwiftTypeCategory,
|
||||
} from "./swiftSymbolClassification.js";
|
||||
|
||||
/** Objective-C class definition markers emitted by modern Apple clang. */
|
||||
const OBJC_CLASS_MARKER = "OBJC_CLASS_$_" as const;
|
||||
|
||||
/** Local class records emitted by Apple's fragile Objective-C ABI. */
|
||||
const LEGACY_OBJC_CLASS_PREFIX = "l_OBJC_CLASS_" as const;
|
||||
|
||||
/**
|
||||
* Objective-C protocol declaration marker. `PROTOCOL_REFERENCE` slots
|
||||
* share the substring but are uses, not definitions.
|
||||
*/
|
||||
const OBJC_PROTOCOL_MARKER = "OBJC_PROTOCOL_$_" as const;
|
||||
|
||||
/** Select and deduplicate Objective-C class labels. */
|
||||
export const discoverObjcClasses = (
|
||||
names: readonly AddressedName[],
|
||||
@@ -12,8 +24,11 @@ export const discoverObjcClasses = (
|
||||
): JsonValue => {
|
||||
const classes = uniqueByName(
|
||||
names
|
||||
.filter(({ name }) =>
|
||||
["OBJC_CLASS", "OBJC_$_CLASS"].some((marker) => name.includes(marker)),
|
||||
.filter(
|
||||
({ name }) =>
|
||||
name.includes(OBJC_CLASS_MARKER) ||
|
||||
(name.startsWith(LEGACY_OBJC_CLASS_PREFIX) &&
|
||||
name.length > LEGACY_OBJC_CLASS_PREFIX.length),
|
||||
)
|
||||
.filter(({ name }) => pattern.length === 0 || name.includes(pattern)),
|
||||
);
|
||||
@@ -29,7 +44,11 @@ export const discoverObjcProtocols = (
|
||||
): JsonValue => {
|
||||
const protocols = uniqueByName(
|
||||
names.filter(
|
||||
({ name }) => name.includes("OBJC_PROTOCOL") || name.includes("_TtP"),
|
||||
({ name }) =>
|
||||
name.includes(OBJC_PROTOCOL_MARKER) ||
|
||||
name.includes("_TtP") ||
|
||||
(name.endsWith("Mp") &&
|
||||
classifySwiftSymbol(name)?.category === "protocols"),
|
||||
),
|
||||
);
|
||||
return {
|
||||
|
||||
@@ -27,8 +27,17 @@ describe("enhanced MCP tools", () => {
|
||||
expect(name).toBe("list_names");
|
||||
return Promise.resolve(
|
||||
ok([
|
||||
{ address: "0x1", value: "_OBJC_CLASS_$_First" },
|
||||
{ address: "0x2", value: "_OBJC_CLASS_$_Last" },
|
||||
// Apple clang modern Objective-C ABI `nm` output: the first row
|
||||
// is a class definition; the remaining rows are method metadata,
|
||||
// class_ro_t, and a class reference.
|
||||
{ address: "0x70", value: "_OBJC_CLASS_$_ProbeClass" },
|
||||
{ address: "0x1a8", value: "__OBJC_$_CLASS_METHODS_ProbeClass" },
|
||||
{ address: "0x230", value: "__OBJC_CLASS_RO_$_ProbeClass" },
|
||||
{ address: "0x450", value: "_OBJC_CLASSLIST_REFERENCES_$_" },
|
||||
{
|
||||
address: "0x298",
|
||||
value: "__OBJC_$_CATEGORY_CLASS_METHODS_ProbeClass_$_Category",
|
||||
},
|
||||
]),
|
||||
);
|
||||
},
|
||||
@@ -36,7 +45,10 @@ describe("enhanced MCP tools", () => {
|
||||
const result = jsonResult(
|
||||
await client.callTool({ name: "get_objc_classes", arguments: {} }),
|
||||
);
|
||||
expect(result).toMatchObject({ count: 2 });
|
||||
expect(result).toEqual({
|
||||
count: 1,
|
||||
classes: [{ address: "0x70", name: "_OBJC_CLASS_$_ProbeClass" }],
|
||||
});
|
||||
});
|
||||
|
||||
it("returns the complete overview inline with exhaustive totals", async () => {
|
||||
|
||||
Reference in new issue
Block a user