Skip to content
396 changes: 200 additions & 196 deletions lib/analyze-action.js

Large diffs are not rendered by default.

810 changes: 406 additions & 404 deletions lib/init-action-post.js

Large diffs are not rendered by default.

342 changes: 172 additions & 170 deletions lib/upload-lib.js

Large diffs are not rendered by default.

342 changes: 173 additions & 169 deletions lib/upload-sarif-action.js

Large diffs are not rendered by default.

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 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"]);
});
189 changes: 189 additions & 0 deletions src/sarif/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import * as fs from "fs";

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[];
}

export type SarifRunKey = {
name: string | undefined;
fullName: string | undefined;
version: string | undefined;
semanticVersion: string | undefined;
guid: string | undefined;
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(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 readSarifFile(sarifFilePath: string): SarifFile {
return JSON.parse(fs.readFileSync(sarifFilePath, "utf8")) as SarifFile;
}

// 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,
): SarifFile {
logger.info(`Loading SARIF file(s)`);
const combinedSarif: SarifFile = {
version: null,
runs: [],
};

for (const sarifFile of sarifFiles) {
logger.debug(`Loading SARIF file: ${sarifFile}`);
const sarifObject = readSarifFile(sarifFile);
// Check SARIF version
if (combinedSarif.version === null) {
combinedSarif.version = sarifObject.version;
} else if (combinedSarif.version !== sarifObject.version) {
throw new InvalidSarifUploadError(
`Different SARIF versions encountered: ${combinedSarif.version} and ${sarifObject.version}`,
);
}

combinedSarif.runs.push(...sarifObject.runs);
}

return combinedSarif;
}

/**
* Checks whether all the runs in the given SARIF files were produced by CodeQL.
* @param sarifObjects The list of SARIF objects to check.
*/
export function areAllRunsProducedByCodeQL(sarifObjects: SarifFile[]): boolean {
return sarifObjects.every((sarifObject) => {
return sarifObject.runs?.every(
(run) => run.tool?.driver?.name === "CodeQL",
);
});
}

function createRunKey(run: SarifRun): SarifRunKey {
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 sarifObjects The list of SARIF objects to check.
*/
export function areAllRunsUnique(sarifObjects: SarifFile[]): boolean {
const keys = new Set<string>();

for (const sarifObject of sarifObjects) {
for (const run of sarifObject.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;
}
115 changes: 14 additions & 101 deletions src/upload-lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ import * as gitUtils from "./git-utils";
import { initCodeQL } from "./init";
import { Logger } from "./logging";
import { getRepositoryNwo, RepositoryNwo } from "./repository";
import type { SarifFile } from "./sarif";
import {
areAllRunsProducedByCodeQL,
areAllRunsUnique,
combineSarifFiles,
InvalidSarifUploadError,
} from "./sarif";
import { BasePayload, UploadPayload } from "./upload-lib/types";
import * as util from "./util";
import {
Expand All @@ -30,100 +37,13 @@ import {
GitHubVariant,
GitHubVersion,
satisfiesGHESVersion,
SarifFile,
SarifRun,
} from "./util";

const GENERIC_403_MSG =
"The repo on which this action is running has not opted-in to CodeQL code scanning.";
const GENERIC_404_MSG =
"The CodeQL code scanning feature is forbidden on this repository.";

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

for (const sarifFile of sarifFiles) {
logger.debug(`Loading SARIF file: ${sarifFile}`);
const sarifObject = JSON.parse(
fs.readFileSync(sarifFile, "utf8"),
) as SarifFile;
// Check SARIF version
if (combinedSarif.version === null) {
combinedSarif.version = sarifObject.version;
} else if (combinedSarif.version !== sarifObject.version) {
throw new InvalidSarifUploadError(
`Different SARIF versions encountered: ${combinedSarif.version} and ${sarifObject.version}`,
);
}

combinedSarif.runs.push(...sarifObject.runs);
}

return combinedSarif;
}

/**
* Checks whether all the runs in the given SARIF files were produced by CodeQL.
* @param sarifObjects The list of SARIF objects to check.
*/
function areAllRunsProducedByCodeQL(sarifObjects: SarifFile[]): boolean {
return sarifObjects.every((sarifObject) => {
return sarifObject.runs?.every(
(run) => run.tool?.driver?.name === "CodeQL",
);
});
}

type SarifRunKey = {
name: string | undefined;
fullName: string | undefined;
version: string | undefined;
semanticVersion: string | undefined;
guid: string | undefined;
automationId: string | undefined;
};

function createRunKey(run: SarifRun): SarifRunKey {
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 sarifObjects The list of SARIF objects to check.
*/
function areAllRunsUnique(sarifObjects: SarifFile[]): boolean {
const keys = new Set<string>();

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

// Checks whether the deprecation warning for combining SARIF files should be shown.
export async function shouldShowCombineSarifFilesDeprecationWarning(
sarifObjects: util.SarifFile[],
Expand Down Expand Up @@ -195,9 +115,7 @@ async function combineSarifFilesUsingCLI(
): Promise<SarifFile> {
logger.info("Combining SARIF files using the CodeQL CLI");

const sarifObjects = sarifFiles.map((sarifFile): SarifFile => {
return JSON.parse(fs.readFileSync(sarifFile, "utf8")) as SarifFile;
});
const sarifObjects = sarifFiles.map(util.readSarifFile);

const deprecationWarningMessage =
gitHubVersion.type === GitHubVariant.GHES
Expand Down Expand Up @@ -279,30 +197,30 @@ async function combineSarifFilesUsingCLI(
mergeRunsFromEqualCategory: true,
});

return JSON.parse(fs.readFileSync(outputFile, "utf8")) as SarifFile;
return util.readSarifFile(outputFile);
}

// Populates the run.automationDetails.id field using the analysis_key and environment
// and return an updated sarif file contents.
export function populateRunAutomationDetails(
sarif: SarifFile,
sarifFile: SarifFile,
category: string | undefined,
analysis_key: string,
environment: string | undefined,
): SarifFile {
const automationID = getAutomationID(category, analysis_key, environment);

if (automationID !== undefined) {
for (const run of sarif.runs || []) {
for (const run of sarifFile.runs || []) {
if (run.automationDetails === undefined) {
run.automationDetails = {
id: automationID,
};
}
}
return sarif;
return sarifFile;
}
return sarif;
return sarifFile;
}

function getAutomationID(
Expand Down Expand Up @@ -531,7 +449,7 @@ function countResultsInSarif(sarif: string): number {

export function readSarifFile(sarifFilePath: string): SarifFile {
try {
return JSON.parse(fs.readFileSync(sarifFilePath, "utf8")) as SarifFile;
return util.readSarifFile(sarifFilePath);
} catch (e) {
throw new InvalidSarifUploadError(
`Invalid SARIF. JSON syntax error: ${getErrorMessage(e)}`,
Expand Down Expand Up @@ -1127,11 +1045,6 @@ function sanitize(str?: string) {
return (str ?? "_").replace(/[^a-zA-Z0-9_]/g, "_").toLocaleUpperCase();
}

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

function filterAlertsByDiffRange(logger: Logger, sarif: SarifFile): SarifFile {
const diffRanges = readDiffRangesJsonFile(logger);
if (!diffRanges?.length) {
Expand Down
Loading
Loading