mirror of
https://github.com/github/codeql.git
synced 2026-04-25 16:55:19 +02:00
Merge pull request #6376 from bmuskalla/thirdpartyapitelemtry
Java: Introduce queries to capture information about 3rd party API usage
This commit is contained in:
@@ -616,26 +616,25 @@ private predicate elementSpec(
|
||||
summaryModel(namespace, type, subtypes, name, signature, ext, _, _, _)
|
||||
}
|
||||
|
||||
private predicate relevantCallable(Callable c) { elementSpec(_, _, _, c.getName(), _, _) }
|
||||
|
||||
private string paramsStringPart(Callable c, int i) {
|
||||
relevantCallable(c) and
|
||||
(
|
||||
i = -1 and result = "("
|
||||
i = -1 and result = "("
|
||||
or
|
||||
exists(int n, string p | c.getParameterType(n).getErasure().toString() = p |
|
||||
i = 2 * n and result = p
|
||||
or
|
||||
exists(int n, string p | c.getParameterType(n).getErasure().toString() = p |
|
||||
i = 2 * n and result = p
|
||||
or
|
||||
i = 2 * n - 1 and result = "," and n != 0
|
||||
)
|
||||
or
|
||||
i = 2 * c.getNumberOfParameters() and result = ")"
|
||||
i = 2 * n - 1 and result = "," and n != 0
|
||||
)
|
||||
or
|
||||
i = 2 * c.getNumberOfParameters() and result = ")"
|
||||
}
|
||||
|
||||
private string paramsString(Callable c) {
|
||||
result = concat(int i | | paramsStringPart(c, i) order by i)
|
||||
}
|
||||
/**
|
||||
* Gets a parenthesized string containing all parameter types of this callable, separated by a comma.
|
||||
*
|
||||
* Returns the empty string if the callable has no parameters.
|
||||
* Parameter types are represented by their type erasure.
|
||||
*/
|
||||
string paramsString(Callable c) { result = concat(int i | | paramsStringPart(c, i) order by i) }
|
||||
|
||||
private Element interpretElement0(
|
||||
string namespace, string type, boolean subtypes, string name, string signature
|
||||
|
||||
95
java/ql/src/Telemetry/ExternalAPI.qll
Normal file
95
java/ql/src/Telemetry/ExternalAPI.qll
Normal file
@@ -0,0 +1,95 @@
|
||||
/** Provides classes and predicates related to handling APIs from external libraries. */
|
||||
|
||||
private import java
|
||||
private import semmle.code.java.dataflow.DataFlow
|
||||
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.TaintTracking
|
||||
|
||||
/**
|
||||
* An external API from either the Java Standard Library or a 3rd party library.
|
||||
*/
|
||||
class ExternalAPI extends Callable {
|
||||
ExternalAPI() { not this.fromSource() }
|
||||
|
||||
/** Holds if this API is not worth supporting */
|
||||
predicate isUninteresting() { isTestLibrary() or isParameterlessConstructor() }
|
||||
|
||||
/** Holds if this API is is a constructor without parameters */
|
||||
predicate isParameterlessConstructor() {
|
||||
this instanceof Constructor and this.getNumberOfParameters() = 0
|
||||
}
|
||||
|
||||
/** Holds if this API is part of a common testing library or framework */
|
||||
private predicate isTestLibrary() { getDeclaringType() instanceof TestLibrary }
|
||||
|
||||
/**
|
||||
* Gets information about the external API in the form expected by the CSV modeling framework.
|
||||
*/
|
||||
string getApiName() {
|
||||
result =
|
||||
this.getDeclaringType().getPackage() + "." + this.getDeclaringType().getSourceDeclaration() +
|
||||
"#" + this.getName() + paramsString(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the jar file containing this API. Normalizes the Java Runtime to "rt.jar" despite the presence of modules.
|
||||
*/
|
||||
string jarContainer() { result = containerAsJar(this.getCompilationUnit().getParentContainer*()) }
|
||||
|
||||
private string containerAsJar(Container container) {
|
||||
if container instanceof JarFile then result = container.getBaseName() else result = "rt.jar"
|
||||
}
|
||||
|
||||
/** 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() = 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() = call
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if this API has a supported summary. */
|
||||
predicate hasSummary() {
|
||||
this instanceof SummarizedCallable or
|
||||
TaintTracking::localAdditionalTaintStep(this.getAnInput(), _)
|
||||
}
|
||||
|
||||
/** Holds if this API is a known source. */
|
||||
predicate isSource() {
|
||||
this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _)
|
||||
}
|
||||
|
||||
/** Holds if this API is a known sink. */
|
||||
predicate isSink() { sinkNode(this.getAnInput(), _) }
|
||||
|
||||
/** Holds if this API is supported by existing CodeQL libraries, that is, it is either a recognized source or sink or has a flow summary. */
|
||||
predicate isSupported() { hasSummary() or isSource() or isSink() }
|
||||
}
|
||||
|
||||
private class TestLibrary extends RefType {
|
||||
TestLibrary() {
|
||||
getPackage()
|
||||
.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%"
|
||||
])
|
||||
}
|
||||
}
|
||||
21
java/ql/src/Telemetry/ExternalLibraryUsage.ql
Normal file
21
java/ql/src/Telemetry/ExternalLibraryUsage.ql
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @name External libraries
|
||||
* @description A list of external libraries used in the code
|
||||
* @kind metric
|
||||
* @metricType callable
|
||||
* @id java/telemetry/external-libs
|
||||
*/
|
||||
|
||||
import java
|
||||
import ExternalAPI
|
||||
|
||||
from int usages, string jarname
|
||||
where
|
||||
usages =
|
||||
strictcount(Call c, ExternalAPI a |
|
||||
c.getCallee().getSourceDeclaration() = a and
|
||||
not c.getFile() instanceof GeneratedFile and
|
||||
a.jarContainer() = jarname and
|
||||
not a.isUninteresting()
|
||||
)
|
||||
select jarname, usages order by usages desc
|
||||
22
java/ql/src/Telemetry/SupportedExternalSinks.ql
Normal file
22
java/ql/src/Telemetry/SupportedExternalSinks.ql
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @name Supported sinks in external libraries
|
||||
* @description A list of 3rd party APIs detected as sinks. Excludes test and generated code.
|
||||
* @id java/telemetry/supported-external-api-sinks
|
||||
* @kind metric
|
||||
* @metricType callable
|
||||
*/
|
||||
|
||||
import java
|
||||
import ExternalAPI
|
||||
import semmle.code.java.GeneratedFiles
|
||||
|
||||
from ExternalAPI api, int usages
|
||||
where
|
||||
not api.isUninteresting() and
|
||||
api.isSink() and
|
||||
usages =
|
||||
strictcount(Call c |
|
||||
c.getCallee().getSourceDeclaration() = api and
|
||||
not c.getFile() instanceof GeneratedFile
|
||||
)
|
||||
select api.getApiName() as apiname, usages order by usages desc
|
||||
22
java/ql/src/Telemetry/SupportedExternalSources.ql
Normal file
22
java/ql/src/Telemetry/SupportedExternalSources.ql
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @name Supported sources in external libraries
|
||||
* @description A list of 3rd party APIs detected as sources. Excludes test and generated code.
|
||||
* @id java/telemetry/supported-external-api-sources
|
||||
* @kind metric
|
||||
* @metricType callable
|
||||
*/
|
||||
|
||||
import java
|
||||
import ExternalAPI
|
||||
import semmle.code.java.GeneratedFiles
|
||||
|
||||
from ExternalAPI api, int usages
|
||||
where
|
||||
not api.isUninteresting() and
|
||||
api.isSource() and
|
||||
usages =
|
||||
strictcount(Call c |
|
||||
c.getCallee().getSourceDeclaration() = api and
|
||||
not c.getFile() instanceof GeneratedFile
|
||||
)
|
||||
select api.getApiName() as apiname, usages order by usages desc
|
||||
22
java/ql/src/Telemetry/SupportedExternalTaint.ql
Normal file
22
java/ql/src/Telemetry/SupportedExternalTaint.ql
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @name Supported sinks in external libraries
|
||||
* @description A list of 3rd party APIs detected as sinks. Excludes test and generated code.
|
||||
* @id java/telemetry/supported-external-api-taint
|
||||
* @kind metric
|
||||
* @metricType callable
|
||||
*/
|
||||
|
||||
import java
|
||||
import ExternalAPI
|
||||
import semmle.code.java.GeneratedFiles
|
||||
|
||||
from ExternalAPI api, int usages
|
||||
where
|
||||
not api.isUninteresting() and
|
||||
api.hasSummary() and
|
||||
usages =
|
||||
strictcount(Call c |
|
||||
c.getCallee().getSourceDeclaration() = api and
|
||||
not c.getFile() instanceof GeneratedFile
|
||||
)
|
||||
select api.getApiName() as apiname, usages order by usages desc
|
||||
22
java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
Normal file
22
java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @name Usage of unsupported APIs coming from external libraries
|
||||
* @description A list of 3rd party APIs used in the codebase. Excludes test and generated code.
|
||||
* @id java/telemetry/unsupported-external-api
|
||||
* @kind metric
|
||||
* @metricType callable
|
||||
*/
|
||||
|
||||
import java
|
||||
import ExternalAPI
|
||||
import semmle.code.java.GeneratedFiles
|
||||
|
||||
from ExternalAPI api, int usages
|
||||
where
|
||||
not api.isUninteresting() and
|
||||
not api.isSupported() and
|
||||
usages =
|
||||
strictcount(Call c |
|
||||
c.getCallee().getSourceDeclaration() = api and
|
||||
not c.getFile() instanceof GeneratedFile
|
||||
)
|
||||
select api.getApiName() as apiname, usages order by usages desc
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
| rt.jar | 1 |
|
||||
@@ -0,0 +1 @@
|
||||
Telemetry/ExternalLibraryUsage.ql
|
||||
@@ -0,0 +1,9 @@
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
class ExternalLibraryUsage {
|
||||
public static void main(String[] args) {
|
||||
List<?> foo = new ArrayList(); // Java Runtime
|
||||
System.out.println(foo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
| java.io.FileWriter#FileWriter(File) | 1 |
|
||||
| java.net.URL#openStream() | 1 |
|
||||
@@ -0,0 +1,11 @@
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
class SupportedExternalSinks {
|
||||
public static void main(String[] args) throws Exception {
|
||||
new FileWriter(new File("foo"));
|
||||
new URL("http://foo").openStream();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Telemetry/SupportedExternalSinks.ql
|
||||
@@ -0,0 +1 @@
|
||||
| java.net.URLConnection#getInputStream() | 1 |
|
||||
@@ -0,0 +1,9 @@
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
class SupportedExternalSources {
|
||||
public static void main(String[] args) throws Exception {
|
||||
URL github = new URL("https://www.github.com/");
|
||||
InputStream stream = github.openConnection().getInputStream();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Telemetry/SupportedExternalSources.ql
|
||||
@@ -0,0 +1,2 @@
|
||||
| java.lang.StringBuilder#append(String) | 1 |
|
||||
| java.lang.StringBuilder#toString() | 1 |
|
||||
@@ -0,0 +1,7 @@
|
||||
class SupportedExternalTaint {
|
||||
public static void main(String[] args) throws Exception {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("foo");
|
||||
builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Telemetry/SupportedExternalTaint.ql
|
||||
@@ -0,0 +1,27 @@
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
class ExternalApiUsage {
|
||||
public static void main(String[] args) {
|
||||
List<?> foo = new ArrayList(); // already supported
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("foo", new Object());
|
||||
|
||||
Duration d = java.time.Duration.ofMillis(1000); // not supported
|
||||
|
||||
long l = "foo".length(); // not interesting
|
||||
|
||||
AtomicReference<String> ref = new AtomicReference<>(); // not supported
|
||||
ref.set("foo");
|
||||
|
||||
String.class.isAssignableFrom(Object.class); // parameter with generic type
|
||||
|
||||
System.out.println(d);
|
||||
System.out.println(map);
|
||||
System.out.println(foo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
| java.io.PrintStream#println(Object) | 3 |
|
||||
| java.lang.Class#isAssignableFrom(Class) | 1 |
|
||||
| java.lang.String#length() | 1 |
|
||||
| java.time.Duration#ofMillis(long) | 1 |
|
||||
| java.util.concurrent.atomic.AtomicReference#set(Object) | 1 |
|
||||
@@ -0,0 +1 @@
|
||||
Telemetry/UnsupportedExternalAPIs.ql
|
||||
Reference in New Issue
Block a user