mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
fix(apple): decode Swift conformance type references
Decode direct Objective-C class names separately from nominal descriptors, retain indirect class bindings as partial facts, and preserve readable protocol and witness evidence when related bytes are unavailable. Validated with 98 focused tests, check:ci, docs:check, and named-schema CLI/MCP replays of compiler-produced imported-class and struct fixtures plus an inert kind-3 control. The fixtures were inspected statically, never executed. Fixes #5738
This commit is contained in:
@@ -451,6 +451,13 @@ complete observation.
|
||||
an unfixed zero in a nullable ABI field is treated as null. Incomplete or
|
||||
conflicting fixup streams keep affected pointers unresolved; coverage reports
|
||||
parsing and resource limits while retaining validated prefix facts.
|
||||
Conformance records preserve the raw three-bit `type_reference_kind`. Kinds
|
||||
0 and 1 identify direct and indirect nominal descriptors; kind 2 preserves
|
||||
the inline Objective-C class name and its source location. Kind 3 preserves
|
||||
an imported class binding or the local class-object address when available,
|
||||
but class metadata/name recovery requiring the Objective-C runtime remains
|
||||
partial. Unknown kinds retain the raw kind and reference location without
|
||||
being treated as absent or assigned a guessed type name.
|
||||
|
||||
So an external superclass resolves from its `_OBJC_CLASS_$_` bind (for
|
||||
example `NSObject`). The `pointer_fixups` coverage facet names the mechanism.
|
||||
|
||||
@@ -214,8 +214,14 @@ export type MachoImportBinding = z.infer<typeof machoImportBindingSchema>;
|
||||
/** Swift protocol conformance and its associated witness-table location. */
|
||||
export const swiftConformanceSchema = z.strictObject({
|
||||
type_name: z.string().min(1).nullable(),
|
||||
/** Raw three-bit Swift ABI type-reference kind; absent in older Evidence. */
|
||||
type_reference_kind: z.number().int().min(0).max(7).optional(),
|
||||
/** Address of the descriptor, class-name string, or resolved ObjC class reference. */
|
||||
type_reference_target: nativeMetadataLocationSchema.optional(),
|
||||
type_binding: machoImportBindingSchema.nullable().optional(),
|
||||
protocol_name: z.string().min(1).nullable(),
|
||||
/** Resolved protocol descriptor address, retained when its bytes are unavailable. */
|
||||
protocol_target: nativeMetadataLocationSchema.optional(),
|
||||
protocol_binding: machoImportBindingSchema.nullable().optional(),
|
||||
module: z.string().nullable(),
|
||||
witness_table: nativeMetadataLocationSchema,
|
||||
|
||||
@@ -121,7 +121,7 @@ const fixture = (
|
||||
const provenance = { path: "/fixture/app", sha256: "a".repeat(64) };
|
||||
|
||||
const swiftExternalSwiftBindingImage = (
|
||||
boundField: "protocol" | "type" = "protocol",
|
||||
boundField: "protocol" | "type" | "objc-class" = "protocol",
|
||||
) => {
|
||||
const bytes = fixture();
|
||||
const sectionCommand = 32;
|
||||
@@ -141,7 +141,9 @@ const swiftExternalSwiftBindingImage = (
|
||||
const bind = Buffer.from([
|
||||
0x11, // SET_DYLIB_ORDINAL_IMM 1
|
||||
0x40,
|
||||
...Buffer.from(`${boundField === "protocol" ? "_$sSQMp" : "_$sSiMn"}\0`),
|
||||
...Buffer.from(
|
||||
`${boundField === "protocol" ? "_$sSQMp" : boundField === "objc-class" ? "_OBJC_CLASS_$_NSObject" : "_$sSiMn"}\0`,
|
||||
),
|
||||
0x70,
|
||||
0x80,
|
||||
0x16, // SET_SEGMENT_AND_OFFSET_ULEB segment 0 + offset 0xb00
|
||||
@@ -163,12 +165,15 @@ const swiftExternalSwiftBindingImage = (
|
||||
bytes.writeInt32LE(0x10, 0xa00);
|
||||
bytes.writeInt32LE(boundField === "protocol" ? 0x1ec : 0xec, 0xa14);
|
||||
bytes.writeInt32LE(0, 0xa18); // Nullable witness-table pattern.
|
||||
bytes.writeUInt32LE(boundField === "protocol" ? 0 : 8, 0xa1c);
|
||||
bytes.writeUInt32LE(
|
||||
boundField === "protocol" ? 0 : boundField === "objc-class" ? 24 : 8,
|
||||
0xa1c,
|
||||
);
|
||||
bytes.writeInt32LE(boundField === "protocol" ? 0xf1 : 0x2f0, 0xa10); // Protocol reference: imported indirect slot or local descriptor.
|
||||
bytes.writeBigUInt64LE(0n, 0xb00); // Bound slot has no in-image pointer.
|
||||
bytes.writeUInt32LE(16, 0xc00);
|
||||
bytes.writeInt32LE(0x1f8, 0xc08);
|
||||
if (boundField === "type") {
|
||||
if (boundField !== "protocol") {
|
||||
bytes.writeUInt32LE(3, 0xd00);
|
||||
bytes.writeInt32LE(0xf8, 0xd08);
|
||||
bytes.write("Equatable\0", 0xe00);
|
||||
@@ -237,6 +242,28 @@ const swiftBoundWitnessSlotImage = () => {
|
||||
return bytes;
|
||||
};
|
||||
|
||||
const swiftConformanceTypeReferenceImage = (
|
||||
typeKind: number,
|
||||
input: {
|
||||
readonly typeTargetOffset?: number;
|
||||
readonly indirectClassTargetOffset?: number;
|
||||
} = {},
|
||||
) => {
|
||||
const bytes = swiftBoundWitnessSlotImage();
|
||||
const typeTargetOffset = input.typeTargetOffset ?? 0xe60;
|
||||
bytes.writeUInt32LE(typeKind << 3, 0xa1c);
|
||||
bytes.writeInt32LE(typeTargetOffset - 0xa14, 0xa14);
|
||||
if (typeKind === 2 && typeTargetOffset === 0xe60) {
|
||||
bytes.write("NSObject\0", typeTargetOffset);
|
||||
} else if (typeKind === 3 && typeTargetOffset === 0xe60) {
|
||||
bytes.writeBigUInt64LE(
|
||||
base + BigInt(input.indirectClassTargetOffset ?? 0xe80),
|
||||
typeTargetOffset,
|
||||
);
|
||||
}
|
||||
return bytes;
|
||||
};
|
||||
|
||||
describe("Swift imported protocol references", () => {
|
||||
it("preserves bind identity without reading VM address zero and marks coverage partial", () => {
|
||||
const result = decodeAppleDispatchMetadata(
|
||||
@@ -282,6 +309,7 @@ describe("Swift imported protocol references", () => {
|
||||
expect(result.swift_conformances).toMatchObject([
|
||||
{
|
||||
type_name: null,
|
||||
type_reference_kind: 1,
|
||||
type_binding: {
|
||||
symbol: "_$sSiMn",
|
||||
requested_address: "0x100000b00",
|
||||
@@ -298,6 +326,27 @@ describe("Swift imported protocol references", () => {
|
||||
).toMatchObject({ status: "partial" });
|
||||
});
|
||||
|
||||
it("preserves a kind-3 Objective-C class import without inventing a class name", () => {
|
||||
const result = decodeAppleDispatchMetadata(
|
||||
swiftExternalSwiftBindingImage("objc-class"),
|
||||
100,
|
||||
provenance,
|
||||
);
|
||||
expect(result.swift_conformances).toMatchObject([
|
||||
{
|
||||
type_name: null,
|
||||
type_reference_kind: 3,
|
||||
type_reference_target: { address: null, file_offset: null },
|
||||
type_binding: {
|
||||
symbol: "_OBJC_CLASS_$_NSObject",
|
||||
requested_address: "0x100000b00",
|
||||
},
|
||||
protocol_name: "Equatable",
|
||||
decode: { status: "partial" },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("reports only the legacy stream mechanisms actually examined", () => {
|
||||
const bytes = swiftBoundWitnessSlotImage();
|
||||
const dyldInfo = 32 + 232;
|
||||
@@ -325,6 +374,8 @@ describe("Swift imported protocol references", () => {
|
||||
expect(result.swift_conformances).toMatchObject([
|
||||
{
|
||||
type_name: "Widget",
|
||||
type_reference_kind: 0,
|
||||
type_reference_target: { address: "0x100000c00", file_offset: 3072 },
|
||||
protocol_name: "Equatable",
|
||||
witness_table: { address: "0x100000e00" },
|
||||
decode: { status: "decoded" },
|
||||
@@ -350,6 +401,256 @@ describe("Swift imported protocol references", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Swift conformance type-reference kinds", () => {
|
||||
it("keeps a direct Objective-C class name and its local witness evidence", () => {
|
||||
const result = decodeAppleDispatchMetadata(
|
||||
swiftConformanceTypeReferenceImage(2),
|
||||
100,
|
||||
provenance,
|
||||
);
|
||||
|
||||
expect(result.swift_conformances).toMatchObject([
|
||||
{
|
||||
type_name: "NSObject",
|
||||
type_reference_kind: 2,
|
||||
type_reference_target: { address: "0x100000e60", file_offset: 3680 },
|
||||
protocol_name: "Equatable",
|
||||
witness_table: { address: "0x100000e00" },
|
||||
decode: { status: "decoded" },
|
||||
},
|
||||
]);
|
||||
expect(result.swift_dispatch_slots[0]).toMatchObject({
|
||||
owner: "NSObject: Equatable",
|
||||
table_kind: "witness_table",
|
||||
implementation_address: null,
|
||||
implementation_binding: {
|
||||
symbol: "_externalWitnessImpl",
|
||||
requested_address: "0x100000e08",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Swift partial conformance reference retention", () => {
|
||||
it("retains type and witness facts when the protocol target is unmapped", () => {
|
||||
const bytes = swiftConformanceTypeReferenceImage(2);
|
||||
bytes.writeInt32LE(0x5000 - 0xa10, 0xa10);
|
||||
const result = decodeAppleDispatchMetadata(bytes, 100, provenance);
|
||||
|
||||
expect(result.swift_conformances).toMatchObject([
|
||||
{
|
||||
type_name: "NSObject",
|
||||
type_reference_target: { address: "0x100000e60", file_offset: 3680 },
|
||||
protocol_name: null,
|
||||
protocol_target: { address: "0x100005000", file_offset: null },
|
||||
witness_table: { address: "0x100000e00", file_offset: 3584 },
|
||||
decode: { status: "partial" },
|
||||
},
|
||||
]);
|
||||
expect(result.swift_dispatch_slots).toEqual([]);
|
||||
});
|
||||
|
||||
it("preserves a typed type binding beside an unmapped protocol without a guessed name", () => {
|
||||
const bytes = swiftExternalSwiftBindingImage("objc-class");
|
||||
bytes.writeInt32LE(0x5000 - 0xa10, 0xa10);
|
||||
const result = decodeAppleDispatchMetadata(bytes, 100, provenance);
|
||||
|
||||
expect(result.swift_conformances).toMatchObject([
|
||||
{
|
||||
type_name: null,
|
||||
type_reference_kind: 3,
|
||||
type_reference_target: { address: null, file_offset: null },
|
||||
type_binding: {
|
||||
symbol: "_OBJC_CLASS_$_NSObject",
|
||||
requested_address: "0x100000b00",
|
||||
},
|
||||
protocol_name: null,
|
||||
protocol_target: { address: "0x100005000", file_offset: null },
|
||||
decode: { status: "partial" },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("retains protocol and type targets when the protocol name is unmapped", () => {
|
||||
const bytes = swiftConformanceTypeReferenceImage(2);
|
||||
bytes.writeInt32LE(0x5000 - 0xd08, 0xd08);
|
||||
const result = decodeAppleDispatchMetadata(bytes, 100, provenance);
|
||||
expect(result.swift_conformances).toMatchObject([
|
||||
{
|
||||
type_name: "NSObject",
|
||||
protocol_name: null,
|
||||
protocol_target: { address: "0x100000d00", file_offset: 3328 },
|
||||
witness_table: { address: "0x100000e00", file_offset: 3584 },
|
||||
decode: { status: "partial" },
|
||||
},
|
||||
]);
|
||||
expect(result.swift_dispatch_slots).toEqual([]);
|
||||
});
|
||||
|
||||
it("retains other facts when the protocol descriptor kind is unsupported", () => {
|
||||
const bytes = swiftConformanceTypeReferenceImage(2);
|
||||
bytes.writeUInt32LE(4, 0xd00);
|
||||
const result = decodeAppleDispatchMetadata(bytes, 100, provenance);
|
||||
|
||||
expect(result.swift_conformances).toMatchObject([
|
||||
{
|
||||
type_name: "NSObject",
|
||||
protocol_name: null,
|
||||
protocol_target: { address: "0x100000d00", file_offset: 3328 },
|
||||
witness_table: { address: "0x100000e00", file_offset: 3584 },
|
||||
decode: { status: "partial" },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("retains other facts when a protocol name is invalid UTF-8", () => {
|
||||
const bytes = swiftConformanceTypeReferenceImage(2);
|
||||
bytes.writeUInt8(0xff, 0xe50);
|
||||
const result = decodeAppleDispatchMetadata(bytes, 100, provenance);
|
||||
|
||||
expect(result.swift_conformances).toMatchObject([
|
||||
{
|
||||
type_name: "NSObject",
|
||||
protocol_name: null,
|
||||
protocol_target: { address: "0x100000d00", file_offset: 3328 },
|
||||
witness_table: { address: "0x100000e00", file_offset: 3584 },
|
||||
decode: { status: "partial" },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("retains mixed readable witness slots when a later slot is unmapped", () => {
|
||||
const bytes = swiftConformanceTypeReferenceImage(2);
|
||||
bytes.writeUInt32LE(2, 0xd10);
|
||||
bytes.writeInt32LE(0xff0 - 0xa18, 0xa18);
|
||||
bytes.writeBigUInt64LE(base + 0xa10n, 0xff0);
|
||||
const result = decodeAppleDispatchMetadata(bytes, 100, provenance);
|
||||
|
||||
expect(result.swift_conformances).toMatchObject([
|
||||
{
|
||||
type_name: "NSObject",
|
||||
protocol_name: "Equatable",
|
||||
witness_table: { address: "0x100000ff0", file_offset: 4080 },
|
||||
},
|
||||
]);
|
||||
expect(result.swift_dispatch_slots).toMatchObject([
|
||||
{
|
||||
slot_index: 1,
|
||||
location: { address: "0x100000ff8", file_offset: 4088 },
|
||||
},
|
||||
]);
|
||||
expect(result.swift_dispatch_slots).toHaveLength(1);
|
||||
expect(
|
||||
result.coverage.find(
|
||||
({ facet }) => facet === "swift_conformances_static_witness_slots",
|
||||
),
|
||||
).toMatchObject({ status: "partial" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("Swift unresolved conformance type references", () => {
|
||||
it.each([3, 4, 7] as const)(
|
||||
"retains an unresolved type-reference kind %i with conformance and witness locations",
|
||||
(typeKind) => {
|
||||
const result = decodeAppleDispatchMetadata(
|
||||
swiftConformanceTypeReferenceImage(typeKind),
|
||||
100,
|
||||
provenance,
|
||||
);
|
||||
|
||||
expect(result.swift_conformances).toMatchObject([
|
||||
{
|
||||
type_name: null,
|
||||
type_reference_kind: typeKind,
|
||||
...(typeKind === 3
|
||||
? {
|
||||
type_reference_target: {
|
||||
address: "0x100000e80",
|
||||
file_offset: 3712,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
protocol_name: "Equatable",
|
||||
witness_table: { address: "0x100000e00" },
|
||||
decode: { status: "partial" },
|
||||
},
|
||||
]);
|
||||
expect(result.swift_dispatch_slots).toEqual([]);
|
||||
expect(
|
||||
result.coverage.find(
|
||||
({ facet }) => facet === "swift_conformances_static_witness_slots",
|
||||
),
|
||||
).toMatchObject({ status: "partial" });
|
||||
},
|
||||
);
|
||||
|
||||
it("retains a kind-2 source location when its inline class name is empty", () => {
|
||||
const bytes = swiftConformanceTypeReferenceImage(2);
|
||||
bytes.writeUInt8(0, 0xe60);
|
||||
const result = decodeAppleDispatchMetadata(bytes, 100, provenance);
|
||||
|
||||
expect(result.swift_conformances).toMatchObject([
|
||||
{
|
||||
type_name: null,
|
||||
type_reference_kind: 2,
|
||||
type_reference_target: { address: "0x100000e60", file_offset: 3680 },
|
||||
protocol_name: "Equatable",
|
||||
witness_table: { address: "0x100000e00" },
|
||||
decode: { status: "partial" },
|
||||
},
|
||||
]);
|
||||
expect(result.swift_dispatch_slots).toEqual([]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ kind: 0, target: 0x5000, actual: 0x5000 },
|
||||
{ kind: 1, target: 0x5000, actual: 0x5000 },
|
||||
{ kind: 2, target: 0x5000, actual: 0x5000 },
|
||||
{ kind: 3, target: 0x5000, actual: 0x5000 },
|
||||
{
|
||||
kind: 3,
|
||||
target: 0xe60,
|
||||
indirectClassTarget: 0x5000,
|
||||
actual: 0x5000,
|
||||
},
|
||||
{ kind: 4, target: 0x5000, actual: 0x5000 },
|
||||
])(
|
||||
"retains partial conformance evidence when kind $kind targets non-file-backed address $target",
|
||||
({ kind, target, indirectClassTarget, actual }) => {
|
||||
const result = decodeAppleDispatchMetadata(
|
||||
swiftConformanceTypeReferenceImage(kind, {
|
||||
typeTargetOffset: target,
|
||||
...(indirectClassTarget === undefined
|
||||
? {}
|
||||
: { indirectClassTargetOffset: indirectClassTarget }),
|
||||
}),
|
||||
100,
|
||||
provenance,
|
||||
);
|
||||
|
||||
expect(result.swift_conformances).toMatchObject([
|
||||
{
|
||||
type_name: null,
|
||||
type_reference_kind: kind,
|
||||
type_reference_target: {
|
||||
address: `0x${(base + BigInt(actual)).toString(16)}`,
|
||||
file_offset: null,
|
||||
},
|
||||
protocol_name: "Equatable",
|
||||
witness_table: { address: "0x100000e00" },
|
||||
decode: { status: "partial" },
|
||||
},
|
||||
]);
|
||||
expect(result.swift_dispatch_slots).toEqual([]);
|
||||
expect(
|
||||
result.coverage.find(
|
||||
({ facet }) => facet === "swift_conformances_static_witness_slots",
|
||||
),
|
||||
).toMatchObject({ status: "partial" });
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const withProtocolListSection = (bytes: Buffer): Buffer => {
|
||||
const pointer = (at: number, offset: number) =>
|
||||
bytes.writeBigUInt64LE(base + BigInt(offset), at);
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
createSwiftRelativeReader,
|
||||
decodeSwiftDispatchFacets,
|
||||
type DispatchRecordBudget,
|
||||
type SwiftRelativeTargetLocation,
|
||||
} from "./AppleSwiftDispatchFacets.js";
|
||||
import { decodeSwiftClassVtables } from "./AppleSwiftVtables.js";
|
||||
import { inspectObjcClassImportBindings } from "./AppleObjcClassImportBindings.js";
|
||||
@@ -97,6 +98,16 @@ export const decodeAppleDispatchMetadata = (
|
||||
address: `0x${address.toString(16)}`,
|
||||
file_offset: offset(address),
|
||||
});
|
||||
const targetLocation = (address: bigint): SwiftRelativeTargetLocation => {
|
||||
let fileOffset: number | null;
|
||||
try {
|
||||
fileOffset = offset(address);
|
||||
} catch (cause) {
|
||||
if (!(cause instanceof RangeError)) throw cause;
|
||||
fileOffset = null;
|
||||
}
|
||||
return { address: `0x${address.toString(16)}`, file_offset: fileOffset };
|
||||
};
|
||||
const evidence = (address: bigint, description: string) => [
|
||||
{
|
||||
kind: "binary_metadata" as const,
|
||||
@@ -164,6 +175,7 @@ export const decodeAppleDispatchMetadata = (
|
||||
bound,
|
||||
string,
|
||||
location,
|
||||
targetLocation,
|
||||
evidence,
|
||||
offset,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { decodeSwiftConformanceTypeReference } from "./AppleSwiftConformanceTypeReference.js";
|
||||
import type { SwiftDispatchReaders } from "./AppleSwiftDispatchFacets.js";
|
||||
|
||||
describe("Swift conformance type-reference read failures", () => {
|
||||
it("propagates unexpected reader errors instead of reporting malformed evidence", () => {
|
||||
const failure = new Error("reader invariant failed");
|
||||
const read = {
|
||||
u32: () => {
|
||||
throw failure;
|
||||
},
|
||||
} as unknown as SwiftDispatchReaders;
|
||||
|
||||
expect(() =>
|
||||
decodeSwiftConformanceTypeReference({
|
||||
descriptor: 0x100n,
|
||||
flags: 0,
|
||||
read,
|
||||
relative: () => ({ kind: "target", target: 0x200n }),
|
||||
unresolvedReason: () => "unresolved",
|
||||
}),
|
||||
).toThrow(failure);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,265 @@
|
||||
import type { PointerResolution } from "./AppleMachoFixups.js";
|
||||
import type {
|
||||
SwiftDispatchReaders,
|
||||
SwiftRelativeField,
|
||||
SwiftRelativeValue,
|
||||
} from "./AppleSwiftDispatchFacets.js";
|
||||
import { expectedSwiftMetadataReadFailure } from "./AppleSwiftMetadataReadFailure.js";
|
||||
|
||||
const hex = (value: bigint) => `0x${value.toString(16)}`;
|
||||
|
||||
export const swiftImportBinding = (
|
||||
requestedAddress: bigint,
|
||||
binding: Extract<PointerResolution, { readonly kind: "bind" }>,
|
||||
) => ({
|
||||
requested_address: hex(requestedAddress),
|
||||
symbol: binding.symbol,
|
||||
binding_source: binding.binding_source,
|
||||
library_ordinal: binding.library_ordinal,
|
||||
library: binding.library,
|
||||
weak: binding.weak,
|
||||
addend: binding.addend.toString(),
|
||||
});
|
||||
|
||||
export interface SwiftConformanceTypeReference {
|
||||
readonly kind: number;
|
||||
/** Descriptor, inline class-name, local ObjC class pointer, or raw target. */
|
||||
readonly target: bigint | null;
|
||||
readonly name: string | null;
|
||||
readonly binding: ReturnType<typeof swiftImportBinding> | null;
|
||||
readonly reason: string | null;
|
||||
readonly staticWitnessIdentity: boolean;
|
||||
readonly generic: boolean | null;
|
||||
}
|
||||
|
||||
interface SwiftTypeReferenceInput {
|
||||
readonly descriptor: bigint;
|
||||
readonly flags: number;
|
||||
readonly read: SwiftDispatchReaders;
|
||||
readonly relative: (
|
||||
field: bigint,
|
||||
kind: SwiftRelativeField,
|
||||
) => SwiftRelativeValue;
|
||||
readonly unresolvedReason: (
|
||||
value: Exclude<SwiftRelativeValue, { readonly kind: "target" }>,
|
||||
field: SwiftRelativeField,
|
||||
) => string;
|
||||
}
|
||||
|
||||
const typeReferenceResult = (input: {
|
||||
readonly kind: number;
|
||||
readonly target: bigint | null;
|
||||
readonly name?: string | null;
|
||||
readonly binding?: ReturnType<typeof swiftImportBinding> | null;
|
||||
readonly reason?: string | null;
|
||||
readonly generic?: boolean | null;
|
||||
}): SwiftConformanceTypeReference => ({
|
||||
kind: input.kind,
|
||||
target: input.target,
|
||||
name: input.name ?? null,
|
||||
binding: input.binding ?? null,
|
||||
reason: input.reason ?? null,
|
||||
staticWitnessIdentity:
|
||||
input.name !== null && input.name !== undefined && input.kind <= 2,
|
||||
generic: input.generic ?? null,
|
||||
});
|
||||
|
||||
export const requiredSwiftRelativeTarget = (
|
||||
relative: (field: bigint, kind: SwiftRelativeField) => SwiftRelativeValue,
|
||||
field: bigint,
|
||||
kind: SwiftRelativeField,
|
||||
unresolvedReason: SwiftTypeReferenceInput["unresolvedReason"],
|
||||
): bigint => {
|
||||
const value = relative(field, kind);
|
||||
if (value.kind !== "target")
|
||||
throw new RangeError(unresolvedReason(value, kind));
|
||||
return value.target;
|
||||
};
|
||||
|
||||
const decodeNominalTypeReference = (
|
||||
kind: 0 | 1,
|
||||
input: SwiftTypeReferenceInput,
|
||||
value: SwiftRelativeValue,
|
||||
): SwiftConformanceTypeReference => {
|
||||
const { read, relative } = input;
|
||||
let target = value.kind === "target" ? value.target : null;
|
||||
let binding =
|
||||
value.kind === "binding"
|
||||
? swiftImportBinding(value.requestedAddress, value.binding)
|
||||
: null;
|
||||
let reason =
|
||||
value.kind === "unknown"
|
||||
? value.reason
|
||||
: value.kind === "null"
|
||||
? "Null Swift conformance type reference"
|
||||
: null;
|
||||
try {
|
||||
if (kind === 1 && target !== null) {
|
||||
const pointer = read.resolvePointer(target);
|
||||
if (pointer.kind === "bind") {
|
||||
binding = swiftImportBinding(target, pointer);
|
||||
target = null;
|
||||
} else if (pointer.kind === "raw" && pointer.target === 0n) {
|
||||
target = null;
|
||||
reason = "Null indirect Swift conformance type reference";
|
||||
} else if (pointer.kind === "raw" || pointer.kind === "rebase") {
|
||||
target = pointer.target;
|
||||
} else {
|
||||
target = null;
|
||||
reason = pointer.reason;
|
||||
}
|
||||
}
|
||||
if (target === null)
|
||||
return typeReferenceResult({ kind, target, binding, reason });
|
||||
|
||||
const descriptorFlags = read.u32(target);
|
||||
if (![16, 17, 18].includes(descriptorFlags & 31))
|
||||
return typeReferenceResult({
|
||||
kind,
|
||||
target,
|
||||
binding,
|
||||
reason: "Unsupported Swift type descriptor kind",
|
||||
});
|
||||
|
||||
const name = read.string(
|
||||
requiredSwiftRelativeTarget(
|
||||
relative,
|
||||
target + 8n,
|
||||
"typeName",
|
||||
input.unresolvedReason,
|
||||
),
|
||||
);
|
||||
return typeReferenceResult({
|
||||
kind,
|
||||
target,
|
||||
name: name.length === 0 ? null : name,
|
||||
binding,
|
||||
reason: name.length === 0 ? "Empty Swift conformance type name" : null,
|
||||
generic: (descriptorFlags & 0x80) !== 0,
|
||||
});
|
||||
} catch (cause) {
|
||||
const reason = expectedSwiftMetadataReadFailure(cause);
|
||||
if (reason === null) throw cause;
|
||||
return typeReferenceResult({
|
||||
kind,
|
||||
target,
|
||||
binding,
|
||||
reason,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const decodeDirectObjcClassName = (
|
||||
kind: 2,
|
||||
read: SwiftDispatchReaders,
|
||||
value: SwiftRelativeValue,
|
||||
): SwiftConformanceTypeReference => {
|
||||
if (value.kind !== "target")
|
||||
return typeReferenceResult({
|
||||
kind,
|
||||
target: null,
|
||||
reason:
|
||||
value.kind === "unknown"
|
||||
? value.reason
|
||||
: "Null Swift conformance type reference",
|
||||
});
|
||||
try {
|
||||
const name = read.string(value.target);
|
||||
return typeReferenceResult({
|
||||
kind,
|
||||
target: value.target,
|
||||
name: name.length === 0 ? null : name,
|
||||
reason: name.length === 0 ? "Empty inline Objective-C class name" : null,
|
||||
});
|
||||
} catch (cause) {
|
||||
const reason = expectedSwiftMetadataReadFailure(cause);
|
||||
if (reason === null) throw cause;
|
||||
return typeReferenceResult({
|
||||
kind,
|
||||
target: value.target,
|
||||
reason,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const decodeIndirectObjcClass = (
|
||||
kind: 3,
|
||||
read: SwiftDispatchReaders,
|
||||
value: SwiftRelativeValue,
|
||||
): SwiftConformanceTypeReference => {
|
||||
if (value.kind !== "target")
|
||||
return typeReferenceResult({
|
||||
kind,
|
||||
target: null,
|
||||
reason:
|
||||
value.kind === "unknown"
|
||||
? value.reason
|
||||
: "Null Swift conformance type reference",
|
||||
});
|
||||
let pointer: PointerResolution;
|
||||
try {
|
||||
pointer = read.resolvePointer(value.target);
|
||||
} catch (cause) {
|
||||
const reason = expectedSwiftMetadataReadFailure(cause);
|
||||
if (reason === null) throw cause;
|
||||
return typeReferenceResult({
|
||||
kind,
|
||||
target: value.target,
|
||||
reason,
|
||||
});
|
||||
}
|
||||
if (pointer.kind === "bind")
|
||||
return typeReferenceResult({
|
||||
kind,
|
||||
target: null,
|
||||
binding: swiftImportBinding(value.target, pointer),
|
||||
reason: "Indirect Objective-C class metadata requires runtime lookup",
|
||||
});
|
||||
if (pointer.kind === "raw" || pointer.kind === "rebase")
|
||||
return typeReferenceResult({
|
||||
kind,
|
||||
target: pointer.target === 0n ? null : pointer.target,
|
||||
reason: "Indirect Objective-C class metadata requires runtime lookup",
|
||||
});
|
||||
return typeReferenceResult({
|
||||
kind,
|
||||
target: null,
|
||||
reason: pointer.reason,
|
||||
});
|
||||
};
|
||||
|
||||
/** Decode source-encoded class identity; ObjC runtime lookup remains unavailable. */
|
||||
export const decodeSwiftConformanceTypeReference = (
|
||||
input: SwiftTypeReferenceInput,
|
||||
): SwiftConformanceTypeReference => {
|
||||
const kind = (input.flags >>> 3) & 7;
|
||||
try {
|
||||
const value = input.relative(input.descriptor + 4n, "conformanceType");
|
||||
switch (kind) {
|
||||
case 0:
|
||||
case 1:
|
||||
return decodeNominalTypeReference(kind, input, value);
|
||||
case 2:
|
||||
return decodeDirectObjcClassName(kind, input.read, value);
|
||||
case 3:
|
||||
return decodeIndirectObjcClass(kind, input.read, value);
|
||||
default:
|
||||
return typeReferenceResult({
|
||||
kind,
|
||||
target: value.kind === "target" ? value.target : null,
|
||||
reason:
|
||||
value.kind === "unknown"
|
||||
? value.reason
|
||||
: `Unsupported Swift conformance type-reference kind ${kind}`,
|
||||
});
|
||||
}
|
||||
} catch (cause) {
|
||||
const reason = expectedSwiftMetadataReadFailure(cause);
|
||||
if (reason === null) throw cause;
|
||||
return typeReferenceResult({
|
||||
kind,
|
||||
target: null,
|
||||
reason,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -2,6 +2,12 @@ import type { ObjcSwiftMetadata } from "../domain/native/objcSwiftMetadata.js";
|
||||
import type { PointerResolution } from "./AppleMachoFixups.js";
|
||||
import type { Segment, Section } from "./AppleMachoSelection.js";
|
||||
import { issue, type FacetDecodeFacts } from "./AppleDispatchDecodeFacts.js";
|
||||
import {
|
||||
decodeSwiftConformanceTypeReference,
|
||||
requiredSwiftRelativeTarget,
|
||||
swiftImportBinding as importBinding,
|
||||
} from "./AppleSwiftConformanceTypeReference.js";
|
||||
import { expectedSwiftMetadataReadFailure } from "./AppleSwiftMetadataReadFailure.js";
|
||||
|
||||
/** Resolved file-backed metadata location from the Mach-O layout. */
|
||||
export interface ResolvedMetadataLocation {
|
||||
@@ -9,6 +15,12 @@ export interface ResolvedMetadataLocation {
|
||||
file_offset: number;
|
||||
}
|
||||
|
||||
/** Address fact for a relative target that may not map to file-backed bytes. */
|
||||
export interface SwiftRelativeTargetLocation {
|
||||
address: string;
|
||||
file_offset: number | null;
|
||||
}
|
||||
|
||||
/** Binary-metadata evidence produced by the Apple dispatch readers. */
|
||||
export type BinaryMetadataEvidence = {
|
||||
kind: "binary_metadata";
|
||||
@@ -84,6 +96,84 @@ export const unresolvedSwiftRelativeReason = (
|
||||
return `Swift relative pointer for ${field} has an unresolved external binding at ${hex(value.requestedAddress)}`;
|
||||
};
|
||||
|
||||
interface SwiftProtocolFacts {
|
||||
readonly name: string | null;
|
||||
readonly readable: boolean;
|
||||
readonly reason: string | null;
|
||||
}
|
||||
|
||||
const readSwiftProtocolFacts = (input: {
|
||||
readonly target: bigint | null;
|
||||
readonly relative: (
|
||||
field: bigint,
|
||||
kind: SwiftRelativeField,
|
||||
) => SwiftRelativeValue;
|
||||
readonly read: SwiftDispatchReaders;
|
||||
}): SwiftProtocolFacts => {
|
||||
const { target, relative, read } = input;
|
||||
if (target === null)
|
||||
return {
|
||||
name: null,
|
||||
readable: false,
|
||||
reason: "Protocol descriptor target is unresolved",
|
||||
};
|
||||
let descriptorKind: number;
|
||||
try {
|
||||
descriptorKind = read.u32(target) & 31;
|
||||
} catch (cause) {
|
||||
const reason = expectedSwiftMetadataReadFailure(cause);
|
||||
if (reason === null) throw cause;
|
||||
return { name: null, readable: false, reason };
|
||||
}
|
||||
if (descriptorKind !== 3)
|
||||
return {
|
||||
name: null,
|
||||
readable: false,
|
||||
reason: "Unsupported Swift protocol descriptor kind",
|
||||
};
|
||||
let nameAddress: bigint;
|
||||
try {
|
||||
nameAddress = requiredSwiftRelativeTarget(
|
||||
relative,
|
||||
target + 8n,
|
||||
"protocolName",
|
||||
unresolvedSwiftRelativeReason,
|
||||
);
|
||||
} catch (cause) {
|
||||
const reason = expectedSwiftMetadataReadFailure(cause);
|
||||
if (reason === null) throw cause;
|
||||
return { name: null, readable: false, reason };
|
||||
}
|
||||
try {
|
||||
return { name: read.string(nameAddress), readable: true, reason: null };
|
||||
} catch (cause) {
|
||||
const reason = expectedSwiftMetadataReadFailure(cause);
|
||||
if (reason === null) throw cause;
|
||||
return { name: null, readable: false, reason };
|
||||
}
|
||||
};
|
||||
|
||||
const readWitnessConformanceHeader = (
|
||||
read: SwiftDispatchReaders,
|
||||
witness: bigint,
|
||||
descriptor: bigint,
|
||||
): string | null => {
|
||||
try {
|
||||
const header = read.resolvePointer(witness);
|
||||
if (header.kind !== "raw" && header.kind !== "rebase")
|
||||
return header.kind === "unknown" || header.kind === "unsupported"
|
||||
? header.reason
|
||||
: "Witness-table conformance header is an external binding";
|
||||
return header.target === descriptor
|
||||
? null
|
||||
: "Witness-table conformance header is unresolved or encoded";
|
||||
} catch (cause) {
|
||||
const reason = expectedSwiftMetadataReadFailure(cause);
|
||||
if (reason === null) throw cause;
|
||||
return reason;
|
||||
}
|
||||
};
|
||||
|
||||
/** Byte readers for Swift relative-pointer and conformance decode facets. */
|
||||
export interface SwiftDispatchReaders {
|
||||
u32(address: bigint): number;
|
||||
@@ -91,25 +181,13 @@ export interface SwiftDispatchReaders {
|
||||
resolvePointer(address: bigint): PointerResolution;
|
||||
string(address: bigint): string;
|
||||
location(address: bigint): ResolvedMetadataLocation;
|
||||
targetLocation(address: bigint): SwiftRelativeTargetLocation;
|
||||
evidence(address: bigint, description: string): BinaryMetadataEvidence;
|
||||
offset(address: bigint, size?: number): number;
|
||||
}
|
||||
|
||||
const hex = (value: bigint) => `0x${value.toString(16)}`;
|
||||
const decoded = { status: "decoded" as const, reason: null };
|
||||
const importBinding = (
|
||||
requestedAddress: bigint,
|
||||
binding: Extract<PointerResolution, { readonly kind: "bind" }>,
|
||||
) => ({
|
||||
requested_address: hex(requestedAddress),
|
||||
symbol: binding.symbol,
|
||||
binding_source: binding.binding_source,
|
||||
library_ordinal: binding.library_ordinal,
|
||||
library: binding.library,
|
||||
weak: binding.weak,
|
||||
addend: binding.addend.toString(),
|
||||
});
|
||||
|
||||
/** Create a Swift ABI signed-relative pointer decoder that records relative_pointers. */
|
||||
export const createSwiftRelativeReader = (input: {
|
||||
bytes: Buffer;
|
||||
@@ -239,17 +317,7 @@ export const decodeSwiftDispatchFacets = (input: {
|
||||
result: ObjcSwiftMetadata;
|
||||
}): FacetDecodeFacts => {
|
||||
const { sections, segments, readers: read, relative, budget, result } = input;
|
||||
const swiftFailures: string[] = [];
|
||||
const issues = [] as ReturnType<typeof issue>[];
|
||||
const requiredRelative = (
|
||||
field: bigint,
|
||||
kind: SwiftRelativeField,
|
||||
): bigint => {
|
||||
const value = relative(field, kind);
|
||||
if (value.kind !== "target")
|
||||
throw new RangeError(unresolvedSwiftRelativeReason(value, kind));
|
||||
return value.target;
|
||||
};
|
||||
let swiftExamined = 0;
|
||||
let decodedRecords = 0;
|
||||
for (const section of sections.filter(
|
||||
@@ -262,7 +330,12 @@ export const decodeSwiftDispatchFacets = (input: {
|
||||
swiftExamined++;
|
||||
const field = section.address + BigInt(index * 4);
|
||||
try {
|
||||
const descriptor = requiredRelative(field, "conformanceRecord");
|
||||
const descriptor = requiredSwiftRelativeTarget(
|
||||
relative,
|
||||
field,
|
||||
"conformanceRecord",
|
||||
unresolvedSwiftRelativeReason,
|
||||
);
|
||||
const flags = read.u32(descriptor + 12n);
|
||||
const protocolValue = relative(descriptor, "conformanceProtocol");
|
||||
const protocol =
|
||||
@@ -274,12 +347,12 @@ export const decodeSwiftDispatchFacets = (input: {
|
||||
"conformanceProtocol",
|
||||
)
|
||||
: null;
|
||||
if (protocol !== null && (read.u32(protocol) & 31) !== 3)
|
||||
throw new TypeError("Unsupported Swift protocol descriptor kind");
|
||||
const protocolName =
|
||||
protocol === null
|
||||
? null
|
||||
: read.string(requiredRelative(protocol + 8n, "protocolName"));
|
||||
const protocolFacts = readSwiftProtocolFacts({
|
||||
target: protocol,
|
||||
relative,
|
||||
read,
|
||||
});
|
||||
const protocolName = protocolFacts.name;
|
||||
const protocolBinding =
|
||||
protocolValue.kind === "binding"
|
||||
? importBinding(
|
||||
@@ -287,107 +360,85 @@ export const decodeSwiftDispatchFacets = (input: {
|
||||
protocolValue.binding,
|
||||
)
|
||||
: null;
|
||||
const typeKind = (flags >>> 3) & 7;
|
||||
if (typeKind !== 0 && typeKind !== 1)
|
||||
throw new TypeError(
|
||||
"Unsupported Swift conformance type-reference kind",
|
||||
);
|
||||
const typeValue = relative(descriptor + 4n, "conformanceType");
|
||||
let type = typeValue.kind === "target" ? typeValue.target : null;
|
||||
let typeBinding =
|
||||
typeValue.kind === "binding"
|
||||
? importBinding(typeValue.requestedAddress, typeValue.binding)
|
||||
: null;
|
||||
let unresolvedTypeReason =
|
||||
typeValue.kind === "unknown" ? typeValue.reason : null;
|
||||
if (typeKind === 1 && type !== null) {
|
||||
const pointer = read.resolvePointer(type);
|
||||
if (pointer.kind === "bind") {
|
||||
typeBinding = importBinding(type, pointer);
|
||||
type = null;
|
||||
} else if (pointer.kind === "raw" && pointer.target === 0n) {
|
||||
type = null;
|
||||
unresolvedTypeReason =
|
||||
"Null indirect Swift conformance type reference";
|
||||
} else if (pointer.kind === "rebase" || pointer.kind === "raw") {
|
||||
type = pointer.target;
|
||||
} else {
|
||||
type = null;
|
||||
unresolvedTypeReason = pointer.reason;
|
||||
}
|
||||
}
|
||||
if (type !== null && ![16, 17, 18].includes(read.u32(type) & 31))
|
||||
throw new TypeError("Unsupported Swift type descriptor kind");
|
||||
const typeName =
|
||||
type === null
|
||||
? null
|
||||
: read.string(requiredRelative(type + 8n, "typeName"));
|
||||
const witnessValue =
|
||||
protocol !== null && type !== null
|
||||
? relative(descriptor + 8n, "witnessTablePattern")
|
||||
: { kind: "null" as const };
|
||||
const typeReference = decodeSwiftConformanceTypeReference({
|
||||
descriptor,
|
||||
flags,
|
||||
read,
|
||||
relative,
|
||||
unresolvedReason: unresolvedSwiftRelativeReason,
|
||||
});
|
||||
const witnessValue = relative(descriptor + 8n, "witnessTablePattern");
|
||||
const witness =
|
||||
witnessValue.kind === "target" ? witnessValue.target : null;
|
||||
const unresolvedWitnessReason =
|
||||
witnessValue.kind === "unknown" || witnessValue.kind === "binding"
|
||||
? unresolvedSwiftRelativeReason(witnessValue, "witnessTablePattern")
|
||||
: null;
|
||||
const staticWitness =
|
||||
let staticWitness =
|
||||
witness !== null &&
|
||||
protocol !== null &&
|
||||
type !== null &&
|
||||
(flags & ~0x38) === 0 &&
|
||||
(read.u32(type) & 0x80) === 0;
|
||||
protocolFacts.readable &&
|
||||
typeReference.staticWitnessIdentity &&
|
||||
(flags & ~0x38) === 0;
|
||||
staticWitness &&=
|
||||
typeReference.kind === 2 || typeReference.generic === false;
|
||||
let witnessReason: string | null = staticWitness
|
||||
? null
|
||||
: (unresolvedProtocolReason ??
|
||||
unresolvedTypeReason ??
|
||||
protocolFacts.reason ??
|
||||
typeReference.reason ??
|
||||
unresolvedWitnessReason ??
|
||||
(protocolBinding !== null || typeBinding !== null
|
||||
(protocolBinding !== null || typeReference.binding !== null
|
||||
? "Conformance references an external Mach-O binding"
|
||||
: "Conditional, generic, resilient or missing static witness table is not decoded"));
|
||||
if (staticWitness) {
|
||||
const header = read.resolvePointer(witness);
|
||||
if (header.kind !== "raw" && header.kind !== "rebase")
|
||||
witnessReason =
|
||||
header.kind === "unknown" || header.kind === "unsupported"
|
||||
? header.reason
|
||||
: "Witness-table conformance header is an external binding";
|
||||
else if (header.target !== descriptor)
|
||||
witnessReason =
|
||||
"Witness-table conformance header is unresolved or encoded";
|
||||
if (staticWitness && witness !== null) {
|
||||
witnessReason = readWitnessConformanceHeader(
|
||||
read,
|
||||
witness,
|
||||
descriptor,
|
||||
);
|
||||
}
|
||||
const conformanceDecode =
|
||||
witnessReason === null
|
||||
? { status: "decoded" as const, reason: null }
|
||||
: { status: "partial" as const, reason: witnessReason };
|
||||
result.swift_conformances.push({
|
||||
type_name: typeName,
|
||||
type_binding: typeBinding,
|
||||
type_name: typeReference.name,
|
||||
type_reference_kind: typeReference.kind,
|
||||
type_reference_target:
|
||||
typeReference.target === null
|
||||
? { address: null, file_offset: null }
|
||||
: read.targetLocation(typeReference.target),
|
||||
type_binding: typeReference.binding,
|
||||
protocol_name: protocolName,
|
||||
...(protocol === null
|
||||
? {}
|
||||
: { protocol_target: read.targetLocation(protocol) }),
|
||||
protocol_binding: protocolBinding,
|
||||
module: null,
|
||||
witness_table:
|
||||
witness === null
|
||||
? { address: null, file_offset: null }
|
||||
: read.location(witness),
|
||||
: read.targetLocation(witness),
|
||||
location: read.location(descriptor),
|
||||
decode: conformanceDecode,
|
||||
evidence: read.evidence(
|
||||
descriptor,
|
||||
"Swift protocol conformance descriptor and directly encoded type/protocol names",
|
||||
"Swift protocol conformance descriptor, type reference and protocol name",
|
||||
),
|
||||
});
|
||||
if (conformanceDecode.status === "decoded") decodedRecords++;
|
||||
if (
|
||||
witnessReason !== null ||
|
||||
witness === null ||
|
||||
typeName === null ||
|
||||
protocolName === null
|
||||
typeReference.name === null ||
|
||||
protocolName === null ||
|
||||
!protocolFacts.readable
|
||||
) {
|
||||
const reason =
|
||||
witnessReason ?? "Missing static witness table is not decoded";
|
||||
swiftFailures.push(`${typeName ?? "<unknown type>"}: ${reason}`);
|
||||
witnessReason ??
|
||||
typeReference.reason ??
|
||||
"Missing static witness table is not decoded";
|
||||
issues.push(
|
||||
issue(
|
||||
"swift_conformances_static_witness_slots",
|
||||
@@ -405,75 +456,87 @@ export const decodeSwiftDispatchFacets = (input: {
|
||||
for (let slot = 0; slot < count; slot++) {
|
||||
if (!budget.admit()) break;
|
||||
const entry = witness + BigInt((slot + 1) * 8);
|
||||
const implementationResolution = read.resolvePointer(entry);
|
||||
const implementation =
|
||||
implementationResolution.kind === "raw" ||
|
||||
implementationResolution.kind === "rebase"
|
||||
? implementationResolution.target
|
||||
: null;
|
||||
const requirementFlags = read.u32(
|
||||
protocol! + 24n + BigInt(signatureCount * 12 + slot * 8),
|
||||
);
|
||||
const kind = requirementFlags & 15;
|
||||
const synchronousFunction =
|
||||
kind >= 1 && kind <= 4 && (requirementFlags & 0x20) === 0;
|
||||
const executable =
|
||||
implementation !== null &&
|
||||
synchronousFunction &&
|
||||
segments.some(
|
||||
(segment) =>
|
||||
segment.executable &&
|
||||
implementation >= segment.address &&
|
||||
implementation < segment.address + segment.size,
|
||||
try {
|
||||
const implementationResolution = read.resolvePointer(entry);
|
||||
const implementation =
|
||||
implementationResolution.kind === "raw" ||
|
||||
implementationResolution.kind === "rebase"
|
||||
? implementationResolution.target
|
||||
: null;
|
||||
const requirementFlags = read.u32(
|
||||
protocol! + 24n + BigInt(signatureCount * 12 + slot * 8),
|
||||
);
|
||||
const slotDecode = executable
|
||||
? { status: "decoded" as const, reason: null }
|
||||
: {
|
||||
status: "partial" as const,
|
||||
reason:
|
||||
implementationResolution.kind === "bind"
|
||||
? "Witness implementation is an external Mach-O binding"
|
||||
: implementationResolution.kind === "unknown" ||
|
||||
implementationResolution.kind === "unsupported"
|
||||
? implementationResolution.reason
|
||||
: "Witness slot is non-function, async, nil, external, authenticated or encoded; implementation target is unresolved",
|
||||
};
|
||||
result.swift_dispatch_slots.push({
|
||||
owner: `${typeName}: ${protocolName}`,
|
||||
table_kind: "witness_table",
|
||||
slot_index: slot + 1,
|
||||
requirement: null,
|
||||
implementation: null,
|
||||
implementation_address: executable ? hex(implementation) : null,
|
||||
...(implementationResolution.kind === "bind"
|
||||
? {
|
||||
implementation_binding: importBinding(
|
||||
entry,
|
||||
implementationResolution,
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
thunk_address: null,
|
||||
location: read.location(entry),
|
||||
decode: slotDecode,
|
||||
evidence: read.evidence(
|
||||
entry,
|
||||
"Static Swift witness slot, indexed after the conformance-descriptor header",
|
||||
),
|
||||
});
|
||||
if (slotDecode.status !== "decoded")
|
||||
const kind = requirementFlags & 15;
|
||||
const synchronousFunction =
|
||||
kind >= 1 && kind <= 4 && (requirementFlags & 0x20) === 0;
|
||||
const executable =
|
||||
implementation !== null &&
|
||||
synchronousFunction &&
|
||||
segments.some(
|
||||
(segment) =>
|
||||
segment.executable &&
|
||||
implementation >= segment.address &&
|
||||
implementation < segment.address + segment.size,
|
||||
);
|
||||
const slotDecode = executable
|
||||
? { status: "decoded" as const, reason: null }
|
||||
: {
|
||||
status: "partial" as const,
|
||||
reason:
|
||||
implementationResolution.kind === "bind"
|
||||
? "Witness implementation is an external Mach-O binding"
|
||||
: implementationResolution.kind === "unknown" ||
|
||||
implementationResolution.kind === "unsupported"
|
||||
? implementationResolution.reason
|
||||
: "Witness slot is non-function, async, nil, external, authenticated or encoded; implementation target is unresolved",
|
||||
};
|
||||
result.swift_dispatch_slots.push({
|
||||
owner: `${typeReference.name}: ${protocolName}`,
|
||||
table_kind: "witness_table",
|
||||
slot_index: slot + 1,
|
||||
requirement: null,
|
||||
implementation: null,
|
||||
implementation_address: executable ? hex(implementation) : null,
|
||||
...(implementationResolution.kind === "bind"
|
||||
? {
|
||||
implementation_binding: importBinding(
|
||||
entry,
|
||||
implementationResolution,
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
thunk_address: null,
|
||||
location: read.location(entry),
|
||||
decode: slotDecode,
|
||||
evidence: read.evidence(
|
||||
entry,
|
||||
"Static Swift witness slot, indexed after the conformance-descriptor header",
|
||||
),
|
||||
});
|
||||
if (slotDecode.status !== "decoded")
|
||||
issues.push(
|
||||
issue(
|
||||
"swift_conformances_static_witness_slots",
|
||||
"witness_slot_unresolved",
|
||||
slotDecode.reason,
|
||||
hex(entry),
|
||||
),
|
||||
);
|
||||
} catch (cause) {
|
||||
if (!(cause instanceof RangeError)) throw cause;
|
||||
issues.push(
|
||||
issue(
|
||||
"swift_conformances_static_witness_slots",
|
||||
"witness_slot_unresolved",
|
||||
slotDecode.reason,
|
||||
cause.message,
|
||||
hex(entry),
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (cause) {
|
||||
const message = cause instanceof Error ? cause.message : String(cause);
|
||||
swiftFailures.push(`${hex(field)}: ${message}`);
|
||||
issues.push(
|
||||
issue(
|
||||
"swift_conformances_static_witness_slots",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/** Expected format-boundary failures from bounded Swift metadata reads. */
|
||||
export const expectedSwiftMetadataReadFailure = (
|
||||
cause: unknown,
|
||||
): string | null => {
|
||||
if (cause instanceof RangeError) return cause.message;
|
||||
if (
|
||||
cause instanceof TypeError &&
|
||||
"code" in cause &&
|
||||
cause.code === "ERR_ENCODING_INVALID_ENCODED_DATA"
|
||||
)
|
||||
return cause.message;
|
||||
return null;
|
||||
};
|
||||
@@ -18,6 +18,10 @@ const readers = (bytes: Buffer) => ({
|
||||
address: `0x${address.toString(16)}`,
|
||||
file_offset: Number(address),
|
||||
}),
|
||||
targetLocation: (address: bigint) => ({
|
||||
address: `0x${address.toString(16)}`,
|
||||
file_offset: Number(address),
|
||||
}),
|
||||
evidence: (address: bigint, description: string) => [
|
||||
{
|
||||
kind: "binary_metadata" as const,
|
||||
|
||||
@@ -52,6 +52,10 @@ it("preserves a required conformance pointer's unknown reason", () => {
|
||||
address: `0x${address.toString(16)}`,
|
||||
file_offset: Number(address),
|
||||
}),
|
||||
targetLocation: (address) => ({
|
||||
address: `0x${address.toString(16)}`,
|
||||
file_offset: Number(address),
|
||||
}),
|
||||
evidence: () => [],
|
||||
offset: (address) => Number(address),
|
||||
},
|
||||
@@ -203,6 +207,10 @@ it("returns a located issue for an emitted unresolved witness slot", () => {
|
||||
address: `0x${address.toString(16)}`,
|
||||
file_offset: Number(address),
|
||||
}),
|
||||
targetLocation: (address) => ({
|
||||
address: `0x${address.toString(16)}`,
|
||||
file_offset: Number(address),
|
||||
}),
|
||||
evidence: (address, description) => [
|
||||
{
|
||||
kind: "binary_metadata",
|
||||
|
||||
Reference in New Issue
Block a user