From 4c50d85b423343ec67f1913749556e31d68aade7 Mon Sep 17 00:00:00 2001 From: Don Syme Date: Tue, 3 Nov 2026 18:34:49 +0000 Subject: [PATCH] Dispatch fingerprint callbacks only to matching source lines Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/entry-points.js | 11 +++-- src/fingerprints.test.ts | 49 +++++++++++++++++++ src/fingerprints.ts | 17 ++++--- .../fingerprint-callback-dispatch.md | 1 + 4 files changed, 66 insertions(+), 12 deletions(-) create mode 100644 unreleased-change-notes/fingerprint-callback-dispatch.md diff --git a/lib/entry-points.js b/lib/entry-points.js index 0e179f3fc7..eb00bf4c38 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -187830,16 +187830,17 @@ async function addFingerprints(sarifLog, sourceRoot, logger2) { continue; } if (!callbacksByFile[filepath]) { - callbacksByFile[filepath] = []; + callbacksByFile[filepath] = /* @__PURE__ */ new Map(); } - callbacksByFile[filepath].push( - locationUpdateCallback(result, primaryLocation, logger2) - ); + const startLine = primaryLocation.physicalLocation.region.startLine; + const callbacks = callbacksByFile[filepath].get(startLine) ?? []; + callbacks.push(locationUpdateCallback(result, primaryLocation, logger2)); + callbacksByFile[filepath].set(startLine, callbacks); } } for (const [filepath, callbacks] of Object.entries(callbacksByFile)) { const teeCallback = function(lineNumber, hashValue) { - for (const c of Object.values(callbacks)) { + for (const c of callbacks.get(lineNumber) ?? []) { c(lineNumber, hashValue); } }; diff --git a/src/fingerprints.test.ts b/src/fingerprints.test.ts index 6b480a777f..8f2672612a 100644 --- a/src/fingerprints.test.ts +++ b/src/fingerprints.test.ts @@ -223,6 +223,55 @@ test("addFingerprints", async (t) => { ); }); +test("addFingerprints dispatches to every result on the matching line", async (t) => { + await util.withTmpDir(async (tmpDir) => { + const filepath = path.join(tmpDir, "source.c"); + fs.writeFileSync(filepath, "first\nsecond\nthird\n"); + const hashes = new Map(); + await fingerprints.hash((line, hash) => hashes.set(line, hash), filepath); + const lines = [1, 2, 2, 3, 999]; + const results: sarif.Result[] = lines.map((startLine) => ({ + message: { text: "Finding" }, + locations: [ + { + physicalLocation: { + artifactLocation: { uri: "source.c" }, + region: { startLine }, + }, + }, + ], + })); + results[0].partialFingerprints = { + primaryLocationLineHash: "existing-fingerprint", + }; + const logger = new RecordingLogger(); + await fingerprints.addFingerprints( + { + runs: [{ tool: { driver: { name: "test" } }, results }], + }, + tmpDir, + logger, + ); + + t.is( + results[0].partialFingerprints?.primaryLocationLineHash, + "existing-fingerprint", + ); + t.true( + logger.hasMessage( + "existing inconsistent fingerprint value existing-fingerprint", + ), + ); + for (const index of [1, 2, 3]) { + t.is( + results[index].partialFingerprints?.primaryLocationLineHash, + hashes.get(lines[index]), + ); + } + t.is(results[4].partialFingerprints, undefined); + }); +}); + test("missingRegions", async (t) => { // Run an end-to-end test on a test file const input = JSON.parse( diff --git a/src/fingerprints.ts b/src/fingerprints.ts index 14cf30e63f..fb6e63922c 100644 --- a/src/fingerprints.ts +++ b/src/fingerprints.ts @@ -266,7 +266,9 @@ export async function addFingerprints( ); // Gather together results for the same file and construct // callbacks to accept hashes for that file and update the location - const callbacksByFile: { [filename: string]: hashCallback[] } = {}; + const callbacksByFile: { + [filename: string]: Map; + } = {}; for (const run of sarifLog.runs || []) { // We may need the list of artifacts to resolve against const artifacts = run.artifacts || []; @@ -298,19 +300,20 @@ export async function addFingerprints( continue; } if (!callbacksByFile[filepath]) { - callbacksByFile[filepath] = []; + callbacksByFile[filepath] = new Map(); } - callbacksByFile[filepath].push( - locationUpdateCallback(result, primaryLocation, logger), - ); + const startLine = primaryLocation.physicalLocation.region.startLine; + const callbacks = callbacksByFile[filepath].get(startLine) ?? []; + callbacks.push(locationUpdateCallback(result, primaryLocation, logger)); + callbacksByFile[filepath].set(startLine, callbacks); } } // Now hash each file that was found for (const [filepath, callbacks] of Object.entries(callbacksByFile)) { - // A callback that forwards the hash to all other callbacks for that file + // Forward each hash only to results on that line. const teeCallback = function (lineNumber: number, hashValue: string) { - for (const c of Object.values(callbacks)) { + for (const c of callbacks.get(lineNumber) ?? []) { c(lineNumber, hashValue); } }; diff --git a/unreleased-change-notes/fingerprint-callback-dispatch.md b/unreleased-change-notes/fingerprint-callback-dispatch.md new file mode 100644 index 0000000000..57409469b7 --- /dev/null +++ b/unreleased-change-notes/fingerprint-callback-dispatch.md @@ -0,0 +1 @@ +- Improved SARIF fingerprint generation for source files with many results without changing the generated fingerprints.