Skip to content

Commit 8b5bdbb

Browse files
authored
Merge pull request #350 from jcreedcmu/jcreed/cli-bqrs-parsing
Experimental: Enable parsing bqrs with the cli instead of in the webview
2 parents 6080a0d + 0ad9cdd commit 8b5bdbb

File tree

6 files changed

+142
-27
lines changed

6 files changed

+142
-27
lines changed

extensions/ql-vscode/src/adapt.ts

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { DecodedBqrsChunk, ResultSetSchema, ColumnKind, Column, ColumnValue } from "./bqrs-cli-types";
2+
import { LocationValue, ResultSetSchema as AdaptedSchema, ColumnSchema, ColumnType, LocationStyle } from 'semmle-bqrs';
3+
4+
// FIXME: This is a temporary bit of impedance matching to convert
5+
// from the types provided by ./bqrs-cli-types, to the types used by
6+
// the view layer.
7+
//
8+
// The reason that it is benign for now is that it is only used by
9+
// feature-flag-guarded codepaths that won't be encountered by normal
10+
// users. It is not yet guaranteed to produce correct output for raw
11+
// results.
12+
//
13+
// Eventually, the view layer should be refactored to directly accept data
14+
// of types coming from bqrs-cli-types, and this file can be deleted.
15+
16+
export type ResultRow = ResultValue[];
17+
18+
export interface ResultElement {
19+
label: string;
20+
location?: LocationValue;
21+
}
22+
23+
export interface ResultUri {
24+
uri: string;
25+
}
26+
27+
export type ResultValue = ResultElement | ResultUri | string;
28+
29+
export interface RawResultSet {
30+
readonly schema: AdaptedSchema;
31+
readonly rows: readonly ResultRow[];
32+
}
33+
34+
function adaptKind(kind: ColumnKind): ColumnType {
35+
// XXX what about 'u'?
36+
if (kind === 'e') {
37+
return { type: 'e', primitiveType: 's', locationStyle: LocationStyle.FivePart, hasLabel: true }
38+
}
39+
else {
40+
return { type: kind };
41+
}
42+
}
43+
44+
function adaptColumn(col: Column): ColumnSchema {
45+
return { name: col.name!, type: adaptKind(col.kind) };
46+
}
47+
48+
export function adaptSchema(schema: ResultSetSchema): AdaptedSchema {
49+
return {
50+
columns: schema.columns.map(adaptColumn),
51+
name: schema.name,
52+
tupleCount: schema.rows,
53+
version: 0,
54+
}
55+
}
56+
57+
export function adaptValue(val: ColumnValue): ResultValue {
58+
// XXX taking a lot of incorrect shortcuts here
59+
60+
if (typeof val === 'string') {
61+
return val;
62+
}
63+
64+
if (typeof val === 'number' || typeof val === 'boolean') {
65+
return val + '';
66+
}
67+
68+
const url = val.url;
69+
70+
if (typeof url === 'string') {
71+
return url;
72+
}
73+
74+
if (url === undefined) {
75+
return 'none';
76+
}
77+
78+
return {
79+
label: val.label || '',
80+
location: {
81+
t: LocationStyle.FivePart,
82+
lineStart: url.startLine,
83+
lineEnd: url.endLine,
84+
colStart: url.startColumn,
85+
colEnd: url.endColumn,
86+
// FIXME: This seems definitely wrong. Should we be using
87+
// something like the code in sarif-utils.ts?
88+
file: url.uri.replace(/file:/, ''),
89+
}
90+
}
91+
92+
}
93+
94+
export function adaptRow(row: ColumnValue[]): ResultRow {
95+
return row.map(adaptValue);
96+
}
97+
98+
export function adaptBqrs(schema: AdaptedSchema, page: DecodedBqrsChunk): RawResultSet {
99+
return {
100+
schema,
101+
rows: page.tuples.map(adaptRow),
102+
}
103+
}

extensions/ql-vscode/src/config.ts

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ const ROOT_SETTING = new Setting('codeQL');
5050
*/
5151
export const EXPERIMENTAL_FEATURES_SETTING = new Setting('experimentalFeatures', ROOT_SETTING);
5252

