diff --git a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll index 535c667698f..e1d92779bbe 100644 --- a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll +++ b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll @@ -82,6 +82,7 @@ private module Frameworks { private import semmle.code.java.frameworks.guava.Guava private import semmle.code.java.frameworks.jackson.JacksonSerializability private import semmle.code.java.frameworks.JaxWS + private import semmle.code.java.frameworks.Optional private import semmle.code.java.frameworks.spring.SpringHttp private import semmle.code.java.frameworks.spring.SpringWebClient private import semmle.code.java.security.ResponseSplitting diff --git a/java/ql/src/semmle/code/java/frameworks/Optional.qll b/java/ql/src/semmle/code/java/frameworks/Optional.qll new file mode 100644 index 00000000000..9747de229ea --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/Optional.qll @@ -0,0 +1,21 @@ +/** Definitions related to `java.util.Optional`. */ + +import semmle.code.java.dataflow.ExternalFlow + +private class OptionalModel extends SummaryModelCsv { + override predicate row(string s) { + s = + [ + "java.util;Optional;false;filter;;;Element of Argument[-1];Element of ReturnValue;value", + "java.util;Optional;false;get;;;Element of Argument[-1];ReturnValue;value", + "java.util;Optional;false;of;;;Argument[0];Element of ReturnValue;value", + "java.util;Optional;false;ofNullable;;;Argument[0];Element of ReturnValue;value", + "java.util;Optional;false;or;;;Element of Argument[-1];Element of ReturnValue;value", + "java.util;Optional;false;orElse;;;Element of Argument[-1];ReturnValue;value", + "java.util;Optional;false;orElse;;;Argument[0];ReturnValue;value", + "java.util;Optional;false;orElseGet;;;Element of Argument[-1];ReturnValue;value", + "java.util;Optional;false;orElseThrow;;;Element of Argument[-1];ReturnValue;value", + "java.util;Optional;false;stream;;;Element of Argument[-1];Element of ReturnValue;value" + ] + } +} diff --git a/java/ql/test/library-tests/optional/Test.java b/java/ql/test/library-tests/optional/Test.java new file mode 100644 index 00000000000..b7aa992124d --- /dev/null +++ b/java/ql/test/library-tests/optional/Test.java @@ -0,0 +1,98 @@ +package generatedtest; + +import java.util.Optional; +import java.util.stream.Stream; + +// Test case generated by GenerateFlowTestCase.ql +public class Test { + + Object getElement(Optional container) { return container.get(); } + Object getStreamElement(Stream container) { return null; /* Modelled in .ql file */ } + Optional newWithElement(Object element) { return Optional.of(element); } + Object source() { return null; } + void sink(Object o) { } + + public void test() { + + { + // "java.util;Optional;false;filter;;;Element of Argument[-1];Element of ReturnValue;value" + Optional out = null; + Optional in = newWithElement(source()); + out = in.filter(null); + sink(getElement(out)); // $hasValueFlow + } + { + // "java.util;Optional;false;get;;;Element of Argument[-1];ReturnValue;value" + Object out = null; + Optional in = newWithElement(source()); + out = in.get(); + sink(out); // $hasValueFlow + } + { + // "java.util;Optional;false;of;;;Argument[0];Element of ReturnValue;value" + Optional out = null; + Object in = (Object)source(); + out = Optional.of(in); + sink(getElement(out)); // $hasValueFlow + } + { + // "java.util;Optional;false;ofNullable;;;Argument[0];Element of ReturnValue;value" + Optional out = null; + Object in = (Object)source(); + out = Optional.ofNullable(in); + sink(getElement(out)); // $hasValueFlow + } + { + // "java.util;Optional;false;or;;;Element of Argument[-1];Element of ReturnValue;value" + Optional out = null; + Optional in = newWithElement(source()); + out = in.or(null); + sink(getElement(out)); // $hasValueFlow + } + { + // "java.util;Optional;false;orElse;;;Argument[0];ReturnValue;value" + Object out = null; + Object in = (Object)source(); + Optional instance = null; + out = instance.orElse(in); + sink(out); // $hasValueFlow + } + { + // "java.util;Optional;false;orElse;;;Element of Argument[-1];ReturnValue;value" + Object out = null; + Optional in = newWithElement(source()); + out = in.orElse(null); + sink(out); // $hasValueFlow + } + { + // "java.util;Optional;false;orElseGet;;;Element of Argument[-1];ReturnValue;value" + Object out = null; + Optional in = newWithElement(source()); + out = in.orElseGet(null); + sink(out); // $hasValueFlow + } + { + // "java.util;Optional;false;orElseThrow;;;Element of Argument[-1];ReturnValue;value" + Object out = null; + Optional in = newWithElement(source()); + out = in.orElseThrow(null); + sink(out); // $hasValueFlow + } + { + // "java.util;Optional;false;orElseThrow;;;Element of Argument[-1];ReturnValue;value" + Object out = null; + Optional in = newWithElement(source()); + out = in.orElseThrow(); + sink(out); // $hasValueFlow + } + { + // "java.util;Optional;false;stream;;;Element of Argument[-1];Element of ReturnValue;value" + Stream out = null; + Optional in = newWithElement(source()); + out = in.stream(); + sink(getStreamElement(out)); // $hasValueFlow + } + + } + +} \ No newline at end of file diff --git a/java/ql/test/library-tests/optional/test.expected b/java/ql/test/library-tests/optional/test.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/java/ql/test/library-tests/optional/test.ql b/java/ql/test/library-tests/optional/test.ql new file mode 100644 index 00000000000..7beccd686cd --- /dev/null +++ b/java/ql/test/library-tests/optional/test.ql @@ -0,0 +1,63 @@ +import java +import semmle.code.java.dataflow.DataFlow +import semmle.code.java.dataflow.ExternalFlow +import semmle.code.java.dataflow.TaintTracking +import TestUtilities.InlineExpectationsTest + +class SummaryModelTest extends SummaryModelCsv { + override predicate row(string row) { + row = + [ + //"package;type;overrides;name;signature;ext;inputspec;outputspec;kind", + "generatedtest;Test;false;getStreamElement;;;Element of Argument[0];ReturnValue;value" + ] + } +} + +class ValueFlowConf extends DataFlow::Configuration { + ValueFlowConf() { this = "qltest:valueFlowConf" } + + override predicate isSource(DataFlow::Node n) { + n.asExpr().(MethodAccess).getMethod().hasName("source") + } + + override predicate isSink(DataFlow::Node n) { + n.asExpr().(Argument).getCall().getCallee().hasName("sink") + } +} + +class TaintFlowConf extends TaintTracking::Configuration { + TaintFlowConf() { this = "qltest:taintFlowConf" } + + override predicate isSource(DataFlow::Node n) { + n.asExpr().(MethodAccess).getMethod().hasName("source") + } + + override predicate isSink(DataFlow::Node n) { + n.asExpr().(Argument).getCall().getCallee().hasName("sink") + } +} + +class HasFlowTest extends InlineExpectationsTest { + HasFlowTest() { this = "HasFlowTest" } + + override string getARelevantTag() { result = ["hasValueFlow", "hasTaintFlow"] } + + override predicate hasActualResult(Location location, string element, string tag, string value) { + tag = "hasValueFlow" and + exists(DataFlow::Node src, DataFlow::Node sink, ValueFlowConf conf | conf.hasFlow(src, sink) | + sink.getLocation() = location and + element = sink.toString() and + value = "" + ) + or + tag = "hasTaintFlow" and + exists(DataFlow::Node src, DataFlow::Node sink, TaintFlowConf conf | + conf.hasFlow(src, sink) and not any(ValueFlowConf c).hasFlow(src, sink) + | + sink.getLocation() = location and + element = sink.toString() and + value = "" + ) + } +}