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
1 change: 1 addition & 0 deletions extensions/ql-vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

- Fix a bug where invoking _View AST_ from the file explorer would not view the selected file. Instead it would view the active editor. Also, prevent the _View AST_ from appearing if the current selection includes a directory or multiple files. [#1113](https://github.com/github/vscode-codeql/pull/1113)
- Add query history items as soon as a query is run, including new icons for each history item. [#1094](https://github.com/github/vscode-codeql/pull/1094)
- Save query history items across restarts. Items will be saved for 30 days and can be overwritten by setting the `codeQL.queryHistory.ttl` configuration setting. [#1130](https://github.com/github/vscode-codeql/pull/1130)
- Allow in-progress query items to be cancelled from the query history view. [#1105](https://github.com/github/vscode-codeql/pull/1105)

## 1.5.10 - 25 January 2022
Expand Down
6 changes: 6 additions & 0 deletions extensions/ql-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,12 @@
"default": "%q on %d - %s, %r result count [%t]",
"markdownDescription": "Default string for how to label query history items.\n* %t is the time of the query\n* %q is the human-readable query name\n* %f is the query file name\n* %d is the database name\n* %r is the number of results\n* %s is a status string"
},
"codeQL.queryHistory.ttl": {
"type": "number",
"default": 30,
"description": "Number of days to retain queries in the query history before being automatically deleted.",
"scope": "machine"
},
"codeQL.runningTests.additionalTestArguments": {
"scope": "window",
"type": "array",
Expand Down
16 changes: 14 additions & 2 deletions extensions/ql-vscode/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { workspace, Event, EventEmitter, ConfigurationChangeEvent, Configuration
import { DistributionManager } from './distribution';
import { logger } from './logging';

const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000;

/** Helper class to look up a labelled (and possibly nested) setting. */
export class Setting {
name: string;
Expand Down Expand Up @@ -54,8 +56,11 @@ const DISTRIBUTION_SETTING = new Setting('cli', ROOT_SETTING);
export const CUSTOM_CODEQL_PATH_SETTING = new Setting('executablePath', DISTRIBUTION_SETTING);
const INCLUDE_PRERELEASE_SETTING = new Setting('includePrerelease', DISTRIBUTION_SETTING);
const PERSONAL_ACCESS_TOKEN_SETTING = new Setting('personalAccessToken', DISTRIBUTION_SETTING);

// Query History configuration
const QUERY_HISTORY_SETTING = new Setting('queryHistory', ROOT_SETTING);
const QUERY_HISTORY_FORMAT_SETTING = new Setting('format', QUERY_HISTORY_SETTING);
const QUERY_HISTORY_TTL = new Setting('format', QUERY_HISTORY_SETTING);

/** When these settings change, the distribution should be updated. */
const DISTRIBUTION_CHANGE_SETTINGS = [CUSTOM_CODEQL_PATH_SETTING, INCLUDE_PRERELEASE_SETTING, PERSONAL_ACCESS_TOKEN_SETTING];
Expand All @@ -71,7 +76,6 @@ export interface DistributionConfig {
}

// Query server configuration

const RUNNING_QUERIES_SETTING = new Setting('runningQueries', ROOT_SETTING);
const NUMBER_OF_THREADS_SETTING = new Setting('numberOfThreads', RUNNING_QUERIES_SETTING);
const SAVE_CACHE_SETTING = new Setting('saveCache', RUNNING_QUERIES_SETTING);
Expand Down Expand Up @@ -106,10 +110,11 @@ export interface QueryServerConfig {
}

/** When these settings change, the query history should be refreshed. */
const QUERY_HISTORY_SETTINGS = [QUERY_HISTORY_FORMAT_SETTING];
const QUERY_HISTORY_SETTINGS = [QUERY_HISTORY_FORMAT_SETTING, QUERY_HISTORY_TTL];

export interface QueryHistoryConfig {
format: string;
ttlInMillis: number;
onDidChangeConfiguration: Event<void>;
}

Expand Down Expand Up @@ -251,6 +256,13 @@ export class QueryHistoryConfigListener extends ConfigListener implements QueryH
public get format(): string {
return QUERY_HISTORY_FORMAT_SETTING.getValue<string>();
}

/**
* The configuration value is in days, but return the value in milliseconds.
*/
public get ttlInMillis(): number {
return (QUERY_HISTORY_TTL.getValue<number>() || 30) * ONE_DAY_IN_MS;
}
}

export class CliConfigListener extends ConfigListener implements CliConfig {
Expand Down
3 changes: 3 additions & 0 deletions extensions/ql-vscode/src/contextual/locationFinder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface FullLocationLink extends LocationLink {
* @param dbm The database manager
* @param uriString The selected source file and location
* @param keyType The contextual query type to run
* @param queryStorageDir The directory to store the query results
* @param progress A progress callback
* @param token A CancellationToken
* @param filter A function that will filter extraneous results
Expand All @@ -38,6 +39,7 @@ export async function getLocationsForUriString(
dbm: DatabaseManager,
uriString: string,
keyType: KeyType,
queryStorageDir: string,
progress: ProgressCallback,
token: CancellationToken,
filter: (src: string, dest: string) => boolean
Expand Down Expand Up @@ -69,6 +71,7 @@ export async function getLocationsForUriString(
qs,
db,
initialInfo,
queryStorageDir,
progress,
token,
templates
Expand Down
6 changes: 6 additions & 0 deletions extensions/ql-vscode/src/contextual/templateProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export class TemplateQueryDefinitionProvider implements DefinitionProvider {
private cli: CodeQLCliServer,
private qs: QueryServerClient,
private dbm: DatabaseManager,
private queryStorageDir: string,
) {
this.cache = new CachedOperation<LocationLink[]>(this.getDefinitions.bind(this));
}
Expand Down Expand Up @@ -69,6 +70,7 @@ export class TemplateQueryDefinitionProvider implements DefinitionProvider {
this.dbm,
uriString,
KeyType.DefinitionQuery,
this.queryStorageDir,
progress,
token,
(src, _dest) => src === uriString
Expand All @@ -84,6 +86,7 @@ export class TemplateQueryReferenceProvider implements ReferenceProvider {
private cli: CodeQLCliServer,
private qs: QueryServerClient,
private dbm: DatabaseManager,
private queryStorageDir: string,
) {
this.cache = new CachedOperation<FullLocationLink[]>(this.getReferences.bind(this));
}
Expand Down Expand Up @@ -116,6 +119,7 @@ export class TemplateQueryReferenceProvider implements ReferenceProvider {
this.dbm,
uriString,
KeyType.DefinitionQuery,
this.queryStorageDir,
progress,
token,
(src, _dest) => src === uriString
Expand All @@ -136,6 +140,7 @@ export class TemplatePrintAstProvider {
private cli: CodeQLCliServer,
private qs: QueryServerClient,
private dbm: DatabaseManager,
private queryStorageDir: string,
) {
this.cache = new CachedOperation<QueryWithDb>(this.getAst.bind(this));
}
Expand Down Expand Up @@ -216,6 +221,7 @@ export class TemplatePrintAstProvider {
this.qs,
db,
initialInfo,
this.queryStorageDir,
progress,
token,
templates
Expand Down
19 changes: 14 additions & 5 deletions extensions/ql-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from 'vscode';
import { LanguageClient } from 'vscode-languageclient';
import * as os from 'os';
import * as fs from 'fs-extra';
import * as path from 'path';
import * as tmp from 'tmp-promise';
import { testExplorerExtensionId, TestHub } from 'vscode-test-adapter-api';
Expand Down Expand Up @@ -435,16 +436,22 @@ async function activateWithInstalledDistribution(
ctx.subscriptions.push(queryHistoryConfigurationListener);
const showResults = async (item: FullCompletedQueryInfo) =>
showResultsForCompletedQuery(item, WebviewReveal.Forced);
const queryStorageDir = path.join(ctx.globalStorageUri.fsPath, 'queries');
await fs.ensureDir(queryStorageDir);

void logger.log('Initializing query history.');
const qhm = new QueryHistoryManager(
qs,
dbm,
ctx.extensionPath,
queryStorageDir,
ctx,
queryHistoryConfigurationListener,
showResults,
async (from: FullCompletedQueryInfo, to: FullCompletedQueryInfo) =>
showResultsForComparison(from, to),
);
await qhm.readQueryHistory();

ctx.subscriptions.push(qhm);
void logger.log('Initializing results panel interface.');
const intm = new InterfaceManager(ctx, dbm, cliServer, queryServerLogger);
Expand Down Expand Up @@ -513,10 +520,12 @@ async function activateWithInstalledDistribution(
qs,
databaseItem,
initialInfo,
queryStorageDir,
progress,
source.token,
);
item.completeThisQuery(completedQueryInfo);
await qhm.writeQueryHistory();
await showResultsForCompletedQuery(item as FullCompletedQueryInfo, WebviewReveal.NotForced);
// Note we must update the query history view after showing results as the
// display and sorting might depend on the number of results
Expand Down Expand Up @@ -988,16 +997,16 @@ async function activateWithInstalledDistribution(
void logger.log('Registering jump-to-definition handlers.');
languages.registerDefinitionProvider(
{ scheme: archiveFilesystemProvider.zipArchiveScheme },
new TemplateQueryDefinitionProvider(cliServer, qs, dbm)
new TemplateQueryDefinitionProvider(cliServer, qs, dbm, queryStorageDir)
);

languages.registerReferenceProvider(
{ scheme: archiveFilesystemProvider.zipArchiveScheme },
new TemplateQueryReferenceProvider(cliServer, qs, dbm)
new TemplateQueryReferenceProvider(cliServer, qs, dbm, queryStorageDir)
);

const astViewer = new AstViewer();
const templateProvider = new TemplatePrintAstProvider(cliServer, qs, dbm);
const templateProvider = new TemplatePrintAstProvider(cliServer, qs, dbm, queryStorageDir);

ctx.subscriptions.push(astViewer);
ctx.subscriptions.push(commandRunnerWithProgress('codeQL.viewAst', async (
Expand Down Expand Up @@ -1036,7 +1045,7 @@ async function activateWithInstalledDistribution(
}

function getContextStoragePath(ctx: ExtensionContext) {
return ctx.storagePath || ctx.globalStoragePath;
return ctx.storageUri?.fsPath || ctx.globalStorageUri.fsPath;
}

async function initializeLogging(ctx: ExtensionContext): Promise<void> {
Expand Down
135 changes: 135 additions & 0 deletions extensions/ql-vscode/src/query-history-scrubber.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import * as fs from 'fs-extra';
import * as os from 'os';
import * as path from 'path';
import { Disposable, ExtensionContext } from 'vscode';
import { logger } from './logging';

const LAST_SCRUB_TIME_KEY = 'lastScrubTime';

type Counter = {
increment: () => void;
};

/**
* Registers an interval timer that will periodically check for queries old enought
* to be deleted.
*
* Note that this scrubber will clean all queries from all workspaces. It should not
* run too often and it should only run from one workspace at a time.
*
* Generally, `wakeInterval` should be significantly shorter than `throttleTime`.
*
* @param wakeInterval How often to check to see if the job should run.
* @param throttleTime How often to actually run the job.
* @param maxQueryTime The maximum age of a query before is ready for deletion.
* @param queryDirectory The directory containing all queries.
* @param ctx The extension context.
*/
export function registerQueryHistoryScubber(
wakeInterval: number,
throttleTime: number,
maxQueryTime: number,
queryDirectory: string,
ctx: ExtensionContext,

// optional counter to keep track of how many times the scrubber has run
counter?: Counter
): Disposable {
const deregister = setInterval(scrubQueries, wakeInterval, throttleTime, maxQueryTime, queryDirectory, ctx, counter);

return {
dispose: () => {
clearInterval(deregister);
}
};
}

async function scrubQueries(
throttleTime: number,
maxQueryTime: number,
queryDirectory: string,
ctx: ExtensionContext,
counter?: Counter
) {
const lastScrubTime = ctx.globalState.get<number>(LAST_SCRUB_TIME_KEY);
const now = Date.now();

// If we have never scrubbed before, or if the last scrub was more than `throttleTime` ago,
// then scrub again.
if (lastScrubTime === undefined || now - lastScrubTime >= throttleTime) {
await ctx.globalState.update(LAST_SCRUB_TIME_KEY, now);

let scrubCount = 0; // total number of directories deleted
try {
counter?.increment();
void logger.log('Scrubbing query directory. Removing old queries.');
if (!(await fs.pathExists(queryDirectory))) {
void logger.log(`Cannot scrub. Query directory does not exist: ${queryDirectory}`);
return;
}

const baseNames = await fs.readdir(queryDirectory);
const errors: string[] = [];
for (const baseName of baseNames) {
const dir = path.join(queryDirectory, baseName);
const scrubResult = await scrubDirectory(dir, now, maxQueryTime);
if (scrubResult.errorMsg) {
errors.push(scrubResult.errorMsg);
}
if (scrubResult.deleted) {
scrubCount++;
}
}

if (errors.length) {
throw new Error(os.EOL + errors.join(os.EOL));
}
} catch (e) {
void logger.log(`Error while scrubbing queries: ${e}`);
} finally {
void logger.log(`Scrubbed ${scrubCount} old queries.`);
}
}
}

async function scrubDirectory(dir: string, now: number, maxQueryTime: number): Promise<{
errorMsg?: string,
deleted: boolean
}> {
const timestampFile = path.join(dir, 'timestamp');
try {
let deleted = true;
if (!(await fs.stat(dir)).isDirectory()) {
void logger.log(` ${dir} is not a directory. Deleting.`);
await fs.remove(dir);
} else if (!(await fs.pathExists(timestampFile))) {
void logger.log(` ${dir} has no timestamp file. Deleting.`);
await fs.remove(dir);
} else if (!(await fs.stat(timestampFile)).isFile()) {
void logger.log(` ${timestampFile} is not a file. Deleting.`);
await fs.remove(dir);
} else {
const timestampText = await fs.readFile(timestampFile, 'utf8');
const timestamp = parseInt(timestampText, 10);

if (Number.isNaN(timestamp)) {
void logger.log(` ${dir} has invalid timestamp '${timestampText}'. Deleting.`);
await fs.remove(dir);
} else if (now - timestamp > maxQueryTime) {
void logger.log(` ${dir} is older than ${maxQueryTime / 1000} seconds. Deleting.`);
await fs.remove(dir);
} else {
void logger.log(` ${dir} is not older than ${maxQueryTime / 1000} seconds. Keeping.`);
deleted = false;
}
}
return {
deleted
};
} catch (err) {
return {
errorMsg: ` Could not delete '${dir}': ${err}`,
deleted: false
};
}
}
Loading