Skip to content

Commit a2aef40

Browse files
authored
Merge pull request #4194 from paldepind/use-bqrs-diff
Use `bqrs diff` when comparing query results
2 parents 51aceaf + 5d9aab7 commit a2aef40

File tree

12 files changed

+273
-38236
lines changed

12 files changed

+273
-38236
lines changed

extensions/ql-vscode/src/codeql-cli/cli.ts

Lines changed: 95 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { SemVer } from "semver";
99
import type { Readable } from "stream";
1010
import tk from "tree-kill";
1111
import type { CancellationToken, Disposable, Uri } from "vscode";
12+
import { dir } from "tmp-promise";
1213

1314
import type {
1415
BqrsInfo,
@@ -202,9 +203,11 @@ interface BqrsDecodeOptions {
202203
entities?: string[];
203204
}
204205

205-
type OnLineCallback = (
206-
line: string,
207-
) => Promise<string | undefined> | string | undefined;
206+
interface BqrsDiffOptions {
207+
retainResultSets?: string[];
208+
}
209+
210+
type OnLineCallback = (line: string) => Promise<string | undefined>;
208211

209212
type VersionChangedListener = (
210213
newVersionAndFeatures: VersionAndFeatures | undefined,
@@ -368,12 +371,11 @@ export class CodeQLCliServer implements Disposable {
368371
*/
369372
private async launchProcess(): Promise<ChildProcessWithoutNullStreams> {
370373
const codeQlPath = await this.getCodeQlPath();
371-
const args = [];
372-
if (shouldDebugCliServer()) {
373-
args.push(
374-
"-J=-agentlib:jdwp=transport=dt_socket,address=localhost:9012,server=n,suspend=y,quiet=y",
375-
);
376-
}
374+
const args = shouldDebugCliServer()
375+
? [
376+
"-J=-agentlib:jdwp=transport=dt_socket,address=localhost:9012,server=n,suspend=y,quiet=y",
377+
]
378+
: [];
377379

378380
return spawnServer(
379381
codeQlPath,
@@ -399,15 +401,11 @@ export class CodeQLCliServer implements Disposable {
399401
}
400402
this.commandInProcess = true;
401403
try {
402-
//Launch the process if it doesn't exist
403-
if (!this.process) {
404-
this.process = await this.launchProcess();
405-
}
406-
// Grab the process so that typescript know that it is always defined.
407-
const process = this.process;
404+
// Launch the process if it doesn't exist
405+
this.process ??= await this.launchProcess();
408406

409407
// Compute the full args array
410-
const args = command.concat(LOGGING_FLAGS).concat(commandArgs);
408+
const args = command.concat(LOGGING_FLAGS, commandArgs);
411409
const argsString = args.join(" ");
412410
// If we are running silently, we don't want to print anything to the console.
413411
if (!silent) {
@@ -416,7 +414,7 @@ export class CodeQLCliServer implements Disposable {
416414
);
417415
}
418416
try {
419-
return await this.handleProcessOutput(process, {
417+
return await this.handleProcessOutput(this.process, {
420418
handleNullTerminator: true,
421419
onListenStart: (process) => {
422420
// Write the command followed by a null terminator.
@@ -451,7 +449,7 @@ export class CodeQLCliServer implements Disposable {
451449
): Promise<string> {
452450
const codeqlPath = await this.getCodeQlPath();
453451

454-
const args = command.concat(LOGGING_FLAGS).concat(commandArgs);
452+
const args = command.concat(LOGGING_FLAGS, commandArgs);
455453
const argsString = args.join(" ");
456454

457455
// If we are running silently, we don't want to print anything to the console.
@@ -569,16 +567,15 @@ export class CodeQLCliServer implements Disposable {
569567

570568
stdoutBuffers.push(newData);
571569

572-
if (handleNullTerminator) {
570+
if (
571+
handleNullTerminator &&
573572
// If the buffer ends in '0' then exit.
574573
// We don't have to check the middle as no output will be written after the null until
575574
// the next command starts
576-
if (
577-
newData.length > 0 &&
578-
newData.readUInt8(newData.length - 1) === 0
579-
) {
580-
resolve();
581-
}
575+
newData.length > 0 &&
576+
newData.readUInt8(newData.length - 1) === 0
577+
) {
578+
resolve();
582579
}
583580
};
584581
stderrListener = (newData: Buffer) => {
@@ -693,9 +690,7 @@ export class CodeQLCliServer implements Disposable {
693690
*/
694691
private runNext(): void {
695692
const callback = this.commandQueue.shift();
696-
if (callback) {
697-
callback();
698-
}
693+
callback?.();
699694
}
700695

701696
/**
@@ -813,7 +808,7 @@ export class CodeQLCliServer implements Disposable {
813808
* is false or not specified, this option is ignored.
814809
* @returns The contents of the command's stdout, if the command succeeded.
815810
*/
816-
runCodeQlCliCommand(
811+
private runCodeQlCliCommand(
817812
command: string[],
818813
commandArgs: string[],
819814
description: string,
@@ -825,9 +820,7 @@ export class CodeQLCliServer implements Disposable {
825820
token,
826821
}: RunOptions = {},
827822
): Promise<string> {
828-
if (progressReporter) {
829-
progressReporter.report({ message: description });
830-
}
823+
progressReporter?.report({ message: description });
831824

832825
if (runInNewProcess) {
833826
return this.runCodeQlCliInNewProcess(
@@ -874,18 +867,17 @@ export class CodeQLCliServer implements Disposable {
874867
* @param progressReporter Used to output progress messages, e.g. to the status bar.
875868
* @returns The contents of the command's stdout, if the command succeeded.
876869
*/
877-
async runJsonCodeQlCliCommand<OutputType>(
870+
private async runJsonCodeQlCliCommand<OutputType>(
878871
command: string[],
879872
commandArgs: string[],
880873
description: string,
881874
{ addFormat = true, ...runOptions }: JsonRunOptions = {},
882875
): Promise<OutputType> {
883-
let args: string[] = [];
884-
if (addFormat) {
876+
const args = [
885877
// Add format argument first, in case commandArgs contains positional parameters.
886-
args = args.concat(["--format", "json"]);
887-
}
888-
args = args.concat(commandArgs);
878+
...(addFormat ? ["--format", "json"] : []),
879+
...commandArgs,
880+
];
889881
const result = await this.runCodeQlCliCommand(
890882
command,
891883
args,
@@ -922,7 +914,7 @@ export class CodeQLCliServer implements Disposable {
922914
* @param runOptions Options for running the command.
923915
* @returns The contents of the command's stdout, if the command succeeded.
924916
*/
925-
async runJsonCodeQlCliCommandWithAuthentication<OutputType>(
917+
private async runJsonCodeQlCliCommandWithAuthentication<OutputType>(
926918
command: string[],
927919
commandArgs: string[],
928920
description: string,
@@ -1226,8 +1218,8 @@ export class CodeQLCliServer implements Disposable {
12261218
}
12271219

12281220
/**
1229-
* Gets the results from a bqrs.
1230-
* @param bqrsPath The path to the bqrs.
1221+
* Gets the results from a bqrs file.
1222+
* @param bqrsPath The path to the bqrs file.
12311223
* @param resultSet The result set to get.
12321224
* @param options Optional BqrsDecodeOptions arguments
12331225
*/
@@ -1240,30 +1232,62 @@ export class CodeQLCliServer implements Disposable {
12401232
`--entities=${entities.join(",")}`,
12411233
"--result-set",
12421234
resultSet,
1243-
]
1244-
.concat(pageSize ? ["--rows", pageSize.toString()] : [])
1245-
.concat(offset ? ["--start-at", offset.toString()] : [])
1246-
.concat([bqrsPath]);
1247-
return await this.runJsonCodeQlCliCommand<DecodedBqrsChunk>(
1235+
...(pageSize ? ["--rows", pageSize.toString()] : []),
1236+
...(offset ? ["--start-at", offset.toString()] : []),
1237+
bqrsPath,
1238+
];
1239+
return this.runJsonCodeQlCliCommand<DecodedBqrsChunk>(
12481240
["bqrs", "decode"],
12491241
subcommandArgs,
12501242
"Reading bqrs data",
12511243
);
12521244
}
12531245

12541246
/**
1255-
* Gets all results from a bqrs.
1256-
* @param bqrsPath The path to the bqrs.
1247+
* Gets all results from a bqrs file.
1248+
* @param bqrsPath The path to the bqrs file.
12571249
*/
12581250
async bqrsDecodeAll(bqrsPath: string): Promise<DecodedBqrs> {
1259-
return await this.runJsonCodeQlCliCommand<DecodedBqrs>(
1251+
return this.runJsonCodeQlCliCommand<DecodedBqrs>(
12601252
["bqrs", "decode"],
12611253
[bqrsPath],
12621254
"Reading all bqrs data",
12631255
);
12641256
}
12651257

1266-
async runInterpretCommand(
1258+
/** Gets the difference between two bqrs files. */
1259+
async bqrsDiff(
1260+
bqrsPath1: string,
1261+
bqrsPath2: string,
1262+
options?: BqrsDiffOptions,
1263+
): Promise<{
1264+
uniquePath1: string;
1265+
uniquePath2: string;
1266+
path: string;
1267+
cleanup: () => Promise<void>;
1268+
}> {
1269+
const { path, cleanup } = await dir({ unsafeCleanup: true });
1270+
const uniquePath1 = join(path, "left.bqrs");
1271+
const uniquePath2 = join(path, "right.bqrs");
1272+
await this.runCodeQlCliCommand(
1273+
["bqrs", "diff"],
1274+
[
1275+
"--left",
1276+
uniquePath1,
1277+
"--right",
1278+
uniquePath2,
1279+
...(options?.retainResultSets
1280+
? ["--retain-result-sets", options.retainResultSets.join(",")]
1281+
: []),
1282+
bqrsPath1,
1283+
bqrsPath2,
1284+
],
1285+
"Diffing bqrs files",
1286+
);
1287+
return { uniquePath1, uniquePath2, path, cleanup };
1288+
}
1289+
1290+
private async runInterpretCommand(
12671291
format: string,
12681292
additonalArgs: string[],
12691293
metadata: QueryMetadata,
@@ -1278,21 +1302,22 @@ export class CodeQLCliServer implements Disposable {
12781302
format,
12791303
// Forward all of the query metadata.
12801304
...Object.entries(metadata).map(([key, value]) => `-t=${key}=${value}`),
1281-
].concat(additonalArgs);
1282-
if (sourceInfo !== undefined) {
1283-
args.push(
1284-
"--source-archive",
1285-
sourceInfo.sourceArchive,
1286-
"--source-location-prefix",
1287-
sourceInfo.sourceLocationPrefix,
1288-
);
1289-
}
1290-
1291-
args.push("--threads", this.cliConfig.numberThreads.toString());
1292-
1293-
args.push("--max-paths", this.cliConfig.maxPaths.toString());
1305+
...additonalArgs,
1306+
...(sourceInfo !== undefined
1307+
? [
1308+
"--source-archive",
1309+
sourceInfo.sourceArchive,
1310+
"--source-location-prefix",
1311+
sourceInfo.sourceLocationPrefix,
1312+
]
1313+
: []),
1314+
"--threads",
1315+
this.cliConfig.numberThreads.toString(),
1316+
"--max-paths",
1317+
this.cliConfig.maxPaths.toString(),
1318+
resultsPath,
1319+
];
12941320

1295-
args.push(resultsPath);
12961321
await this.runCodeQlCliCommand(
12971322
["bqrs", "interpret"],
12981323
args,
@@ -1797,7 +1822,7 @@ export class CodeQLCliServer implements Disposable {
17971822
* Spawns a child server process using the CodeQL CLI
17981823
* and attaches listeners to it.
17991824
*
1800-
* @param config The configuration containing the path to the CLI.
1825+
* @param codeqlPath The configuration containing the path to the CLI.
18011826
* @param name Name of the server being started, to be shown in log and error messages.
18021827
* @param command The `codeql` command to be run, provided as an array of command/subcommand names.
18031828
* @param commandArgs The arguments to pass to the `codeql` command.
@@ -1823,9 +1848,9 @@ export function spawnServer(
18231848
// Start the server process.
18241849
const base = codeqlPath;
18251850
const argsString = args.join(" ");
1826-
if (progressReporter !== undefined) {
1827-
progressReporter.report({ message: `Starting ${name}` });
1828-
}
1851+
1852+
progressReporter?.report({ message: `Starting ${name}` });
1853+
18291854
void logger.log(`Starting ${name} using CodeQL CLI: ${base} ${argsString}`);
18301855
const child = spawnChildProcess(base, args);
18311856
if (!child || !child.pid) {
@@ -1859,9 +1884,8 @@ export function spawnServer(
18591884
child.stdout.on("data", stdoutListener);
18601885
}
18611886

1862-
if (progressReporter !== undefined) {
1863-
progressReporter.report({ message: `Started ${name}` });
1864-
}
1887+
progressReporter?.report({ message: `Started ${name}` });
1888+
18651889
void logger.log(`${name} started on PID: ${child.pid}`);
18661890
return child;
18671891
}

extensions/ql-vscode/src/codeql-cli/query-language.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export async function findLanguage(
1919
cliServer: CodeQLCliServer,
2020
queryUri: Uri | undefined,
2121
): Promise<QueryLanguage | undefined> {
22-
const uri = queryUri || window.activeTextEditor?.document.uri;
22+
const uri = queryUri ?? window.activeTextEditor?.document.uri;
2323
if (uri !== undefined) {
2424
try {
2525
const queryInfo = await cliServer.resolveQueryByLanguage(

extensions/ql-vscode/src/common/bqrs-raw-results-mapper.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,13 @@ export function bqrsToResultSet(
2727
schema: BqrsResultSetSchema,
2828
chunk: DecodedBqrsChunk,
2929
): RawResultSet {
30-
const name = schema.name;
31-
const totalRowCount = schema.rows;
32-
33-
const columns = schema.columns.map(mapColumn);
34-
35-
const rows = chunk.tuples.map(
36-
(tuple): Row => tuple.map((cell): CellValue => mapCellValue(cell)),
37-
);
38-
3930
const resultSet: RawResultSet = {
40-
name,
41-
totalRowCount,
42-
columns,
43-
rows,
31+
name: schema.name,
32+
totalRowCount: schema.rows,
33+
columns: schema.columns.map(mapColumn),
34+
rows: chunk.tuples.map(
35+
(tuple): Row => tuple.map((cell): CellValue => mapCellValue(cell)),
36+
),
4437
};
4538

4639
if (chunk.next) {

0 commit comments

Comments
 (0)