Update model editor queries

This updates the model editor queries to the version that will be merged
into the CodeQL repository. There are some slight changes to the output
format, so we slightly need to change the BQRS decoding of those
queries.

The queries themselves were copied from the two PRs with some minor
additions at the end since these were changes in core CodeQL library
files.
This commit is contained in:
Koen Vlaswinkel
2023-09-27 09:05:09 +02:00
parent 6c1cd71743
commit e03d106bc2
8 changed files with 1060 additions and 768 deletions

View File

@@ -2,41 +2,72 @@ import { DecodedBqrsChunk } from "../common/bqrs-cli-types";
import { Call, CallClassification, Method } from "./method"; import { Call, CallClassification, Method } from "./method";
import { ModeledMethodType } from "./modeled-method"; import { ModeledMethodType } from "./modeled-method";
import { parseLibraryFilename } from "./library"; 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<string, Method>(); const methodsByApiName = new Map<string, Method>();
chunk?.tuples.forEach((tuple) => { chunk?.tuples.forEach((tuple) => {
const usage = tuple[0] as Call; let usage: Call;
const signature = tuple[1] as string; let packageName: string;
const supported = (tuple[2] as string) === "true"; let typeName: string;
let library = tuple[4] as string; let methodName: string;
let libraryVersion: string | undefined = tuple[5] as string; let methodParameters: string;
const type = tuple[6] as ModeledMethodType; let supported: boolean;
const classification = tuple[8] as CallClassification; 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( classification = CallClassification.Unknown;
0, }
packageWithType.lastIndexOf("."),
);
const typeName = packageWithType.substring(
packageWithType.lastIndexOf(".") + 1,
);
const methodName = methodDeclaration.substring( if (!methodParameters.startsWith("(")) {
0, // There's a difference in how the Java and C# queries return method parameters. In the C# query, the method
methodDeclaration.indexOf("("), // 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.
const methodParameters = methodDeclaration.substring( methodParameters = `(${methodParameters})`;
methodDeclaration.indexOf("("), }
);
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 // 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 // properly specify the version. Therefore, we'll always try to parse the name and version from the library filename
// for Java. // for Java.
if (library.endsWith(".jar") || libraryVersion === "") { if (
library.endsWith(".jar") ||
libraryVersion === "" ||
libraryVersion === undefined
) {
const { name, version } = parseLibraryFilename(library); const { name, version } = parseLibraryFilename(library);
library = name; library = name;
if (version) { if (version) {

View File

@@ -132,7 +132,7 @@ export async function runExternalApiQueries(
maxStep: externalApiQueriesProgressMaxStep, maxStep: externalApiQueriesProgressMaxStep,
}); });
return decodeBqrsToMethods(bqrsChunk); return decodeBqrsToMethods(bqrsChunk, mode);
} }
type GetResultsOptions = { type GetResultsOptions = {
@@ -160,7 +160,5 @@ export async function readQueryResults({
} }
function queryNameFromMode(mode: Mode): string { function queryNameFromMode(mode: Mode): string {
return `FetchExternalApis${ return `${mode.charAt(0).toUpperCase() + mode.slice(1)}ModeEndpoints.ql`;
mode.charAt(0).toUpperCase() + mode.slice(1)
}Mode.ql`;
} }

View File

@@ -2,130 +2,152 @@ import { Query } from "./query";
export const fetchExternalApisQuery: Query = { export const fetchExternalApisQuery: Query = {
applicationModeQuery: `/** applicationModeQuery: `/**
* @name Usage of APIs coming from external libraries * @name Fetch endpoints for use in the model editor (application mode)
* @description A list of 3rd party APIs used in the codebase. * @description A list of 3rd party endpoints (methods and attributes) used in the codebase. Excludes test and generated code.
* @tags telemetry * @kind table
* @kind problem * @id csharp/utils/modeleditor/application-mode-endpoints
* @id cs/telemetry/fetch-external-apis * @tags modeleditor endpoints application-mode
*/ */
private import csharp import csharp
private import AutomodelVsCode import ApplicationModeEndpointsQuery
import ModelEditor
class ExternalApi extends CallableMethod { private Call aUsage(ExternalEndpoint api) { result.getTarget().getUnboundDeclaration() = api }
ExternalApi() {
this.isUnboundDeclaration() and
this.fromLibrary() and
this.(Modifiable).isEffectivelyPublic()
}
}
private Call aUsage(ExternalApi api) { result.getTarget().getUnboundDeclaration() = api } from ExternalEndpoint endpoint, boolean supported, Call usage, string type, string classification
from
ExternalApi api, string apiName, boolean supported, Call usage, string type, string classification
where where
apiName = api.getApiName() and supported = isSupported(endpoint) and
supported = isSupported(api) and usage = aUsage(endpoint) and
usage = aUsage(api) and type = supportedType(endpoint) and
type = supportedType(api) and
classification = methodClassification(usage) classification = methodClassification(usage)
select usage, apiName, supported.toString(), "supported", api.dllName(), api.dllVersion(), type, select usage, endpoint.getNamespace(), endpoint.getTypeName(), endpoint.getName(),
"type", classification, "classification" endpoint.getParameterTypes(), supported, endpoint.dllName(), endpoint.dllVersion(), type,
classification
`, `,
frameworkModeQuery: `/** frameworkModeQuery: `/**
* @name Public methods * @name Fetch endpoints for use in the model editor (framework mode)
* @description A list of APIs callable by consumers. Excludes test and generated code. * @description A list of endpoints accessible (methods and attributes) for consumers of the library. Excludes test and generated code.
* @tags telemetry * @kind table
* @kind problem * @id csharp/utils/modeleditor/framework-mode-endpoints
* @id cs/telemetry/fetch-public-methods * @tags modeleditor endpoints framework-mode
*/ */
private import csharp import csharp
private import dotnet import FrameworkModeEndpointsQuery
private import semmle.code.csharp.frameworks.Test import ModelEditor
private import AutomodelVsCode
class PublicMethod extends CallableMethod { from PublicEndpointFromSource endpoint, boolean supported, string type
PublicMethod() { this.fromSource() and not this.getFile() instanceof TestFile }
}
from PublicMethod publicMethod, string apiName, boolean supported, string type
where where
apiName = publicMethod.getApiName() and supported = isSupported(endpoint) and
supported = isSupported(publicMethod) and type = supportedType(endpoint)
type = supportedType(publicMethod) select endpoint, endpoint.getNamespace(), endpoint.getTypeName(), endpoint.getName(),
select publicMethod, apiName, supported.toString(), "supported", endpoint.getParameterTypes(), supported, endpoint.getFile().getBaseName(), type
publicMethod.getFile().getBaseName(), "library", type, "type", "unknown", "classification"
`, `,
dependencies: { dependencies: {
"AutomodelVsCode.qll": `/** Provides classes and predicates related to handling APIs for the VS Code extension. */ "ApplicationModeEndpointsQuery.qll": `private import csharp
private import csharp
private import dotnet
private import semmle.code.csharp.dispatch.Dispatch
private import semmle.code.csharp.dataflow.ExternalFlow 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.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.dataflow.internal.TaintTrackingPrivate
private import semmle.code.csharp.frameworks.Test
private import semmle.code.csharp.security.dataflow.flowsources.Remote private import semmle.code.csharp.security.dataflow.flowsources.Remote
private import ModelEditor
pragma[nomagic]
private predicate isTestNamespace(Namespace ns) {
ns.getFullName()
.matches([
"NUnit.Framework%", "Xunit%", "Microsoft.VisualStudio.TestTools.UnitTesting%", "Moq%"
])
}
/** /**
* A test library. * A class of effectively public callables in library code.
*/ */
class TestLibrary extends RefType { class ExternalEndpoint extends Endpoint {
TestLibrary() { isTestNamespace(this.getNamespace()) } 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. */ /** 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.getDeclaringType() instanceof TestLibrary or
c.(Constructor).isParameterless() or c.(Constructor).isParameterless() or
c.getDeclaringType() instanceof AnonymousClass 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 { class Endpoint extends Callable {
CallableMethod() { Endpoint() {
this.(Modifiable).isEffectivelyPublic() and [this.(Modifiable), this.(Accessor).getDeclaration()].isEffectivelyPublic() and
not isUninteresting(this) not isUninteresting(this) and
this.isUnboundDeclaration()
} }
/** /**
* Gets the unbound type, name and parameter types of this API. * Gets the namespace of this endpoint.
*/
bindingset[this]
private string getSignature() {
result =
nestedName(this.getDeclaringType().getUnboundDeclaration()) + "#" + this.getName() + "(" +
parameterQualifiedTypeNamesToString(this) + ")"
}
/**
* Gets the namespace of this API.
*/ */
bindingset[this] bindingset[this]
string getNamespace() { this.getDeclaringType().hasQualifiedName(result, _) } string getNamespace() { this.getDeclaringType().hasQualifiedName(result, _) }
/** /**
* Gets the namespace and signature of this API. * Gets the unbound type name of this endpoint.
*/ */
bindingset[this] 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() } private string getDllName() { result = this.getLocation().(Assembly).getName() }
@@ -143,44 +165,17 @@ class CallableMethod extends DotNet::Declaration {
not exists(this.getDllVersion()) and result = "" 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. */ /** Holds if this API has a supported summary. */
pragma[nomagic] pragma[nomagic]
predicate hasSummary() { predicate hasSummary() { this instanceof SummarizedCallable }
this instanceof SummarizedCallable
or
defaultAdditionalTaintStep(this.getAnInput(), _)
}
/** Holds if this API is a known source. */ /** Holds if this API is a known source. */
pragma[nomagic] pragma[nomagic]
predicate isSource() { abstract predicate isSource();
this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _)
}
/** Holds if this API is a known sink. */ /** Holds if this API is a known sink. */
pragma[nomagic] pragma[nomagic]
predicate isSink() { sinkNode(this.getAnInput(), _) } abstract predicate isSink();
/** Holds if this API is a known neutral. */ /** Holds if this API is a known neutral. */
pragma[nomagic] pragma[nomagic]
@@ -195,23 +190,20 @@ class CallableMethod extends DotNet::Declaration {
} }
} }
boolean isSupported(CallableMethod callableMethod) { boolean isSupported(Endpoint endpoint) {
callableMethod.isSupported() and result = true if endpoint.isSupported() then result = true else result = false
or
not callableMethod.isSupported() and
result = false
} }
string supportedType(CallableMethod method) { string supportedType(Endpoint endpoint) {
method.isSink() and result = "sink" endpoint.isSink() and result = "sink"
or or
method.isSource() and result = "source" endpoint.isSource() and result = "source"
or or
method.hasSummary() and result = "summary" endpoint.hasSummary() and result = "summary"
or or
method.isNeutral() and result = "neutral" endpoint.isNeutral() and result = "neutral"
or or
not method.isSupported() and result = "" not endpoint.isSupported() and result = ""
} }
string methodClassification(Call method) { 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 * 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. * the name of the enclosing type, which might be a nested type as well.
*/ */
private string nestedName(Declaration declaration) { private string nestedName(Type t) {
not exists(declaration.getDeclaringType().getUnboundDeclaration()) and not exists(t.getDeclaringType().getUnboundDeclaration()) and
result = declaration.getName() result = t.getName()
or or
nestedName(declaration.getDeclaringType().getUnboundDeclaration()) + "+" + declaration.getName() = nestedName(t.getDeclaringType().getUnboundDeclaration()) + "+" + t.getName() = result
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, _, _, _) }
} }
`, `,
}, },

View File

@@ -2,66 +2,113 @@ import { Query } from "./query";
export const fetchExternalApisQuery: Query = { export const fetchExternalApisQuery: Query = {
applicationModeQuery: `/** applicationModeQuery: `/**
* @name Usage of APIs coming from external libraries * @name Fetch endpoints for use in the model editor (application mode)
* @description A list of 3rd party APIs used in the codebase. Excludes test and generated code. * @description A list of 3rd party endpoints (methods) used in the codebase. Excludes test and generated code.
* @tags telemetry * @kind table
* @kind problem * @id java/utils/modeleditor/application-mode-endpoints
* @id java/telemetry/fetch-external-apis * @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 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.ExternalFlow
private import semmle.code.java.dataflow.FlowSources 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.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.TaintTracking
private import semmle.code.java.dataflow.internal.ModelExclusions 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. * A callable method from either the Standard Library, a 3rd party library or from the source.
*/ */
class CallableMethod extends Callable { class Endpoint extends Callable {
CallableMethod() { not isUninteresting(this) } 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() { string getPackageName() { result = this.getDeclaringType().getPackage().getName() }
result =
this.getDeclaringType().getPackage() + "." + this.getDeclaringType().nestedName() + "#" + /**
this.getName() + paramsString(this) * 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() { private string getJarName() {
result = this.getCompilationUnit().getParentContainer*().(JarFile).getBaseName() result = this.getCompilationUnit().getParentContainer*().(JarFile).getBaseName()
@@ -113,43 +166,23 @@ class CallableMethod extends Callable {
not exists(this.getJarVersion()) and result = "" 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. */ /** Holds if this API has a supported summary. */
pragma[nomagic] pragma[nomagic]
predicate hasSummary() { predicate hasSummary() { this = any(SummarizedCallable sc).asCallable() }
this = any(SummarizedCallable sc).asCallable() or
TaintTracking::localAdditionalTaintStep(this.getAnInput(), _)
}
/** Holds if this API is a known source. */
pragma[nomagic] pragma[nomagic]
predicate isSource() { abstract predicate isSource();
this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _)
}
/** Holds if this API is a known sink. */ /** Holds if this API is a known sink. */
pragma[nomagic] pragma[nomagic]
predicate isSink() { sinkNode(this.getAnInput(), _) } abstract predicate isSink();
/** Holds if this API is a known neutral. */ /** Holds if this API is a known neutral. */
pragma[nomagic] pragma[nomagic]
predicate isNeutral() { predicate isNeutral() {
exists(string namespace, string type, string name, string signature, string kind, string provenance | exists(string namespace, string type, string name, string signature |
neutralModel(namespace, type, name, signature, kind, provenance) and neutralModel(namespace, type, name, signature, _, _) and
this = interpretElement(namespace, type, false, name, signature, "") this = interpretElement(namespace, type, false, name, signature, "")
) )
} }
@@ -163,108 +196,38 @@ class CallableMethod extends Callable {
} }
} }
boolean isSupported(CallableMethod method) { boolean isSupported(Endpoint endpoint) {
method.isSupported() and result = true endpoint.isSupported() and result = true
or or
not method.isSupported() and result = false not endpoint.isSupported() and result = false
} }
string supportedType(CallableMethod method) { string supportedType(Endpoint endpoint) {
method.isSink() and result = "sink" endpoint.isSink() and result = "sink"
or or
method.isSource() and result = "source" endpoint.isSource() and result = "source"
or or
method.hasSummary() and result = "summary" endpoint.hasSummary() and result = "summary"
or or
method.isNeutral() and result = "neutral" endpoint.isNeutral() and result = "neutral"
or or
not method.isSupported() and result = "" not endpoint.isSupported() and result = ""
} }
string methodClassification(Call method) { string usageClassification(Call usage) {
isInTestFile(method.getLocation().getFile()) and result = "test" isInTestFile(usage.getLocation().getFile()) and result = "test"
or or
method.getFile() instanceof GeneratedFile and result = "generated" usage.getFile() instanceof GeneratedFile and result = "generated"
or or
not isInTestFile(method.getLocation().getFile()) and not isInTestFile(usage.getLocation().getFile()) and
not method.getFile() instanceof GeneratedFile and not usage.getFile() instanceof GeneratedFile and
result = "source" 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 // Temporarily copied from java/ql/lib/semmle/code/java/dataflow/internal/ModelExclusions.qll
// to avoid the use of internal modules. predicate isInTestFile(File file) {
/** 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) {
file.getAbsolutePath().matches(["%/test/%", "%/guava-tests/%", "%/guava-testlib/%"]) and file.getAbsolutePath().matches(["%/test/%", "%/guava-tests/%", "%/guava-testlib/%"]) and
not file.getAbsolutePath().matches("%/ql/test/%") // allows our test cases to work not file.getAbsolutePath().matches(["%/ql/test/%", "%/ql/automodel/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)
}
} }
`, `,
}, },

View File

@@ -1,18 +1,21 @@
import { Call, CallClassification } from "../method";
import { ModeledMethodType } from "../modeled-method";
export type Query = { export type Query = {
/** /**
* The application query. * The application query.
* *
* It should select all usages of external APIs, and return the following result pattern: * 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. * - usage: the usage of the external API. This is an entity.
* - apiName: the name of the external API. This is a string. * - packageName: the package 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. * - typeName: the type name of the external API. This is a string.
* - "supported": a string literal. This is required to make the query a valid problem query. * - 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. * - 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. * - 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: 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: 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; 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). * 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: * The result pattern should be as follows:
* - method: the method that is callable by applications. This is an entity. * - 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: 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: the name of the file or library that contains the method. This is a string and usually the basename of a file.
* - 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.
* - type: the modeled kind of the method, either "sink", "source", "summary", or "neutral" * - 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; frameworkModeQuery: string;
dependencies?: { dependencies?: {
[filename: string]: string; [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,
];

File diff suppressed because it is too large Load Diff

View File

@@ -152,7 +152,7 @@ describe("external api usage query", () => {
expect(options.queryRunner.createQueryRun).toHaveBeenCalledWith( expect(options.queryRunner.createQueryRun).toHaveBeenCalledWith(
"/a/b/c/src.zip", "/a/b/c/src.zip",
{ {
queryPath: expect.stringMatching(/FetchExternalApis\S*\.ql/), queryPath: expect.stringMatching(/\S*ModeEndpoints\.ql/),
quickEvalPosition: undefined, quickEvalPosition: undefined,
quickEvalCountOnly: false, quickEvalCountOnly: false,
}, },

View File

@@ -34,9 +34,11 @@ describe("setUpPack", () => {
expect(queryFiles.sort()).toEqual( expect(queryFiles.sort()).toEqual(
[ [
"codeql-pack.yml", "codeql-pack.yml",
"FetchExternalApisApplicationMode.ql", "ApplicationModeEndpoints.ql",
"FetchExternalApisFrameworkMode.ql", "ApplicationModeEndpointsQuery.qll",
"AutomodelVsCode.qll", "FrameworkModeEndpoints.ql",
"FrameworkModeEndpointsQuery.qll",
"ModelEditor.qll",
].sort(), ].sort(),
); );
@@ -58,9 +60,7 @@ describe("setUpPack", () => {
readFileSync( readFileSync(
join( join(
queryDir, queryDir,
`FetchExternalApis${ `${mode.charAt(0).toUpperCase() + mode.slice(1)}ModeEndpoints.ql`,
mode.charAt(0).toUpperCase() + mode.slice(1)
}Mode.ql`,
), ),
"utf8", "utf8",
), ),