diff --git a/extensions/ql-vscode/src/interface-types.ts b/extensions/ql-vscode/src/interface-types.ts index 58536c6df..a3bfc8b8e 100644 --- a/extensions/ql-vscode/src/interface-types.ts +++ b/extensions/ql-vscode/src/interface-types.ts @@ -37,8 +37,9 @@ export interface Interpretation { sarif: sarif.Log; } -export interface ResultsInfo { +export interface ResultsPaths { resultsPath: string; + interpretedResultsPath: string; } export interface SortedResultSetInfo { @@ -60,6 +61,7 @@ export interface ResultsUpdatingMsg { export interface SetStateMsg { t: 'setState'; resultsPath: string; + origResultsPaths: ResultsPaths; sortedResultsMap: SortedResultsMap; interpretation: undefined | Interpretation; database: DatabaseInfo; @@ -94,7 +96,7 @@ interface ToggleDiagnostics { t: 'toggleDiagnostics'; databaseUri: string; metadata?: QueryMetadata - resultsPath: string; + origResultsPaths: ResultsPaths; visible: boolean; kind?: string; }; diff --git a/extensions/ql-vscode/src/interface.ts b/extensions/ql-vscode/src/interface.ts index ed95607ce..e3c3a3408 100644 --- a/extensions/ql-vscode/src/interface.ts +++ b/extensions/ql-vscode/src/interface.ts @@ -6,13 +6,13 @@ import { parseSarifLocation, parseSarifPlainTextMessage } from './sarif-utils'; import { FivePartLocation, LocationValue, ResolvableLocationValue, WholeFileLocation, tryGetResolvableLocation, LocationStyle } from 'semmle-bqrs'; import { DisposableObject } from 'semmle-vscode-utils'; import * as vscode from 'vscode'; -import { Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, languages, Location, Position, Range, Uri, window as Window, workspace } from 'vscode'; +import { Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, languages, Location, Range, Uri, window as Window, workspace } from 'vscode'; import { CodeQLCliServer } from './cli'; import { DatabaseItem, DatabaseManager } from './databases'; import * as helpers from './helpers'; import { showAndLogErrorMessage } from './helpers'; import { assertNever } from './helpers-pure'; -import { FromResultsViewMsg, Interpretation, IntoResultsViewMsg, ResultsInfo, SortedResultSetInfo, SortedResultsMap, INTERPRETED_RESULTS_PER_RUN_LIMIT, QueryMetadata } from './interface-types'; +import { FromResultsViewMsg, Interpretation, IntoResultsViewMsg, ResultsPaths, SortedResultSetInfo, SortedResultsMap, INTERPRETED_RESULTS_PER_RUN_LIMIT, QueryMetadata } from './interface-types'; import { Logger } from './logging'; import * as messages from './messages'; import { EvaluationInfo, interpretResults, QueryInfo, tmpDir } from './queries'; @@ -166,7 +166,7 @@ export class InterfaceManager extends DisposableObject { if (msg.visible) { const databaseItem = this.databaseManager.findDatabaseItem(Uri.parse(msg.databaseUri)); if (databaseItem !== undefined) { - await this.showResultsAsDiagnostics(msg.resultsPath, msg.metadata, databaseItem); + await this.showResultsAsDiagnostics(msg.origResultsPaths, msg.metadata, databaseItem); } } else { // TODO: Only clear diagnostics on the same database. @@ -223,7 +223,7 @@ export class InterfaceManager extends DisposableObject { return; } - const interpretation = await this.interpretResultsInfo(info.query, info.query.resultsInfo); + const interpretation = await this.interpretResultsInfo(info.query, info.query.resultsPaths); const sortedResultsMap: SortedResultsMap = {}; info.query.sortedResultsInfo.forEach((v, k) => @@ -259,7 +259,8 @@ export class InterfaceManager extends DisposableObject { await this.postMessage({ t: 'setState', interpretation, - resultsPath: this.convertPathToWebviewUri(info.query.resultsInfo.resultsPath), + origResultsPaths: info.query.resultsPaths, + resultsPath: this.convertPathToWebviewUri(info.query.resultsPaths.resultsPath), sortedResultsMap, database: info.database, shouldKeepOldResultsWhileRendering, @@ -267,8 +268,8 @@ export class InterfaceManager extends DisposableObject { }); } -private async getTruncatedResults(metadata : QueryMetadata | undefined ,resultsPathOnDisk: string, sourceInfo : cli.SourceInfo | undefined, sourceLocationPrefix : string ) : Promise { - const sarif = await interpretResults(this.cliServer, metadata, resultsPathOnDisk, sourceInfo); +private async getTruncatedResults(metadata : QueryMetadata | undefined ,resultsInfo: ResultsPaths, sourceInfo : cli.SourceInfo | undefined, sourceLocationPrefix : string ) : Promise { + const sarif = await interpretResults(this.cliServer, metadata, resultsInfo, sourceInfo); // For performance reasons, limit the number of results we try // to serialize and send to the webview. TODO: possibly also // limit number of paths per result, number of steps per path, @@ -288,7 +289,7 @@ private async getTruncatedResults(metadata : QueryMetadata | undefined ,resultsP ; } - private async interpretResultsInfo(query: QueryInfo, resultsInfo: ResultsInfo): Promise { + private async interpretResultsInfo(query: QueryInfo, resultsInfo: ResultsPaths): Promise { let interpretation: Interpretation | undefined = undefined; if (query.hasInterpretedResults() && query.quickEvalPosition === undefined // never do results interpretation if quickEval @@ -299,7 +300,7 @@ private async getTruncatedResults(metadata : QueryMetadata | undefined ,resultsP const sourceInfo = sourceArchiveUri === undefined ? undefined : { sourceArchive: sourceArchiveUri.fsPath, sourceLocationPrefix }; - interpretation = await this.getTruncatedResults(query.metadata, resultsInfo.resultsPath, sourceInfo, sourceLocationPrefix); + interpretation = await this.getTruncatedResults(query.metadata, resultsInfo, sourceInfo, sourceLocationPrefix); } catch (e) { // If interpretation fails, accept the error and continue @@ -311,15 +312,13 @@ private async getTruncatedResults(metadata : QueryMetadata | undefined ,resultsP } - private async showResultsAsDiagnostics(webviewResultsUri: string, metadata: QueryMetadata | undefined, database: DatabaseItem) { - // URIs from the webview have the vscode-resource scheme, so convert into a filesystem URI first. - const resultsPathOnDisk = webviewUriToFileUri(webviewResultsUri).fsPath; + private async showResultsAsDiagnostics(resultsInfo: ResultsPaths, metadata: QueryMetadata | undefined, database: DatabaseItem) { const sourceLocationPrefix = await database.getSourceLocationPrefix(this.cliServer); const sourceArchiveUri = database.sourceArchive; const sourceInfo = sourceArchiveUri === undefined ? undefined : { sourceArchive: sourceArchiveUri.fsPath, sourceLocationPrefix }; - const interpretation = await this.getTruncatedResults(metadata, resultsPathOnDisk, sourceInfo, sourceLocationPrefix); + const interpretation = await this.getTruncatedResults(metadata, resultsInfo, sourceInfo, sourceLocationPrefix); try { await this.showProblemResultsAsDiagnostics(interpretation, database); diff --git a/extensions/ql-vscode/src/queries.ts b/extensions/ql-vscode/src/queries.ts index a97eca6bd..4169443ab 100644 --- a/extensions/ql-vscode/src/queries.ts +++ b/extensions/ql-vscode/src/queries.ts @@ -7,7 +7,7 @@ import * as vscode from 'vscode'; import * as cli from './cli'; import { DatabaseItem, getUpgradesDirectories } from './databases'; import * as helpers from './helpers'; -import { DatabaseInfo, SortState, ResultsInfo, SortedResultSetInfo, QueryMetadata } from './interface-types'; +import { DatabaseInfo, SortState, ResultsPaths, SortedResultSetInfo, QueryMetadata } from './interface-types'; import { logger } from './logging'; import * as messages from './messages'; import * as qsClient from './queryserver-client'; @@ -50,7 +50,7 @@ export class UserCancellationException extends Error { } */ export class QueryInfo { compiledQueryPath: string; - resultsInfo: ResultsInfo; + resultsPaths: ResultsPaths; private static nextQueryId = 0; /** @@ -68,7 +68,8 @@ export class QueryInfo { ) { this.queryId = QueryInfo.nextQueryId++; this.compiledQueryPath = path.join(tmpDir.name, `compiledQuery${this.queryId}.qlo`); - this.resultsInfo = { + this.resultsPaths = { + interpretedResultsPath: path.join(tmpDir.name, `interpretedResults${this.queryId}.sarif`), resultsPath: path.join(tmpDir.name, `results${this.queryId}.bqrs`), }; this.sortedResultsInfo = new Map(); @@ -86,7 +87,7 @@ export class QueryInfo { const callbackId = qs.registerCallback(res => { result = res }); const queryToRun: messages.QueryToRun = { - resultsPath: this.resultsInfo.resultsPath, + resultsPath: this.resultsPaths.resultsPath, qlo: vscode.Uri.file(this.compiledQueryPath).toString(), allowUnknownTemplates: true, id: callbackId, @@ -177,7 +178,7 @@ export class QueryInfo { sortState }; - await server.sortBqrs(this.resultsInfo.resultsPath, sortedResultSetInfo.resultsPath, resultSetName, [sortState.columnIndex], [sortState.direction]); + await server.sortBqrs(this.resultsPaths.resultsPath, sortedResultSetInfo.resultsPath, resultSetName, [sortState.columnIndex], [sortState.direction]); this.sortedResultsInfo.set(resultSetName, sortedResultSetInfo); } } @@ -185,11 +186,10 @@ export class QueryInfo { /** * Call cli command to interpret results. */ -export async function interpretResults(server: cli.CodeQLCliServer, metadata: QueryMetadata | undefined, resultsPath: string, sourceInfo?: cli.SourceInfo): Promise { - const interpretedResultsPath = resultsPath + ".interpreted.sarif" +export async function interpretResults(server: cli.CodeQLCliServer, metadata: QueryMetadata | undefined, resultsInfo: ResultsPaths, sourceInfo?: cli.SourceInfo): Promise { - if (await fs.pathExists(interpretedResultsPath)) { - return JSON.parse(await fs.readFile(interpretedResultsPath, 'utf8')); + if (await fs.pathExists(resultsInfo.interpretedResultsPath)) { + return JSON.parse(await fs.readFile(resultsInfo.interpretedResultsPath, 'utf8')); } if (metadata == undefined) { throw new Error('Can\'t interpret results without query metadata'); @@ -203,7 +203,7 @@ export async function interpretResults(server: cli.CodeQLCliServer, metadata: Qu // SARIF format does, so in the absence of one, we use a dummy id. id = "dummy-id"; } - return await server.interpretBqrs( { kind, id }, resultsPath, interpretedResultsPath, sourceInfo); + return await server.interpretBqrs( { kind, id }, resultsInfo.resultsPath, resultsInfo.interpretedResultsPath, sourceInfo); } export interface EvaluationInfo { diff --git a/extensions/ql-vscode/src/view/result-tables.tsx b/extensions/ql-vscode/src/view/result-tables.tsx index 4064ecc8c..7b8c13cbd 100644 --- a/extensions/ql-vscode/src/view/result-tables.tsx +++ b/extensions/ql-vscode/src/view/result-tables.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { DatabaseInfo, Interpretation, SortState, QueryMetadata } from '../interface-types'; +import { DatabaseInfo, Interpretation, SortState, QueryMetadata, ResultsPaths } from '../interface-types'; import { PathTable } from './alert-table'; import { RawTable } from './raw-results-table'; import { ResultTableProps, tableSelectionHeaderClassName, toggleDiagnosticsClassName } from './result-table-utils'; @@ -13,7 +13,8 @@ export interface ResultTablesProps { interpretation: Interpretation | undefined; database: DatabaseInfo; metadata? : QueryMetadata - resultsPath: string | undefined; + resultsPath: string ; + origResultsPaths: ResultsPaths; sortStates: Map; isLoadingNewResults: boolean; } @@ -95,7 +96,7 @@ export class ResultTables render(): React.ReactNode { const { selectedTable } = this.state; const resultSets = this.getResultSets(); - const { database, resultsPath, metadata } = this.props; + const { database, resultsPath, metadata, origResultsPaths } = this.props; // Only show the Problems view display checkbox for the alerts table. const diagnosticsCheckBox = selectedTable === ALERTS_TABLE_NAME ? @@ -104,7 +105,7 @@ export class ResultTables if (resultsPath !== undefined) { vscode.postMessage({ t: 'toggleDiagnostics', - resultsPath: resultsPath, + origResultsPaths: origResultsPaths, databaseUri: database.databaseUri, visible: e.target.checked, metadata: metadata diff --git a/extensions/ql-vscode/src/view/results.tsx b/extensions/ql-vscode/src/view/results.tsx index 52e0ab46c..3e97066de 100644 --- a/extensions/ql-vscode/src/view/results.tsx +++ b/extensions/ql-vscode/src/view/results.tsx @@ -3,7 +3,7 @@ import * as Rdom from 'react-dom'; import * as bqrs from 'semmle-bqrs'; import { ElementBase, LocationValue, PrimitiveColumnValue, PrimitiveTypeKind, ResultSetSchema, tryGetResolvableLocation } from 'semmle-bqrs'; import { assertNever } from '../helpers-pure'; -import { DatabaseInfo, FromResultsViewMsg, Interpretation, IntoResultsViewMsg, SortedResultSetInfo, SortState, NavigatePathMsg, QueryMetadata } from '../interface-types'; +import { DatabaseInfo, FromResultsViewMsg, Interpretation, IntoResultsViewMsg, SortedResultSetInfo, SortState, NavigatePathMsg, QueryMetadata, ResultsPaths } from '../interface-types'; import { ResultTables } from './result-tables'; import { EventHandlers as EventHandlerList } from './event-handler-list'; @@ -127,6 +127,7 @@ async function parseResultSets(response: Response): Promise; @@ -186,6 +187,7 @@ class App extends React.Component<{}, ResultsViewState> { case 'setState': this.updateStateWithNewResultsInfo({ resultsPath: msg.resultsPath, + origResultsPaths: msg.origResultsPaths, sortedResultsMap: new Map(Object.entries(msg.sortedResultsMap)), database: msg.database, interpretation: msg.interpretation, @@ -304,11 +306,12 @@ class App extends React.Component<{}, ResultsViewState> { render() { const displayedResults = this.state.displayedResults; - if (displayedResults.results !== null) { + if (displayedResults.results !== null && displayedResults.resultsInfo !== null) { return ;