fix(native): admit the retroactive flag when decoding Swift static witnesses

A retroactive protocol conformance sets flag bit 0x40. The static
witness check admitted only the type-reference kind bits (0x38), so a
retroactive conformance lost its static witness table. The admitted
mask is now 0x78; conditional requirements, resilient witnesses and
generic witness tables stay unadmitted.

Fixes #7445

Only the mask change and its tests are taken from #7489.

Supersedes #7489

Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
Sarthak Pandeyandgensokyo-dev authored and Ysopwnable committed 2026-11-14 16:31:24 +01:00
1 parent 85198fd5cb
commit bb1bb46dcb
2 files changed
+200 -1

No files matched your search

+11 -1
View File
@@ -187,6 +187,16 @@ export interface SwiftDispatchReaders {
const hex = (value: bigint) => `0x${value.toString(16)}`;
const decoded = { status: "decoded" as const, reason: null };
/**
* Conformance flags admitted for static witness table decoding:
* - Type-reference kind: bits 3..5 (0x38)
* - Retroactive conformance: bit 6 (0x40)
*
* Conditional requirements (bits 8..15), generic witness tables (bit 17),
* and resilient witness flags (bit 16) remain unadmitted.
*/
const SWIFT_STATIC_WITNESS_FLAGS_MASK = 0x78;
/** Create a Swift ABI signed-relative pointer decoder that records relative_pointers. */
export const createSwiftRelativeReader = (input: {
bytes: Buffer;
@@ -392,7 +402,7 @@ export const decodeSwiftDispatchFacets = (input: {
protocol !== null &&
protocolFacts.readable &&
typeReference.staticWitnessIdentity &&
(flags & ~0x38) === 0;
(flags & ~SWIFT_STATIC_WITNESS_FLAGS_MASK) === 0;
staticWitness &&=
typeReference.kind === 2 || typeReference.generic === false;
let witnessReason: string | null = staticWitness
+189
View File
@@ -356,6 +356,195 @@ it("returns a located issue for an emitted unresolved witness slot", () => {
});
});
it("decodes a retroactive static witness slot when flags include retroactive bit 0x40", () => {
const result = objcSwiftMetadataSchema.parse({ db_save_result: null });
const values = new Map<bigint, number>([
[0x20cn, 0x48], // kind 1 (0x08) + retroactive conformance (0x40)
[0x500n, 3], // protocol descriptor kind
[0x800n, 0], // module context
[0x50cn, 0], // signature count
[0x510n, 1], // witness requirement count
[0x2b0n, 16], // nominal type descriptor kind
[0x518n, 1], // synchronous function witness requirement
]);
const budget: DispatchRecordBudget = {
truncated: false,
isFull: () => false,
markTruncated: () => {
budget.truncated = true;
},
admit: () => true,
};
const facts = decodeSwiftDispatchFacets({
sections: [{ name: "__swift5_proto", address: 0x100n, size: 4 }],
segments: [
{
address: 0x900n,
size: 0x100n,
offset: 0x900,
executable: true,
},
],
readers: {
u32: (address) => values.get(address) ?? 0,
pointer: (address) => (address === 0x380n ? 0x200n : 0x900n),
resolvePointer: (address) => ({
kind: "raw",
target:
address === 0x2a0n ? 0x2b0n : address === 0x380n ? 0x200n : 0x900n,
}),
string: (address) => (address === 0x600n ? "Proto" : "Widget"),
location: (address) => ({
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",
description,
location: {
address: `0x${address.toString(16)}`,
file_offset: Number(address),
},
artifact_path: "/fixture",
artifact_sha256: "a".repeat(64),
},
],
offset: (address) => Number(address),
},
relative: (field) => ({
kind: "target",
target:
new Map<bigint, bigint>([
[0x100n, 0x200n],
[0x200n, 0x500n],
[0x508n, 0x600n],
[0x504n, 0x800n],
[0x808n, 0x820n],
[0x204n, 0x2a0n], // indirect pointer to type descriptor
[0x2b8n, 0x700n],
[0x208n, 0x380n],
]).get(field) ?? 0n,
}),
budget,
result,
});
expect(result.swift_conformances[0]).toMatchObject({
type_name: "Widget",
protocol_name: "Proto",
decode: { status: "decoded", reason: null },
});
expect(result.swift_dispatch_slots).toHaveLength(1);
expect(result.swift_dispatch_slots[0]).toMatchObject({
slot_index: 1,
implementation_address: "0x900",
decode: { status: "decoded", reason: null },
});
expect(facts).toMatchObject({
facet: "swift_conformances_static_witness_slots",
exhaustive: true,
examined: 1,
decoded: 1,
issues: [],
});
});
it("retains partial coverage for retroactive conformance when conditional requirement flags are set", () => {
const result = objcSwiftMetadataSchema.parse({ db_save_result: null });
const values = new Map<bigint, number>([
[0x20cn, 0x148], // kind 1 (0x08) + retroactive (0x40) + conditional requirements (0x100)
[0x500n, 3], // protocol descriptor kind
[0x800n, 0], // module context
[0x50cn, 0], // signature count
[0x510n, 1], // witness requirement count
[0x2b0n, 16], // nominal type descriptor kind
]);
const budget: DispatchRecordBudget = {
truncated: false,
isFull: () => false,
markTruncated: () => {},
admit: () => true,
};
const facts = decodeSwiftDispatchFacets({
sections: [{ name: "__swift5_proto", address: 0x100n, size: 4 }],
segments: [],
readers: {
u32: (address) => values.get(address) ?? 0,
pointer: () => 0n,
resolvePointer: (address) => ({
kind: "raw",
target: address === 0x2a0n ? 0x2b0n : 0x200n,
}),
string: (address) => (address === 0x600n ? "Proto" : "Widget"),
location: (address) => ({
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",
description,
location: {
address: `0x${address.toString(16)}`,
file_offset: Number(address),
},
artifact_path: "/fixture",
artifact_sha256: "a".repeat(64),
},
],
offset: (address) => Number(address),
},
relative: (field) => ({
kind: "target",
target:
new Map<bigint, bigint>([
[0x100n, 0x200n],
[0x200n, 0x500n],
[0x508n, 0x600n],
[0x504n, 0x800n],
[0x808n, 0x820n],
[0x204n, 0x2a0n],
[0x2b8n, 0x700n],
[0x208n, 0x380n],
]).get(field) ?? 0n,
}),
budget,
result,
});
expect(result.swift_conformances[0]).toMatchObject({
decode: {
status: "partial",
reason:
"Conditional, generic, resilient or missing static witness table is not decoded",
},
});
expect(result.swift_dispatch_slots).toHaveLength(0);
expect(facts).toMatchObject({
facet: "swift_conformances_static_witness_slots",
exhaustive: false,
examined: 1,
decoded: 0,
issues: [
{
code: "conformance_decode_partial",
message:
"Conditional, generic, resilient or missing static witness table is not decoded",
location: "0x200",
},
],
});
});
describe("Swift vtable coverage facts: unsupported pointer fixup coverage", () => {
it("marks pointer fixup coverage partial for an unsupported format with no decoded pointers", () => {
const result = objcSwiftMetadataSchema.parse({ db_save_result: null });