53+
/* Advanced setting: used to enable bqrs parsing in the cli instead of in the webview. */
54+
export const EXPERIMENTAL_BQRS_SETTING = new Setting('experimentalBqrsParsing', ROOT_SETTING);
55+
5356
// Distribution configuration
5457

5558
const DISTRIBUTION_SETTING = new Setting('cli', ROOT_SETTING);

extensions/ql-vscode/src/interface-types.ts

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as sarif from 'sarif';
22
import { ResolvableLocationValue } from 'semmle-bqrs';
3+
import { RawResultSet } from './adapt';
34

45
/**
56
* Only ever show this many results per run in interpreted results.
@@ -77,6 +78,12 @@ export interface SetStateMsg {
7778
* This is useful to prevent properties like scroll state being lost when rendering the sorted results after sorting a column.
7879
*/
7980
shouldKeepOldResultsWhileRendering: boolean;
81+
82+
/**
83+
* An experimental way of providing results from the extension.
84+
* Should be undefined unless config.EXPERIMENTAL_BQRS_SETTING is set to true.
85+
*/
86+
resultSets?: RawResultSet[];
8087
}
8188

8289
/** Advance to the next or previous path no in the path viewer */

extensions/ql-vscode/src/interface.ts

+20-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import * as messages from './messages';
1616
import { CompletedQuery, interpretResults } from './query-results';
1717
import { QueryInfo, tmpDir } from './run-queries';
1818
import { parseSarifLocation, parseSarifPlainTextMessage } from './sarif-utils';
19+
import { adaptSchema, adaptBqrs, RawResultSet } from './adapt';
20+
import { EXPERIMENTAL_BQRS_SETTING } from './config';
1921

