Skip to content

Commit e71173d

Browse files
authored
Merge pull request #6591 from bmuskalla/inlineFlowTest
Java: Simplify setup for flow tests using `InlineExpectationsTest`
2 parents d52616b + 24d740b commit e71173d

File tree

24 files changed

+153
-987
lines changed

24 files changed

+153
-987
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/**
2+
* Provides a simple base test for flow-related tests using inline expectations.
3+
*
4+
* Example for a test.ql:
5+
* ```ql
6+
* import java
7+
* import TestUtilities.InlineFlowTest
8+
* ```
9+
*
10+
* To declare expecations, you can use the $hasTaintFlow or $hasValueFlow comments within the test source files.
11+
* Example of the corresponding test file, e.g. Test.java
12+
* ```java
13+
* public class Test {
14+
*
15+
* Object source() { return null; }
16+
* String taint() { return null; }
17+
* void sink(Object o) { }
18+
*
19+
* public void test() {
20+
* Object s = source();
21+
* sink(s); //$hasValueFlow
22+
* String t = "foo" + taint();
23+
* sink(t); //$hasTaintFlow
24+
* }
25+
*
26+
* }
27+
* ```
28+
*
29+
* If you're not interested in a specific flow type, you can disable either value or taint flow expectations as follows:
30+
* ```ql
31+
* class HasFlowTest extends InlineFlowTest {
32+
* override DataFlow::Configuration getTaintFlowConfig() { none() }
33+
*
34+
* override DataFlow::Configuration getValueFlowConfig() { none() }
35+
* }
36+
* ```
37+
*
38+
* If you need more fine-grained tuning, consider implementing a test using `InlineExpectationsTest`.
39+
*/
40+
41+
import semmle.code.java.dataflow.DataFlow
42+
import semmle.code.java.dataflow.ExternalFlow
43+
import semmle.code.java.dataflow.TaintTracking
44+
import TestUtilities.InlineExpectationsTest
45+
46+
class DefaultValueFlowConf extends DataFlow::Configuration {
47+
DefaultValueFlowConf() { this = "qltest:defaultValueFlowConf" }
48+
49+
override predicate isSource(DataFlow::Node n) {
50+
n.asExpr().(MethodAccess).getMethod().getName() = ["source", "taint"]
51+
}
52+
53+
override predicate isSink(DataFlow::Node n) {
54+
exists(MethodAccess ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument())
55+
}
56+
57+
override int fieldFlowBranchLimit() { result = 1000 }
58+
}
59+
60+
class DefaultTaintFlowConf extends TaintTracking::Configuration {
61+
DefaultTaintFlowConf() { this = "qltest:defaultTaintFlowConf" }
62+
63+
override predicate isSource(DataFlow::Node n) {
64+
n.asExpr().(MethodAccess).getMethod().getName() = ["source", "taint"]
65+
}
66+
67+
override predicate isSink(DataFlow::Node n) {
68+
exists(MethodAccess ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument())
69+
}
70+
71+
override int fieldFlowBranchLimit() { result = 1000 }
72+
}
73+
74+
class InlineFlowTest extends InlineExpectationsTest {
75+
InlineFlowTest() { this = "HasFlowTest" }
76+
77+
override string getARelevantTag() { result = ["hasValueFlow", "hasTaintFlow"] }
78+
79+
override predicate hasActualResult(Location location, string element, string tag, string value) {
80+
tag = "hasValueFlow" and
81+
exists(DataFlow::Node src, DataFlow::Node sink | getValueFlowConfig().hasFlow(src, sink) |
82+
sink.getLocation() = location and
83+
element = sink.toString() and
84+
value = ""
85+
)
86+
or
87+
tag = "hasTaintFlow" and
88+
exists(DataFlow::Node src, DataFlow::Node sink |
89+
getTaintFlowConfig().hasFlow(src, sink) and not getValueFlowConfig().hasFlow(src, sink)
90+
|
91+
sink.getLocation() = location and
92+
element = sink.toString() and
93+
value = ""
94+
)
95+
}
96+
97+
DataFlow::Configuration getValueFlowConfig() { result = any(DefaultValueFlowConf config) }
98+
99+
DataFlow::Configuration getTaintFlowConfig() { result = any(DefaultTaintFlowConf config) }
100+
}
Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import java
22
import semmle.code.java.dataflow.DataFlow
33
import semmle.code.java.dataflow.ExternalFlow
4-
import TestUtilities.InlineExpectationsTest
5-
import DataFlow
4+
import TestUtilities.InlineFlowTest
65

