mirror of
https://github.com/morluto/rea.git
synced 2026-11-09 16:26:36 +00:00
373 lines
12 KiB
TypeScript
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",
|
|
}),
|
|
);
|
|
});
|