2022
/**
2123
* interface.ts
@@ -349,9 +351,7 @@ export class InterfaceManager extends DisposableObject {
349351
const showButton = "View Results";
350352
const queryName = results.queryName;
351353
const resultPromise = vscode.window.showInformationMessage(
352-
`Finished running query ${
353-
queryName.length > 0 ? ` “${queryName}”` : ""
354-
}.`,
354+
`Finished running query ${queryName.length > 0 ? ` "${queryName}"` : ""}.`,
355355
showButton
356356
);
357357
// Address this click asynchronously so we still update the
@@ -363,13 +363,30 @@ export class InterfaceManager extends DisposableObject {
363363
});
364364
}
365365

366+
let resultSets: RawResultSet[] | undefined;
367+
368+
if (EXPERIMENTAL_BQRS_SETTING.getValue()) {
369+
resultSets = [];
370+
// Setting pageSize very large for now for the sake of
371+
// performance testing of vscode on containers.
372+
const pageSize = 1e20;
373+
const schemas = await this.cliServer.bqrsInfo(results.query.resultsPaths.resultsPath, pageSize);
374+
for (const schema of schemas["result-sets"]) {
375+
const chunk = await this.cliServer.bqrsDecode(results.query.resultsPaths.resultsPath, schema.name, pageSize, 0)
376+
const adaptedSchema = adaptSchema(schema);
377+
const resultSet = adaptBqrs(adaptedSchema, chunk);
378+
resultSets.push(resultSet);
379+
}
380+
}
381+
366382
await this.postMessage({
367383
t: "setState",
368384
interpretation,
369385
origResultsPaths: results.query.resultsPaths,
370386
resultsPath: this.convertPathToWebviewUri(
371387
results.query.resultsPaths.resultsPath
372388
),
389+
resultSets,
373390
sortedResultsMap,
374391
database: results.database,
375392
shouldKeepOldResultsWhileRendering,

extensions/ql-vscode/src/view/raw-results-table.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from "react";
22
import { renderLocation, ResultTableProps, zebraStripe, className, nextSortDirection } from "./result-table-utils";
3-
import { RawTableResultSet, ResultValue, vscode } from "./results";
3+
import { RawTableResultSet, vscode } from "./results";
4+
import { ResultValue } from "../adapt";
45
import { SortDirection, RAW_RESULTS_LIMIT, RawResultsSortState } from "../interface-types";
56

67
export type RawTableProps = ResultTableProps & {

extensions/ql-vscode/src/view/results.tsx

+7-23
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import * as React from 'react';
22
import * as Rdom from 'react-dom';
33
import * as bqrs from 'semmle-bqrs';
4-
import { ElementBase, LocationValue, PrimitiveColumnValue, PrimitiveTypeKind, ResultSetSchema, tryGetResolvableLocation } from 'semmle-bqrs';
4+
import { ElementBase, PrimitiveColumnValue, PrimitiveTypeKind, ResultSetSchema, tryGetResolvableLocation } from 'semmle-bqrs';
55
import { assertNever } from '../helpers-pure';
66
import { DatabaseInfo, FromResultsViewMsg, Interpretation, IntoResultsViewMsg, SortedResultSetInfo, RawResultsSortState, NavigatePathMsg, QueryMetadata, ResultsPaths } from '../interface-types';
77
import { EventHandlers as EventHandlerList } from './event-handler-list';
88
import { ResultTables } from './result-tables';
9+
import { RawResultSet, ResultValue, ResultRow } from '../adapt';
910

1011
/**
1112
* results.tsx
@@ -23,31 +24,13 @@ interface VsCodeApi {
2324
declare const acquireVsCodeApi: () => VsCodeApi;
2425
export const vscode = acquireVsCodeApi();
2526

26-
export interface ResultElement {
27-
label: string;
28-
location?: LocationValue;
29-
}
30-
31-
export interface ResultUri {
32-
uri: string;
33-
}
34-
35-
export type ResultValue = ResultElement | ResultUri | string;
36-
37-
export type ResultRow = ResultValue[];
38-
3927
export type RawTableResultSet = { t: 'RawResultSet' } & RawResultSet;
4028
export type PathTableResultSet = { t: 'SarifResultSet'; readonly schema: ResultSetSchema; name: string } & Interpretation;
4129

4230
export type ResultSet =
4331
| RawTableResultSet
4432
| PathTableResultSet;
4533

46-
export interface RawResultSet {
47-
readonly schema: ResultSetSchema;
48-
readonly rows: readonly ResultRow[];
49-
}
50-
5134
async function* getChunkIterator(response: Response): AsyncIterableIterator<Uint8Array> {
5235
if (!response.ok) {
5336
throw new Error(`Failed to load results: (${response.status}) ${response.statusText}`);
@@ -62,9 +45,7 @@ async function* getChunkIterator(response: Response): AsyncIterableIterator<Uint
6245
}
6346
}
6447

65-
function translatePrimitiveValue(value: PrimitiveColumnValue, type: PrimitiveTypeKind):
66-
ResultValue {
67-
48+
function translatePrimitiveValue(value: PrimitiveColumnValue, type: PrimitiveTypeKind): ResultValue {
6849
switch (type) {
6950
case 'i':
7051
case 'f':
@@ -127,6 +108,7 @@ async function parseResultSets(response: Response): Promise<readonly ResultSet[]
127108

128109
interface ResultsInfo {
129110
resultsPath: string;
111+
resultSets: ResultSet[] | undefined;
130112
origResultsPaths: ResultsPaths;
131113
database: DatabaseInfo;
132114
interpretation: Interpretation | undefined;
@@ -187,6 +169,7 @@ class App extends React.Component<{}, ResultsViewState> {
187169
case 'setState':
188170
this.updateStateWithNewResultsInfo({
189171
resultsPath: msg.resultsPath,
172+
resultSets: msg.resultSets?.map(x => ({ t: 'RawResultSet', ...x })),
190173
origResultsPaths: msg.origResultsPaths,
191174
sortedResultsMap: new Map(Object.entries(msg.sortedResultsMap)),
192175
database: msg.database,
@@ -247,8 +230,9 @@ class App extends React.Component<{}, ResultsViewState> {
247230
let results: Results | null = null;
248231
let statusText = '';
249232
try {
233+
const resultSets = resultsInfo.resultSets || await this.getResultSets(resultsInfo);
250234
results = {
251-
resultSets: await this.getResultSets(resultsInfo),
235+
resultSets,
252236
database: resultsInfo.database,
253237
sortStates: this.getSortStates(resultsInfo)
254238
};

0 commit comments

Comments
 (0)