Skip to content

Commit 7c5135d

Browse files
Dave Bartolomeoaeisenberg
authored andcommitted
Fix AST viewer for refactored language packs
Most of the languages have recently been refactored into separate library and query packs, with the contextual queries defined in the query pack. In the near future, these contextual queries will move to the library pack. Current CLI releases throw an error in `codeql resolve queries` when the extension tries to search the library pack for contextual queries. This change makes two related fixes: 1. If the queries are not found in the library pack, it then scans the corresponding standard query pack as a fallback. 2. It detects the problematic combination of CLI and packs, and avoids scanning the library pack at all in those cases. If no queries are found in the problematic scenario, the error message instructs the user to upgrade to the latest CLI version, instead of claiming that the language simply doesn't support the contextual queries yet. This change depends on CLI 2.6.1, which is being released soon, adding the `--allow-library-packs` option to `codeql resolve queries`. That PR is already open against the CLI.
1 parent cdd6738 commit 7c5135d

File tree

5 files changed

+156
-38
lines changed

5 files changed

+156
-38
lines changed

extensions/ql-vscode/src/cli.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -775,11 +775,15 @@ export class CodeQLCliServer implements Disposable {
775775
* the default CLI search path is used.
776776
* @returns A list of query files found.
777777
*/
778-
resolveQueriesInSuite(suite: string, additionalPacks: string[], searchPath?: string[]): Promise<string[]> {
778+
async resolveQueriesInSuite(suite: string, additionalPacks: string[], searchPath?: string[]): Promise<string[]> {
779779
const args = ['--additional-packs', additionalPacks.join(path.delimiter)];
780780
if (searchPath !== undefined) {
781781
args.push('--search-path', path.join(...searchPath));
782782
}
783+
if (await this.cliConstraints.supportsAllowLibraryPacksInResolveQueries()) {
784+
// All of our usage of `codeql resolve queries` needs to handle library packs.
785+
args.push('--allow-library-packs');
786+
}
783787
args.push(suite);
784788
return this.runJsonCodeQlCliCommand<string[]>(
785789
['resolve', 'queries'],
@@ -1064,6 +1068,12 @@ export class CliVersionConstraint {
10641068
*/
10651069
public static CLI_VERSION_WITH_DB_REGISTRATION = new SemVer('2.4.1');
10661070

1071+
/**
1072+
* CLI version where the `--allow-library-packs` option to `codeql resolve queries` was
1073+
* introduced.
1074+
*/
1075+
public static CLI_VERSION_WITH_ALLOW_LIBRARY_PACKS_IN_RESOLVE_QUERIES = new SemVer('2.6.1');
1076+
10671077
constructor(private readonly cli: CodeQLCliServer) {
10681078
/**/
10691079
}
@@ -1088,6 +1098,10 @@ export class CliVersionConstraint {
10881098
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_QLREF);
10891099
}
10901100

1101+
public async supportsAllowLibraryPacksInResolveQueries() {
1102+
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_ALLOW_LIBRARY_PACKS_IN_RESOLVE_QUERIES);
1103+
}
1104+
10911105
async supportsDatabaseRegistration() {
10921106
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_DB_REGISTRATION);
10931107
}

extensions/ql-vscode/src/contextual/queryResolver.ts

Lines changed: 82 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import {
1111
} from './keyType';
1212
import { CodeQLCliServer } from '../cli';
1313
import { DatabaseItem } from '../databases';
14+
import { QlPacksForLanguage } from '../helpers';
1415

15-
export async function qlpackOfDatabase(cli: CodeQLCliServer, db: DatabaseItem): Promise<string> {
16+
export async function qlpackOfDatabase(cli: CodeQLCliServer, db: DatabaseItem): Promise<QlPacksForLanguage> {
1617
if (db.contents === undefined) {
1718
throw new Error('Database is invalid and cannot infer QLPack.');
1819
}
@@ -21,28 +22,87 @@ export async function qlpackOfDatabase(cli: CodeQLCliServer, db: DatabaseItem):
2122
return await helpers.getQlPackForDbscheme(cli, dbscheme);
2223
}
2324

25+
/**
26+
* Finds the contextual queries with the specified key in a list of CodeQL packs.
27+
*
28+
* @param cli The CLI instance to use.
29+
* @param qlpacks The list of packs to search.
30+
* @param keyType The contextual query key of the query to search for.
31+
* @returns The found queries from the first pack in which any matching queries were found.
32+
*/
33+
async function resolveQueriesFromPacks(cli: CodeQLCliServer, qlpacks: string[], keyType: KeyType): Promise<string[]> {
34+
for (const qlpack of qlpacks) {
35+
const suiteFile = (await tmp.file({
36+
postfix: '.qls'
37+
})).path;
38+
const suiteYaml = {
39+
qlpack,
40+
include: {
41+
kind: kindOfKeyType(keyType),
42+
'tags contain': tagOfKeyType(keyType)
43+
}
44+
};
45+
await fs.writeFile(suiteFile, yaml.safeDump(suiteYaml), 'utf8');
2446

25-
export async function resolveQueries(cli: CodeQLCliServer, qlpack: string, keyType: KeyType): Promise<string[]> {
26-
const suiteFile = (await tmp.file({
27-
postfix: '.qls'
28-
})).path;
29-
const suiteYaml = {
30-
qlpack,
31-
include: {
32-
kind: kindOfKeyType(keyType),
33-
'tags contain': tagOfKeyType(keyType)
47+
const queries = await cli.resolveQueriesInSuite(suiteFile, helpers.getOnDiskWorkspaceFolders());
48+
if (queries.length > 0) {
49+
return queries;
3450
}
35-
};
36-
await fs.writeFile(suiteFile, yaml.safeDump(suiteYaml), 'utf8');
37-
38-
const queries = await cli.resolveQueriesInSuite(suiteFile, helpers.getOnDiskWorkspaceFolders());
39-
if (queries.length === 0) {
40-
void helpers.showAndLogErrorMessage(
41-
`No ${nameOfKeyType(keyType)} queries (tagged "${tagOfKeyType(keyType)}") could be found in the current library path. \
42-
Try upgrading the CodeQL libraries. If that doesn't work, then ${nameOfKeyType(keyType)} queries are not yet available \
43-
for this language.`
44-
);
45-
throw new Error(`Couldn't find any queries tagged ${tagOfKeyType(keyType)} for qlpack ${qlpack}`);
4651
}
47-
return queries;
52+
53+
return [];
54+
}
55+
56+
export async function resolveQueries(cli: CodeQLCliServer, qlpacks: QlPacksForLanguage, keyType: KeyType): Promise<string[]> {
57+
const cliCanHandleLibraryPack = await cli.cliConstraints.supportsAllowLibraryPacksInResolveQueries();
58+
const packsToSearch: string[] = [];
59+
let blameCli: boolean;
60+
61+
if (cliCanHandleLibraryPack) {
62+
// The CLI can handle both library packs and query packs, so search both packs in order.
63+
packsToSearch.push(qlpacks.dbschemePack);
64+
if (qlpacks.queryPack !== undefined) {
65+
packsToSearch.push(qlpacks.queryPack);
66+
}
67+
// If we don't find the query, it's because it's not there, not because the CLI was unable to
68+
// search the pack.
69+
blameCli = false;
70+
} else {
71+
// Older CLIs can't handle `codeql resolve queries` with a suite that references a library pack.
72+
if (qlpacks.dbschemePackIsLibraryPack) {
73+
if (qlpacks.queryPack !== undefined) {
74+
// Just search the query pack, because some older library/query releases still had the
75+
// contextual queries in the query pack.
76+
packsToSearch.push(qlpacks.queryPack);
77+
}
78+
// If we don't find it, it's because the CLI was unable to search the library pack that
79+
// actually contains the query. Blame any failure on the CLI, not the packs.
80+
blameCli = true;
81+
} else {
82+
// We have an old CLI, but the dbscheme pack is old enough that it's still a unified pack with
83+
// both libraries and queries. Just search that pack.
84+
packsToSearch.push(qlpacks.dbschemePack);
85+
// Any CLI should be able to search the single query pack, so if we don't find it, it's
86+
// because the language doesn't support it.
87+
blameCli = false;
88+
}
89+
}
90+
91+
const queries = await resolveQueriesFromPacks(cli, packsToSearch, keyType);
92+
if (queries.length > 0) {
93+
return queries;
94+
}
95+
96+
// No queries found. Determine the correct error message for the various scenarios.
97+
const errorMessage = blameCli ?
98+
`Your current version of the CodeQL CLI, '${(await cli.getVersion()).version}', \
99+
is unable to use contextual queries from recent versions of the standard CodeQL libraries. \
100+
Please upgrade to the latest version of the CodeQL CLI.`
101+
:
102+
`No ${nameOfKeyType(keyType)} queries (tagged "${tagOfKeyType(keyType)}") could be found in the current library path. \
103+
Try upgrading the CodeQL libraries. If that doesn't work, then ${nameOfKeyType(keyType)} queries are not yet available \
104+
for this language.`;
105+
106+
void helpers.showAndLogErrorMessage(errorMessage);
107+
throw new Error(`Couldn't find any queries tagged ${tagOfKeyType(keyType)} in any of the following packs: ${packsToSearch.join(', ')}.`);
48108
}

extensions/ql-vscode/src/contextual/templateProvider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,8 @@ export class TemplatePrintAstProvider {
175175
throw new Error('Can\'t infer database from the provided source.');
176176
}
177177

178-
const qlpack = await qlpackOfDatabase(this.cli, db);
179-
const queries = await resolveQueries(this.cli, qlpack, KeyType.PrintAstQuery);
178+
const qlpacks = await qlpackOfDatabase(this.cli, db);
179+
const queries = await resolveQueries(this.cli, qlpacks, KeyType.PrintAstQuery);
180180
if (queries.length > 1) {
181181
throw new Error('Found multiple Print AST queries. Can\'t continue');
182182
}

extensions/ql-vscode/src/helpers.ts

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
workspace,
1010
env
1111
} from 'vscode';
12-
import { CodeQLCliServer } from './cli';
12+
import { CodeQLCliServer, QlpacksInfo } from './cli';
1313
import { logger } from './logging';
1414

1515
/**
@@ -254,9 +254,55 @@ function createRateLimitedResult(): RateLimitedResult {
254254
};
255255
}
256256

257-
export async function getQlPackForDbscheme(cliServer: CodeQLCliServer, dbschemePath: string): Promise<string> {
257+
export interface QlPacksForLanguage {
258+
/** The name of the pack containing the dbscheme. */
259+
dbschemePack: string;
260+
/** `true` if `dbschemePack` is a library pack. */
261+
dbschemePackIsLibraryPack: boolean;
262+
/**
263+
* The name of the corresponding standard query pack.
264+
* Only defined if `dbschemePack` is a library pack.
265+
*/
266+
queryPack?: string;
267+
}
268+
269+
interface QlPackWithPath {
270+
packName: string;
271+
packDir: string | undefined;
272+
}
273+
274+
async function findDbschemePack(packs: QlPackWithPath[], dbschemePath: string): Promise<{ name: string; isLibraryPack: boolean; }> {
275+
for (const { packDir, packName } of packs) {
276+
if (packDir !== undefined) {
277+
const qlpack = yaml.safeLoad(await fs.readFile(path.join(packDir, 'qlpack.yml'), 'utf8')) as { dbscheme: string; library?: boolean; };
278+
if (qlpack.dbscheme !== undefined && path.basename(qlpack.dbscheme) === path.basename(dbschemePath)) {
279+
return {
280+
name: packName,
281+
isLibraryPack: qlpack.library === true
282+
};
283+
}
284+
}
285+
}
286+
throw new Error(`Could not find qlpack file for dbscheme ${dbschemePath}`);
287+
}
288+
289+
function findStandardQueryPack(qlpacks: QlpacksInfo, dbschemePackName: string): string | undefined {
290+
const matches = dbschemePackName.match(/^codeql\/(?<language>[a-z]+)-all$/);
291+
if (matches) {
292+
const queryPackName = `codeql/${matches.groups!.language}-queries`;
293+
if (qlpacks[queryPackName] !== undefined) {
294+
return queryPackName;
295+
}
296+
}
297+
298+
// Either the dbscheme pack didn't look like one where the queries might be in the query pack, or
299+
// no query pack was found in the search path. Either is OK.
300+
return undefined;
301+
}
302+
303+
export async function getQlPackForDbscheme(cliServer: CodeQLCliServer, dbschemePath: string): Promise<QlPacksForLanguage> {
258304
const qlpacks = await cliServer.resolveQlpacks(getOnDiskWorkspaceFolders());
259-
const packs: { packDir: string | undefined; packName: string }[] =
305+
const packs: QlPackWithPath[] =
260306
Object.entries(qlpacks).map(([packName, dirs]) => {
261307
if (dirs.length < 1) {
262308
void logger.log(`In getQlPackFor ${dbschemePath}, qlpack ${packName} has no directories`);
@@ -270,15 +316,13 @@ export async function getQlPackForDbscheme(cliServer: CodeQLCliServer, dbschemeP
270316
packDir: dirs[0]
271317
};
272318
});
273-
for (const { packDir, packName } of packs) {
274-
if (packDir !== undefined) {
275-
const qlpack = yaml.safeLoad(await fs.readFile(path.join(packDir, 'qlpack.yml'), 'utf8')) as { dbscheme: string };
276-
if (qlpack.dbscheme !== undefined && path.basename(qlpack.dbscheme) === path.basename(dbschemePath)) {
277-
return packName;
278-
}
279-
}
280-
}
281-
throw new Error(`Could not find qlpack file for dbscheme ${dbschemePath}`);
319+
const dbschemePack = await findDbschemePack(packs, dbschemePath);
320+
const queryPack = dbschemePack.isLibraryPack ? findStandardQueryPack(qlpacks, dbschemePack.name) : undefined;
321+
return {
322+
dbschemePack: dbschemePack.name,
323+
dbschemePackIsLibraryPack: dbschemePack.isLibraryPack,
324+
queryPack
325+
};
282326
}
283327

284328
export async function getPrimaryDbscheme(datasetFolder: string): Promise<string> {

extensions/ql-vscode/src/quick-query.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export async function displayQuickQuery(
110110

111111
const datasetFolder = await dbItem.getDatasetFolder(cliServer);
112112
const dbscheme = await getPrimaryDbscheme(datasetFolder);
113-
const qlpack = await getQlPackForDbscheme(cliServer, dbscheme);
113+
const qlpack = (await getQlPackForDbscheme(cliServer, dbscheme)).dbschemePack;
114114
const qlPackFile = path.join(queriesDir, 'qlpack.yml');
115115
const qlFile = path.join(queriesDir, QUICK_QUERY_QUERY_NAME);
116116
const shouldRewrite = await checkShouldRewrite(qlPackFile, qlpack);

0 commit comments

Comments
 (0)