mirror of
https://github.com/morluto/rea
synced 2026-10-26 22:28:27 -04:00
d0067ba34a
Resolve relative pointers using field-owned ABI encodings. Keep null, raw, rebased, imported and unknown targets distinct, and retain imported conformance rows with exact Mach-O binding provenance. Decode legacy rebase membership with source-derived memory and work bounds, preserving validated prefix facts when streams are incomplete. Validation: check:ci, docs:check and build passed; 158 source tests and 20 schema/acceptance tests passed. Ten source-built Apple dispatch variants and four issue-specific CLI/MCP workflows passed without executing targets. Paired cold/warm CLI/MCP cost observations retained locally; model tokens and maximum-sized input runtime were not measured. Closes #5683 Closes #5685 BREAKING CHANGE: Swift conformance type_name and protocol_name can be null when names cannot be resolved; consumers must use typed type_binding and protocol_binding evidence. MachoImportBinding replaces the former Objective-C-specific binding type and schema.
275 lines
8.1 KiB
TypeScript
275 lines
8.1 KiB
TypeScript
import { expect, it } from "vitest";
|
|
|
|
import { objcSwiftMetadataSchema } from "../domain/native/objcSwiftMetadata.js";
|
|
import {
|
|
createSwiftRelativeReader,
|
|
type DispatchRecordBudget,
|
|
} from "./AppleSwiftDispatchFacets.js";
|
|
|
|
const readers = (bytes: Buffer) => ({
|
|
u32: (address: bigint) => bytes.readUInt32LE(Number(address)),
|
|
pointer: (address: bigint) => bytes.readBigUInt64LE(Number(address)),
|
|
resolvePointer: (address: bigint) => ({
|
|
kind: "raw" as const,
|
|
target: bytes.readBigUInt64LE(Number(address)),
|
|
}),
|
|
string: () => "fixture",
|
|
location: (address: bigint) => ({
|
|
address: `0x${address.toString(16)}`,
|
|
file_offset: Number(address),
|
|
}),
|
|
evidence: (address: bigint, description: string) => [
|
|
{
|
|
kind: "binary_metadata" as const,
|
|
description,
|
|
location: {
|
|
address: `0x${address.toString(16)}`,
|
|
file_offset: Number(address),
|
|
},
|
|
artifact_path: "/fixture",
|
|
artifact_sha256: "a".repeat(64),
|
|
},
|
|
],
|
|
offset: (address: bigint, size = 1) => {
|
|
if (address < 0n || address + BigInt(size) > BigInt(bytes.length))
|
|
throw new RangeError("outside fixture bytes");
|
|
return Number(address);
|
|
},
|
|
});
|
|
|
|
const budget = (): DispatchRecordBudget => ({
|
|
truncated: false,
|
|
isFull: () => false,
|
|
markTruncated: () => undefined,
|
|
admit: () => true,
|
|
});
|
|
|
|
it("keeps an encoded null distinct from a nonzero relative offset to address zero", () => {
|
|
const bytes = Buffer.alloc(0x80);
|
|
bytes.writeInt32LE(0, 0x20);
|
|
bytes.writeInt32LE(-0x40, 0x40);
|
|
bytes.writeInt32LE(0x25, 0x24);
|
|
bytes.writeBigUInt64LE(0n, 0x48);
|
|
bytes.writeInt32LE(0x25, 0x2c);
|
|
bytes.writeBigUInt64LE(0x60n, 0x50);
|
|
const result = objcSwiftMetadataSchema.parse({ db_save_result: null });
|
|
const relative = createSwiftRelativeReader({
|
|
bytes,
|
|
readers: readers(bytes),
|
|
budget: budget(),
|
|
result,
|
|
});
|
|
|
|
expect(relative(0x20n, "methodImplementation")).toEqual({ kind: "null" });
|
|
expect(relative(0x40n, "methodImplementation")).toEqual({
|
|
kind: "target",
|
|
target: 0n,
|
|
});
|
|
expect(relative(0x24n, "conformanceProtocol")).toEqual({ kind: "null" });
|
|
expect(relative(0x2cn, "conformanceProtocol")).toEqual({
|
|
kind: "target",
|
|
target: 0x60n,
|
|
});
|
|
expect(result.relative_pointers).toMatchObject([
|
|
{ displacement: 0, target: { address: null, file_offset: null } },
|
|
{ displacement: -0x40, target: { address: "0x0", file_offset: 0 } },
|
|
{ displacement: 0x25, indirect: true, target: { address: null } },
|
|
{ displacement: 0x25, indirect: true, target: { address: "0x60" } },
|
|
]);
|
|
});
|
|
|
|
it("resolves zero offsets for nonnullable fields to their own field address", () => {
|
|
const bytes = Buffer.alloc(0x80);
|
|
bytes.writeInt32LE(0, 0x20);
|
|
const result = objcSwiftMetadataSchema.parse({ db_save_result: null });
|
|
const relative = createSwiftRelativeReader({
|
|
bytes,
|
|
readers: readers(bytes),
|
|
budget: budget(),
|
|
result,
|
|
});
|
|
|
|
for (const kind of [
|
|
"conformanceRecord",
|
|
"protocolName",
|
|
"typeName",
|
|
"typeMetadataRecord",
|
|
"classDescriptorName",
|
|
] as const) {
|
|
expect(relative(0x20n, kind)).toEqual({ kind: "target", target: 0x20n });
|
|
}
|
|
expect(result.relative_pointers).toHaveLength(5);
|
|
expect(result.relative_pointers).toMatchObject(
|
|
Array.from({ length: 5 }, () => ({
|
|
target: { address: "0x20", file_offset: 0x20 },
|
|
})),
|
|
);
|
|
});
|
|
|
|
it("accepts encoded zero only for nullable direct Swift fields", () => {
|
|
const bytes = Buffer.alloc(0x80);
|
|
const result = objcSwiftMetadataSchema.parse({ db_save_result: null });
|
|
const relative = createSwiftRelativeReader({
|
|
bytes,
|
|
readers: readers(bytes),
|
|
budget: budget(),
|
|
result,
|
|
});
|
|
|
|
for (const kind of [
|
|
"conformanceType",
|
|
"witnessTablePattern",
|
|
"methodImplementation",
|
|
] as const) {
|
|
expect(relative(0x20n, kind)).toEqual({ kind: "null" });
|
|
}
|
|
});
|
|
|
|
it("masks only the verified direct type metadata tag and keeps other tags unsupported", () => {
|
|
const bytes = Buffer.alloc(0x80);
|
|
bytes.writeInt32LE(0x24, 0x20);
|
|
bytes.writeInt32LE(0x25, 0x28);
|
|
bytes.writeInt32LE(0x26, 0x2c);
|
|
bytes.writeInt32LE(0x27, 0x30);
|
|
const result = objcSwiftMetadataSchema.parse({ db_save_result: null });
|
|
const relative = createSwiftRelativeReader({
|
|
bytes,
|
|
readers: readers(bytes),
|
|
budget: budget(),
|
|
result,
|
|
});
|
|
|
|
expect(relative(0x20n, "typeMetadataRecord")).toEqual({
|
|
kind: "target",
|
|
target: 0x44n,
|
|
});
|
|
for (const [field, tag] of [
|
|
[0x28n, 1],
|
|
[0x2cn, 2],
|
|
[0x30n, 3],
|
|
] as const) {
|
|
expect(relative(field, "typeMetadataRecord")).toEqual({
|
|
kind: "unknown",
|
|
reason: `Unsupported Swift type metadata record tag ${tag}`,
|
|
});
|
|
}
|
|
expect(result.relative_pointers.slice(-4)).toMatchObject([
|
|
{ displacement: 0x24, encoding_tag: 0, target: { address: "0x44" } },
|
|
{
|
|
displacement: 0x25,
|
|
encoding_tag: 1,
|
|
decode: { status: "unsupported" },
|
|
},
|
|
{
|
|
displacement: 0x26,
|
|
encoding_tag: 2,
|
|
decode: { status: "unsupported" },
|
|
},
|
|
{
|
|
displacement: 0x27,
|
|
encoding_tag: 3,
|
|
decode: { status: "unsupported" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("treats a zero pointer loaded through a nullable indirect field as null", () => {
|
|
const bytes = Buffer.alloc(0x80);
|
|
bytes.writeInt32LE(-0x23, 0x24); // odd-tagged displacement to cell address 0
|
|
const result = objcSwiftMetadataSchema.parse({ db_save_result: null });
|
|
const relative = createSwiftRelativeReader({
|
|
bytes,
|
|
readers: readers(bytes),
|
|
budget: budget(),
|
|
result,
|
|
});
|
|
|
|
expect(relative(0x24n, "conformanceProtocol")).toEqual({ kind: "null" });
|
|
expect(result.relative_pointers[0]).toMatchObject({
|
|
indirect: true,
|
|
target: { address: null, file_offset: null },
|
|
});
|
|
});
|
|
|
|
it("keeps a fixup rebase to VM address zero as an address, not null", () => {
|
|
const bytes = Buffer.alloc(0x80);
|
|
bytes.writeInt32LE(-0x23, 0x24);
|
|
const result = objcSwiftMetadataSchema.parse({ db_save_result: null });
|
|
const relative = createSwiftRelativeReader({
|
|
bytes,
|
|
readers: {
|
|
...readers(bytes),
|
|
resolvePointer: () => ({ kind: "rebase", target: 0n }),
|
|
offset: (address, size = 1) => {
|
|
if (address === 0n) throw new RangeError("unmapped target address");
|
|
return readers(bytes).offset(address, size);
|
|
},
|
|
},
|
|
budget: budget(),
|
|
result,
|
|
});
|
|
|
|
expect(relative(0x24n, "conformanceProtocol")).toEqual({
|
|
kind: "target",
|
|
target: 0n,
|
|
});
|
|
expect(result.relative_pointers[0]).toMatchObject({
|
|
target: { address: "0x0", file_offset: null },
|
|
decode: { status: "partial", reason: "unmapped target address" },
|
|
});
|
|
});
|
|
|
|
it("preserves an indirect import binding instead of reading VM address zero", () => {
|
|
const bytes = Buffer.alloc(0x80);
|
|
bytes.writeInt32LE(-0x23, 0x24);
|
|
const result = objcSwiftMetadataSchema.parse({ db_save_result: null });
|
|
const relative = createSwiftRelativeReader({
|
|
bytes,
|
|
readers: {
|
|
...readers(bytes),
|
|
resolvePointer: () => ({
|
|
kind: "bind",
|
|
symbol: "_$sSQMp",
|
|
binding_source: "chained",
|
|
library_ordinal: 1,
|
|
library: "/System/Library/libswiftCore.dylib",
|
|
weak: false,
|
|
addend: 0n,
|
|
}),
|
|
},
|
|
budget: budget(),
|
|
result,
|
|
});
|
|
|
|
expect(relative(0x24n, "conformanceProtocol")).toMatchObject({
|
|
kind: "binding",
|
|
binding: { symbol: "_$sSQMp" },
|
|
});
|
|
expect(result.relative_pointers[0]).toMatchObject({
|
|
target: { address: null, file_offset: null },
|
|
binding: { symbol: "_$sSQMp", requested_address: "0x0" },
|
|
decode: { status: "partial" },
|
|
});
|
|
});
|
|
|
|
it("retains unsafe target addresses as partial decode facts", () => {
|
|
const bytes = Buffer.alloc(0x80);
|
|
bytes.writeInt32LE(0x100, 0x20);
|
|
const result = objcSwiftMetadataSchema.parse({ db_save_result: null });
|
|
const relative = createSwiftRelativeReader({
|
|
bytes,
|
|
readers: readers(bytes),
|
|
budget: budget(),
|
|
result,
|
|
});
|
|
|
|
expect(relative(0x20n, "protocolName")).toEqual({
|
|
kind: "target",
|
|
target: 0x120n,
|
|
});
|
|
expect(result.relative_pointers[0]).toMatchObject({
|
|
target: { address: "0x120", file_offset: null },
|
|
decode: { status: "partial", reason: "outside fixture bytes" },
|
|
});
|
|
});
|