1
1
import * as crypto from 'crypto' ;
2
2
import * as path from 'path' ;
3
- import * as bqrs from 'semmle-bqrs' ;
4
- import { CustomResultSets , FivePartLocation , LocationStyle , LocationValue , PathProblemQueryResults , ProblemQueryResults , ResolvableLocationValue , tryGetResolvableLocation , WholeFileLocation } from 'semmle-bqrs' ;
5
- import { FileReader } from 'semmle-io-node' ;
3
+ import * as cli from './cli' ;
4
+ import * as Sarif from 'sarif' ;
5
+ import { parseSarifLocation , parseSarifPlainTextMessage } from './sarif-utils' ;
6
+ import { FivePartLocation , LocationValue , ResolvableLocationValue , WholeFileLocation , tryGetResolvableLocation , LocationStyle } from 'semmle-bqrs' ;
6
7
import { DisposableObject } from 'semmle-vscode-utils' ;
7
8
import * as vscode from 'vscode' ;
8
- import { Diagnostic , DiagnosticRelatedInformation , DiagnosticSeverity , languages , Location , Position , Range , Uri , window as Window , workspace } from 'vscode' ;
9
+ import { Diagnostic , DiagnosticRelatedInformation , DiagnosticSeverity , languages , Location , Range , Uri , window as Window , workspace } from 'vscode' ;
9
10
import { CodeQLCliServer } from './cli' ;
10
11
import { DatabaseItem , DatabaseManager } from './databases' ;
11
12
import * as helpers from './helpers' ;
12
13
import { showAndLogErrorMessage } from './helpers' ;
13
14
import { assertNever } from './helpers-pure' ;
14
- import { FromResultsViewMsg , Interpretation , IntoResultsViewMsg , ResultsInfo , SortedResultSetInfo , SortedResultsMap , INTERPRETED_RESULTS_PER_RUN_LIMIT } from './interface-types' ;
15
+ import { FromResultsViewMsg , Interpretation , IntoResultsViewMsg , ResultsPaths , SortedResultSetInfo , SortedResultsMap , INTERPRETED_RESULTS_PER_RUN_LIMIT , QueryMetadata } from './interface-types' ;
15
16
import { Logger } from './logging' ;
16
17
import * as messages from './messages' ;
17
18
import { EvaluationInfo , interpretResults , QueryInfo , tmpDir } from './queries' ;
@@ -165,7 +166,7 @@ export class InterfaceManager extends DisposableObject {
165
166
if ( msg . visible ) {
166
167
const databaseItem = this . databaseManager . findDatabaseItem ( Uri . parse ( msg . databaseUri ) ) ;
167
168
if ( databaseItem !== undefined ) {
168
- await this . showResultsAsDiagnostics ( msg . resultsPath , msg . kind , databaseItem ) ;
169
+ await this . showResultsAsDiagnostics ( msg . origResultsPaths , msg . metadata , databaseItem ) ;
169
170
}
170
171
} else {
171
172
// TODO: Only clear diagnostics on the same database.
@@ -222,7 +223,7 @@ export class InterfaceManager extends DisposableObject {
222
223
return ;
223
224
}
224
225
225
- const interpretation = await this . interpretResultsInfo ( info . query , info . query . resultsInfo ) ;
226
+ const interpretation = await this . interpretResultsInfo ( info . query , info . query . resultsPaths ) ;
226
227
227
228
const sortedResultsMap : SortedResultsMap = { } ;
228
229
info . query . sortedResultsInfo . forEach ( ( v , k ) =>
@@ -258,15 +259,37 @@ export class InterfaceManager extends DisposableObject {
258
259
await this . postMessage ( {
259
260
t : 'setState' ,
260
261
interpretation,
261
- resultsPath : this . convertPathToWebviewUri ( info . query . resultsInfo . resultsPath ) ,
262
+ origResultsPaths : info . query . resultsPaths ,
263
+ resultsPath : this . convertPathToWebviewUri ( info . query . resultsPaths . resultsPath ) ,
262
264
sortedResultsMap,
263
265
database : info . database ,
264
266
shouldKeepOldResultsWhileRendering,
265
- kind : info . query . metadata ? info . query . metadata . kind : undefined
267
+ metadata : info . query . metadata
266
268
} ) ;
267
269
}
268
270
269
- private async interpretResultsInfo ( query : QueryInfo , resultsInfo : ResultsInfo ) : Promise < Interpretation | undefined > {
271
+ private async getTruncatedResults ( metadata : QueryMetadata | undefined , resultsInfo : ResultsPaths , sourceInfo : cli . SourceInfo | undefined , sourceLocationPrefix : string ) : Promise < Interpretation > {
272
+ const sarif = await interpretResults ( this . cliServer , metadata , resultsInfo , sourceInfo ) ;
273
+ // For performance reasons, limit the number of results we try
274
+ // to serialize and send to the webview. TODO: possibly also
275
+ // limit number of paths per result, number of steps per path,
276
+ // or throw an error if we are in aggregate trying to send
277
+ // massively too much data, as it can make the extension
278
+ // unresponsive.
279
+ let numTruncatedResults = 0 ;
280
+ sarif . runs . forEach ( run => {
281
+ if ( run . results !== undefined ) {
282
+ if ( run . results . length > INTERPRETED_RESULTS_PER_RUN_LIMIT ) {
283
+ numTruncatedResults += run . results . length - INTERPRETED_RESULTS_PER_RUN_LIMIT ;
284
+ run . results = run . results . slice ( 0 , INTERPRETED_RESULTS_PER_RUN_LIMIT ) ;
285
+ }
286
+ }
287
+ } ) ;
288
+ return { sarif, sourceLocationPrefix, numTruncatedResults } ;
289
+ ;
290
+ }
291
+
292
+ private async interpretResultsInfo ( query : QueryInfo , resultsInfo : ResultsPaths ) : Promise < Interpretation | undefined > {
270
293
let interpretation : Interpretation | undefined = undefined ;
271
294
if ( query . hasInterpretedResults ( )
272
295
&& query . quickEvalPosition === undefined // never do results interpretation if quickEval
@@ -277,114 +300,105 @@ export class InterfaceManager extends DisposableObject {
277
300
const sourceInfo = sourceArchiveUri === undefined ?
278
301
undefined :
279
302
{ sourceArchive : sourceArchiveUri . fsPath , sourceLocationPrefix } ;
280
- const sarif = await interpretResults ( this . cliServer , query , resultsInfo , sourceInfo ) ;
281
- // For performance reasons, limit the number of results we try
282
- // to serialize and send to the webview. TODO: possibly also
283
- // limit number of paths per result, number of steps per path,
284
- // or throw an error if we are in aggregate trying to send
285
- // massively too much data, as it can make the extension
286
- // unresponsive.
287
- let numTruncatedResults = 0 ;
288
- sarif . runs . forEach ( run => {
289
- if ( run . results !== undefined ) {
290
- if ( run . results . length > INTERPRETED_RESULTS_PER_RUN_LIMIT ) {
291
- numTruncatedResults += run . results . length - INTERPRETED_RESULTS_PER_RUN_LIMIT ;
292
- run . results = run . results . slice ( 0 , INTERPRETED_RESULTS_PER_RUN_LIMIT ) ;
293
- }
294
- }
295
- } ) ;
296
- interpretation = { sarif, sourceLocationPrefix, numTruncatedResults } ;
303
+ interpretation = await this . getTruncatedResults ( query . metadata , resultsInfo , sourceInfo , sourceLocationPrefix ) ;
297
304
}
298
305
catch ( e ) {
299
306
// If interpretation fails, accept the error and continue
300
307
// trying to render uninterpreted results anyway.
301
308
this . logger . log ( `Exception during results interpretation: ${ e . message } . Will show raw results instead.` ) ;
302
309
}
303
310
}
304
-
305
311
return interpretation ;
306
312
}
307
313
308
- private async showResultsAsDiagnostics ( resultsPath : string , kind : string | undefined ,
309
- database : DatabaseItem ) {
310
-
311
- // URIs from the webview have the vscode-resource scheme, so convert into a filesystem URI first.
312
- const resultsPathOnDisk = webviewUriToFileUri ( resultsPath ) . fsPath ;
313
- const fileReader = await FileReader . open ( resultsPathOnDisk ) ;
314
- try {
315
- const resultSets = await bqrs . open ( fileReader ) ;
316
- try {
317
- switch ( kind || 'problem' ) {
318
- case 'problem' : {
319
- const customResults = bqrs . createCustomResultSets < ProblemQueryResults > ( resultSets , ProblemQueryResults ) ;
320
- await this . showProblemResultsAsDiagnostics ( customResults , database ) ;
321
- }
322
- break ;
323
314
324
- case 'path-problem' : {
325
- const customResults = bqrs . createCustomResultSets < PathProblemQueryResults > ( resultSets , PathProblemQueryResults ) ;
326
- await this . showProblemResultsAsDiagnostics ( customResults , database ) ;
327
- }
328
- break ;
315
+ private async showResultsAsDiagnostics ( resultsInfo : ResultsPaths , metadata : QueryMetadata | undefined , database : DatabaseItem ) {
316
+ const sourceLocationPrefix = await database . getSourceLocationPrefix ( this . cliServer ) ;
317
+ const sourceArchiveUri = database . sourceArchive ;
318
+ const sourceInfo = sourceArchiveUri === undefined ?
319
+ undefined :
320
+ { sourceArchive : sourceArchiveUri . fsPath , sourceLocationPrefix } ;
321
+ const interpretation = await this . getTruncatedResults ( metadata , resultsInfo , sourceInfo , sourceLocationPrefix ) ;
329
322
330
- default :
331
- throw new Error ( `Unrecognized query kind '${ kind } '.` ) ;
332
- }
333
- }
334
- catch ( e ) {
335
- const msg = e instanceof Error ? e . message : e . toString ( ) ;
336
- this . logger . log ( `Exception while computing problem results as diagnostics: ${ msg } ` ) ;
337
- this . _diagnosticCollection . clear ( ) ;
338
- }
323
+ try {
324
+ await this . showProblemResultsAsDiagnostics ( interpretation , database ) ;
339
325
}
340
- finally {
341
- fileReader . dispose ( ) ;
326
+ catch ( e ) {
327
+ const msg = e instanceof Error ? e . message : e . toString ( ) ;
328
+ this . logger . log ( `Exception while computing problem results as diagnostics: ${ msg } ` ) ;
329
+ this . _diagnosticCollection . clear ( ) ;
342
330
}
331
+
343
332
}
344
333
345
- private async showProblemResultsAsDiagnostics ( results : CustomResultSets < ProblemQueryResults > ,
346
- databaseItem : DatabaseItem ) : Promise < void > {
334
+ private async showProblemResultsAsDiagnostics ( interpretation : Interpretation , databaseItem : DatabaseItem ) : Promise < void > {
335
+ const { sarif, sourceLocationPrefix } = interpretation ;
336
+
337
+
338
+ if ( ! sarif . runs || ! sarif . runs [ 0 ] . results ) {
339
+ this . logger . log ( "Didn't find a run in the sarif results. Error processing sarif?" )
340
+ return ;
341
+ }
347
342
348
343
const diagnostics : [ Uri , ReadonlyArray < Diagnostic > ] [ ] = [ ] ;
349
- for await ( const problemRow of results . problems . readTuples ( ) ) {
350
- const codeLocation = resolveLocation ( problemRow . element . location , databaseItem ) ;
351
- let message : string ;
352
- const references = problemRow . references ;
353
- if ( references ) {
354
- let referenceIndex = 0 ;
355
- message = problemRow . message . replace ( / \$ \@ / g, sub => {
356
- if ( referenceIndex < references . length ) {
357
- const replacement = references [ referenceIndex ] . text ;
358
- referenceIndex ++ ;
359
- return replacement ;
360
- }
361
- else {
362
- return sub ;
363
- }
364
- } ) ;
344
+
345
+ for ( const result of sarif . runs [ 0 ] . results ) {
346
+ const message = result . message . text ;
347
+ if ( message === undefined ) {
348
+ this . logger . log ( "Sarif had result without plaintext message" )
349
+ continue ;
350
+ }
351
+ if ( ! result . locations ) {
352
+ this . logger . log ( "Sarif had result without location" )
353
+ continue ;
354
+ }
355
+
356
+ const sarifLoc = parseSarifLocation ( result . locations [ 0 ] , sourceLocationPrefix ) ;
357
+ if ( sarifLoc . t == "NoLocation" ) {
358
+ continue ;
359
+ }
360
+ const resultLocation = tryResolveLocation ( sarifLoc , databaseItem )
361
+ if ( ! resultLocation ) {
362
+ this . logger . log ( "Sarif location was not resolvable " + sarifLoc )
363
+ continue ;
365
364
}
366
- else {
367
- message = problemRow . message ;
365
+ const parsedMessage = parseSarifPlainTextMessage ( message ) ;
366
+ const relatedInformation : DiagnosticRelatedInformation [ ] = [ ] ;
367
+ const relatedLocationsById : { [ k : number ] : Sarif . Location } = { } ;
368
+
369
+
370
+ for ( let loc of result . relatedLocations || [ ] ) {
371
+ relatedLocationsById [ loc . id ! ] = loc ;
368
372
}
369
- const diagnostic = new Diagnostic ( codeLocation . range , message , DiagnosticSeverity . Warning ) ;
370
- if ( problemRow . references ) {
371
- const relatedInformation : DiagnosticRelatedInformation [ ] = [ ] ;
372
- for ( const reference of problemRow . references ) {
373
- const referenceLocation = tryResolveLocation ( reference . element . location , databaseItem ) ;
373
+ let resultMessageChunks : string [ ] = [ ] ;
374
+ for ( const section of parsedMessage ) {
375
+ if ( typeof section === "string" ) {
376
+ resultMessageChunks . push ( section ) ;
377
+ } else {
378
+ resultMessageChunks . push ( section . text ) ;
379
+ const sarifChunkLoc = parseSarifLocation ( relatedLocationsById [ section . dest ] , sourceLocationPrefix ) ;
380
+ if ( sarifChunkLoc . t == "NoLocation" ) {
381
+ continue ;
382
+ }
383
+ const referenceLocation = tryResolveLocation ( sarifChunkLoc , databaseItem ) ;
384
+
385
+
374
386
if ( referenceLocation ) {
375
387
const related = new DiagnosticRelatedInformation ( referenceLocation ,
376
- reference . text ) ;
388
+ section . text ) ;
377
389
relatedInformation . push ( related ) ;
378
390
}
379
391
}
380
- diagnostic . relatedInformation = relatedInformation ;
381
392
}
393
+ const diagnostic = new Diagnostic ( resultLocation . range , resultMessageChunks . join ( "" ) , DiagnosticSeverity . Warning ) ;
394
+ diagnostic . relatedInformation = relatedInformation ;
395
+
382
396
diagnostics . push ( [
383
- codeLocation . uri ,
397
+ resultLocation . uri ,
384
398
[ diagnostic ]
385
399
] ) ;
386
- }
387
400
401
+ }
388
402
this . _diagnosticCollection . set ( diagnostics ) ;
389
403
}
390
404
@@ -476,22 +490,6 @@ function resolveWholeFileLocation(loc: WholeFileLocation, databaseItem: Database
476
490
return new Location ( databaseItem . resolveSourceFile ( loc . file ) , range ) ;
477
491
}
478
492
479
- /**
480
- * Resolve the specified CodeQL location to a URI into the source archive.
481
- * @param loc CodeQL location to resolve
482
- * @param databaseItem Database in which to resolve the file location.
483
- */
484
- function resolveLocation ( loc : LocationValue | undefined , databaseItem : DatabaseItem ) : Location {
485
- const resolvedLocation = tryResolveLocation ( loc , databaseItem ) ;
486
- if ( resolvedLocation ) {
487
- return resolvedLocation ;
488
- }
489
- else {
490
- // Return a fake position in the source archive directory itself.
491
- return new Location ( databaseItem . resolveSourceFile ( undefined ) , new Position ( 0 , 0 ) ) ;
492
- }
493
- }
494
-
495
493
/**
496
494
* Try to resolve the specified CodeQL location to a URI into the source archive. If no exact location
497
495
* can be resolved, returns `undefined`.
0 commit comments