mirror of
https://github.com/github/codeql-action.git
synced 2026-05-22 22:08:56 +00:00
142 lines
4.2 KiB
TypeScript
142 lines
4.2 KiB
TypeScript
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;
|
|
}
|