From f237316c5ae1ee5b7e8096ac149a339ab1006c6f Mon Sep 17 00:00:00 2001 From: Max Veytsman Date: Wed, 29 Apr 2020 15:25:48 -0400 Subject: [PATCH 01/18] Improve errors & warnings in autobuild --- lib/autobuild.js | 8 ++++++-- src/autobuild.ts | 9 +++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/autobuild.js b/lib/autobuild.js index bce7f3a34..8f3dcecba 100644 --- a/lib/autobuild.js +++ b/lib/autobuild.js @@ -22,12 +22,16 @@ async function run() { // We want pick the dominant language in the repo from the ones we're able to build // The languages are sorted in order specified by user or by lines of code if we got // them from the GitHub API, so try to build the first language on the list. - const language = (_a = process.env[sharedEnv.CODEQL_ACTION_TRACED_LANGUAGES]) === null || _a === void 0 ? void 0 : _a.split(',')[0]; + const autobuildLanguages = ((_a = process.env[sharedEnv.CODEQL_ACTION_TRACED_LANGUAGES]) === null || _a === void 0 ? void 0 : _a.split(',')) || []; + const language = autobuildLanguages[0]; if (!language) { core.info("None of the languages in this project require extra build steps"); return; } core.debug(`Detected dominant traced language: ${language}`); + if (autobuildLanguages.length > 1) { + core.warning(`We will only automatically build ${language} code. If you wish to scan ${autobuildLanguages.slice(1).join(' and ')}, you must replace this block with custom build steps.`); + } core.startGroup(`Attempting to automatically build ${language} code`); // TODO: share config accross actions better via env variables const codeqlCmd = util.getRequiredEnvParam(sharedEnv.CODEQL_ACTION_CMD); @@ -51,6 +55,6 @@ async function run() { await util.reportActionSucceeded('autobuild'); } run().catch(e => { - core.setFailed("autobuild action failed: " + e); + core.setFailed("We were unable to automatically build your code. Please replace the call to the autobuild action with your custom build steps. codeql/autobuild action failed. " + e); console.log(e); }); diff --git a/src/autobuild.ts b/src/autobuild.ts index bd7b37f25..519ebb190 100644 --- a/src/autobuild.ts +++ b/src/autobuild.ts @@ -15,7 +15,8 @@ async function run() { // We want pick the dominant language in the repo from the ones we're able to build // The languages are sorted in order specified by user or by lines of code if we got // them from the GitHub API, so try to build the first language on the list. - const language = process.env[sharedEnv.CODEQL_ACTION_TRACED_LANGUAGES]?.split(',')[0]; + const autobuildLanguages = process.env[sharedEnv.CODEQL_ACTION_TRACED_LANGUAGES]?.split(',') || []; + const language = autobuildLanguages[0]; if (!language) { core.info("None of the languages in this project require extra build steps"); @@ -24,6 +25,10 @@ async function run() { core.debug(`Detected dominant traced language: ${language}`); + if (autobuildLanguages.length > 1) { + core.warning(`We will only automatically build ${language} code. If you wish to scan ${autobuildLanguages.slice(1).join(' and ')}, you must replace this block with custom build steps.`); + } + core.startGroup(`Attempting to automatically build ${language} code`); // TODO: share config accross actions better via env variables const codeqlCmd = util.getRequiredEnvParam(sharedEnv.CODEQL_ACTION_CMD); @@ -53,6 +58,6 @@ async function run() { } run().catch(e => { - core.setFailed("autobuild action failed: " + e); + core.setFailed("We were unable to automatically build your code. Please replace the call to the autobuild action with your custom build steps. codeql/autobuild action failed. " + e); console.log(e); }); From 7963db13d860f4ccdb4463e1ebf3e1b854516f5e Mon Sep 17 00:00:00 2001 From: Max Veytsman Date: Thu, 30 Apr 2020 09:11:50 -0400 Subject: [PATCH 02/18] Move error to correct catch block --- lib/autobuild.js | 4 ++-- src/autobuild.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/autobuild.js b/lib/autobuild.js index 8f3dcecba..6776b80ff 100644 --- a/lib/autobuild.js +++ b/lib/autobuild.js @@ -48,13 +48,13 @@ async function run() { core.endGroup(); } catch (error) { - core.setFailed(error.message); + core.setFailed("We were unable to automatically build your code. Please replace the call to the autobuild action with your custom build steps. " + error.message); await util.reportActionFailed('autobuild', error.message, error.stack); return; } await util.reportActionSucceeded('autobuild'); } run().catch(e => { - core.setFailed("We were unable to automatically build your code. Please replace the call to the autobuild action with your custom build steps. codeql/autobuild action failed. " + e); + core.setFailed("autobuild action failed. " + e); console.log(e); }); diff --git a/src/autobuild.ts b/src/autobuild.ts index 519ebb190..4dedb3343 100644 --- a/src/autobuild.ts +++ b/src/autobuild.ts @@ -49,7 +49,7 @@ async function run() { core.endGroup(); } catch (error) { - core.setFailed(error.message); + core.setFailed("We were unable to automatically build your code. Please replace the call to the autobuild action with your custom build steps. " + error.message); await util.reportActionFailed('autobuild', error.message, error.stack); return; } @@ -58,6 +58,6 @@ async function run() { } run().catch(e => { - core.setFailed("We were unable to automatically build your code. Please replace the call to the autobuild action with your custom build steps. codeql/autobuild action failed. " + e); + core.setFailed("autobuild action failed. " + e); console.log(e); }); From 26e955cfa321f8ee3150ca31cf77d693930ed1bb Mon Sep 17 00:00:00 2001 From: Joshua Hale Date: Thu, 30 Apr 2020 16:28:53 +0100 Subject: [PATCH 03/18] report status as failure if upload fails --- lib/finalize-db.js | 5 ++++- lib/upload-lib.js | 13 +++++++++---- lib/upload-sarif.js | 10 +++++++--- src/finalize-db.ts | 5 ++++- src/upload-lib.ts | 18 ++++++++++++------ src/upload-sarif.ts | 10 ++++++---- 6 files changed, 42 insertions(+), 19 deletions(-) diff --git a/lib/finalize-db.js b/lib/finalize-db.js index ec7e6f440..caaa4ce83 100644 --- a/lib/finalize-db.js +++ b/lib/finalize-db.js @@ -125,7 +125,10 @@ async function run() { core.info('Analyzing database'); await runQueries(codeqlCmd, databaseFolder, sarifFolder, config); if ('true' === core.getInput('upload')) { - await upload_lib.upload(sarifFolder); + if (!await upload_lib.upload(sarifFolder)) { + await util.reportActionFailed('failed', 'upload'); + return; + } } } catch (error) { diff --git a/lib/upload-lib.js b/lib/upload-lib.js index f28f085a4..74e8a300a 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -56,21 +56,24 @@ function combineSarifFiles(sarifFiles) { exports.combineSarifFiles = combineSarifFiles; // Uploads a single sarif file or a directory of sarif files // depending on what the path happens to refer to. +// Returns true iff the upload occurred and succeeded async function upload(input) { if (fs.lstatSync(input).isDirectory()) { const sarifFiles = fs.readdirSync(input) .filter(f => f.endsWith(".sarif")) .map(f => path.resolve(input, f)); - await uploadFiles(sarifFiles); + return await uploadFiles(sarifFiles); } else { - await uploadFiles([input]); + return await uploadFiles([input]); } } exports.upload = upload; // Uploads the given set of sarif files. +// Returns true iff the upload occurred and succeeded async function uploadFiles(sarifFiles) { core.startGroup("Uploading results"); + let succeeded = false; try { // Check if an upload has happened before. If so then abort. // This is intended to catch when the finish and upload-sarif actions @@ -78,7 +81,7 @@ async function uploadFiles(sarifFiles) { const sentinelFile = await getSentinelFilePath(); if (fs.existsSync(sentinelFile)) { core.info("Aborting as an upload has already happened from this job"); - return; + return false; } const commitOid = util.getRequiredEnvParam('GITHUB_SHA'); const workflowRunIDStr = util.getRequiredEnvParam('GITHUB_RUN_ID'); @@ -94,7 +97,7 @@ async function uploadFiles(sarifFiles) { const workflowRunID = parseInt(workflowRunIDStr, 10); if (Number.isNaN(workflowRunID)) { core.setFailed('GITHUB_RUN_ID must define a non NaN workflow run ID'); - return; + return false; } let matrix = core.getInput('matrix'); if (matrix === "null" || matrix === "") { @@ -131,6 +134,7 @@ async function uploadFiles(sarifFiles) { } else { core.info("Successfully uploaded results"); + succeeded = true; } // Mark that we have made an upload fs.writeFileSync(sentinelFile, ''); @@ -139,4 +143,5 @@ async function uploadFiles(sarifFiles) { core.setFailed(error.message); } core.endGroup(); + return succeeded; } diff --git a/lib/upload-sarif.js b/lib/upload-sarif.js index 5bd8e593d..b22d2ac62 100644 --- a/lib/upload-sarif.js +++ b/lib/upload-sarif.js @@ -15,16 +15,20 @@ async function run() { return; } try { - await upload_lib.upload(core.getInput('sarif_file')); + if (await upload_lib.upload(core.getInput('sarif_file'))) { + await util.reportActionSucceeded('upload-sarif'); + } + else { + await util.reportActionFailed('upload-sarif', 'upload'); + } } catch (error) { core.setFailed(error.message); await util.reportActionFailed('upload-sarif', error.message, error.stack); return; } - await util.reportActionSucceeded('upload-sarif'); } run().catch(e => { - core.setFailed("upload-sarif action failed: " + e); + core.setFailed("codeql/upload-sarif action failed: " + e); console.log(e); }); diff --git a/src/finalize-db.ts b/src/finalize-db.ts index a4165e97e..f58ab6c1c 100644 --- a/src/finalize-db.ts +++ b/src/finalize-db.ts @@ -150,7 +150,10 @@ async function run() { await runQueries(codeqlCmd, databaseFolder, sarifFolder, config); if ('true' === core.getInput('upload')) { - await upload_lib.upload(sarifFolder); + if (!await upload_lib.upload(sarifFolder)) { + await util.reportActionFailed('failed', 'upload'); + return; + } } } catch (error) { diff --git a/src/upload-lib.ts b/src/upload-lib.ts index 8c6a31e4e..c7965555d 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -49,20 +49,23 @@ export function combineSarifFiles(sarifFiles: string[]): string { // Uploads a single sarif file or a directory of sarif files // depending on what the path happens to refer to. -export async function upload(input: string) { +// Returns true iff the upload occurred and succeeded +export async function upload(input: string): Promise { if (fs.lstatSync(input).isDirectory()) { const sarifFiles = fs.readdirSync(input) .filter(f => f.endsWith(".sarif")) .map(f => path.resolve(input, f)); - await uploadFiles(sarifFiles); + return await uploadFiles(sarifFiles); } else { - await uploadFiles([input]); + return await uploadFiles([input]); } } // Uploads the given set of sarif files. -async function uploadFiles(sarifFiles: string[]) { +// Returns true iff the upload occurred and succeeded +async function uploadFiles(sarifFiles: string[]): Promise { core.startGroup("Uploading results"); + let succeeded = false; try { // Check if an upload has happened before. If so then abort. // This is intended to catch when the finish and upload-sarif actions @@ -70,7 +73,7 @@ async function uploadFiles(sarifFiles: string[]) { const sentinelFile = await getSentinelFilePath(); if (fs.existsSync(sentinelFile)) { core.info("Aborting as an upload has already happened from this job"); - return; + return false; } const commitOid = util.getRequiredEnvParam('GITHUB_SHA'); @@ -90,7 +93,7 @@ async function uploadFiles(sarifFiles: string[]) { if (Number.isNaN(workflowRunID)) { core.setFailed('GITHUB_RUN_ID must define a non NaN workflow run ID'); - return; + return false; } let matrix: string | undefined = core.getInput('matrix'); @@ -130,6 +133,7 @@ async function uploadFiles(sarifFiles: string[]) { core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); } else { core.info("Successfully uploaded results"); + succeeded = true; } // Mark that we have made an upload @@ -139,4 +143,6 @@ async function uploadFiles(sarifFiles: string[]) { core.setFailed(error.message); } core.endGroup(); + + return succeeded; } diff --git a/src/upload-sarif.ts b/src/upload-sarif.ts index b0aedce5a..418769c27 100644 --- a/src/upload-sarif.ts +++ b/src/upload-sarif.ts @@ -9,17 +9,19 @@ async function run() { } try { - await upload_lib.upload(core.getInput('sarif_file')); + if (await upload_lib.upload(core.getInput('sarif_file'))) { + await util.reportActionSucceeded('upload-sarif'); + } else { + await util.reportActionFailed('upload-sarif', 'upload'); + } } catch (error) { core.setFailed(error.message); await util.reportActionFailed('upload-sarif', error.message, error.stack); return; } - - await util.reportActionSucceeded('upload-sarif'); } run().catch(e => { - core.setFailed("upload-sarif action failed: " + e); + core.setFailed("codeql/upload-sarif action failed: " + e); console.log(e); }); From 1da651c2196959a0403d7f49689ac6aabac909a9 Mon Sep 17 00:00:00 2001 From: Robert Brignull Date: Fri, 1 May 2020 10:39:23 +0100 Subject: [PATCH 04/18] Add retries to the upload --- lib/upload-lib.js | 56 ++++++++++++++++++++++++++--------------- src/upload-lib.ts | 63 +++++++++++++++++++++++++++++++++-------------- 2 files changed, 80 insertions(+), 39 deletions(-) diff --git a/lib/upload-lib.js b/lib/upload-lib.js index f28f085a4..62d49a065 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -54,6 +54,40 @@ function combineSarifFiles(sarifFiles) { return JSON.stringify(combinedSarif); } exports.combineSarifFiles = combineSarifFiles; +// Upload the given payload. +// If the request fails then this will be retry a small number of times. +async function uploadPayload(payload) { + core.info('Uploading results'); + const githubToken = core.getInput('token'); + const ph = new auth.BearerCredentialHandler(githubToken); + const client = new http.HttpClient('Code Scanning : Upload SARIF', [ph]); + const url = 'https://api.github.com/repos/' + process.env['GITHUB_REPOSITORY'] + '/code-scanning/analysis'; + // Make up to 4 attempts to upload, and sleep for these + // number of seconds between each attempt. + // We don't want to backoff too much to avoid wasting action + // minutes, but just waiting a little bit could maybe help. + const backoffPeriods = [1, 5, 15]; + for (let attempt = 0; attempt <= backoffPeriods.length; attempt++) { + const res = await client.put(url, payload); + core.debug('response status: ' + res.message.statusCode); + if (res.message.statusCode === 202) { + core.info("Successfully uploaded results"); + return; + } + const requestID = res.message.headers["x-github-request-id"]; + if (attempt < backoffPeriods.length) { + // Log the failure as a warning but don't mark the action as failed yet + core.warning('Upload attempt (' + (attempt + 1) + ' of ' + (backoffPeriods.length + 1) + + ') failed (' + requestID + '). Retrying in ' + backoffPeriods[attempt] + ' seconds: ' + + await res.readBody()); + // Sleep for the backoff period + await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000)); + } + else { + core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); + } + } +} // Uploads a single sarif file or a directory of sarif files // depending on what the path happens to refer to. async function upload(input) { @@ -112,26 +146,8 @@ async function uploadFiles(sarifFiles) { "started_at": startedAt, "tool_names": toolNames, }); - core.info('Uploading results'); - const githubToken = core.getInput('token'); - const ph = new auth.BearerCredentialHandler(githubToken); - const client = new http.HttpClient('Code Scanning : Upload SARIF', [ph]); - const url = 'https://api.github.com/repos/' + process.env['GITHUB_REPOSITORY'] + '/code-scanning/analysis'; - const res = await client.put(url, payload); - const requestID = res.message.headers["x-github-request-id"]; - core.debug('response status: ' + res.message.statusCode); - if (res.message.statusCode === 500) { - // If the upload fails with 500 then we assume it is a temporary problem - // with turbo-scan and not an error that the user has caused or can fix. - // We avoid marking the job as failed to avoid breaking CI workflows. - core.error('Upload failed (' + requestID + '): ' + await res.readBody()); - } - else if (res.message.statusCode !== 202) { - core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); - } - else { - core.info("Successfully uploaded results"); - } + // Make the upload + await uploadPayload(payload); // Mark that we have made an upload fs.writeFileSync(sentinelFile, ''); } diff --git a/src/upload-lib.ts b/src/upload-lib.ts index 8c6a31e4e..c9b69dfcb 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -47,6 +47,48 @@ export function combineSarifFiles(sarifFiles: string[]): string { return JSON.stringify(combinedSarif); } +// Upload the given payload. +// If the request fails then this will be retry a small number of times. +async function uploadPayload(payload) { + core.info('Uploading results'); + + const githubToken = core.getInput('token'); + const ph: auth.BearerCredentialHandler = new auth.BearerCredentialHandler(githubToken); + const client = new http.HttpClient('Code Scanning : Upload SARIF', [ph]); + const url = 'https://api.github.com/repos/' + process.env['GITHUB_REPOSITORY'] + '/code-scanning/analysis'; + + // Make up to 4 attempts to upload, and sleep for these + // number of seconds between each attempt. + // We don't want to backoff too much to avoid wasting action + // minutes, but just waiting a little bit could maybe help. + const backoffPeriods = [1, 5, 15]; + + for (let attempt = 0; attempt <= backoffPeriods.length; attempt++) { + + const res: http.HttpClientResponse = await client.put(url, payload); + core.debug('response status: ' + res.message.statusCode); + + if (res.message.statusCode === 202) { + core.info("Successfully uploaded results"); + return; + } + + const requestID = res.message.headers["x-github-request-id"]; + + if (attempt < backoffPeriods.length) { + // Log the failure as a warning but don't mark the action as failed yet + core.warning('Upload attempt (' + (attempt + 1) + ' of ' + (backoffPeriods.length + 1) + + ') failed (' + requestID + '). Retrying in ' + backoffPeriods[attempt] + ' seconds: ' + + await res.readBody()); + // Sleep for the backoff period + await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000)); + + } else { + core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); + } + } +} + // Uploads a single sarif file or a directory of sarif files // depending on what the path happens to refer to. export async function upload(input: string) { @@ -112,25 +154,8 @@ async function uploadFiles(sarifFiles: string[]) { "tool_names": toolNames, }); - core.info('Uploading results'); - const githubToken = core.getInput('token'); - const ph: auth.BearerCredentialHandler = new auth.BearerCredentialHandler(githubToken); - const client = new http.HttpClient('Code Scanning : Upload SARIF', [ph]); - const url = 'https://api.github.com/repos/' + process.env['GITHUB_REPOSITORY'] + '/code-scanning/analysis'; - const res: http.HttpClientResponse = await client.put(url, payload); - const requestID = res.message.headers["x-github-request-id"]; - - core.debug('response status: ' + res.message.statusCode); - if (res.message.statusCode === 500) { - // If the upload fails with 500 then we assume it is a temporary problem - // with turbo-scan and not an error that the user has caused or can fix. - // We avoid marking the job as failed to avoid breaking CI workflows. - core.error('Upload failed (' + requestID + '): ' + await res.readBody()); - } else if (res.message.statusCode !== 202) { - core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); - } else { - core.info("Successfully uploaded results"); - } + // Make the upload + await uploadPayload(payload); // Mark that we have made an upload fs.writeFileSync(sentinelFile, ''); From 5d2700f9cbcdfd67a172405ed36c95850ccf35bf Mon Sep 17 00:00:00 2001 From: Chris Gavin Date: Fri, 1 May 2020 11:07:58 +0100 Subject: [PATCH 05/18] Increase the log level of the message showing what SARIF files were uploaded. --- lib/upload-lib.js | 2 +- src/upload-lib.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/upload-lib.js b/lib/upload-lib.js index f28f085a4..2136fee71 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -85,7 +85,7 @@ async function uploadFiles(sarifFiles) { const ref = util.getRequiredEnvParam('GITHUB_REF'); // it's in the form "refs/heads/master" const analysisName = util.getRequiredEnvParam('GITHUB_WORKFLOW'); const startedAt = process.env[sharedEnv.CODEQL_ACTION_STARTED_AT]; - core.debug("Uploading sarif files: " + JSON.stringify(sarifFiles)); + core.info("Uploading sarif files: " + JSON.stringify(sarifFiles)); let sarifPayload = combineSarifFiles(sarifFiles); sarifPayload = fingerprints.addFingerprints(sarifPayload); const zipped_sarif = zlib_1.default.gzipSync(sarifPayload).toString('base64'); diff --git a/src/upload-lib.ts b/src/upload-lib.ts index 8c6a31e4e..105e440b1 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -79,7 +79,7 @@ async function uploadFiles(sarifFiles: string[]) { const analysisName = util.getRequiredEnvParam('GITHUB_WORKFLOW'); const startedAt = process.env[sharedEnv.CODEQL_ACTION_STARTED_AT]; - core.debug("Uploading sarif files: " + JSON.stringify(sarifFiles)); + core.info("Uploading sarif files: " + JSON.stringify(sarifFiles)); let sarifPayload = combineSarifFiles(sarifFiles); sarifPayload = fingerprints.addFingerprints(sarifPayload); From cffc0f7b4e89a6b33851dd8f9033f870784b54ed Mon Sep 17 00:00:00 2001 From: Robert Brignull Date: Fri, 1 May 2020 11:19:39 +0100 Subject: [PATCH 06/18] fix typo --- lib/upload-lib.js | 2 +- src/upload-lib.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/upload-lib.js b/lib/upload-lib.js index 62d49a065..668f5f8cc 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -55,7 +55,7 @@ function combineSarifFiles(sarifFiles) { } exports.combineSarifFiles = combineSarifFiles; // Upload the given payload. -// If the request fails then this will be retry a small number of times. +// If the request fails then this will retry a small number of times. async function uploadPayload(payload) { core.info('Uploading results'); const githubToken = core.getInput('token'); diff --git a/src/upload-lib.ts b/src/upload-lib.ts index c9b69dfcb..18d43f861 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -48,7 +48,7 @@ export function combineSarifFiles(sarifFiles: string[]): string { } // Upload the given payload. -// If the request fails then this will be retry a small number of times. +// If the request fails then this will retry a small number of times. async function uploadPayload(payload) { core.info('Uploading results'); From e52e34ba17846a8305514ca2459c4102b88a10cf Mon Sep 17 00:00:00 2001 From: Robert Brignull Date: Fri, 1 May 2020 11:20:21 +0100 Subject: [PATCH 07/18] remove change to behaviour on 500 errors --- lib/upload-lib.js | 6 ++++++ src/upload-lib.ts | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/lib/upload-lib.js b/lib/upload-lib.js index 668f5f8cc..d8f4639d9 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -83,6 +83,12 @@ async function uploadPayload(payload) { // Sleep for the backoff period await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000)); } + else if (res.message.statusCode === 500) { + // If the upload fails with 500 then we assume it is a temporary problem + // with turbo-scan and not an error that the user has caused or can fix. + // We avoid marking the job as failed to avoid breaking CI workflows. + core.error('Upload failed (' + requestID + '): ' + await res.readBody()); + } else { core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); } diff --git a/src/upload-lib.ts b/src/upload-lib.ts index 18d43f861..1342ccfa8 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -83,6 +83,12 @@ async function uploadPayload(payload) { // Sleep for the backoff period await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000)); + } else if (res.message.statusCode === 500) { + // If the upload fails with 500 then we assume it is a temporary problem + // with turbo-scan and not an error that the user has caused or can fix. + // We avoid marking the job as failed to avoid breaking CI workflows. + core.error('Upload failed (' + requestID + '): ' + await res.readBody()); + } else { core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); } From b6a0306228047c89bc6638afbf4606fca113562b Mon Sep 17 00:00:00 2001 From: Chris Gavin Date: Fri, 1 May 2020 11:12:15 +0100 Subject: [PATCH 08/18] Fail the upload action if uploading a folder with no SARIF files in. --- lib/upload-lib.js | 3 +++ src/upload-lib.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/lib/upload-lib.js b/lib/upload-lib.js index 2136fee71..568d2934f 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -61,6 +61,9 @@ async function upload(input) { const sarifFiles = fs.readdirSync(input) .filter(f => f.endsWith(".sarif")) .map(f => path.resolve(input, f)); + if (sarifFiles.length === 0) { + core.setFailed("No SARIF files found to upload in \"" + input + "\"."); + } await uploadFiles(sarifFiles); } else { diff --git a/src/upload-lib.ts b/src/upload-lib.ts index 105e440b1..a4959ae36 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -54,6 +54,9 @@ export async function upload(input: string) { const sarifFiles = fs.readdirSync(input) .filter(f => f.endsWith(".sarif")) .map(f => path.resolve(input, f)); + if (sarifFiles.length === 0) { + core.setFailed("No SARIF files found to upload in \"" + input + "\"."); + } await uploadFiles(sarifFiles); } else { await uploadFiles([input]); From 0c4fc16b490c4da59b8fa673963871f640c36ba5 Mon Sep 17 00:00:00 2001 From: Robert Brignull Date: Fri, 1 May 2020 11:32:09 +0100 Subject: [PATCH 09/18] only retry on 5xx status codes --- lib/upload-lib.js | 18 ++++++++++++------ src/upload-lib.ts | 19 +++++++++++++------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/lib/upload-lib.js b/lib/upload-lib.js index d8f4639d9..a23f54c4b 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -70,11 +70,18 @@ async function uploadPayload(payload) { for (let attempt = 0; attempt <= backoffPeriods.length; attempt++) { const res = await client.put(url, payload); core.debug('response status: ' + res.message.statusCode); - if (res.message.statusCode === 202) { + const statusCode = res.message.statusCode; + if (statusCode === 202) { core.info("Successfully uploaded results"); return; } const requestID = res.message.headers["x-github-request-id"]; + // On any other status code that's not 5xx mark the upload as failed + if (!statusCode || statusCode < 500 || statusCode >= 600) { + core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); + return; + } + // On a 5xx status code we may retry the request if (attempt < backoffPeriods.length) { // Log the failure as a warning but don't mark the action as failed yet core.warning('Upload attempt (' + (attempt + 1) + ' of ' + (backoffPeriods.length + 1) + @@ -82,15 +89,14 @@ async function uploadPayload(payload) { await res.readBody()); // Sleep for the backoff period await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000)); + continue; } - else if (res.message.statusCode === 500) { - // If the upload fails with 500 then we assume it is a temporary problem + else { + // If the upload fails with 5xx then we assume it is a temporary problem // with turbo-scan and not an error that the user has caused or can fix. // We avoid marking the job as failed to avoid breaking CI workflows. core.error('Upload failed (' + requestID + '): ' + await res.readBody()); - } - else { - core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); + return; } } } diff --git a/src/upload-lib.ts b/src/upload-lib.ts index 1342ccfa8..d404b2685 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -68,13 +68,21 @@ async function uploadPayload(payload) { const res: http.HttpClientResponse = await client.put(url, payload); core.debug('response status: ' + res.message.statusCode); - if (res.message.statusCode === 202) { + const statusCode = res.message.statusCode; + if (statusCode === 202) { core.info("Successfully uploaded results"); return; } const requestID = res.message.headers["x-github-request-id"]; + // On any other status code that's not 5xx mark the upload as failed + if (!statusCode || statusCode < 500 || statusCode >= 600) { + core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); + return; + } + + // On a 5xx status code we may retry the request if (attempt < backoffPeriods.length) { // Log the failure as a warning but don't mark the action as failed yet core.warning('Upload attempt (' + (attempt + 1) + ' of ' + (backoffPeriods.length + 1) + @@ -82,15 +90,14 @@ async function uploadPayload(payload) { await res.readBody()); // Sleep for the backoff period await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000)); + continue; - } else if (res.message.statusCode === 500) { - // If the upload fails with 500 then we assume it is a temporary problem + } else { + // If the upload fails with 5xx then we assume it is a temporary problem // with turbo-scan and not an error that the user has caused or can fix. // We avoid marking the job as failed to avoid breaking CI workflows. core.error('Upload failed (' + requestID + '): ' + await res.readBody()); - - } else { - core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); + return; } } } From a23cb1d61aa40e508980e1112d465e92f44d7c76 Mon Sep 17 00:00:00 2001 From: Robert Brignull Date: Fri, 1 May 2020 11:45:00 +0100 Subject: [PATCH 10/18] include status code is error message --- lib/upload-lib.js | 8 ++++---- src/upload-lib.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/upload-lib.js b/lib/upload-lib.js index a23f54c4b..e2dd3b32d 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -78,15 +78,15 @@ async function uploadPayload(payload) { const requestID = res.message.headers["x-github-request-id"]; // On any other status code that's not 5xx mark the upload as failed if (!statusCode || statusCode < 500 || statusCode >= 600) { - core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); + core.setFailed('Upload failed (' + requestID + '): (' + statusCode + ') ' + await res.readBody()); return; } // On a 5xx status code we may retry the request if (attempt < backoffPeriods.length) { // Log the failure as a warning but don't mark the action as failed yet core.warning('Upload attempt (' + (attempt + 1) + ' of ' + (backoffPeriods.length + 1) + - ') failed (' + requestID + '). Retrying in ' + backoffPeriods[attempt] + ' seconds: ' + - await res.readBody()); + ') failed (' + requestID + '). Retrying in ' + backoffPeriods[attempt] + + ' seconds: (' + statusCode + ') ' + await res.readBody()); // Sleep for the backoff period await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000)); continue; @@ -95,7 +95,7 @@ async function uploadPayload(payload) { // If the upload fails with 5xx then we assume it is a temporary problem // with turbo-scan and not an error that the user has caused or can fix. // We avoid marking the job as failed to avoid breaking CI workflows. - core.error('Upload failed (' + requestID + '): ' + await res.readBody()); + core.error('Upload failed (' + requestID + '): (' + statusCode + ') ' + await res.readBody()); return; } } diff --git a/src/upload-lib.ts b/src/upload-lib.ts index d404b2685..bcb6056dd 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -78,7 +78,7 @@ async function uploadPayload(payload) { // On any other status code that's not 5xx mark the upload as failed if (!statusCode || statusCode < 500 || statusCode >= 600) { - core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); + core.setFailed('Upload failed (' + requestID + '): (' + statusCode + ') ' + await res.readBody()); return; } @@ -86,8 +86,8 @@ async function uploadPayload(payload) { if (attempt < backoffPeriods.length) { // Log the failure as a warning but don't mark the action as failed yet core.warning('Upload attempt (' + (attempt + 1) + ' of ' + (backoffPeriods.length + 1) + - ') failed (' + requestID + '). Retrying in ' + backoffPeriods[attempt] + ' seconds: ' + - await res.readBody()); + ') failed (' + requestID + '). Retrying in ' + backoffPeriods[attempt] + + ' seconds: (' + statusCode + ') ' + await res.readBody()); // Sleep for the backoff period await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000)); continue; @@ -96,7 +96,7 @@ async function uploadPayload(payload) { // If the upload fails with 5xx then we assume it is a temporary problem // with turbo-scan and not an error that the user has caused or can fix. // We avoid marking the job as failed to avoid breaking CI workflows. - core.error('Upload failed (' + requestID + '): ' + await res.readBody()); + core.error('Upload failed (' + requestID + '): (' + statusCode + ') ' + await res.readBody()); return; } } From 129ce28897c35736adad3bd570afe34aa4ba9e05 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 1 May 2020 11:59:44 +0100 Subject: [PATCH 11/18] Update upload-lib.ts --- lib/upload-lib.js | 2 +- src/upload-lib.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/upload-lib.js b/lib/upload-lib.js index e2dd3b32d..631e8fe39 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -93,7 +93,7 @@ async function uploadPayload(payload) { } else { // If the upload fails with 5xx then we assume it is a temporary problem - // with turbo-scan and not an error that the user has caused or can fix. + // and not an error that the user has caused or can fix. // We avoid marking the job as failed to avoid breaking CI workflows. core.error('Upload failed (' + requestID + '): (' + statusCode + ') ' + await res.readBody()); return; diff --git a/src/upload-lib.ts b/src/upload-lib.ts index bcb6056dd..2e8139991 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -94,7 +94,7 @@ async function uploadPayload(payload) { } else { // If the upload fails with 5xx then we assume it is a temporary problem - // with turbo-scan and not an error that the user has caused or can fix. + // and not an error that the user has caused or can fix. // We avoid marking the job as failed to avoid breaking CI workflows. core.error('Upload failed (' + requestID + '): (' + statusCode + ') ' + await res.readBody()); return; From 4e9886ad2bf3b89c655a3c6c66a849ba06ae5b1e Mon Sep 17 00:00:00 2001 From: Chris Gavin Date: Fri, 1 May 2020 15:27:32 +0100 Subject: [PATCH 12/18] Stop the upload action early if no files will be uploaded. --- lib/upload-lib.js | 1 + src/upload-lib.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/upload-lib.js b/lib/upload-lib.js index 568d2934f..a9abfb900 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -63,6 +63,7 @@ async function upload(input) { .map(f => path.resolve(input, f)); if (sarifFiles.length === 0) { core.setFailed("No SARIF files found to upload in \"" + input + "\"."); + return; } await uploadFiles(sarifFiles); } diff --git a/src/upload-lib.ts b/src/upload-lib.ts index a4959ae36..443a95e86 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -56,6 +56,7 @@ export async function upload(input: string) { .map(f => path.resolve(input, f)); if (sarifFiles.length === 0) { core.setFailed("No SARIF files found to upload in \"" + input + "\"."); + return; } await uploadFiles(sarifFiles); } else { From d90fca396a8c2971e72efdb96e26b4b462d098c4 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 24 Apr 2020 17:15:22 +0100 Subject: [PATCH 13/18] Create undeclared-action-input.ql --- queries/undeclared-action-input.ql | 63 ++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 queries/undeclared-action-input.ql diff --git a/queries/undeclared-action-input.ql b/queries/undeclared-action-input.ql new file mode 100644 index 000000000..23627205b --- /dev/null +++ b/queries/undeclared-action-input.ql @@ -0,0 +1,63 @@ +/** + * @name Undeclared action input + * @description Code tries to use an input parameter that is not defined for this action. + Perhaps this code is shared by multiple actions. + * @kind problem + * @problem.severity error + * @id javascript/codeql-action/undeclared-action-input + */ + +import javascript + +class ActionDeclaration extends File { + ActionDeclaration() { + getRelativePath().matches("%/action.yml") + } + + string getName() { + result = getRelativePath().regexpCapture("(.*)/action.yml", 1) + } + + YAMLDocument getRootNode() { + result.getFile() = this + } + + string getAnInput() { + result = getRootNode().(YAMLMapping).lookup("inputs").(YAMLMapping).getKey(_).(YAMLString).getValue() + } + + FunctionDeclStmt getEntrypoint() { + result.getFile().getRelativePath() = getRootNode(). + (YAMLMapping).lookup("runs"). + (YAMLMapping).lookup("main"). + (YAMLString).getValue().regexpReplaceAll("\\.\\./lib/(.*)\\.js", "src/$1.ts") and + result.getName() = "run" + } +} + +Expr getAFunctionChildExpr(Function f) { + result = f.getBody().getAChildStmt*().getAChildExpr*() +} + +/* + * Result is a function that is called from the body of the given function `f` + */ +Function calledBy(Function f) { + result = getAFunctionChildExpr(f).(InvokeExpr).getResolvedCallee() +} + +class GetInputMethodCallExpr extends MethodCallExpr { + GetInputMethodCallExpr() { + getMethodName() = "getInput" + } + + string getInputName() { + result = getArgument(0).(StringLiteral).getValue() + } +} + +from ActionDeclaration action, GetInputMethodCallExpr getInputCall, string inputName +where getAFunctionChildExpr(calledBy*(action.getEntrypoint())) = getInputCall and + inputName = getInputCall.getInputName() and + not inputName = action.getAnInput() +select getInputCall, "The $@ input is not defined for the $@ action", inputName, inputName, action, action.getName() From dcd81b5847b81489052bf4eb6e7accdc2d6df8ab Mon Sep 17 00:00:00 2001 From: Robert Brignull Date: Mon, 4 May 2020 15:16:23 +0100 Subject: [PATCH 14/18] Make use of getContainer --- queries/undeclared-action-input.ql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/queries/undeclared-action-input.ql b/queries/undeclared-action-input.ql index 23627205b..a8ec7c3f4 100644 --- a/queries/undeclared-action-input.ql +++ b/queries/undeclared-action-input.ql @@ -36,7 +36,7 @@ class ActionDeclaration extends File { } Expr getAFunctionChildExpr(Function f) { - result = f.getBody().getAChildStmt*().getAChildExpr*() + result.getContainer() = f } /* @@ -44,6 +44,8 @@ Expr getAFunctionChildExpr(Function f) { */ Function calledBy(Function f) { result = getAFunctionChildExpr(f).(InvokeExpr).getResolvedCallee() + or + result.getEnclosingContainer() = f // assume outer function causes inner function to be called } class GetInputMethodCallExpr extends MethodCallExpr { From 290b34d5dfd896189952af7799c14e04bb6aed67 Mon Sep 17 00:00:00 2001 From: Ana Armas Romero <54946499+anaarmas@users.noreply.github.com> Date: Mon, 4 May 2020 19:55:51 +0200 Subject: [PATCH 15/18] Note in readme about go analysis in macos-latest --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f61fdee5c..a0fade7a0 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ env: to `github/codeql-action/analyze`. -### If you do not use a vendor directory +#### If you do not use a vendor directory Dependencies on public repositories should just work. If you have dependencies on private repositories, one option is to use `git config` and a [personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) to authenticate when downloading dependencies. Add a section like @@ -163,6 +163,10 @@ dotnet build /p:UseSharedCompilation=false Version 3 does not require the additional flag. +### Analysing Go together with other languages on `macos-latest` + +This is currently not possible for Java, C/C++, or C#. + ## License This project is released under the [MIT License](LICENSE). From ab918b676bd014a667688f1500e4e1b86ad8cc99 Mon Sep 17 00:00:00 2001 From: Robert Brignull Date: Tue, 5 May 2020 11:59:05 +0100 Subject: [PATCH 16/18] use tmp dir for external queries test --- lib/external-queries.js | 3 ++- lib/util.js | 10 ++++++++++ src/external-queries.test.ts | 12 ++++++++---- src/external-queries.ts | 3 ++- src/util.ts | 10 ++++++++++ 5 files changed, 32 insertions(+), 6 deletions(-) diff --git a/lib/external-queries.js b/lib/external-queries.js index 253cf2762..90e028938 100644 --- a/lib/external-queries.js +++ b/lib/external-queries.js @@ -11,8 +11,9 @@ const core = __importStar(require("@actions/core")); const exec = __importStar(require("@actions/exec")); const fs = __importStar(require("fs")); const path = __importStar(require("path")); +const util = __importStar(require("./util")); async function checkoutExternalQueries(config) { - const folder = process.env['RUNNER_WORKSPACE'] || '/tmp/codeql-action'; + const folder = util.getRequiredEnvParam('RUNNER_WORKSPACE'); for (const externalQuery of config.externalQueries) { core.info('Checking out ' + externalQuery.repository); const checkoutLocation = path.join(folder, externalQuery.repository); diff --git a/lib/util.js b/lib/util.js index d12a91044..a9d79bb41 100644 --- a/lib/util.js +++ b/lib/util.js @@ -15,6 +15,8 @@ const http = __importStar(require("@actions/http-client")); const auth = __importStar(require("@actions/http-client/auth")); const octokit = __importStar(require("@octokit/rest")); const console_log_level_1 = __importDefault(require("console-log-level")); +const fs = __importStar(require("fs")); +const os = __importStar(require("os")); const path = __importStar(require("path")); const sharedEnv = __importStar(require("./shared-environment")); /** @@ -280,3 +282,11 @@ function getToolNames(sarifContents) { return Object.keys(toolNames); } exports.getToolNames = getToolNames; +// Creates a random temporary directory, runs the given body, and then deletes the directory. +// Mostly intended for use within tests. +async function withTmpDir(body) { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codeql-action-')); + await body(tmpDir); + fs.rmdirSync(tmpDir, { recursive: true }); +} +exports.withTmpDir = withTmpDir; diff --git a/src/external-queries.test.ts b/src/external-queries.test.ts index 088f3a3fe..a79f3f3e5 100644 --- a/src/external-queries.test.ts +++ b/src/external-queries.test.ts @@ -3,15 +3,19 @@ import * as path from "path"; import * as configUtils from "./config-utils"; import * as externalQueries from "./external-queries"; +import * as util from "./util"; test("checkoutExternalQueries", async () => { let config = new configUtils.Config(); config.externalQueries = [ new configUtils.ExternalQuery("github/codeql-go", "df4c6869212341b601005567381944ed90906b6b"), ]; - await externalQueries.checkoutExternalQueries(config); - let destination = process.env["RUNNER_WORKSPACE"] || "/tmp/codeql-action/"; - // COPYRIGHT file existed in df4c6869212341b601005567381944ed90906b6b but not in master - expect(fs.existsSync(path.join(destination, "github", "codeql-go", "COPYRIGHT"))).toBeTruthy(); + await util.withTmpDir(async tmpDir => { + process.env["RUNNER_WORKSPACE"] = tmpDir; + await externalQueries.checkoutExternalQueries(config); + + // COPYRIGHT file existed in df4c6869212341b601005567381944ed90906b6b but not in master + expect(fs.existsSync(path.join(tmpDir, "github", "codeql-go", "COPYRIGHT"))).toBeTruthy(); + }); }); diff --git a/src/external-queries.ts b/src/external-queries.ts index c9724148d..a478f538b 100644 --- a/src/external-queries.ts +++ b/src/external-queries.ts @@ -4,9 +4,10 @@ import * as fs from 'fs'; import * as path from 'path'; import * as configUtils from './config-utils'; +import * as util from './util'; export async function checkoutExternalQueries(config: configUtils.Config) { - const folder = process.env['RUNNER_WORKSPACE'] || '/tmp/codeql-action'; + const folder = util.getRequiredEnvParam('RUNNER_WORKSPACE'); for (const externalQuery of config.externalQueries) { core.info('Checking out ' + externalQuery.repository); diff --git a/src/util.ts b/src/util.ts index cfdd2419c..d17571d5d 100644 --- a/src/util.ts +++ b/src/util.ts @@ -3,6 +3,8 @@ import * as http from '@actions/http-client'; import * as auth from '@actions/http-client/auth'; import * as octokit from '@octokit/rest'; import consoleLogLevel from 'console-log-level'; +import * as fs from "fs"; +import * as os from 'os'; import * as path from 'path'; import * as sharedEnv from './shared-environment'; @@ -313,3 +315,11 @@ export function getToolNames(sarifContents: string): string[] { return Object.keys(toolNames); } + +// Creates a random temporary directory, runs the given body, and then deletes the directory. +// Mostly intended for use within tests. +export async function withTmpDir(body: (tmpDir: string) => Promise) { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codeql-action-')); + await body(tmpDir); + fs.rmdirSync(tmpDir, { recursive: true }); +} From 4fff14bba4a36c1aee5e81ad2b0a229df30cd4b7 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 6 May 2020 10:55:34 +0100 Subject: [PATCH 17/18] Update README.md --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f61fdee5c..5de83cb3f 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,12 @@ This action runs GitHub's industry-leading static analysis engine, CodeQL, against a repository's source code to find security vulnerabilities. It then automatically uploads the results to GitHub so they can be displayed in the repository's security tab. CodeQL runs an extensible set of [queries](https://github.com/semmle/ql), which have been developed by the community and the [GitHub Security Lab](https://securitylab.github.com/) to find common vulnerabilities in your code. +## License + +This project is released under the [MIT License](LICENSE). + +The underlying CodeQL CLI, used in this action, is licensed under the [GitHub CodeQL Terms and Conditions](https://securitylab.github.com/tools/codeql/license). As such, this action may be used on open source projects hosted on GitHub, and on private repositories that are owned by an organisation with GitHub Advanced Security enabled. + ## Usage To get code scanning results from CodeQL analysis on your repo you can use the following workflow as a template: @@ -162,7 +168,3 @@ dotnet build /p:UseSharedCompilation=false ``` Version 3 does not require the additional flag. - -## License - -This project is released under the [MIT License](LICENSE). From 4c11b3d9bf7b4658602568713bef2078aa498e66 Mon Sep 17 00:00:00 2001 From: Ana Armas Romero <54946499+anaarmas@users.noreply.github.com> Date: Fri, 8 May 2020 20:16:30 +0200 Subject: [PATCH 18/18] rephrase Go support limitations --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a0fade7a0..c94da7b1d 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ Version 3 does not require the additional flag. ### Analysing Go together with other languages on `macos-latest` -This is currently not possible for Java, C/C++, or C#. +When running on macos it is currently not possible to analyze Go in conjunction with any of Java, C/C++, or C#. Each language can still be analyzed separately. ## License