diff --git a/src/artifacts/StableArtifactFailure.ts b/src/artifacts/StableArtifactFailure.ts new file mode 100644 index 0000000000..0c4fb62751 --- /dev/null +++ b/src/artifacts/StableArtifactFailure.ts @@ -0,0 +1,50 @@ +import type { AnalysisError } from "../domain/analysisErrorBase.js"; +import { analysisErrorWithCleanupFailure } from "../domain/analysisErrorCleanup.js"; +import { ProviderCleanupError } from "../domain/providerCleanupError.js"; +import { ArtifactReaderFailure } from "./ArtifactReader.js"; +import { bindArtifactFileCleanup } from "./ArtifactFileOperation.js"; +import { completedStableArtifactObservation } from "./StableArtifactObservation.js"; + +const artifactCleanupFailure = ( + cause: unknown, + providerId: string, + operation: string, +): ProviderCleanupError | undefined => { + if (!(cause instanceof ArtifactReaderFailure) || cause.cleanup === undefined) + return undefined; + return new ProviderCleanupError( + providerId, + cause.cleanup.resources, + { reason: cause.cleanup.reason }, + { cause, operation }, + ); +}; + +/** Project descriptor cleanup while keeping its exact leases bound to the error. */ +export const projectArtifactCloseFailure = ( + cause: unknown, + providerId: string, + operation: string, +): unknown => { + const error = artifactCleanupFailure(cause, providerId, operation); + if (error === undefined) return cause; + bindArtifactFileCleanup(cause, error); + return error; +}; + +/** Preserve completed physical identity and exact cleanup ownership around a provider's primary failure. */ +export const projectStableArtifactFailure = ( + cause: unknown, + primary: AnalysisError, + path: string, + providerId: string, + operation: string, +): AnalysisError => { + const observation = completedStableArtifactObservation(cause, path); + if (observation !== undefined) primary.retainPartialObservation(observation); + const cleanup = artifactCleanupFailure(cause, providerId, operation); + if (cleanup === undefined) return primary; + const error = analysisErrorWithCleanupFailure(primary, cleanup, operation); + bindArtifactFileCleanup(cause, error); + return error; +}; diff --git a/src/erlang/BeamProvider.ts b/src/erlang/BeamProvider.ts index 049f916516..4aa2301115 100644 --- a/src/erlang/BeamProvider.ts +++ b/src/erlang/BeamProvider.ts @@ -1,15 +1,16 @@ +import { + projectStableArtifactFailure, + projectArtifactCloseFailure, +} from "../artifacts/StableArtifactFailure.js"; import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js"; import { readStableArtifact } from "../artifacts/readStableArtifact.js"; import { artifactFilePrimaryFacts, type ArtifactFilePrimaryFacts, artifactSelectedPathErrno, - bindArtifactFileCleanup, artifactFileCancellationCause, } from "../artifacts/ArtifactFileOperation.js"; import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js"; -import { analysisErrorWithCleanupFailure } from "../domain/analysisErrorCleanup.js"; -import { completedStableArtifactObservation } from "../artifacts/StableArtifactObservation.js"; import type { BeamPort } from "../application/erlang/BeamPort.js"; import type { ExecutionOptions } from "../application/AnalysisProvider.js"; import { ArtifactOperationError } from "../domain/artifactOperationError.js"; @@ -27,7 +28,6 @@ import { type InspectErlangBeamInput, } from "../domain/erlang/beam.js"; import { err, ok } from "../domain/result.js"; -import { ProviderCleanupError } from "../domain/providerCleanupError.js"; import { BeamCapacityFailure, parseErlangBeam } from "./BeamParser.js"; /** Bundled format reader identity; module names are separate observed facts. */ @@ -48,19 +48,11 @@ export class BeamProvider implements BeamPort { try { await this.#resources.close(); } catch (cause: unknown) { - if ( - !(cause instanceof ArtifactReaderFailure) || - cause.cleanup === undefined - ) - throw cause; - const error = new ProviderCleanupError( + throw projectArtifactCloseFailure( + cause, ERLANG_BEAM_PROVIDER_IDENTITY.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { cause, operation: OPERATION }, + OPERATION, ); - bindArtifactFileCleanup(cause, error); - throw error; } } @@ -204,17 +196,11 @@ const beamFailurePrimary = ( const beamFailure = (cause: unknown, input: InspectErlangBeamInput) => { const primary = beamFailurePrimary(artifactFilePrimaryFacts(cause), input); - const observation = completedStableArtifactObservation(cause, input.path); - if (observation !== undefined) primary.retainPartialObservation(observation); - if (!(cause instanceof ArtifactReaderFailure) || cause.cleanup === undefined) - return primary; - const cleanup = new ProviderCleanupError( + return projectStableArtifactFailure( + cause, + primary, + input.path, ERLANG_BEAM_PROVIDER_IDENTITY.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { operation: OPERATION, cause }, + OPERATION, ); - const error = analysisErrorWithCleanupFailure(primary, cleanup, OPERATION); - bindArtifactFileCleanup(cause, error); - return error; }; diff --git a/src/firmware/devicetree/DevicetreeProvider.ts b/src/firmware/devicetree/DevicetreeProvider.ts index 5d4a42938f..848f9bec51 100644 --- a/src/firmware/devicetree/DevicetreeProvider.ts +++ b/src/firmware/devicetree/DevicetreeProvider.ts @@ -1,14 +1,16 @@ +import { + projectStableArtifactFailure, + projectArtifactCloseFailure, +} from "../../artifacts/StableArtifactFailure.js"; import { isSelectedRegularFileError } from "../../filesystem/RegularFile.js"; import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js"; import { readStableArtifact } from "../../artifacts/readStableArtifact.js"; import { artifactFilePrimaryFacts, type ArtifactFilePrimaryFacts, - bindArtifactFileCleanup, artifactFileCancellationCause, } from "../../artifacts/ArtifactFileOperation.js"; import { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js"; -import { analysisErrorWithCleanupFailure } from "../../domain/analysisErrorCleanup.js"; import type { DevicetreePort } from "../../application/binaryDiagnostics/DevicetreePort.js"; import type { ExecutionOptions } from "../../application/AnalysisProvider.js"; import { ArtifactOperationError } from "../../domain/artifactOperationError.js"; @@ -24,9 +26,7 @@ import { MAX_DEVICETREE_FILE_BYTES, type InspectDevicetreeInput, } from "../../domain/firmware/devicetree.js"; -import { completedStableArtifactObservation } from "../../artifacts/StableArtifactObservation.js"; import { err, ok } from "../../domain/result.js"; -import { ProviderCleanupError } from "../../domain/providerCleanupError.js"; import { DEVICETREE_PROVIDER } from "../../domain/investigationProviders.js"; import { BinaryFormatFailure } from "../../native/metadata/BinaryContainer.js"; import { DevicetreeLimitFailure, parseDevicetree } from "./DevicetreeParser.js"; @@ -42,19 +42,11 @@ export class DevicetreeProvider implements DevicetreePort { try { await this.#resources.close(); } catch (cause: unknown) { - if ( - !(cause instanceof ArtifactReaderFailure) || - cause.cleanup === undefined - ) - throw cause; - const error = new ProviderCleanupError( + throw projectArtifactCloseFailure( + cause, DEVICETREE_PROVIDER.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { cause, operation: OPERATION }, + OPERATION, ); - bindArtifactFileCleanup(cause, error); - throw error; } } @@ -199,17 +191,11 @@ const devicetreeFailure = (cause: unknown, input: InspectDevicetreeInput) => { artifactFilePrimaryFacts(cause), input, ); - const observation = completedStableArtifactObservation(cause, input.path); - if (observation !== undefined) primary.retainPartialObservation(observation); - if (!(cause instanceof ArtifactReaderFailure) || cause.cleanup === undefined) - return primary; - const cleanup = new ProviderCleanupError( + return projectStableArtifactFailure( + cause, + primary, + input.path, DEVICETREE_PROVIDER.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { operation: OPERATION, cause }, + OPERATION, ); - const error = analysisErrorWithCleanupFailure(primary, cleanup, OPERATION); - bindArtifactFileCleanup(cause, error); - return error; }; diff --git a/src/go/GoBinaryProvider.ts b/src/go/GoBinaryProvider.ts index 2df40bf65c..54b12fe726 100644 --- a/src/go/GoBinaryProvider.ts +++ b/src/go/GoBinaryProvider.ts @@ -1,15 +1,17 @@ +import { + projectStableArtifactFailure, + projectArtifactCloseFailure, +} from "../artifacts/StableArtifactFailure.js"; import { isSelectedRegularFileError } from "../filesystem/RegularFile.js"; import { readStableArtifact } from "../artifacts/readStableArtifact.js"; import { artifactFilePrimaryFacts, type ArtifactFilePrimaryFacts, assertArtifactReadActive, - bindArtifactFileCleanup, artifactFileCancellationCause, } from "../artifacts/ArtifactFileOperation.js"; import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js"; import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js"; -import { analysisErrorWithCleanupFailure } from "../domain/analysisErrorCleanup.js"; import type { ExecutionOptions } from "../application/AnalysisProvider.js"; import type { GoBinaryPort } from "../application/go/GoBinaryPort.js"; import { AnalysisError } from "../domain/analysisErrorBase.js"; @@ -29,7 +31,6 @@ import { } from "../domain/go/goBinary.js"; import { ProviderAdapterError } from "../domain/providerAdapterError.js"; import { fixedAllowanceOptions } from "../domain/fixedAllowanceRemediation.js"; -import { ProviderCleanupError } from "../domain/providerCleanupError.js"; import { err, ok, type Result } from "../domain/result.js"; import { readGoBinaryImage, type GoBinaryImage } from "./GoBinaryImage.js"; import { requireSelectedNativeImage } from "../native/metadata/MachoSliceSelection.js"; @@ -37,7 +38,6 @@ import { BinaryFormatFailure, BinaryResourceFailure, } from "../native/metadata/BinaryContainer.js"; -import { completedStableArtifactObservation } from "../artifacts/StableArtifactObservation.js"; /** Bundled format reader identity; embedded compiler versions are separate observed facts. */ export const GO_BINARY_PROVIDER_IDENTITY = { @@ -147,19 +147,13 @@ const failureForPrimary = ( const failureFor = (cause: unknown, path: string): AnalysisError => { const primary = failureForPrimary(artifactFilePrimaryFacts(cause), path); - const observation = completedStableArtifactObservation(cause, path); - if (observation !== undefined) primary.retainPartialObservation(observation); - if (!(cause instanceof ArtifactReaderFailure) || cause.cleanup === undefined) - return primary; - const cleanup = new ProviderCleanupError( + return projectStableArtifactFailure( + cause, + primary, + path, GO_BINARY_PROVIDER_IDENTITY.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { operation: OPERATION, cause }, + OPERATION, ); - const error = analysisErrorWithCleanupFailure(primary, cleanup, OPERATION); - bindArtifactFileCleanup(cause, error); - return error; }; const outputLimit = (path: string) => @@ -231,19 +225,11 @@ export class GoBinaryProvider implements GoBinaryPort { try { await this.#resources.close(); } catch (cause: unknown) { - if ( - !(cause instanceof ArtifactReaderFailure) || - cause.cleanup === undefined - ) - throw cause; - const error = new ProviderCleanupError( + throw projectArtifactCloseFailure( + cause, GO_BINARY_PROVIDER_IDENTITY.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { cause, operation: OPERATION }, + OPERATION, ); - bindArtifactFileCleanup(cause, error); - throw error; } } diff --git a/src/native/apple/AppleIpsProvider.ts b/src/native/apple/AppleIpsProvider.ts index 8fadd76561..26a1fc901f 100644 --- a/src/native/apple/AppleIpsProvider.ts +++ b/src/native/apple/AppleIpsProvider.ts @@ -1,3 +1,7 @@ +import { + projectStableArtifactFailure, + projectArtifactCloseFailure, +} from "../../artifacts/StableArtifactFailure.js"; import { isSelectedRegularFileError } from "../../filesystem/RegularFile.js"; import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js"; import { readStableArtifact } from "../../artifacts/readStableArtifact.js"; @@ -5,12 +9,9 @@ import { artifactFilePrimaryFacts, type ArtifactFilePrimaryFacts, assertArtifactReadActive, - bindArtifactFileCleanup, artifactFileCancellationCause, } from "../../artifacts/ArtifactFileOperation.js"; import { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js"; -import { analysisErrorWithCleanupFailure } from "../../domain/analysisErrorCleanup.js"; -import { completedStableArtifactObservation } from "../../artifacts/StableArtifactObservation.js"; import type { AppleIpsPort } from "../../application/binaryDiagnostics/AppleIpsPort.js"; import type { ExecutionOptions } from "../../application/AnalysisProvider.js"; import { AnalysisError } from "../../domain/analysisErrorBase.js"; @@ -34,7 +35,6 @@ import { } from "../../domain/native/appleIps.js"; import { fixedAllowanceOptions } from "../../domain/fixedAllowanceRemediation.js"; import { ProviderAdapterError } from "../../domain/providerAdapterError.js"; -import { ProviderCleanupError } from "../../domain/providerCleanupError.js"; import { err, ok } from "../../domain/result.js"; import { APPLE_IPS_PROVIDER } from "../../domain/investigationProviders.js"; import { @@ -55,19 +55,11 @@ export class AppleIpsProvider implements AppleIpsPort { try { await this.#resources.close(); } catch (cause: unknown) { - if ( - !(cause instanceof ArtifactReaderFailure) || - cause.cleanup === undefined - ) - throw cause; - const error = new ProviderCleanupError( + throw projectArtifactCloseFailure( + cause, APPLE_IPS_PROVIDER.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { cause, operation: OPERATION }, + OPERATION, ); - bindArtifactFileCleanup(cause, error); - throw error; } } @@ -279,17 +271,11 @@ const appleIpsFailure = ( artifactFilePrimaryFacts(cause), input, ); - const observation = completedStableArtifactObservation(cause, input.path); - if (observation !== undefined) primary.retainPartialObservation(observation); - if (!(cause instanceof ArtifactReaderFailure) || cause.cleanup === undefined) - return primary; - const cleanup = new ProviderCleanupError( + return projectStableArtifactFailure( + cause, + primary, + input.path, APPLE_IPS_PROVIDER.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { operation: OPERATION, cause }, + OPERATION, ); - const error = analysisErrorWithCleanupFailure(primary, cleanup, OPERATION); - bindArtifactFileCleanup(cause, error); - return error; }; diff --git a/src/native/elf/ElfNotesProvider.ts b/src/native/elf/ElfNotesProvider.ts index 663cd8dd20..9efe2d648e 100644 --- a/src/native/elf/ElfNotesProvider.ts +++ b/src/native/elf/ElfNotesProvider.ts @@ -1,15 +1,16 @@ +import { + projectStableArtifactFailure, + projectArtifactCloseFailure, +} from "../../artifacts/StableArtifactFailure.js"; import { isSelectedRegularFileError } from "../../filesystem/RegularFile.js"; import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js"; import { readStableArtifact } from "../../artifacts/readStableArtifact.js"; import { artifactFilePrimaryFacts, type ArtifactFilePrimaryFacts, - bindArtifactFileCleanup, artifactFileCancellationCause, } from "../../artifacts/ArtifactFileOperation.js"; import { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js"; -import { analysisErrorWithCleanupFailure } from "../../domain/analysisErrorCleanup.js"; -import { completedStableArtifactObservation } from "../../artifacts/StableArtifactObservation.js"; import type { ElfNotesPort } from "../../application/binaryDiagnostics/ElfNotesPort.js"; import type { ExecutionOptions } from "../../application/AnalysisProvider.js"; import { ArtifactOperationError } from "../../domain/artifactOperationError.js"; @@ -28,7 +29,6 @@ import { type InspectElfNotesInput, } from "../../domain/native/elfNotes.js"; import { err, ok } from "../../domain/result.js"; -import { ProviderCleanupError } from "../../domain/providerCleanupError.js"; import { ELF_NOTES_PROVIDER } from "../../domain/investigationProviders.js"; import { BinaryFormatFailure } from "../metadata/BinaryContainer.js"; import { @@ -47,19 +47,11 @@ export class ElfNotesProvider implements ElfNotesPort { try { await this.#resources.close(); } catch (cause: unknown) { - if ( - !(cause instanceof ArtifactReaderFailure) || - cause.cleanup === undefined - ) - throw cause; - const error = new ProviderCleanupError( + throw projectArtifactCloseFailure( + cause, ELF_NOTES_PROVIDER.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { cause, operation: OPERATION }, + OPERATION, ); - bindArtifactFileCleanup(cause, error); - throw error; } } @@ -241,17 +233,11 @@ const elfNotesFailure = (cause: unknown, input: InspectElfNotesInput) => { artifactFilePrimaryFacts(cause), input, ); - const observation = completedStableArtifactObservation(cause, input.path); - if (observation !== undefined) primary.retainPartialObservation(observation); - if (!(cause instanceof ArtifactReaderFailure) || cause.cleanup === undefined) - return primary; - const cleanup = new ProviderCleanupError( + return projectStableArtifactFailure( + cause, + primary, + input.path, ELF_NOTES_PROVIDER.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { operation: OPERATION, cause }, + OPERATION, ); - const error = analysisErrorWithCleanupFailure(primary, cleanup, OPERATION); - bindArtifactFileCleanup(cause, error); - return error; }; diff --git a/src/native/elf/KernelModuleProvider.ts b/src/native/elf/KernelModuleProvider.ts index c0e6833c85..d1a4d4113e 100644 --- a/src/native/elf/KernelModuleProvider.ts +++ b/src/native/elf/KernelModuleProvider.ts @@ -1,15 +1,16 @@ +import { + projectStableArtifactFailure, + projectArtifactCloseFailure, +} from "../../artifacts/StableArtifactFailure.js"; import { isSelectedRegularFileError } from "../../filesystem/RegularFile.js"; import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js"; import { readStableArtifact } from "../../artifacts/readStableArtifact.js"; import { artifactFilePrimaryFacts, type ArtifactFilePrimaryFacts, - bindArtifactFileCleanup, artifactFileCancellationCause, } from "../../artifacts/ArtifactFileOperation.js"; import { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js"; -import { analysisErrorWithCleanupFailure } from "../../domain/analysisErrorCleanup.js"; -import { completedStableArtifactObservation } from "../../artifacts/StableArtifactObservation.js"; import type { KernelModulePort } from "../../application/binaryDiagnostics/KernelModulePort.js"; import type { ExecutionOptions } from "../../application/AnalysisProvider.js"; import { ArtifactOperationError } from "../../domain/artifactOperationError.js"; @@ -28,7 +29,6 @@ import { type InspectKernelModuleInput, } from "../../domain/native/kernelModule.js"; import { err, ok } from "../../domain/result.js"; -import { ProviderCleanupError } from "../../domain/providerCleanupError.js"; import { KERNEL_MODULE_PROVIDER } from "../../domain/investigationProviders.js"; import { BinaryFormatFailure, @@ -47,19 +47,11 @@ export class KernelModuleProvider implements KernelModulePort { try { await this.#resources.close(); } catch (cause: unknown) { - if ( - !(cause instanceof ArtifactReaderFailure) || - cause.cleanup === undefined - ) - throw cause; - const error = new ProviderCleanupError( + throw projectArtifactCloseFailure( + cause, KERNEL_MODULE_PROVIDER.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { cause, operation: OPERATION }, + OPERATION, ); - bindArtifactFileCleanup(cause, error); - throw error; } } @@ -236,17 +228,11 @@ const kernelModuleFailure = ( artifactFilePrimaryFacts(cause), input, ); - const observation = completedStableArtifactObservation(cause, input.path); - if (observation !== undefined) primary.retainPartialObservation(observation); - if (!(cause instanceof ArtifactReaderFailure) || cause.cleanup === undefined) - return primary; - const cleanup = new ProviderCleanupError( + return projectStableArtifactFailure( + cause, + primary, + input.path, KERNEL_MODULE_PROVIDER.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { operation: OPERATION, cause }, + OPERATION, ); - const error = analysisErrorWithCleanupFailure(primary, cleanup, OPERATION); - bindArtifactFileCleanup(cause, error); - return error; }; diff --git a/src/native/exports/NativeExportsProvider.ts b/src/native/exports/NativeExportsProvider.ts index bbd04773a8..e5682bc369 100644 --- a/src/native/exports/NativeExportsProvider.ts +++ b/src/native/exports/NativeExportsProvider.ts @@ -1,3 +1,7 @@ +import { + projectStableArtifactFailure, + projectArtifactCloseFailure, +} from "../../artifacts/StableArtifactFailure.js"; import { isSelectedRegularFileError } from "../../filesystem/RegularFile.js"; import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js"; import { readStableArtifact } from "../../artifacts/readStableArtifact.js"; @@ -5,12 +9,9 @@ import { artifactFilePrimaryFacts, type ArtifactFilePrimaryFacts, assertArtifactReadActive, - bindArtifactFileCleanup, artifactFileCancellationCause, } from "../../artifacts/ArtifactFileOperation.js"; import { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js"; -import { analysisErrorWithCleanupFailure } from "../../domain/analysisErrorCleanup.js"; -import { completedStableArtifactObservation } from "../../artifacts/StableArtifactObservation.js"; import type { NativeExportsPort } from "../../application/binaryDiagnostics/NativeExportsPort.js"; import type { ExecutionOptions } from "../../application/AnalysisProvider.js"; import { AnalysisError } from "../../domain/analysisErrorBase.js"; @@ -28,7 +29,6 @@ import { type InspectNativeExportsInput, } from "../../domain/native/nativeExports.js"; import { ProviderAdapterError } from "../../domain/providerAdapterError.js"; -import { ProviderCleanupError } from "../../domain/providerCleanupError.js"; import { err, ok } from "../../domain/result.js"; import { NATIVE_EXPORTS_PROVIDER } from "../../domain/investigationProviders.js"; import { @@ -48,19 +48,11 @@ export class NativeExportsProvider implements NativeExportsPort { try { await this.#resources.close(); } catch (cause: unknown) { - if ( - !(cause instanceof ArtifactReaderFailure) || - cause.cleanup === undefined - ) - throw cause; - const error = new ProviderCleanupError( + throw projectArtifactCloseFailure( + cause, NATIVE_EXPORTS_PROVIDER.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { cause, operation: OPERATION }, + OPERATION, ); - bindArtifactFileCleanup(cause, error); - throw error; } } @@ -203,17 +195,11 @@ const nativeExportsFailure = ( artifactFilePrimaryFacts(cause), input, ); - const observation = completedStableArtifactObservation(cause, input.path); - if (observation !== undefined) primary.retainPartialObservation(observation); - if (!(cause instanceof ArtifactReaderFailure) || cause.cleanup === undefined) - return primary; - const cleanup = new ProviderCleanupError( + return projectStableArtifactFailure( + cause, + primary, + input.path, NATIVE_EXPORTS_PROVIDER.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { operation: OPERATION, cause }, + OPERATION, ); - const error = analysisErrorWithCleanupFailure(primary, cleanup, OPERATION); - bindArtifactFileCleanup(cause, error); - return error; }; diff --git a/src/native/pe/PeCertificatesProvider.ts b/src/native/pe/PeCertificatesProvider.ts index 02571cce3d..c8687a05ca 100644 --- a/src/native/pe/PeCertificatesProvider.ts +++ b/src/native/pe/PeCertificatesProvider.ts @@ -1,15 +1,16 @@ +import { + projectStableArtifactFailure, + projectArtifactCloseFailure, +} from "../../artifacts/StableArtifactFailure.js"; import { isSelectedRegularFileError } from "../../filesystem/RegularFile.js"; import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js"; import { readStableArtifact } from "../../artifacts/readStableArtifact.js"; import { artifactFilePrimaryFacts, type ArtifactFilePrimaryFacts, - bindArtifactFileCleanup, artifactFileCancellationCause, } from "../../artifacts/ArtifactFileOperation.js"; import { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js"; -import { analysisErrorWithCleanupFailure } from "../../domain/analysisErrorCleanup.js"; -import { completedStableArtifactObservation } from "../../artifacts/StableArtifactObservation.js"; import type { PeCertificatesPort } from "../../application/binaryDiagnostics/PeCertificatesPort.js"; import type { ExecutionOptions } from "../../application/AnalysisProvider.js"; import { ArtifactOperationError } from "../../domain/artifactOperationError.js"; @@ -26,7 +27,6 @@ import { type InspectPeCertificatesInput, } from "../../domain/native/peCertificates.js"; import { err, ok } from "../../domain/result.js"; -import { ProviderCleanupError } from "../../domain/providerCleanupError.js"; import { PE_CERTIFICATES_PROVIDER } from "../../domain/investigationProviders.js"; import { parsePeCertificates } from "./PeCertificateParser.js"; @@ -41,19 +41,11 @@ export class PeCertificatesProvider implements PeCertificatesPort { try { await this.#resources.close(); } catch (cause: unknown) { - if ( - !(cause instanceof ArtifactReaderFailure) || - cause.cleanup === undefined - ) - throw cause; - const error = new ProviderCleanupError( + throw projectArtifactCloseFailure( + cause, PE_CERTIFICATES_PROVIDER.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { cause, operation: OPERATION }, + OPERATION, ); - bindArtifactFileCleanup(cause, error); - throw error; } } @@ -193,17 +185,11 @@ const peCertificatesFailure = ( artifactFilePrimaryFacts(cause), input, ); - const observation = completedStableArtifactObservation(cause, input.path); - if (observation !== undefined) primary.retainPartialObservation(observation); - if (!(cause instanceof ArtifactReaderFailure) || cause.cleanup === undefined) - return primary; - const cleanup = new ProviderCleanupError( + return projectStableArtifactFailure( + cause, + primary, + input.path, PE_CERTIFICATES_PROVIDER.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { operation: OPERATION, cause }, + OPERATION, ); - const error = analysisErrorWithCleanupFailure(primary, cleanup, OPERATION); - bindArtifactFileCleanup(cause, error); - return error; }; diff --git a/src/native/pe/PeImportsProvider.ts b/src/native/pe/PeImportsProvider.ts index bc8528a976..2e4cf5d452 100644 --- a/src/native/pe/PeImportsProvider.ts +++ b/src/native/pe/PeImportsProvider.ts @@ -1,3 +1,7 @@ +import { + projectStableArtifactFailure, + projectArtifactCloseFailure, +} from "../../artifacts/StableArtifactFailure.js"; import { isSelectedRegularFileError } from "../../filesystem/RegularFile.js"; import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js"; import { readStableArtifact } from "../../artifacts/readStableArtifact.js"; @@ -5,12 +9,9 @@ import { artifactFilePrimaryFacts, type ArtifactFilePrimaryFacts, assertArtifactReadActive, - bindArtifactFileCleanup, artifactFileCancellationCause, } from "../../artifacts/ArtifactFileOperation.js"; import { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js"; -import { analysisErrorWithCleanupFailure } from "../../domain/analysisErrorCleanup.js"; -import { completedStableArtifactObservation } from "../../artifacts/StableArtifactObservation.js"; import type { PeImportsPort } from "../../application/binaryDiagnostics/PeImportsPort.js"; import type { ExecutionOptions } from "../../application/AnalysisProvider.js"; import { AnalysisError } from "../../domain/analysisErrorBase.js"; @@ -32,7 +33,6 @@ import { type InspectPeImportsInput, } from "../../domain/native/peImports.js"; import { ProviderAdapterError } from "../../domain/providerAdapterError.js"; -import { ProviderCleanupError } from "../../domain/providerCleanupError.js"; import { err, ok } from "../../domain/result.js"; import { PE_IMPORTS_PROVIDER } from "../../domain/investigationProviders.js"; import { @@ -56,19 +56,11 @@ export class PeImportsProvider implements PeImportsPort { try { await this.#resources.close(); } catch (cause: unknown) { - if ( - !(cause instanceof ArtifactReaderFailure) || - cause.cleanup === undefined - ) - throw cause; - const error = new ProviderCleanupError( + throw projectArtifactCloseFailure( + cause, PE_IMPORTS_PROVIDER.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { cause, operation: OPERATION }, + OPERATION, ); - bindArtifactFileCleanup(cause, error); - throw error; } } @@ -246,17 +238,11 @@ const peImportsFailure = ( artifactFilePrimaryFacts(cause), input, ); - const observation = completedStableArtifactObservation(cause, input.path); - if (observation !== undefined) primary.retainPartialObservation(observation); - if (!(cause instanceof ArtifactReaderFailure) || cause.cleanup === undefined) - return primary; - const cleanup = new ProviderCleanupError( + return projectStableArtifactFailure( + cause, + primary, + input.path, PE_IMPORTS_PROVIDER.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { operation: OPERATION, cause }, + OPERATION, ); - const error = analysisErrorWithCleanupFailure(primary, cleanup, OPERATION); - bindArtifactFileCleanup(cause, error); - return error; }; diff --git a/src/native/pe/PeLoadConfigProvider.ts b/src/native/pe/PeLoadConfigProvider.ts index b88f93e7e1..7087a7e0ef 100644 --- a/src/native/pe/PeLoadConfigProvider.ts +++ b/src/native/pe/PeLoadConfigProvider.ts @@ -1,3 +1,7 @@ +import { + projectStableArtifactFailure, + projectArtifactCloseFailure, +} from "../../artifacts/StableArtifactFailure.js"; import { isSelectedRegularFileError } from "../../filesystem/RegularFile.js"; import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js"; import { readStableArtifact } from "../../artifacts/readStableArtifact.js"; @@ -5,15 +9,12 @@ import { artifactFilePrimaryFacts, type ArtifactFilePrimaryFacts, assertArtifactReadActive, - bindArtifactFileCleanup, artifactFileCancellationCause, } from "../../artifacts/ArtifactFileOperation.js"; import { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js"; -import { analysisErrorWithCleanupFailure } from "../../domain/analysisErrorCleanup.js"; import type { PeLoadConfigPort } from "../../application/binaryDiagnostics/PeLoadConfigPort.js"; import type { ExecutionOptions } from "../../application/AnalysisProvider.js"; import { AnalysisError } from "../../domain/analysisErrorBase.js"; -import { completedStableArtifactObservation } from "../../artifacts/StableArtifactObservation.js"; import { AnalysisAccessDeniedError, AnalysisArtifactChangedError, @@ -30,7 +31,6 @@ import { type InspectPeLoadConfigInput, } from "../../domain/native/peLoadConfig.js"; import { ProviderAdapterError } from "../../domain/providerAdapterError.js"; -import { ProviderCleanupError } from "../../domain/providerCleanupError.js"; import { err, ok } from "../../domain/result.js"; import { PE_LOAD_CONFIG_PROVIDER } from "../../domain/investigationProviders.js"; import { @@ -54,19 +54,11 @@ export class PeLoadConfigProvider implements PeLoadConfigPort { try { await this.#resources.close(); } catch (cause: unknown) { - if ( - !(cause instanceof ArtifactReaderFailure) || - cause.cleanup === undefined - ) - throw cause; - const error = new ProviderCleanupError( + throw projectArtifactCloseFailure( + cause, PE_LOAD_CONFIG_PROVIDER.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { cause, operation: OPERATION }, + OPERATION, ); - bindArtifactFileCleanup(cause, error); - throw error; } } @@ -223,17 +215,11 @@ const peLoadConfigFailure = ( artifactFilePrimaryFacts(cause), input, ); - const observation = completedStableArtifactObservation(cause, input.path); - if (observation !== undefined) primary.retainPartialObservation(observation); - if (!(cause instanceof ArtifactReaderFailure) || cause.cleanup === undefined) - return primary; - const cleanup = new ProviderCleanupError( + return projectStableArtifactFailure( + cause, + primary, + input.path, PE_LOAD_CONFIG_PROVIDER.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { operation: OPERATION, cause }, + OPERATION, ); - const error = analysisErrorWithCleanupFailure(primary, cleanup, OPERATION); - bindArtifactFileCleanup(cause, error); - return error; }; diff --git a/src/native/pe/PeResourcesProvider.ts b/src/native/pe/PeResourcesProvider.ts index 13dfaa6369..8bb56154c3 100644 --- a/src/native/pe/PeResourcesProvider.ts +++ b/src/native/pe/PeResourcesProvider.ts @@ -1,3 +1,7 @@ +import { + projectStableArtifactFailure, + projectArtifactCloseFailure, +} from "../../artifacts/StableArtifactFailure.js"; import { isSelectedRegularFileError } from "../../filesystem/RegularFile.js"; import { fixedNativeAllowance } from "../../domain/native/nativeDeadlines.js"; import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js"; @@ -5,12 +9,9 @@ import { readStableArtifact } from "../../artifacts/readStableArtifact.js"; import { artifactFilePrimaryFacts, type ArtifactFilePrimaryFacts, - bindArtifactFileCleanup, artifactFileCancellationCause, } from "../../artifacts/ArtifactFileOperation.js"; import { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js"; -import { analysisErrorWithCleanupFailure } from "../../domain/analysisErrorCleanup.js"; -import { completedStableArtifactObservation } from "../../artifacts/StableArtifactObservation.js"; import type { PeResourcesPort } from "../../application/binaryDiagnostics/PeResourcesPort.js"; import type { ExecutionOptions } from "../../application/AnalysisProvider.js"; import { ArtifactOperationError } from "../../domain/artifactOperationError.js"; @@ -27,7 +28,6 @@ import { type InspectPeResourcesInput, } from "../../domain/native/peResources.js"; import { err, ok } from "../../domain/result.js"; -import { ProviderCleanupError } from "../../domain/providerCleanupError.js"; import { PE_RESOURCES_PROVIDER } from "../../domain/investigationProviders.js"; import { parsePeResources, @@ -43,19 +43,11 @@ export class PeResourcesProvider implements PeResourcesPort { try { await this.#resources.close(); } catch (cause: unknown) { - if ( - !(cause instanceof ArtifactReaderFailure) || - cause.cleanup === undefined - ) - throw cause; - const error = new ProviderCleanupError( + throw projectArtifactCloseFailure( + cause, PE_RESOURCES_PROVIDER.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { cause, operation: "inspect_pe_resources" }, + "inspect_pe_resources", ); - bindArtifactFileCleanup(cause, error); - throw error; } } @@ -213,21 +205,11 @@ const peResourcesFailure = (cause: unknown, input: InspectPeResourcesInput) => { artifactFilePrimaryFacts(cause), input, ); - const observation = completedStableArtifactObservation(cause, input.path); - if (observation !== undefined) primary.retainPartialObservation(observation); - if (!(cause instanceof ArtifactReaderFailure) || cause.cleanup === undefined) - return primary; - const cleanup = new ProviderCleanupError( - PE_RESOURCES_PROVIDER.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { operation: "inspect_pe_resources", cause }, - ); - const error = analysisErrorWithCleanupFailure( + return projectStableArtifactFailure( + cause, primary, - cleanup, + input.path, + PE_RESOURCES_PROVIDER.id, "inspect_pe_resources", ); - bindArtifactFileCleanup(cause, error); - return error; }; diff --git a/src/python/PythonPycProvider.ts b/src/python/PythonPycProvider.ts index 56295d6fb8..18f7c0b278 100644 --- a/src/python/PythonPycProvider.ts +++ b/src/python/PythonPycProvider.ts @@ -1,15 +1,16 @@ +import { + projectStableArtifactFailure, + projectArtifactCloseFailure, +} from "../artifacts/StableArtifactFailure.js"; import { isSelectedRegularFileError } from "../filesystem/RegularFile.js"; import { artifactFileCancellationCause, artifactFilePrimaryFacts, type ArtifactFilePrimaryFacts, - bindArtifactFileCleanup, } from "../artifacts/ArtifactFileOperation.js"; import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js"; import { readStableArtifact } from "../artifacts/readStableArtifact.js"; import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js"; -import { analysisErrorWithCleanupFailure } from "../domain/analysisErrorCleanup.js"; -import { completedStableArtifactObservation } from "../artifacts/StableArtifactObservation.js"; import type { PythonPycPort } from "../application/python/PythonPycPort.js"; import type { ExecutionOptions } from "../application/AnalysisProvider.js"; import { AnalysisError } from "../domain/analysisErrorBase.js"; @@ -26,7 +27,6 @@ import { type InspectPythonPycInput, } from "../domain/python/pythonPyc.js"; import { ProviderAdapterError } from "../domain/providerAdapterError.js"; -import { ProviderCleanupError } from "../domain/providerCleanupError.js"; import { err, ok } from "../domain/result.js"; import { PythonPycFormatFailure, @@ -165,19 +165,13 @@ const failureForPrimary = ( const failureFor = (cause: unknown, input: InspectPythonPycInput) => { const primary = failureForPrimary(artifactFilePrimaryFacts(cause), input); - const observation = completedStableArtifactObservation(cause, input.path); - if (observation !== undefined) primary.retainPartialObservation(observation); - if (!(cause instanceof ArtifactReaderFailure) || cause.cleanup === undefined) - return primary; - const cleanup = new ProviderCleanupError( + return projectStableArtifactFailure( + cause, + primary, + input.path, PYTHON_PYC_PROVIDER_IDENTITY.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { operation: OPERATION, cause }, + OPERATION, ); - const error = analysisErrorWithCleanupFailure(primary, cleanup, OPERATION); - bindArtifactFileCleanup(cause, error); - return error; }; /** Portable parser over one bounded, identity-checked regular file. */ @@ -189,19 +183,11 @@ export class PythonPycProvider implements PythonPycPort { try { await this.#resources.close(); } catch (cause: unknown) { - if ( - !(cause instanceof ArtifactReaderFailure) || - cause.cleanup === undefined - ) - throw cause; - const error = new ProviderCleanupError( + throw projectArtifactCloseFailure( + cause, PYTHON_PYC_PROVIDER_IDENTITY.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { cause, operation: OPERATION }, + OPERATION, ); - bindArtifactFileCleanup(cause, error); - throw error; } } diff --git a/src/rust/RustBinaryProvider.ts b/src/rust/RustBinaryProvider.ts index e32a80ee6d..c6712c5c2a 100644 --- a/src/rust/RustBinaryProvider.ts +++ b/src/rust/RustBinaryProvider.ts @@ -1,16 +1,16 @@ +import { + projectStableArtifactFailure, + projectArtifactCloseFailure, +} from "../artifacts/StableArtifactFailure.js"; import { isSelectedRegularFileError } from "../filesystem/RegularFile.js"; import { readStableArtifact } from "../artifacts/readStableArtifact.js"; -import { completedStableArtifactObservation } from "../artifacts/StableArtifactObservation.js"; import { artifactFilePrimaryFacts, type ArtifactFilePrimaryFacts, artifactFileCancellationCause, assertArtifactReadActive, - bindArtifactFileCleanup, } from "../artifacts/ArtifactFileOperation.js"; import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js"; -import { analysisErrorWithCleanupFailure } from "../domain/analysisErrorCleanup.js"; -import { ProviderCleanupError } from "../domain/providerCleanupError.js"; import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js"; import type { ExecutionOptions } from "../application/AnalysisProvider.js"; import type { RustBinaryPort } from "../application/rust/RustBinaryPort.js"; @@ -62,19 +62,11 @@ export class RustBinaryProvider implements RustBinaryPort { try { await this.#resources.close(); } catch (cause: unknown) { - if ( - !(cause instanceof ArtifactReaderFailure) || - cause.cleanup === undefined - ) - throw cause; - const error = new ProviderCleanupError( + throw projectArtifactCloseFailure( + cause, RUST_BINARY_PROVIDER_IDENTITY.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { cause, operation: OPERATION }, + OPERATION, ); - bindArtifactFileCleanup(cause, error); - throw error; } } @@ -286,19 +278,13 @@ const failureForPrimary = ( const failureFor = (cause: unknown, path: string): AnalysisError => { const primary = failureForPrimary(artifactFilePrimaryFacts(cause), path); - const observation = completedStableArtifactObservation(cause, path); - if (observation !== undefined) primary.retainPartialObservation(observation); - if (!(cause instanceof ArtifactReaderFailure) || cause.cleanup === undefined) - return primary; - const cleanup = new ProviderCleanupError( + return projectStableArtifactFailure( + cause, + primary, + path, RUST_BINARY_PROVIDER_IDENTITY.id, - cause.cleanup.resources, - { reason: cause.cleanup.reason }, - { cause, operation: OPERATION }, + OPERATION, ); - const error = analysisErrorWithCleanupFailure(primary, cleanup, OPERATION); - bindArtifactFileCleanup(cause, error); - return error; }; const artifactFailure = ( diff --git a/tests/boundary/filesystem/pythonBeamCleanup.test.ts b/tests/boundary/filesystem/pythonBeamCleanup.test.ts index 4d8bec7057..808abadada 100644 --- a/tests/boundary/filesystem/pythonBeamCleanup.test.ts +++ b/tests/boundary/filesystem/pythonBeamCleanup.test.ts @@ -12,6 +12,9 @@ import { PythonPycService } from "../../../src/application/python/PythonPycServi import { BeamService } from "../../../src/application/erlang/BeamService.js"; import { BeamProvider } from "../../../src/erlang/BeamProvider.js"; import { erlangBeamFixture } from "../../../src/erlang/Beam.fixture.js"; +import { DevicetreeService } from "../../../src/application/binaryDiagnostics/DevicetreeService.js"; +import { DevicetreeProvider } from "../../../src/firmware/devicetree/DevicetreeProvider.js"; +import { devicetreeFixture } from "../../../src/firmware/devicetree/Devicetree.fixture.js"; const injected = vi.hoisted(() => ({ opened: undefined as @@ -49,6 +52,12 @@ const providers = [ bytes: erlangBeamFixture(), create: () => new BeamService(new BeamProvider()), }, + { + name: "devicetree", + operation: "inspect_devicetree", + bytes: devicetreeFixture(), + create: () => new DevicetreeService(new DevicetreeProvider()), + }, ]; const outcomes = [ @@ -152,3 +161,47 @@ it.each( } }, ); + +it.each([{ source: "BEAM successful close" }, null])( + "preserves BEAM cancellation during successful close with reason %j", + async (reason) => { + const root = await createTestTempDirectory("rea-beam-successful-close-"); + const path = join(root, "selected.beam"); + const bytes = erlangBeamFixture(); + await fileSystem.writeFile(path, bytes); + const controller = new AbortController(); + const handles: FileHandle[] = []; + injected.opened = (openedPath, handle) => { + if (String(openedPath) !== path) return; + handles.push(handle); + const close = handle.close.bind(handle); + vi.spyOn(handle, "close").mockImplementation(async () => { + controller.abort(reason); + await close(); + }); + }; + const service = new BeamService(new BeamProvider()); + try { + const result = await service.inspect( + { path }, + { signal: controller.signal }, + ); + if (result.ok) throw new Error("Expected finalization cancellation"); + expect(result.error._tag).toBe("AnalysisCancelledError"); + expect(result.error.cause).toBe(reason); + expect(result.error.cleanupIncomplete).toBe(false); + expect(result.error.partialObservation).toEqual({ + kind: "file-snapshot-hash", + path, + sha256: createHash("sha256").update(bytes).digest("hex"), + bytes: bytes.length, + }); + expect(handles).toHaveLength(1); + expect(handles[0]?.fd).toBe(-1); + await service.close(); + expect(handles).toHaveLength(1); + } finally { + await service.close(); + } + }, +);