From 7a2cd1ea0a63e8c44166860b7ee9c7a1aa43492b Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Tue, 13 Oct 2026 13:12:06 +0800 Subject: [PATCH] fix(domain): preserve derived byte counts and uncertain identity --- .../ManagedReconstructionService.test.ts | 20 +++++++++++++ src/domain/functionComparison.test.ts | 28 +++++++++++++++++++ src/domain/functionComparisonNormalization.ts | 21 ++++++-------- src/domain/managed/managedReconstruction.ts | 3 +- 4 files changed, 59 insertions(+), 13 deletions(-) diff --git a/src/application/managed/ManagedReconstructionService.test.ts b/src/application/managed/ManagedReconstructionService.test.ts index 56b9d94428..793951ae9f 100644 --- a/src/application/managed/ManagedReconstructionService.test.ts +++ b/src/application/managed/ManagedReconstructionService.test.ts @@ -22,6 +22,26 @@ const exampleMethod = () => { }; describe("managed decompiler reconstruction import", () => { + it("reports UTF-8 byte totals while preserving UTF-16 text lengths", () => { + const text = 'return "é"; // 😀'; + const method = exampleMethod(); + const result = importManagedReconstructionEvidence({ + ...MANAGED_RECONSTRUCTION_IMPORT_EXAMPLE, + methods: [ + { + ...method, + reconstruction: { ...method.reconstruction, text }, + }, + ], + }); + if (!result.ok) throw result.error; + + expect(result.value.normalized_result).toMatchObject({ + summary: { total_text_bytes: 20 }, + methods: [{ reconstruction: { text, text_length: 17 } }], + }); + }); + it("rejects reconstruction text hash drift", () => { expect(() => importManagedReconstruction({ diff --git a/src/domain/functionComparison.test.ts b/src/domain/functionComparison.test.ts index acb505dac9..e03a277489 100644 --- a/src/domain/functionComparison.test.ts +++ b/src/domain/functionComparison.test.ts @@ -119,6 +119,34 @@ const observe = ( ); describe("function comparison normalized identity", () => { + it("keeps differing generated names ambiguous and stable names mismatched", () => { + const named = (base: "0x1000" | "0x2000", name: string) => + functionDossierSchema.parse({ + ...dossier("return 0;", base), + procedure: { + ...procedure(base, name), + signature: "int helper(void)", + locals: [], + }, + }); + const generated = compareFunctions( + observe("1", named("0x1000", "sub_1000")), + observe("2", named("0x2000", "sub_2000")), + ); + expect(generated.function_match.status).toBe("ambiguous"); + expect(generated.status).toBe("unknown"); + expect( + generated.dimensions.find(({ dimension }) => dimension === "identity"), + ).toMatchObject({ status: "unknown" }); + + const stable = compareFunctions( + observe("3", named("0x1000", "firstFunction")), + observe("4", named("0x2000", "secondFunction")), + ); + expect(stable.function_match.status).toBe("mismatched"); + expect(stable.status).toBe("changed"); + }); + it.each(["sub_deallocate", "sub_deadbeef_handler", "fcn.dispatch"])( "recognizes the explicit symbol %s without a generated-name prefix false positive", (name) => { diff --git a/src/domain/functionComparisonNormalization.ts b/src/domain/functionComparisonNormalization.ts index a676e1e491..5282272e14 100644 --- a/src/domain/functionComparisonNormalization.ts +++ b/src/domain/functionComparisonNormalization.ts @@ -16,24 +16,21 @@ export const functionMatch = ( left: FunctionSnapshot, right: FunctionSnapshot, ): FunctionComparisonResult["function_match"] => { - if ( - !isAutoName(left.dossier.procedure.name) && - left.dossier.procedure.name === right.dossier.procedure.name - ) + const leftName = left.dossier.procedure.name; + const rightName = right.dossier.procedure.name; + const stableNames = !isAutoName(leftName) && !isAutoName(rightName); + if (stableNames && leftName === rightName) return { status: "matched", method: "symbol", - left_name: left.dossier.procedure.name, - right_name: right.dossier.procedure.name, + left_name: leftName, + right_name: rightName, }; return { - status: - left.dossier.procedure.name === right.dossier.procedure.name - ? "ambiguous" - : "mismatched", + status: stableNames ? "mismatched" : "ambiguous", method: "explicit", - left_name: left.dossier.procedure.name, - right_name: right.dossier.procedure.name, + left_name: leftName, + right_name: rightName, }; }; diff --git a/src/domain/managed/managedReconstruction.ts b/src/domain/managed/managedReconstruction.ts index e10be0ffd3..9600166aec 100644 --- a/src/domain/managed/managedReconstruction.ts +++ b/src/domain/managed/managedReconstruction.ts @@ -204,7 +204,8 @@ const buildReconstructionSummary = ( ({ reconstruction }) => reconstruction.kind === "semantic-pseudocode", ).length, total_text_bytes: methods.reduce( - (total, { reconstruction }) => total + reconstruction.text_length, + (total, { reconstruction }) => + total + Buffer.byteLength(reconstruction.text, "utf8"), 0, ), });