Merge pull request #2868 from github/koesie10/new-model-editor-queries
Update model editor queries
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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`;
|
||||
}
|
||||
|
||||
@@ -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, _, _, _) }
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
@@ -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
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user