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

373 lines
12 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
inspectNativeDispatchMetadata,
objcImplementationClassName,
} from "./objcSwiftMetadata.js";
describe("symbol-only Objective-C implementation identity", () => {
it.each([" ", " ", "\t"])(
"interprets legacy symbol ownership with separator %j without rewriting retained facts",
(separator) => {
const metadata = inspectNativeDispatchMetadata(
[
{
address: "0x1000",
name: `-[StoreController(Actions)${separator}buildTapped:]`,
},
],
1,
);
const current = metadata.objc_dispatch_implementations[0]!;
const { category: _category, ...record } = current;
const legacy = { ...record, class_name: "StoreController(Actions)" };
expect(objcImplementationClassName(legacy)).toBe("StoreController");
expect(legacy.class_name).toBe("StoreController(Actions)");
expect(legacy.evidence).toBe(current.evidence);
expect(objcImplementationClassName({ ...legacy, evidence: [] })).toBe(
legacy.class_name,
);
expect(
objcImplementationClassName({
...legacy,
evidence: [
...legacy.evidence,
{
...legacy.evidence[0]!,
description:
"Provider symbol -[OtherController(Actions) buildTapped:]",
},
],
}),
).toBe(legacy.class_name);
expect(objcImplementationClassName({ ...legacy, category: null })).toBe(
legacy.class_name,
);
expect(
objcImplementationClassName({
...legacy,
decode: { status: "decoded", reason: null },
}),
).toBe(legacy.class_name);
},
);
it("parses symbol category ownership once and retains the original symbol evidence", () => {
const names = [
"-[StoreController(Actions) buildTapped:]",
"-[StoreController(OtherActions) buildTapped:]",
];
const result = inspectNativeDispatchMetadata(
names.map((name) => ({ address: "0x1000", name })),
2,
);
expect(result.objc_classes.map(({ name }) => name)).toEqual([
"StoreController",
]);
expect(result.objc_dispatch_implementations).toMatchObject([
{
class_name: "StoreController",
category: "Actions",
decode: { status: "partial", reason: "symbol_name_only" },
evidence: [{ description: `Provider symbol ${names[0]}` }],
},
{
class_name: "StoreController",
category: "OtherActions",
evidence: [{ description: `Provider symbol ${names[1]}` }],
},
]);
});
it("retains an owner outside the category-symbol grammar without guessing a category", () => {
const result = inspectNativeDispatchMetadata(
[{ address: "0x1000", name: "-[StoreController(A)(B) buildTapped:]" }],
1,
);
expect(result.objc_dispatch_implementations[0]).toMatchObject({
class_name: "StoreController(A)(B)",
});
expect(result.objc_dispatch_implementations[0]).not.toHaveProperty(
"category",
);
});
it("orders distinct category identities independently of provider inventory order", () => {
const names = [
"-[StoreController(Actions) buildTapped:]",
"-[StoreController(OtherActions) buildTapped:]",
];
const inspect = (ordered: string[]) =>
inspectNativeDispatchMetadata(
ordered.map((name) => ({ address: "0x1000", name })),
ordered.length,
);
const forward = inspect(names);
const reverse = inspect([...names].reverse());
expect(reverse).toEqual(forward);
expect(
reverse.objc_dispatch_implementations.map(({ category }) => category),
).toEqual(["Actions", "OtherActions"]);
expect(
reverse.objc_dispatch_implementations.map(({ evidence }) =>
evidence.map(({ description }) => description),
),
).toEqual(names.map((name) => [`Provider symbol ${name}`]));
});
});
describe("symbol-only Objective-C implementation addresses", () => {
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:]" }],
1,
);
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" },
],
2,
);
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:]",
})),
ordered.length,
);
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:]",
})),
2,
);
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 symbol spelling and counts it", () => {
const names = [
"$s5Store7launchyyF",
"_$s5Store7launchyyF",
"$S5Store7launchyyF",
"_$S5Store7launchyyF",
"_T05Store7launchyyF",
"__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: 10,
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" },
],
3,
);
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 })),
7,
);
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",
}),
);
});