Skip to content

Commit fadbb32

Browse files
committed
Add backward dataflow edges through fluent function invocations.
This means that much as obj.getA().setB(...) already has a side-effect on `obj`, all three setters in obj.setA(...).setB(...).setC(...) will have a side-effect on `obj`.
1 parent 37baf77 commit fadbb32

File tree

5 files changed

+102
-1
lines changed

5 files changed

+102
-1
lines changed

java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,10 @@ private predicate isAdditionalFlowStep(
223223
* Holds if data can flow in one local step from `node1` to `node2`.
224224
*/
225225
private predicate localFlowStep(Node node1, Node node2, Configuration config) {
226-
simpleLocalFlowStep(node1, node2) and
226+
(
227+
simpleLocalFlowStep(node1, node2) or
228+
reverseStepThroughInputOutputAlias(node1, node2)
229+
) and
227230
not outBarrier(node1, config) and
228231
not inBarrier(node2, config) and
229232
not fullBarrier(node1, config) and

java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,22 @@ private module Cached {
415415
store(node1, tc.getContent(), node2, contentType, tc.getContainerType())
416416
}
417417

418+
/**
419+
* Holds if data can flow from `fromNode` to `toNode` because they are the post-update
420+
* nodes of some function output and input respectively, where the output and input
421+
* are aliases. A typical example is a function returning `this`, implementing a fluent
422+
* interface.
423+
*/
424+
cached
425+
predicate reverseStepThroughInputOutputAlias(Node fromNode, Node toNode) {
426+
exists(Node fromPre, Node toPre |
427+
fromPre = fromNode.(PostUpdateNode).getPreUpdateNode() and
428+
toPre = toNode.(PostUpdateNode).getPreUpdateNode()
429+
|
430+
argumentValueFlowsThrough(toPre, TReadStepTypesNone(), fromPre)
431+
)
432+
}
433+
418434
/**
419435
* Holds if the call context `call` either improves virtual dispatch in
420436
* `callable` or if it allows us to prune unreachable nodes in `callable`.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package smowton;
2+
3+
public class Test {
4+
5+
private String field;
6+
7+
public Test fluentNoop() {
8+
return this;
9+
}
10+
11+
public Test indirectlyFluentNoop() {
12+
return this.fluentNoop();
13+
}
14+
15+
public Test fluentSet(String x) {
16+
this.field = x;
17+
return this;
18+
}
19+
20+
public static Test identity(Test t) {
21+
return t;
22+
}
23+
24+
public String get() {
25+
return field;
26+
}
27+
28+
public static String source() {
29+
return "taint";
30+
}
31+
32+
public static void sink(String s) {}
33+
34+
public static void test1() {
35+
Test t = new Test();
36+
t.fluentNoop().fluentSet(source()).fluentNoop();
37+
sink(t.get()); // $hasTaintFlow=y
38+
}
39+
40+
public static void test2() {
41+
Test t = new Test();
42+
Test.identity(t).fluentNoop().fluentSet(source()).fluentNoop();
43+
sink(t.get()); // $hasTaintFlow=y
44+
}
45+
46+
public static void test3() {
47+
Test t = new Test();
48+
t.indirectlyFluentNoop().fluentSet(source()).fluentNoop();
49+
sink(t.get()); // $hasTaintFlow=y
50+
}
51+
52+
}

java/ql/test/library-tests/dataflow/fluent-methods/flow.expected

Whitespace-only changes.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import java
2+
import semmle.code.java.dataflow.TaintTracking
3+
import TestUtilities.InlineExpectationsTest
4+
5+
class Conf extends TaintTracking::Configuration {
6+
Conf() { this = "qltest:dataflow:fluent-methods" }
7+
8+
override predicate isSource(DataFlow::Node n) {
9+
n.asExpr().(MethodAccess).getMethod().hasName("source")
10+
}
11+
12+
override predicate isSink(DataFlow::Node n) {
13+
exists(MethodAccess ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument())
14+
}
15+
}
16+
17+
class HasFlowTest extends InlineExpectationsTest {
18+
HasFlowTest() { this = "HasFlowTest" }
19+
20+
override string getARelevantTag() { result = "hasTaintFlow" }
21+
22+
override predicate hasActualResult(Location location, string element, string tag, string value) {
23+
tag = "hasTaintFlow" and
24+
exists(DataFlow::Node src, DataFlow::Node sink, Conf conf | conf.hasFlow(src, sink) |
25+
sink.getLocation() = location and
26+
element = sink.toString() and
27+
value = "y"
28+
)
29+
}
30+
}

0 commit comments

Comments
 (0)