diff --git a/extensions/ql-vscode/src/model-editor/bqrs.ts b/extensions/ql-vscode/src/model-editor/bqrs.ts index 136d693a1..45200da64 100644 --- a/extensions/ql-vscode/src/model-editor/bqrs.ts +++ b/extensions/ql-vscode/src/model-editor/bqrs.ts @@ -2,41 +2,72 @@ import { DecodedBqrsChunk } from "../common/bqrs-cli-types"; import { Call, CallClassification, Method } from "./method"; import { ModeledMethodType } from "./modeled-method"; import { parseLibraryFilename } from "./library"; +import { Mode } from "./shared/mode"; +import { ApplicationModeTuple, FrameworkModeTuple } from "./queries/query"; -export function decodeBqrsToMethods(chunk: DecodedBqrsChunk): Method[] { +export function decodeBqrsToMethods( + chunk: DecodedBqrsChunk, + mode: Mode, +): Method[] { const methodsByApiName = new Map(); chunk?.tuples.forEach((tuple) => { - const usage = tuple[0] as Call; - const signature = tuple[1] as string; - const supported = (tuple[2] as string) === "true"; - let library = tuple[4] as string; - let libraryVersion: string | undefined = tuple[5] as string; - const type = tuple[6] as ModeledMethodType; - const classification = tuple[8] as CallClassification; + let usage: Call; + let packageName: string; + let typeName: string; + let methodName: string; + let methodParameters: string; + let supported: boolean; + let library: string; + let libraryVersion: string | undefined; + let type: ModeledMethodType; + let classification: CallClassification; - const [packageWithType, methodDeclaration] = signature.split("#"); + if (mode === Mode.Application) { + [ + usage, + packageName, + typeName, + methodName, + methodParameters, + supported, + library, + libraryVersion, + type, + classification, + ] = tuple as ApplicationModeTuple; + } else { + [ + usage, + packageName, + typeName, + methodName, + methodParameters, + supported, + library, + type, + ] = tuple as FrameworkModeTuple; - const packageName = packageWithType.substring( - 0, - packageWithType.lastIndexOf("."), - ); - const typeName = packageWithType.substring( - packageWithType.lastIndexOf(".") + 1, - ); + classification = CallClassification.Unknown; + } - const methodName = methodDeclaration.substring( - 0, - methodDeclaration.indexOf("("), - ); - const methodParameters = methodDeclaration.substring( - methodDeclaration.indexOf("("), - ); + if (!methodParameters.startsWith("(")) { + // There's a difference in how the Java and C# queries return method parameters. In the C# query, the method + // parameters are returned without parentheses. In the Java query, the method parameters are returned with + // parentheses. Therefore, we'll just add them if we don't see them. + methodParameters = `(${methodParameters})`; + } + + const signature = `${packageName}.${typeName}#${methodName}${methodParameters}`; // For Java, we'll always get back a .jar file, and the library version may be bad because not all library authors // properly specify the version. Therefore, we'll always try to parse the name and version from the library filename // for Java. - if (library.endsWith(".jar") || libraryVersion === "") { + if ( + library.endsWith(".jar") || + libraryVersion === "" || + libraryVersion === undefined + ) { const { name, version } = parseLibraryFilename(library); library = name; if (version) { diff --git a/extensions/ql-vscode/src/model-editor/external-api-usage-queries.ts b/extensions/ql-vscode/src/model-editor/external-api-usage-queries.ts index 7bb85f644..d44d9bb11 100644 --- a/extensions/ql-vscode/src/model-editor/external-api-usage-queries.ts +++ b/extensions/ql-vscode/src/model-editor/external-api-usage-queries.ts @@ -132,7 +132,7 @@ export async function runExternalApiQueries( maxStep: externalApiQueriesProgressMaxStep, }); - return decodeBqrsToMethods(bqrsChunk); + return decodeBqrsToMethods(bqrsChunk, mode); } type GetResultsOptions = { @@ -160,7 +160,5 @@ export async function readQueryResults({ } function queryNameFromMode(mode: Mode): string { - return `FetchExternalApis${ - mode.charAt(0).toUpperCase() + mode.slice(1) - }Mode.ql`; + return `${mode.charAt(0).toUpperCase() + mode.slice(1)}ModeEndpoints.ql`; } diff --git a/extensions/ql-vscode/src/model-editor/queries/csharp.ts b/extensions/ql-vscode/src/model-editor/queries/csharp.ts index e882f84be..611019017 100644 --- a/extensions/ql-vscode/src/model-editor/queries/csharp.ts +++ b/extensions/ql-vscode/src/model-editor/queries/csharp.ts @@ -2,130 +2,152 @@ import { Query } from "./query"; export const fetchExternalApisQuery: Query = { applicationModeQuery: `/** - * @name Usage of APIs coming from external libraries - * @description A list of 3rd party APIs used in the codebase. - * @tags telemetry - * @kind problem - * @id cs/telemetry/fetch-external-apis + * @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 csharp/utils/modeleditor/application-mode-endpoints + * @tags modeleditor endpoints application-mode */ -private import csharp -private import AutomodelVsCode +import csharp +import ApplicationModeEndpointsQuery +import ModelEditor -class ExternalApi extends CallableMethod { - ExternalApi() { - this.isUnboundDeclaration() and - this.fromLibrary() and - this.(Modifiable).isEffectivelyPublic() - } -} +private Call aUsage(ExternalEndpoint api) { result.getTarget().getUnboundDeclaration() = api } -private Call aUsage(ExternalApi api) { result.getTarget().getUnboundDeclaration() = api } - -from - ExternalApi api, string apiName, boolean supported, Call usage, string type, string classification +from ExternalEndpoint endpoint, boolean supported, Call usage, string type, string classification where - apiName = api.getApiName() and - supported = isSupported(api) and - usage = aUsage(api) and - type = supportedType(api) and + supported = isSupported(endpoint) and + usage = aUsage(endpoint) and + type = supportedType(endpoint) and classification = methodClassification(usage) -select usage, apiName, supported.toString(), "supported", api.dllName(), api.dllVersion(), type, - "type", classification, "classification" +select usage, endpoint.getNamespace(), endpoint.getTypeName(), endpoint.getName(), + endpoint.getParameterTypes(), supported, endpoint.dllName(), endpoint.dllVersion(), type, + classification `, frameworkModeQuery: `/** - * @name Public methods - * @description A list of APIs callable by consumers. Excludes test and generated code. - * @tags telemetry - * @kind problem - * @id cs/telemetry/fetch-public-methods + * @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 csharp/utils/modeleditor/framework-mode-endpoints + * @tags modeleditor endpoints framework-mode */ -private import csharp -private import dotnet -private import semmle.code.csharp.frameworks.Test -private import AutomodelVsCode +import csharp +import FrameworkModeEndpointsQuery +import ModelEditor -class PublicMethod extends CallableMethod { - PublicMethod() { this.fromSource() and not this.getFile() instanceof TestFile } -} - -from PublicMethod publicMethod, string apiName, boolean supported, string type +from PublicEndpointFromSource endpoint, boolean supported, string type where - apiName = publicMethod.getApiName() and - supported = isSupported(publicMethod) and - type = supportedType(publicMethod) -select publicMethod, apiName, supported.toString(), "supported", - publicMethod.getFile().getBaseName(), "library", type, "type", "unknown", "classification" + supported = isSupported(endpoint) and + type = supportedType(endpoint) +select endpoint, endpoint.getNamespace(), endpoint.getTypeName(), endpoint.getName(), + endpoint.getParameterTypes(), supported, endpoint.getFile().getBaseName(), type `, dependencies: { - "AutomodelVsCode.qll": `/** Provides classes and predicates related to handling APIs for the VS Code extension. */ - -private import csharp -private import dotnet -private import semmle.code.csharp.dispatch.Dispatch + "ApplicationModeEndpointsQuery.qll": `private import csharp private import semmle.code.csharp.dataflow.ExternalFlow -private import semmle.code.csharp.dataflow.FlowSummary -private import semmle.code.csharp.dataflow.internal.DataFlowImplCommon as DataFlowImplCommon -private import semmle.code.csharp.dataflow.internal.DataFlowPrivate private import semmle.code.csharp.dataflow.internal.DataFlowDispatch as DataFlowDispatch -private import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl +private import semmle.code.csharp.dataflow.internal.DataFlowPrivate private import semmle.code.csharp.dataflow.internal.TaintTrackingPrivate -private import semmle.code.csharp.frameworks.Test private import semmle.code.csharp.security.dataflow.flowsources.Remote - -pragma[nomagic] -private predicate isTestNamespace(Namespace ns) { - ns.getFullName() - .matches([ - "NUnit.Framework%", "Xunit%", "Microsoft.VisualStudio.TestTools.UnitTesting%", "Moq%" - ]) -} +private import ModelEditor /** - * A test library. + * A class of effectively public callables in library code. */ -class TestLibrary extends RefType { - TestLibrary() { isTestNamespace(this.getNamespace()) } +class ExternalEndpoint extends Endpoint { + ExternalEndpoint() { this.fromLibrary() } + + /** Gets a node that is an input to a call to this API. */ + private ArgumentNode getAnInput() { + result + .getCall() + .(DataFlowDispatch::NonDelegateDataFlowCall) + .getATarget(_) + .getUnboundDeclaration() = this + } + + /** Gets a node that is an output from a call to this API. */ + private DataFlow::Node getAnOutput() { + exists(Call c, DataFlowDispatch::NonDelegateDataFlowCall dc | + dc.getDispatchCall().getCall() = c and + c.getTarget().getUnboundDeclaration() = this + | + result = DataFlowDispatch::getAnOutNode(dc, _) + ) + } + + override predicate hasSummary() { + Endpoint.super.hasSummary() + or + defaultAdditionalTaintStep(this.getAnInput(), _) + } + + override predicate isSource() { + this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _) + } + + override predicate isSink() { sinkNode(this.getAnInput(), _) } } +`, + "FrameworkModeEndpointsQuery.qll": `private import csharp +private import semmle.code.csharp.dataflow.ExternalFlow +private import semmle.code.csharp.frameworks.Test +private import ModelEditor + +/** + * A class of effectively public callables from source code. + */ +class PublicEndpointFromSource extends Endpoint { + PublicEndpointFromSource() { this.fromSource() and not this.getFile() instanceof TestFile } + + override predicate isSource() { this instanceof SourceCallable } + + override predicate isSink() { this instanceof SinkCallable } +}`, + "ModelEditor.qll": `/** Provides classes and predicates related to handling APIs for the VS Code extension. */ + +private import csharp +private import semmle.code.csharp.dataflow.FlowSummary +private import semmle.code.csharp.dataflow.internal.DataFlowPrivate +private import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl +private import semmle.code.csharp.frameworks.Test /** Holds if the given callable is not worth supporting. */ -private predicate isUninteresting(DotNet::Declaration c) { +private predicate isUninteresting(Callable c) { c.getDeclaringType() instanceof TestLibrary or c.(Constructor).isParameterless() or c.getDeclaringType() instanceof AnonymousClass } /** - * An callable method from either the C# Standard Library, a 3rd party library, or from the source. + * A callable method or accessor from either the C# Standard Library, a 3rd party library, or from the source. */ -class CallableMethod extends DotNet::Declaration { - CallableMethod() { - this.(Modifiable).isEffectivelyPublic() and - not isUninteresting(this) +class Endpoint extends Callable { + Endpoint() { + [this.(Modifiable), this.(Accessor).getDeclaration()].isEffectivelyPublic() and + not isUninteresting(this) and + this.isUnboundDeclaration() } /** - * Gets the unbound type, name and parameter types of this API. - */ - bindingset[this] - private string getSignature() { - result = - nestedName(this.getDeclaringType().getUnboundDeclaration()) + "#" + this.getName() + "(" + - parameterQualifiedTypeNamesToString(this) + ")" - } - - /** - * Gets the namespace of this API. + * Gets the namespace of this endpoint. */ bindingset[this] string getNamespace() { this.getDeclaringType().hasQualifiedName(result, _) } /** - * Gets the namespace and signature of this API. + * Gets the unbound type name of this endpoint. */ bindingset[this] - string getApiName() { result = this.getNamespace() + "." + this.getSignature() } + string getTypeName() { result = nestedName(this.getDeclaringType().getUnboundDeclaration()) } + + /** + * Gets the parameter types of this endpoint. + */ + bindingset[this] + string getParameterTypes() { result = parameterQualifiedTypeNamesToString(this) } private string getDllName() { result = this.getLocation().(Assembly).getName() } @@ -143,44 +165,17 @@ class CallableMethod extends DotNet::Declaration { not exists(this.getDllVersion()) and result = "" } - /** Gets a node that is an input to a call to this API. */ - private ArgumentNode getAnInput() { - result - .getCall() - .(DataFlowDispatch::NonDelegateDataFlowCall) - .getATarget(_) - .getUnboundDeclaration() = this - } - - /** Gets a node that is an output from a call to this API. */ - private DataFlow::Node getAnOutput() { - exists( - Call c, DataFlowDispatch::NonDelegateDataFlowCall dc, DataFlowImplCommon::ReturnKindExt ret - | - dc.getDispatchCall().getCall() = c and - c.getTarget().getUnboundDeclaration() = this - | - result = ret.getAnOutNode(dc) - ) - } - /** Holds if this API has a supported summary. */ pragma[nomagic] - predicate hasSummary() { - this instanceof SummarizedCallable - or - defaultAdditionalTaintStep(this.getAnInput(), _) - } + predicate hasSummary() { this instanceof SummarizedCallable } /** Holds if this API is a known source. */ pragma[nomagic] - predicate isSource() { - this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _) - } + abstract predicate isSource(); /** Holds if this API is a known sink. */ pragma[nomagic] - predicate isSink() { sinkNode(this.getAnInput(), _) } + abstract predicate isSink(); /** Holds if this API is a known neutral. */ pragma[nomagic] @@ -195,23 +190,20 @@ class CallableMethod extends DotNet::Declaration { } } -boolean isSupported(CallableMethod callableMethod) { - callableMethod.isSupported() and result = true - or - not callableMethod.isSupported() and - result = false +boolean isSupported(Endpoint endpoint) { + if endpoint.isSupported() then result = true else result = false } -string supportedType(CallableMethod method) { - method.isSink() and result = "sink" +string supportedType(Endpoint endpoint) { + endpoint.isSink() and result = "sink" or - method.isSource() and result = "source" + endpoint.isSource() and result = "source" or - method.hasSummary() and result = "summary" + endpoint.hasSummary() and result = "summary" or - method.isNeutral() and result = "neutral" + endpoint.isNeutral() and result = "neutral" or - not method.isSupported() and result = "" + not endpoint.isSupported() and result = "" } string methodClassification(Call method) { @@ -222,18 +214,51 @@ string methodClassification(Call method) { } /** - * Gets the nested name of the declaration. + * Gets the nested name of the type \`t\`. * - * If the declaration is not a nested type, the result is the same as \`getName()\`. + * If the type is not a nested type, the result is the same as \`getName()\`. * Otherwise the name of the nested type is prefixed with a \`+\` and appended to * the name of the enclosing type, which might be a nested type as well. */ -private string nestedName(Declaration declaration) { - not exists(declaration.getDeclaringType().getUnboundDeclaration()) and - result = declaration.getName() +private string nestedName(Type t) { + not exists(t.getDeclaringType().getUnboundDeclaration()) and + result = t.getName() or - nestedName(declaration.getDeclaringType().getUnboundDeclaration()) + "+" + declaration.getName() = - result + nestedName(t.getDeclaringType().getUnboundDeclaration()) + "+" + t.getName() = result +} + +// Temporary copy of csharp/ql/src/Telemetry/TestLibrary.qll + +pragma[nomagic] +private predicate isTestNamespace(Namespace ns) { + ns.getFullName() + .matches([ + "NUnit.Framework%", "Xunit%", "Microsoft.VisualStudio.TestTools.UnitTesting%", "Moq%" + ]) +} + +/** + * A test library. + */ +class TestLibrary extends RefType { + TestLibrary() { isTestNamespace(this.getNamespace()) } +} + +// Temporary copy of csharp/ql/lib/semmle/code/csharp/dataflow/ExternalFlow.qll +private import semmle.code.csharp.dataflow.internal.FlowSummaryImplSpecific + +/** + * A callable where there exists a MaD sink model that applies to it. + */ +class SinkCallable extends Callable { + SinkCallable() { sinkElement(this, _, _, _) } +} + +/** + * A callable where there exists a MaD source model that applies to it. + */ +class SourceCallable extends Callable { + SourceCallable() { sourceElement(this, _, _, _) } } `, }, diff --git a/extensions/ql-vscode/src/model-editor/queries/java.ts b/extensions/ql-vscode/src/model-editor/queries/java.ts index b08f40b2c..c40a79887 100644 --- a/extensions/ql-vscode/src/model-editor/queries/java.ts +++ b/extensions/ql-vscode/src/model-editor/queries/java.ts @@ -2,66 +2,113 @@ import { Query } from "./query"; export const fetchExternalApisQuery: Query = { applicationModeQuery: `/** - * @name Usage of APIs coming from external libraries - * @description A list of 3rd party APIs used in the codebase. Excludes test and generated code. - * @tags telemetry - * @kind problem - * @id java/telemetry/fetch-external-apis + * @name Fetch endpoints for use in the model editor (application mode) + * @description A list of 3rd party endpoints (methods) used in the codebase. Excludes test and generated code. + * @kind table + * @id java/utils/modeleditor/application-mode-endpoints + * @tags modeleditor endpoints application-mode */ -import java -import AutomodelVsCode - -class ExternalApi extends CallableMethod { - ExternalApi() { not this.fromSource() } -} - -private Call aUsage(ExternalApi api) { result.getCallee().getSourceDeclaration() = api } - -from - ExternalApi externalApi, string apiName, boolean supported, Call usage, string type, - string classification -where - apiName = externalApi.getApiName() and - supported = isSupported(externalApi) and - usage = aUsage(externalApi) and - type = supportedType(externalApi) and - classification = methodClassification(usage) -select usage, apiName, supported.toString(), "supported", externalApi.jarContainer(), - externalApi.jarVersion(), type, "type", classification, "classification" -`, - frameworkModeQuery: `/** - * @name Public methods - * @description A list of APIs callable by consumers. Excludes test and generated code. - * @tags telemetry - * @kind problem - * @id java/telemetry/fetch-public-methods - */ - -import java -import AutomodelVsCode - -class PublicMethodFromSource extends CallableMethod, ModelApi { } - -from PublicMethodFromSource publicMethod, string apiName, boolean supported, string type -where - apiName = publicMethod.getApiName() and - supported = isSupported(publicMethod) and - type = supportedType(publicMethod) -select publicMethod, apiName, supported.toString(), "supported", - publicMethod.getCompilationUnit().getParentContainer().getBaseName(), "library", type, "type", - "unknown", "classification" -`, - dependencies: { - "AutomodelVsCode.qll": `/** Provides classes and predicates related to handling APIs for the VS Code extension. */ - private import java -private import semmle.code.java.dataflow.DataFlow +private import ApplicationModeEndpointsQuery +private import ModelEditor + +private Call aUsage(ExternalEndpoint endpoint) { + result.getCallee().getSourceDeclaration() = endpoint +} + +from ExternalEndpoint endpoint, boolean supported, Call usage, string type, string classification +where + supported = isSupported(endpoint) and + usage = aUsage(endpoint) and + type = supportedType(endpoint) and + classification = usageClassification(usage) +select usage, endpoint.getPackageName(), endpoint.getTypeName(), endpoint.getName(), + endpoint.getParameterTypes(), supported, endpoint.jarContainer(), endpoint.jarVersion(), type, + classification +`, + frameworkModeQuery: `/** + * @name Fetch endpoints for use in the model editor (framework mode) + * @description A list of endpoints accessible (methods) for consumers of the library. Excludes test and generated code. + * @kind table + * @id java/utils/modeleditor/framework-mode-endpoints + * @tags modeleditor endpoints framework-mode + */ + +private import java +private import FrameworkModeEndpointsQuery +private import ModelEditor + +from PublicEndpointFromSource endpoint, boolean supported, string type +where + supported = isSupported(endpoint) and + type = supportedType(endpoint) +select endpoint, endpoint.getPackageName(), endpoint.getTypeName(), endpoint.getName(), + endpoint.getParameterTypes(), supported, + endpoint.getCompilationUnit().getParentContainer().getBaseName(), type +`, + dependencies: { + "ApplicationModeEndpointsQuery.qll": `private import java private import semmle.code.java.dataflow.ExternalFlow private import semmle.code.java.dataflow.FlowSources -private import semmle.code.java.dataflow.FlowSummary private import semmle.code.java.dataflow.internal.DataFlowPrivate -private import semmle.code.java.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl +private import ModelEditor + +/** + * A class of effectively public callables in library code. + */ +class ExternalEndpoint extends Endpoint { + ExternalEndpoint() { not this.fromSource() } + + /** Gets a node that is an input to a call to this API. */ + private DataFlow::Node getAnInput() { + exists(Call call | call.getCallee().getSourceDeclaration() = this | + result.asExpr().(Argument).getCall() = call or + result.(ArgumentNode).getCall().asCall() = call + ) + } + + /** Gets a node that is an output from a call to this API. */ + private DataFlow::Node getAnOutput() { + exists(Call call | call.getCallee().getSourceDeclaration() = this | + result.asExpr() = call or + result.(DataFlow::PostUpdateNode).getPreUpdateNode().(ArgumentNode).getCall().asCall() = call + ) + } + + override predicate hasSummary() { + Endpoint.super.hasSummary() + or + TaintTracking::localAdditionalTaintStep(this.getAnInput(), _) + } + + override predicate isSource() { + this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _) + } + + override predicate isSink() { sinkNode(this.getAnInput(), _) } +} +`, + "FrameworkModeEndpointsQuery.qll": `private import java +private import semmle.code.java.dataflow.internal.DataFlowPrivate +private import semmle.code.java.dataflow.internal.FlowSummaryImplSpecific +private import semmle.code.java.dataflow.internal.ModelExclusions +private import ModelEditor + +/** + * A class of effectively public callables from source code. + */ +class PublicEndpointFromSource extends Endpoint, ModelApi { + override predicate isSource() { sourceElement(this, _, _, _) } + + override predicate isSink() { sinkElement(this, _, _, _) } +} +`, + "ModelEditor.qll": `/** Provides classes and predicates related to handling APIs for the VS Code extension. */ + +private import java +private import semmle.code.java.dataflow.ExternalFlow +private import semmle.code.java.dataflow.FlowSummary private import semmle.code.java.dataflow.TaintTracking private import semmle.code.java.dataflow.internal.ModelExclusions @@ -75,17 +122,23 @@ private predicate isUninteresting(Callable c) { /** * A callable method from either the Standard Library, a 3rd party library or from the source. */ -class CallableMethod extends Callable { - CallableMethod() { not isUninteresting(this) } +class Endpoint extends Callable { + Endpoint() { not isUninteresting(this) } /** - * Gets information about the external API in the form expected by the MaD modeling framework. + * Gets the package name of this endpoint. */ - string getApiName() { - result = - this.getDeclaringType().getPackage() + "." + this.getDeclaringType().nestedName() + "#" + - this.getName() + paramsString(this) - } + string getPackageName() { result = this.getDeclaringType().getPackage().getName() } + + /** + * Gets the type name of this endpoint. + */ + string getTypeName() { result = this.getDeclaringType().nestedName() } + + /** + * Gets the parameter types of this endpoint. + */ + string getParameterTypes() { result = paramsString(this) } private string getJarName() { result = this.getCompilationUnit().getParentContainer*().(JarFile).getBaseName() @@ -113,43 +166,23 @@ class CallableMethod extends Callable { not exists(this.getJarVersion()) and result = "" } - /** Gets a node that is an input to a call to this API. */ - private DataFlow::Node getAnInput() { - exists(Call call | call.getCallee().getSourceDeclaration() = this | - result.asExpr().(Argument).getCall() = call or - result.(ArgumentNode).getCall().asCall() = call - ) - } - - /** Gets a node that is an output from a call to this API. */ - private DataFlow::Node getAnOutput() { - exists(Call call | call.getCallee().getSourceDeclaration() = this | - result.asExpr() = call or - result.(DataFlow::PostUpdateNode).getPreUpdateNode().(ArgumentNode).getCall().asCall() = call - ) - } - /** Holds if this API has a supported summary. */ pragma[nomagic] - predicate hasSummary() { - this = any(SummarizedCallable sc).asCallable() or - TaintTracking::localAdditionalTaintStep(this.getAnInput(), _) - } + predicate hasSummary() { this = any(SummarizedCallable sc).asCallable() } + /** Holds if this API is a known source. */ pragma[nomagic] - predicate isSource() { - this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _) - } + abstract predicate isSource(); /** Holds if this API is a known sink. */ pragma[nomagic] - predicate isSink() { sinkNode(this.getAnInput(), _) } + abstract predicate isSink(); /** Holds if this API is a known neutral. */ pragma[nomagic] predicate isNeutral() { - exists(string namespace, string type, string name, string signature, string kind, string provenance | - neutralModel(namespace, type, name, signature, kind, provenance) and + exists(string namespace, string type, string name, string signature | + neutralModel(namespace, type, name, signature, _, _) and this = interpretElement(namespace, type, false, name, signature, "") ) } @@ -163,108 +196,38 @@ class CallableMethod extends Callable { } } -boolean isSupported(CallableMethod method) { - method.isSupported() and result = true +boolean isSupported(Endpoint endpoint) { + endpoint.isSupported() and result = true or - not method.isSupported() and result = false + not endpoint.isSupported() and result = false } -string supportedType(CallableMethod method) { - method.isSink() and result = "sink" +string supportedType(Endpoint endpoint) { + endpoint.isSink() and result = "sink" or - method.isSource() and result = "source" + endpoint.isSource() and result = "source" or - method.hasSummary() and result = "summary" + endpoint.hasSummary() and result = "summary" or - method.isNeutral() and result = "neutral" + endpoint.isNeutral() and result = "neutral" or - not method.isSupported() and result = "" + not endpoint.isSupported() and result = "" } -string methodClassification(Call method) { - isInTestFile(method.getLocation().getFile()) and result = "test" +string usageClassification(Call usage) { + isInTestFile(usage.getLocation().getFile()) and result = "test" or - method.getFile() instanceof GeneratedFile and result = "generated" + usage.getFile() instanceof GeneratedFile and result = "generated" or - not isInTestFile(method.getLocation().getFile()) and - not method.getFile() instanceof GeneratedFile and + not isInTestFile(usage.getLocation().getFile()) and + not usage.getFile() instanceof GeneratedFile and result = "source" } -// The below is a copy of https://github.com/github/codeql/blob/249f9f863db1e94e3c46ca85b49fb0ec32f8ca92/java/ql/lib/semmle/code/java/dataflow/internal/ModelExclusions.qll -// to avoid the use of internal modules. -/** Holds if the given package \`p\` is a test package. */ -pragma[nomagic] -private predicate isTestPackage(Package p) { - p.getName() - .matches([ - "org.junit%", "junit.%", "org.mockito%", "org.assertj%", - "com.github.tomakehurst.wiremock%", "org.hamcrest%", "org.springframework.test.%", - "org.springframework.mock.%", "org.springframework.boot.test.%", "reactor.test%", - "org.xmlunit%", "org.testcontainers.%", "org.opentest4j%", "org.mockserver%", - "org.powermock%", "org.skyscreamer.jsonassert%", "org.rnorth.visibleassertions", - "org.openqa.selenium%", "com.gargoylesoftware.htmlunit%", "org.jboss.arquillian.testng%", - "org.testng%" - ]) -} - -/** - * A test library. - */ -class TestLibrary extends RefType { - TestLibrary() { isTestPackage(this.getPackage()) } -} - -/** Holds if the given file is a test file. */ -private predicate isInTestFile(File file) { +// Temporarily copied from java/ql/lib/semmle/code/java/dataflow/internal/ModelExclusions.qll +predicate isInTestFile(File file) { file.getAbsolutePath().matches(["%/test/%", "%/guava-tests/%", "%/guava-testlib/%"]) and - not file.getAbsolutePath().matches("%/ql/test/%") // allows our test cases to work -} - -/** Holds if the given compilation unit's package is a JDK internal. */ -private predicate isJdkInternal(CompilationUnit cu) { - cu.getPackage().getName().matches("org.graalvm%") or - cu.getPackage().getName().matches("com.sun%") or - cu.getPackage().getName().matches("sun%") or - cu.getPackage().getName().matches("jdk%") or - cu.getPackage().getName().matches("java2d%") or - cu.getPackage().getName().matches("build.tools%") or - cu.getPackage().getName().matches("propertiesparser%") or - cu.getPackage().getName().matches("org.jcp%") or - cu.getPackage().getName().matches("org.w3c%") or - cu.getPackage().getName().matches("org.ietf.jgss%") or - cu.getPackage().getName().matches("org.xml.sax%") or - cu.getPackage().getName().matches("com.oracle%") or - cu.getPackage().getName().matches("org.omg%") or - cu.getPackage().getName().matches("org.relaxng%") or - cu.getPackage().getName() = "compileproperties" or - cu.getPackage().getName() = "transparentruler" or - cu.getPackage().getName() = "genstubs" or - cu.getPackage().getName() = "netscape.javascript" or - cu.getPackage().getName() = "" -} - -/** Holds if the given callable is not worth modeling. */ -predicate isUninterestingForModels(Callable c) { - isInTestFile(c.getCompilationUnit().getFile()) or - isJdkInternal(c.getCompilationUnit()) or - c instanceof MainMethod or - c instanceof StaticInitializer or - exists(FunctionalExpr funcExpr | c = funcExpr.asMethod()) or - c.getDeclaringType() instanceof TestLibrary or - c.(Constructor).isParameterless() -} - -/** - * A class that represents all callables for which we might be - * interested in having a MaD model. - */ -class ModelApi extends SrcCallable { - ModelApi() { - this.fromSource() and - this.isEffectivelyPublic() and - not isUninterestingForModels(this) - } + not file.getAbsolutePath().matches(["%/ql/test/%", "%/ql/automodel/test/%"]) // allows our test cases to work } `, }, diff --git a/extensions/ql-vscode/src/model-editor/queries/query.ts b/extensions/ql-vscode/src/model-editor/queries/query.ts index e78e72ed6..04abf0e56 100644 --- a/extensions/ql-vscode/src/model-editor/queries/query.ts +++ b/extensions/ql-vscode/src/model-editor/queries/query.ts @@ -1,18 +1,21 @@ +import { Call, CallClassification } from "../method"; +import { ModeledMethodType } from "../modeled-method"; + export type Query = { /** * The application query. * * It should select all usages of external APIs, and return the following result pattern: * - usage: the usage of the external API. This is an entity. - * - apiName: the name of the external API. This is a string. - * - supported: whether the external API is modeled. This should be a string representation of a boolean to satify the result pattern for a problem query. - * - "supported": a string literal. This is required to make the query a valid problem query. + * - packageName: the package name of the external API. This is a string. + * - typeName: the type name of the external API. This is a string. + * - methodName: the method name of the external API. This is a string. + * - methodParameters: the parameters of the external API. This is a string. + * - supported: whether the external API is modeled. This is a boolean. * - libraryName: the name of the library that contains the external API. This is a string and usually the basename of a file. * - libraryVersion: the version of the library that contains the external API. This is a string and can be empty if the version cannot be determined. * - type: the modeled kind of the method, either "sink", "source", "summary", or "neutral" - * - "type": a string literal. This is required to make the query a valid problem query. * - classification: the classification of the use of the method, either "source", "test", "generated", or "unknown" - * - "classification: a string literal. This is required to make the query a valid problem query. */ applicationModeQuery: string; /** @@ -21,18 +24,40 @@ export type Query = { * It should select all methods that are callable by applications, which is usually all public methods (and constructors). * The result pattern should be as follows: * - method: the method that is callable by applications. This is an entity. - * - apiName: the name of the external API. This is a string. + * - packageName: the package name of the method. This is a string. + * - typeName: the type name of the method. This is a string. + * - methodName: the method name of the method. This is a string. + * - methodParameters: the parameters of the method. This is a string. * - supported: whether this method is modeled. This should be a string representation of a boolean to satify the result pattern for a problem query. - * - "supported": a string literal. This is required to make the query a valid problem query. - * - libraryName: an arbitrary string. This is required to make it match the structure of the application query. - * - libraryVersion: an arbitrary string. This is required to make it match the structure of the application query. + * - libraryName: the name of the file or library that contains the method. This is a string and usually the basename of a file. * - type: the modeled kind of the method, either "sink", "source", "summary", or "neutral" - * - "type": a string literal. This is required to make the query a valid problem query. - * - "unknown": a string literal. This is required to make it match the structure of the application query. - * - "classification: a string literal. This is required to make the query a valid problem query. */ frameworkModeQuery: string; dependencies?: { [filename: string]: string; }; }; + +export type ApplicationModeTuple = [ + Call, + string, + string, + string, + string, + boolean, + string, + string, + ModeledMethodType, + CallClassification, +]; + +export type FrameworkModeTuple = [ + Call, + string, + string, + string, + string, + boolean, + string, + ModeledMethodType, +]; diff --git a/extensions/ql-vscode/test/unit-tests/model-editor/bqrs.test.ts b/extensions/ql-vscode/test/unit-tests/model-editor/bqrs.test.ts index 6520b7582..8fade3e93 100644 --- a/extensions/ql-vscode/test/unit-tests/model-editor/bqrs.test.ts +++ b/extensions/ql-vscode/test/unit-tests/model-editor/bqrs.test.ts @@ -1,442 +1,692 @@ import { decodeBqrsToMethods } from "../../../src/model-editor/bqrs"; import { DecodedBqrsChunk } from "../../../src/common/bqrs-cli-types"; import { CallClassification } from "../../../src/model-editor/method"; +import { Mode } from "../../../src/model-editor/shared/mode"; describe("decodeBqrsToMethods", () => { - const chunk: DecodedBqrsChunk = { - columns: [ - { name: "usage", kind: "Entity" }, - { name: "apiName", kind: "String" }, - { kind: "String" }, - { kind: "String" }, - { kind: "String" }, - { kind: "String" }, - { name: "type", kind: "String" }, - { kind: "String" }, - { name: "classification", kind: "String" }, - { kind: "String" }, - ], - tuples: [ - [ - { - label: "println(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", - startLine: 29, - startColumn: 9, - endLine: 29, - endColumn: 49, - }, - }, - "java.io.PrintStream#println(String)", - "true", - "supported", - "rt.jar", - "", - "sink", - "type", - "source", - "classification", - ], - [ - { - label: "run(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/Sql2oExampleApplication.java", - startLine: 9, - startColumn: 9, - endLine: 9, - endColumn: 66, - }, - }, - "org.springframework.boot.SpringApplication#run(Class,String[])", - "false", - "supported", - "spring-boot-3.0.2.jar", - "", - "none", - "type", - "source", - "classification", - ], - [ - { - label: "createQuery(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", - startLine: 15, - startColumn: 13, - endLine: 15, - endColumn: 56, - }, - }, - "org.sql2o.Connection#createQuery(String)", - "true", - "supported", - "sql2o-1.6.0.jar", - "", - "sink", - "type", - "source", - "classification", - ], - [ - { - label: "createQuery(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", - startLine: 26, - startColumn: 13, - endLine: 26, - endColumn: 39, - }, - }, - "org.sql2o.Connection#createQuery(String)", - "true", - "supported", - "sql2o-1.6.0.jar", - "", - "sink", - "type", - "source", - "classification", - ], - [ - { - label: "executeScalar(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", - startLine: 15, - startColumn: 13, - endLine: 15, - endColumn: 85, - }, - }, - "org.sql2o.Query#executeScalar(Class)", - "true", - "supported", - "sql2o-1.6.0.jar", - "", - "sink", - "type", - "source", - "classification", - ], - [ - { - label: "executeScalar(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", - startLine: 26, - startColumn: 13, - endLine: 26, - endColumn: 68, - }, - }, - "org.sql2o.Query#executeScalar(Class)", - "true", - "supported", - "sql2o-1.6.0.jar", - "", - "sink", - "type", - "source", - "classification", - ], - [ - { - label: "open(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", - startLine: 14, - startColumn: 24, - endLine: 14, - endColumn: 35, - }, - }, - "org.sql2o.Sql2o#open()", - "true", - "supported", - "sql2o-1.6.0.jar", - "", - "sink", - "type", - "source", - "classification", - ], - [ - { - label: "open(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", - startLine: 25, - startColumn: 24, - endLine: 25, - endColumn: 35, - }, - }, - "org.sql2o.Sql2o#open()", - "true", - "supported", - "sql2o-1.6.0.jar", - "", - "sink", - "type", - "source", - "classification", - ], - [ - { - label: "new Sql2o(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", - startLine: 10, - startColumn: 33, - endLine: 10, - endColumn: 88, - }, - }, - "org.sql2o.Sql2o#Sql2o(String,String,String)", - "true", - "supported", - "sql2o-1.6.0.jar", - "", - "sink", - "type", - "source", - "classification", - ], - [ - { - label: "new Sql2o(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", - startLine: 23, - startColumn: 23, - endLine: 23, - endColumn: 36, - }, - }, - "org.sql2o.Sql2o#Sql2o(String)", - "true", - "supported", - "sql2o-1.6.0.jar", - "", - "sink", - "type", - "source", - "classification", - ], - ], - }; + describe("Java queries", () => { + describe("application mode query", () => { + const chunk: DecodedBqrsChunk = { + columns: [ + { name: "usage", kind: "Entity" }, + { kind: "String" }, + { kind: "String" }, + { kind: "String" }, + { kind: "String" }, + { name: "supported", kind: "Boolean" }, + { kind: "String" }, + { kind: "String" }, + { name: "type", kind: "String" }, + { name: "classification", kind: "String" }, + ], + tuples: [ + [ + { + label: "println(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 29, + startColumn: 9, + endLine: 29, + endColumn: 49, + }, + }, + "java.io", + "PrintStream", + "println", + "(String)", + true, + "rt.jar", + "", + "sink", + "source", + ], + [ + { + label: "run(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/Sql2oExampleApplication.java", + startLine: 9, + startColumn: 9, + endLine: 9, + endColumn: 66, + }, + }, + "org.springframework.boot", + "SpringApplication", + "run", + "(Class,String[])", + false, + "spring-boot-3.0.2.jar", + "", + "none", + "source", + ], + [ + { + label: "createQuery(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 15, + startColumn: 13, + endLine: 15, + endColumn: 56, + }, + }, + "org.sql2o", + "Connection", + "createQuery", + "(String)", + true, + "sql2o-1.6.0.jar", + "", + "sink", + "source", + ], + [ + { + label: "createQuery(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 26, + startColumn: 13, + endLine: 26, + endColumn: 39, + }, + }, + "org.sql2o", + "Connection", + "createQuery", + "(String)", + true, + "sql2o-1.6.0.jar", + "", + "sink", + "source", + ], + [ + { + label: "executeScalar(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 15, + startColumn: 13, + endLine: 15, + endColumn: 85, + }, + }, + "org.sql2o", + "Query", + "executeScalar", + "(Class)", + true, + "sql2o-1.6.0.jar", + "", + "sink", + "source", + ], + [ + { + label: "executeScalar(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 26, + startColumn: 13, + endLine: 26, + endColumn: 68, + }, + }, + "org.sql2o", + "Query", + "executeScalar", + "(Class)", + true, + "sql2o-1.6.0.jar", + "", + "sink", + "source", + ], + [ + { + label: "open(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 14, + startColumn: 24, + endLine: 14, + endColumn: 35, + }, + }, + "org.sql2o", + "Sql2o", + "open", + "()", + true, + "sql2o-1.6.0.jar", + "", + "sink", + "source", + ], + [ + { + label: "open(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 25, + startColumn: 24, + endLine: 25, + endColumn: 35, + }, + }, + "org.sql2o", + "Sql2o", + "open", + "()", + true, + "sql2o-1.6.0.jar", + "", + "sink", + "source", + ], + [ + { + label: "new Sql2o(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 10, + startColumn: 33, + endLine: 10, + endColumn: 88, + }, + }, + "org.sql2o", + "Sql2o", + "Sql2o", + "(String,String,String)", + true, + "sql2o-1.6.0.jar", + "", + "sink", + "source", + ], + [ + { + label: "new Sql2o(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 23, + startColumn: 23, + endLine: 23, + endColumn: 36, + }, + }, + "org.sql2o", + "Sql2o", + "Sql2o", + "(String)", + true, + "sql2o-1.6.0.jar", + "", + "sink", + "source", + ], + ], + }; - it("extracts methods", () => { - // Even though there are a number of methods with the same number of usages, the order returned should be stable: - // - Iterating over a map (as done by .values()) is guaranteed to be in insertion order - // - Sorting the array of methods is guaranteed to be a stable sort - expect(decodeBqrsToMethods(chunk)).toEqual([ - { - library: "rt", - libraryVersion: undefined, - signature: "java.io.PrintStream#println(String)", - packageName: "java.io", - typeName: "PrintStream", - methodName: "println", - methodParameters: "(String)", - supported: true, - supportedType: "sink", - usages: [ + it("extracts methods", () => { + // Even though there are a number of methods with the same number of usages, the order returned should be stable: + // - Iterating over a map (as done by .values()) is guaranteed to be in insertion order + // - Sorting the array of methods is guaranteed to be a stable sort + expect(decodeBqrsToMethods(chunk, Mode.Application)).toEqual([ { - label: "println(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", - startLine: 29, - startColumn: 9, - endLine: 29, - endColumn: 49, - }, - classification: CallClassification.Source, + library: "rt", + libraryVersion: undefined, + signature: "java.io.PrintStream#println(String)", + packageName: "java.io", + typeName: "PrintStream", + methodName: "println", + methodParameters: "(String)", + supported: true, + supportedType: "sink", + usages: [ + { + label: "println(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 29, + startColumn: 9, + endLine: 29, + endColumn: 49, + }, + classification: CallClassification.Source, + }, + ], }, + { + library: "spring-boot", + libraryVersion: "3.0.2", + signature: + "org.springframework.boot.SpringApplication#run(Class,String[])", + packageName: "org.springframework.boot", + typeName: "SpringApplication", + methodName: "run", + methodParameters: "(Class,String[])", + supported: false, + supportedType: "none", + usages: [ + { + label: "run(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/Sql2oExampleApplication.java", + startLine: 9, + startColumn: 9, + endLine: 9, + endColumn: 66, + }, + classification: CallClassification.Source, + }, + ], + }, + { + library: "sql2o", + libraryVersion: "1.6.0", + signature: "org.sql2o.Connection#createQuery(String)", + packageName: "org.sql2o", + typeName: "Connection", + methodName: "createQuery", + methodParameters: "(String)", + supported: true, + supportedType: "sink", + usages: [ + { + label: "createQuery(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 15, + startColumn: 13, + endLine: 15, + endColumn: 56, + }, + classification: CallClassification.Source, + }, + { + label: "createQuery(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 26, + startColumn: 13, + endLine: 26, + endColumn: 39, + }, + classification: CallClassification.Source, + }, + ], + }, + { + library: "sql2o", + libraryVersion: "1.6.0", + signature: "org.sql2o.Query#executeScalar(Class)", + packageName: "org.sql2o", + typeName: "Query", + methodName: "executeScalar", + methodParameters: "(Class)", + supported: true, + supportedType: "sink", + usages: [ + { + label: "executeScalar(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 15, + startColumn: 13, + endLine: 15, + endColumn: 85, + }, + classification: CallClassification.Source, + }, + { + label: "executeScalar(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 26, + startColumn: 13, + endLine: 26, + endColumn: 68, + }, + classification: CallClassification.Source, + }, + ], + }, + { + library: "sql2o", + libraryVersion: "1.6.0", + signature: "org.sql2o.Sql2o#open()", + packageName: "org.sql2o", + typeName: "Sql2o", + methodName: "open", + methodParameters: "()", + supported: true, + supportedType: "sink", + usages: [ + { + label: "open(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 14, + startColumn: 24, + endLine: 14, + endColumn: 35, + }, + classification: CallClassification.Source, + }, + { + label: "open(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 25, + startColumn: 24, + endLine: 25, + endColumn: 35, + }, + classification: CallClassification.Source, + }, + ], + }, + { + library: "sql2o", + libraryVersion: "1.6.0", + signature: "org.sql2o.Sql2o#Sql2o(String,String,String)", + packageName: "org.sql2o", + typeName: "Sql2o", + methodName: "Sql2o", + methodParameters: "(String,String,String)", + supported: true, + supportedType: "sink", + usages: [ + { + label: "new Sql2o(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 10, + startColumn: 33, + endLine: 10, + endColumn: 88, + }, + classification: CallClassification.Source, + }, + ], + }, + { + library: "sql2o", + libraryVersion: "1.6.0", + signature: "org.sql2o.Sql2o#Sql2o(String)", + packageName: "org.sql2o", + typeName: "Sql2o", + methodName: "Sql2o", + methodParameters: "(String)", + supported: true, + supportedType: "sink", + usages: [ + { + label: "new Sql2o(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 23, + startColumn: 23, + endLine: 23, + endColumn: 36, + }, + classification: CallClassification.Source, + }, + ], + }, + ]); + }); + }); + + describe("framework mode query", () => { + const chunk: DecodedBqrsChunk = { + columns: [ + { name: "endpoint", kind: "Entity" }, + { kind: "String" }, + { kind: "String" }, + { kind: "String" }, + { kind: "String" }, + { name: "supported", kind: "Boolean" }, + { kind: "String" }, + { name: "type", kind: "String" }, ], - }, - { - library: "spring-boot", - libraryVersion: "3.0.2", - signature: - "org.springframework.boot.SpringApplication#run(Class,String[])", - packageName: "org.springframework.boot", - typeName: "SpringApplication", - methodName: "run", - methodParameters: "(Class,String[])", - supported: false, - supportedType: "none", - usages: [ - { - label: "run(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/Sql2oExampleApplication.java", - startLine: 9, - startColumn: 9, - endLine: 9, - endColumn: 66, + tuples: [ + [ + { + label: "connect", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 22, + startColumn: 19, + endLine: 22, + endColumn: 25, + }, }, - classification: CallClassification.Source, - }, + "org.example", + "HelloController", + "connect", + "(String)", + false, + "example", + "", + ], + [ + { + label: "index", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 13, + startColumn: 19, + endLine: 13, + endColumn: 23, + }, + }, + "org.example", + "HelloController", + "index", + "(String)", + true, + "example", + "summary", + ], ], - }, - { - library: "sql2o", - libraryVersion: "1.6.0", - signature: "org.sql2o.Connection#createQuery(String)", - packageName: "org.sql2o", - typeName: "Connection", - methodName: "createQuery", - methodParameters: "(String)", - supported: true, - supportedType: "sink", - usages: [ + }; + + it("extracts methods", () => { + expect(decodeBqrsToMethods(chunk, Mode.Framework)).toEqual([ { - label: "createQuery(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", - startLine: 15, - startColumn: 13, - endLine: 15, - endColumn: 56, - }, - classification: CallClassification.Source, + library: "", + libraryVersion: undefined, + signature: "org.example.HelloController#connect(String)", + packageName: "org.example", + typeName: "HelloController", + methodName: "connect", + methodParameters: "(String)", + supported: false, + supportedType: "", + usages: [ + { + label: "connect", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 22, + startColumn: 19, + endLine: 22, + endColumn: 25, + }, + classification: CallClassification.Unknown, + }, + ], }, { - label: "createQuery(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", - startLine: 26, - startColumn: 13, - endLine: 26, - endColumn: 39, - }, - classification: CallClassification.Source, + library: "", + libraryVersion: undefined, + signature: "org.example.HelloController#index(String)", + packageName: "org.example", + typeName: "HelloController", + methodName: "index", + methodParameters: "(String)", + supported: true, + supportedType: "summary", + usages: [ + { + label: "index", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 13, + startColumn: 19, + endLine: 13, + endColumn: 23, + }, + classification: CallClassification.Unknown, + }, + ], }, + ]); + }); + }); + }); + + describe("C# queries", () => { + describe("application mode query", () => { + const chunk: DecodedBqrsChunk = { + columns: [ + { name: "usage", kind: "Entity" }, + { kind: "String" }, + { kind: "String" }, + { kind: "String" }, + { kind: "String" }, + { name: "supported", kind: "Boolean" }, + { kind: "String" }, + { kind: "String" }, + { name: "type", kind: "String" }, + { name: "classification", kind: "String" }, ], - }, - { - library: "sql2o", - libraryVersion: "1.6.0", - signature: "org.sql2o.Query#executeScalar(Class)", - packageName: "org.sql2o", - typeName: "Query", - methodName: "executeScalar", - methodParameters: "(Class)", - supported: true, - supportedType: "sink", - usages: [ - { - label: "executeScalar(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", - startLine: 15, - startColumn: 13, - endLine: 15, - endColumn: 85, + tuples: [ + [ + { + label: "call to method GetMethodInfo", + url: { + uri: "file:/home/runner/work/bulk-builder/bulk-builder/src/Moq/ActionObserver.cs", + startLine: 74, + startColumn: 40, + endLine: 74, + endColumn: 61, + }, }, - classification: CallClassification.Source, - }, - { - label: "executeScalar(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", - startLine: 26, - startColumn: 13, - endLine: 26, - endColumn: 68, - }, - classification: CallClassification.Source, - }, + "System.Reflection", + "RuntimeReflectionExtensions", + "GetMethodInfo", + "System.Delegate", + true, + "mscorlib", + "4.0.0.0", + "summary", + "source", + ], ], - }, - { - library: "sql2o", - libraryVersion: "1.6.0", - signature: "org.sql2o.Sql2o#open()", - packageName: "org.sql2o", - typeName: "Sql2o", - methodName: "open", - methodParameters: "()", - supported: true, - supportedType: "sink", - usages: [ + }; + + it("extracts methods", () => { + expect(decodeBqrsToMethods(chunk, Mode.Application)).toEqual([ { - label: "open(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", - startLine: 14, - startColumn: 24, - endLine: 14, - endColumn: 35, - }, - classification: CallClassification.Source, - }, - { - label: "open(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", - startLine: 25, - startColumn: 24, - endLine: 25, - endColumn: 35, - }, - classification: CallClassification.Source, + library: "mscorlib", + libraryVersion: "4.0.0.0", + signature: + "System.Reflection.RuntimeReflectionExtensions#GetMethodInfo(System.Delegate)", + packageName: "System.Reflection", + typeName: "RuntimeReflectionExtensions", + methodName: "GetMethodInfo", + methodParameters: "(System.Delegate)", + supported: true, + supportedType: "summary", + usages: [ + { + label: "call to method GetMethodInfo", + url: { + uri: "file:/home/runner/work/bulk-builder/bulk-builder/src/Moq/ActionObserver.cs", + startLine: 74, + startColumn: 40, + endLine: 74, + endColumn: 61, + }, + classification: CallClassification.Source, + }, + ], }, + ]); + }); + }); + + describe("framework mode query", () => { + const chunk: DecodedBqrsChunk = { + columns: [ + { name: "endpoint", kind: "Entity" }, + { kind: "String" }, + { kind: "String" }, + { kind: "String" }, + { kind: "String" }, + { name: "supported", kind: "Boolean" }, + { kind: "String" }, + { name: "type", kind: "String" }, ], - }, - { - library: "sql2o", - libraryVersion: "1.6.0", - signature: "org.sql2o.Sql2o#Sql2o(String,String,String)", - packageName: "org.sql2o", - typeName: "Sql2o", - methodName: "Sql2o", - methodParameters: "(String,String,String)", - supported: true, - supportedType: "sink", - usages: [ - { - label: "new Sql2o(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", - startLine: 10, - startColumn: 33, - endLine: 10, - endColumn: 88, + tuples: [ + [ + { + label: "Validate", + url: { + uri: "file:/home/runner/work/bulk-builder/bulk-builder/src/Moq/Times.cs", + startLine: 369, + startColumn: 21, + endLine: 369, + endColumn: 28, + }, }, - classification: CallClassification.Source, - }, + "Moq", + "Times", + "Validate", + "System.Int32", + false, + "Times.cs", + "", + ], ], - }, - { - library: "sql2o", - libraryVersion: "1.6.0", - signature: "org.sql2o.Sql2o#Sql2o(String)", - packageName: "org.sql2o", - typeName: "Sql2o", - methodName: "Sql2o", - methodParameters: "(String)", - supported: true, - supportedType: "sink", - usages: [ + }; + + it("extracts methods", () => { + expect(decodeBqrsToMethods(chunk, Mode.Framework)).toEqual([ { - label: "new Sql2o(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", - startLine: 23, - startColumn: 23, - endLine: 23, - endColumn: 36, - }, - classification: CallClassification.Source, + library: "Times", + libraryVersion: undefined, + signature: "Moq.Times#Validate(System.Int32)", + packageName: "Moq", + typeName: "Times", + methodName: "Validate", + methodParameters: "(System.Int32)", + supported: false, + supportedType: "", + usages: [ + { + label: "Validate", + url: { + uri: "file:/home/runner/work/bulk-builder/bulk-builder/src/Moq/Times.cs", + startLine: 369, + startColumn: 21, + endLine: 369, + endColumn: 28, + }, + classification: CallClassification.Unknown, + }, + ], }, - ], - }, - ]); + ]); + }); + }); }); }); 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 2fdd1a3bd..f6f8f28aa 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 @@ -152,7 +152,7 @@ describe("external api usage query", () => { expect(options.queryRunner.createQueryRun).toHaveBeenCalledWith( "/a/b/c/src.zip", { - queryPath: expect.stringMatching(/FetchExternalApis\S*\.ql/), + queryPath: expect.stringMatching(/\S*ModeEndpoints\.ql/), quickEvalPosition: undefined, quickEvalCountOnly: false, }, 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 f722bbec4..1db71d531 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 @@ -34,9 +34,11 @@ describe("setUpPack", () => { expect(queryFiles.sort()).toEqual( [ "codeql-pack.yml", - "FetchExternalApisApplicationMode.ql", - "FetchExternalApisFrameworkMode.ql", - "AutomodelVsCode.qll", + "ApplicationModeEndpoints.ql", + "ApplicationModeEndpointsQuery.qll", + "FrameworkModeEndpoints.ql", + "FrameworkModeEndpointsQuery.qll", + "ModelEditor.qll", ].sort(), ); @@ -58,9 +60,7 @@ describe("setUpPack", () => { readFileSync( join( queryDir, - `FetchExternalApis${ - mode.charAt(0).toUpperCase() + mode.slice(1) - }Mode.ql`, + `${mode.charAt(0).toUpperCase() + mode.slice(1)}ModeEndpoints.ql`, ), "utf8", ),