Skip to content

Commit bd155ab

Browse files
committed
Graph viewer support
1 parent 1f47fee commit bd155ab

15 files changed

Lines changed: 1012 additions & 39 deletions

extensions/ql-vscode/package-lock.json

Lines changed: 695 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extensions/ql-vscode/package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"onCommand:codeQLDatabases.chooseDatabaseLgtm",
3535
"onCommand:codeQL.setCurrentDatabase",
3636
"onCommand:codeQL.viewAst",
37+
"onCommand:codeQL.viewCfg",
3738
"onCommand:codeQL.chooseDatabaseFolder",
3839
"onCommand:codeQL.chooseDatabaseArchive",
3940
"onCommand:codeQL.chooseDatabaseInternet",
@@ -281,6 +282,10 @@
281282
"command": "codeQL.viewAst",
282283
"title": "CodeQL: View AST"
283284
},
285+
{
286+
"command": "codeQL.viewCfg",
287+
"title": "CodeQL: View CFG"
288+
},
284289
{
285290
"command": "codeQL.upgradeCurrentDatabase",
286291
"title": "CodeQL: Upgrade Current Database"
@@ -615,6 +620,11 @@
615620
"group": "9_qlCommands",
616621
"when": "resourceScheme == codeql-zip-archive"
617622
},
623+
{
624+
"command": "codeQL.viewCfg",
625+
"group": "9_qlCommands",
626+
"when": "resourceScheme == codeql-zip-archive"
627+
},
618628
{
619629
"command": "codeQL.runQueries",
620630
"group": "9_qlCommands",
@@ -642,6 +652,10 @@
642652
"command": "codeQL.viewAst",
643653
"when": "resourceScheme == codeql-zip-archive"
644654
},
655+
{
656+
"command": "codeQL.viewCfg",
657+
"when": "resourceScheme == codeql-zip-archive"
658+
},
645659
{
646660
"command": "codeQLDatabases.setCurrentDatabase",
647661
"when": "false"
@@ -830,6 +844,8 @@
830844
"dependencies": {
831845
"child-process-promise": "^2.2.1",
832846
"classnames": "~2.2.6",
847+
"d3": "^6.3.1",
848+
"d3-graphviz": "^2.6.1",
833849
"fs-extra": "^9.0.1",
834850
"glob-promise": "^3.4.0",
835851
"js-yaml": "^3.14.0",
@@ -854,6 +870,8 @@
854870
"@types/chai-as-promised": "~7.1.2",
855871
"@types/child-process-promise": "^2.2.1",
856872
"@types/classnames": "~2.2.9",
873+
"@types/d3": "^6.2.0",
874+
"@types/d3-graphviz": "^2.6.6",
857875
"@types/fs-extra": "^9.0.6",
858876
"@types/glob": "^7.1.1",
859877
"@types/google-protobuf": "^3.2.7",

extensions/ql-vscode/src/blob.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88
* succeeds.
99
*/
1010

11-
declare type Blob = string;
11+
//declare type Blob = string; // TODO: Check this

extensions/ql-vscode/src/cli.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,26 @@ export class CodeQLCliServer implements Disposable {
642642
}
643643
}
644644

645+
async readDotFiles(dir: string): Promise<string[]> {
646+
return Promise.all((await fs.readdir(dir))
647+
.filter(name => path.extname(name).toLowerCase() === '.dot')
648+
.map(file => fs.readFile(path.join(dir, file), 'utf8'))
649+
);
650+
}
651+
652+
async interpretBqrsGraph(metadata: QueryMetadata, resultsPath: string, interpretedResultsPath: string, sourceInfo?: SourceInfo): Promise<string[]> {
653+
const additionalArgs = sourceInfo ? ['--dot-location-url-format', 'file://' + sourceInfo.sourceLocationPrefix + '{path}:{start:line}:{start:column}:{end:line}:{end:column}'] : [];
654+
655+
await this.runInterpretCommand('dot', additionalArgs, metadata, resultsPath, interpretedResultsPath, sourceInfo);
656+
657+
try {
658+
const dot = await this.readDotFiles(interpretedResultsPath);
659+
return dot;
660+
} catch (err) {
661+
throw new Error(`Reading output of interpretation failed: ${err.stderr || err}`);
662+
}
663+
}
664+
645665
async generateResultsCsv(metadata: QueryMetadata, resultsPath: string, csvPath: string, sourceInfo?: SourceInfo): Promise<void> {
646666
await this.runInterpretCommand(CSV_FORMAT, [], metadata, resultsPath, csvPath, sourceInfo);
647667
}

extensions/ql-vscode/src/contextual/keyType.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export enum KeyType {
22
DefinitionQuery = 'DefinitionQuery',
33
ReferenceQuery = 'ReferenceQuery',
44
PrintAstQuery = 'PrintAstQuery',
5+
PrintCfgQuery = 'PrintCfgQuery',
56
}
67

78
export function tagOfKeyType(keyType: KeyType): string {
@@ -12,6 +13,8 @@ export function tagOfKeyType(keyType: KeyType): string {
1213
return 'ide-contextual-queries/local-references';
1314
case KeyType.PrintAstQuery:
1415
return 'ide-contextual-queries/print-ast';
16+
case KeyType.PrintCfgQuery:
17+
return 'ide-contextual-queries/print-cfg';
1518
}
1619
}
1720

@@ -23,6 +26,8 @@ export function nameOfKeyType(keyType: KeyType): string {
2326
return 'references';
2427
case KeyType.PrintAstQuery:
2528
return 'print AST';
29+
case KeyType.PrintCfgQuery:
30+
return 'print CFG';
2631
}
2732
}
2833

