From 15c3805ad37066c81f67c6da60af742f8313e636 Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Thu, 1 Feb 2024 12:01:40 +0100 Subject: [PATCH 1/2] Remove bundled Ruby queries This removes the bundled Ruby queries since these queries are in a CodeQL release now, so we should never find that the queries don't exist. --- .../src/model-editor/queries/index.ts | 6 +- .../src/model-editor/queries/ruby.ts | 351 ------------------ 2 files changed, 3 insertions(+), 354 deletions(-) delete mode 100644 extensions/ql-vscode/src/model-editor/queries/ruby.ts diff --git a/extensions/ql-vscode/src/model-editor/queries/index.ts b/extensions/ql-vscode/src/model-editor/queries/index.ts index 9f52c4ba27a..0a6e71d3406 100644 --- a/extensions/ql-vscode/src/model-editor/queries/index.ts +++ b/extensions/ql-vscode/src/model-editor/queries/index.ts @@ -1,7 +1,7 @@ -import { fetchExternalApisQuery as rubyFetchExternalApisQuery } from "./ruby"; import type { Query } from "./query"; -import { QueryLanguage } from "../../common/query-language"; +import type { QueryLanguage } from "../../common/query-language"; export const fetchExternalApiQueries: Partial> = { - [QueryLanguage.Ruby]: rubyFetchExternalApisQuery, + // Right now, there are no bundled queries. However, if we're adding a new + // language for the model editor, we can add the query here. }; diff --git a/extensions/ql-vscode/src/model-editor/queries/ruby.ts b/extensions/ql-vscode/src/model-editor/queries/ruby.ts deleted file mode 100644 index b7e558f13fa..00000000000 --- a/extensions/ql-vscode/src/model-editor/queries/ruby.ts +++ /dev/null @@ -1,351 +0,0 @@ -import type { Query } from "./query"; - -export const fetchExternalApisQuery: Query = { - applicationModeQuery: `/** - * @name Fetch endpoints for use in the model editor (application mode) - * @description A list of 3rd party endpoints (methods and attributes) used in the codebase. Excludes test and generated code. - * @kind table - * @id rb/utils/modeleditor/application-mode-endpoints - * @tags modeleditor endpoints application-mode - */ - -import codeql.ruby.AST - -// This query is empty as Application Mode is not yet supported for Ruby. -from - Call usage, string package, string type, string name, string parameters, boolean supported, - string namespace, string version, string supportedType, string classification -where none() -select usage, package, namespace, type, name, parameters, supported, namespace, version, - supportedType, classification -`, - frameworkModeQuery: `/** - * @name Fetch endpoints for use in the model editor (framework mode) - * @description A list of endpoints accessible (methods and attributes) for consumers of the library. Excludes test and generated code. - * @kind table - * @id rb/utils/modeleditor/framework-mode-endpoints - * @tags modeleditor endpoints framework-mode - */ - -import ruby -import ModelEditor - -from PublicEndpointFromSource endpoint -select endpoint, endpoint.getNamespace(), endpoint.getTypeName(), endpoint.getName(), - endpoint.getParameterTypes(), endpoint.getSupportedStatus(), endpoint.getFile().getBaseName(), - endpoint.getSupportedType() -`, - dependencies: { - "ModelEditor.qll": `/** Provides classes and predicates related to handling APIs for the VS Code extension. */ - -private import ruby -private import codeql.ruby.dataflow.FlowSummary -private import codeql.ruby.dataflow.internal.DataFlowPrivate -private import codeql.ruby.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl -private import codeql.ruby.dataflow.internal.FlowSummaryImplSpecific -private import codeql.ruby.frameworks.core.Gem -private import codeql.ruby.frameworks.data.ModelsAsData -private import codeql.ruby.frameworks.data.internal.ApiGraphModelsExtensions -private import queries.modeling.internal.Util as Util - -/** Holds if the given callable is not worth supporting. */ -private predicate isUninteresting(DataFlow::MethodNode c) { - c.getLocation().getFile() instanceof TestFile -} - -private predicate gemFileStep(Gem::GemSpec gem, Folder folder, int n) { - n = 0 and folder.getAFile() = gem.(File) - or - exists(Folder parent, int m | - gemFileStep(gem, parent, m) and - parent.getAFolder() = folder and - n = m + 1 - ) -} - -/** - * A callable method or accessor from either the Ruby Standard Library, a 3rd party library, or from the source. - */ -class Endpoint extends DataFlow::MethodNode { - Endpoint() { this.isPublic() and not isUninteresting(this) } - - File getFile() { result = this.getLocation().getFile() } - - string getName() { result = this.getMethodName() } - - /** - * Gets the namespace of this endpoint. - */ - bindingset[this] - string getNamespace() { - exists(Folder folder | folder = this.getFile().getParentContainer() | - // The nearest gemspec to this endpoint, if one exists - result = min(Gem::GemSpec g, int n | gemFileStep(g, folder, n) | g order by n).getName() - or - not gemFileStep(_, folder, _) and - result = "" - ) - } - - /** - * Gets the unbound type name of this endpoint. - */ - bindingset[this] - string getTypeName() { - result = - any(DataFlow::ModuleNode m | m.getOwnInstanceMethod(this.getMethodName()) = this) - .getQualifiedName() or - result = - any(DataFlow::ModuleNode m | m.getOwnSingletonMethod(this.getMethodName()) = this) - .getQualifiedName() + "!" - } - - /** - * Gets the parameter types of this endpoint. - */ - bindingset[this] - string getParameterTypes() { - // For now, return the names of postional and keyword parameters. We don't always have type information, so we can't return type names. - // We don't yet handle splat params or block params. - result = - "(" + - concat(string key, string value | - value = any(int i | i.toString() = key | this.asCallable().getParameter(i)).getName() - or - exists(DataFlow::ParameterNode param | - param = this.asCallable().getKeywordParameter(key) - | - value = key + ":" - ) - | - value, "," order by key - ) + ")" - } - - /** Holds if this API has a supported summary. */ - pragma[nomagic] - predicate hasSummary() { none() } - - /** Holds if this API is a known source. */ - pragma[nomagic] - abstract predicate isSource(); - - /** Holds if this API is a known sink. */ - pragma[nomagic] - abstract predicate isSink(); - - /** Holds if this API is a known neutral. */ - pragma[nomagic] - predicate isNeutral() { none() } - - /** - * Holds if this API is supported by existing CodeQL libraries, that is, it is either a - * recognized source, sink or neutral or it has a flow summary. - */ - predicate isSupported() { - this.hasSummary() or this.isSource() or this.isSink() or this.isNeutral() - } - - boolean getSupportedStatus() { if this.isSupported() then result = true else result = false } - - string getSupportedType() { - this.isSink() and result = "sink" - or - this.isSource() and result = "source" - or - this.hasSummary() and result = "summary" - or - this.isNeutral() and result = "neutral" - or - not this.isSupported() and result = "" - } -} - -string methodClassification(Call method) { - method.getFile() instanceof TestFile and result = "test" - or - not method.getFile() instanceof TestFile and - result = "source" -} - -class TestFile extends File { - TestFile() { - this.getRelativePath().regexpMatch(".*(test|spec).+") and - not this.getAbsolutePath().matches("%/ql/test/%") // allows our test cases to work - } -} - -/** - * A callable where there exists a MaD sink model that applies to it. - */ -class SinkCallable extends DataFlow::MethodNode { - SinkCallable() { - exists(string type, string path, string method | - method = path.regexpCapture("(Method\\\\[[^\\\\]]+\\\\]).*", 1) and - Util::pathToMethod(this, type, method) and - sinkModel(type, path, _) - ) - } -} - -/** - * A callable where there exists a MaD source model that applies to it. - */ -class SourceCallable extends DataFlow::CallableNode { - SourceCallable() { - exists(string type, string path, string method | - method = path.regexpCapture("(Method\\\\[[^\\\\]]+\\\\]).*", 1) and - Util::pathToMethod(this, type, method) and - sourceModel(type, path, _) - ) - } -} - -/** - * A class of effectively public callables from source code. - */ -class PublicEndpointFromSource extends Endpoint { - override predicate isSource() { this instanceof SourceCallable } - - override predicate isSink() { this instanceof SinkCallable } -} -`, - "queries/modeling/internal/Util.qll": `/** - * Contains utility methods and classes to assist with generating data extensions models. - */ - -private import ruby -private import codeql.ruby.ApiGraphs - -/** - * A file that is relevant in the context of library modeling. - * - * In practice, this means a file that is not part of test code. - */ -class RelevantFile extends File { - RelevantFile() { not this.getRelativePath().regexpMatch(".*/?test(case)?s?/.*") } -} - -/** - * Gets an access path of an argument corresponding to the given \`paramNode\`. - */ -string getArgumentPath(DataFlow::ParameterNode paramNode) { - paramNode.getLocation().getFile() instanceof RelevantFile and - exists(string paramSpecifier | - exists(Ast::Parameter param | - param = paramNode.asParameter() and - ( - paramSpecifier = param.getPosition().toString() - or - paramSpecifier = param.(Ast::KeywordParameter).getName() + ":" - or - param instanceof Ast::BlockParameter and - paramSpecifier = "block" - ) - ) - or - paramNode instanceof DataFlow::SelfParameterNode and paramSpecifier = "self" - | - result = "Argument[" + paramSpecifier + "]" - ) -} - -/** - * Holds if \`(type,path)\` evaluates to the given method, when evalauted from a client of the current library. - */ -predicate pathToMethod(DataFlow::MethodNode method, string type, string path) { - method.getLocation().getFile() instanceof RelevantFile and - exists(DataFlow::ModuleNode mod, string methodName | - method = mod.getOwnInstanceMethod(methodName) and - if methodName = "initialize" - then ( - type = mod.getQualifiedName() + "!" and - path = "Method[new]" - ) else ( - type = mod.getQualifiedName() and - path = "Method[" + methodName + "]" - ) - or - method = mod.getOwnSingletonMethod(methodName) and - type = mod.getQualifiedName() + "!" and - path = "Method[" + methodName + "]" - ) -} - -/** - * Gets any parameter to \`methodNode\`. This may be a positional, keyword, - * block, or self parameter. - */ -DataFlow::ParameterNode getAnyParameter(DataFlow::MethodNode methodNode) { - result = - [ - methodNode.getParameter(_), methodNode.getKeywordParameter(_), methodNode.getBlockParameter(), - methodNode.getSelfParameter() - ] -} - -private predicate pathToNodeBase(API::Node node, string type, string path, boolean isOutput) { - exists(DataFlow::MethodNode method, string prevPath | pathToMethod(method, type, prevPath) | - isOutput = true and - node = method.getAReturnNode().backtrack() and - path = prevPath + ".ReturnValue" and - not method.getMethodName() = "initialize" // ignore return value of initialize method - or - isOutput = false and - exists(DataFlow::ParameterNode paramNode | - paramNode = getAnyParameter(method) and - node = paramNode.track() - | - path = prevPath + "." + getArgumentPath(paramNode) - ) - ) -} - -private predicate pathToNodeRec( - API::Node node, string type, string path, boolean isOutput, int pathLength -) { - pathLength < 8 and - ( - pathToNodeBase(node, type, path, isOutput) and - pathLength = 1 - or - exists(API::Node prevNode, string prevPath, boolean prevIsOutput, int prevPathLength | - pathToNodeRec(prevNode, type, prevPath, prevIsOutput, prevPathLength) and - pathLength = prevPathLength + 1 - | - node = prevNode.getAnElement() and - path = prevPath + ".Element" and - isOutput = prevIsOutput - or - node = prevNode.getReturn() and - path = prevPath + ".ReturnValue" and - isOutput = prevIsOutput - or - prevIsOutput = false and - isOutput = true and - ( - exists(int n | - node = prevNode.getParameter(n) and - path = prevPath + ".Parameter[" + n + "]" - ) - or - exists(string name | - node = prevNode.getKeywordParameter(name) and - path = prevPath + ".Parameter[" + name + ":]" - ) - or - node = prevNode.getBlock() and - path = prevPath + ".Parameter[block]" - ) - ) - ) -} - -/** - * Holds if \`(type,path)\` evaluates to a value corresponding to \`node\`, when evaluated from a client of the current library. - */ -predicate pathToNode(API::Node node, string type, string path, boolean isOutput) { - pathToNodeRec(node, type, path, isOutput, _) -}`, - }, -}; From 4e967d81111432dfbc6e61e34060e5343981c6a5 Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Thu, 1 Feb 2024 12:10:19 +0100 Subject: [PATCH 2/2] Fix tests when there are no bundled queries --- .../src/model-editor/supported-languages.ts | 2 +- .../external-api-usage-query.test.ts | 18 +++--------------- .../model-editor/model-editor-queries.test.ts | 9 +++++++++ 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/extensions/ql-vscode/src/model-editor/supported-languages.ts b/extensions/ql-vscode/src/model-editor/supported-languages.ts index e1cff3e2da6..0eb67ae1cdc 100644 --- a/extensions/ql-vscode/src/model-editor/supported-languages.ts +++ b/extensions/ql-vscode/src/model-editor/supported-languages.ts @@ -5,7 +5,7 @@ import type { ModelConfig } from "../config"; * Languages that are always supported by the model editor. These languages * do not require a separate config setting to enable them. */ -const SUPPORTED_LANGUAGES: QueryLanguage[] = [ +export const SUPPORTED_LANGUAGES: QueryLanguage[] = [ QueryLanguage.Java, QueryLanguage.CSharp, ]; diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/external-api-usage-query.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/external-api-usage-query.test.ts index 6ff5af2c9e6..8bbda315a42 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/external-api-usage-query.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/external-api-usage-query.test.ts @@ -7,22 +7,20 @@ import type { DatabaseItem } from "../../../../src/databases/local-databases"; import { DatabaseKind } from "../../../../src/databases/local-databases"; import { dirSync, file } from "tmp-promise"; import { QueryResultType } from "../../../../src/query-server/messages"; -import { fetchExternalApiQueries } from "../../../../src/model-editor/queries"; import * as log from "../../../../src/common/logging/notifications"; import { RedactableError } from "../../../../src/common/errors"; import type { showAndLogExceptionWithTelemetry } from "../../../../src/common/logging"; -import type { QueryLanguage } from "../../../../src/common/query-language"; import { mockedObject, mockedUri } from "../../utils/mocking.helpers"; import { Mode } from "../../../../src/model-editor/shared/mode"; import { join } from "path"; import type { CodeQLCliServer } from "../../../../src/codeql-cli/cli"; import type { QueryRunner } from "../../../../src/query-server"; import { QueryOutputDir } from "../../../../src/local-queries/query-output-dir"; +import { SUPPORTED_LANGUAGES } from "../../../../src/model-editor/supported-languages"; describe("runModelEditorQueries", () => { - const language = Object.keys(fetchExternalApiQueries)[ - Math.floor(Math.random() * Object.keys(fetchExternalApiQueries).length) - ] as QueryLanguage; + const language = + SUPPORTED_LANGUAGES[Math.floor(Math.random() * SUPPORTED_LANGUAGES.length)]; const queryDir = dirSync({ unsafeCleanup: true }).name; @@ -33,11 +31,6 @@ describe("runModelEditorQueries", () => { const outputDir = new QueryOutputDir(join((await file()).path, "1")); - const query = fetchExternalApiQueries[language]; - if (!query) { - throw new Error(`No query found for language ${language}`); - } - const options = { cliServer: mockedObject({ resolveQlpacks: jest.fn().mockResolvedValue({ @@ -96,11 +89,6 @@ describe("runModelEditorQueries", () => { it("should run query for random language", async () => { const outputDir = new QueryOutputDir(join((await file()).path, "1")); - const query = fetchExternalApiQueries[language]; - if (!query) { - throw new Error(`No query found for language ${language}`); - } - const options = { cliServer: mockedObject({ resolveQlpacks: jest.fn().mockResolvedValue({ diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/model-editor-queries.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/model-editor-queries.test.ts index 22c293e5ec3..2045419f8f8 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/model-editor-queries.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/model-editor-queries.test.ts @@ -27,6 +27,15 @@ describe("setUpPack", () => { return { language: lang as QueryLanguage, query }; }); + if (languages.length === 0) { + // If we currently don't have any bundled queries, skip this test, but ensure there's still at least one test. + test("should not have any bundled queries", () => { + expect(languages).toHaveLength(0); + }); + + return; + } + describe.each(languages)("for language $language", ({ language, query }) => { test("should create the files when not found", async () => { const cliServer = mockedObject({