Merge pull request #1746 from github/update-v2.20.2-7dfbc0e0d

Merge main into releases/v2
This commit is contained in:
Angela P Wen 2023-07-03 03:58:33 -07:00 committed by GitHub
commit 004c5de30b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 549 additions and 209 deletions

View file

@ -1,5 +1,9 @@
# CodeQL Action Changelog
## 2.20.2 - 03 Jul 2023
No user facing changes.
## 2.20.1 - 21 Jun 2023
- Update default CodeQL bundle version to 2.13.4. [#1721](https://github.com/github/codeql-action/pull/1721)

File diff suppressed because one or more lines are too long

6
lib/analyze-action.js generated
View file

@ -44,7 +44,7 @@ const logging_1 = require("./logging");
const repository_1 = require("./repository");
const shared_environment_1 = require("./shared-environment");
const trap_caching_1 = require("./trap-caching");
const upload_lib = __importStar(require("./upload-lib"));
const uploadLib = __importStar(require("./upload-lib"));
const util = __importStar(require("./util"));
const util_1 = require("./util");
async function sendStatusReport(startedAt, config, stats, error, trapCacheUploadTime, dbCreationTimings, didUploadTrapCaches, logger) {
@ -177,7 +177,7 @@ async function run() {
core.setOutput("db-locations", dbLocations);
const uploadInput = actionsUtil.getOptionalInput("upload");
if (runStats && actionsUtil.getUploadValue(uploadInput) === "always") {
uploadResult = await upload_lib.uploadFromActions(outputDir, actionsUtil.getRequiredInput("checkout_path"), actionsUtil.getOptionalInput("category"), logger);
uploadResult = await uploadLib.uploadFromActions(outputDir, actionsUtil.getRequiredInput("checkout_path"), actionsUtil.getOptionalInput("category"), logger);
core.setOutput("sarif-id", uploadResult.sarifID);
}
else {
@ -196,7 +196,7 @@ async function run() {
}
else if (uploadResult !== undefined &&
actionsUtil.getRequiredInput("wait-for-processing") === "true") {
await upload_lib.waitForProcessing((0, repository_1.parseRepositoryNwo)(util.getRequiredEnvParam("GITHUB_REPOSITORY")), uploadResult.sarifID, (0, logging_1.getActionsLogger)());
await uploadLib.waitForProcessing((0, repository_1.parseRepositoryNwo)(util.getRequiredEnvParam("GITHUB_REPOSITORY")), uploadResult.sarifID, (0, logging_1.getActionsLogger)());
}
// If we did not throw an error yet here, but we expect one, throw it.
if (actionsUtil.getOptionalInput("expect-error") === "true") {

File diff suppressed because one or more lines are too long

51
lib/analyze.js generated
View file

@ -39,6 +39,7 @@ const configUtils = __importStar(require("./config-utils"));
const feature_flags_1 = require("./feature-flags");
const languages_1 = require("./languages");
const tracer_config_1 = require("./tracer-config");
const upload_lib_1 = require("./upload-lib");
const util = __importStar(require("./util"));
class CodeQLAnalysisError extends Error {
constructor(queriesStatusReport, message) {
@ -138,6 +139,9 @@ async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag,
const queryFilters = validateQueryFilters(config.originalUserInput["query-filters"]);
const packsWithVersion = config.packs[language] || [];
try {
const sarifFile = path.join(sarifFolder, `${language}.sarif`);
let startTimeInterpretResults;
let endTimeInterpretResults;
if (await util.useCodeScanningConfigInCli(codeql, features)) {
// If we are using the code scanning config in the CLI,
// much of the work needed to generate the query suites
@ -152,11 +156,11 @@ async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag,
statusReport[`analyze_builtin_queries_${language}_duration_ms`] =
new Date().getTime() - startTimeBuiltIn;
logger.startGroup(`Interpreting results for ${language}`);
const startTimeInterpretResults = new Date().getTime();
const sarifFile = path.join(sarifFolder, `${language}.sarif`);
startTimeInterpretResults = new Date().getTime();
const analysisSummary = await runInterpretResults(language, undefined, sarifFile, config.debugMode);
endTimeInterpretResults = new Date().getTime();
statusReport[`interpret_results_${language}_duration_ms`] =
new Date().getTime() - startTimeInterpretResults;
endTimeInterpretResults - startTimeInterpretResults;
logger.endGroup();
logger.info(analysisSummary);
}
@ -201,14 +205,29 @@ async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag,
}
logger.endGroup();
logger.startGroup(`Interpreting results for ${language}`);
const startTimeInterpretResults = new Date().getTime();
const sarifFile = path.join(sarifFolder, `${language}.sarif`);
startTimeInterpretResults = new Date().getTime();
const analysisSummary = await runInterpretResults(language, querySuitePaths, sarifFile, config.debugMode);
endTimeInterpretResults = new Date().getTime();
statusReport[`interpret_results_${language}_duration_ms`] =
new Date().getTime() - startTimeInterpretResults;
endTimeInterpretResults - startTimeInterpretResults;
logger.endGroup();
logger.info(analysisSummary);
}
if (await features.getValue(feature_flags_1.Feature.QaTelemetryEnabled)) {
const perQueryAlertCounts = getPerQueryAlertCounts(sarifFile, logger);
const perQueryAlertCountEventReport = {
event: "codeql database interpret-results",
started_at: startTimeInterpretResults.toString(),
completed_at: endTimeInterpretResults.toString(),
exit_status: "success",
language,
properties: perQueryAlertCounts,
};
if (statusReport["event_reports"] === undefined) {
statusReport["event_reports"] = [];
}
statusReport["event_reports"].push(perQueryAlertCountEventReport);
}
await runPrintLinesOfCode(language);
}
catch (e) {
@ -225,6 +244,26 @@ async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag,
const databasePath = util.getCodeQLDatabasePath(config, language);
return await codeql.databaseInterpretResults(databasePath, queries, sarifFile, addSnippetsFlag, threadsFlag, enableDebugLogging ? "-vv" : "-v", automationDetailsId, config, features, logger);
}
/** Get an object with all queries and their counts parsed from a SARIF file path. */
function getPerQueryAlertCounts(sarifPath, log) {
(0, upload_lib_1.validateSarifFileSchema)(sarifPath, log);
const sarifObject = JSON.parse(fs.readFileSync(sarifPath, "utf8"));
// We do not need to compute fingerprints because we are not sending data based off of locations.
// Generate the query: alert count object
const perQueryAlertCounts = {};
// All rules (queries), from all results, from all runs
for (const sarifRun of sarifObject.runs) {
if (sarifRun.results) {
for (const result of sarifRun.results) {
const query = result.rule?.id || result.ruleId;
if (query) {
perQueryAlertCounts[query] = (perQueryAlertCounts[query] || 0) + 1;
}
}
}
}
return perQueryAlertCounts;
}
async function runPrintLinesOfCode(language) {
const databasePath = util.getCodeQLDatabasePath(config, language);
return await codeql.databasePrintBaseline(databasePath);

File diff suppressed because one or more lines are too long

37
lib/analyze.test.js generated
View file

@ -37,11 +37,16 @@ const feature_flags_1 = require("./feature-flags");
const languages_1 = require("./languages");
const logging_1 = require("./logging");
const testing_utils_1 = require("./testing-utils");
const uploadLib = __importStar(require("./upload-lib"));
const util = __importStar(require("./util"));
(0, testing_utils_1.setupTests)(ava_1.default);
// Checks that the duration fields are populated for the correct language
// and correct case of builtin or custom. Also checks the correct search
// paths are set in the database analyze invocation.
/** Checks that the duration fields are populated for the correct language
* and correct case of builtin or custom. Also checks the correct search
* paths are set in the database analyze invocation.
*
* Mocks the QA telemetry feature flag and checks the appropriate status report
* fields.
*/
(0, ava_1.default)("status report fields and search path setting", async (t) => {
let searchPathsUsed = [];
return await util.withTmpDir(async (tmpDir) => {
@ -53,6 +58,7 @@ const util = __importStar(require("./util"));
[languages_1.Language.cpp]: ["a/b@1.0.0"],
[languages_1.Language.java]: ["c/d@2.0.0"],
};
sinon.stub(uploadLib, "validateSarifFileSchema");
for (const language of Object.values(languages_1.Language)) {
(0, codeql_1.setCodeQL)({
packDownload: async () => ({ packs: [] }),
@ -130,18 +136,25 @@ const util = __importStar(require("./util"));
builtin: ["foo.ql"],
custom: [],
};
const builtinStatusReport = await (0, analyze_1.runQueries)(tmpDir, memoryFlag, addSnippetsFlag, threadsFlag, undefined, config, (0, logging_1.getRunnerLogger)(true), (0, testing_utils_1.createFeatures)([]));
const builtinStatusReport = await (0, analyze_1.runQueries)(tmpDir, memoryFlag, addSnippetsFlag, threadsFlag, undefined, config, (0, logging_1.getRunnerLogger)(true), (0, testing_utils_1.createFeatures)([feature_flags_1.Feature.QaTelemetryEnabled]));
const hasPacks = language in packs;
const statusReportKeys = Object.keys(builtinStatusReport).sort();
if (hasPacks) {
t.deepEqual(statusReportKeys.length, 3, statusReportKeys.toString());
t.deepEqual(statusReportKeys.length, 4, statusReportKeys.toString());
t.deepEqual(statusReportKeys[0], `analyze_builtin_queries_${language}_duration_ms`);
t.deepEqual(statusReportKeys[1], `analyze_custom_queries_${language}_duration_ms`);
t.deepEqual(statusReportKeys[2], `interpret_results_${language}_duration_ms`);
t.deepEqual(statusReportKeys[2], "event_reports");
t.deepEqual(statusReportKeys[3], `interpret_results_${language}_duration_ms`);
}
else {
t.deepEqual(statusReportKeys[0], `analyze_builtin_queries_${language}_duration_ms`);
t.deepEqual(statusReportKeys[1], `interpret_results_${language}_duration_ms`);
t.deepEqual(statusReportKeys[1], "event_reports");
t.deepEqual(statusReportKeys[2], `interpret_results_${language}_duration_ms`);
}
if (builtinStatusReport.event_reports) {
for (const eventReport of builtinStatusReport.event_reports) {
t.deepEqual(eventReport.event, "codeql database interpret-results");
}
}
config.queries[language] = {
builtin: [],
@ -156,14 +169,20 @@ const util = __importStar(require("./util"));
},
],
};
const customStatusReport = await (0, analyze_1.runQueries)(tmpDir, memoryFlag, addSnippetsFlag, threadsFlag, undefined, config, (0, logging_1.getRunnerLogger)(true), (0, testing_utils_1.createFeatures)([]));
t.deepEqual(Object.keys(customStatusReport).length, 2);
const customStatusReport = await (0, analyze_1.runQueries)(tmpDir, memoryFlag, addSnippetsFlag, threadsFlag, undefined, config, (0, logging_1.getRunnerLogger)(true), (0, testing_utils_1.createFeatures)([feature_flags_1.Feature.QaTelemetryEnabled]));
t.deepEqual(Object.keys(customStatusReport).length, 3);
t.true(`analyze_custom_queries_${language}_duration_ms` in customStatusReport);
const expectedSearchPathsUsed = hasPacks
? [undefined, undefined, "/1", "/2", undefined]
: [undefined, "/1", "/2"];
t.deepEqual(searchPathsUsed, expectedSearchPathsUsed);
t.true(`interpret_results_${language}_duration_ms` in customStatusReport);
t.true("event_reports" in customStatusReport);
if (customStatusReport.event_reports) {
for (const eventReport of customStatusReport.event_reports) {
t.deepEqual(eventReport.event, "codeql database interpret-results");
}
}
}
verifyQuerySuites(tmpDir);
});

File diff suppressed because one or more lines are too long

6
lib/feature-flags.js generated
View file

@ -40,6 +40,7 @@ var Feature;
Feature["ExportCodeScanningConfigEnabled"] = "export_code_scanning_config_enabled";
Feature["ExportDiagnosticsEnabled"] = "export_diagnostics_enabled";
Feature["MlPoweredQueriesEnabled"] = "ml_powered_queries_enabled";
Feature["QaTelemetryEnabled"] = "qa_telemetry_enabled";
Feature["UploadFailedSarifEnabled"] = "upload_failed_sarif_enabled";
})(Feature = exports.Feature || (exports.Feature = {}));
exports.featureConfig = {
@ -68,6 +69,11 @@ exports.featureConfig = {
minimumVersion: "2.7.5",
defaultValue: false,
},
[Feature.QaTelemetryEnabled]: {
envVar: "CODEQL_ACTION_QA_TELEMETRY",
minimumVersion: undefined,
defaultValue: false,
},
[Feature.UploadFailedSarifEnabled]: {
envVar: "CODEQL_ACTION_UPLOAD_FAILED_SARIF",
minimumVersion: "2.11.3",

File diff suppressed because one or more lines are too long

4
lib/upload-lib.js generated
View file

@ -26,7 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.pruneInvalidResults = exports.validateUniqueCategory = exports.waitForProcessing = exports.buildPayload = exports.validateSarifFileSchema = exports.countResultsInSarif = exports.uploadFromActions = exports.findSarifFilesInDir = exports.populateRunAutomationDetails = exports.combineSarifFiles = void 0;
exports.pruneInvalidResults = exports.validateUniqueCategory = exports.waitForProcessing = exports.buildPayload = exports.validateSarifFileSchema = exports.uploadFromActions = exports.findSarifFilesInDir = exports.populateRunAutomationDetails = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const process_1 = require("process");
@ -62,7 +62,6 @@ function combineSarifFiles(sarifFiles) {
}
return combinedSarif;
}
exports.combineSarifFiles = combineSarifFiles;
// Populates the run.automationDetails.id field using the analysis_key and environment
// and return an updated sarif file contents.
function populateRunAutomationDetails(sarif, category, analysis_key, environment) {
@ -174,7 +173,6 @@ function countResultsInSarif(sarif) {
}
return numResults;
}
exports.countResultsInSarif = countResultsInSarif;
// Validates that the given file path refers to a valid SARIF file.
// Throws an error if the file is invalid.
function validateSarifFileSchema(sarifFilePath, logger) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

8
node_modules/.package-lock.json generated vendored
View file

@ -1,6 +1,6 @@
{
"name": "codeql",
"version": "2.20.1",
"version": "2.20.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
@ -5221,9 +5221,9 @@
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"node_modules/semver": {
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
"version": "7.5.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz",
"integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==",
"dependencies": {
"lru-cache": "^6.0.0"
},

67
node_modules/semver/README.md generated vendored
View file

@ -110,6 +110,9 @@ Options:
-l --loose
Interpret versions and ranges loosely
-n <0|1>
This is the base to be used for the prerelease identifier.
-p --include-prerelease
Always include prerelease versions in range matching
@ -232,6 +235,35 @@ $ semver 1.2.4-beta.0 -i prerelease
1.2.4-beta.1
```
#### Prerelease Identifier Base
The method `.inc` takes an optional parameter 'identifierBase' string
that will let you let your prerelease number as zero-based or one-based.
Set to `false` to omit the prerelease number altogether.
If you do not specify this parameter, it will default to zero-based.
```javascript
semver.inc('1.2.3', 'prerelease', 'beta', '1')
// '1.2.4-beta.1'
```
```javascript
semver.inc('1.2.3', 'prerelease', 'beta', false)
// '1.2.4-beta'
```
command-line example:
```bash
$ semver 1.2.3 -i prerelease --preid beta -n 1
1.2.4-beta.1
```
```bash
$ semver 1.2.3 -i prerelease --preid beta -n false
1.2.4-beta
```
### Advanced Range Syntax
Advanced range syntax desugars to primitive comparators in
@ -513,6 +545,40 @@ ex.
* `s.clean(' 2.1.5 ')`: `'2.1.5'`
* `s.clean('~1.0.0')`: `null`
## Constants
As a convenience, helper constants are exported to provide information about what `node-semver` supports:
### `RELEASE_TYPES`
- major
- premajor
- minor
- preminor
- patch
- prepatch
- prerelease
```
const semver = require('semver');
if (semver.RELEASE_TYPES.includes(arbitraryUserInput)) {
console.log('This is a valid release type!');
} else {
console.warn('This is NOT a valid release type!');
}
```
### `SEMVER_SPEC_VERSION`
2.0.0
```
const semver = require('semver');
console.log('We are currently using the semver specification version:', semver.SEMVER_SPEC_VERSION);
```
## Exported Modules
<!--
@ -566,3 +632,4 @@ The following modules are available:
* `require('semver/ranges/outside')`
* `require('semver/ranges/to-comparators')`
* `require('semver/ranges/valid')`

18
node_modules/semver/bin/semver.js generated vendored
View file

@ -23,7 +23,10 @@ let rtl = false
let identifier
let identifierBase
const semver = require('../')
const parseOptions = require('../internal/parse-options')
let reverse = false
@ -71,6 +74,12 @@ const main = () => {
case '-r': case '--range':
range.push(argv.shift())
break
case '-n':
identifierBase = argv.shift()
if (identifierBase === 'false') {
identifierBase = false
}
break
case '-c': case '--coerce':
coerce = true
break
@ -88,7 +97,7 @@ const main = () => {
}
}
options = { loose: loose, includePrerelease: includePrerelease, rtl: rtl }
options = parseOptions({ loose, includePrerelease, rtl })
versions = versions.map((v) => {
return coerce ? (semver.coerce(v, options) || { version: v }).version : v
@ -127,7 +136,7 @@ const success = () => {
}).map((v) => {
return semver.clean(v, options)
}).map((v) => {
return inc ? semver.inc(v, inc, options, identifier) : v
return inc ? semver.inc(v, inc, options, identifier, identifierBase) : v
}).forEach((v, i, _) => {
console.log(v)
})
@ -172,6 +181,11 @@ Options:
--ltr
Coerce version strings left to right (default)
-n <base>
Base number to be used for the prerelease identifier.
Can be either 0 or 1, or false to omit the number altogether.
Defaults to 0.
Program exits successfully if any valid version satisfies
all supplied ranges, and prints all satisfying versions.

View file

@ -16,6 +16,7 @@ class Comparator {
}
}
comp = comp.trim().split(/\s+/).join(' ')
debug('comparator', comp, options)
this.options = options
this.loose = !!options.loose
@ -78,13 +79,6 @@ class Comparator {
throw new TypeError('a Comparator is required')
}
if (!options || typeof options !== 'object') {
options = {
loose: !!options,
includePrerelease: false,
}
}
if (this.operator === '') {
if (this.value === '') {
return true
@ -97,39 +91,50 @@ class Comparator {
return new Range(this.value, options).test(comp.semver)
}
const sameDirectionIncreasing =
(this.operator === '>=' || this.operator === '>') &&
(comp.operator === '>=' || comp.operator === '>')
const sameDirectionDecreasing =
(this.operator === '<=' || this.operator === '<') &&
(comp.operator === '<=' || comp.operator === '<')
const sameSemVer = this.semver.version === comp.semver.version
const differentDirectionsInclusive =
(this.operator === '>=' || this.operator === '<=') &&
(comp.operator === '>=' || comp.operator === '<=')
const oppositeDirectionsLessThan =
cmp(this.semver, '<', comp.semver, options) &&
(this.operator === '>=' || this.operator === '>') &&
(comp.operator === '<=' || comp.operator === '<')
const oppositeDirectionsGreaterThan =
cmp(this.semver, '>', comp.semver, options) &&
(this.operator === '<=' || this.operator === '<') &&
(comp.operator === '>=' || comp.operator === '>')
options = parseOptions(options)
return (
sameDirectionIncreasing ||
sameDirectionDecreasing ||
(sameSemVer && differentDirectionsInclusive) ||
oppositeDirectionsLessThan ||
oppositeDirectionsGreaterThan
)
// Special cases where nothing can possibly be lower
if (options.includePrerelease &&
(this.value === '<0.0.0-0' || comp.value === '<0.0.0-0')) {
return false
}
if (!options.includePrerelease &&
(this.value.startsWith('<0.0.0') || comp.value.startsWith('<0.0.0'))) {
return false
}
// Same direction increasing (> or >=)
if (this.operator.startsWith('>') && comp.operator.startsWith('>')) {
return true
}
// Same direction decreasing (< or <=)
if (this.operator.startsWith('<') && comp.operator.startsWith('<')) {
return true
}
// same SemVer and both sides are inclusive (<= or >=)
if (
(this.semver.version === comp.semver.version) &&
this.operator.includes('=') && comp.operator.includes('=')) {
return true
}
// opposite directions less than
if (cmp(this.semver, '<', comp.semver, options) &&
this.operator.startsWith('>') && comp.operator.startsWith('<')) {
return true
}
// opposite directions greater than
if (cmp(this.semver, '>', comp.semver, options) &&
this.operator.startsWith('<') && comp.operator.startsWith('>')) {
return true
}
return false
}
}
module.exports = Comparator
const parseOptions = require('../internal/parse-options')
const { re, t } = require('../internal/re')
const { safeRe: re, t } = require('../internal/re')
const cmp = require('../functions/cmp')
const debug = require('../internal/debug')
const SemVer = require('./semver')

72
node_modules/semver/classes/range.js generated vendored
View file

@ -26,19 +26,26 @@ class Range {
this.loose = !!options.loose
this.includePrerelease = !!options.includePrerelease
// First, split based on boolean or ||
// First reduce all whitespace as much as possible so we do not have to rely
// on potentially slow regexes like \s*. This is then stored and used for
// future error messages as well.
this.raw = range
this.set = range
.trim()
.split(/\s+/)
.join(' ')
// First, split on ||
this.set = this.raw
.split('||')
// map the range to a 2d array of comparators
.map(r => this.parseRange(r.trim()))
.map(r => this.parseRange(r))
// throw out any comparator lists that are empty
// this generally means that it was not a valid range, which is allowed
// in loose mode, but will still throw if the WHOLE range is invalid.
.filter(c => c.length)
if (!this.set.length) {
throw new TypeError(`Invalid SemVer Range: ${range}`)
throw new TypeError(`Invalid SemVer Range: ${this.raw}`)
}
// if we have any that are not the null set, throw out null sets.
@ -64,9 +71,7 @@ class Range {
format () {
this.range = this.set
.map((comps) => {
return comps.join(' ').trim()
})
.map((comps) => comps.join(' ').trim())
.join('||')
.trim()
return this.range
@ -77,12 +82,12 @@ class Range {
}
parseRange (range) {
range = range.trim()
// memoize range parsing for performance.
// this is a very hot path, and fully deterministic.
const memoOpts = Object.keys(this.options).join(',')
const memoKey = `parseRange:${memoOpts}:${range}`
const memoOpts =
(this.options.includePrerelease && FLAG_INCLUDE_PRERELEASE) |
(this.options.loose && FLAG_LOOSE)
const memoKey = memoOpts + ':' + range
const cached = cache.get(memoKey)
if (cached) {
return cached
@ -103,9 +108,6 @@ class Range {
// `^ 1.2.3` => `^1.2.3`
range = range.replace(re[t.CARETTRIM], caretTrimReplace)
// normalize spaces
range = range.split(/\s+/).join(' ')
// At this point, the range is completely trimmed and
// ready to be split into comparators.
@ -190,6 +192,7 @@ class Range {
return false
}
}
module.exports = Range
const LRU = require('lru-cache')
@ -200,12 +203,13 @@ const Comparator = require('./comparator')
const debug = require('../internal/debug')
const SemVer = require('./semver')
const {
re,
safeRe: re,
t,
comparatorTrimReplace,
tildeTrimReplace,
caretTrimReplace,
} = require('../internal/re')
const { FLAG_INCLUDE_PRERELEASE, FLAG_LOOSE } = require('../internal/constants')
const isNullSet = c => c.value === '<0.0.0-0'
const isAny = c => c.value === ''
@ -253,10 +257,13 @@ const isX = id => !id || id.toLowerCase() === 'x' || id === '*'
// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0-0
// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0-0
// ~0.0.1 --> >=0.0.1 <0.1.0-0
const replaceTildes = (comp, options) =>
comp.trim().split(/\s+/).map((c) => {
return replaceTilde(c, options)
}).join(' ')
const replaceTildes = (comp, options) => {
return comp
.trim()
.split(/\s+/)
.map((c) => replaceTilde(c, options))
.join(' ')
}
const replaceTilde = (comp, options) => {
const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE]
@ -294,10 +301,13 @@ const replaceTilde = (comp, options) => {
// ^1.2.0 --> >=1.2.0 <2.0.0-0
// ^0.0.1 --> >=0.0.1 <0.0.2-0
// ^0.1.0 --> >=0.1.0 <0.2.0-0
const replaceCarets = (comp, options) =>
comp.trim().split(/\s+/).map((c) => {
return replaceCaret(c, options)
}).join(' ')
const replaceCarets = (comp, options) => {
return comp
.trim()
.split(/\s+/)
.map((c) => replaceCaret(c, options))
.join(' ')
}
const replaceCaret = (comp, options) => {
debug('caret', comp, options)
@ -354,9 +364,10 @@ const replaceCaret = (comp, options) => {
const replaceXRanges = (comp, options) => {
debug('replaceXRanges', comp, options)
return comp.split(/\s+/).map((c) => {
return replaceXRange(c, options)
}).join(' ')
return comp
.split(/\s+/)
.map((c) => replaceXRange(c, options))
.join(' ')
}
const replaceXRange = (comp, options) => {
@ -439,12 +450,15 @@ const replaceXRange = (comp, options) => {
const replaceStars = (comp, options) => {
debug('replaceStars', comp, options)
// Looseness is ignored here. star is always as loose as it gets!
return comp.trim().replace(re[t.STAR], '')
return comp
.trim()
.replace(re[t.STAR], '')
}
const replaceGTE0 = (comp, options) => {
debug('replaceGTE0', comp, options)
return comp.trim()
return comp
.trim()
.replace(re[options.includePrerelease ? t.GTE0PRE : t.GTE0], '')
}
@ -482,7 +496,7 @@ const hyphenReplace = incPr => ($0,
to = `<=${to}`
}
return (`${from} ${to}`).trim()
return `${from} ${to}`.trim()
}
const testSet = (set, version, options) => {

View file

@ -1,6 +1,6 @@
const debug = require('../internal/debug')
const { MAX_LENGTH, MAX_SAFE_INTEGER } = require('../internal/constants')
const { re, t } = require('../internal/re')
const { safeRe: re, t } = require('../internal/re')
const parseOptions = require('../internal/parse-options')
const { compareIdentifiers } = require('../internal/identifiers')
@ -16,7 +16,7 @@ class SemVer {
version = version.version
}
} else if (typeof version !== 'string') {
throw new TypeError(`Invalid Version: ${version}`)
throw new TypeError(`Invalid version. Must be a string. Got type "${typeof version}".`)
}
if (version.length > MAX_LENGTH) {
@ -175,36 +175,36 @@ class SemVer {
// preminor will bump the version up to the next minor release, and immediately
// down to pre-release. premajor and prepatch work the same way.
inc (release, identifier) {
inc (release, identifier, identifierBase) {
switch (release) {
case 'premajor':
this.prerelease.length = 0
this.patch = 0
this.minor = 0
this.major++
this.inc('pre', identifier)
this.inc('pre', identifier, identifierBase)
break
case 'preminor':
this.prerelease.length = 0
this.patch = 0
this.minor++
this.inc('pre', identifier)
this.inc('pre', identifier, identifierBase)
break
case 'prepatch':
// If this is already a prerelease, it will bump to the next version
// drop any prereleases that might already exist, since they are not
// relevant at this point.
this.prerelease.length = 0
this.inc('patch', identifier)
this.inc('pre', identifier)
this.inc('patch', identifier, identifierBase)
this.inc('pre', identifier, identifierBase)
break
// If the input is a non-prerelease version, this acts the same as
// prepatch.
case 'prerelease':
if (this.prerelease.length === 0) {
this.inc('patch', identifier)
this.inc('patch', identifier, identifierBase)
}
this.inc('pre', identifier)
this.inc('pre', identifier, identifierBase)
break
case 'major':
@ -246,9 +246,15 @@ class SemVer {
break
// This probably shouldn't be used publicly.
// 1.0.0 'pre' would become 1.0.0-0 which is the wrong direction.
case 'pre':
case 'pre': {
const base = Number(identifierBase) ? 1 : 0
if (!identifier && identifierBase === false) {
throw new Error('invalid increment argument: identifier is empty')
}
if (this.prerelease.length === 0) {
this.prerelease = [0]
this.prerelease = [base]
} else {
let i = this.prerelease.length
while (--i >= 0) {
@ -259,27 +265,36 @@ class SemVer {
}
if (i === -1) {
// didn't increment anything
this.prerelease.push(0)
if (identifier === this.prerelease.join('.') && identifierBase === false) {
throw new Error('invalid increment argument: identifier already exists')
}
this.prerelease.push(base)
}
}
if (identifier) {
// 1.2.0-beta.1 bumps to 1.2.0-beta.2,
// 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0
let prerelease = [identifier, base]
if (identifierBase === false) {
prerelease = [identifier]
}
if (compareIdentifiers(this.prerelease[0], identifier) === 0) {
if (isNaN(this.prerelease[1])) {
this.prerelease = [identifier, 0]
this.prerelease = prerelease
}
} else {
this.prerelease = [identifier, 0]
this.prerelease = prerelease
}
}
break
}
default:
throw new Error(`invalid increment argument: ${release}`)
}
this.format()
this.raw = this.version
this.raw = this.format()
if (this.build.length) {
this.raw += `+${this.build.join('.')}`
}
return this
}
}

View file

@ -1,6 +1,6 @@
const SemVer = require('../classes/semver')
const parse = require('./parse')
const { re, t } = require('../internal/re')
const { safeRe: re, t } = require('../internal/re')
const coerce = (version, options) => {
if (version instanceof SemVer) {

View file

@ -1,23 +1,65 @@
const parse = require('./parse')
const eq = require('./eq')
const parse = require('./parse.js')
const diff = (version1, version2) => {
if (eq(version1, version2)) {
const v1 = parse(version1, null, true)
const v2 = parse(version2, null, true)
const comparison = v1.compare(v2)
if (comparison === 0) {
return null
} else {
const v1 = parse(version1)
const v2 = parse(version2)
const hasPre = v1.prerelease.length || v2.prerelease.length
const prefix = hasPre ? 'pre' : ''
const defaultResult = hasPre ? 'prerelease' : ''
for (const key in v1) {
if (key === 'major' || key === 'minor' || key === 'patch') {
if (v1[key] !== v2[key]) {
return prefix + key
}
}
}
return defaultResult // may be undefined
}
const v1Higher = comparison > 0
const highVersion = v1Higher ? v1 : v2
const lowVersion = v1Higher ? v2 : v1
const highHasPre = !!highVersion.prerelease.length
const lowHasPre = !!lowVersion.prerelease.length
if (lowHasPre && !highHasPre) {
// Going from prerelease -> no prerelease requires some special casing
// If the low version has only a major, then it will always be a major
// Some examples:
// 1.0.0-1 -> 1.0.0
// 1.0.0-1 -> 1.1.1
// 1.0.0-1 -> 2.0.0
if (!lowVersion.patch && !lowVersion.minor) {
return 'major'
}
// Otherwise it can be determined by checking the high version
if (highVersion.patch) {
// anything higher than a patch bump would result in the wrong version
return 'patch'
}
if (highVersion.minor) {
// anything higher than a minor bump would result in the wrong version
return 'minor'
}
// bumping major/minor/patch all have same result
return 'major'
}
// add the `pre` prefix if we are going to a prerelease version
const prefix = highHasPre ? 'pre' : ''
if (v1.major !== v2.major) {
return prefix + 'major'
}
if (v1.minor !== v2.minor) {
return prefix + 'minor'
}
if (v1.patch !== v2.patch) {
return prefix + 'patch'
}
// high and low are preleases
return 'prerelease'
}
module.exports = diff

View file

@ -1,7 +1,8 @@
const SemVer = require('../classes/semver')
const inc = (version, release, options, identifier) => {
const inc = (version, release, options, identifier, identifierBase) => {
if (typeof (options) === 'string') {
identifierBase = identifier
identifier = options
options = undefined
}
@ -10,7 +11,7 @@ const inc = (version, release, options, identifier) => {
return new SemVer(
version instanceof SemVer ? version.version : version,
options
).inc(release, identifier).version
).inc(release, identifier, identifierBase).version
} catch (er) {
return null
}

View file

@ -1,32 +1,15 @@
const { MAX_LENGTH } = require('../internal/constants')
const { re, t } = require('../internal/re')
const SemVer = require('../classes/semver')
const parseOptions = require('../internal/parse-options')
const parse = (version, options) => {
options = parseOptions(options)
const parse = (version, options, throwErrors = false) => {
if (version instanceof SemVer) {
return version
}
if (typeof version !== 'string') {
return null
}
if (version.length > MAX_LENGTH) {
return null
}
const r = options.loose ? re[t.LOOSE] : re[t.FULL]
if (!r.test(version)) {
return null
}
try {
return new SemVer(version, options)
} catch (er) {
return null
if (!throwErrors) {
return null
}
throw er
}
}

1
node_modules/semver/index.js generated vendored
View file

@ -83,6 +83,7 @@ module.exports = {
src: internalRe.src,
tokens: internalRe.t,
SEMVER_SPEC_VERSION: constants.SEMVER_SPEC_VERSION,
RELEASE_TYPES: constants.RELEASE_TYPES,
compareIdentifiers: identifiers.compareIdentifiers,
rcompareIdentifiers: identifiers.rcompareIdentifiers,
}

View file

@ -9,9 +9,22 @@ const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER ||
// Max safe segment length for coercion.
const MAX_SAFE_COMPONENT_LENGTH = 16
const RELEASE_TYPES = [
'major',
'premajor',
'minor',
'preminor',
'patch',
'prepatch',
'prerelease',
]
module.exports = {
SEMVER_SPEC_VERSION,
MAX_LENGTH,
MAX_SAFE_INTEGER,
MAX_SAFE_COMPONENT_LENGTH,
MAX_SAFE_INTEGER,
RELEASE_TYPES,
SEMVER_SPEC_VERSION,
FLAG_INCLUDE_PRERELEASE: 0b001,
FLAG_LOOSE: 0b010,
}

View file

@ -1,11 +1,15 @@
// parse out just the options we care about so we always get a consistent
// obj with keys in a consistent order.
const opts = ['includePrerelease', 'loose', 'rtl']
const parseOptions = options =>
!options ? {}
: typeof options !== 'object' ? { loose: true }
: opts.filter(k => options[k]).reduce((o, k) => {
o[k] = true
return o
}, {})
// parse out just the options we care about
const looseOption = Object.freeze({ loose: true })
const emptyOpts = Object.freeze({ })
const parseOptions = options => {
if (!options) {
return emptyOpts
}
if (typeof options !== 'object') {
return looseOption
}
return options
}
module.exports = parseOptions

11
node_modules/semver/internal/re.js generated vendored
View file

@ -4,16 +4,27 @@ exports = module.exports = {}
// The actual regexps go on exports.re
const re = exports.re = []
const safeRe = exports.safeRe = []
const src = exports.src = []
const t = exports.t = {}
let R = 0
const createToken = (name, value, isGlobal) => {
// Replace all greedy whitespace to prevent regex dos issues. These regex are
// used internally via the safeRe object since all inputs in this library get
// normalized first to trim and collapse all extra whitespace. The original
// regexes are exported for userland consumption and lower level usage. A
// future breaking change could export the safer regex only with a note that
// all input should have extra whitespace removed.
const safe = value
.split('\\s*').join('\\s{0,1}')
.split('\\s+').join('\\s')
const index = R++
debug(name, index, value)
t[name] = index
src[index] = value
re[index] = new RegExp(value, isGlobal ? 'g' : undefined)
safeRe[index] = new RegExp(safe, isGlobal ? 'g' : undefined)
}
// The following Regular Expressions can be used for tokenizing,

15
node_modules/semver/package.json generated vendored
View file

@ -1,6 +1,6 @@
{
"name": "semver",
"version": "7.3.8",
"version": "7.5.2",
"description": "The semantic version parser used by npm.",
"main": "index.js",
"scripts": {
@ -13,8 +13,8 @@
"template-oss-apply": "template-oss-apply --force"
},
"devDependencies": {
"@npmcli/eslint-config": "^3.0.1",
"@npmcli/template-oss": "4.4.4",
"@npmcli/eslint-config": "^4.0.0",
"@npmcli/template-oss": "4.15.1",
"tap": "^16.0.0"
},
"license": "ISC",
@ -37,7 +37,7 @@
"range.bnf"
],
"tap": {
"check-coverage": true,
"timeout": 30,
"coverage-map": "map.js",
"nyc-arg": [
"--exclude",
@ -53,9 +53,8 @@
"author": "GitHub Inc.",
"templateOSS": {
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
"version": "4.4.4",
"version": "4.15.1",
"engines": ">=10",
"content": "./scripts",
"ciVersions": [
"10.0.0",
"10.x",
@ -64,6 +63,7 @@
"16.x",
"18.x"
],
"npmSpec": "8",
"distPaths": [
"classes/",
"functions/",
@ -81,6 +81,7 @@
"/index.js",
"/preload.js",
"/range.bnf"
]
],
"publish": "true"
}
}

View file

@ -2,6 +2,6 @@ const Range = require('../classes/range')
const intersects = (r1, r2, options) => {
r1 = new Range(r1, options)
r2 = new Range(r2, options)
return r1.intersects(r2)
return r1.intersects(r2, options)
}
module.exports = intersects

View file

@ -68,6 +68,9 @@ const subset = (sub, dom, options = {}) => {
return true
}
const minimumVersionWithPreRelease = [new Comparator('>=0.0.0-0')]
const minimumVersion = [new Comparator('>=0.0.0')]
const simpleSubset = (sub, dom, options) => {
if (sub === dom) {
return true
@ -77,9 +80,9 @@ const simpleSubset = (sub, dom, options) => {
if (dom.length === 1 && dom[0].semver === ANY) {
return true
} else if (options.includePrerelease) {
sub = [new Comparator('>=0.0.0-0')]
sub = minimumVersionWithPreRelease
} else {
sub = [new Comparator('>=0.0.0')]
sub = minimumVersion
}
}
@ -87,7 +90,7 @@ const simpleSubset = (sub, dom, options) => {
if (options.includePrerelease) {
return true
} else {
dom = [new Comparator('>=0.0.0')]
dom = minimumVersion
}
}

12
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "codeql",
"version": "2.20.1",
"version": "2.20.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "codeql",
"version": "2.20.1",
"version": "2.20.2",
"license": "MIT",
"dependencies": {
"@actions/artifact": "^1.1.0",
@ -32,7 +32,7 @@
"jsonschema": "1.4.1",
"long": "^5.2.0",
"path": "^0.12.7",
"semver": "^7.3.2",
"semver": "^7.5.2",
"uuid": "^9.0.0",
"zlib": "^1.0.5"
},
@ -5277,9 +5277,9 @@
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"node_modules/semver": {
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
"version": "7.5.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz",
"integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==",
"dependencies": {
"lru-cache": "^6.0.0"
},

View file

@ -1,6 +1,6 @@
{
"name": "codeql",
"version": "2.20.1",
"version": "2.20.2",
"private": true,
"description": "CodeQL action",
"scripts": {
@ -44,7 +44,7 @@
"jsonschema": "1.4.1",
"long": "^5.2.0",
"path": "^0.12.7",
"semver": "^7.3.2",
"semver": "^7.5.2",
"uuid": "^9.0.0",
"zlib": "^1.0.5"
},

View file

@ -315,6 +315,25 @@ export type ActionStatus =
| "failure"
| "user-error";
// Any status report may include an array of EventReports associated with it.
export interface EventReport {
/** An enumerable description of the event. */
event: string;
/** Time this event started. */
started_at: string;
/** Time this event ended. */
completed_at: string;
/** eg: `success`, `failure`, `timeout`, etc. */
exit_status?: string;
/** If the event is language-specific. */
language?: string;
/**
* A generic JSON blob of data related to this event.
* Use Object.assign() to append additional fields to the object.
*/
properties?: object;
}
export interface StatusReportBase {
/**
* UUID representing the job run that this status report belongs to. We

View file

@ -28,13 +28,13 @@ import {
CODEQL_ACTION_DID_AUTOBUILD_GOLANG,
} from "./shared-environment";
import { getTotalCacheSize, uploadTrapCaches } from "./trap-caching";
import * as upload_lib from "./upload-lib";
import * as uploadLib from "./upload-lib";
import { UploadResult } from "./upload-lib";
import * as util from "./util";
import { checkForTimeout, wrapError } from "./util";
interface AnalysisStatusReport
extends upload_lib.UploadStatusReport,
extends uploadLib.UploadStatusReport,
QueriesStatusReport {}
interface FinishStatusReport
@ -269,7 +269,7 @@ async function run() {
core.setOutput("db-locations", dbLocations);
const uploadInput = actionsUtil.getOptionalInput("upload");
if (runStats && actionsUtil.getUploadValue(uploadInput) === "always") {
uploadResult = await upload_lib.uploadFromActions(
uploadResult = await uploadLib.uploadFromActions(
outputDir,
actionsUtil.getRequiredInput("checkout_path"),
actionsUtil.getOptionalInput("category"),
@ -296,7 +296,7 @@ async function run() {
uploadResult !== undefined &&
actionsUtil.getRequiredInput("wait-for-processing") === "true"
) {
await upload_lib.waitForProcessing(
await uploadLib.waitForProcessing(
parseRepositoryNwo(util.getRequiredEnvParam("GITHUB_REPOSITORY")),
uploadResult.sarifID,
getActionsLogger()

View file

@ -18,13 +18,18 @@ import { Feature } from "./feature-flags";
import { Language } from "./languages";
import { getRunnerLogger } from "./logging";
import { setupTests, setupActionsVars, createFeatures } from "./testing-utils";
import * as uploadLib from "./upload-lib";
import * as util from "./util";
setupTests(test);
// Checks that the duration fields are populated for the correct language
// and correct case of builtin or custom. Also checks the correct search
// paths are set in the database analyze invocation.
/** Checks that the duration fields are populated for the correct language
* and correct case of builtin or custom. Also checks the correct search
* paths are set in the database analyze invocation.
*
* Mocks the QA telemetry feature flag and checks the appropriate status report
* fields.
*/
test("status report fields and search path setting", async (t) => {
let searchPathsUsed: Array<string | undefined> = [];
return await util.withTmpDir(async (tmpDir) => {
@ -38,6 +43,8 @@ test("status report fields and search path setting", async (t) => {
[Language.java]: ["c/d@2.0.0"],
};
sinon.stub(uploadLib, "validateSarifFileSchema");
for (const language of Object.values(Language)) {
setCodeQL({
packDownload: async () => ({ packs: [] }),
@ -135,12 +142,12 @@ test("status report fields and search path setting", async (t) => {
undefined,
config,
getRunnerLogger(true),
createFeatures([])
createFeatures([Feature.QaTelemetryEnabled])
);
const hasPacks = language in packs;
const statusReportKeys = Object.keys(builtinStatusReport).sort();
if (hasPacks) {
t.deepEqual(statusReportKeys.length, 3, statusReportKeys.toString());
t.deepEqual(statusReportKeys.length, 4, statusReportKeys.toString());
t.deepEqual(
statusReportKeys[0],
`analyze_builtin_queries_${language}_duration_ms`
@ -149,8 +156,9 @@ test("status report fields and search path setting", async (t) => {
statusReportKeys[1],
`analyze_custom_queries_${language}_duration_ms`
);
t.deepEqual(statusReportKeys[2], "event_reports");
t.deepEqual(
statusReportKeys[2],
statusReportKeys[3],
`interpret_results_${language}_duration_ms`
);
} else {
@ -158,11 +166,17 @@ test("status report fields and search path setting", async (t) => {
statusReportKeys[0],
`analyze_builtin_queries_${language}_duration_ms`
);
t.deepEqual(statusReportKeys[1], "event_reports");
t.deepEqual(
statusReportKeys[1],
statusReportKeys[2],
`interpret_results_${language}_duration_ms`
);
}
if (builtinStatusReport.event_reports) {
for (const eventReport of builtinStatusReport.event_reports) {
t.deepEqual(eventReport.event, "codeql database interpret-results");
}
}
config.queries[language] = {
builtin: [],
@ -185,9 +199,9 @@ test("status report fields and search path setting", async (t) => {
undefined,
config,
getRunnerLogger(true),
createFeatures([])
createFeatures([Feature.QaTelemetryEnabled])
);
t.deepEqual(Object.keys(customStatusReport).length, 2);
t.deepEqual(Object.keys(customStatusReport).length, 3);
t.true(
`analyze_custom_queries_${language}_duration_ms` in customStatusReport
);
@ -196,6 +210,12 @@ test("status report fields and search path setting", async (t) => {
: [undefined, "/1", "/2"];
t.deepEqual(searchPathsUsed, expectedSearchPathsUsed);
t.true(`interpret_results_${language}_duration_ms` in customStatusReport);
t.true("event_reports" in customStatusReport);
if (customStatusReport.event_reports) {
for (const eventReport of customStatusReport.event_reports) {
t.deepEqual(eventReport.event, "codeql database interpret-results");
}
}
}
verifyQuerySuites(tmpDir);

View file

@ -6,7 +6,7 @@ import * as toolrunner from "@actions/exec/lib/toolrunner";
import del from "del";
import * as yaml from "js-yaml";
import { DatabaseCreationTimings } from "./actions-util";
import { DatabaseCreationTimings, EventReport } from "./actions-util";
import * as analysisPaths from "./analysis-paths";
import { CodeQL, getCodeQL } from "./codeql";
import * as configUtils from "./config-utils";
@ -14,6 +14,7 @@ import { FeatureEnablement, Feature } from "./feature-flags";
import { isScannedLanguage, Language } from "./languages";
import { Logger } from "./logging";
import { endTracingForCluster } from "./tracer-config";
import { validateSarifFileSchema } from "./upload-lib";
import * as util from "./util";
export class CodeQLAnalysisError extends Error {
@ -78,6 +79,8 @@ export interface QueriesStatusReport {
interpret_results_swift_duration_ms?: number;
/** Name of language that errored during analysis (or undefined if no language failed). */
analyze_failure_language?: string;
/** Reports on discrete events associated with this status report. */
event_reports?: EventReport[];
}
async function setupPythonExtractor(
@ -242,6 +245,9 @@ export async function runQueries(
const packsWithVersion = config.packs[language] || [];
try {
const sarifFile = path.join(sarifFolder, `${language}.sarif`);
let startTimeInterpretResults: number;
let endTimeInterpretResults: number;
if (await util.useCodeScanningConfigInCli(codeql, features)) {
// If we are using the code scanning config in the CLI,
// much of the work needed to generate the query suites
@ -257,16 +263,16 @@ export async function runQueries(
new Date().getTime() - startTimeBuiltIn;
logger.startGroup(`Interpreting results for ${language}`);
const startTimeInterpretResults = new Date().getTime();
const sarifFile = path.join(sarifFolder, `${language}.sarif`);
startTimeInterpretResults = new Date().getTime();
const analysisSummary = await runInterpretResults(
language,
undefined,
sarifFile,
config.debugMode
);
endTimeInterpretResults = new Date().getTime();
statusReport[`interpret_results_${language}_duration_ms`] =
new Date().getTime() - startTimeInterpretResults;
endTimeInterpretResults - startTimeInterpretResults;
logger.endGroup();
logger.info(analysisSummary);
} else {
@ -342,19 +348,37 @@ export async function runQueries(
}
logger.endGroup();
logger.startGroup(`Interpreting results for ${language}`);
const startTimeInterpretResults = new Date().getTime();
const sarifFile = path.join(sarifFolder, `${language}.sarif`);
startTimeInterpretResults = new Date().getTime();
const analysisSummary = await runInterpretResults(
language,
querySuitePaths,
sarifFile,
config.debugMode
);
endTimeInterpretResults = new Date().getTime();
statusReport[`interpret_results_${language}_duration_ms`] =
new Date().getTime() - startTimeInterpretResults;
endTimeInterpretResults - startTimeInterpretResults;
logger.endGroup();
logger.info(analysisSummary);
}
if (await features.getValue(Feature.QaTelemetryEnabled)) {
const perQueryAlertCounts = getPerQueryAlertCounts(sarifFile, logger);
const perQueryAlertCountEventReport: EventReport = {
event: "codeql database interpret-results",
started_at: startTimeInterpretResults.toString(),
completed_at: endTimeInterpretResults.toString(),
exit_status: "success",
language,
properties: perQueryAlertCounts,
};
if (statusReport["event_reports"] === undefined) {
statusReport["event_reports"] = [];
}
statusReport["event_reports"].push(perQueryAlertCountEventReport);
}
await runPrintLinesOfCode(language);
} catch (e) {
logger.info(String(e));
@ -392,6 +416,34 @@ export async function runQueries(
);
}
/** Get an object with all queries and their counts parsed from a SARIF file path. */
function getPerQueryAlertCounts(
sarifPath: string,
log: Logger
): Record<string, number> {
validateSarifFileSchema(sarifPath, log);
const sarifObject = JSON.parse(
fs.readFileSync(sarifPath, "utf8")
) as util.SarifFile;
// We do not need to compute fingerprints because we are not sending data based off of locations.
// Generate the query: alert count object
const perQueryAlertCounts: Record<string, number> = {};
// All rules (queries), from all results, from all runs
for (const sarifRun of sarifObject.runs) {
if (sarifRun.results) {
for (const result of sarifRun.results) {
const query = result.rule?.id || result.ruleId;
if (query) {
perQueryAlertCounts[query] = (perQueryAlertCounts[query] || 0) + 1;
}
}
}
}
return perQueryAlertCounts;
}
async function runPrintLinesOfCode(language: Language): Promise<string> {
const databasePath = util.getCodeQLDatabasePath(config, language);
return await codeql.databasePrintBaseline(databasePath);

View file

@ -43,6 +43,7 @@ export enum Feature {
ExportCodeScanningConfigEnabled = "export_code_scanning_config_enabled",
ExportDiagnosticsEnabled = "export_diagnostics_enabled",
MlPoweredQueriesEnabled = "ml_powered_queries_enabled",
QaTelemetryEnabled = "qa_telemetry_enabled",
UploadFailedSarifEnabled = "upload_failed_sarif_enabled",
}
@ -76,6 +77,11 @@ export const featureConfig: Record<
minimumVersion: "2.7.5",
defaultValue: false,
},
[Feature.QaTelemetryEnabled]: {
envVar: "CODEQL_ACTION_QA_TELEMETRY",
minimumVersion: undefined,
defaultValue: false,
},
[Feature.UploadFailedSarifEnabled]: {
envVar: "CODEQL_ACTION_UPLOAD_FAILED_SARIF",
minimumVersion: "2.11.3",

View file

@ -20,7 +20,7 @@ import * as workflow from "./workflow";
// 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[]): SarifFile {
function combineSarifFiles(sarifFiles: string[]): SarifFile {
const combinedSarif: SarifFile = {
version: null,
runs: [],
@ -198,7 +198,7 @@ function getSarifFilePaths(sarifPath: string) {
}
// Counts the number of results in the given SARIF file
export function countResultsInSarif(sarif: string): number {
function countResultsInSarif(sarif: string): number {
let numResults = 0;
let parsedSarif;
try {
@ -224,7 +224,7 @@ export function countResultsInSarif(sarif: string): number {
// Validates that the given file path refers to a valid SARIF file.
// Throws an error if the file is invalid.
export function validateSarifFileSchema(sarifFilePath: string, logger: Logger) {
const sarif = JSON.parse(fs.readFileSync(sarifFilePath, "utf8"));
const sarif = JSON.parse(fs.readFileSync(sarifFilePath, "utf8")) as SarifFile;
const schema = require("../src/sarif-schema-2.1.0.json") as jsonschema.Schema;
const result = new jsonschema.Validator().validate(sarif, schema);

View file

@ -72,6 +72,9 @@ export interface SarifInvocation {
export interface SarifResult {
ruleId?: string;
rule?: {
id?: string;
};
message?: {
text?: string;
};