fix(native): preserve dispatch metadata format refusals (#8013)

* fix(native): preserve dispatch metadata format refusals

* test(native): normalize CLI format refusal envelopes

* test(native): assert admitted metadata target paths
This commit is contained in:
gensokyo-dev authored and GitHub committed 2026-10-29 15:48:42 +08:00
1 parent 17ceb77ca5
commit e3d8d20236
10 files changed
+247 -7

No files matched your search

@@ -1,6 +1,28 @@
import { expect, it } from "vitest";
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
import { appleDispatchMetadataFailure } from "./AppleDispatchMetadata.js";
import { BinaryFormatFailure } from "./metadata/BinaryContainer.js";
it.each(["malformed", "unsupported"] as const)(
"keeps an explicit %s metadata refusal target-specific",
(kind) => {
const cause = new BinaryFormatFailure(kind, "metadata format refused");
expect(appleDispatchMetadataFailure(cause, "/selected")).toMatchObject({
_tag: "AnalysisUnsupportedTargetError",
path: "/selected",
reason: "metadata format refused",
});
},
);
it.each([new RangeError("unexpected range"), new TypeError("unexpected type")])(
"keeps an unclassified parser exception as a provider failure (%s)",
(cause) => {
expect(appleDispatchMetadataFailure(cause, "/selected")._tag).toBe(
"ProviderAdapterError",
);
},
);
// The metadata target is already bound, so losing it is a change to that artifact rather than a new input error.
it.each(["integrity", "path"] as const)(
+19 -1
View File
@@ -18,6 +18,7 @@ import {
AnalysisAccessDeniedError,
AnalysisCapabilityUnavailableError,
AnalysisResourceConstraintError,
AnalysisUnsupportedTargetError,
} from "../domain/analysisErrorCore.js";
import {
ArtifactFileCleanupFailure,
@@ -41,6 +42,7 @@ import { readBoundedFileBytes } from "../process/BoundedFileBytes.js";
import { EvidenceIntegrityError } from "../domain/evidenceErrors.js";
import { pushDispatchCoverage } from "./AppleDispatchCoverage.js";
import { parsePointerFixups } from "./AppleMachoFixups.js";
import { BinaryFormatFailure } from "./metadata/BinaryContainer.js";
import {
parseMachoLayout,
UnsupportedMachoMetadataFormatError,
@@ -230,7 +232,10 @@ export const decodeAppleDispatchMetadata = (
({ name }) => name === "__swift5_types",
)) {
if (section.size % 4 !== 0)
throw new RangeError("Misaligned Swift type section");
throw new BinaryFormatFailure(
"malformed",
"Misaligned Swift type section",
);
for (let index = 0; index < section.size / 4 && index < maxRecords; index++)
typeEntries.push(section.address + BigInt(index * 4));
if (section.size / 4 > maxRecords) budget.truncated = true;
@@ -445,6 +450,19 @@ const appleDispatchMetadataPrimaryFailure = (
): AnalysisError => {
if (origin === "owned-or-classified") {
if (cause instanceof AnalysisError) return cause;
if (cause instanceof BinaryFormatFailure)
return new AnalysisUnsupportedTargetError(
OPERATION,
path,
cause.message,
{
cause,
remediationAction:
cause.kind === "malformed"
? "Select a Mach-O artifact with well-formed metadata sections, reopen the target, then retry."
: "Select a supported little-endian 64-bit Mach-O artifact, reopen the target, then retry.",
},
);
if (cause instanceof UnsupportedMachoMetadataFormatError)
return new AnalysisCapabilityUnavailableError(
IDENTITY.id,
+4 -1
View File
@@ -183,7 +183,10 @@ export const parseMachoLayout = (
}
if (table.wide && !table.littleEndian) {
if (cpuType !== 0x01000012)
throw new TypeError("Unsupported big-endian Mach-O CPU type");
throw new BinaryFormatFailure(
"unsupported",
"Unsupported big-endian Mach-O CPU type",
);
throw new UnsupportedMachoMetadataFormatError(
"Unsupported big-endian 64-bit PowerPC Mach-O metadata",
);
+5 -1
View File
@@ -3,6 +3,7 @@ import type { Section } from "./AppleMachoSelection.js";
import type { ObjcDispatchReaders } from "./AppleObjcDispatchFacets.js";
import { readObjcPropertiesOf } from "./AppleObjcProperties.js";
import { issue, type DecodeIssue } from "./AppleDispatchDecodeFacts.js";
import { BinaryFormatFailure } from "./metadata/BinaryContainer.js";
const CLASS_SYMBOL = /^_OBJC_(?:META)?CLASS_\$_(.+)$/u;
@@ -59,7 +60,10 @@ export const decodeObjcCategories = (input: {
({ name }) => name === "__objc_catlist",
)) {
if (section.size % 8 !== 0)
throw new RangeError("Misaligned Objective-C category list");
throw new BinaryFormatFailure(
"malformed",
"Misaligned Objective-C category list",
);
for (let index = 0; index < section.size / 8; index++) {
if (!input.admit()) break;
examined++;
+9 -2
View File
@@ -6,6 +6,7 @@ import {
import type { Segment, Section } from "./AppleMachoSelection.js";
import { boundClassName, decodeObjcCategories } from "./AppleObjcCategories.js";
import { readObjcPropertiesOf } from "./AppleObjcProperties.js";
import { BinaryFormatFailure } from "./metadata/BinaryContainer.js";
import type { DecodeIssue } from "./AppleDispatchDecodeFacts.js";
import { createObjcProtocolReader } from "./AppleObjcProtocols.js";
import type {
@@ -250,7 +251,10 @@ export const decodeObjcDispatchFacets = (input: {
({ name }) => name === "__objc_protolist",
)) {
if (section.size % 8 !== 0)
throw new RangeError("Misaligned Objective-C protocol list");
throw new BinaryFormatFailure(
"malformed",
"Misaligned Objective-C protocol list",
);
for (let index = 0; index < section.size / 8; index++) {
if (budget.isFull()) {
budget.markTruncated();
@@ -406,7 +410,10 @@ export const decodeObjcDispatchFacets = (input: {
({ name }) => name === "__objc_classlist",
)) {
if (section.size % 8 !== 0)
throw new RangeError("Misaligned Objective-C class-list size");
throw new BinaryFormatFailure(
"malformed",
"Misaligned Objective-C class-list size",
);
for (let index = 0; index < section.size / 8; index++) {
if (budget.isFull()) {
budget.markTruncated();
+5 -1
View File
@@ -5,6 +5,7 @@ import {
import type { ObjcSwiftMetadata } from "../domain/native/objcSwiftMetadata.js";
import type { PointerResolution } from "./AppleMachoFixups.js";
import type { Segment, Section } from "./AppleMachoSelection.js";
import { BinaryFormatFailure } from "./metadata/BinaryContainer.js";
import { issue, type FacetDecodeFacts } from "./AppleDispatchDecodeFacts.js";
import {
decodeSwiftConformanceTypeReference,
@@ -325,7 +326,10 @@ export const decodeSwiftDispatchFacets = (input: {
({ name }) => name === "__swift5_proto",
)) {
if (section.size % 4 !== 0)
throw new RangeError("Misaligned Swift conformance section");
throw new BinaryFormatFailure(
"malformed",
"Misaligned Swift conformance section",
);
for (let index = 0; index < section.size / 4; index++) {
if (!budget.admit()) break;
swiftExamined++;