76
class SummaryModelTest extends SummaryModelCsv {
87
override predicate row(string row) {
@@ -15,25 +14,6 @@ class SummaryModelTest extends SummaryModelCsv {
1514
}
1615
}
1716

18-
class ContainerFlowConf extends Configuration {
19-
ContainerFlowConf() { this = "qltest:ContainerFlowConf" }
20-
21-
override predicate isSource(Node n) { n.asExpr().(MethodAccess).getMethod().hasName("source") }
22-
23-
override predicate isSink(Node n) { n.asExpr().(Argument).getCall().getCallee().hasName("sink") }
24-
}
25-
26-
class HasFlowTest extends InlineExpectationsTest {
27-
HasFlowTest() { this = "HasFlowTest" }
28-
29-
override string getARelevantTag() { result = "hasValueFlow" }
30-
31-
override predicate hasActualResult(Location location, string element, string tag, string value) {
32-
tag = "hasValueFlow" and
33-
exists(Node src, Node sink, ContainerFlowConf conf | conf.hasFlow(src, sink) |
34-
sink.getLocation() = location and
35-
element = sink.toString() and
36-
value = ""
37-
)
38-
}
17+
class HasFlowTest extends InlineFlowTest {
18+
override DataFlow::Configuration getTaintFlowConfig() { none() }
3919
}
Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,7 @@
11
import java
22
import semmle.code.java.dataflow.DataFlow
33
import semmle.code.java.dataflow.FlowSteps
4-
import TestUtilities.InlineExpectationsTest
5-
6-
class Conf extends DataFlow::Configuration {
7-
Conf() { this = "qltest:dataflow:fluent-methods" }
8-
9-
override predicate isSource(DataFlow::Node n) {
10-
n.asExpr().(MethodAccess).getMethod().hasName("source")
11-
}
12-
13-
override predicate isSink(DataFlow::Node n) {
14-
exists(MethodAccess ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument())
15-
}
16-
}
4+
import TestUtilities.InlineFlowTest
175

186
class Model extends FluentMethod {
197
Model() { this.getName() = "modelledFluentMethod" }
@@ -25,17 +13,6 @@ class IdentityModel extends ValuePreservingMethod {
2513
override predicate returnsValue(int arg) { arg = 0 }
2614
}
2715

28-
class HasFlowTest extends InlineExpectationsTest {
29-
HasFlowTest() { this = "HasFlowTest" }
30-
31-
override string getARelevantTag() { result = "hasTaintFlow" }
32-
33-
override predicate hasActualResult(Location location, string element, string tag, string value) {
34-
tag = "hasTaintFlow" and
35-
exists(DataFlow::Node src, DataFlow::Node sink, Conf conf | conf.hasFlow(src, sink) |
36-
sink.getLocation() = location and
37-
element = sink.toString() and
38-
value = ""
39-
)
40-
}
16+
class HasFlowTest extends InlineFlowTest {
17+
override DataFlow::Configuration getValueFlowConfig() { none() }
4118
}
Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,47 @@
11
import java.util.Formatter;
22
import java.lang.StringBuilder;
33

4-
5-
64
class A {
7-
public static String taint() { return "tainted"; }
5+
public static String source() {
6+
return "tainted";
7+
}
88

99
public static void test1() {
10-
String bad = taint();
10+
String bad = source(); // $ hasTaintFlow
1111
String good = "hi";
1212

13-
bad.formatted(good);
14-
good.formatted("a", bad, "b", good);
15-
String.format("%s%s", bad, good);
13+
bad.formatted(good); // $ hasTaintFlow
14+
good.formatted("a", bad, "b", good); // $ hasTaintFlow
15+
String.format("%s%s", bad, good); // $ hasTaintFlow
1616
String.format("%s", good);
17-
String.format("%s %s %s %s %s %s %s %s %s %s ", "a", "a", "a", "a", "a", "a", "a", "a", "a", bad);
17+
String.format("%s %s %s %s %s %s %s %s %s %s ", "a", "a", "a", "a", "a", "a", "a", "a", "a", bad); // $ hasTaintFlow
1818
}
1919

2020
public static void test2() {
21-
String bad = taint();
21+
String bad = source(); // $ hasTaintFlow
2222
Formatter f = new Formatter();
2323

2424
f.toString();
25-
f.format("%s", bad);
26-
f.toString();
25+
f.format("%s", bad); // $ hasTaintFlow
26+
f.toString(); // $ hasTaintFlow
2727
}
2828

2929
public static void test3() {
30-
String bad = taint();
30+
String bad = source(); // $ hasTaintFlow
3131
StringBuilder sb = new StringBuilder();
3232
Formatter f = new Formatter(sb);
3333

34-
sb.toString(); // false positive
35-
f.format("%s", bad);
36-
sb.toString();
34+
sb.toString(); // $ hasTaintFlow false positive
35+
f.format("%s", bad); // $ hasTaintFlow
36+
sb.toString(); // $ hasTaintFlow
3737
}
3838

3939
public static void test4() {
40-
String bad = taint();
40+
String bad = source(); // $ hasTaintFlow
4141
StringBuilder sb = new StringBuilder();
4242

43-
sb.append(bad);
43+
sb.append(bad); // $ hasTaintFlow
4444

45-
new Formatter(sb).format("ok").toString();
45+
new Formatter(sb).format("ok").toString(); // $ hasTaintFlow
4646
}
4747
}
Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +0,0 @@
1-
| A.java:10:22:10:28 | taint(...) | A.java:10:22:10:28 | taint(...) |
2-
| A.java:10:22:10:28 | taint(...) | A.java:13:9:13:11 | bad |
3-
| A.java:10:22:10:28 | taint(...) | A.java:13:9:13:27 | formatted(...) |
4-
| A.java:10:22:10:28 | taint(...) | A.java:14:9:14:43 | formatted(...) |
5-
| A.java:10:22:10:28 | taint(...) | A.java:14:9:14:43 | new ..[] { .. } |
6-
| A.java:10:22:10:28 | taint(...) | A.java:14:29:14:31 | bad |
7-
| A.java:10:22:10:28 | taint(...) | A.java:15:9:15:40 | format(...) |
8-
| A.java:10:22:10:28 | taint(...) | A.java:15:9:15:40 | new ..[] { .. } |
9-
| A.java:10:22:10:28 | taint(...) | A.java:15:31:15:33 | bad |
10-
| A.java:10:22:10:28 | taint(...) | A.java:17:9:17:105 | format(...) |
11-
| A.java:10:22:10:28 | taint(...) | A.java:17:9:17:105 | new ..[] { .. } |
12-
| A.java:10:22:10:28 | taint(...) | A.java:17:102:17:104 | bad |
13-
| A.java:10:22:10:28 | taint(...) | file:///modules/java.base/java/lang/String.class:0:0:0:0 | [summary] read: [] of argument 0 in formatted |
14-
| A.java:10:22:10:28 | taint(...) | file:///modules/java.base/java/lang/String.class:0:0:0:0 | [summary] read: [] of argument 1 in format |
15-
| A.java:10:22:10:28 | taint(...) | file:///modules/java.base/java/lang/String.class:0:0:0:0 | [summary] to write: return (return) in format |
16-
| A.java:10:22:10:28 | taint(...) | file:///modules/java.base/java/lang/String.class:0:0:0:0 | [summary] to write: return (return) in formatted |
17-
| A.java:10:22:10:28 | taint(...) | file:///modules/java.base/java/lang/String.class:0:0:0:0 | parameter this |
18-
| A.java:10:22:10:28 | taint(...) | file://:0:0:0:0 | p0 |
19-
| A.java:10:22:10:28 | taint(...) | file://:0:0:0:0 | p1 |
20-
| A.java:21:22:21:28 | taint(...) | A.java:21:22:21:28 | taint(...) |
21-
| A.java:21:22:21:28 | taint(...) | A.java:25:9:25:9 | f [post update] |
22-
| A.java:21:22:21:28 | taint(...) | A.java:25:9:25:27 | format(...) |
23-
| A.java:21:22:21:28 | taint(...) | A.java:25:9:25:27 | new ..[] { .. } |
24-
| A.java:21:22:21:28 | taint(...) | A.java:25:24:25:26 | bad |
25-
| A.java:21:22:21:28 | taint(...) | A.java:26:9:26:9 | f |
26-
| A.java:21:22:21:28 | taint(...) | A.java:26:9:26:20 | toString(...) |
27-
| A.java:30:22:30:28 | taint(...) | A.java:30:22:30:28 | taint(...) |
28-
| A.java:30:22:30:28 | taint(...) | A.java:34:9:34:10 | sb |
29-
| A.java:30:22:30:28 | taint(...) | A.java:34:9:34:21 | toString(...) |
30-
| A.java:30:22:30:28 | taint(...) | A.java:35:9:35:9 | f [post update] |
31-
| A.java:30:22:30:28 | taint(...) | A.java:35:9:35:27 | format(...) |
32-
| A.java:30:22:30:28 | taint(...) | A.java:35:9:35:27 | new ..[] { .. } |
33-
| A.java:30:22:30:28 | taint(...) | A.java:35:24:35:26 | bad |
34-
| A.java:30:22:30:28 | taint(...) | A.java:36:9:36:10 | sb |
35-
| A.java:30:22:30:28 | taint(...) | A.java:36:9:36:21 | toString(...) |
36-
| A.java:30:22:30:28 | taint(...) | file:///modules/java.base/java/lang/StringBuilder.class:0:0:0:0 | [summary] to write: return (return) in toString |
37-
| A.java:30:22:30:28 | taint(...) | file:///modules/java.base/java/lang/StringBuilder.class:0:0:0:0 | parameter this |
38-
| A.java:40:22:40:28 | taint(...) | A.java:40:22:40:28 | taint(...) |
39-
| A.java:40:22:40:28 | taint(...) | A.java:43:9:43:10 | sb [post update] |
40-
| A.java:40:22:40:28 | taint(...) | A.java:43:9:43:22 | append(...) |
41-
| A.java:40:22:40:28 | taint(...) | A.java:43:19:43:21 | bad |
42-
| A.java:40:22:40:28 | taint(...) | A.java:45:9:45:25 | new Formatter(...) |
43-
| A.java:40:22:40:28 | taint(...) | A.java:45:9:45:38 | format(...) |
44-
| A.java:40:22:40:28 | taint(...) | A.java:45:9:45:49 | toString(...) |
45-
| A.java:40:22:40:28 | taint(...) | A.java:45:23:45:24 | sb |
46-
| A.java:40:22:40:28 | taint(...) | file:///modules/java.base/java/lang/StringBuilder.class:0:0:0:0 | [summary] to write: argument -1 in append |
47-
| A.java:40:22:40:28 | taint(...) | file:///modules/java.base/java/lang/StringBuilder.class:0:0:0:0 | [summary] to write: return (return) in append |
48-
| A.java:40:22:40:28 | taint(...) | file:///modules/java.base/java/lang/StringBuilder.class:0:0:0:0 | parameter this |
49-
| A.java:40:22:40:28 | taint(...) | file://:0:0:0:0 | p0 |
Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,8 @@
11
import java
2+
import semmle.code.java.dataflow.DataFlow
23
import semmle.code.java.dataflow.TaintTracking
4+
import TestUtilities.InlineFlowTest
35

4-
class Conf extends TaintTracking::Configuration {
5-
Conf() { this = "qltest:dataflow:format" }
6-
7-
override predicate isSource(DataFlow::Node n) {
8-
n.asExpr().(MethodAccess).getMethod().hasName("taint")
9-
}
10-
11-
override predicate isSink(DataFlow::Node n) { any() }
6+
class TaintFlowConf extends DefaultTaintFlowConf {
7+
override predicate isSink(DataFlow::Node n) { n instanceof DataFlow::ExprNode }
128
}
13-
14-
from DataFlow::Node src, DataFlow::Node sink, Conf conf
15-
where conf.hasFlow(src, sink)
16-
select src, sink
Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,2 @@
11
import java
2-
import semmle.code.java.dataflow.DataFlow
3-
import semmle.code.java.dataflow.TaintTracking
4-
import semmle.code.java.dataflow.FlowSources
5-
import TestUtilities.InlineExpectationsTest
6-
7-
class Conf extends TaintTracking::Configuration {
8-
Conf() { this = "qltest:dataflow:jackson" }
9-
10-
override predicate isSource(DataFlow::Node n) {
11-
n.asExpr().(MethodAccess).getMethod().hasName("taint")
12-
or
13-
n instanceof RemoteFlowSource
14-
}
15-
16-
override predicate isSink(DataFlow::Node n) {
17-
exists(MethodAccess ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument())
18-
}
19-
}
20-
21-
class HasFlowTest extends InlineExpectationsTest {
22-
HasFlowTest() { this = "HasFlowTest" }
23-
24-
override string getARelevantTag() { result = "hasTaintFlow" }
25-
26-
override predicate hasActualResult(Location location, string element, string tag, string value) {
27-
tag = "hasTaintFlow" and
28-
exists(DataFlow::Node src, DataFlow::Node sink, Conf conf | conf.hasFlow(src, sink) |
29-
sink.getLocation() = location and
30-
element = sink.toString() and
31-
value = ""
32-
)
33-
}
34-
}
2+
import TestUtilities.InlineFlowTest

0 commit comments

Comments
 (0)