Skip to content

Commit 9300c07

Browse files
committed
Add command to view the DIL of a query
1 parent 8e817ee commit 9300c07

7 files changed

Lines changed: 172 additions & 2 deletions

File tree

extensions/ql-vscode/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- Fix proper escaping of backslashes in SARIF message strings.
1313
- Allow setting `codeQL.runningQueries.numberOfThreads` and `codeQL.runningTests.numberOfThreads` to 0, (which is interpreted as 'use one thread per core on the machine').
1414
- Clear the problems view of all CodeQL query results when a database is removed.
15+
- Add a `View DIL` command on query history items. This opens a text editor containing the Datalog Intermediary Language representation of the compiled query.
1516

1617
## 1.3.3 - 16 September 2020
1718

extensions/ql-vscode/package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,10 @@
329329
"command": "codeQLQueryHistory.viewSarif",
330330
"title": "View SARIF"
331331
},
332+
{
333+
"command": "codeQLQueryHistory.viewDil",
334+
"title": "View DIL"
335+
},
332336
{
333337
"command": "codeQLQueryHistory.setLabel",
334338
"title": "Set Label"
@@ -484,6 +488,11 @@
484488
"group": "9_qlCommands",
485489
"when": "view == codeQLQueryHistory && viewItem == interpretedResultsItem"
486490
},
491+
{
492+
"command": "codeQLQueryHistory.viewDil",
493+
"group": "9_qlCommands",
494+
"when": "view == codeQLQueryHistory"
495+
},
487496
{
488497
"command": "codeQLTests.showOutputDifferences",
489498
"group": "qltest@1",
@@ -600,6 +609,10 @@
600609
"command": "codeQLQueryHistory.viewSarif",
601610
"when": "false"
602611
},
612+
{
613+
"command": "codeQLQueryHistory.viewDil",
614+
"when": "false"
615+
},
603616
{
604617
"command": "codeQLQueryHistory.setLabel",
605618
"when": "false"

extensions/ql-vscode/src/cli.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,14 @@ export class CodeQLCliServer implements Disposable {
650650
'Resolving queries',
651651
);
652652
}
653+
654+
async generateDil(qloFile: string, outFile: string): Promise<void> {
655+
await this.runCodeQlCliCommand(
656+
['query', 'decompile'],
657+
['--kind', 'dil', '-o', outFile, qloFile],
658+
'Generating DIL',
659+
);
660+
}
653661
}
654662

655663
/**

extensions/ql-vscode/src/extension.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ async function activateWithInstalledDistribution(
353353

354354
const qhm = new QueryHistoryManager(
355355
ctx,
356+
qs,
356357
queryHistoryConfigurationListener,
357358
showResults,
358359
async (from: CompletedQuery, to: CompletedQuery) =>

extensions/ql-vscode/src/query-history.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { QueryWithResults } from './run-queries';
77
import * as helpers from './helpers';
88
import { logger } from './logging';
99
import { URLSearchParams } from 'url';
10+
import { QueryServerClient } from './queryserver-client';
1011

1112
/**
1213
* query-history.ts
@@ -180,6 +181,7 @@ export class QueryHistoryManager {
180181

181182
constructor(
182183
ctx: ExtensionContext,
184+
private qs: QueryServerClient,
183185
private queryHistoryConfigListener: QueryHistoryConfig,
184186
private selectedCallback: (item: CompletedQuery) => Promise<void>,
185187
private doCompareCallback: (
@@ -250,6 +252,12 @@ export class QueryHistoryManager {
250252
this.handleViewSarif.bind(this)
251253
)
252254
);
255+
ctx.subscriptions.push(
256+
helpers.commandRunner(
257+
'codeQLQueryHistory.viewDil',
258+
this.handleViewDil.bind(this)
259+
)
260+
);
253261
ctx.subscriptions.push(
254262
helpers.commandRunner(
255263
'codeQLQueryHistory.itemClicked',
@@ -459,6 +467,19 @@ export class QueryHistoryManager {
459467
}
460468
}
461469

470+
async handleViewDil(
471+
singleItem: CompletedQuery,
472+
multiSelect: CompletedQuery[],
473+
) {
474+
if (!this.assertSingleQuery(multiSelect)) {
475+
return;
476+
}
477+
478+
await this.tryOpenExternalFile(
479+
await singleItem.query.ensureDilPath(this.qs)
480+
);
481+
}
482+
462483
async getQueryText(queryHistoryItem: CompletedQuery): Promise<string> {
463484
if (queryHistoryItem.options.queryText) {
464485
return queryHistoryItem.options.queryText;
@@ -511,7 +532,9 @@ export class QueryHistoryManager {
511532
private async tryOpenExternalFile(fileLocation: string) {
512533
const uri = vscode.Uri.file(fileLocation);
513534
try {
514-
await vscode.window.showTextDocument(uri);
535+
await vscode.window.showTextDocument(uri, {
536+
preview: false
537+
});
515538
} catch (e) {
516539
if (
517540
e.message.includes(

extensions/ql-vscode/src/run-queries.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export class QueryInfo {
5151
private static nextQueryId = 0;
5252

5353
readonly compiledQueryPath: string;
54+
readonly dilPath: string;
5455
readonly resultsPaths: ResultsPaths;
5556
readonly dataset: Uri; // guarantee the existence of a well-defined dataset dir at this point
5657
readonly queryID: number;
@@ -65,9 +66,10 @@ export class QueryInfo {
6566
) {
6667
this.queryID = QueryInfo.nextQueryId++;
6768
this.compiledQueryPath = path.join(tmpDir.name, `compiledQuery${this.queryID}.qlo`);
69+
this.dilPath = path.join(tmpDir.name, `results${this.queryID}.dil`);
6870
this.resultsPaths = {
6971
resultsPath: path.join(tmpDir.name, `results${this.queryID}.bqrs`),
70-
interpretedResultsPath: path.join(tmpDir.name, `interpretedResults${this.queryID}.sarif`),
72+
interpretedResultsPath: path.join(tmpDir.name, `interpretedResults${this.queryID}.sarif`)
7173
};
7274
if (dbItem.contents === undefined) {
7375
throw new Error('Can\'t run query on invalid database.');
@@ -169,6 +171,29 @@ export class QueryInfo {
169171
async hasInterpretedResults(): Promise<boolean> {
170172
return fs.pathExists(this.resultsPaths.interpretedResultsPath);
171173
}
174+
175+
/**
176+
* Holds if this query already has DIL produced
177+
*/
178+
async hasDil(): Promise<boolean> {
179+
return fs.pathExists(this.dilPath);
180+
}
181+
182+
async ensureDilPath(qs: qsClient.QueryServerClient): Promise<string> {
183+
if (await this.hasDil()) {
184+
return this.dilPath;
185+
}
186+
187+
if (!(await fs.pathExists(this.compiledQueryPath))) {
188+
throw new Error(
189+
`Cannot create DIL because compiled query is missing. ${this.compiledQueryPath}`
190+
);
191+
}
192+
193+
await qs.cliServer.generateDil(this.compiledQueryPath, this.dilPath);
194+
return this.dilPath;
195+
}
196+
172197
}
173198

