Skip to content

Commit c18f795

Browse files
authored
Merge pull request #1111 from github/aeisenberg/graph-viewer
More work on the graph viewer
2 parents 95d93ee + 3a292b0 commit c18f795

29 files changed

Lines changed: 2179 additions & 1021 deletions

.github/workflows/main.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222

2323
- uses: actions/setup-node@v1
2424
with:
25-
node-version: '14.14.0'
25+
node-version: '16.13.0'
2626

2727
- name: Install dependencies
2828
working-directory: extensions/ql-vscode
@@ -82,7 +82,7 @@ jobs:
8282

8383
- uses: actions/setup-node@v1
8484
with:
85-
node-version: '14.14.0'
85+
node-version: '16.13.0'
8686

8787
- name: Install dependencies
8888
working-directory: extensions/ql-vscode
@@ -147,7 +147,7 @@ jobs:
147147

148148
- uses: actions/setup-node@v1
149149
with:
150-
node-version: '14.14.0'
150+
node-version: '16.13.0'
151151

152152
- name: Install dependencies
153153
working-directory: extensions/ql-vscode

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222

2323
- uses: actions/setup-node@v1
2424
with:
25-
node-version: '10.18.1'
25+
node-version: '16.13.0'
2626

2727
- name: Install dependencies
2828
run: |

extensions/ql-vscode/package-lock.json

