diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll index 0abf432e1ba..8434d74d839 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll @@ -400,6 +400,19 @@ predicate simpleLocalFlowStep(Node node1, Node node2) { node2.asExpr().(ChooseExpr).getAResultExpr() = node1.asExpr() or node2.asExpr().(AssignExpr).getSource() = node1.asExpr() + or + exists(MethodAccess ma, Method m | + ma = node2.asExpr() and + m = ma.getMethod() and + m.getDeclaringType().hasQualifiedName("java.util", "Objects") and + ( + m.hasName(["requireNonNull", "requireNonNullElseGet"]) and node1.asExpr() = ma.getArgument(0) + or + m.hasName("requireNonNullElse") and node1.asExpr() = ma.getAnArgument() + or + m.hasName("toString") and node1.asExpr() = ma.getArgument(1) + ) + ) } /** diff --git a/java/ql/test/library-tests/dataflow/local-flow/ObjectsTest.java b/java/ql/test/library-tests/dataflow/local-flow/ObjectsTest.java new file mode 100644 index 00000000000..c74dc0f434e --- /dev/null +++ b/java/ql/test/library-tests/dataflow/local-flow/ObjectsTest.java @@ -0,0 +1,14 @@ +import java.util.Objects; + +class ObjectsTest { + public static void valueSteps() { + sink(Objects.requireNonNull(source())); + sink(Objects.requireNonNull(source(), "message")); + sink(Objects.requireNonNull(source(), () -> "value1")); + sink(Objects.requireNonNullElse(source(), source())); + sink(Objects.requireNonNullElseGet(source(), () -> "value2")); + sink(Objects.toString(null, source())); + } + private static T source() { return null; } + private static void sink(Object o) {} +} diff --git a/java/ql/test/library-tests/dataflow/local-flow/flow.expected b/java/ql/test/library-tests/dataflow/local-flow/flow.expected new file mode 100644 index 00000000000..22a78a4694e --- /dev/null +++ b/java/ql/test/library-tests/dataflow/local-flow/flow.expected @@ -0,0 +1,7 @@ +| ObjectsTest.java:5:31:5:38 | source(...) | ObjectsTest.java:5:8:5:39 | requireNonNull(...) | +| ObjectsTest.java:6:31:6:38 | source(...) | ObjectsTest.java:6:8:6:50 | requireNonNull(...) | +| ObjectsTest.java:7:31:7:38 | source(...) | ObjectsTest.java:7:8:7:55 | requireNonNull(...) | +| ObjectsTest.java:8:35:8:42 | source(...) | ObjectsTest.java:8:8:8:53 | requireNonNullElse(...) | +| ObjectsTest.java:8:45:8:52 | source(...) | ObjectsTest.java:8:8:8:53 | requireNonNullElse(...) | +| ObjectsTest.java:9:38:9:45 | source(...) | ObjectsTest.java:9:8:9:62 | requireNonNullElseGet(...) | +| ObjectsTest.java:10:31:10:38 | source(...) | ObjectsTest.java:10:8:10:39 | toString(...) | diff --git a/java/ql/test/library-tests/dataflow/local-flow/flow.ql b/java/ql/test/library-tests/dataflow/local-flow/flow.ql new file mode 100644 index 00000000000..b568a1be73d --- /dev/null +++ b/java/ql/test/library-tests/dataflow/local-flow/flow.ql @@ -0,0 +1,21 @@ +import java +import semmle.code.java.dataflow.DataFlow + +class Conf extends DataFlow::Configuration { + Conf() { this = "conf" } + + override predicate isSource(DataFlow::Node src) { + src.asExpr().(MethodAccess).getMethod().hasName("source") + } + + override predicate isSink(DataFlow::Node sink) { + exists(MethodAccess ma | + sink.asExpr() = ma.getAnArgument() and + ma.getMethod().hasName("sink") + ) + } +} + +from Conf c, DataFlow::Node src, DataFlow::Node sink +where c.hasFlow(src, sink) +select src, sink diff --git a/java/ql/test/library-tests/dataflow/local-flow/options b/java/ql/test/library-tests/dataflow/local-flow/options new file mode 100644 index 00000000000..3f12170222c --- /dev/null +++ b/java/ql/test/library-tests/dataflow/local-flow/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args -source 14 -target 14