diff --git a/src/application/reverse/ReverseEngineeringService.ts b/src/application/reverse/ReverseEngineeringService.ts index 74dada85d3..6bcfd534e5 100644 --- a/src/application/reverse/ReverseEngineeringService.ts +++ b/src/application/reverse/ReverseEngineeringService.ts @@ -77,6 +77,7 @@ interface ReverseEngineeringCommandRunner { readonly stderrInvalidUtf8?: boolean; readonly stdoutBytes?: number; readonly stderrBytes?: number; + readonly outputTruncated?: boolean; }>; } @@ -468,6 +469,7 @@ export class ReverseEngineeringService { stderrBytes = output.stderrBytes ?? Buffer.byteLength(stderr); stdoutInvalidUtf8 = output.stdoutInvalidUtf8 === true; stderrInvalidUtf8 = output.stderrInvalidUtf8 === true; + outputTruncated = output.outputTruncated === true; } catch (cause) { cleanupOwner = cause instanceof OwnedOutputFailure ? cause.cleanupOwner : undefined; @@ -734,7 +736,7 @@ export class ReverseEngineeringService { : []), ...(outputTruncated ? [ - "The provider command ended before completion could be confirmed; captured output is partial.", + "Captured provider output is partial because bytes were omitted; process completion is reported separately.", ] : []), ...(signalName === null @@ -924,6 +926,7 @@ const runOwnedCommandOutput = return { stdout: snapshot.stdout.text, stderr: snapshot.stderr.text, + outputTruncated: snapshot.diagnosticTruncated === true, stdoutInvalidUtf8: snapshot.stdout.invalidUtf8 === true, stderrInvalidUtf8: snapshot.stderr.invalidUtf8 === true, stdoutBytes: snapshot.stdout.bytes, diff --git a/tests/composition/reverseEngineeringEdgeCases.test.ts b/tests/composition/reverseEngineeringEdgeCases.test.ts index 5dfc282187..95e0d14028 100644 --- a/tests/composition/reverseEngineeringEdgeCases.test.ts +++ b/tests/composition/reverseEngineeringEdgeCases.test.ts @@ -1051,3 +1051,31 @@ it.each([ } }, ); + +it.each(["objdump", "rizin"] as const)( + "preserves successful %s output-loss facts independently of process completion", + async (provider) => { + const { directory, path } = await createArtifact(); + const service = new ReverseEngineeringService({ + runOwned: async () => providerSnapshot({ diagnosticTruncated: true }), + }); + try { + const result = + provider === "objdump" + ? await service.inspectWithObjdump({ path, operation: "symbols" }) + : await service.executeRizinCommand({ path, command: "iI" }); + if (!result.ok) throw result.error; + expect(result.value.normalized_result).toMatchObject({ + exit_code: 0, + output_truncated: true, + completion_status: "complete", + }); + expect(result.value.limitations).toContain( + "Captured provider output is partial because bytes were omitted; process completion is reported separately.", + ); + } finally { + await service.close(); + await rm(directory, { recursive: true, force: true }); + } + }, +);