272 lines
9.3 KiB
TypeScript
272 lines
9.3 KiB
TypeScript
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
|
|
*/
|
|
|
|
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 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 semmle.code.java.dataflow.TaintTracking
|
|
private import semmle.code.java.dataflow.internal.ModelExclusions
|
|
|
|
/** Holds if the given callable/method is not worth supporting. */
|
|
private predicate isUninteresting(Callable c) {
|
|
c.getDeclaringType() instanceof TestLibrary or
|
|
c.(Constructor).isParameterless() or
|
|
c.getDeclaringType() instanceof AnonymousClass
|
|
}
|
|
|
|
/**
|
|
* A callable method from either the Standard Library, a 3rd party library or from the source.
|
|
*/
|
|
class CallableMethod extends Callable {
|
|
CallableMethod() { not isUninteresting(this) }
|
|
|
|
/**
|
|
* Gets information about the external API in the form expected by the MaD modeling framework.
|
|
*/
|
|
string getApiName() {
|
|
result =
|
|
this.getDeclaringType().getPackage() + "." + this.getDeclaringType().nestedName() + "#" +
|
|
this.getName() + paramsString(this)
|
|
}
|
|
|
|
private string getJarName() {
|
|
result = this.getCompilationUnit().getParentContainer*().(JarFile).getBaseName()
|
|
}
|
|
|
|
private string getJarVersion() {
|
|
result = this.getCompilationUnit().getParentContainer*().(JarFile).getSpecificationVersion()
|
|
}
|
|
|
|
/**
|
|
* Gets the jar file containing this API. Normalizes the Java Runtime to "rt.jar" despite the presence of modules.
|
|
*/
|
|
string jarContainer() {
|
|
result = this.getJarName()
|
|
or
|
|
not exists(this.getJarName()) and result = "rt.jar"
|
|
}
|
|
|
|
/**
|
|
* Gets the version of the JAR file containing this API. Empty if no version is found in the JAR.
|
|
*/
|
|
string jarVersion() {
|
|
result = this.getJarVersion()
|
|
or
|
|
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(), _)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
predicate isSource() {
|
|
this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _)
|
|
}
|
|
|
|
/** Holds if this API is a known sink. */
|
|
pragma[nomagic]
|
|
predicate isSink() { sinkNode(this.getAnInput(), _) }
|
|
|
|
/** 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
|
|
this = interpretElement(namespace, type, false, name, signature, "")
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if this API is supported by existing CodeQL libraries, that is, it is either a
|
|
* recognized source, sink or neutral or it has a flow summary.
|
|
*/
|
|
predicate isSupported() {
|
|
this.hasSummary() or this.isSource() or this.isSink() or this.isNeutral()
|
|
}
|
|
}
|
|
|
|
boolean isSupported(CallableMethod method) {
|
|
method.isSupported() and result = true
|
|
or
|
|
not method.isSupported() and result = false
|
|
}
|
|
|
|
string supportedType(CallableMethod method) {
|
|
method.isSink() and result = "sink"
|
|
or
|
|
method.isSource() and result = "source"
|
|
or
|
|
method.hasSummary() and result = "summary"
|
|
or
|
|
method.isNeutral() and result = "neutral"
|
|
or
|
|
not method.isSupported() and result = ""
|
|
}
|
|
|
|
string methodClassification(Call method) {
|
|
isInTestFile(method.getLocation().getFile()) and result = "test"
|
|
or
|
|
method.getFile() instanceof GeneratedFile and result = "generated"
|
|
or
|
|
not isInTestFile(method.getLocation().getFile()) and
|
|
not method.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) {
|
|
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)
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
};
|