Skip to content
Open
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
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