Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #138 from github/esbena/additional-codeql-options
Support additional codeql options
Esben Sparre Andreasen authored and GitHub committed Aug 18, 2020
2 parents c5e07eb + 9597f2e commit e9e2284
Showing 12 changed files with 284 additions and 8 deletions.
55 changes: 53 additions & 2 deletions lib/codeql.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/codeql.js.map

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions lib/codeql.test.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/codeql.test.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions lib/util.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/util.js.map

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions lib/util.test.js
2 changes: 1 addition & 1 deletion lib/util.test.js.map
32 changes: 32 additions & 0 deletions src/codeql.test.ts
@@ -63,3 +63,35 @@ test('parse codeql bundle url version', t => {
}
}
});

test('getExtraOptions works for explicit paths', t => {
t.deepEqual(codeql.getExtraOptions({}, ['foo'], []), []);

t.deepEqual(codeql.getExtraOptions({foo: [42]}, ['foo'], []), ['42']);

t.deepEqual(codeql.getExtraOptions({foo: {bar: [42]}}, ['foo', 'bar'], []), ['42']);
});

test('getExtraOptions works for wildcards', t => {
t.deepEqual(codeql.getExtraOptions({'*': [42]}, ['foo'], []), ['42']);
});

test('getExtraOptions works for wildcards and explicit paths', t => {
let o1 = {'*': [42], foo: [87]};
t.deepEqual(codeql.getExtraOptions(o1, ['foo'], []), ['42', '87']);

let o2 = {'*': [42], foo: [87]};
t.deepEqual(codeql.getExtraOptions(o2, ['foo', 'bar'], []), ['42']);

let o3 = {'*': [42], foo: { '*': [87], bar: [99]}};
let p = ['foo', 'bar'];
t.deepEqual(codeql.getExtraOptions(o3, p, []), ['42', '87', '99']);
});

test('getExtraOptions throws for bad content', t => {
t.throws(() => codeql.getExtraOptions({'*': 42}, ['foo'], []));

t.throws(() => codeql.getExtraOptions({foo: 87}, ['foo'], []));

t.throws(() => codeql.getExtraOptions({'*': [42], foo: { '*': 87, bar: [99]}}, ['foo', 'bar'], []));
});
81 changes: 79 additions & 2 deletions src/codeql.ts
@@ -14,6 +14,27 @@ import * as api from './api-client';
import * as defaults from './defaults.json'; // Referenced from codeql-action-sync-tool!
import * as util from './util';

type Options = (string|number|boolean)[];

/**
* Extra command line options for the codeql commands.
*/
interface ExtraOptions {
'*'?: Options;
database?: {
'*'?: Options,
init?: Options,
'trace-command'?: Options,
analyze?: Options,
finalize?: Options
};
resolve?: {
'*'?: Options,
extractor?: Options,
queries?: Options
};
}