@@ -32,6 +37,7 @@ export function kindOfKeyType(keyType: KeyType): string {
3237
case KeyType.ReferenceQuery:
3338
return 'definitions';
3439
case KeyType.PrintAstQuery:
40+
case KeyType.PrintCfgQuery:
3541
return 'graph';
3642
}
3743
}

extensions/ql-vscode/src/contextual/templateProvider.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,62 @@ export class TemplatePrintAstProvider {
200200
);
201201
}
202202
}
203+
204+
export class TemplatePrintCfgProvider {
205+
private cache: CachedOperation<[Uri, messages.TemplateDefinitions] | undefined>;
206+
207+
constructor(
208+
private cli: CodeQLCliServer,
209+
private dbm: DatabaseManager
210+
) {
211+
this.cache = new CachedOperation<[Uri, messages.TemplateDefinitions] | undefined>(this.getCfgUri.bind(this));
212+
}
213+
214+
async provideCfgUri(document?: TextDocument): Promise<[Uri, messages.TemplateDefinitions] | undefined> {
215+
if (!document) {
216+
return;
217+
}
218+
return await this.cache.get(document.uri.toString());
219+
}
220+
221+
private async getCfgUri(uriString: string): Promise<[Uri, messages.TemplateDefinitions]> {
222+
const uri = Uri.parse(uriString, true);
223+
if (uri.scheme !== zipArchiveScheme) {
224+
throw new Error('CFG Viewing is only available for databases with zipped source archives.');
225+
}
226+
227+
const zippedArchive = decodeSourceArchiveUri(uri);
228+
const sourceArchiveUri = encodeArchiveBasePath(zippedArchive.sourceArchiveZipPath);
229+
const db = this.dbm.findDatabaseItemBySourceArchive(sourceArchiveUri);
230+
231+
if (!db) {
232+
throw new Error('Can\'t infer database from the provided source.');
233+
}
234+
235+
const qlpack = await qlpackOfDatabase(this.cli, db);
236+
if (!qlpack) {
237+
throw new Error('Can\'t infer qlpack from database source archive');
238+
}
239+
const queries = await resolveQueries(this.cli, qlpack, KeyType.PrintCfgQuery);
240+
if (queries.length > 1) {
241+
throw new Error('Found multiple Print CFG queries. Can\'t continue');
242+
}
243+
if (queries.length === 0) {
244+
throw new Error('Did not find any Print CFG queries. Can\'t continue');
245+
}
246+
247+
const queryUri = Uri.file(queries[0]);
248+
249+
const templates: messages.TemplateDefinitions = {
250+
[TEMPLATE_NAME]: {
251+
values: {
252+
tuples: [[{
253+
stringValue: zippedArchive.pathWithinSourceArchive
254+
}]]
255+
}
256+
}
257+
};
258+
259+
return [queryUri, templates];
260+
}
261+
}

extensions/ql-vscode/src/extension.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ import { DatabaseUI } from './databases-ui';
3232
import {
3333
TemplateQueryDefinitionProvider,
3434
TemplateQueryReferenceProvider,
35-
TemplatePrintAstProvider
35+
TemplatePrintAstProvider,
36+
TemplatePrintCfgProvider
3637
} from './contextual/templateProvider';
3738
import {
3839
DEFAULT_DISTRIBUTION_VERSION_RANGE,
@@ -68,6 +69,7 @@ import {
6869
ProgressUpdate
6970
} from './commandRunner';
7071
import { CodeQlStatusBarHandler } from './status-bar';
72+
import * as messages from './pure/messages';
7173

7274
/**
7375
* extension.ts
@@ -457,6 +459,7 @@ async function activateWithInstalledDistribution(
457459
selectedQuery: Uri | undefined,
458460
progress: ProgressCallback,
459461
token: CancellationToken,
462+
templates?: messages.TemplateDefinitions,
460463
): Promise<void> {
461464
if (qs !== undefined) {
462465
const dbItem = await databaseUI.getDatabaseItem(progress, token);
@@ -470,7 +473,8 @@ async function activateWithInstalledDistribution(
470473
quickEval,
471474
selectedQuery,
472475
progress,
473-
token
476+
token,
477+
templates
474478
);
475479
const item = qhm.buildCompletedQuery(info);
476480
await showResultsForCompletedQuery(item, WebviewReveal.NotForced);
@@ -675,6 +679,26 @@ async function activateWithInstalledDistribution(
675679
commandRunner('codeQL.openDocumentation', async () =>
676680
env.openExternal(Uri.parse('https://codeql.github.com/docs/'))));
677681

682+
ctx.subscriptions.push(
683+
commandRunnerWithProgress(
684+
'codeQL.viewCfg',
685+
async (
686+
progress: ProgressCallback,
687+
token: CancellationToken
688+
) => {
689+
const res = await new TemplatePrintCfgProvider(cliServer, dbm)
690+
.provideCfgUri(window.activeTextEditor?.document);
691+
if (res) {
692+
await compileAndRunQuery(false, res[0], progress, token, res[1]);
693+
}
694+
},
695+
{
696+
title: 'Calculate CFG',
697+
cancellable: true
698+
}
699+
)
700+
);
701+
678702
logger.log('Starting language server.');
679703
ctx.subscriptions.push(client.start());
680704

0 commit comments

Comments
 (0)