Skip to content

Commit c4956a4

Browse files
authored
Merge pull request github#6376 from bmuskalla/thirdpartyapitelemtry
Java: Introduce queries to capture information about 3rd party API usage
2 parents 5d58edb + c0e65e7 commit c4956a4

23 files changed

+297
-15
lines changed

java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -616,26 +616,25 @@ private predicate elementSpec(
616616
summaryModel(namespace, type, subtypes, name, signature, ext, _, _, _)
617617
}
618618

619-
private predicate relevantCallable(Callable c) { elementSpec(_, _, _, c.getName(), _, _) }
620-
621619
private string paramsStringPart(Callable c, int i) {
622-
relevantCallable(c) and
623-
(
624-
i = -1 and result = "("
625-
or
626-
exists(int n, string p | c.getParameterType(n).getErasure().toString() = p |
627-
i = 2 * n and result = p
628-
or
629-
i = 2 * n - 1 and result = "," and n != 0
630-
)
620+
i = -1 and result = "("
621+
or
622+
exists(int n, string p | c.getParameterType(n).getErasure().toString() = p |
623+
i = 2 * n and result = p
631624
or
632-
i = 2 * c.getNumberOfParameters() and result = ")"
625+
i = 2 * n - 1 and result = "," and n != 0
633626
)
627+
or
628+
i = 2 * c.getNumberOfParameters() and result = ")"
634629
}
635630

636-
private string paramsString(Callable c) {
637-
result = concat(int i | | paramsStringPart(c, i) order by i)
638-
}
631+
/**
632+
* Gets a parenthesized string containing all parameter types of this callable, separated by a comma.
633+
*
634+
* Returns the empty string if the callable has no parameters.
635+
* Parameter types are represented by their type erasure.
636+
*/
637+
string paramsString(Callable c) { result = concat(int i | | paramsStringPart(c, i) order by i) }
639638

640639
private Element interpretElement0(
641640
string namespace, string type, boolean subtypes, string name, string signature

java/ql/src/Telemetry/ExternalAPI.qll

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/** Provides classes and predicates related to handling APIs from external libraries. */
2+
3+
private import java
4+
private import semmle.code.java.dataflow.DataFlow
5+
private import semmle.code.java.dataflow.DataFlow
6+
private import semmle.code.java.dataflow.ExternalFlow
7+
private import semmle.code.java.dataflow.FlowSources
8+
private import semmle.code.java.dataflow.FlowSummary
9+
private import semmle.code.java.dataflow.internal.DataFlowPrivate
10+
private import semmle.code.java.dataflow.TaintTracking
11+
12+
/**
13+
* An external API from either the Java Standard Library or a 3rd party library.
14+
*/
15+
class ExternalAPI extends Callable {
16+
ExternalAPI() { not this.fromSource() }
17+
18+
/** Holds if this API is not worth supporting */
19+
predicate isUninteresting() { isTestLibrary() or isParameterlessConstructor() }
20+
21+
/** Holds if this API is is a constructor without parameters */
22+
predicate isParameterlessConstructor() {
23+
this instanceof Constructor and this.getNumberOfParameters() = 0
24+
}
25+
26+
/** Holds if this API is part of a common testing library or framework */
27+
private predicate isTestLibrary() { getDeclaringType() instanceof TestLibrary }
28+
29+
/**
30+
* Gets information about the external API in the form expected by the CSV modeling framework.
31+
*/
32+
string getApiName() {
33+
result =
34+
this.getDeclaringType().getPackage() + "." + this.getDeclaringType().getSourceDeclaration() +
35+
"#" + this.getName() + paramsString(this)
36+
}
37+
38+
/**
39+
* Gets the jar file containing this API. Normalizes the Java Runtime to "rt.jar" despite the presence of modules.
40+
*/
41+
string jarContainer() { result = containerAsJar(this.getCompilationUnit().getParentContainer*()) }
42+
43+
private string containerAsJar(Container container) {
44+
if container instanceof JarFile then result = container.getBaseName() else result = "rt.jar"
45+
}
46+
47+
/** Gets a node that is an input to a call to this API. */
48+
private DataFlow::Node getAnInput() {
49+
exists(Call call | call.getCallee().getSourceDeclaration() = this |
50+
result.asExpr().(Argument).getCall() = call or
51+
result.(ArgumentNode).getCall() = call
52+
)
53+
}
54+
55+
/** Gets a node that is an output from a call to this API. */
56+
private DataFlow::Node getAnOutput() {
57+
exists(Call call | call.getCallee().getSourceDeclaration() = this |
58+
result.asExpr() = call or
59+
result.(DataFlow::PostUpdateNode).getPreUpdateNode().(ArgumentNode).getCall() = call
60+
)
61+
}
62+
63+
/** Holds if this API has a supported summary. */
64+
predicate hasSummary() {
65+
this instanceof SummarizedCallable or
66+
TaintTracking::localAdditionalTaintStep(this.getAnInput(), _)
67+
}
68+
69+
/** Holds if this API is a known source. */
70+
predicate isSource() {
71+
this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _)
72+
}
73+
74+
/** Holds if this API is a known sink. */
75+
predicate isSink() { sinkNode(this.getAnInput(), _) }
76+
77+
/** 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. */
78+
predicate isSupported() { hasSummary() or isSource() or isSink() }
79+
}
80+
81+
private class TestLibrary extends RefType {
82+
TestLibrary() {
83+
getPackage()
84+
.getName()
85+
.matches([
86+
"org.junit%", "junit.%", "org.mockito%", "org.assertj%",
87+
"com.github.tomakehurst.wiremock%", "org.hamcrest%", "org.springframework.test.%",
88+
"org.springframework.mock.%", "org.springframework.boot.test.%", "reactor.test%",
89+
"org.xmlunit%", "org.testcontainers.%", "org.opentest4j%", "org.mockserver%",
90+
"org.powermock%", "org.skyscreamer.jsonassert%", "org.rnorth.visibleassertions",
91+
"org.openqa.selenium%", "com.gargoylesoftware.htmlunit%",
92+
"org.jboss.arquillian.testng%", "org.testng%"
93+
])
94+
}
95+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* @name External libraries
3+
* @description A list of external libraries used in the code
4+
* @kind metric
5+
* @metricType callable
6+
* @id java/telemetry/external-libs
7+
*/
8+
9+
import java
10+
import ExternalAPI
11+
12+
from int usages, string jarname
13+
where
14+
usages =
15+
strictcount(Call c, ExternalAPI a |
16+
c.getCallee().getSourceDeclaration() = a and
17+
not c.getFile() instanceof GeneratedFile and
18+
a.jarContainer() = jarname and
19+
not a.isUninteresting()
20+
)
21+
select jarname, usages order by usages desc
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* @name Supported sinks in external libraries
3+
* @description A list of 3rd party APIs detected as sinks. Excludes test and generated code.
4+
* @id java/telemetry/supported-external-api-sinks
5+
* @kind metric
6+
* @metricType callable
7+
*/
8+
9+
import java
10+
import ExternalAPI
11+
import semmle.code.java.GeneratedFiles
12+
13+
from ExternalAPI api, int usages
14+
where
15+
not api.isUninteresting() and
16+
api.isSink() and
17+
usages =
18+
strictcount(Call c |
19+
c.getCallee().getSourceDeclaration() = api and
20+
not c.getFile() instanceof GeneratedFile
21+
)
22+
select api.getApiName() as apiname, usages order by usages desc
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* @name Supported sources in external libraries
3+
* @description A list of 3rd party APIs detected as sources. Excludes test and generated code.
4+
* @id java/telemetry/supported-external-api-sources
5+
* @kind metric
6+
* @metricType callable
7+
*/
8+
9+
import java
10+
import ExternalAPI
11+
import semmle.code.java.GeneratedFiles
12+
13+
from ExternalAPI api, int usages
14+
where
15+
not api.isUninteresting() and
16+
api.isSource() and
17+
usages =
18+
strictcount(Call c |
19+
c.getCallee().getSourceDeclaration() = api and
20+
not c.getFile() instanceof GeneratedFile
21+
)
22+
select api.getApiName() as apiname, usages order by usages desc
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* @name Supported sinks in external libraries
3+
* @description A list of 3rd party APIs detected as sinks. Excludes test and generated code.
4+
* @id java/telemetry/supported-external-api-taint
5+
* @kind metric
6+
* @metricType callable
7+
*/
8+
9+
import java
10+
import ExternalAPI
11+
import semmle.code.java.GeneratedFiles
12+
13+
from ExternalAPI api, int usages
14+
where
15+
not api.isUninteresting() and
16+
api.hasSummary() and
17+
usages =
18+
strictcount(Call c |
19+
c.getCallee().getSourceDeclaration() = api and
20+
not c.getFile() instanceof GeneratedFile
21+
)
22+
select api.getApiName() as apiname, usages order by usages desc
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* @name Usage of unsupported APIs coming from external libraries
3+
* @description A list of 3rd party APIs used in the codebase. Excludes test and generated code.
4+
* @id java/telemetry/unsupported-external-api
5+
* @kind metric
6+
* @metricType callable
7+
*/
8+
9+
import java
10+
import ExternalAPI
11+
import semmle.code.java.GeneratedFiles
12+
13+
from ExternalAPI api, int usages
14+
where
15+
not api.isUninteresting() and
16+
not api.isSupported() and
17+
usages =
18+
strictcount(Call c |
19+
c.getCallee().getSourceDeclaration() = api and
20+
not c.getFile() instanceof GeneratedFile
21+
)
22+
select api.getApiName() as apiname, usages order by usages desc
Binary file not shown.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| rt.jar | 1 |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Telemetry/ExternalLibraryUsage.ql

0 commit comments

Comments
 (0)