mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
fix(native): share Swift context and preserve partial protocol facts
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
|
||||
import { appleDispatchMetadataFailure } from "./AppleDispatchMetadata.js";
|
||||
|
||||
it.each(["integrity", "path"] as const)(
|
||||
"keeps %s source-read refusals distinct from provider execution failures",
|
||||
(reason) => {
|
||||
const cause = new ArtifactReaderFailure(reason, "source boundary refused");
|
||||
expect(appleDispatchMetadataFailure(cause, "/selected")._tag).toBe(
|
||||
reason === "integrity"
|
||||
? "AnalysisArtifactChangedError"
|
||||
: "AnalysisInputError",
|
||||
);
|
||||
},
|
||||
);
|
||||
it.each(["EACCES", "EPERM", "ENOENT"])(
|
||||
"preserves the filesystem outcome %s for a selected metadata target",
|
||||
(code) => {
|
||||
const physical = Object.assign(new Error("selected read refused"), {
|
||||
code,
|
||||
});
|
||||
const cause = new ArtifactReaderFailure("io", "could not read", {
|
||||
cause: physical,
|
||||
});
|
||||
expect(appleDispatchMetadataFailure(cause, "/selected")._tag).toBe(
|
||||
code === "ENOENT" ? "AnalysisInputError" : "AnalysisAccessDeniedError",
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -277,6 +277,10 @@ const swiftBoundWitnessSlotImage = () => {
|
||||
bytes.writeInt32LE(0x2f0, 0xa10);
|
||||
bytes.writeUInt32LE(16, 0xc00);
|
||||
bytes.writeInt32LE(0x238, 0xc08);
|
||||
bytes.writeInt32LE(0xc40 - 0xc04, 0xc04);
|
||||
bytes.writeUInt32LE(0, 0xc40);
|
||||
bytes.writeInt32LE(0xec0 - 0xc48, 0xc48);
|
||||
bytes.write("FixtureModule\0", 0xec0);
|
||||
bytes.writeUInt32LE(3, 0xd00);
|
||||
bytes.writeInt32LE(0x148, 0xd08);
|
||||
bytes.writeUInt32LE(0, 0xd0c);
|
||||
@@ -2102,3 +2106,60 @@ describe("malformed Objective-C protocol method lists", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("decodes protocol instance properties through the same property-list owner as classes", () => {
|
||||
const bytes = fixture();
|
||||
bytes.writeBigUInt64LE(base + 0x600n, 0x778);
|
||||
const result = decodeAppleDispatchMetadata(bytes, 100, provenance);
|
||||
expect(result.objc_protocols[0]?.properties).toEqual(
|
||||
result.objc_classes[0]?.properties,
|
||||
);
|
||||
expect(result.objc_protocols[0]?.properties).toHaveLength(3);
|
||||
expect(result.objc_protocol_records[0]?.decode).toEqual({
|
||||
status: "decoded",
|
||||
reason: null,
|
||||
});
|
||||
});
|
||||
it("retains protocol methods when its instance-property list is unreadable", () => {
|
||||
const bytes = fixture();
|
||||
bytes.writeBigUInt64LE(base + 0x5000n, 0x778);
|
||||
const result = decodeAppleDispatchMetadata(bytes, 100, provenance);
|
||||
expect(result.objc_protocols[0]).toMatchObject({
|
||||
name: "FixtureProtocol",
|
||||
methods: [{ selector: "requiredAction:" }],
|
||||
properties: [],
|
||||
});
|
||||
expect(result.objc_protocol_records[0]?.decode.status).toBe("partial");
|
||||
expect(
|
||||
result.coverage.find(
|
||||
({ facet }) => facet === "objc_class_method_ivar_metadata",
|
||||
)?.status,
|
||||
).toBe("partial");
|
||||
});
|
||||
it("retains conformance facts beside an empty Swift protocol name", () => {
|
||||
const bytes = swiftConformanceTypeReferenceImage(2);
|
||||
bytes[0xe50] = 0;
|
||||
const result = decodeAppleDispatchMetadata(bytes, 100, provenance);
|
||||
expect(result.swift_conformances[0]).toMatchObject({
|
||||
type_name: "NSObject",
|
||||
protocol_name: null,
|
||||
decode: { status: "partial" },
|
||||
});
|
||||
expect(result.objc_classes).not.toHaveLength(0);
|
||||
});
|
||||
|
||||
it("retains the enclosing Swift type and module in a conformance owner", () => {
|
||||
const bytes = swiftBoundWitnessSlotImage();
|
||||
bytes.writeInt32LE(0xc20 - 0xc04, 0xc04);
|
||||
bytes.writeUInt32LE(17, 0xc20);
|
||||
bytes.writeInt32LE(0xc40 - 0xc24, 0xc24);
|
||||
bytes.writeInt32LE(0xea0 - 0xc28, 0xc28);
|
||||
bytes.write("Outer\0", 0xea0);
|
||||
const result = decodeAppleDispatchMetadata(bytes, 100, provenance);
|
||||
expect(result.swift_conformances[0]).toMatchObject({
|
||||
type_name: "Outer.Widget",
|
||||
module: "FixtureModule",
|
||||
decode: { status: "decoded" },
|
||||
});
|
||||
expect(result.swift_dispatch_slots[0]?.owner).toBe("Outer.Widget: Equatable");
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { isSelectedRegularFileError } from "../filesystem/RegularFile.js";
|
||||
import {
|
||||
DEFAULT_DISPATCH_METADATA_INPUT_BYTES,
|
||||
MAXIMUM_DISPATCH_METADATA_INPUT_BYTES,
|
||||
@@ -13,6 +14,9 @@ import {
|
||||
} from "../domain/binaryTargetTypes.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisInputError,
|
||||
AnalysisArtifactChangedError,
|
||||
AnalysisAccessDeniedError,
|
||||
AnalysisCapabilityUnavailableError,
|
||||
AnalysisResourceConstraintError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
@@ -49,7 +53,10 @@ import {
|
||||
type SwiftRelativeTargetLocation,
|
||||
} from "./AppleSwiftDispatchFacets.js";
|
||||
import { createSwiftClassVtableDecoder } from "./AppleSwiftVtables.js";
|
||||
import { decodeSwiftNominalTypes } from "./AppleSwiftNominalTypes.js";
|
||||
import {
|
||||
decodeSwiftNominalTypes,
|
||||
createSwiftContextReader,
|
||||
} from "./AppleSwiftNominalTypes.js";
|
||||
import { inspectObjcClassImportBindings } from "./AppleObjcClassImportBindings.js";
|
||||
|
||||
/** Decode validated little-endian 64-bit Apple Objective-C metadata without loading target code. */
|
||||
@@ -204,7 +211,13 @@ export const decodeAppleDispatchMetadata = (
|
||||
budget,
|
||||
result,
|
||||
});
|
||||
const contextReader = createSwiftContextReader({
|
||||
u32,
|
||||
string,
|
||||
relative: swiftRelative,
|
||||
});
|
||||
const swift = decodeSwiftDispatchFacets({
|
||||
contextReader,
|
||||
sections,
|
||||
segments,
|
||||
readers,
|
||||
@@ -242,6 +255,7 @@ export const decodeAppleDispatchMetadata = (
|
||||
},
|
||||
});
|
||||
const declarations = decodeSwiftNominalTypes({
|
||||
contextReader,
|
||||
entries: typeEntries,
|
||||
result,
|
||||
visitor: vtables.visitor,
|
||||
@@ -436,6 +450,34 @@ export const appleDispatchMetadataFailure = (
|
||||
cause.message,
|
||||
{ cause },
|
||||
);
|
||||
if (cause instanceof ArtifactReaderFailure && cause.reason === "integrity")
|
||||
return new AnalysisArtifactChangedError(OPERATION, path, cause.message, {
|
||||
cause,
|
||||
});
|
||||
if (cause instanceof ArtifactReaderFailure && cause.reason === "path")
|
||||
return new AnalysisInputError(OPERATION, { cause }, [
|
||||
{ path: ["path"], reason: "invalid_value", message: cause.message },
|
||||
]);
|
||||
const readCause =
|
||||
cause instanceof ArtifactReaderFailure && cause.reason === "io"
|
||||
? cause.cause
|
||||
: cause;
|
||||
if (
|
||||
readCause instanceof Error &&
|
||||
"code" in readCause &&
|
||||
(readCause.code === "EACCES" || readCause.code === "EPERM")
|
||||
)
|
||||
return new AnalysisAccessDeniedError(OPERATION, path, readCause.code, {
|
||||
cause,
|
||||
});
|
||||
if (isSelectedRegularFileError(readCause))
|
||||
return new AnalysisInputError(OPERATION, { cause }, [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: "invalid_value",
|
||||
message: `Selected metadata target could not be read (${String(readCause.code)}): ${path}`,
|
||||
},
|
||||
]);
|
||||
return new ProviderAdapterError(IDENTITY.id, OPERATION, {
|
||||
cause,
|
||||
diagnostics: {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { readObjcPropertyList } from "./AppleObjcProperties.js";
|
||||
import type { ObjcSwiftMetadata } from "../domain/native/objcSwiftMetadata.js";
|
||||
import type { SwiftNominalReaders } from "./AppleSwiftNominalTypes.js";
|
||||
|
||||
@@ -86,7 +87,8 @@ export const createObjcProtocolReader = (input: {
|
||||
if (address === 0n || visited.has(address) || !read.admit()) return;
|
||||
visited.add(address);
|
||||
try {
|
||||
if (read.u32(address + 64n) < 72)
|
||||
const protocolSize = read.u32(address + 64n);
|
||||
if (protocolSize < 72)
|
||||
throw new RangeError("Truncated Objective-C protocol declaration");
|
||||
const protocolName = name(address);
|
||||
// Adopted protocols are independent declarations; decode them before
|
||||
@@ -135,7 +137,34 @@ export const createObjcProtocolReader = (input: {
|
||||
failures.push(
|
||||
`Protocol 0x${address.toString(16)}: Objective-C protocol ${protocolName} ${message}`,
|
||||
);
|
||||
let properties: ObjcSwiftMetadata["objc_protocols"][number]["properties"] =
|
||||
[];
|
||||
const propertyReasons: string[] = [];
|
||||
try {
|
||||
const decoded = readObjcPropertyList(
|
||||
read,
|
||||
read.pointer(address + 56n),
|
||||
() => read.admit(),
|
||||
onTruncated,
|
||||
);
|
||||
properties = decoded.properties;
|
||||
for (const problem of decoded.issues)
|
||||
propertyReasons.push(problem.message);
|
||||
// Older protocol revisions end at size/flags. The later class-property
|
||||
// list has no class/instance discriminator in the public property shape.
|
||||
if (protocolSize >= 96 && read.pointer(address + 88n) !== 0n)
|
||||
propertyReasons.push("protocol class properties are not decoded");
|
||||
} catch (cause) {
|
||||
propertyReasons.push(
|
||||
cause instanceof Error ? cause.message : String(cause),
|
||||
);
|
||||
}
|
||||
for (const message of propertyReasons)
|
||||
failures.push(
|
||||
`Protocol 0x${address.toString(16)}: Objective-C protocol ${protocolName} properties: ${message}`,
|
||||
);
|
||||
const decodeReasons = [
|
||||
...propertyReasons,
|
||||
...(skipped.length === 0 ? [] : ["protocol_method_selector_empty"]),
|
||||
...(budgetTruncated ? ["max_records_reached"] : []),
|
||||
];
|
||||
@@ -143,7 +172,7 @@ export const createObjcProtocolReader = (input: {
|
||||
name: protocolName,
|
||||
methods: required,
|
||||
optional_methods: optional,
|
||||
properties: [],
|
||||
properties,
|
||||
});
|
||||
result.objc_protocol_records.push({
|
||||
name: protocolName,
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
createSwiftContextReader,
|
||||
type SwiftContextReader,
|
||||
} from "./AppleSwiftNominalTypes.js";
|
||||
import type { PointerResolution } from "./AppleMachoFixups.js";
|
||||
import type {
|
||||
SwiftDispatchReaders,
|
||||
@@ -26,6 +30,7 @@ export interface SwiftConformanceTypeReference {
|
||||
/** Descriptor, inline class-name, local ObjC class pointer, or raw target. */
|
||||
readonly target: bigint | null;
|
||||
readonly name: string | null;
|
||||
readonly module: string | null;
|
||||
readonly binding: ReturnType<typeof swiftImportBinding> | null;
|
||||
readonly reason: string | null;
|
||||
readonly staticWitnessIdentity: boolean;
|
||||
@@ -36,6 +41,7 @@ interface SwiftTypeReferenceInput {
|
||||
readonly descriptor: bigint;
|
||||
readonly flags: number;
|
||||
readonly read: SwiftDispatchReaders;
|
||||
readonly contextReader?: SwiftContextReader;
|
||||
readonly relative: (
|
||||
field: bigint,
|
||||
kind: SwiftRelativeField,
|
||||
@@ -50,6 +56,7 @@ const typeReferenceResult = (input: {
|
||||
readonly kind: number;
|
||||
readonly target: bigint | null;
|
||||
readonly name?: string | null;
|
||||
readonly module?: string | null;
|
||||
readonly binding?: ReturnType<typeof swiftImportBinding> | null;
|
||||
readonly reason?: string | null;
|
||||
readonly generic?: boolean | null;
|
||||
@@ -57,6 +64,7 @@ const typeReferenceResult = (input: {
|
||||
kind: input.kind,
|
||||
target: input.target,
|
||||
name: input.name ?? null,
|
||||
module: input.module ?? null,
|
||||
binding: input.binding ?? null,
|
||||
reason: input.reason ?? null,
|
||||
staticWitnessIdentity:
|
||||
@@ -121,20 +129,17 @@ const decodeNominalTypeReference = (
|
||||
reason: "Unsupported Swift type descriptor kind",
|
||||
});
|
||||
|
||||
const name = read.string(
|
||||
requiredSwiftRelativeTarget(
|
||||
relative,
|
||||
target + 8n,
|
||||
"typeName",
|
||||
input.unresolvedReason,
|
||||
),
|
||||
);
|
||||
const contextReader =
|
||||
input.contextReader ?? createSwiftContextReader({ ...read, relative });
|
||||
const name = contextReader.typeName(target);
|
||||
const context = contextReader.contextOf(target);
|
||||
return typeReferenceResult({
|
||||
kind,
|
||||
target,
|
||||
name: name.length === 0 ? null : name,
|
||||
name: context.path ?? name,
|
||||
module: context.module,
|
||||
binding,
|
||||
reason: name.length === 0 ? "Empty Swift conformance type name" : null,
|
||||
reason: context.reason,
|
||||
generic: (descriptorFlags & 0x80) !== 0,
|
||||
});
|
||||
} catch (cause) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { SwiftContextReader } from "./AppleSwiftNominalTypes.js";
|
||||
import type { ObjcSwiftMetadata } from "../domain/native/objcSwiftMetadata.js";
|
||||
import type { PointerResolution } from "./AppleMachoFixups.js";
|
||||
import type { Segment, Section } from "./AppleMachoSelection.js";
|
||||
@@ -149,7 +150,10 @@ const readSwiftProtocolFacts = (input: {
|
||||
return { name: null, readable: false, reason };
|
||||
}
|
||||
try {
|
||||
return { name: read.string(nameAddress), readable: true, reason: null };
|
||||
const name = read.string(nameAddress);
|
||||
if (name.length === 0)
|
||||
throw new RangeError("Swift protocol descriptor name is empty");
|
||||
return { name, readable: true, reason: null };
|
||||
} catch (cause) {
|
||||
const reason = expectedSwiftMetadataReadFailure(cause);
|
||||
if (reason === null) throw cause;
|
||||
@@ -313,6 +317,7 @@ export const createSwiftRelativeReader = (input: {
|
||||
|
||||
/** Decode `__swift5_proto` conformances and static witness slots into `result`. */
|
||||
export const decodeSwiftDispatchFacets = (input: {
|
||||
contextReader?: SwiftContextReader;
|
||||
sections: readonly Section[];
|
||||
segments: readonly Segment[];
|
||||
readers: SwiftDispatchReaders;
|
||||
@@ -368,6 +373,9 @@ export const decodeSwiftDispatchFacets = (input: {
|
||||
descriptor,
|
||||
flags,
|
||||
read,
|
||||
...(input.contextReader === undefined
|
||||
? {}
|
||||
: { contextReader: input.contextReader }),
|
||||
relative,
|
||||
unresolvedReason: unresolvedSwiftRelativeReason,
|
||||
});
|
||||
@@ -402,10 +410,20 @@ export const decodeSwiftDispatchFacets = (input: {
|
||||
descriptor,
|
||||
);
|
||||
}
|
||||
const conformanceReason = witnessReason ?? typeReference.reason;
|
||||
const conformanceDecode =
|
||||
witnessReason === null
|
||||
conformanceReason === null
|
||||
? { status: "decoded" as const, reason: null }
|
||||
: { status: "partial" as const, reason: witnessReason };
|
||||
: { status: "partial" as const, reason: conformanceReason };
|
||||
if (witnessReason === null && typeReference.reason !== null)
|
||||
issues.push(
|
||||
issue(
|
||||
"swift_conformances_static_witness_slots",
|
||||
"conformance_identity_partial",
|
||||
typeReference.reason,
|
||||
hex(descriptor),
|
||||
),
|
||||
);
|
||||
result.swift_conformances.push({
|
||||
type_name: typeReference.name,
|
||||
type_reference_kind: typeReference.kind,
|
||||
@@ -419,7 +437,7 @@ export const decodeSwiftDispatchFacets = (input: {
|
||||
? {}
|
||||
: { protocol_target: read.targetLocation(protocol) }),
|
||||
protocol_binding: protocolBinding,
|
||||
module: null,
|
||||
module: typeReference.module,
|
||||
witness_table:
|
||||
witness === null
|
||||
? { address: null, file_offset: null }
|
||||
|
||||
@@ -480,3 +480,25 @@ describe("Swift nominal type declaration bounds", () => {
|
||||
).toMatchObject({ status: "complete" });
|
||||
});
|
||||
});
|
||||
|
||||
it("uses qualified nominal context for a nested class vtable owner", () => {
|
||||
const bytes = nominalImage((image) => {
|
||||
context(image, MODULE, 0, null, { at: 0x700, value: "ReaMod" });
|
||||
context(image, 0x340, 0x51, MODULE, { at: 0x710, value: "Outer" });
|
||||
context(image, 0x380, 0x80000050, 0x340, { at: 0x720, value: "Inner" });
|
||||
image.u32(0x380 + 28, 1);
|
||||
image.u32(0x380 + 48, 1);
|
||||
image.rel(0x380 + 56, 0x900);
|
||||
return [0x380];
|
||||
});
|
||||
const result = decodeAppleDispatchMetadata(bytes, 100, provenance);
|
||||
expect(result.swift_decls[0]).toMatchObject({
|
||||
name: "Inner",
|
||||
qualified_name: "Outer.Inner",
|
||||
module: "ReaMod",
|
||||
});
|
||||
expect(result.swift_dispatch_slots[0]).toMatchObject({
|
||||
owner: "Outer.Inner",
|
||||
decode: { status: "decoded" },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface SwiftNominalDescriptor {
|
||||
readonly descriptor: bigint;
|
||||
readonly flags: number;
|
||||
readonly name: string;
|
||||
readonly context: SwiftContextFacts;
|
||||
}
|
||||
|
||||
/** A `__swift5_types` record that may be a class whose name is unknown. */
|
||||
@@ -37,7 +38,7 @@ export interface SwiftNominalRecordFailure {
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives each descriptor as soon as its kind and name are read, so a
|
||||
* Receives each descriptor with its shared context facts, so a
|
||||
* consumer spends the record budget in record order rather than after every
|
||||
* declaration.
|
||||
*/
|
||||
@@ -66,7 +67,7 @@ const hex = (value: bigint) => `0x${value.toString(16)}`;
|
||||
* Facts of one context. `depth` counts contexts up to and including its
|
||||
* module, so a cached result is valid wherever a walk reaches it.
|
||||
*/
|
||||
interface ContextFacts {
|
||||
export interface SwiftContextFacts {
|
||||
readonly module: string | null;
|
||||
/** Dotted named path below the module; null when unresolved. */
|
||||
readonly path: string | null;
|
||||
@@ -77,7 +78,10 @@ interface ContextFacts {
|
||||
readonly walkLimited: boolean;
|
||||
}
|
||||
|
||||
const unresolved = (reason: string, walkLimited = false): ContextFacts => ({
|
||||
const unresolved = (
|
||||
reason: string,
|
||||
walkLimited = false,
|
||||
): SwiftContextFacts => ({
|
||||
module: null,
|
||||
path: null,
|
||||
depth: 0,
|
||||
@@ -86,28 +90,17 @@ const unresolved = (reason: string, walkLimited = false): ContextFacts => ({
|
||||
walkLimited,
|
||||
});
|
||||
|
||||
const tooDeep = (): ContextFacts =>
|
||||
const tooDeep = (): SwiftContextFacts =>
|
||||
unresolved(
|
||||
`Swift context parent chain exceeds ${MAXIMUM_CONTEXT_DEPTH} contexts or is cyclic`,
|
||||
);
|
||||
|
||||
/**
|
||||
* Decode `__swift5_types` nominal type descriptors into `swift_decls`.
|
||||
* Each context is read once, so shared parents add one relative-pointer
|
||||
* record regardless of how many types they contain.
|
||||
*/
|
||||
export const decodeSwiftNominalTypes = (input: {
|
||||
readers: SwiftNominalReaders;
|
||||
entries: readonly bigint[];
|
||||
result: ObjcSwiftMetadata;
|
||||
visitor: SwiftNominalVisitor;
|
||||
}): FacetDecodeFacts => {
|
||||
const { readers: read, result, visitor } = input;
|
||||
const issues = [] as ReturnType<typeof issue>[];
|
||||
const contexts = new Map<bigint, ContextFacts>();
|
||||
/** One snapshot-scoped owner of nominal names and parent-context identity. */
|
||||
export const createSwiftContextReader = (
|
||||
read: Pick<SwiftNominalReaders, "u32" | "relative" | "string">,
|
||||
) => {
|
||||
const contexts = new Map<bigint, SwiftContextFacts>();
|
||||
const names = new Map<bigint, string>();
|
||||
let examined = 0;
|
||||
let decoded = 0;
|
||||
|
||||
const typeName = (descriptor: bigint): string => {
|
||||
const known = names.get(descriptor);
|
||||
@@ -122,7 +115,7 @@ export const decodeSwiftNominalTypes = (input: {
|
||||
return name;
|
||||
};
|
||||
|
||||
const parentOf = (context: bigint, walk: number): ContextFacts => {
|
||||
const parentOf = (context: bigint, walk: number): SwiftContextFacts => {
|
||||
const value = read.relative(context + 4n, "contextParent");
|
||||
if (value.kind === "target") return contextOf(value.target, walk + 1);
|
||||
return unresolved(
|
||||
@@ -134,9 +127,9 @@ export const decodeSwiftNominalTypes = (input: {
|
||||
|
||||
/** One level below `parent`, bounded in depth and qualified-name length. */
|
||||
const child = (
|
||||
parent: ContextFacts,
|
||||
facts: Partial<ContextFacts>,
|
||||
): ContextFacts => {
|
||||
parent: SwiftContextFacts,
|
||||
facts: Partial<SwiftContextFacts>,
|
||||
): SwiftContextFacts => {
|
||||
if (parent.depth + 1 > MAXIMUM_CONTEXT_DEPTH)
|
||||
return { ...tooDeep(), walkLimited: parent.walkLimited };
|
||||
const next = {
|
||||
@@ -156,7 +149,7 @@ export const decodeSwiftNominalTypes = (input: {
|
||||
: next;
|
||||
};
|
||||
|
||||
const readContext = (context: bigint, walk: number): ContextFacts => {
|
||||
const readContext = (context: bigint, walk: number): SwiftContextFacts => {
|
||||
const kind = read.u32(context) & 31;
|
||||
switch (kind) {
|
||||
case MODULE: {
|
||||
@@ -207,14 +200,14 @@ export const decodeSwiftNominalTypes = (input: {
|
||||
}
|
||||
};
|
||||
|
||||
const contextOf = (context: bigint, walk: number): ContextFacts => {
|
||||
const contextOf = (context: bigint, walk: number): SwiftContextFacts => {
|
||||
const known = contexts.get(context);
|
||||
if (known !== undefined) return known;
|
||||
// Bound recursion on cyclic or over-deep chains; this stop depends on
|
||||
// where the walk began, so neither it nor its descendants are cached.
|
||||
if (walk > MAXIMUM_CONTEXT_DEPTH)
|
||||
return { ...tooDeep(), walkLimited: true };
|
||||
let facts: ContextFacts;
|
||||
let facts: SwiftContextFacts;
|
||||
try {
|
||||
facts = readContext(context, walk);
|
||||
} catch (cause) {
|
||||
@@ -226,6 +219,31 @@ export const decodeSwiftNominalTypes = (input: {
|
||||
return facts;
|
||||
};
|
||||
|
||||
return {
|
||||
typeName,
|
||||
contextOf: (descriptor: bigint) => contextOf(descriptor, 0),
|
||||
};
|
||||
};
|
||||
export type SwiftContextReader = ReturnType<typeof createSwiftContextReader>;
|
||||
|
||||
/**
|
||||
* Decode `__swift5_types` nominal type descriptors into `swift_decls`.
|
||||
* Each context is read once, so shared parents add one relative-pointer
|
||||
* record regardless of how many types they contain.
|
||||
*/
|
||||
export const decodeSwiftNominalTypes = (input: {
|
||||
readers: SwiftNominalReaders;
|
||||
entries: readonly bigint[];
|
||||
result: ObjcSwiftMetadata;
|
||||
visitor: SwiftNominalVisitor;
|
||||
contextReader: SwiftContextReader;
|
||||
}): FacetDecodeFacts => {
|
||||
const { readers: read, result, visitor } = input;
|
||||
const issues = [] as ReturnType<typeof issue>[];
|
||||
const { typeName, contextOf } = input.contextReader;
|
||||
let examined = 0;
|
||||
let decoded = 0;
|
||||
|
||||
const accessorOf = (
|
||||
descriptor: bigint,
|
||||
): { address: string | null; reason: string | null } => {
|
||||
@@ -296,8 +314,8 @@ export const decodeSwiftNominalTypes = (input: {
|
||||
issues.push(issue(FACET, "type_decode_failed", message, hex(record)));
|
||||
continue;
|
||||
}
|
||||
visitor.descriptor({ record, descriptor, flags, name });
|
||||
const context = contextOf(descriptor, 0);
|
||||
const context = contextOf(descriptor);
|
||||
visitor.descriptor({ record, descriptor, flags, name, context });
|
||||
const accessor = accessorOf(descriptor);
|
||||
const reason = context.reason ?? accessor.reason;
|
||||
result.swift_decls.push({
|
||||
|
||||
@@ -143,6 +143,14 @@ it("counts examined slots and reports an unresolved async slot", () => {
|
||||
descriptor: 0x200n,
|
||||
flags: 0x80000010,
|
||||
name: "Widget",
|
||||
context: {
|
||||
module: "Test",
|
||||
path: "Widget",
|
||||
depth: 2,
|
||||
anonymous: false,
|
||||
reason: null,
|
||||
walkLimited: false,
|
||||
},
|
||||
});
|
||||
const facts = vtables.facts();
|
||||
|
||||
|
||||
@@ -54,9 +54,12 @@ export const createSwiftClassVtableDecoder = (input: {
|
||||
record,
|
||||
descriptor,
|
||||
flags,
|
||||
name: owner,
|
||||
name,
|
||||
context,
|
||||
}: SwiftNominalDescriptor): void => {
|
||||
if ((flags & 31) !== 16) return;
|
||||
const owner = context.path ?? name;
|
||||
if (context.reason !== null) failure({ record, message: context.reason });
|
||||
try {
|
||||
const specific = flags >>> 16;
|
||||
if ((flags & 0x80) !== 0 || (specific & 0x2000) !== 0)
|
||||
|
||||
Reference in New Issue
Block a user