11import * as crypto from 'crypto' ;
22import * 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' ;
67import { DisposableObject } from 'semmle-vscode-utils' ;
78import * as vscode from 'vscode' ;
89import { Diagnostic , DiagnosticRelatedInformation , DiagnosticSeverity , languages , Location , Position , Range , Uri , window as Window , workspace } from 'vscode' ;
@@ -11,7 +12,7 @@ import { DatabaseItem, DatabaseManager } from './databases';
1112import * as helpers from './helpers' ;
1213import { showAndLogErrorMessage } from './helpers' ;
1314import { 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' ;
1516import { Logger } from './logging' ;
1617import * as messages from './messages' ;
1718import { EvaluationInfo , interpretResults , QueryInfo , tmpDir } from './queries' ;
@@ -165,7 +166,7 @@ export class InterfaceManager extends DisposableObject {
165166 if ( msg . visible ) {
166167 const databaseItem = this . databaseManager . findDatabaseItem ( Uri . parse ( msg . databaseUri ) ) ;
167168 if ( databaseItem !== undefined ) {
168- await this . showResultsAsDiagnostics ( msg . resultsPath , msg . kind , databaseItem ) ;
169+ await this . showResultsAsDiagnostics ( msg . resultsPath , msg . metadata , databaseItem ) ;
169170 }
170171 } else {
171172 // TODO: Only clear diagnostics on the same database.
@@ -262,10 +263,31 @@ export class InterfaceManager extends DisposableObject {
262263 sortedResultsMap,
263264 database : info . database ,
264265 shouldKeepOldResultsWhileRendering,
265- kind : info . query . metadata ? info . query . metadata . kind : undefined
266+ metadata : info . query . metadata
266267 } ) ;
267268 }
268269
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+
269291 private async interpretResultsInfo ( query : QueryInfo , resultsInfo : ResultsInfo ) : Promise < Interpretation | undefined > {
270292 let interpretation : Interpretation | undefined = undefined ;
271293 if ( query . hasInterpretedResults ( )
@@ -277,114 +299,107 @@ export class InterfaceManager extends DisposableObject {
277299 const sourceInfo = sourceArchiveUri === undefined ?
278300 undefined :
279301 { 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 ) ;
297303 }
298304 catch ( e ) {
299305 // If interpretation fails, accept the error and continue
300306 // trying to render uninterpreted results anyway.
301307 this . logger . log ( `Exception during results interpretation: ${ e . message } . Will show raw results instead.` ) ;
302308 }
303309 }
304-
305310 return interpretation ;
306311 }
307312
308- private async showResultsAsDiagnostics ( resultsPath : string , kind : string | undefined ,
309- database : DatabaseItem ) {
310313
314+ private async showResultsAsDiagnostics ( webviewResultsUri : string , metadata : QueryMetadata | undefined , database : DatabaseItem ) {
311315 // 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 ) ;
329323
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 ) ;
339326 }
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 ( ) ;
342331 }
332+
343333 }
344334
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+ }
347343
348344 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 ;
365365 }
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 ;
368373 }
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+
374387 if ( referenceLocation ) {
375388 const related = new DiagnosticRelatedInformation ( referenceLocation ,
376- reference . text ) ;
389+ section . text ) ;
377390 relatedInformation . push ( related ) ;
378391 }
379392 }
380- diagnostic . relatedInformation = relatedInformation ;
381393 }
394+ const diagnostic = new Diagnostic ( resultLocation . range , resultMessageChunks . join ( "" ) , DiagnosticSeverity . Warning ) ;
395+ diagnostic . relatedInformation = relatedInformation ;
396+
382397 diagnostics . push ( [
383- codeLocation . uri ,
398+ resultLocation . uri ,
384399 [ diagnostic ]
385400 ] ) ;
386- }
387401
402+ }
388403 this . _diagnosticCollection . set ( diagnostics ) ;
389404 }
390405
@@ -479,22 +494,6 @@ function resolveWholeFileLocation(loc: WholeFileLocation, databaseItem: Database
479494 return new Location ( databaseItem . resolveSourceFile ( loc . file ) , range ) ;
480495}
481496
482- /**
483- * Resolve the specified CodeQL location to a URI into the source archive.
484- * @param loc CodeQL location to resolve
485- * @param databaseItem Database in which to resolve the file location.
486- */
487- function resolveLocation ( loc : LocationValue | undefined , databaseItem : DatabaseItem ) : Location {
488- const resolvedLocation = tryResolveLocation ( loc , databaseItem ) ;
489- if ( resolvedLocation ) {
490- return resolvedLocation ;
491- }
492- else {
493- // Return a fake position in the source archive directory itself.
494- return new Location ( databaseItem . resolveSourceFile ( undefined ) , new Position ( 0 , 0 ) ) ;
495- }
496- }
497-
498497/**
499498 * Try to resolve the specified CodeQL location to a URI into the source archive. If no exact location
500499 * can be resolved, returns `undefined`.
0 commit comments