mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
fix(native): share exact Objective-C class symbol interpretation
This commit is contained in:
@@ -506,6 +506,12 @@ complete observation.
|
||||
comments do not change that handling. XML key names combine their text and
|
||||
CDATA segments without trimming or expanding literal CDATA entities. Other
|
||||
values and the original archive digest are preserved.
|
||||
Symbol fallback accepts complete Objective-C class/metaclass symbol leaves,
|
||||
including qualified names. Pointer/string labels that merely contain a class
|
||||
marker do not establish classes. Provider namespace method labels are not
|
||||
interpreted as Objective-C declarations; class definition versus import origin
|
||||
remains unsupported coverage rather than an inferred classification.
|
||||
|
||||
- `decode_interface_builder` / `rea decode-interface-builder <app>` reads
|
||||
compiled storyboard and nib resources into objects, outlets, actions,
|
||||
connections and controller/class names. A full app graph is often several
|
||||
|
||||
@@ -214,3 +214,34 @@ describe("ObjC/Swift metadata", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("derives classes only from complete class symbol leaves", () => {
|
||||
const result = inspectNativeDispatchMetadata(
|
||||
[
|
||||
"PTR__OBJC_CLASS_$_NSAlert_100028668",
|
||||
"s__OBJC_CLASS_$_NSAlert_10002ed0d",
|
||||
"s__OBJC_CLASS_$_NSAppleEventManage_10002ed23",
|
||||
"_OBJC_CLASS_$_Local",
|
||||
"/System/Library/Frameworks/AppKit::_OBJC_CLASS_$_NSAlert",
|
||||
"_OBJC_METACLASS_$_Local",
|
||||
"OwnController::viewDidLoad",
|
||||
].map((name, index) => ({ address: `0x${String(index + 1)}`, name })),
|
||||
);
|
||||
expect(result.objc_classes.map(({ name }) => name)).toEqual([
|
||||
"Local",
|
||||
"NSAlert",
|
||||
]);
|
||||
expect(result.coverage).toContainEqual(
|
||||
expect.objectContaining({
|
||||
facet: "objc_class_definitions",
|
||||
status: "unsupported",
|
||||
reason: expect.stringContaining("namespace_declarations"),
|
||||
}),
|
||||
);
|
||||
expect(result.coverage).toContainEqual(
|
||||
expect.objectContaining({
|
||||
facet: "objc_dispatch_implementations",
|
||||
reason: "provider_name_inventory_has_no_supported_objc_method_symbols",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { objcClassSymbol } from "../objcClassSymbol.js";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
BINARY_ARCHITECTURES,
|
||||
@@ -500,10 +501,8 @@ export const inspectNativeDispatchMetadata = (
|
||||
const parsedAddress = nativeAddressInputSchema.safeParse(entry.address);
|
||||
const address = parsedAddress.success ? parsedAddress.data : null;
|
||||
const sourceAddress = parsedAddress.success ? entry.address : null;
|
||||
const classMatch = /(?:OBJC_CLASS_|OBJC_METACLASS_)\$_([^\s]+)/u.exec(
|
||||
entry.name,
|
||||
);
|
||||
if (classMatch?.[1] !== undefined) classNames.add(classMatch[1]);
|
||||
const classSymbol = objcClassSymbol(entry.name);
|
||||
if (classSymbol !== undefined) classNames.add(classSymbol.name);
|
||||
|
||||
const methodMatch = /^([+-])\[([^\s\]]+)\s+([^\]]+)\]$/u.exec(entry.name);
|
||||
if (methodMatch?.[1] !== undefined && methodMatch[2] && methodMatch[3]) {
|
||||
@@ -605,10 +604,18 @@ export const inspectNativeDispatchMetadata = (
|
||||
? "record_limit_reached_and_only_symbolized_methods_are_resolved"
|
||||
: decodedImplementations.length > 0
|
||||
? "only_symbolized_methods_are_resolved"
|
||||
: "provider_name_inventory_has_no_objc_method_symbols",
|
||||
: "provider_name_inventory_has_no_supported_objc_method_symbols",
|
||||
examined: examinedNames.length,
|
||||
decoded: decodedImplementations.length,
|
||||
},
|
||||
{
|
||||
facet: "objc_class_definitions",
|
||||
status: "unsupported",
|
||||
reason:
|
||||
"symbol_inventory_does_not_establish_class_definitions_import_origins_or_namespace_declarations",
|
||||
examined: examinedNames.length,
|
||||
decoded: 0,
|
||||
},
|
||||
{
|
||||
facet: "objc_ivar_layouts",
|
||||
status: "unsupported",
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/** An Objective-C class symbol leaf, optionally qualified by a provider namespace. */
|
||||
export const objcClassSymbol = (
|
||||
name: string,
|
||||
):
|
||||
| {
|
||||
readonly name: string;
|
||||
readonly symbol: string;
|
||||
readonly metaclass: boolean;
|
||||
}
|
||||
| undefined => {
|
||||
const namespace = name.lastIndexOf("::");
|
||||
const leaf = namespace < 0 ? name : name.slice(namespace + 2);
|
||||
const match = /^_?(OBJC_(META)?CLASS_\$_([^\s]+))$/u.exec(leaf);
|
||||
if (match?.[1] === undefined || match[3] === undefined) return undefined;
|
||||
return {
|
||||
name: match[3],
|
||||
symbol: match[1],
|
||||
metaclass: match[2] !== undefined,
|
||||
};
|
||||
};
|
||||
@@ -224,3 +224,18 @@ it("keeps regular self-scope and weak rebinding records separate", () => {
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("excludes pointer and string labels that only contain a class marker", () => {
|
||||
const result = discoverObjcClasses(
|
||||
[
|
||||
{ address: "0x1", name: "PTR__OBJC_CLASS_$_NSAlert_100028668" },
|
||||
{ address: "0x2", name: "s__OBJC_CLASS_$_NSAlert_10002ed0d" },
|
||||
{ address: "0x3", name: "AppKit::_OBJC_CLASS_$_NSAlert" },
|
||||
],
|
||||
"",
|
||||
);
|
||||
expect(result).toMatchObject({
|
||||
count: 1,
|
||||
classes: [{ address: "0x3", name: "AppKit::_OBJC_CLASS_$_NSAlert" }],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { objcClassSymbol } from "./objcClassSymbol.js";
|
||||
import type { JsonValue } from "./jsonValue.js";
|
||||
import type { AddressedName } from "./hopperValues.js";
|
||||
import type {
|
||||
@@ -10,9 +11,6 @@ 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;
|
||||
|
||||
@@ -44,7 +42,7 @@ export const selectObjcClassCandidates = (
|
||||
names
|
||||
.filter(
|
||||
({ name, objc_class_label }) =>
|
||||
name.includes(OBJC_CLASS_MARKER) ||
|
||||
objcClassSymbol(name)?.metaclass === false ||
|
||||
objc_class_label === "hopper_legacy_class_object" ||
|
||||
(name.startsWith(LEGACY_OBJC_CLASS_PREFIX) &&
|
||||
name.length > LEGACY_OBJC_CLASS_PREFIX.length &&
|
||||
@@ -333,20 +331,11 @@ const coalesceBoundClassAliases = (
|
||||
};
|
||||
|
||||
const classNameFromImport = (symbol: string): string | undefined => {
|
||||
const canonical = symbol.startsWith("_") ? symbol.slice(1) : symbol;
|
||||
return canonical.startsWith(OBJC_CLASS_MARKER) &&
|
||||
canonical.length > OBJC_CLASS_MARKER.length
|
||||
? canonical
|
||||
: undefined;
|
||||
const facts = objcClassSymbol(symbol);
|
||||
return facts?.metaclass === false ? facts.symbol : undefined;
|
||||
};
|
||||
|
||||
const classNameFromDisplayName = (name: string): string | undefined =>
|
||||
name.startsWith(OBJC_CLASS_MARKER) && name.length > OBJC_CLASS_MARKER.length
|
||||
? name
|
||||
: name.startsWith(`_${OBJC_CLASS_MARKER}`) &&
|
||||
name.length > OBJC_CLASS_MARKER.length + 1
|
||||
? name.slice(1)
|
||||
: undefined;
|
||||
const classNameFromDisplayName = classNameFromImport;
|
||||
|
||||
const toObjcClassEntry = (entry: ObjcClassOutputEntry): JsonValue => ({
|
||||
address: entry.address,
|
||||
|
||||
Reference in New Issue
Block a user