Files
rea/src/native/AppleSwiftRelativePointer.test.ts
T
morluto d0067ba34a fix(native)!: preserve Swift pointer nullability and import bindings
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.
2026-10-21 08:17:12 +08:00

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" },
});
});