Files
reverse-engineering-anything/src/domain/native/objcSwiftMetadata.test.ts
T

248 lines
8.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { inspectNativeDispatchMetadata } from "./objcSwiftMetadata.js";
describe("symbol-only Objective-C implementation identity", () => {
it.each([
{ source: "0X00002000", canonical: "0x2000", numeric: 8192 },
{
source: "0x1fffffffffffff",
canonical: "0x1fffffffffffff",
numeric: Number.MAX_SAFE_INTEGER,
},
{
source: "0x20000000000000",
canonical: "0x20000000000000",
numeric: null,
},
{ source: "ram:0x2000", canonical: "ram:0x2000", numeric: null },
{ source: "rom:0x2000", canonical: "rom:0x2000", numeric: null },
])(
"preserves $source without inventing a numeric method identity",
({ source, canonical, numeric }) => {
const result = inspectNativeDispatchMetadata([
{ address: source, name: "-[StoreController buildTapped:]" },
]);
expect(result.objc_classes[0]?.methods[0]?.address).toBe(numeric);
expect(result.objc_dispatch_implementations[0]).toMatchObject({
implementation_address: canonical,
location: { address: canonical },
evidence: [{ location: { address: source } }],
});
},
);
it("retains unparseable symbol coordinates as reported evidence, without claiming a location", () => {
const source = "invalid::0x2000";
const result = inspectNativeDispatchMetadata([
{ address: source, name: "-[StoreController buildTapped:]" },
{ address: source, name: "$s5Store7launchyyF" },
]);
expect(result.objc_dispatch_implementations[0]).toMatchObject({
implementation_address: null,
location: { address: null },
evidence: [{ location: { address: null }, reported_address: source }],
});
expect(result.swift_symbols[0]).toMatchObject({
address: null,
evidence: [{ location: { address: null }, reported_address: source }],
});
});
it("merges equivalent method coordinates and retains every source spelling", () => {
const addresses = ["0x2000", "0X002000"];
for (const ordered of [addresses, [...addresses].reverse()]) {
const result = inspectNativeDispatchMetadata(
ordered.map((address) => ({
address,
name: "-[StoreController buildTapped:]",
})),
);
expect(result.objc_dispatch_implementations).toHaveLength(1);
expect(result.objc_dispatch_implementations[0]).toMatchObject({
implementation_address: "0x2000",
location: { address: "0x2000" },
});
expect(
result.objc_dispatch_implementations[0]?.evidence.map(
({ location }) => location.address,
),
).toEqual(ordered);
expect(result.objc_classes[0]?.methods).toMatchObject([
{ selector: "buildTapped:", address: 8192 },
]);
}
});
it.each([
["0x2000", "0x3000"],
["ram:0x2000", "rom:0x2000"],
["", "0x2000"],
["unresolved", "unresolved"],
])(
"retains distinct or unknown implementation identities: %j and %j",
(left, right) => {
const result = inspectNativeDispatchMetadata(
[left, right].map((address) => ({
address,
name: "-[StoreController buildTapped:]",
})),
);
expect(result.objc_dispatch_implementations).toHaveLength(2);
expect(result.objc_classes[0]?.methods).toEqual([
{
selector: "buildTapped:",
method_type: "instance",
address: null,
is_required: null,
is_optional: null,
},
]);
},
);
});
describe("ObjC/Swift metadata", () => {
it("bounds symbol projections and labels symbol-only metadata as partial", () => {
const result = inspectNativeDispatchMetadata(
[
{ address: "ram:0x1000", name: "OBJC_CLASS_$_StoreController" },
{ address: "ram:0x2000", name: "-[StoreController buildTapped:]" },
{ address: "ram:0x3000", name: "$s5Store7launchyyF" },
],
2,
);
expect(result.objc_classes).toHaveLength(1);
expect(result.objc_dispatch_implementations).toHaveLength(1);
expect(result.objc_classes[0]).toMatchObject({
is_meta_class: null,
is_root_class: null,
ivar_count: null,
});
expect(result.objc_classes[0]?.methods[0]).toMatchObject({
is_required: null,
is_optional: null,
});
expect(result.swift_symbols).toHaveLength(0);
expect(result.coverage).toContainEqual(
expect.objectContaining({
facet: "objc_class_symbols",
status: "partial",
reason: expect.stringContaining("record_limit_reached"),
}),
);
expect(result.coverage).toContainEqual(
expect.objectContaining({
facet: "swift_dispatch_tables",
status: "unsupported",
}),
);
});
it("projects every accepted Swift mangling prefix and counts it", () => {
const names = [
"$s5Store7launchyyF",
"_$s5Store7launchyyF",
"$S5Store7launchyyF",
"_$S5Store7launchyyF",
"_T05Store7launchyyF",
"@__swiftmacro_5Store7launchfMf_",
"$e5Store7launchyyF",
"_$e5Store7launchyyF",
"_TtC5Store11StoreHandle",
"_Tprintf",
];
const result = inspectNativeDispatchMetadata(
names.map((name, index) => ({
address: `ram:0x${(0x1000 + index * 0x100).toString(16)}`,
name,
})),
names.length,
);
expect(result.swift_symbols.map((entry) => entry.mangled_name)).toEqual(
names.slice(0, -1),
);
expect(
result.swift_symbols.every((entry) => entry.decode.status === "partial"),
).toBe(true);
expect(result.coverage).toContainEqual(
expect.objectContaining({
facet: "swift_declarations",
examined: 9,
decoded: 0,
}),
);
});
it("retains unlocated Swift and Objective-C symbols with explicit unknown addresses and source evidence", () => {
const result = inspectNativeDispatchMetadata([
{ name: "$s4Test7MyClassC", address: "" },
{ name: "-[StoreController buildTapped:]", address: "" },
{ name: "$s5Store7launchyyF", address: "ram:0x3000" },
]);
expect(result.swift_symbols).toMatchObject([
{
mangled_name: "$s4Test7MyClassC",
address: null,
evidence: [{ location: { address: null }, reported_address: "" }],
},
{ mangled_name: "$s5Store7launchyyF", address: "ram:0x3000" },
]);
expect(result.swift_symbols[1]?.evidence[0]).not.toHaveProperty(
"reported_address",
);
expect(result.objc_dispatch_implementations).toMatchObject([
{
class_name: "StoreController",
implementation_address: null,
location: { address: null },
evidence: [{ location: { address: null }, reported_address: "" }],
},
]);
expect(result.objc_classes[0]?.methods[0]).toMatchObject({
selector: "buildTapped:",
address: null,
});
expect(result.coverage).toContainEqual(
expect.objectContaining({
facet: "swift_declarations",
status: "partial",
examined: 2,
decoded: 0,
}),
);
});
});
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",
}),
);
});