diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll index 93cb973b7717..c38d0df4e57a 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll @@ -193,6 +193,18 @@ predicate readStep(Node node1, Content f, Node node2) { fr.getField() = f.(FieldContent).getField() and fr = node2.asExpr() ) + or + exists(Record r, Method getter, Field recf, MethodAccess get | + getter.getDeclaringType() = r and + recf.getDeclaringType() = r and + getter.getNumberOfParameters() = 0 and + getter.getName() = recf.getName() and + not exists(getter.getBody()) and + recf = f.(FieldContent).getField() and + get.getMethod() = getter and + node1.asExpr() = get.getQualifier() and + node2.asExpr() = get + ) } /** diff --git a/java/ql/test/library-tests/dataflow/records/A.java b/java/ql/test/library-tests/dataflow/records/A.java new file mode 100644 index 000000000000..c01b7a85ab53 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/records/A.java @@ -0,0 +1,65 @@ +public class A { + record Pair(Object x, Object y) { } + + static Object source() { return null; } + + void sink(Object o) { } + + void foo() { + Pair p1 = new Pair(source(), null); + Pair p2 = new Pair(new Object(), source()); + bar(p1, p2); + } + + void bar(Pair p1, Pair p2) { + sink(p1.x); + sink(p1.y); + sink(p2.x); + sink(p2.y); + Object p1x = p1.x(); + Object p1y = p1.y(); + Object p2x = p2.x(); + Object p2y = p2.y(); + sink(p1x); + sink(p1y); + sink(p2x); + sink(p2y); + } + + record RecWithGetter(Object f) { + public Object f() { + return this.f; + } + } + + record RecWithWeirdGetter1(Object f) { + public Object f() { + return new Object(); + } + } + + record RecWithWeirdGetter2(Object f) { + public Object f() { + return source(); + } + } + + void testExplicitGetter1() { + RecWithGetter r1 = new RecWithGetter(source()); + RecWithWeirdGetter1 r2 = new RecWithWeirdGetter1(source()); + RecWithWeirdGetter2 r3 = new RecWithWeirdGetter2(source()); + testExplicitGetter2(r1, r2, r3); + } + + void testExplicitGetter2(RecWithGetter r1, RecWithWeirdGetter1 r2, RecWithWeirdGetter2 r3) { + sink(r1.f); + sink(r2.f); + sink(r3.f); + Object r1f = r1.f(); + Object r2f = r2.f(); + Object r3f = r3.f(); + sink(r1f); + sink(r2f); + sink(r3f); + } +} diff --git a/java/ql/test/library-tests/dataflow/records/options b/java/ql/test/library-tests/dataflow/records/options new file mode 100644 index 000000000000..266b0eadc5e0 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/records/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args --enable-preview -source 14 -target 14 diff --git a/java/ql/test/library-tests/dataflow/records/test.expected b/java/ql/test/library-tests/dataflow/records/test.expected new file mode 100644 index 000000000000..3d497268db7b --- /dev/null +++ b/java/ql/test/library-tests/dataflow/records/test.expected @@ -0,0 +1,9 @@ +| A.java:9:24:9:31 | source(...) | A.java:15:10:15:13 | p1.x | +| A.java:9:24:9:31 | source(...) | A.java:23:10:23:12 | p1x | +| A.java:10:38:10:45 | source(...) | A.java:18:10:18:13 | p2.y | +| A.java:10:38:10:45 | source(...) | A.java:26:10:26:12 | p2y | +| A.java:43:14:43:21 | source(...) | A.java:63:10:63:12 | r3f | +| A.java:48:42:48:49 | source(...) | A.java:55:10:55:13 | r1.f | +| A.java:48:42:48:49 | source(...) | A.java:61:10:61:12 | r1f | +| A.java:49:54:49:61 | source(...) | A.java:56:10:56:13 | r2.f | +| A.java:50:54:50:61 | source(...) | A.java:57:10:57:13 | r3.f | diff --git a/java/ql/test/library-tests/dataflow/records/test.ql b/java/ql/test/library-tests/dataflow/records/test.ql new file mode 100644 index 000000000000..3ce69be095e1 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/records/test.ql @@ -0,0 +1,15 @@ +import java +import semmle.code.java.dataflow.DataFlow +import DataFlow + +class Conf extends Configuration { + Conf() { this = "qqconf" } + + override predicate isSource(Node n) { n.asExpr().(MethodAccess).getMethod().hasName("source") } + + override predicate isSink(Node n) { n.asExpr().(Argument).getCall().getCallee().hasName("sink") } +} + +from Conf conf, Node src, Node sink +where conf.hasFlow(src, sink) +select src, sink