Copy more files into the synthetic variant analysis pack

Before this change and starting with CLI v2.14.3, if you wanted to run
a variant analysis query and the pack it is contained in has at least
one query that contains an extensible predicate, this would be an error.

The reason is that v2.14.3 introduced deep validation for data
extensions. We are not copying the query that contains an extensible
predicate to the synthetic pack we are generating. This means that deep
validation will fail because there will be extensions that target the
missing extensible predicate.

This change avoids the problem by copying any query files that contain
extensible predicates to the synthetic pack. It uses the internal
`generate extensible-predicate-metadata` command to discover which
query files contain extensible predicates and copies them.
This commit is contained in:
Andrew Eisenberg
2023-09-05 13:28:53 -07:00
parent 247ba4e8ea
commit d264bca304
3 changed files with 100 additions and 4 deletions

View File

@@ -123,6 +123,15 @@ export type ResolveExtensionsResult = {
};
};
export type GenerateExtensiblePredicateMetadataResult = {
// There are other properties in this object, but they are
// not relevant for its use in the extension, so we omit them.
extensible_predicates: Array<{
// pack relative path
path: string;
}>;
};
/**
* The expected output of `codeql resolve qlref`.
*/
@@ -1458,6 +1467,17 @@ export class CodeQLCliServer implements Disposable {
);
}
async generateExtensiblePredicateMetadata(
packRoot: string,
): Promise<GenerateExtensiblePredicateMetadataResult> {
return await this.runJsonCodeQlCliCommand(
["generate", "extensible-predicate-metadata"],
[packRoot],
"Generating extensible predicate metadata",
{ addFormat: false },
);
}
public async getVersion() {
if (!this._version) {
try {
@@ -1830,6 +1850,14 @@ export class CliVersionConstraint {
*/
public static CLI_VERSION_WITH_QUICK_EVAL_COUNT = new SemVer("2.13.3");
/**
* CLI version where the `generate extensible-predicate-metadata`
* command was implemented.
*/
public static CLI_VERSION_WITH_EXTENSIBLE_PREDICATE_METADATA = new SemVer(
"2.14.3",
);
/**
* CLI version where the langauge server supports visisbility change notifications.
*/
@@ -1916,4 +1944,10 @@ export class CliVersionConstraint {
CliVersionConstraint.CLI_VERSION_WITH_QUICK_EVAL_COUNT,
);
}
async supportsGenerateExtensiblePredicateMetadata() {
return this.isVersionAtLeast(
CliVersionConstraint.CLI_VERSION_WITH_EXTENSIBLE_PREDICATE_METADATA,
);
}
}

View File

@@ -189,6 +189,22 @@ async function copyExistingQueryPack(
) {
const toCopy = await cliServer.packPacklist(originalPackRoot, false);
// Also include query files that contain extensible predicates. These query files are not
// needed for the query to run, but they are needed for the query pack to pass deep validation
// of data extensions.
if (
await cliServer.cliConstraints.supportsGenerateExtensiblePredicateMetadata()
) {
const metadata = await cliServer.generateExtensiblePredicateMetadata(
originalPackRoot,
);
metadata.extensible_predicates.forEach((predicate) => {
if (predicate.path.endsWith(".ql")) {
toCopy.push(join(originalPackRoot, predicate.path));
}
});
}
[
// also copy the lock file (either new name or old name) and the query file itself. These are not included in the packlist.
...QLPACK_LOCK_FILENAMES.map((f) => join(originalPackRoot, f)),

View File

@@ -2,7 +2,7 @@ import { CancellationTokenSource, commands, Uri, window } from "vscode";
import { extLogger } from "../../../../src/common/logging/vscode";
import { setRemoteControllerRepo } from "../../../../src/config";
import * as ghApiClient from "../../../../src/variant-analysis/gh-api/gh-api-client";
import { join } from "path";
import { isAbsolute, join } from "path";
import { VariantAnalysisManager } from "../../../../src/variant-analysis/variant-analysis-manager";
import {
@@ -275,13 +275,52 @@ describe("Variant Analysis Manager", () => {
});
});
// Test running core java queries to ensure that we can compile queries in packs
// that contain queries with extensible predicates
it("should run a remote query that is part of the java pack", async () => {
if (
!(await cli.cliConstraints.supportsGenerateExtensiblePredicateMetadata())
) {
console.log(
`Skipping test because generating extensible predicate metadata was only introduced in CLI version ${CliVersionConstraint.CLI_VERSION_WITH_EXTENSIBLE_PREDICATE_METADATA}.`,
);
return;
}
if (!process.env.TEST_CODEQL_PATH) {
fail(
"TEST_CODEQL_PATH environment variable not set. It should point to the absolute path to a checkout of the codeql repository.",
);
}
const queryToRun =
"Security/CWE/CWE-020/ExternalAPIsUsedWithUntrustedData.ql";
const extraQuery = "Telemetry/ExtractorInformation.ql";
await doVariantAnalysisTest({
queryPath: join(
process.env.TEST_CODEQL_PATH,
"java/ql/src",
queryToRun,
),
expectedPackName: "codeql/java-queries",
filesThatExist: [queryToRun, extraQuery],
filesThatDoNotExist: [],
qlxFilesThatExist: [],
dependenciesToCheck: ["codeql/java-all"],
// Don't check the version since it will be the same version
checkVersion: false,
});
});
async function doVariantAnalysisTest({
queryPath,
expectedPackName,
filesThatExist,
qlxFilesThatExist,
filesThatDoNotExist,
dependenciesToCheck = ["codeql/javascript-all"],
dependenciesToCheck = ["codeql/java-all"],
checkVersion = true,
}: {
queryPath: string;
expectedPackName: string;
@@ -289,6 +328,7 @@ describe("Variant Analysis Manager", () => {
qlxFilesThatExist: string[];
filesThatDoNotExist: string[];
dependenciesToCheck?: string[];
checkVersion?: boolean;
}) {
const fileUri = getFile(queryPath);
await variantAnalysisManager.runVariantAnalysis(
@@ -339,7 +379,9 @@ describe("Variant Analysis Manager", () => {
packFS.fileContents(packFileName).toString("utf-8"),
);
expect(qlpackContents.name).toEqual(expectedPackName);
expect(qlpackContents.version).toEqual("0.0.0");
if (checkVersion) {
expect(qlpackContents.version).toEqual("0.0.0");
}
expect(qlpackContents.dependencies?.["codeql/javascript-all"]).toEqual(
"*",
);
@@ -357,7 +399,11 @@ describe("Variant Analysis Manager", () => {
}
function getFile(file: string): Uri {
return Uri.file(join(baseDir, file));
if (isAbsolute(file)) {
return Uri.file(file);
} else {
return Uri.file(join(baseDir, file));
}
}
});
});