diff --git a/lib/analyze-action.js b/lib/analyze-action.js index cf0eef7f6..685b28513 100644 --- a/lib/analyze-action.js +++ b/lib/analyze-action.js @@ -105896,6 +105896,21 @@ var safeDump = renamed("safeDump", "dump"); // src/util.ts var semver = __toESM(require_semver2()); + +// src/sarif/index.ts +function getToolNames(sarif) { + const toolNames = {}; + for (const run2 of sarif.runs || []) { + const tool = run2.tool || {}; + const driver = tool.driver || {}; + if (typeof driver.name === "string" && driver.name.length > 0) { + toolNames[driver.name] = true; + } + } + return Object.keys(toolNames); +} + +// src/util.ts var BASE_DATABASE_OIDS_FILE_NAME = "base-database-oids.json"; var BROKEN_VERSIONS = ["0.0.0-20211207"]; var GITHUB_DOTCOM_URL = "https://github.com"; @@ -105916,17 +105931,6 @@ function getExtraOptionsEnvParam() { ); } } -function getToolNames(sarif) { - const toolNames = {}; - for (const run2 of sarif.runs || []) { - const tool = run2.tool || {}; - const driver = tool.driver || {}; - if (typeof driver.name === "string" && driver.name.length > 0) { - toolNames[driver.name] = true; - } - } - return Object.keys(toolNames); -} function getSystemReservedMemoryMegaBytes(totalMemoryMegaBytes, platform2) { const fixedAmount = 1024 * (platform2 === "win32" ? 1.5 : 1); const scaledAmount = getReservedRamScaleFactor() * Math.max(totalMemoryMegaBytes - 8 * 1024, 0); diff --git a/lib/init-action-post.js b/lib/init-action-post.js index 000e55c0a..f0bd4ddd0 100644 --- a/lib/init-action-post.js +++ b/lib/init-action-post.js @@ -164000,6 +164000,19 @@ var semver = __toESM(require_semver2()); var maximumVersion = "3.20"; var minimumVersion = "3.14"; +// src/sarif/index.ts +function getToolNames(sarif) { + const toolNames = {}; + for (const run3 of sarif.runs || []) { + const tool = run3.tool || {}; + const driver = tool.driver || {}; + if (typeof driver.name === "string" && driver.name.length > 0) { + toolNames[driver.name] = true; + } + } + return Object.keys(toolNames); +} + // src/util.ts var BASE_DATABASE_OIDS_FILE_NAME = "base-database-oids.json"; var BROKEN_VERSIONS = ["0.0.0-20211207"]; @@ -164020,17 +164033,6 @@ function getExtraOptionsEnvParam() { ); } } -function getToolNames(sarif) { - const toolNames = {}; - for (const run3 of sarif.runs || []) { - const tool = run3.tool || {}; - const driver = tool.driver || {}; - if (typeof driver.name === "string" && driver.name.length > 0) { - toolNames[driver.name] = true; - } - } - return Object.keys(toolNames); -} function getCodeQLDatabasePath(config, language) { return path.resolve(config.dbLocation, language); } diff --git a/lib/upload-lib.js b/lib/upload-lib.js index eadfa5063..711e28744 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -105913,6 +105913,21 @@ var safeDump = renamed("safeDump", "dump"); // src/util.ts var semver = __toESM(require_semver2()); + +// src/sarif/index.ts +function getToolNames(sarif) { + const toolNames = {}; + for (const run of sarif.runs || []) { + const tool = run.tool || {}; + const driver = tool.driver || {}; + if (typeof driver.name === "string" && driver.name.length > 0) { + toolNames[driver.name] = true; + } + } + return Object.keys(toolNames); +} + +// src/util.ts var BASE_DATABASE_OIDS_FILE_NAME = "base-database-oids.json"; var BROKEN_VERSIONS = ["0.0.0-20211207"]; var GITHUB_DOTCOM_URL = "https://github.com"; @@ -105932,17 +105947,6 @@ function getExtraOptionsEnvParam() { ); } } -function getToolNames(sarif) { - const toolNames = {}; - for (const run of sarif.runs || []) { - const tool = run.tool || {}; - const driver = tool.driver || {}; - if (typeof driver.name === "string" && driver.name.length > 0) { - toolNames[driver.name] = true; - } - } - return Object.keys(toolNames); -} function getCodeQLDatabasePath(config, language) { return path.resolve(config.dbLocation, language); } diff --git a/lib/upload-sarif-action.js b/lib/upload-sarif-action.js index 91ddb60d5..b46e3b6b3 100644 --- a/lib/upload-sarif-action.js +++ b/lib/upload-sarif-action.js @@ -105887,6 +105887,21 @@ var safeDump = renamed("safeDump", "dump"); // src/util.ts var semver = __toESM(require_semver2()); + +// src/sarif/index.ts +function getToolNames(sarif) { + const toolNames = {}; + for (const run2 of sarif.runs || []) { + const tool = run2.tool || {}; + const driver = tool.driver || {}; + if (typeof driver.name === "string" && driver.name.length > 0) { + toolNames[driver.name] = true; + } + } + return Object.keys(toolNames); +} + +// src/util.ts var BASE_DATABASE_OIDS_FILE_NAME = "base-database-oids.json"; var BROKEN_VERSIONS = ["0.0.0-20211207"]; var GITHUB_DOTCOM_URL = "https://github.com"; @@ -105906,17 +105921,6 @@ function getExtraOptionsEnvParam() { ); } } -function getToolNames(sarif) { - const toolNames = {}; - for (const run2 of sarif.runs || []) { - const tool = run2.tool || {}; - const driver = tool.driver || {}; - if (typeof driver.name === "string" && driver.name.length > 0) { - toolNames[driver.name] = true; - } - } - return Object.keys(toolNames); -} function getCodeQLDatabasePath(config, language) { return path.resolve(config.dbLocation, language); } diff --git a/src/sarif/index.test.ts b/src/sarif/index.test.ts new file mode 100644 index 000000000..3d41f848b --- /dev/null +++ b/src/sarif/index.test.ts @@ -0,0 +1,88 @@ +import * as fs from "fs"; + +import test from "ava"; + +import { + getRecordingLogger, + LoggedMessage, + setupTests, +} from "../testing-utils"; + +import { + fixInvalidNotifications, + getToolNames, + SarifLocation, + type SarifFile, +} from "."; + +setupTests(test); + +test("getToolNames", (t) => { + const input = fs.readFileSync( + `${__dirname}/../../src/testdata/tool-names.sarif`, + "utf8", + ); + const toolNames = getToolNames(JSON.parse(input) as SarifFile); + t.deepEqual(toolNames, ["CodeQL command-line toolchain", "ESLint"]); +}); + +function createMockSarifWithNotification( + locations: SarifLocation[], +): SarifFile { + return { + runs: [ + { + tool: { + driver: { + name: "CodeQL", + }, + }, + invocations: [ + { + toolExecutionNotifications: [ + { + locations, + }, + ], + }, + ], + }, + ], + }; +} + +const stubLocation: SarifLocation = { + physicalLocation: { + artifactLocation: { + uri: "file1", + }, + }, +}; + +test("fixInvalidNotifications leaves notifications with unique locations alone", (t) => { + const messages: LoggedMessage[] = []; + const result = fixInvalidNotifications( + createMockSarifWithNotification([stubLocation]), + getRecordingLogger(messages), + ); + t.deepEqual(result, createMockSarifWithNotification([stubLocation])); + t.is(messages.length, 1); + t.deepEqual(messages[0], { + type: "debug", + message: "No duplicate locations found in SARIF notification objects.", + }); +}); + +test("fixInvalidNotifications removes duplicate locations", (t) => { + const messages: LoggedMessage[] = []; + const result = fixInvalidNotifications( + createMockSarifWithNotification([stubLocation, stubLocation]), + getRecordingLogger(messages), + ); + t.deepEqual(result, createMockSarifWithNotification([stubLocation])); + t.is(messages.length, 1); + t.deepEqual(messages[0], { + type: "info", + message: "Removed 1 duplicate locations from SARIF notification objects.", + }); +}); diff --git a/src/sarif/index.ts b/src/sarif/index.ts new file mode 100644 index 000000000..6dab84391 --- /dev/null +++ b/src/sarif/index.ts @@ -0,0 +1,168 @@ +import { Logger } from "../logging"; + +export interface SarifLocation { + physicalLocation?: { + artifactLocation?: { + uri?: string; + }; + }; +} + +export interface SarifNotification { + locations?: SarifLocation[]; +} + +export interface SarifInvocation { + toolExecutionNotifications?: SarifNotification[]; +} + +export interface SarifResult { + ruleId?: string; + rule?: { + id?: string; + }; + message?: { + text?: string; + }; + locations: Array<{ + physicalLocation: { + artifactLocation: { + uri: string; + }; + region?: { + startLine?: number; + }; + }; + }>; + relatedLocations?: Array<{ + physicalLocation: { + artifactLocation: { + uri: string; + }; + region?: { + startLine?: number; + }; + }; + }>; + partialFingerprints: { + primaryLocationLineHash?: string; + }; +} + +export interface SarifRun { + tool?: { + driver?: { + guid?: string; + name?: string; + fullName?: string; + semanticVersion?: string; + version?: string; + }; + }; + automationDetails?: { + id?: string; + }; + artifacts?: string[]; + invocations?: SarifInvocation[]; + results?: SarifResult[]; +} + +export interface SarifFile { + version?: string | null; + runs: SarifRun[]; +} + +/** + * Get the array of all the tool names contained in the given sarif contents. + * + * Returns an array of unique string tool names. + */ +export function getToolNames(sarif: SarifFile): string[] { + const toolNames = {}; + + for (const run of sarif.runs || []) { + const tool = run.tool || {}; + const driver = tool.driver || {}; + if (typeof driver.name === "string" && driver.name.length > 0) { + toolNames[driver.name] = true; + } + } + + return Object.keys(toolNames); +} + +export function removeDuplicateLocations( + locations: SarifLocation[], +): SarifLocation[] { + const newJsonLocations = new Set(); + return locations.filter((location) => { + const jsonLocation = JSON.stringify(location); + if (!newJsonLocations.has(jsonLocation)) { + newJsonLocations.add(jsonLocation); + return true; + } + return false; + }); +} + +export function fixInvalidNotifications( + sarif: SarifFile, + logger: Logger, +): SarifFile { + if (!Array.isArray(sarif.runs)) { + return sarif; + } + + // Ensure that the array of locations for each SARIF notification contains unique locations. + // This is a workaround for a bug in the CodeQL CLI that causes duplicate locations to be + // emitted in some cases. + let numDuplicateLocationsRemoved = 0; + + const newSarif = { + ...sarif, + runs: sarif.runs.map((run) => { + if ( + run.tool?.driver?.name !== "CodeQL" || + !Array.isArray(run.invocations) + ) { + return run; + } + return { + ...run, + invocations: run.invocations.map((invocation) => { + if (!Array.isArray(invocation.toolExecutionNotifications)) { + return invocation; + } + return { + ...invocation, + toolExecutionNotifications: + invocation.toolExecutionNotifications.map((notification) => { + if (!Array.isArray(notification.locations)) { + return notification; + } + const newLocations = removeDuplicateLocations( + notification.locations, + ); + numDuplicateLocationsRemoved += + notification.locations.length - newLocations.length; + return { + ...notification, + locations: newLocations, + }; + }), + }; + }), + }; + }), + }; + + if (numDuplicateLocationsRemoved > 0) { + logger.info( + `Removed ${numDuplicateLocationsRemoved} duplicate locations from SARIF notification ` + + "objects.", + ); + } else { + logger.debug("No duplicate locations found in SARIF notification objects."); + } + return newSarif; +} diff --git a/src/util.test.ts b/src/util.test.ts index 7b6850018..a7e49d470 100644 --- a/src/util.test.ts +++ b/src/util.test.ts @@ -10,20 +10,11 @@ import * as sinon from "sinon"; import * as api from "./api-client"; import { EnvVar } from "./environment"; import { getRunnerLogger } from "./logging"; -import { getRecordingLogger, LoggedMessage, setupTests } from "./testing-utils"; +import { setupTests } from "./testing-utils"; import * as util from "./util"; setupTests(test); -test("getToolNames", (t) => { - const input = fs.readFileSync( - `${__dirname}/../src/testdata/tool-names.sarif`, - "utf8", - ); - const toolNames = util.getToolNames(JSON.parse(input) as util.SarifFile); - t.deepEqual(toolNames, ["CodeQL command-line toolchain", "ESLint"]); -}); - const GET_MEMORY_FLAG_TESTS = [ { input: undefined, @@ -368,67 +359,6 @@ test("waitForResultWithTimeLimit doesn't call callback if promise resolves", asy t.deepEqual(result, 99); }); -function createMockSarifWithNotification( - locations: util.SarifLocation[], -): util.SarifFile { - return { - runs: [ - { - tool: { - driver: { - name: "CodeQL", - }, - }, - invocations: [ - { - toolExecutionNotifications: [ - { - locations, - }, - ], - }, - ], - }, - ], - }; -} - -const stubLocation: util.SarifLocation = { - physicalLocation: { - artifactLocation: { - uri: "file1", - }, - }, -}; - -test("fixInvalidNotifications leaves notifications with unique locations alone", (t) => { - const messages: LoggedMessage[] = []; - const result = util.fixInvalidNotifications( - createMockSarifWithNotification([stubLocation]), - getRecordingLogger(messages), - ); - t.deepEqual(result, createMockSarifWithNotification([stubLocation])); - t.is(messages.length, 1); - t.deepEqual(messages[0], { - type: "debug", - message: "No duplicate locations found in SARIF notification objects.", - }); -}); - -test("fixInvalidNotifications removes duplicate locations", (t) => { - const messages: LoggedMessage[] = []; - const result = util.fixInvalidNotifications( - createMockSarifWithNotification([stubLocation, stubLocation]), - getRecordingLogger(messages), - ); - t.deepEqual(result, createMockSarifWithNotification([stubLocation])); - t.is(messages.length, 1); - t.deepEqual(messages[0], { - type: "info", - message: "Removed 1 duplicate locations from SARIF notification objects.", - }); -}); - function formatGitHubVersion(version: util.GitHubVersion): string { switch (version.type) { case util.GitHubVariant.DOTCOM: diff --git a/src/util.ts b/src/util.ts index 823291a0a..60def1d82 100644 --- a/src/util.ts +++ b/src/util.ts @@ -17,6 +17,8 @@ import { EnvVar } from "./environment"; import { Language } from "./languages"; import { Logger } from "./logging"; +export * from "./sarif"; + /** * The name of the file containing the base database OIDs, as stored in the * root of the database location. @@ -55,78 +57,6 @@ const DEFAULT_RESERVED_RAM_SCALING_FACTOR = 0.05; */ const MINIMUM_CGROUP_MEMORY_LIMIT_BYTES = 1024 * 1024; -export interface SarifFile { - version?: string | null; - runs: SarifRun[]; -} - -export interface SarifRun { - tool?: { - driver?: { - guid?: string; - name?: string; - fullName?: string; - semanticVersion?: string; - version?: string; - }; - }; - automationDetails?: { - id?: string; - }; - artifacts?: string[]; - invocations?: SarifInvocation[]; - results?: SarifResult[]; -} - -export interface SarifInvocation { - toolExecutionNotifications?: SarifNotification[]; -} - -export interface SarifResult { - ruleId?: string; - rule?: { - id?: string; - }; - message?: { - text?: string; - }; - locations: Array<{ - physicalLocation: { - artifactLocation: { - uri: string; - }; - region?: { - startLine?: number; - }; - }; - }>; - relatedLocations?: Array<{ - physicalLocation: { - artifactLocation: { - uri: string; - }; - region?: { - startLine?: number; - }; - }; - }>; - partialFingerprints: { - primaryLocationLineHash?: string; - }; -} - -export interface SarifNotification { - locations?: SarifLocation[]; -} - -export interface SarifLocation { - physicalLocation?: { - artifactLocation?: { - uri?: string; - }; - }; -} - /** * Get the extra options for the codeql commands. */ @@ -146,25 +76,6 @@ export function getExtraOptionsEnvParam(): object { } } -/** - * Get the array of all the tool names contained in the given sarif contents. - * - * Returns an array of unique string tool names. - */ -export function getToolNames(sarif: SarifFile): string[] { - const toolNames = {}; - - for (const run of sarif.runs || []) { - const tool = run.tool || {}; - const driver = tool.driver || {}; - if (typeof driver.name === "string" && driver.name.length > 0) { - toolNames[driver.name] = true; - } - } - - return Object.keys(toolNames); -} - // Creates a random temporary directory, runs the given body, and then deletes the directory. // Mostly intended for use within tests. export async function withTmpDir( @@ -984,80 +895,6 @@ export function parseMatrixInput( return JSON.parse(matrixInput) as { [key: string]: string }; } -function removeDuplicateLocations(locations: SarifLocation[]): SarifLocation[] { - const newJsonLocations = new Set(); - return locations.filter((location) => { - const jsonLocation = JSON.stringify(location); - if (!newJsonLocations.has(jsonLocation)) { - newJsonLocations.add(jsonLocation); - return true; - } - return false; - }); -} - -export function fixInvalidNotifications( - sarif: SarifFile, - logger: Logger, -): SarifFile { - if (!Array.isArray(sarif.runs)) { - return sarif; - } - - // Ensure that the array of locations for each SARIF notification contains unique locations. - // This is a workaround for a bug in the CodeQL CLI that causes duplicate locations to be - // emitted in some cases. - let numDuplicateLocationsRemoved = 0; - - const newSarif = { - ...sarif, - runs: sarif.runs.map((run) => { - if ( - run.tool?.driver?.name !== "CodeQL" || - !Array.isArray(run.invocations) - ) { - return run; - } - return { - ...run, - invocations: run.invocations.map((invocation) => { - if (!Array.isArray(invocation.toolExecutionNotifications)) { - return invocation; - } - return { - ...invocation, - toolExecutionNotifications: - invocation.toolExecutionNotifications.map((notification) => { - if (!Array.isArray(notification.locations)) { - return notification; - } - const newLocations = removeDuplicateLocations( - notification.locations, - ); - numDuplicateLocationsRemoved += - notification.locations.length - newLocations.length; - return { - ...notification, - locations: newLocations, - }; - }), - }; - }), - }; - }), - }; - - if (numDuplicateLocationsRemoved > 0) { - logger.info( - `Removed ${numDuplicateLocationsRemoved} duplicate locations from SARIF notification ` + - "objects.", - ); - } else { - logger.debug("No duplicate locations found in SARIF notification objects."); - } - return newSarif; -} - export function wrapError(error: unknown): Error { return error instanceof Error ? error : new Error(String(error)); }