236 lines
7.6 KiB
TypeScript
236 lines
7.6 KiB
TypeScript
import type { ObjcSwiftMetadata } from "../domain/native/objcSwiftMetadata.js";
|
|
import type { PointerFixups } from "./AppleMachoFixups.js";
|
|
import type { DecodeIssue } from "./AppleDispatchDecodeFacts.js";
|
|
import type { FacetDecodeFacts } from "./AppleDispatchDecodeFacts.js";
|
|
|
|
const describeFixups = (fixups: PointerFixups): string => {
|
|
const unread =
|
|
fixups.unreadRebaseBytes > 0
|
|
? `LC_DYLD_INFO rebase opcodes were not decoded (${fixups.unreadRebaseBytes} bytes)`
|
|
: undefined;
|
|
const kind =
|
|
fixups.kind === "chained"
|
|
? `chained fixups: ${fixups.formats.join(", ") || "no fixup segments"}`
|
|
: fixups.kind === "dyld-info"
|
|
? "LC_DYLD_INFO bind opcodes"
|
|
: unread === undefined
|
|
? "no fixup load commands"
|
|
: undefined;
|
|
return [unread, kind].filter((line) => line !== undefined).join("; ");
|
|
};
|
|
|
|
/** Append the per-facet coverage of one Apple dispatch metadata decode. */
|
|
export const pushDispatchCoverage = (input: {
|
|
readonly result: ObjcSwiftMetadata;
|
|
readonly failures: readonly string[];
|
|
readonly categoryIssues: readonly DecodeIssue[];
|
|
readonly examined: number;
|
|
readonly categoriesExamined: number;
|
|
readonly truncated: boolean;
|
|
readonly fixups: PointerFixups;
|
|
readonly vtables: FacetDecodeFacts;
|
|
readonly swift: FacetDecodeFacts;
|
|
}): void => {
|
|
const {
|
|
result,
|
|
failures,
|
|
categoryIssues,
|
|
examined,
|
|
categoriesExamined,
|
|
truncated,
|
|
fixups,
|
|
vtables,
|
|
swift,
|
|
} = input;
|
|
const classOwnedMethods = result.objc_dispatch_implementations.filter(
|
|
({ category }) => category === undefined,
|
|
);
|
|
const classRecords = [
|
|
...result.objc_classes.flatMap((record) =>
|
|
record.decode?.status === "decoded"
|
|
? []
|
|
: [
|
|
`class ${record.name}: ${record.decode?.reason ?? record.decode?.status ?? "decode_status_missing"}`,
|
|
],
|
|
),
|
|
...result.objc_protocol_records.flatMap((record) =>
|
|
record.decode.status === "decoded"
|
|
? []
|
|
: [
|
|
`protocol ${record.name}: ${record.decode.reason ?? record.decode.status}`,
|
|
],
|
|
),
|
|
...classOwnedMethods.flatMap((record) =>
|
|
record.decode.status === "decoded"
|
|
? []
|
|
: [
|
|
`method ${record.class_name} ${record.method_type} ${record.selector}: ${record.decode.reason ?? record.decode.status}`,
|
|
],
|
|
),
|
|
...result.objc_ivars.flatMap((record) =>
|
|
record.decode.status === "decoded"
|
|
? []
|
|
: [
|
|
`ivar ${record.class_name}.${record.name}: ${record.decode.reason ?? record.decode.status}`,
|
|
],
|
|
),
|
|
];
|
|
const classReasons = [
|
|
...failures,
|
|
...classRecords,
|
|
...(truncated ? ["max_records_reached"] : []),
|
|
];
|
|
const categoryMethods = result.objc_dispatch_implementations.filter(
|
|
({ category }) => category !== undefined,
|
|
);
|
|
const categoryMethodReasons = categoryMethods.flatMap((record) =>
|
|
record.decode.status === "decoded"
|
|
? []
|
|
: [
|
|
`category method ${record.category} ${record.class_name} ${record.method_type} ${record.selector}: ${record.decode.reason ?? record.decode.status}`,
|
|
],
|
|
);
|
|
result.coverage.push({
|
|
facet: "objc_class_method_ivar_metadata",
|
|
status: classReasons.length > 0 ? "partial" : "complete",
|
|
reason: classReasons.join("; ") || null,
|
|
examined,
|
|
decoded: result.objc_classes.filter(
|
|
({ decode }) => decode?.status === "decoded",
|
|
).length,
|
|
});
|
|
result.coverage.push({
|
|
facet: "binary_relative_pointers",
|
|
status:
|
|
truncated ||
|
|
result.relative_pointers.some((item) => item.decode.status !== "decoded")
|
|
? "partial"
|
|
: "complete",
|
|
reason: truncated
|
|
? "max_records_reached"
|
|
: result.relative_pointers.some(
|
|
(item) => item.decode.status !== "decoded",
|
|
)
|
|
? "relative_pointer_targets_unresolved"
|
|
: null,
|
|
examined: result.relative_pointers.length,
|
|
decoded: result.relative_pointers.filter(
|
|
(item) => item.decode.status === "decoded",
|
|
).length,
|
|
});
|
|
const categoryFailures = categoryIssues.map(
|
|
({ code, location, message }) =>
|
|
`${code}${location === undefined ? "" : ` at ${location}`}: ${message}`,
|
|
);
|
|
result.coverage.push({
|
|
facet: "objc_properties_categories",
|
|
status:
|
|
truncated ||
|
|
categoryFailures.length > 0 ||
|
|
result.objc_categories.some(
|
|
({ decode }) => decode.status !== "decoded",
|
|
) ||
|
|
categoryMethodReasons.length > 0
|
|
? "partial"
|
|
: "complete",
|
|
reason:
|
|
[
|
|
...categoryFailures,
|
|
...categoryMethodReasons,
|
|
...(truncated ? ["max_records_reached"] : []),
|
|
].join("; ") || null,
|
|
examined: categoriesExamined,
|
|
decoded: result.objc_categories.filter(
|
|
({ decode }) => decode.status === "decoded",
|
|
).length,
|
|
});
|
|
const fixupReason = [
|
|
describeFixups(fixups),
|
|
...fixups.issues.map(
|
|
({ code, location, message }) =>
|
|
`${code}${location === undefined ? "" : ` at ${location}`}: ${message}`,
|
|
),
|
|
]
|
|
.filter((line) => line.length > 0)
|
|
.join("; ");
|
|
result.coverage.push({
|
|
facet: "pointer_fixups",
|
|
status:
|
|
fixups.issues.length > 0 || fixups.unreadRebaseBytes > 0
|
|
? "partial"
|
|
: fixups.examined === 0
|
|
? "unsupported"
|
|
: "complete",
|
|
reason: fixupReason.length > 0 ? fixupReason : null,
|
|
examined: fixups.examined,
|
|
decoded: fixups.decoded,
|
|
});
|
|
result.coverage.push({
|
|
facet: "swift_generic_resilient_witnesses_overrides_async_coroutines",
|
|
status: "unsupported",
|
|
reason:
|
|
"This reader admits simple Swift conformance/static witness records and non-generic, non-resilient class vtables; generic/resilient tables and other Swift metadata families are not decoded",
|
|
examined: 0,
|
|
decoded: 0,
|
|
});
|
|
result.coverage.push({
|
|
facet: "swift_conformances_static_witness_slots",
|
|
status:
|
|
swift.exhaustive &&
|
|
!truncated &&
|
|
!result.swift_conformances.some(
|
|
({ decode }) => decode.status !== "decoded",
|
|
) &&
|
|
!result.swift_dispatch_slots.some(
|
|
({ table_kind, decode }) =>
|
|
table_kind === "witness_table" && decode.status !== "decoded",
|
|
)
|
|
? "complete"
|
|
: "partial",
|
|
reason:
|
|
[
|
|
...swift.issues.map(formatIssue),
|
|
...(truncated ? ["max_records_reached"] : []),
|
|
...(result.swift_conformances.some(
|
|
({ decode }) => decode.status !== "decoded",
|
|
) ||
|
|
result.swift_dispatch_slots.some(
|
|
({ table_kind, decode }) =>
|
|
table_kind === "witness_table" && decode.status !== "decoded",
|
|
)
|
|
? ["swift_records_unresolved"]
|
|
: []),
|
|
].join("; ") || null,
|
|
examined: swift.examined,
|
|
decoded: swift.decoded,
|
|
});
|
|
result.coverage.push({
|
|
facet: "swift_class_vtable_descriptors",
|
|
status:
|
|
vtables.exhaustive &&
|
|
!truncated &&
|
|
!result.swift_dispatch_slots.some(
|
|
({ table_kind, decode }) =>
|
|
table_kind === "class_vtable" && decode.status !== "decoded",
|
|
)
|
|
? "complete"
|
|
: "partial",
|
|
reason:
|
|
[
|
|
...vtables.issues.map(formatIssue),
|
|
...(truncated ? ["max_records_reached"] : []),
|
|
...(result.swift_dispatch_slots.some(
|
|
({ table_kind, decode }) =>
|
|
table_kind === "class_vtable" && decode.status !== "decoded",
|
|
)
|
|
? ["vtable_implementations_unresolved"]
|
|
: []),
|
|
].join("; ") || null,
|
|
examined: vtables.examined,
|
|
decoded: vtables.decoded,
|
|
});
|
|
};
|
|
|
|
const formatIssue = ({ code, location, message }: DecodeIssue): string =>
|
|
`${code}${location === undefined ? "" : ` at ${location}`}: ${message}`;
|