Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/analyze-action-post.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

287 changes: 148 additions & 139 deletions lib/analyze-action.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/autobuild-action.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

697 changes: 353 additions & 344 deletions lib/init-action-post.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/init-action.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/resolve-environment-action.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/setup-codeql-action.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/start-proxy-action-post.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/start-proxy-action.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

293 changes: 150 additions & 143 deletions lib/upload-lib.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/upload-sarif-action-post.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

379 changes: 194 additions & 185 deletions lib/upload-sarif-action.js

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.19.9",
"@types/node-forge": "^1.3.14",
"@types/sarif": "^2.1.7",
"@types/semver": "^7.7.1",
"@types/sinon": "^21.0.0",
"ava": "^6.4.1",
Expand Down
3 changes: 2 additions & 1 deletion src/analyze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { FeatureEnablement, Feature } from "./feature-flags";
import { KnownLanguage, Language } from "./languages";
import { Logger, withGroupAsync } from "./logging";
import { OverlayDatabaseMode } from "./overlay";
import type * as sarif from "./sarif";
import { DatabaseCreationTimings, EventReport } from "./status-report";
import { endTracingForCluster } from "./tracer-config";
import * as util from "./util";
Expand Down Expand Up @@ -594,7 +595,7 @@ export async function runQueries(
function getPerQueryAlertCounts(sarifPath: string): Record<string, number> {
const sarifObject = JSON.parse(
fs.readFileSync(sarifPath, "utf8"),
) as util.SarifFile;
) as sarif.Log;
// We do not need to compute fingerprints because we are not sending data based off of locations.

// Generate the query: alert count object
Expand Down
5 changes: 3 additions & 2 deletions src/fingerprints.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import test from "ava";

import * as fingerprints from "./fingerprints";
import { getRunnerLogger } from "./logging";
import * as sarif from "./sarif";
import { setupTests } from "./testing-utils";
import * as util from "./util";

Expand Down Expand Up @@ -201,7 +202,7 @@ test("addFingerprints", async (t) => {
fs
.readFileSync(`${__dirname}/../src/testdata/fingerprinting.input.sarif`)
.toString(),
) as util.SarifFile;
) as sarif.Log;
const expected = JSON.parse(
fs
.readFileSync(
Expand Down Expand Up @@ -229,7 +230,7 @@ test("missingRegions", async (t) => {
fs
.readFileSync(`${__dirname}/../src/testdata/fingerprinting2.input.sarif`)
.toString(),
) as util.SarifFile;
) as sarif.Log;
const expected = JSON.parse(
fs
.readFileSync(
Expand Down
12 changes: 6 additions & 6 deletions src/fingerprints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Long from "long";

import { DocUrl } from "./doc-url";
import { Logger } from "./logging";
import { SarifFile, SarifResult } from "./util";
import type * as sarif from "./sarif";

const tab = "\t".charCodeAt(0);
const space = " ".charCodeAt(0);
Expand Down Expand Up @@ -138,7 +138,7 @@ export async function hash(callback: hashCallback, filepath: string) {
// Generate a hash callback function that updates the given result in-place
// when it receives a hash for the correct line number. Ignores hashes for other lines.
function locationUpdateCallback(
result: SarifResult,
result: sarif.Result,
location: any,
logger: Logger,
): hashCallback {
Expand Down Expand Up @@ -256,17 +256,17 @@ export function resolveUriToFile(
// Compute fingerprints for results in the given sarif file
// and return an updated sarif file contents.
export async function addFingerprints(
sarif: SarifFile,
sarifLog: Partial<sarif.Log>,
sourceRoot: string,
logger: Logger,
): Promise<SarifFile> {
): Promise<Partial<sarif.Log>> {
logger.info(
`Adding fingerprints to SARIF file. See ${DocUrl.TRACK_CODE_SCANNING_ALERTS_ACROSS_RUNS} for more information.`,
);
// 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[] } = {};
for (const run of sarif.runs || []) {
for (const run of sarifLog.runs || []) {
// We may need the list of artifacts to resolve against
const artifacts = run.artifacts || [];

Expand Down Expand Up @@ -316,5 +316,5 @@ export async function addFingerprints(
await hash(teeCallback, filepath);
}

return sarif;
return sarifLog;
}
18 changes: 18 additions & 0 deletions src/sarif/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as fs from "fs";

import test from "ava";

import { setupTests } from "../testing-utils";

import { getToolNames, type Log } from ".";

setupTests(test);

test("getToolNames", (t) => {
const input = fs.readFileSync(
`${__dirname}/../../src/testdata/tool-names.sarif`,
"utf8",
);
const toolNames = getToolNames(JSON.parse(input) as Log);
t.deepEqual(toolNames, ["CodeQL command-line toolchain", "ESLint"]);
});
141 changes: 141 additions & 0 deletions src/sarif/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import * as fs from "fs";

import { Logger } from "../logging";

import * as sarif from "sarif";

export type * from "sarif";

// Extends `ToolComponent` with the non-standard `automationId` property we use.
export type RunKey = sarif.ToolComponent & {
/**
* Describes a SARIF run (either uniquely or not uniquely) based on the criteria used by
* Code Scanning to determine analysis categories
*/
automationId: string | undefined;
};

/**
* An error that occurred due to an invalid SARIF upload request.
*/
export class InvalidSarifUploadError extends Error {}

/**
* 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(sarifFile: Partial<sarif.Log>): string[] {
const toolNames = {};

for (const run of sarifFile.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);
}

/**
* Reads the file pointed at by `sarifFilePath` and parses it as JSON. This function does
* not validate that the JSON represents a valid SARIF file. I.e. this function will only
* throw if the file cannot be read or does not contain valid JSON.
*
* @param sarifFilePath The file to read.
* @returns The resulting JSON value, cast to a SARIF `Log`.
*/
export function readSarifFile(sarifFilePath: string): Partial<sarif.Log> {
return JSON.parse(fs.readFileSync(sarifFilePath, "utf8")) as sarif.Log;
}

// Takes a list of paths to sarif files and combines them together,
// returning the contents of the combined sarif file.
export function combineSarifFiles(
sarifFiles: string[],
logger: Logger,
): sarif.Log {
logger.info(`Loading SARIF file(s)`);
const runs: sarif.Run[] = [];
let version: sarif.Log.version | undefined = undefined;

for (const sarifFile of sarifFiles) {
logger.debug(`Loading SARIF file: ${sarifFile}`);
const sarifLog = readSarifFile(sarifFile);
// If this is the first SARIF file we are reading, store the version from it so that we
// can put it in the combined SARIF. If not, then check that the versions match and
// throw an exception if they do not.
if (version === undefined) {
version = sarifLog.version;
} else if (version !== sarifLog.version) {
throw new InvalidSarifUploadError(
`Different SARIF versions encountered: ${version} and ${sarifLog.version}`,
);
}

runs.push(...(sarifLog?.runs || []));
}

// We can't guarantee that the SARIF files we load will have version properties. As a fallback,
// we set it to the expected version if we didn't find any other.
if (version === undefined) {
version = "2.1.0";
}

return { version, runs };
}

/**
* Checks whether all the runs in the given SARIF files were produced by CodeQL.
* @param sarifLogs The list of SARIF objects to check.
*/
export function areAllRunsProducedByCodeQL(
sarifLogs: Array<Partial<sarif.Log>>,
): boolean {
return sarifLogs.every((sarifLog: Partial<sarif.Log>) => {
return sarifLog.runs?.every((run) => run.tool?.driver?.name === "CodeQL");
});
}

function createRunKey(run: sarif.Run): RunKey {
return {
name: run.tool?.driver?.name,
fullName: run.tool?.driver?.fullName,
version: run.tool?.driver?.version,
semanticVersion: run.tool?.driver?.semanticVersion,
guid: run.tool?.driver?.guid,
automationId: run.automationDetails?.id,
};
}

/**
* Checks whether all runs in the given SARIF files are unique (based on the
* criteria used by Code Scanning to determine analysis categories).
* @param sarifLogs The list of SARIF objects to check.
*/
export function areAllRunsUnique(
sarifLogs: Array<Partial<sarif.Log>>,
): boolean {
const keys = new Set<string>();

for (const sarifLog of sarifLogs) {
if (sarifLog.runs === undefined) {
continue;
}

for (const run of sarifLog.runs) {
const key = JSON.stringify(createRunKey(run));

// If the key already exists, the runs are not unique.
if (keys.has(key)) {
return false;
}

keys.add(key);
}
}

return true;
}
Loading
Loading