export interface CodeQL {
/**
* Get the directory where the CodeQL executable is located.
@@ -286,6 +307,7 @@ function getCodeQLForCmd(cmd: string): CodeQL {
'trace-command',
databasePath,
...compilerSpecArg,
...getExtraOptionsFromEnv(['database', 'trace-command']),
process.execPath,
path.resolve(__dirname, 'tracer-env.js'),
envFile
@@ -299,6 +321,7 @@ function getCodeQLForCmd(cmd: string): CodeQL {
databasePath,
'--language=' + language,
'--source-root=' + sourceRoot,
...getExtraOptionsFromEnv(['database', 'init']),
]);
},
runAutobuild: async function(language: string) {
@@ -324,7 +347,8 @@ function getCodeQLForCmd(cmd: string): CodeQL {
'resolve',
'extractor',
'--format=json',
'--language=' + language
'--language=' + language,
...getExtraOptionsFromEnv(['resolve', 'extractor']),
],
{
silent: true,
@@ -342,6 +366,7 @@ function getCodeQLForCmd(cmd: string): CodeQL {
await exec.exec(cmd, [
'database',
'trace-command',
...getExtraOptionsFromEnv(['database', 'trace-command']),
databasePath,
'--',
traceCommand
@@ -351,6 +376,7 @@ function getCodeQLForCmd(cmd: string): CodeQL {
await exec.exec(cmd, [
'database',
'finalize',
...getExtraOptionsFromEnv(['database', 'finalize']),
databasePath
]);
},
@@ -359,7 +385,8 @@ function getCodeQLForCmd(cmd: string): CodeQL {
'resolve',
'queries',
...queries,
'--format=bylanguage'
'--format=bylanguage',
...getExtraOptionsFromEnv(['resolve', 'queries'])
];
if (extraSearchPath !== undefined) {
codeqlArgs.push('--search-path', extraSearchPath);
@@ -385,6 +412,7 @@ function getCodeQLForCmd(cmd: string): CodeQL {
'--format=sarif-latest',
'--output=' + sarifFile,
'--no-sarif-add-snippets',
...getExtraOptionsFromEnv(['database', 'analyze']),
querySuite
]);
}
@@ -398,3 +426,52 @@ export function isTracedLanguage(language: string): boolean {
export function isScannedLanguage(language: string): boolean {
return !isTracedLanguage(language);
}

/**
* Gets the options for `path` of `options` as an array of extra option strings.
*/
function getExtraOptionsFromEnv(path: string[]) {
let options: ExtraOptions = util.getExtraOptionsEnvParam();
return getExtraOptions(options, path, []);
}

/**
* Gets the options for `path` of `options` as an array of extra option strings.
*
* - the special terminal step name '*' in `options` matches all path steps
* - throws an exception if this conversion is impossible.
*/
export /* exported for testing */ function getExtraOptions(
options: any,
path: string[],
pathInfo: string[]): string[] {
/**
* Gets `options` as an array of extra option strings.
*
* - throws an exception mentioning `pathInfo` if this conversion is impossible.
*/
function asExtraOptions(options: any, pathInfo: string[]): string[] {
if (options === undefined) {
return [];
}
if (!Array.isArray(options)) {
const msg =
`The extra options for '${pathInfo.join('.')}' ('${JSON.stringify(options)}') are not in an array.`;
throw new Error(msg);
}
return options.map(o => {
const t = typeof o;
if (t !== 'string' && t !== 'number' && t !== 'boolean') {
const msg =
`The extra option for '${pathInfo.join('.')}' ('${JSON.stringify(o)}') is not a primitive value.`;
throw new Error(msg);
}
return o + '';
});
}
let all = asExtraOptions(options?.['*'], pathInfo.concat('*'));
let specific = path.length === 0 ?
asExtraOptions(options, pathInfo) :
getExtraOptions(options?.[path[0]], path?.slice(1), pathInfo.concat(path[0]));
return all.concat(specific);
}
34 changes: 34 additions & 0 deletions src/util.test.ts
@@ -116,3 +116,37 @@ test('prepareEnvironment() when a local run', t => {

process.env.CODEQL_LOCAL_RUN = origLocalRun;
});

test('getExtraOptionsEnvParam() succeeds on valid JSON with invalid options (for now)', t => {
const origExtraOptions = process.env.CODEQL_ACTION_EXTRA_OPTIONS;

const options = {foo: 42};

process.env.CODEQL_ACTION_EXTRA_OPTIONS = JSON.stringify(options);

t.deepEqual(util.getExtraOptionsEnvParam(), <any>options);

process.env.CODEQL_ACTION_EXTRA_OPTIONS = origExtraOptions;
});


test('getExtraOptionsEnvParam() succeeds on valid options', t => {
const origExtraOptions = process.env.CODEQL_ACTION_EXTRA_OPTIONS;

const options = { database: { init: ["--debug"] } };
process.env.CODEQL_ACTION_EXTRA_OPTIONS =
JSON.stringify(options);

t.deepEqual(util.getExtraOptionsEnvParam(), options);

process.env.CODEQL_ACTION_EXTRA_OPTIONS = origExtraOptions;
});

test('getExtraOptionsEnvParam() fails on invalid JSON', t => {
const origExtraOptions = process.env.CODEQL_ACTION_EXTRA_OPTIONS;

process.env.CODEQL_ACTION_EXTRA_OPTIONS = "{{invalid-json}}";
t.throws(util.getExtraOptionsEnvParam);

process.env.CODEQL_ACTION_EXTRA_OPTIONS = origExtraOptions;
});
20 changes: 20 additions & 0 deletions src/util.ts
@@ -39,6 +39,26 @@ export function getRequiredEnvParam(paramName: string): string {
return value;
}

/**
* Get the extra options for the codeql commands.
*/
export function getExtraOptionsEnvParam(): object {
const varName = 'CODEQL_ACTION_EXTRA_OPTIONS';
const raw = process.env[varName];
if (raw === undefined || raw.length === 0) {
return {};
}
try {
return JSON.parse(raw);
} catch (e) {
throw new Error(
varName +
' environment variable is set, but does not contain valid JSON: ' +
e.message
);
}
}

export function isLocalRun(): boolean {
return !!process.env.CODEQL_LOCAL_RUN
&& process.env.CODEQL_LOCAL_RUN !== 'false'

0 comments on commit e9e2284

Please sign in to comment.