Merge pull request #2868 from github/koesie10/new-model-editor-queries

Update model editor queries
This commit is contained in:
Koen Vlaswinkel
2023-09-27 12:03:48 +02:00
committed by GitHub
8 changed files with 1060 additions and 769 deletions

View File

@@ -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<string, Method>();
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) {

View File

@@ -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`;
}

View File

@@ -2,130 +2,151 @@ 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
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
"ApplicationModeEndpointsQuery.qll": `private import csharp
private import semmle.code.csharp.dataflow.ExternalFlow as ExternalFlow
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 ExternalFlow::sourceNode(this.getAnOutput(), _)
}
override predicate isSink() { ExternalFlow::sinkNode(this.getAnInput(), _) }
}
`,
"FrameworkModeEndpointsQuery.qll": `private import csharp
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 +164,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 +189,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 +213,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, _, _, _) }
}
`,
},

View File

@@ -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
}
`,
},

View File

@@ -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,
];

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(
"/a/b/c/src.zip",
{
queryPath: expect.stringMatching(/FetchExternalApis\S*\.ql/),
queryPath: expect.stringMatching(/\S*ModeEndpoints\.ql/),
quickEvalPosition: undefined,
quickEvalCountOnly: false,
},

View File

@@ -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",
),