Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion extensions/ql-vscode/src/ast-cfg-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
TemplatePrintCfgProvider,
} from "./contextual/templateProvider";
import { AstCfgCommands } from "./common/commands";
import { LocalQueries } from "./local-queries";
import { LocalQueries } from "./local-queries/local-queries";

type AstCfgOptions = {
localQueries: LocalQueries;
Expand Down
2 changes: 1 addition & 1 deletion extensions/ql-vscode/src/debugger/debug-configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
WorkspaceFolder,
} from "vscode";
import { getOnDiskWorkspaceFolders, showAndLogErrorMessage } from "../helpers";
import { LocalQueries } from "../local-queries";
import { LocalQueries } from "../local-queries/local-queries";
import { getQuickEvalContext, validateQueryPath } from "../run-queries-shared";
import * as CodeQLProtocol from "./debug-protocol";
import { getErrorMessage } from "../pure/helpers-pure";
Expand Down
2 changes: 1 addition & 1 deletion extensions/ql-vscode/src/debugger/debugger-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
ProviderResult,
} from "vscode";
import { isCanary } from "../config";
import { LocalQueries } from "../local-queries";
import { LocalQueries } from "../local-queries/local-queries";
import { DisposableObject } from "../pure/disposable-object";
import { QueryRunner } from "../queryRunner";
import { QLDebugConfigurationProvider } from "./debug-configuration";
Expand Down
3 changes: 2 additions & 1 deletion extensions/ql-vscode/src/debugger/debugger-ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from "vscode";
import { DebuggerCommands } from "../common/commands";
import { DatabaseManager } from "../local-databases";
import { LocalQueries, LocalQueryRun } from "../local-queries";
import { LocalQueries } from "../local-queries/local-queries";
import { DisposableObject } from "../pure/disposable-object";
import { CoreQueryResults } from "../queryRunner";
import {
Expand All @@ -20,6 +20,7 @@ import {
import { QLResolvedDebugConfiguration } from "./debug-configuration";
import * as CodeQLProtocol from "./debug-protocol";
import { App } from "../common/app";
import { LocalQueryRun } from "../local-queries/local-query-run";

/**
* Listens to messages passing between VS Code and the debug adapter, so that we can supplement the
Expand Down
2 changes: 1 addition & 1 deletion extensions/ql-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ import {
PreActivationCommands,
QueryServerCommands,
} from "./common/commands";
import { LocalQueries } from "./local-queries";
import { LocalQueries } from "./local-queries/local-queries";
import { getAstCfgCommands } from "./ast-cfg-commands";
import { getQueryEditorCommands } from "./query-editor";
import { App } from "./common/app";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ProgressCallback, ProgressUpdate, withProgress } from "./progress";
import { ProgressCallback, ProgressUpdate, withProgress } from "../progress";
import {
CancellationToken,
CancellationTokenSource,
Expand All @@ -8,76 +8,46 @@ import {
window,
workspace,
} from "vscode";
import { BaseLogger, extLogger, Logger, TeeLogger } from "./common";
import { isCanary, MAX_QUERIES } from "./config";
import { gatherQlFiles } from "./pure/files";
import { extLogger, TeeLogger } from "../common";
import { isCanary, MAX_QUERIES } from "../config";
import { gatherQlFiles } from "../pure/files";
import { basename } from "path";
import {
createTimestampFile,
findLanguage,
getOnDiskWorkspaceFolders,
showAndLogErrorMessage,
showAndLogExceptionWithTelemetry,
showAndLogWarningMessage,
showBinaryChoiceDialog,
tryGetQueryMetadata,
} from "./helpers";
import { displayQuickQuery } from "./quick-query";
import {
CoreCompletedQuery,
CoreQueryResults,
QueryRunner,
} from "./queryRunner";
import { QueryHistoryManager } from "./query-history/query-history-manager";
import { DatabaseUI } from "./local-databases-ui";
import { ResultsView } from "./interface";
import { DatabaseItem, DatabaseManager } from "./local-databases";
} from "../helpers";
import { displayQuickQuery } from "../quick-query";
import { CoreCompletedQuery, QueryRunner } from "../queryRunner";
import { QueryHistoryManager } from "../query-history/query-history-manager";
import { DatabaseUI } from "../local-databases-ui";
import { ResultsView } from "../interface";
import { DatabaseItem, DatabaseManager } from "../local-databases";
import {
createInitialQueryInfo,
EvaluatorLogPaths,
generateEvalLogSummaries,
getQuickEvalContext,
logEndSummary,
promptUserToSaveChanges,
QueryEvaluationInfo,
QueryOutputDir,
QueryWithResults,
SelectedQuery,
validateQueryUri,
} from "./run-queries-shared";
import { CompletedLocalQueryInfo, LocalQueryInfo } from "./query-results";
import { WebviewReveal } from "./interface-utils";
import { asError, getErrorMessage } from "./pure/helpers-pure";
import { CodeQLCliServer } from "./cli";
import { LocalQueryCommands } from "./common/commands";
import { App } from "./common/app";
import { DisposableObject } from "./pure/disposable-object";
import { QueryResultType } from "./pure/new-messages";
import { redactableError } from "./pure/errors";
import { SkeletonQueryWizard } from "./skeleton-query-wizard";
} from "../run-queries-shared";
import { CompletedLocalQueryInfo, LocalQueryInfo } from "../query-results";
import { WebviewReveal } from "../interface-utils";
import { asError, getErrorMessage } from "../pure/helpers-pure";
import { CodeQLCliServer } from "../cli";
import { LocalQueryCommands } from "../common/commands";
import { App } from "../common/app";
import { DisposableObject } from "../pure/disposable-object";
import { SkeletonQueryWizard } from "../skeleton-query-wizard";
import { LocalQueryRun } from "./local-query-run";

interface DatabaseQuickPickItem extends QuickPickItem {
databaseItem: DatabaseItem;
}

function formatResultMessage(result: CoreQueryResults): string {
switch (result.resultType) {
case QueryResultType.CANCELLATION:
return `cancelled after ${Math.round(
result.evaluationTime / 1000,
)} seconds`;
case QueryResultType.OOM:
return "out of memory";
case QueryResultType.SUCCESS:
return `finished in ${Math.round(result.evaluationTime / 1000)} seconds`;
case QueryResultType.COMPILATION_ERROR:
return `compilation failed: ${result.message}`;
case QueryResultType.OTHER_ERROR:
default:
return result.message ? `failed: ${result.message}` : "failed";
}
}

/**
* If either the query file or the quickeval file is dirty, give the user the chance to save them.
*/
Expand All @@ -97,142 +67,6 @@ async function promptToSaveQueryIfNeeded(query: SelectedQuery): Promise<void> {
}
}

/**
* Tracks the evaluation of a local query, including its interactions with the UI.
*
* The client creates an instance of `LocalQueryRun` when the evaluation starts, and then invokes
* the `complete()` function once the query has completed (successfully or otherwise).
*
* Having the client tell the `LocalQueryRun` when the evaluation is complete, rather than having
* the `LocalQueryRun` manage the evaluation itself, may seem a bit clunky. It's done this way
* because once we move query evaluation into a Debug Adapter, the debugging UI drives the
* evaluation, and we can only respond to events from the debug adapter.
*/
export class LocalQueryRun {
public constructor(
private readonly outputDir: QueryOutputDir,
private readonly localQueries: LocalQueries,
private readonly queryInfo: LocalQueryInfo,
private readonly dbItem: DatabaseItem,
public readonly logger: Logger, // Public so that other clients, like the debug adapter, know where to send log output
private readonly queryHistoryManager: QueryHistoryManager,
private readonly cliServer: CodeQLCliServer,
) {}

/**
* Updates the UI based on the results of the query evaluation. This creates the evaluator log
* summaries, updates the query history item for the evaluation with the results and evaluation
* time, and displays the results view.
*
* This function must be called when the evaluation completes, whether the evaluation was
* successful or not.
* */
public async complete(results: CoreQueryResults): Promise<void> {
const evalLogPaths = await this.summarizeEvalLog(
results.resultType,
this.outputDir,
this.logger,
);
if (evalLogPaths !== undefined) {
this.queryInfo.setEvaluatorLogPaths(evalLogPaths);
}
const queryWithResults = await this.getCompletedQueryInfo(results);
this.queryHistoryManager.completeQuery(this.queryInfo, queryWithResults);
await this.localQueries.showResultsForCompletedQuery(
this.queryInfo as CompletedLocalQueryInfo,
WebviewReveal.Forced,
);
// Note we must update the query history view after showing results as the
// display and sorting might depend on the number of results
await this.queryHistoryManager.refreshTreeView();
}

/**
* Updates the UI in the case where query evaluation throws an exception.
*/
public async fail(err: Error): Promise<void> {
err.message = `Error running query: ${err.message}`;
this.queryInfo.failureReason = err.message;
await this.queryHistoryManager.refreshTreeView();
}

/**
* Generate summaries of the structured evaluator log.
*/
private async summarizeEvalLog(
resultType: QueryResultType,
outputDir: QueryOutputDir,
logger: BaseLogger,
): Promise<EvaluatorLogPaths | undefined> {
const evalLogPaths = await generateEvalLogSummaries(
this.cliServer,
outputDir,
);
if (evalLogPaths !== undefined) {
if (evalLogPaths.endSummary !== undefined) {
void logEndSummary(evalLogPaths.endSummary, logger); // Logged asynchrnously
}
} else {
// Raw evaluator log was not found. Notify the user, unless we know why it wasn't found.
if (resultType === QueryResultType.SUCCESS) {
void showAndLogWarningMessage(
`Failed to write structured evaluator log to ${outputDir.evalLogPath}.`,
);
} else {
// Don't bother notifying the user if there's no log. For some errors, like compilation
// errors, we don't expect a log. For cancellations and OOM errors, whether or not we have
// a log depends on how far execution got before termination.
}
}

return evalLogPaths;
}

/**
* Gets a `QueryWithResults` containing information about the evaluation of the query and its
* result, in the form expected by the query history UI.
*/
private async getCompletedQueryInfo(
results: CoreQueryResults,
): Promise<QueryWithResults> {
// Read the query metadata if possible, to use in the UI.
const metadata = await tryGetQueryMetadata(
this.cliServer,
this.queryInfo.initialInfo.queryPath,
);
const query = new QueryEvaluationInfo(
this.outputDir.querySaveDir,
this.dbItem.databaseUri.fsPath,
await this.dbItem.hasMetadataFile(),
this.queryInfo.initialInfo.quickEvalPosition,
metadata,
);

if (results.resultType !== QueryResultType.SUCCESS) {
const message = results.message
? redactableError`Failed to run query: ${results.message}`
: redactableError`Failed to run query`;
void showAndLogExceptionWithTelemetry(message);
}
const message = formatResultMessage(results);
const successful = results.resultType === QueryResultType.SUCCESS;
return {
query,
result: {
evaluationTime: results.evaluationTime,
queryId: 0,
resultType: successful
? QueryResultType.SUCCESS
: QueryResultType.OTHER_ERROR,
runId: 0,
message,
},
message,
successful,
};
}
}

export class LocalQueries extends DisposableObject {
public constructor(
private readonly app: App,
Expand Down
Loading