@@ -14,14 +14,11 @@ import { Disposable } from "vscode";
1414import { CancellationTokenSource } from "vscode-jsonrpc" ;
1515import { BaseLogger , LogOptions , queryServerLogger } from "../common" ;
1616import { QueryResultType } from "../pure/new-messages" ;
17- import {
18- CoreCompletedQuery ,
19- CoreQueryResults ,
20- CoreQueryRun ,
21- QueryRunner ,
22- } from "../queryRunner" ;
17+ import { CoreQueryResults , CoreQueryRun , QueryRunner } from "../queryRunner" ;
2318import * as CodeQLProtocol from "./debug-protocol" ;
2419import { QuickEvalContext } from "../run-queries-shared" ;
20+ import { getErrorMessage } from "../pure/helpers-pure" ;
21+ import { DisposableObject } from "../pure/disposable-object" ;
2522
2623// More complete implementations of `Event` for certain events, because the classes from
2724// `@vscode/debugadapter` make it more difficult to provide some of the message values.
@@ -131,21 +128,129 @@ const QUERY_THREAD_ID = 1;
131128/** The user-visible name of the query evaluation thread. */
132129const QUERY_THREAD_NAME = "Evaluation thread" ;
133130
131+ /**
132+ * An active query evaluation within a debug session.
133+ *
134+ * This class encapsulates the state and resources associated with the running query, to avoid
135+ * having multiple properties within `QLDebugSession` that are only defined during query evaluation.
136+ */
137+ class RunningQuery extends DisposableObject {
138+ private readonly tokenSource = this . push ( new CancellationTokenSource ( ) ) ;
139+ public readonly queryRun : CoreQueryRun ;
140+
141+ public constructor (
142+ queryRunner : QueryRunner ,
143+ config : CodeQLProtocol . LaunchConfig ,
144+ private readonly quickEvalContext : QuickEvalContext | undefined ,
145+ queryStorageDir : string ,
146+ private readonly logger : BaseLogger ,
147+ private readonly sendEvent : ( event : Event ) => void ,
148+ ) {
149+ super ( ) ;
150+
151+ // Create the query run, which will give us some information about the query even before the
152+ // evaluation has completed.
153+ this . queryRun = queryRunner . createQueryRun (
154+ config . database ,
155+ {
156+ queryPath : config . query ,
157+ quickEvalPosition : quickEvalContext ?. quickEvalPosition ,
158+ } ,
159+ true ,
160+ config . additionalPacks ,
161+ config . extensionPacks ,
162+ queryStorageDir ,
163+ undefined ,
164+ undefined ,
165+ ) ;
166+ }
167+
168+ public get id ( ) : string {
169+ return this . queryRun . id ;
170+ }
171+
172+ /**
173+ * Evaluates the query, firing progress events along the way. The evaluation can be cancelled by
174+ * calling `cancel()`.
175+ *
176+ * This function does not throw exceptions to report query evaluation failure. It just returns an
177+ * evaluation result with a failure message instead.
178+ */
179+ public async evaluate ( ) : Promise <
180+ CodeQLProtocol . EvaluationCompletedEvent [ "body" ]
181+ > {
182+ // Send the `EvaluationStarted` event first, to let the client known where the outputs are
183+ // going to show up.
184+ this . sendEvent (
185+ new EvaluationStartedEvent (
186+ this . queryRun . id ,
187+ this . queryRun . outputDir . querySaveDir ,
188+ this . quickEvalContext ,
189+ ) ,
190+ ) ;
191+
192+ try {
193+ // Report progress via the debugger protocol.
194+ const progressStart = new ProgressStartEvent (
195+ this . queryRun . id ,
196+ "Running query" ,
197+ undefined ,
198+ 0 ,
199+ ) ;
200+ progressStart . body . cancellable = true ;
201+ this . sendEvent ( progressStart ) ;
202+ try {
203+ return await this . queryRun . evaluate (
204+ ( p ) => {
205+ const progressUpdate = new ProgressUpdateEvent (
206+ this . queryRun . id ,
207+ p . message ,
208+ ( p . step * 100 ) / p . maxStep ,
209+ ) ;
210+ this . sendEvent ( progressUpdate ) ;
211+ } ,
212+ this . tokenSource . token ,
213+ this . logger ,
214+ ) ;
215+ } finally {
216+ this . sendEvent ( new ProgressEndEvent ( this . queryRun . id ) ) ;
217+ }
218+ } catch ( e ) {
219+ const message = getErrorMessage ( e ) ;
220+ return {
221+ resultType : QueryResultType . OTHER_ERROR ,
222+ message,
223+ evaluationTime : 0 ,
224+ } ;
225+ }
226+ }
227+
228+ /**
229+ * Attempts to cancel the running evaluation.
230+ */
231+ public cancel ( ) : void {
232+ this . tokenSource . cancel ( ) ;
233+ }
234+ }
235+
134236/**
135237 * An in-process implementation of the debug adapter for CodeQL queries.
136238 *
137239 * For now, this is pretty much just a wrapper around the query server.
138240 */
139241export class QLDebugSession extends LoggingDebugSession implements Disposable {
242+ /** A `BaseLogger` that sends output to the debug console. */
243+ private readonly logger : BaseLogger = {
244+ log : async ( message : string , _options : LogOptions ) : Promise < void > => {
245+ this . sendEvent ( new OutputEvent ( message , "console" ) ) ;
246+ } ,
247+ } ;
140248 private state : State = "uninitialized" ;
141249 private terminateOnComplete = false ;
142250 private args : CodeQLProtocol . LaunchRequest [ "arguments" ] | undefined =
143251 undefined ;
144- private tokenSource : CancellationTokenSource | undefined = undefined ;
145- private queryRun : CoreQueryRun | undefined = undefined ;
146- private lastResult :
147- | CodeQLProtocol . EvaluationCompletedEvent [ "body" ]
148- | undefined = undefined ;
252+ private runningQuery : RunningQuery | undefined = undefined ;
253+ private lastResultType : QueryResultType = QueryResultType . CANCELLATION ;
149254
150255 constructor (
151256 private readonly queryStorageDir : string ,
@@ -155,7 +260,9 @@ export class QLDebugSession extends LoggingDebugSession implements Disposable {
155260 }
156261
157262 public dispose ( ) : void {
158- this . cancelEvaluation ( ) ;
263+ if ( this . runningQuery !== undefined ) {
264+ this . runningQuery . cancel ( ) ;
265+ }
159266 }
160267
161268 protected dispatchRequest ( request : Protocol . Request ) : void {
@@ -230,19 +337,11 @@ export class QLDebugSession extends LoggingDebugSession implements Disposable {
230337 }
231338
232339 private terminateOrDisconnect ( response : Protocol . Response ) : void {
233- switch ( this . state ) {
234- case "running" :
235- this . terminateOnComplete = true ;
236- this . cancelEvaluation ( ) ;
237- break ;
238-
239- case "stopped" :
240- this . terminateAndExit ( ) ;
241- break ;
242-
243- default :
244- // Ignore
245- break ;
340+ if ( this . runningQuery !== undefined ) {
341+ this . terminateOnComplete = true ;
342+ this . runningQuery . cancel ( ) ;
343+ } else if ( this . state === "stopped" ) {
344+ this . terminateAndExit ( ) ;
246345 }
247346
248347 this . sendResponse ( response ) ;
@@ -349,18 +448,11 @@ export class QLDebugSession extends LoggingDebugSession implements Disposable {
349448 args : Protocol . CancelArguments ,
350449 _request ?: Protocol . Request ,
351450 ) : void {
352- switch ( this . state ) {
353- case "running" :
354- if ( args . progressId !== undefined ) {
355- if ( this . queryRun ! . id === args . progressId ) {
356- this . cancelEvaluation ( ) ;
357- }
358- }
359- break ;
360-
361- default :
362- // Ignore;
363- break ;
451+ if (
452+ args . progressId !== undefined &&
453+ this . runningQuery ?. id === args . progressId
454+ ) {
455+ this . runningQuery . cancel ( ) ;
364456 }
365457
366458 this . sendResponse ( response ) ;
@@ -436,15 +528,6 @@ export class QLDebugSession extends LoggingDebugSession implements Disposable {
436528 }
437529 }
438530
439- /** Creates a `BaseLogger` that sends output to the debug console. */
440- private createLogger ( ) : BaseLogger {
441- return {
442- log : async ( message : string , _options : LogOptions ) : Promise < void > => {
443- this . sendEvent ( new OutputEvent ( message , "console" ) ) ;
444- } ,
445- } ;
446- }
447-
448531 /**
449532 * Runs the query or quickeval, and notifies the debugger client when the evaluation completes.
450533 *
@@ -456,75 +539,23 @@ export class QLDebugSession extends LoggingDebugSession implements Disposable {
456539 ) : Promise < void > {
457540 const args = this . args ! ;
458541
459- this . tokenSource = new CancellationTokenSource ( ) ;
460- try {
461- // Create the query run, which will give us some information about the query even before the
462- // evaluation has completed.
463- this . queryRun = this . queryRunner . createQueryRun (
464- args . database ,
465- {
466- queryPath : args . query ,
467- quickEvalPosition : quickEvalContext ?. quickEvalPosition ,
468- } ,
469- true ,
470- args . additionalPacks ,
471- args . extensionPacks ,
472- this . queryStorageDir ,
473- undefined ,
474- undefined ,
475- ) ;
476-
477- this . state = "running" ;
478-
479- // Send the `EvaluationStarted` event first, to let the client known where the outputs are
480- // going to show up.
481- this . sendEvent (
482- new EvaluationStartedEvent (
483- this . queryRun . id ,
484- this . queryRun . outputDir . querySaveDir ,
485- quickEvalContext ,
486- ) ,
487- ) ;
542+ const runningQuery = new RunningQuery (
543+ this . queryRunner ,
544+ args ,
545+ quickEvalContext ,
546+ this . queryStorageDir ,
547+ this . logger ,
548+ ( event ) => this . sendEvent ( event ) ,
549+ ) ;
550+ this . runningQuery = runningQuery ;
551+ this . state = "running" ;
488552
489- try {
490- // Report progress via the debugger protocol.
491- const progressStart = new ProgressStartEvent (
492- this . queryRun . id ,
493- "Running query" ,
494- undefined ,
495- 0 ,
496- ) ;
497- progressStart . body . cancellable = true ;
498- this . sendEvent ( progressStart ) ;
499- let result : CoreCompletedQuery ;
500- try {
501- result = await this . queryRun . evaluate (
502- ( p ) => {
503- const progressUpdate = new ProgressUpdateEvent (
504- this . queryRun ! . id ,
505- p . message ,
506- ( p . step * 100 ) / p . maxStep ,
507- ) ;
508- this . sendEvent ( progressUpdate ) ;
509- } ,
510- this . tokenSource ! . token ,
511- this . createLogger ( ) ,
512- ) ;
513- } finally {
514- // Report the end of the progress
515- this . sendEvent ( new ProgressEndEvent ( this . queryRun ! . id ) ) ;
516- }
517- this . completeEvaluation ( result ) ;
518- } catch ( e ) {
519- const message = e instanceof Error ? e . message : "Unknown error" ;
520- this . completeEvaluation ( {
521- resultType : QueryResultType . OTHER_ERROR ,
522- message,
523- evaluationTime : 0 ,
524- } ) ;
525- }
553+ try {
554+ const result = await runningQuery . evaluate ( ) ;
555+ this . completeEvaluation ( result ) ;
526556 } finally {
527- this . disposeTokenSource ( ) ;
557+ this . runningQuery = undefined ;
558+ runningQuery . dispose ( ) ;
528559 }
529560 }
530561
@@ -534,7 +565,7 @@ export class QLDebugSession extends LoggingDebugSession implements Disposable {
534565 private completeEvaluation (
535566 result : CodeQLProtocol . EvaluationCompletedEvent [ "body" ] ,
536567 ) : void {
537- this . lastResult = result ;
568+ this . lastResultType = result . resultType ;
538569
539570 // Report the evaluation result
540571 this . sendEvent ( new EvaluationCompletedEvent ( result ) ) ;
@@ -546,8 +577,6 @@ export class QLDebugSession extends LoggingDebugSession implements Disposable {
546577 }
547578
548579 this . reportStopped ( ) ;
549-
550- this . queryRun = undefined ;
551580 }
552581
553582 private reportStopped ( ) : void {
@@ -566,22 +595,8 @@ export class QLDebugSession extends LoggingDebugSession implements Disposable {
566595 this . sendEvent ( new TerminatedEvent ( ) ) ;
567596
568597 // Report the debuggee as exited.
569- this . sendEvent ( new ExitedEvent ( this . lastResult ! . resultType ) ) ;
598+ this . sendEvent ( new ExitedEvent ( this . lastResultType ) ) ;
570599
571600 this . state = "terminated" ;
572601 }
573-
574- private disposeTokenSource ( ) : void {
575- if ( this . tokenSource !== undefined ) {
576- this . tokenSource ! . dispose ( ) ;
577- this . tokenSource = undefined ;
578- }
579- }
580-
581- private cancelEvaluation ( ) : void {
582- if ( this . tokenSource !== undefined ) {
583- this . tokenSource . cancel ( ) ;
584- this . disposeTokenSource ( ) ;
585- }
586- }
587602}
0 commit comments