Lines changed: 1535 additions & 862 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: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"onCommand:codeQLDatabases.chooseDatabaseLgtm",
4949
"onCommand:codeQL.setCurrentDatabase",
5050
"onCommand:codeQL.viewAst",
51+
"onCommand:codeQL.viewCfg",
5152
"onCommand:codeQL.openReferencedFile",
5253
"onCommand:codeQL.previewQueryHelp",
5354
"onCommand:codeQL.chooseDatabaseFolder",
@@ -374,6 +375,10 @@
374375
"command": "codeQL.viewAst",
375376
"title": "CodeQL: View AST"
376377
},
378+
{
379+
"command": "codeQL.viewCfg",
380+
"title": "CodeQL: View CFG"
381+
},
377382
{
378383
"command": "codeQL.upgradeCurrentDatabase",
379384
"title": "CodeQL: Upgrade Current Database"
@@ -743,6 +748,11 @@
743748
"group": "9_qlCommands",
744749
"when": "resourceScheme == codeql-zip-archive && !explorerResourceIsFolder && !listMultiSelection"
745750
},
751+
{
752+
"command": "codeQL.viewCfg",
753+
"group": "9_qlCommands",
754+
"when": "resourceScheme == codeql-zip-archive && config.codeQL.canary"
755+
},
746756
{
747757
"command": "codeQL.runQueries",
748758
"group": "9_qlCommands",
@@ -804,6 +814,10 @@
804814
"command": "codeQL.viewAst",
805815
"when": "resourceScheme == codeql-zip-archive"
806816
},
817+
{
818+
"command": "codeQL.viewCfg",
819+
"when": "resourceScheme == codeql-zip-archive && config.codeQL.canary"
820+
},
807821
{
808822
"command": "codeQLDatabases.setCurrentDatabase",
809823
"when": "false"
@@ -950,6 +964,10 @@
950964
"command": "codeQL.viewAst",
951965
"when": "resourceScheme == codeql-zip-archive"
952966
},
967+
{
968+
"command": "codeQL.viewCfg",
969+
"when": "resourceScheme == codeql-zip-archive && config.codeQL.canary"
970+
},
953971
{
954972
"command": "codeQL.quickEval",
955973
"when": "editorLangId == ql"
@@ -1023,6 +1041,8 @@
10231041
"@primer/react": "^34.3.0",
10241042
"child-process-promise": "^2.2.1",
10251043
"classnames": "~2.2.6",
1044+
"d3": "^6.3.1",
1045+
"d3-graphviz": "^2.6.1",
10261046
"fs-extra": "^9.0.1",
10271047
"glob-promise": "^3.4.0",
10281048
"js-yaml": "^3.14.0",
@@ -1054,6 +1074,8 @@
10541074
"@types/child-process-promise": "^2.2.1",
10551075
"@types/classnames": "~2.2.9",
10561076
"@types/del": "^4.0.0",
1077+
"@types/d3": "^6.2.0",
1078+
"@types/d3-graphviz": "^2.6.6",
10571079
"@types/fs-extra": "^9.0.6",
10581080
"@types/glob": "^7.1.1",
10591081
"@types/google-protobuf": "^3.2.7",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* The d3 library is designed to work in both the browser and
3+
* node. Consequently their typings files refer to both node
4+
* types like `Buffer` (which don't exist in the browser), and browser
5+
* types like `Blob` (which don't exist in node). Instead of sticking
6+
* all of `dom` in `compilerOptions.lib`, it suffices just to put in a
7+
* stub definition of the affected types so that compilation
8+
* succeeds.
9+
*/
10+
11+
declare type RequestInit = Record<string, unknown>;
12+
declare type ElementTagNameMap = any;
13+
declare type NodeListOf<T> = Record<string, T>;
14+
declare type Node = Record<string, unknown>;
15+
declare type XMLDocument = Record<string, unknown>;

extensions/ql-vscode/src/cli.ts

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as cpp from 'child-process-promise';
22
import * as child_process from 'child_process';
3+
import * as fs from 'fs-extra';
34
import * as path from 'path';
45
import * as sarif from 'sarif';
56
import { SemVer } from 'semver';
@@ -17,7 +18,7 @@ import { QueryMetadata, SortDirection } from './pure/interface-types';
1718
import { Logger, ProgressReporter } from './logging';
1819
import { CompilationMessage } from './pure/messages';
1920
import { sarifParser } from './sarif-parser';
20-
import { dbSchemeToLanguage } from './helpers';
21+
import { dbSchemeToLanguage, walkDirectory } from './helpers';
2122

2223
/**
2324
* The version of the SARIF format that we are using.
@@ -687,20 +688,13 @@ export class CodeQLCliServer implements Disposable {
687688
return await this.runJsonCodeQlCliCommand<DecodedBqrsChunk>(['bqrs', 'decode'], subcommandArgs, 'Reading bqrs data');
688689
}
689690

690-
async runInterpretCommand(format: string, metadata: QueryMetadata, resultsPath: string, interpretedResultsPath: string, sourceInfo?: SourceInfo) {
691+
async runInterpretCommand(format: string, additonalArgs: string[], metadata: QueryMetadata, resultsPath: string, interpretedResultsPath: string, sourceInfo?: SourceInfo) {
691692
const args = [
692693
'--output', interpretedResultsPath,
693694
'--format', format,
694695
// Forward all of the query metadata.
695696
...Object.entries(metadata).map(([key, value]) => `-t=${key}=${value}`)
696-
];
697-
if (format == SARIF_FORMAT) {
698-
// TODO: This flag means that we don't group interpreted results
699-
// by primary location. We may want to revisit whether we call
700-
// interpretation with and without this flag, or do some
701-
// grouping client-side.
702-
args.push('--no-group-results');
703-
}
697+
].concat(additonalArgs);
704698
if (sourceInfo !== undefined) {
705699
args.push(
706700
'--source-archive', sourceInfo.sourceArchive,
@@ -722,13 +716,47 @@ export class CodeQLCliServer implements Disposable {
722716
await this.runCodeQlCliCommand(['bqrs', 'interpret'], args, 'Interpreting query results');
723717
}
724718

725-
async interpretBqrs(metadata: QueryMetadata, resultsPath: string, interpretedResultsPath: string, sourceInfo?: SourceInfo): Promise<sarif.Log> {
726-
await this.runInterpretCommand(SARIF_FORMAT, metadata, resultsPath, interpretedResultsPath, sourceInfo);
719+
async interpretBqrsSarif(metadata: QueryMetadata, resultsPath: string, interpretedResultsPath: string, sourceInfo?: SourceInfo): Promise<sarif.Log> {
720+
const additionalArgs = [
721+
// TODO: This flag means that we don't group interpreted results
722+
// by primary location. We may want to revisit whether we call
723+
// interpretation with and without this flag, or do some
724+
// grouping client-side.
725+
'--no-group-results'
726+
];
727+
728+
await this.runInterpretCommand(SARIF_FORMAT, additionalArgs, metadata, resultsPath, interpretedResultsPath, sourceInfo);
727729
return await sarifParser(interpretedResultsPath);
728730
}
729731

732+
// Warning: this function is untenable for large dot files,
733+
async readDotFiles(dir: string): Promise<string[]> {
734+
const dotFiles: Promise<string>[] = [];
735+
for await (const file of walkDirectory(dir)) {
736+
if (file.endsWith('.dot')) {
737+
dotFiles.push(fs.readFile(file, 'utf8'));
738+
}
739+
}
740+
return Promise.all(dotFiles);
741+
}
742+
743+
async interpretBqrsGraph(metadata: QueryMetadata, resultsPath: string, interpretedResultsPath: string, sourceInfo?: SourceInfo): Promise<string[]> {
744+
const additionalArgs = sourceInfo
745+
? ['--dot-location-url-format', 'file://' + sourceInfo.sourceLocationPrefix + '{path}:{start:line}:{start:column}:{end:line}:{end:column}']
746+
: [];
747+
748+
await this.runInterpretCommand('dot', additionalArgs, metadata, resultsPath, interpretedResultsPath, sourceInfo);
749+
750+
try {
751+
const dot = await this.readDotFiles(interpretedResultsPath);
752+
return dot;
753+
} catch (err) {
754+
throw new Error(`Reading output of interpretation failed: ${err.stderr || err}`);
755+
}
756+
}
757+
730758
async generateResultsCsv(metadata: QueryMetadata, resultsPath: string, csvPath: string, sourceInfo?: SourceInfo): Promise<void> {
731-
await this.runInterpretCommand(CSV_FORMAT, metadata, resultsPath, csvPath, sourceInfo);
759+
await this.runInterpretCommand(CSV_FORMAT, [], metadata, resultsPath, csvPath, sourceInfo);
732760
}
733761

734762
async sortBqrs(resultsPath: string, sortedResultsPath: string, resultSet: string, sortKeys: number[], sortDirections: SortDirection[]): Promise<void> {
@@ -1224,9 +1252,9 @@ export class CliVersionConstraint {
12241252

12251253
/**
12261254
* CLI version where the `--evaluator-log` and related options to the query server were introduced,
1227-
* on a per-query server basis.
1255+
* on a per-query server basis.
12281256
*/
1229-
public static CLI_VERSION_WITH_STRUCTURED_EVAL_LOG = new SemVer('2.8.2');
1257+
public static CLI_VERSION_WITH_STRUCTURED_EVAL_LOG = new SemVer('2.8.2');
12301258

12311259
constructor(private readonly cli: CodeQLCliServer) {
12321260
/**/

extensions/ql-vscode/src/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ const CUSTOM_LOG_DIRECTORY_SETTING = new Setting('customLogDirectory', RUNNING_Q
9595

9696
/** When these settings change, the running query server should be restarted. */
9797
const QUERY_SERVER_RESTARTING_SETTINGS = [
98-
NUMBER_OF_THREADS_SETTING, SAVE_CACHE_SETTING, CACHE_SIZE_SETTING, MEMORY_SETTING,
99-
DEBUG_SETTING, CUSTOM_LOG_DIRECTORY_SETTING,
98+
NUMBER_OF_THREADS_SETTING, SAVE_CACHE_SETTING, CACHE_SIZE_SETTING, MEMORY_SETTING,
99+
DEBUG_SETTING, CUSTOM_LOG_DIRECTORY_SETTING,
100100
];
101101

102102
export interface QueryServerConfig {

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
@@ -230,3 +230,62 @@ export class TemplatePrintAstProvider {
230230
};
231231
}
232232
}
233+
234+
export class TemplatePrintCfgProvider {
235+
private cache: CachedOperation<[Uri, messages.TemplateDefinitions] | undefined>;
236+
237+
constructor(
238+
private cli: CodeQLCliServer,
239+
private dbm: DatabaseManager,
240+
) {
241+
this.cache = new CachedOperation<[Uri, messages.TemplateDefinitions] | undefined>(this.getCfgUri.bind(this));
242+
}
243+
244+
async provideCfgUri(document?: TextDocument): Promise<[Uri, messages.TemplateDefinitions] | undefined> {
245+
if (!document) {
246+
return;
247+
}
248+
return await this.cache.get(document.uri.toString());
249+
}
250+
251+
private async getCfgUri(uriString: string): Promise<[Uri, messages.TemplateDefinitions]> {
252+
const uri = Uri.parse(uriString, true);
253+
if (uri.scheme !== zipArchiveScheme) {
254+
throw new Error('CFG Viewing is only available for databases with zipped source archives.');
255+
}
256+
257+
const zippedArchive = decodeSourceArchiveUri(uri);
258+
const sourceArchiveUri = encodeArchiveBasePath(zippedArchive.sourceArchiveZipPath);
259+
const db = this.dbm.findDatabaseItemBySourceArchive(sourceArchiveUri);
260+
261+
if (!db) {
262+
throw new Error('Can\'t infer database from the provided source.');
263+
}
264+
265+
const qlpack = await qlpackOfDatabase(this.cli, db);
266+
if (!qlpack) {
267+
throw new Error('Can\'t infer qlpack from database source archive.');
268+
}
269+
const queries = await resolveQueries(this.cli, qlpack, KeyType.PrintCfgQuery);
270+
if (queries.length > 1) {
271+
throw new Error(`Found multiple Print CFG queries. Can't continue. Make sure there is exacly one query with the tag ${KeyType.PrintCfgQuery}`);
272+
}
273+
if (queries.length === 0) {
274+
throw new Error(`Did not find any Print CFG queries. Can't continue. Make sure there is exacly one query with the tag ${KeyType.PrintCfgQuery}`);
275+
}
276+
277+
const queryUri = Uri.file(queries[0]);
278+
279+
const templates: messages.TemplateDefinitions = {
280+
[TEMPLATE_NAME]: {
281+
values: {
282+
tuples: [[{
283+
stringValue: zippedArchive.pathWithinSourceArchive
284+
}]]
285+
}
286+
}
287+
};
288+
289+
return [queryUri, templates];
290+
}
291+
}

extensions/ql-vscode/src/extension.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ import { DatabaseUI } from './databases-ui';
4242
import {
4343
TemplateQueryDefinitionProvider,
4444
TemplateQueryReferenceProvider,
45-
TemplatePrintAstProvider
45+
TemplatePrintAstProvider,
46+
TemplatePrintCfgProvider
4647
} from './contextual/templateProvider';
4748
import {
4849
DEFAULT_DISTRIBUTION_VERSION_RANGE,
@@ -1047,15 +1048,16 @@ async function activateWithInstalledDistribution(
10471048
);
10481049

10491050
const astViewer = new AstViewer();
1050-
const templateProvider = new TemplatePrintAstProvider(cliServer, qs, dbm, contextualQueryStorageDir);
1051+
const printAstTemplateProvider = new TemplatePrintAstProvider(cliServer, qs, dbm, contextualQueryStorageDir);
1052+
const cfgTemplateProvider = new TemplatePrintCfgProvider(cliServer, dbm);
10511053

10521054
ctx.subscriptions.push(astViewer);
10531055
ctx.subscriptions.push(commandRunnerWithProgress('codeQL.viewAst', async (
10541056
progress: ProgressCallback,
10551057
token: CancellationToken,
10561058
selectedFile: Uri
10571059
) => {
1058-
const ast = await templateProvider.provideAst(
1060+
const ast = await printAstTemplateProvider.provideAst(
10591061
progress,
10601062
token,
10611063
selectedFile ?? window.activeTextEditor?.document.uri,
@@ -1068,6 +1070,25 @@ async function activateWithInstalledDistribution(
10681070
title: 'Calculate AST'
10691071
}));
10701072

1073+
ctx.subscriptions.push(
1074+
commandRunnerWithProgress(
1075+
'codeQL.viewCfg',
1076+
async (
1077+
progress: ProgressCallback,
1078+
token: CancellationToken
1079+
) => {
1080+
const res = await cfgTemplateProvider.provideCfgUri(window.activeTextEditor?.document);
1081+
if (res) {
1082+
await compileAndRunQuery(false, res[0], progress, token, undefined);
1083+
}
1084+
},
1085+
{
1086+
title: 'Calculating Control Flow Graph',
1087+
cancellable: true
1088+
}
1089+
)
1090+
);
1091+
10711092
await commands.executeCommand('codeQLDatabases.removeOrphanedDatabases');
10721093

10731094
void logger.log('Successfully finished extension initialization.');

0 commit comments

Comments
 (0)