Merge branch 'main' into update-bundle/codeql-bundle-v2.20.5

This commit is contained in:
Henry Mercer 2025-02-19 18:38:45 +00:00 committed by GitHub
commit 67e48c1eaf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 263 additions and 71 deletions

View file

@ -12,6 +12,10 @@ import { setupCppAutobuild } from "./autobuild";
import { CodeQL, getCodeQL } from "./codeql";
import * as configUtils from "./config-utils";
import { addDiagnostic, makeDiagnostic } from "./diagnostics";
import {
DiffThunkRange,
writeDiffRangesJsonFile,
} from "./diff-filtering-utils";
import { EnvVar } from "./environment";
import { FeatureEnablement, Feature } from "./feature-flags";
import { isScannedLanguage, Language } from "./languages";
@ -284,12 +288,6 @@ export async function setupDiffInformedQueryRun(
);
}
interface DiffThunkRange {
path: string;
startLine: number;
endLine: number;
}
/**
* Return the file line ranges that were added or modified in the pull request.
*
@ -537,6 +535,10 @@ extensions:
`Wrote pr-diff-range extension pack to ${extensionFilePath}:\n${extensionContents}`,
);
// Write the diff ranges to a JSON file, for action-side alert filtering by the
// upload-lib module.
writeDiffRangesJsonFile(logger, ranges);
return diffRangeDir;
}

View file

@ -0,0 +1,42 @@
import * as fs from "fs";
import * as path from "path";
import * as actionsUtil from "./actions-util";
import { Logger } from "./logging";
export interface DiffThunkRange {
path: string;
startLine: number;
endLine: number;
}
function getDiffRangesJsonFilePath(): string {
return path.join(actionsUtil.getTemporaryDirectory(), "pr-diff-range.json");
}
export function writeDiffRangesJsonFile(
logger: Logger,
ranges: DiffThunkRange[],
): void {
const jsonContents = JSON.stringify(ranges, null, 2);
const jsonFilePath = getDiffRangesJsonFilePath();
fs.writeFileSync(jsonFilePath, jsonContents);
logger.debug(
`Wrote pr-diff-range JSON file to ${jsonFilePath}:\n${jsonContents}`,
);
}
export function readDiffRangesJsonFile(
logger: Logger,
): DiffThunkRange[] | undefined {
const jsonFilePath = getDiffRangesJsonFilePath();
if (!fs.existsSync(jsonFilePath)) {
logger.debug(`Diff ranges JSON file does not exist at ${jsonFilePath}`);
return undefined;
}
const jsonContents = fs.readFileSync(jsonFilePath, "utf8");
logger.debug(
`Read pr-diff-range JSON file from ${jsonFilePath}:\n${jsonContents}`,
);
return JSON.parse(jsonContents) as DiffThunkRange[];
}

View file

@ -15,13 +15,12 @@ const MIN_REQUIRED_BSD_TAR_VERSION = "3.4.3";
const MIN_REQUIRED_GNU_TAR_VERSION = "1.31";
export type TarVersion = {
name: string;
type: "gnu" | "bsd";
version: string;
};
async function getTarVersion(programName: string): Promise<TarVersion> {
const tar = await io.which(programName, true);
async function getTarVersion(): Promise<TarVersion> {
const tar = await io.which("tar", true);
let stdout = "";
const exitCode = await new ToolRunner(tar, ["--version"], {
listeners: {
@ -31,43 +30,28 @@ async function getTarVersion(programName: string): Promise<TarVersion> {
},
}).exec();
if (exitCode !== 0) {
throw new Error(`Failed to call ${programName} --version`);
throw new Error("Failed to call tar --version");
}
// Return whether this is GNU tar or BSD tar, and the version number
if (stdout.includes("GNU tar")) {
const match = stdout.match(/tar \(GNU tar\) ([0-9.]+)/);
if (!match || !match[1]) {
throw new Error(`Failed to parse output of ${programName} --version.`);
throw new Error("Failed to parse output of tar --version.");
}
return { name: programName, type: "gnu", version: match[1] };
return { type: "gnu", version: match[1] };
} else if (stdout.includes("bsdtar")) {
const match = stdout.match(/bsdtar ([0-9.]+)/);
if (!match || !match[1]) {
throw new Error(`Failed to parse output of ${programName} --version.`);
throw new Error("Failed to parse output of tar --version.");
}
return { name: programName, type: "bsd", version: match[1] };
return { type: "bsd", version: match[1] };
} else {
throw new Error("Unknown tar version");
}
}
async function pickTarCommand(): Promise<TarVersion> {
// bsdtar 3.5.3 on the macos-14 (arm) action runner image is prone to crash with the following
// error messages when extracting zstd archives:
//
// tar: Child process exited with status 1
// tar: Error exit delayed from previous errors.
//
// To avoid this problem, prefer GNU tar under the name "gtar" if it is available.
try {
return await getTarVersion("gtar");
} catch {
return await getTarVersion("tar");
}
}
export interface ZstdAvailability {
available: boolean;
foundZstdBinary: boolean;
@ -79,7 +63,7 @@ export async function isZstdAvailable(
): Promise<ZstdAvailability> {
const foundZstdBinary = await isBinaryAccessible("zstd", logger);
try {
const tarVersion = await pickTarCommand();
const tarVersion = await getTarVersion();
const { type, version } = tarVersion;
logger.info(`Found ${type} tar version ${version}.`);
switch (type) {
@ -187,10 +171,10 @@ export async function extractTarZst(
args.push("-f", tar instanceof stream.Readable ? "-" : tar, "-C", dest);
process.stdout.write(`[command]${tarVersion.name} ${args.join(" ")}\n`);
process.stdout.write(`[command]tar ${args.join(" ")}\n`);
await new Promise<void>((resolve, reject) => {
const tarProcess = spawn(tarVersion.name, args, { stdio: "pipe" });
const tarProcess = spawn("tar", args, { stdio: "pipe" });
let stdout = "";
tarProcess.stdout?.on("data", (data: Buffer) => {
@ -221,7 +205,7 @@ export async function extractTarZst(
if (code !== 0) {
reject(
new CommandInvocationError(
tarVersion.name,
"tar",
args,
code ?? undefined,
stdout,

View file

@ -14,6 +14,7 @@ import * as api from "./api-client";
import { getGitHubVersion, wrapApiConfigurationError } from "./api-client";
import { CodeQL, getCodeQL } from "./codeql";
import { getConfig } from "./config-utils";
import { readDiffRangesJsonFile } from "./diff-filtering-utils";
import { EnvVar } from "./environment";
import { FeatureEnablement } from "./feature-flags";
import * as fingerprints from "./fingerprints";
@ -578,6 +579,7 @@ export async function uploadFiles(
features,
logger,
);
sarif = filterAlertsByDiffRange(logger, sarif);
sarif = await fingerprints.addFingerprints(sarif, checkoutPath, logger);
const analysisKey = await api.getAnalysisKey();
@ -848,3 +850,50 @@ export class InvalidSarifUploadError extends Error {
super(message);
}
}
function filterAlertsByDiffRange(logger: Logger, sarif: SarifFile): SarifFile {
const diffRanges = readDiffRangesJsonFile(logger);
if (!diffRanges?.length) {
return sarif;
}
const checkoutPath = actionsUtil.getRequiredInput("checkout_path");
for (const run of sarif.runs) {
if (run.results) {
run.results = run.results.filter((result) => {
const locations = [
...(result.locations || []).map((loc) => loc.physicalLocation),
...(result.relatedLocations || []).map((loc) => loc.physicalLocation),
];
return locations.some((physicalLocation) => {
const locationUri = physicalLocation?.artifactLocation?.uri;
const locationStartLine = physicalLocation?.region?.startLine;
if (!locationUri || locationStartLine === undefined) {
return false;
}
// CodeQL always uses forward slashes as the path separator, so on Windows we
// need to replace any backslashes with forward slashes.
const locationPath = path
.join(checkoutPath, locationUri)
.replaceAll(path.sep, "/");
// Alert filtering here replicates the same behavior as the restrictAlertsTo
// extensible predicate in CodeQL. See the restrictAlertsTo documentation
// https://codeql.github.com/codeql-standard-libraries/csharp/codeql/util/AlertFiltering.qll/predicate.AlertFiltering$restrictAlertsTo.3.html
// for more details, such as why the filtering applies only to the first line
// of an alert location.
return diffRanges.some(
(range) =>
range.path === locationPath &&
((range.startLine <= locationStartLine &&
range.endLine >= locationStartLine) ||
(range.startLine === 0 && range.endLine === 0)),
);
});
});
}
}
return sarif;
}

View file

@ -96,6 +96,16 @@ export interface SarifResult {
};
};
}>;
relatedLocations?: Array<{
physicalLocation: {
artifactLocation: {
uri: string;
};
region?: {
startLine?: number;
};
};
}>;
partialFingerprints: {
primaryLocationLineHash?: string;
};