2026-10-07 22:40:48 +08:00
import type { ObjcSwiftMetadata } from "../domain/native/objcSwiftMetadata.js" ;
2026-10-07 12:18:22 +00:00
import type { PointerFixups } from "./AppleMachoFixups.js" ;
2026-10-09 00:20:49 +08:00
import type { DecodeIssue } from "./AppleDispatchDecodeFacts.js" ;
import type { FacetDecodeFacts } from "./AppleDispatchDecodeFacts.js" ;
2026-10-07 12:18:22 +00:00
const describeFixups = ( fixups : PointerFixups ) : string => {
2026-10-21 08:17:12 +08:00
const descriptions : string [] = [];
if ( fixups . unreadRebaseBytes > 0 )
descriptions . push (
`LC_DYLD_INFO rebase opcodes were not decoded ( ${ fixups . unreadRebaseBytes } bytes)` ,
);
if ( fixups . kind === "chained" )
descriptions . push (
`chained fixups: ${ fixups . formats . join ( ", " ) || "no fixup segments" } ` ,
);
if ( fixups . legacyRebaseStreams > 0 )
descriptions . push ( "LC_DYLD_INFO rebase opcodes" );
if ( fixups . legacyBindStreams > 0 )
descriptions . push ( "LC_DYLD_INFO bind opcodes" );
return descriptions . length === 0
? "no fixup load commands"
: descriptions . join ( "; " );
2026-10-07 12:18:22 +00:00
};
/** Append the per-facet coverage of one Apple dispatch metadata decode. */
export const pushDispatchCoverage = ( input : {
readonly result : ObjcSwiftMetadata ;
readonly failures : readonly string [];
2026-10-09 00:20:49 +08:00
readonly categoryIssues : readonly DecodeIssue [];
2026-10-07 12:18:22 +00:00
readonly examined : number ;
readonly categoriesExamined : number ;
readonly truncated : boolean ;
2026-10-14 08:29:16 +08:00
readonly objcListScanTruncated : boolean ;
readonly objcClassListScanTruncated : boolean ;
2026-10-07 12:18:22 +00:00
readonly fixups : PointerFixups ;
2026-10-22 00:19:15 -07:00
readonly declarations : FacetDecodeFacts ;
2026-10-09 00:20:49 +08:00
readonly vtables : FacetDecodeFacts ;
readonly swift : FacetDecodeFacts ;
2026-10-07 12:18:22 +00:00
}) : void => {
2026-10-09 00:20:49 +08:00
const {
result ,
failures ,
categoryIssues ,
examined ,
categoriesExamined ,
truncated ,
2026-10-14 08:29:16 +08:00
objcListScanTruncated ,
objcClassListScanTruncated ,
2026-10-09 00:20:49 +08:00
fixups ,
2026-10-22 00:19:15 -07:00
declarations ,
2026-10-09 00:20:49 +08:00
vtables ,
swift ,
} = input ;
2026-10-13 12:54:22 +08:00
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 ,
2026-10-14 08:29:16 +08:00
...( truncated || objcListScanTruncated ? [ "max_records_reached" ] : []),
2026-10-13 12:54:22 +08:00
];
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 } ` ,
],
);
2026-10-07 12:18:22 +00:00
result . coverage . push ({
facet : "objc_class_method_ivar_metadata" ,
2026-10-13 12:54:22 +08:00
status : classReasons.length > 0 ? "partial" : "complete" ,
reason : classReasons.join ( "; " ) || null ,
2026-10-07 12:18:22 +00:00
examined ,
2026-10-13 12:54:22 +08:00
decoded : result.objc_classes.filter (
({ decode }) => decode ? . status === "decoded" ,
). length ,
2026-10-07 12:18:22 +00:00
});
2026-10-14 08:29:16 +08:00
const relativePointerReasons = [
...( truncated || objcClassListScanTruncated ? [ "max_records_reached" ] : []),
...( result . relative_pointers . some (
( item ) => item . decode . status !== "decoded" ,
)
? [ "relative_pointer_targets_unresolved" ]
: []),
];
2026-10-07 12:18:22 +00:00
result . coverage . push ({
facet : "binary_relative_pointers" ,
2026-10-14 08:29:16 +08:00
status : relativePointerReasons.length > 0 ? "partial" : "complete" ,
reason : relativePointerReasons.join ( "; " ) || null ,
2026-10-07 12:18:22 +00:00
examined : result.relative_pointers.length ,
decoded : result.relative_pointers.filter (
( item ) => item . decode . status === "decoded" ,
). length ,
});
2026-10-09 00:20:49 +08:00
const categoryFailures = categoryIssues . map (
({ code , location , message }) =>
` ${ code }${ location === undefined ? "" : ` at ${ location } ` } : ${ message } ` ,
2026-10-07 12:18:22 +00:00
);
result . coverage . push ({
facet : "objc_properties_categories" ,
status :
2026-10-09 00:20:49 +08:00
truncated ||
2026-10-14 08:29:16 +08:00
objcClassListScanTruncated ||
2026-10-07 12:18:22 +00:00
categoryFailures . length > 0 ||
2026-10-13 12:54:22 +08:00
result . objc_categories . some (
({ decode }) => decode . status !== "decoded" ,
) ||
categoryMethodReasons . length > 0
2026-10-07 12:18:22 +00:00
? "partial"
: "complete" ,
2026-10-09 00:20:49 +08:00
reason :
2026-10-13 12:54:22 +08:00
[
... categoryFailures ,
... categoryMethodReasons ,
2026-10-14 08:29:16 +08:00
...( truncated || objcClassListScanTruncated
? [ "max_records_reached" ]
: []),
2026-10-13 12:54:22 +08:00
]. join ( "; " ) || null ,
2026-10-07 12:18:22 +00:00
examined : categoriesExamined ,
decoded : result.objc_categories.filter (
({ decode }) => decode . status === "decoded" ,
). length ,
});
2026-10-10 17:14:47 +08:00
const fixupReason = [
describeFixups ( fixups ),
... fixups . issues . map (
({ code , location , message }) =>
` ${ code }${ location === undefined ? "" : ` at ${ location } ` } : ${ message } ` ,
),
]
. filter (( line ) => line . length > 0 )
. join ( "; " );
2026-10-07 12:18:22 +00:00
result . coverage . push ({
facet : "pointer_fixups" ,
2026-10-10 17:14:47 +08:00
status :
fixups.issues.length > 0 || fixups . unreadRebaseBytes > 0
? "partial"
: fixups . examined === 0
? "unsupported"
: "complete" ,
reason : fixupReason.length > 0 ? fixupReason : null ,
examined : fixups.examined ,
2026-10-13 12:54:22 +08:00
decoded : fixups.decoded ,
2026-10-07 12:18:22 +00:00
});
result . coverage . push ({
facet : "swift_generic_resilient_witnesses_overrides_async_coroutines" ,
status : "unsupported" ,
reason :
2026-10-22 00:19:15 -07:00
"This reader admits Swift nominal type declarations, simple conformance/static witness records and non-generic, non-resilient class vtables; generic/resilient tables and other Swift metadata families are not decoded" ,
2026-10-07 12:18:22 +00:00
examined : 0 ,
decoded : 0 ,
});
2026-10-09 00:20:49 +08:00
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 ,
});
2026-10-22 00:19:15 -07:00
result . coverage . push ({
facet : "swift_declarations" ,
status : declarations.exhaustive && ! truncated ? "complete" : "partial" ,
reason :
[
... declarations . issues . map ( formatIssue ),
...( truncated ? [ "max_records_reached" ] : []),
]. join ( "; " ) || null ,
examined : declarations.examined ,
decoded : declarations.decoded ,
});
2026-10-09 00:20:49 +08:00
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 ,
});
2026-10-07 12:18:22 +00:00
};
2026-10-09 00:20:49 +08:00
const formatIssue = ({ code , location , message } : DecodeIssue ) : string =>
` ${ code }${ location === undefined ? "" : ` at ${ location } ` } : ${ message } ` ;