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
9
import { Diagnostic , DiagnosticRelatedInformation , DiagnosticSeverity , languages , Location , Position , Range , Uri , window as Window , workspace } from 'vscode' ;
@@ -11,7 +12,7 @@ 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 , ResultsInfo , 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 . resultsPath , msg . metadata , databaseItem ) ;
169
170
}
170
171
} else {
171
172
// TODO: Only clear diagnostics on the same database.
@@ -262,10 +263,31 @@ export class InterfaceManager extends DisposableObject {
262
263
sortedResultsMap,
263
264
database : info . database ,
264
265
shouldKeepOldResultsWhileRendering,
265
- kind : info . query . metadata ? info . query . metadata . kind : undefined
266
+ metadata : info . query . metadata
266
267
} ) ;
267
268
}
268
269
270
+ private async getTruncatedResults ( metadata : QueryMetadata | undefined , resultsPathOnDisk : string , sourceInfo : cli . SourceInfo | undefined , sourceLocationPrefix : string ) : Promise < Interpretation > {
271
+ const sarif = await interpretResults ( this . cliServer , metadata , resultsPathOnDisk , sourceInfo ) ;
272
+ // For performance reasons, limit the number of results we try
273
+ // to serialize and send to the webview. TODO: possibly also
274
+ // limit number of paths per result, number of steps per path,
275
+ // or throw an error if we are in aggregate trying to send
276
+ // massively too much data, as it can make the extension
277
+ // unresponsive.
278
+ let numTruncatedResults = 0 ;
279
+ sarif . runs . forEach ( run => {
280
+ if ( run . results !== undefined ) {
281
+ if ( run . results . length > INTERPRETED_RESULTS_PER_RUN_LIMIT ) {
282
+ numTruncatedResults += run . results . length - INTERPRETED_RESULTS_PER_RUN_LIMIT ;
283
+ run . results = run . results . slice ( 0 , INTERPRETED_RESULTS_PER_RUN_LIMIT ) ;
284
+ }
285
+ }
286
+ } ) ;
287
+ return { sarif, sourceLocationPrefix, numTruncatedResults } ;
288
+ ;
289
+ }
290
+
269
291
private async interpretResultsInfo ( query : QueryInfo , resultsInfo : ResultsInfo ) : Promise < Interpretation | undefined > {
270
292
let interpretation : Interpretation | undefined = undefined ;
271
293
if ( query . hasInterpretedResults ( )
@@ -277,114 +299,107 @@ export class InterfaceManager extends DisposableObject {
277
299
const sourceInfo = sourceArchiveUri === undefined ?
278
300
undefined :
279
301
{ 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 } ;
302
+ interpretation = await this . getTruncatedResults ( query . metadata , resultsInfo . resultsPath , sourceInfo , sourceLocationPrefix ) ;
297
303
}
298
304
catch ( e ) {
299
305
// If interpretation fails, accept the error and continue
300
306
// trying to render uninterpreted results anyway.
301
307
this . logger . log ( `Exception during results interpretation: ${ e . message } . Will show raw results instead.` ) ;
302
308
}
303
309
}
304
-
305
310
return interpretation ;
306
311
}
307
312
308
- private async showResultsAsDiagnostics ( resultsPath : string , kind : string | undefined ,
309
- database : DatabaseItem ) {
310
313
314
+ private async showResultsAsDiagnostics ( webviewResultsUri : string , metadata : QueryMetadata | undefined , database : DatabaseItem ) {
311
315
// 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
-
324
- case 'path-problem' : {
325
- const customResults = bqrs . createCustomResultSets < PathProblemQueryResults > ( resultSets , PathProblemQueryResults ) ;
326
- await this . showProblemResultsAsDiagnostics ( customResults , database ) ;
327
- }
328
- break ;
316
+ const resultsPathOnDisk = webviewUriToFileUri ( webviewResultsUri ) . fsPath ;
317
+ const sourceLocationPrefix = await database . getSourceLocationPrefix ( this . cliServer ) ;
318
+ const sourceArchiveUri = database . sourceArchive ;
319
+ const sourceInfo = sourceArchiveUri === undefined ?
320
+ undefined :
321
+ { sourceArchive : sourceArchiveUri . fsPath , sourceLocationPrefix } ;
322
+ const interpretation = await this . getTruncatedResults ( metadata , resultsPathOnDisk , sourceInfo , sourceLocationPrefix ) ;
329
323
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
- }
324
+ try {
325
+ await this . showProblemResultsAsDiagnostics ( interpretation , database ) ;
339
326
}
340
- finally {
341
- fileReader . dispose ( ) ;
327
+ catch ( e ) {
328
+ const msg = e instanceof Error ? e . message : e . toString ( ) ;
329
+ this . logger . log ( `Exception while computing problem results as diagnostics: ${ msg } ` ) ;
330
+ this . _diagnosticCollection . clear ( ) ;
342
331
}
332
+
343
333
}
344
334
345
- private async showProblemResultsAsDiagnostics ( results : CustomResultSets < ProblemQueryResults > ,
346
- databaseItem : DatabaseItem ) : Promise < void > {
335
+ private async showProblemResultsAsDiagnostics ( interpretation : Interpretation , databaseItem : DatabaseItem ) : Promise < void > {
336
+ const { sarif, sourceLocationPrefix } = interpretation ;
337
+
338
+
339
+ if ( ! sarif . runs || ! sarif . runs [ 0 ] . results ) {
340
+ this . logger . log ( "Didn't find a run in the sarif results. Error processing sarif?" )
341
+ return ;
342
+ }
347
343
348
344
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
- } ) ;
345
+
346
+ for ( const result of sarif . runs [ 0 ] . results ) {
347
+ const message = result . message . text ;
348
+ if ( message === undefined ) {
349
+ this . logger . log ( "Sarif had result without plaintext message" )
350
+ continue ;
351
+ }
352
+ if ( ! result . locations ) {
353
+ this . logger . log ( "Sarif had result without location" )
354
+ continue ;
355
+ }
356
+
357
+ const sarifLoc = parseSarifLocation ( result . locations [ 0 ] , sourceLocationPrefix ) ;
358
+ if ( sarifLoc . t == "NoLocation" ) {
359
+ continue ;
360
+ }
361
+ const resultLocation = tryResolveLocation ( sarifLoc , databaseItem )
362
+ if ( ! resultLocation ) {
363
+ this . logger . log ( "Sarif location was not resolvable " + sarifLoc )
364
+ continue ;
365
365
}
366
- else {
367
- message = problemRow . message ;
366
+ const parsedMessage = parseSarifPlainTextMessage ( message ) ;
367
+ const relatedInformation : DiagnosticRelatedInformation [ ] = [ ] ;
368
+ const relatedLocationsById : { [ k : number ] : Sarif . Location } = { } ;
369
+
370
+
371
+ for ( let loc of result . relatedLocations || [ ] ) {
372
+ relatedLocationsById [ loc . id ! ] = loc ;
368
373
}
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 ) ;
374
+ let resultMessageChunks : string [ ] = [ ] ;
375
+ for ( const section of parsedMessage ) {
376
+ if ( typeof section === "string" ) {
377
+ resultMessageChunks . push ( section ) ;
378
+ } else {
379
+ resultMessageChunks . push ( section . text ) ;
380
+ const sarifChunkLoc = parseSarifLocation ( relatedLocationsById [ section . dest ] , sourceLocationPrefix ) ;
381
+ if ( sarifChunkLoc . t == "NoLocation" ) {
382
+ continue ;
383
+ }
384
+ const referenceLocation = tryResolveLocation ( sarifChunkLoc , databaseItem ) ;
385
+
386
+
374
387
if ( referenceLocation ) {
375
388
const related = new DiagnosticRelatedInformation ( referenceLocation ,
376
- reference . text ) ;
389
+ section . text ) ;
377
390
relatedInformation . push ( related ) ;
378
391
}
379
392
}
380
- diagnostic . relatedInformation = relatedInformation ;
381
393
}
394
+ const diagnostic = new Diagnostic ( resultLocation . range , resultMessageChunks . join ( "" ) , DiagnosticSeverity . Warning ) ;
395
+ diagnostic . relatedInformation = relatedInformation ;
396
+
382
397
diagnostics . push ( [
383
- codeLocation . uri ,
398
+ resultLocation . uri ,
384
399
[ diagnostic ]
385
400
] ) ;
386
- }
387
401
402
+ }
388
403
this . _diagnosticCollection . set ( diagnostics ) ;
389
404
}
390
405
@@ -476,22 +491,6 @@ function resolveWholeFileLocation(loc: WholeFileLocation, databaseItem: Database
476
491
return new Location ( databaseItem . resolveSourceFile ( loc . file ) , range ) ;
477
492
}
478
493
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
494
/**
496
495
* Try to resolve the specified CodeQL location to a URI into the source archive. If no exact location
497
496
* can be resolved, returns `undefined`.
0 commit comments