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

This commit is contained in:
Chuan-kai Lin
2023-07-13 08:19:40 -07:00
committed by GitHub
5373 changed files with 662799 additions and 473557 deletions

View File

@@ -210,9 +210,6 @@ async function run() {
actionsUtil.getOptionalInput("threads") || process.env["CODEQL_THREADS"],
logger
);
const memory = util.getMemoryFlag(
actionsUtil.getOptionalInput("ram") || process.env["CODEQL_RAM"]
);
const repositoryNwo = parseRepositoryNwo(
util.getRequiredEnvParam("GITHUB_REPOSITORY")
@@ -227,6 +224,11 @@ async function run() {
logger
);
const memory = await util.getMemoryFlag(
actionsUtil.getOptionalInput("ram") || process.env["CODEQL_RAM"],
features
);
await runAutobuildIfLegacyGoWorkflow(config, logger);
dbCreationTimings = await runFinalize(

View File

@@ -175,6 +175,8 @@ test("status report fields and search path setting", async (t) => {
if (builtinStatusReport.event_reports) {
for (const eventReport of builtinStatusReport.event_reports) {
t.deepEqual(eventReport.event, "codeql database interpret-results");
t.true("properties" in eventReport);
t.true("alertCounts" in eventReport.properties!);
}
}
@@ -214,6 +216,8 @@ test("status report fields and search path setting", async (t) => {
if (customStatusReport.event_reports) {
for (const eventReport of customStatusReport.event_reports) {
t.deepEqual(eventReport.event, "codeql database interpret-results");
t.true("properties" in eventReport);
t.true("alertCounts" in eventReport.properties!);
}
}
}

View File

@@ -372,7 +372,9 @@ export async function runQueries(
completed_at: endTimeInterpretResults.toISOString(),
exit_status: "success",
language,
properties: perQueryAlertCounts,
properties: {
alertCounts: perQueryAlertCounts,
},
};
if (statusReport["event_reports"] === undefined) {

View File

@@ -23,7 +23,7 @@ import {
createFeatures,
mockLanguagesInRepo as mockLanguagesInRepo,
} from "./testing-utils";
import * as util from "./util";
import { GitHubVariant, GitHubVersion, UserError, withTmpDir } from "./util";
setupTests(test);
@@ -35,7 +35,7 @@ const sampleApiDetails = {
registriesAuthTokens: undefined,
};
const gitHubVersion = { type: util.GitHubVariant.DOTCOM } as util.GitHubVersion;
const gitHubVersion = { type: GitHubVariant.DOTCOM } as GitHubVersion;
// Returns the filepath of the newly-created file
function createConfigFile(inputFileContents: string, tmpDir: string): string {
@@ -55,7 +55,7 @@ function mockGetContents(
data: content,
};
const spyGetContents = sinon
.stub(client.repos, "getContent")
.stub(client.rest.repos, "getContent")
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
.resolves(response as any);
sinon.stub(api, "getApiClient").value(() => client);
@@ -73,12 +73,12 @@ function mockListLanguages(languages: string[]) {
response.data[language] = 123;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
sinon.stub(client.repos, "listLanguages").resolves(response as any);
sinon.stub(client.rest.repos, "listLanguages").resolves(response as any);
sinon.stub(api, "getApiClient").value(() => client);
}
test("load empty config", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
const logger = getRunnerLogger(true);
const languages = "javascript,python";
@@ -145,7 +145,7 @@ test("load empty config", async (t) => {
});
test("loading config saves config", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
const logger = getRunnerLogger(true);
const codeQL = setCodeQL({
@@ -207,7 +207,7 @@ test("loading config saves config", async (t) => {
});
test("load input outside of workspace", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
try {
await configUtils.initConfig(
undefined,
@@ -234,7 +234,7 @@ test("load input outside of workspace", async (t) => {
} catch (err) {
t.deepEqual(
err,
new Error(
new UserError(
configUtils.getConfigFileOutsideWorkspaceErrorMessage(
path.join(tmpDir, "../input")
)
@@ -245,7 +245,7 @@ test("load input outside of workspace", async (t) => {
});
test("load non-local input with invalid repo syntax", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
// no filename given, just a repo
const configFile = "octo-org/codeql-config@main";
@@ -275,7 +275,7 @@ test("load non-local input with invalid repo syntax", async (t) => {
} catch (err) {
t.deepEqual(
err,
new Error(
new UserError(
configUtils.getConfigFileRepoFormatInvalidMessage(
"octo-org/codeql-config@main"
)
@@ -286,7 +286,7 @@ test("load non-local input with invalid repo syntax", async (t) => {
});
test("load non-existent input", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
const languages = "javascript";
const configFile = "input";
t.false(fs.existsSync(path.join(tmpDir, configFile)));
@@ -317,7 +317,7 @@ test("load non-existent input", async (t) => {
} catch (err) {
t.deepEqual(
err,
new Error(
new UserError(
configUtils.getConfigFileDoesNotExistErrorMessage(
path.join(tmpDir, "input")
)
@@ -328,7 +328,7 @@ test("load non-existent input", async (t) => {
});
test("load non-empty input", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
const codeQL = setCodeQL({
async resolveQueries() {
return {
@@ -428,7 +428,7 @@ test("load non-empty input", async (t) => {
});
test("Default queries are used", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
// Check that the default behaviour is to add the default queries.
// In this case if a config file is specified but does not include
// the disable-default-queries field.
@@ -523,7 +523,7 @@ function queriesToResolvedQueryForm(queries: string[]) {
}
test("Queries can be specified in config file", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
const inputFileContents = `
name: my config
queries:
@@ -598,7 +598,7 @@ test("Queries can be specified in config file", async (t) => {
});
test("Queries from config file can be overridden in workflow file", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
const inputFileContents = `
name: my config
queries:
@@ -677,7 +677,7 @@ test("Queries from config file can be overridden in workflow file", async (t) =>
});
test("Queries in workflow file can be used in tandem with the 'disable default queries' option", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
process.env["RUNNER_TEMP"] = tmpDir;
process.env["GITHUB_WORKSPACE"] = tmpDir;
@@ -751,7 +751,7 @@ test("Queries in workflow file can be used in tandem with the 'disable default q
});
test("Multiple queries can be specified in workflow file, no config file required", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
fs.mkdirSync(path.join(tmpDir, "override1"));
fs.mkdirSync(path.join(tmpDir, "override2"));
@@ -829,7 +829,7 @@ test("Multiple queries can be specified in workflow file, no config file require
});
test("Queries in workflow file can be added to the set of queries without overriding config file", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
process.env["RUNNER_TEMP"] = tmpDir;
process.env["GITHUB_WORKSPACE"] = tmpDir;
@@ -926,7 +926,7 @@ test("Queries in workflow file can be added to the set of queries without overri
});
test("Queries can be specified using config input", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
const configInput = `
name: my config
queries:
@@ -1009,7 +1009,7 @@ test("Queries can be specified using config input", async (t) => {
});
test("Using config input and file together, config input should be used.", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
process.env["RUNNER_TEMP"] = tmpDir;
process.env["GITHUB_WORKSPACE"] = tmpDir;
@@ -1101,7 +1101,7 @@ test("Using config input and file together, config input should be used.", async
});
test("Invalid queries in workflow file handled correctly", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
const queries = "foo/bar@v1@v3";
const languages = "javascript";
@@ -1148,14 +1148,16 @@ test("Invalid queries in workflow file handled correctly", async (t) => {
} catch (err) {
t.deepEqual(
err,
new Error(configUtils.getQueryUsesInvalid(undefined, "foo/bar@v1@v3"))
new UserError(
configUtils.getQueryUsesInvalid(undefined, "foo/bar@v1@v3")
)
);
}
});
});
test("API client used when reading remote config", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
const codeQL = setCodeQL({
async resolveQueries() {
return {
@@ -1222,7 +1224,7 @@ test("API client used when reading remote config", async (t) => {
});
test("Remote config handles the case where a directory is provided", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
const dummyResponse = []; // directories are returned as arrays
mockGetContents(dummyResponse);
@@ -1253,14 +1255,16 @@ test("Remote config handles the case where a directory is provided", async (t) =
} catch (err) {
t.deepEqual(
err,
new Error(configUtils.getConfigFileDirectoryGivenMessage(repoReference))
new UserError(
configUtils.getConfigFileDirectoryGivenMessage(repoReference)
)
);
}
});
});
test("Invalid format of remote config handled correctly", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
const dummyResponse = {
// note no "content" property here
};
@@ -1293,14 +1297,16 @@ test("Invalid format of remote config handled correctly", async (t) => {
} catch (err) {
t.deepEqual(
err,
new Error(configUtils.getConfigFileFormatInvalidMessage(repoReference))
new UserError(
configUtils.getConfigFileFormatInvalidMessage(repoReference)
)
);
}
});
});
test("No detected languages", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
mockListLanguages([]);
const codeQL = setCodeQL({
async resolveLanguages() {
@@ -1335,13 +1341,13 @@ test("No detected languages", async (t) => {
);
throw new Error("initConfig did not throw error");
} catch (err) {
t.deepEqual(err, new Error(configUtils.getNoLanguagesError()));
t.deepEqual(err, new UserError(configUtils.getNoLanguagesError()));
}
});
});
test("Unknown languages", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
const languages = "rubbish,english";
try {
@@ -1370,14 +1376,16 @@ test("Unknown languages", async (t) => {
} catch (err) {
t.deepEqual(
err,
new Error(configUtils.getUnknownLanguagesError(["rubbish", "english"]))
new UserError(
configUtils.getUnknownLanguagesError(["rubbish", "english"])
)
);
}
});
});
test("Config specifies packages", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
const codeQL = setCodeQL({
async resolveQueries() {
return {
@@ -1431,7 +1439,7 @@ test("Config specifies packages", async (t) => {
});
test("Config specifies packages for multiple languages", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
const codeQL = setCodeQL({
async resolveQueries() {
return {
@@ -1518,7 +1526,7 @@ function doInvalidInputTest(
expectedErrorMessageGenerator: (configFile: string) => string
) {
test(`load invalid input - ${testName}`, async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
const codeQL = setCodeQL({
async resolveQueries() {
return {
@@ -1561,7 +1569,10 @@ function doInvalidInputTest(
);
throw new Error("initConfig did not throw error");
} catch (err) {
t.deepEqual(err, new Error(expectedErrorMessageGenerator(inputFile)));
t.deepEqual(
err,
new UserError(expectedErrorMessageGenerator(inputFile))
);
}
});
});
@@ -2104,7 +2115,7 @@ const mlPoweredQueriesMacro = test.macro({
queriesInput: string | undefined,
expectedVersionString: string | undefined
) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
const codeQL = setCodeQL({
async getVersion() {
return codeQLVersion;
@@ -2458,7 +2469,7 @@ test(
);
test("downloadPacks-no-registries", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
const packDownloadStub = sinon.stub();
packDownloadStub.callsFake((packs) => ({
packs,
@@ -2495,7 +2506,7 @@ test("downloadPacks-no-registries", async (t) => {
test("downloadPacks-with-registries", async (t) => {
// same thing, but this time include a registries block and
// associated env vars
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
process.env.GITHUB_TOKEN = "not-a-token";
process.env.CODEQL_REGISTRIES_AUTH = undefined;
const logger = getRunnerLogger(true);
@@ -2586,7 +2597,7 @@ test("downloadPacks-with-registries", async (t) => {
test("downloadPacks-with-registries fails on 2.10.3", async (t) => {
// same thing, but this time include a registries block and
// associated env vars
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
process.env.GITHUB_TOKEN = "not-a-token";
process.env.CODEQL_REGISTRIES_AUTH = "not-a-registries-auth";
const logger = getRunnerLogger(true);
@@ -2628,7 +2639,7 @@ test("downloadPacks-with-registries fails on 2.10.3", async (t) => {
test("downloadPacks-with-registries fails with invalid registries block", async (t) => {
// same thing, but this time include a registries block and
// associated env vars
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
process.env.GITHUB_TOKEN = "not-a-token";
process.env.CODEQL_REGISTRIES_AUTH = "not-a-registries-auth";
const logger = getRunnerLogger(true);
@@ -2670,7 +2681,7 @@ test("downloadPacks-with-registries fails with invalid registries block", async
// the happy path for generateRegistries is already tested in downloadPacks.
// these following tests are for the error cases and when nothing is generated.
test("no generateRegistries when CLI is too old", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
const registriesInput = yaml.dump([
{
// no slash
@@ -2698,7 +2709,7 @@ test("no generateRegistries when CLI is too old", async (t) => {
});
});
test("no generateRegistries when registries is undefined", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
const registriesInput = undefined;
const codeQL = setCodeQL({
// Accepted CLI versions are 2.10.4 or higher
@@ -2719,7 +2730,7 @@ test("no generateRegistries when registries is undefined", async (t) => {
});
test("generateRegistries prefers original CODEQL_REGISTRIES_AUTH", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
return await withTmpDir(async (tmpDir) => {
process.env.CODEQL_REGISTRIES_AUTH = "original";
const registriesInput = yaml.dump([
{

View File

@@ -31,6 +31,7 @@ import {
logCodeScanningConfigInCli,
ML_POWERED_JS_QUERIES_PACK_NAME,
useCodeScanningConfigInCli,
UserError,
} from "./util";
// Property names from the user-supplied config file.
@@ -300,7 +301,7 @@ function validateQueries(resolvedQueries: ResolveQueriesOutput) {
const noDeclaredLanguage = resolvedQueries.noDeclaredLanguage;
const noDeclaredLanguageQueries = Object.keys(noDeclaredLanguage);
if (noDeclaredLanguageQueries.length !== 0) {
throw new Error(
throw new UserError(
`${
"The following queries do not declare a language. " +
"Their qlpack.yml files are either missing or is invalid.\n"
@@ -313,7 +314,7 @@ function validateQueries(resolvedQueries: ResolveQueriesOutput) {
multipleDeclaredLanguages
);
if (multipleDeclaredLanguagesQueries.length !== 0) {
throw new Error(
throw new UserError(
`${
"The following queries declare multiple languages. " +
"Their qlpack.yml files are either missing or is invalid.\n"
@@ -404,7 +405,7 @@ async function addBuiltinSuiteQueries(
let injectedMlQueries = false;
const found = builtinSuites.find((suite) => suite === suiteName);
if (!found) {
throw new Error(getQueryUsesInvalid(configFile, suiteName));
throw new UserError(getQueryUsesInvalid(configFile, suiteName));
}
if (
suiteName === "security-experimental" &&
@@ -413,7 +414,7 @@ async function addBuiltinSuiteQueries(
CODEQL_VERSION_SECURITY_EXPERIMENTAL_SUITE
))
) {
throw new Error(
throw new UserError(
`The 'security-experimental' suite is not supported on CodeQL CLI versions earlier than
${CODEQL_VERSION_SECURITY_EXPERIMENTAL_SUITE}. Please upgrade to CodeQL CLI version
${CODEQL_VERSION_SECURITY_EXPERIMENTAL_SUITE} or later.`
@@ -469,7 +470,7 @@ async function addLocalQueries(
// Check the file exists
if (!fs.existsSync(absoluteQueryPath)) {
throw new Error(getLocalPathDoesNotExist(configFile, localQueryPath));
throw new UserError(getLocalPathDoesNotExist(configFile, localQueryPath));
}
// Call this after checking file exists, because it'll fail if file doesn't exist
@@ -481,7 +482,7 @@ async function addLocalQueries(
fs.realpathSync(workspacePath) + path.sep
)
) {
throw new Error(
throw new UserError(
getLocalPathOutsideOfRepository(configFile, localQueryPath)
);
}
@@ -510,7 +511,7 @@ async function addRemoteQueries(
) {
let tok = queryUses.split("@");
if (tok.length !== 2) {
throw new Error(getQueryUsesInvalid(configFile, queryUses));
throw new UserError(getQueryUsesInvalid(configFile, queryUses));
}
const ref = tok[1];
@@ -520,11 +521,11 @@ async function addRemoteQueries(
// The second token is the repo
// The rest is a path, if there is more than one token combine them to form the full path
if (tok.length < 2) {
throw new Error(getQueryUsesInvalid(configFile, queryUses));
throw new UserError(getQueryUsesInvalid(configFile, queryUses));
}
// Check none of the parts of the repository name are empty
if (tok[0].trim() === "" || tok[1].trim() === "") {
throw new Error(getQueryUsesInvalid(configFile, queryUses));
throw new UserError(getQueryUsesInvalid(configFile, queryUses));
}
const nwo = `${tok[0]}/${tok[1]}`;
@@ -573,7 +574,7 @@ async function parseQueryUses(
): Promise<boolean> {
queryUses = queryUses.trim();
if (queryUses === "") {
throw new Error(getQueryUsesInvalid(configFile));
throw new UserError(getQueryUsesInvalid(configFile));
}
// Check for the local path case before we start trying to parse the repository name
@@ -650,7 +651,7 @@ export function validateAndSanitisePath(
// An empty path is not allowed as it's meaningless
if (newPath === "") {
throw new Error(
throw new UserError(
getConfigFilePropertyError(
configFile,
propertyName,
@@ -662,7 +663,7 @@ export function validateAndSanitisePath(
// Check for illegal uses of **
if (newPath.match(pathStarsRegex)) {
throw new Error(
throw new UserError(
getConfigFilePropertyError(
configFile,
propertyName,
@@ -689,7 +690,7 @@ export function validateAndSanitisePath(
// This may not play nicely with project layouts.
// This restriction can be lifted later if we determine they are ok.
if (newPath.indexOf("\\") !== -1) {
throw new Error(
throw new UserError(
getConfigFilePropertyError(
configFile,
propertyName,
@@ -888,7 +889,7 @@ export async function getLanguagesInRepo(
logger: Logger
): Promise<LanguageOrAlias[]> {
logger.debug(`GitHub repo ${repository.owner} ${repository.repo}`);
const response = await api.getApiClient().repos.listLanguages({
const response = await api.getApiClient().rest.repos.listLanguages({
owner: repository.owner,
repo: repository.repo,
});
@@ -900,7 +901,7 @@ export async function getLanguagesInRepo(
// Since sets in javascript maintain insertion order, using a set here and then splatting it
// into an array gives us an array of languages ordered by popularity
const languages: Set<LanguageOrAlias> = new Set();
for (const lang of Object.keys(response.data)) {
for (const lang of Object.keys(response.data as Record<string, number>)) {
const parsedLang = parseLanguage(lang);
if (parsedLang !== undefined) {
languages.add(parsedLang);
@@ -945,7 +946,7 @@ export async function getLanguages(
// If the languages parameter was not given and no languages were
// detected then fail here as this is a workflow configuration error.
if (languages.length === 0) {
throw new Error(getNoLanguagesError());
throw new UserError(getNoLanguagesError());
}
// Make sure they are supported
@@ -964,7 +965,7 @@ export async function getLanguages(
// Any unknown languages here would have come directly from the input
// since we filter unknown languages coming from the GitHub API.
if (unknownLanguages.length > 0) {
throw new Error(getUnknownLanguagesError(unknownLanguages));
throw new UserError(getUnknownLanguagesError(unknownLanguages));
}
return parsedLanguages;
@@ -1193,10 +1194,10 @@ async function loadConfig(
// even though we don't use the value yet.
if (NAME_PROPERTY in parsedYAML) {
if (typeof parsedYAML[NAME_PROPERTY] !== "string") {
throw new Error(getNameInvalid(configFile));
throw new UserError(getNameInvalid(configFile));
}
if (parsedYAML[NAME_PROPERTY]!.length === 0) {
throw new Error(getNameInvalid(configFile));
throw new UserError(getNameInvalid(configFile));
}
}
@@ -1220,7 +1221,7 @@ async function loadConfig(
let disableDefaultQueries = false;
if (DISABLE_DEFAULT_QUERIES_PROPERTY in parsedYAML) {
if (typeof parsedYAML[DISABLE_DEFAULT_QUERIES_PROPERTY] !== "boolean") {
throw new Error(getDisableDefaultQueriesInvalid(configFile));
throw new UserError(getDisableDefaultQueriesInvalid(configFile));
}
disableDefaultQueries = parsedYAML[DISABLE_DEFAULT_QUERIES_PROPERTY]!;
}
@@ -1266,11 +1267,11 @@ async function loadConfig(
) {
const queriesArr = parsedYAML[QUERIES_PROPERTY];
if (!Array.isArray(queriesArr)) {
throw new Error(getQueriesInvalid(configFile));
throw new UserError(getQueriesInvalid(configFile));
}
for (const query of queriesArr) {
if (typeof query[QUERIES_USES_PROPERTY] !== "string") {
throw new Error(getQueriesMissingUses(configFile));
throw new UserError(getQueriesMissingUses(configFile));
}
await parseQueryUses(
languages,
@@ -1290,11 +1291,11 @@ async function loadConfig(
if (PATHS_IGNORE_PROPERTY in parsedYAML) {
if (!Array.isArray(parsedYAML[PATHS_IGNORE_PROPERTY])) {
throw new Error(getPathsIgnoreInvalid(configFile));
throw new UserError(getPathsIgnoreInvalid(configFile));
}
for (const ignorePath of parsedYAML[PATHS_IGNORE_PROPERTY]!) {
if (typeof ignorePath !== "string" || ignorePath === "") {
throw new Error(getPathsIgnoreInvalid(configFile));
throw new UserError(getPathsIgnoreInvalid(configFile));
}
pathsIgnore.push(
validateAndSanitisePath(
@@ -1309,11 +1310,11 @@ async function loadConfig(
if (PATHS_PROPERTY in parsedYAML) {
if (!Array.isArray(parsedYAML[PATHS_PROPERTY])) {
throw new Error(getPathsInvalid(configFile));
throw new UserError(getPathsInvalid(configFile));
}
for (const includePath of parsedYAML[PATHS_PROPERTY]!) {
if (typeof includePath !== "string" || includePath === "") {
throw new Error(getPathsInvalid(configFile));
throw new UserError(getPathsInvalid(configFile));
}
paths.push(
validateAndSanitisePath(includePath, PATHS_PROPERTY, configFile, logger)
@@ -1405,7 +1406,7 @@ function parseQueriesFromInput(
? rawQueriesInput.trim().slice(1).trim()
: rawQueriesInput?.trim() ?? "";
if (queriesInputCombines && trimmedInput.length === 0) {
throw new Error(
throw new UserError(
getConfigFilePropertyError(
undefined,
"queries",
@@ -1445,13 +1446,13 @@ export function parsePacksFromConfig(
} else {
// this is an error since multi-language analysis requires
// packs split by language
throw new Error(getPacksInvalidSplit(configFile));
throw new UserError(getPacksInvalidSplit(configFile));
}
}
for (const [lang, packsArr] of Object.entries(packsByLanguage)) {
if (!Array.isArray(packsArr)) {
throw new Error(getPacksInvalid(configFile));
throw new UserError(getPacksInvalid(configFile));
}
if (!languages.includes(lang as Language)) {
// This particular language is not being analyzed in this run.
@@ -1462,7 +1463,7 @@ export function parsePacksFromConfig(
continue;
} else {
// This language is invalid, probably a misspelling
throw new Error(getPacksRequireLanguage(configFile, lang));
throw new UserError(getPacksRequireLanguage(configFile, lang));
}
}
@@ -1483,18 +1484,20 @@ function parsePacksFromInput(
}
if (languages.length > 1) {
throw new Error(
throw new UserError(
"Cannot specify a 'packs' input in a multi-language analysis. Use a codeql-config.yml file instead and specify packs by language."
);
} else if (languages.length === 0) {
throw new Error("No languages specified. Cannot process the packs input.");
throw new UserError(
"No languages specified. Cannot process the packs input."
);
}
rawPacksInput = rawPacksInput.trim();
if (packsInputCombines) {
rawPacksInput = rawPacksInput.trim().substring(1).trim();
if (!rawPacksInput) {
throw new Error(
throw new UserError(
getConfigFilePropertyError(
undefined,
"packs",
@@ -1535,7 +1538,7 @@ export function parsePacksSpecification(
configFile?: string
): Pack {
if (typeof packStr !== "string") {
throw new Error(getPacksStrInvalid(packStr, configFile));
throw new UserError(getPacksStrInvalid(packStr, configFile));
}
packStr = packStr.trim();
@@ -1563,14 +1566,14 @@ export function parsePacksSpecification(
: undefined;
if (!PACK_IDENTIFIER_PATTERN.test(packName)) {
throw new Error(getPacksStrInvalid(packStr, configFile));
throw new UserError(getPacksStrInvalid(packStr, configFile));
}
if (version) {
try {
new semver.Range(version);
} catch (e) {
// The range string is invalid. OK to ignore the caught error
throw new Error(getPacksStrInvalid(packStr, configFile));
throw new UserError(getPacksStrInvalid(packStr, configFile));
}
}
@@ -1584,12 +1587,12 @@ export function parsePacksSpecification(
path.normalize(packPath).split(path.sep).join("/") !==
packPath.split(path.sep).join("/"))
) {
throw new Error(getPacksStrInvalid(packStr, configFile));
throw new UserError(getPacksStrInvalid(packStr, configFile));
}
if (!packPath && pathStart) {
// 0 length path
throw new Error(getPacksStrInvalid(packStr, configFile));
throw new UserError(getPacksStrInvalid(packStr, configFile));
}
return {
@@ -1635,7 +1638,7 @@ export function parsePacks(
}
if (!packsInputCombines) {
if (!packsFromInput) {
throw new Error(getPacksInvalid(configFile));
throw new UserError(getPacksInvalid(configFile));
}
return packsFromInput;
}
@@ -1775,7 +1778,7 @@ export async function initConfig(
const hasCustomQueries = config.queries[language]?.custom.length > 0;
const hasPacks = (config.packs[language]?.length || 0) > 0;
if (!hasPacks && !hasBuiltinQueries && !hasCustomQueries) {
throw new Error(
throw new UserError(
`Did not detect any queries to run for ${language}. ` +
"Please make sure that the default queries are enabled, or you are specifying queries to run."
);
@@ -1806,7 +1809,7 @@ function parseRegistries(
? (yaml.load(registriesInput) as RegistryConfigWithCredentials[])
: undefined;
} catch (e) {
throw new Error("Invalid registries input. Must be a YAML string.");
throw new UserError("Invalid registries input. Must be a YAML string.");
}
}
@@ -1822,12 +1825,12 @@ function isLocal(configPath: string): boolean {
function getLocalConfig(configFile: string, workspacePath: string): UserConfig {
// Error if the config file is now outside of the workspace
if (!(configFile + path.sep).startsWith(workspacePath + path.sep)) {
throw new Error(getConfigFileOutsideWorkspaceErrorMessage(configFile));
throw new UserError(getConfigFileOutsideWorkspaceErrorMessage(configFile));
}
// Error if the file does not exist
if (!fs.existsSync(configFile)) {
throw new Error(getConfigFileDoesNotExistErrorMessage(configFile));
throw new UserError(getConfigFileDoesNotExistErrorMessage(configFile));
}
return yaml.load(fs.readFileSync(configFile, "utf8")) as UserConfig;
@@ -1844,12 +1847,12 @@ async function getRemoteConfig(
const pieces = format.exec(configFile);
// 5 = 4 groups + the whole expression
if (pieces === null || pieces.groups === undefined || pieces.length < 5) {
throw new Error(getConfigFileRepoFormatInvalidMessage(configFile));
throw new UserError(getConfigFileRepoFormatInvalidMessage(configFile));
}
const response = await api
.getApiClientWithExternalAuth(apiDetails)
.repos.getContent({
.rest.repos.getContent({
owner: pieces.groups.owner,
repo: pieces.groups.repo,
path: pieces.groups.path,
@@ -1860,9 +1863,9 @@ async function getRemoteConfig(
if ("content" in response.data && response.data.content !== undefined) {
fileContents = response.data.content;
} else if (Array.isArray(response.data)) {
throw new Error(getConfigFileDirectoryGivenMessage(configFile));
throw new UserError(getConfigFileDirectoryGivenMessage(configFile));
} else {
throw new Error(getConfigFileFormatInvalidMessage(configFile));
throw new UserError(getConfigFileFormatInvalidMessage(configFile));
}
return yaml.load(
@@ -1986,7 +1989,7 @@ export async function generateRegistries(
if (
!(await codeQlVersionAbove(codeQL, CODEQL_VERSION_GHES_PACK_DOWNLOAD))
) {
throw new Error(
throw new UserError(
`The 'registries' input is not supported on CodeQL CLI versions earlier than ${CODEQL_VERSION_GHES_PACK_DOWNLOAD}. Please upgrade to CodeQL CLI version ${CODEQL_VERSION_GHES_PACK_DOWNLOAD} or later.`
);
}
@@ -2025,7 +2028,7 @@ function createRegistriesBlock(registries: RegistryConfigWithCredentials[]): {
!Array.isArray(registries) ||
registries.some((r) => !r.url || !r.packages)
) {
throw new Error(
throw new UserError(
"Invalid 'registries' input. Must be an array of objects with 'url' and 'packages' properties."
);
}

View File

@@ -39,6 +39,7 @@ export enum Feature {
MlPoweredQueriesEnabled = "ml_powered_queries_enabled",
NewAnalysisSummaryEnabled = "new_analysis_summary_enabled",
QaTelemetryEnabled = "qa_telemetry_enabled",
ScalingReservedRam = "scaling_reserved_ram",
UploadFailedSarifEnabled = "upload_failed_sarif_enabled",
}
@@ -76,6 +77,11 @@ export const featureConfig: Record<
minimumVersion: undefined,
defaultValue: false,
},
[Feature.ScalingReservedRam]: {
envVar: "CODEQL_ACTION_SCALING_RESERVED_RAM",
minimumVersion: undefined,
defaultValue: false,
},
[Feature.UploadFailedSarifEnabled]: {
envVar: "CODEQL_ACTION_UPLOAD_FAILED_SARIF",
minimumVersion: "2.11.3",
@@ -417,7 +423,7 @@ class GitHubFeatureFlags {
"This run of the CodeQL Action does not have permission to access Code Scanning API endpoints. " +
"As a result, it will not be opted into any experimental features. " +
"This could be because the Action is running on a pull request from a fork. If not, " +
`please ensure the Action has the 'security-events: write' permission. Details: ${e}`
`please ensure the Action has the 'security-events: write' permission. Details: ${e.message}`
);
this.hasAccessedRemoteFeatureFlags = false;
return {};

View File

@@ -329,7 +329,7 @@ async function run() {
core.exportVariable(
"CODEQL_RAM",
process.env["CODEQL_RAM"] ||
getMemoryFlagValue(getOptionalInput("ram")).toString()
(await getMemoryFlagValue(getOptionalInput("ram"), features)).toString()
);
core.exportVariable(
"CODEQL_THREADS",

View File

@@ -82,7 +82,7 @@ export async function tryFindCliVersionDotcomOnly(
);
const apiClient = api.getApiClient();
const codeQLActionRepository = getCodeQLActionRepository(logger);
const release = await apiClient.repos.getReleaseByTag({
const release = await apiClient.rest.repos.getReleaseByTag({
owner: codeQLActionRepository.split("/")[0],
repo: codeQLActionRepository.split("/")[1],
tag: tagName,
@@ -165,7 +165,7 @@ async function getCodeQLBundleDownloadURL(
}
const [repositoryOwner, repositoryName] = repository.split("/");
try {
const release = await api.getApiClient().repos.getReleaseByTag({
const release = await api.getApiClient().rest.repos.getReleaseByTag({
owner: repositoryOwner,
repo: repositoryName,
tag: tagName,

View File

@@ -199,8 +199,10 @@ export function mockLanguagesInRepo(languages: string[]) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
mockClient.returns({
repos: {
listLanguages,
rest: {
repos: {
listLanguages,
},
},
} as any);
return listLanguages;

View File

@@ -8,8 +8,14 @@ import * as sinon from "sinon";
import * as api from "./api-client";
import { Config } from "./config-utils";
import { Feature } from "./feature-flags";
import { getRunnerLogger } from "./logging";
import { getRecordingLogger, LoggedMessage, setupTests } from "./testing-utils";
import {
createFeatures,
getRecordingLogger,
LoggedMessage,
setupTests,
} from "./testing-utils";
import * as util from "./util";
setupTests(test);
@@ -23,25 +29,37 @@ test("getToolNames", (t) => {
t.deepEqual(toolNames, ["CodeQL command-line toolchain", "ESLint"]);
});
test("getMemoryFlag() should return the correct --ram flag", (t) => {
const totalMem = Math.floor(os.totalmem() / (1024 * 1024));
const expectedThreshold = process.platform === "win32" ? 1536 : 1024;
test("getMemoryFlag() should return the correct --ram flag", async (t) => {
const totalMem = os.totalmem() / (1024 * 1024);
const fixedAmount = process.platform === "win32" ? 1536 : 1024;
const scaledAmount = 0.02 * totalMem;
const expectedMemoryValue = Math.floor(totalMem - fixedAmount);
const expectedMemoryValueWithScaling = Math.floor(
totalMem - fixedAmount - scaledAmount
);
const tests: Array<[string | undefined, string]> = [
[undefined, `--ram=${totalMem - expectedThreshold}`],
["", `--ram=${totalMem - expectedThreshold}`],
["512", "--ram=512"],
const tests: Array<[string | undefined, boolean, string]> = [
[undefined, false, `--ram=${expectedMemoryValue}`],
["", false, `--ram=${expectedMemoryValue}`],
["512", false, "--ram=512"],
[undefined, true, `--ram=${expectedMemoryValueWithScaling}`],
["", true, `--ram=${expectedMemoryValueWithScaling}`],
];
for (const [input, expectedFlag] of tests) {
const flag = util.getMemoryFlag(input);
for (const [input, withScaling, expectedFlag] of tests) {
const features = createFeatures(
withScaling ? [Feature.ScalingReservedRam] : []
);
const flag = await util.getMemoryFlag(input, features);
t.deepEqual(flag, expectedFlag);
}
});
test("getMemoryFlag() throws if the ram input is < 0 or NaN", (t) => {
test("getMemoryFlag() throws if the ram input is < 0 or NaN", async (t) => {
for (const input of ["-1", "hello!"]) {
t.throws(() => util.getMemoryFlag(input));
await t.throwsAsync(
async () => await util.getMemoryFlag(input, createFeatures([]))
);
}
});
@@ -196,7 +214,7 @@ function mockGetMetaVersionHeader(
},
};
const spyGetContents = sinon
.stub(client.meta, "get")
.stub(client.rest.meta, "get")
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
.resolves(response as any);
sinon.stub(api, "getApiClient").value(() => client);

View File

@@ -157,9 +157,21 @@ export async function withTmpDir<T>(
* from committing too much of the available memory to CodeQL.
* @returns number
*/
function getSystemReservedMemoryMegaBytes(): number {
async function getSystemReservedMemoryMegaBytes(
totalMemoryMegaBytes: number,
features: FeatureEnablement
): Promise<number> {
// Windows needs more memory for OS processes.
return 1024 * (process.platform === "win32" ? 1.5 : 1);
const fixedAmount = 1024 * (process.platform === "win32" ? 1.5 : 1);
if (await features.getValue(Feature.ScalingReservedRam)) {
// Reserve an additional 2% of the total memory, since the amount used by
// the kernel for page tables scales with the size of physical memory.
const scaledAmount = 0.02 * totalMemoryMegaBytes;
return fixedAmount + scaledAmount;
} else {
return fixedAmount;
}
}
/**
@@ -169,7 +181,10 @@ function getSystemReservedMemoryMegaBytes(): number {
*
* @returns {number} the amount of RAM to use, in megabytes
*/
export function getMemoryFlagValue(userInput: string | undefined): number {
export async function getMemoryFlagValue(
userInput: string | undefined,
features: FeatureEnablement
): Promise<number> {
let memoryToUseMegaBytes: number;
if (userInput) {
memoryToUseMegaBytes = Number(userInput);
@@ -179,7 +194,10 @@ export function getMemoryFlagValue(userInput: string | undefined): number {
} else {
const totalMemoryBytes = os.totalmem();
const totalMemoryMegaBytes = totalMemoryBytes / (1024 * 1024);
const reservedMemoryMegaBytes = getSystemReservedMemoryMegaBytes();
const reservedMemoryMegaBytes = await getSystemReservedMemoryMegaBytes(
totalMemoryMegaBytes,
features
);
memoryToUseMegaBytes = totalMemoryMegaBytes - reservedMemoryMegaBytes;
}
return Math.floor(memoryToUseMegaBytes);
@@ -192,8 +210,12 @@ export function getMemoryFlagValue(userInput: string | undefined): number {
*
* @returns string
*/
export function getMemoryFlag(userInput: string | undefined): string {
return `--ram=${getMemoryFlagValue(userInput)}`;
export async function getMemoryFlag(
userInput: string | undefined,
features: FeatureEnablement
): Promise<string> {
const megabytes = await getMemoryFlagValue(userInput, features);
return `--ram=${megabytes}`;
}
/**
@@ -344,7 +366,7 @@ export async function getGitHubVersion(
// Doesn't strictly have to be the meta endpoint as we're only
// using the response headers which are available on every request.
const apiClient = getApiClient();
const response = await apiClient.meta.get();
const response = await apiClient.rest.meta.get();
// This happens on dotcom, although we expect to have already returned in that
// case. This can also serve as a fallback in cases we haven't foreseen.