174199
export interface QueryWithResults {
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import * as chai from 'chai';
2+
import * as path from 'path';
3+
import 'mocha';
4+
import 'sinon-chai';
5+
import * as sinon from 'sinon';
6+
import * as chaiAsPromised from 'chai-as-promised';
7+
8+
import { QueryInfo } from '../../run-queries';
9+
import { QlProgram, Severity, compileQuery } from '../../messages';
10+
import { DatabaseItem } from '../../databases';
11+
12+
chai.use(chaiAsPromised);
13+
const expect = chai.expect;
14+
15+
describe('run-queries', () => {
16+
it('should create a QueryInfo', () => {
17+
const info = createMockQueryInfo();
18+
19+
const queryID = info.queryID;
20+
expect(path.basename(info.compiledQueryPath)).to.eq(`compiledQuery${queryID}.qlo`);
21+
expect(path.basename(info.dilPath)).to.eq(`results${queryID}.dil`);
22+
expect(path.basename(info.resultsPaths.resultsPath)).to.eq(`results${queryID}.bqrs`);
23+
expect(path.basename(info.resultsPaths.interpretedResultsPath)).to.eq(`interpretedResults${queryID}.sarif`);
24+
expect(info.dataset).to.eq('file:///abc');
25+
});
26+
27+
describe('compile', () => {
28+
it('should compile', async () => {
29+
const info = createMockQueryInfo();
30+
const qs = createMockQueryServerClient();
31+
const mockProgress = 'progress-monitor';
32+
const mockCancel = 'cancel-token';
33+
34+
const results = await info.compile(
35+
qs as any,
36+
mockProgress as any,
37+
mockCancel as any
38+
);
39+
40+
expect(results).to.deep.eq([
41+
{ message: 'err', severity: Severity.ERROR }
42+
]);
43+
44+
expect(qs.sendRequest).to.have.been.calledOnceWith(
45+
compileQuery,
46+
{
47+
compilationOptions: {
48+
computeNoLocationUrls: true,
49+
failOnWarnings: false,
50+
fastCompilation: false,
51+
includeDilInQlo: true,
52+
localChecking: false,
53+
noComputeGetUrl: false,
54+
noComputeToString: false,
55+
},
56+
extraOptions: {
57+
timeoutSecs: 5
58+
},
59+
queryToCheck: 'my-program',
60+
resultPath: info.compiledQueryPath,
61+
target: { query: {} }
62+
},
63+
mockCancel,
64+
mockProgress
65+
);
66+
});
67+
});
68+
69+
function createMockQueryInfo() {
70+
return new QueryInfo(
71+
'my-program' as unknown as QlProgram,
72+
{
73+
contents: {
74+
datasetUri: 'file:///abc'
75+
}
76+
} as unknown as DatabaseItem,
77+
'my-scheme' // queryDbscheme
78+
);
79+
}
80+
81+
function createMockQueryServerClient() {
82+
return {
83+
config: {
84+
timeoutSecs: 5
85+
},
86+
sendRequest: sinon.stub().returns(new Promise(resolve => {
87+
resolve({
88+
messages: [
89+
{ message: 'err', severity: Severity.ERROR },
90+
{ message: 'warn', severity: Severity.WARNING },
91+
]
92+
});
93+
})),
94+
logger: {
95+
log: sinon.spy()
96+
}
97+
};
98+
}
99+
});

0 commit comments

Comments
 (0)