From b0ff1da2e7dcfd8a69bf392446d90274b290ccfa Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 28 Jul 2022 14:24:14 +0200 Subject: [PATCH] Java: Add support for data flow through thrown exceptions. --- .../semmle/code/java/dataflow/FlowSummary.qll | 3 +- .../java/dataflow/internal/DataFlowNodes.qll | 130 ++++++++++++++++- .../dataflow/internal/DataFlowPrivate.qll | 134 ++++++++++++++++-- .../java/dataflow/internal/DataFlowUtil.qll | 21 +-- .../internal/FlowSummaryImplSpecific.qll | 26 ++-- .../library-tests/dataflow/capture/B.java | 11 ++ .../library-tests/dataflow/exceptions/A.java | 92 ++++++++++++ .../dataflow/exceptions/flow.expected | 0 .../library-tests/dataflow/exceptions/flow.ql | 2 + 9 files changed, 381 insertions(+), 38 deletions(-) create mode 100644 java/ql/test/library-tests/dataflow/exceptions/A.java create mode 100644 java/ql/test/library-tests/dataflow/exceptions/flow.expected create mode 100644 java/ql/test/library-tests/dataflow/exceptions/flow.ql diff --git a/java/ql/lib/semmle/code/java/dataflow/FlowSummary.qll b/java/ql/lib/semmle/code/java/dataflow/FlowSummary.qll index d3c9fe3b08b5..c9761c2623f4 100644 --- a/java/ql/lib/semmle/code/java/dataflow/FlowSummary.qll +++ b/java/ql/lib/semmle/code/java/dataflow/FlowSummary.qll @@ -5,6 +5,7 @@ import java private import internal.FlowSummaryImpl as Impl private import internal.DataFlowUtil +private import internal.DataFlowPrivate class SummaryComponent = Impl::Public::SummaryComponent; @@ -28,7 +29,7 @@ module SummaryComponent { SummaryComponent mapValue() { result = content(any(MapValueContent c)) } /** Gets a summary component that represents the return value of a call. */ - SummaryComponent return() { result = return(_) } + SummaryComponent return() { result = return(any(NormalReturnKind n)) } } class SummaryComponentStack = Impl::Public::SummaryComponentStack; diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll index 7ee703808e2f..4cea3de65dd5 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll @@ -56,7 +56,12 @@ private module Cached { } or TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or TFieldValueNode(Field f) or - TCaptureNode(CaptureFlow::SynthesizedCaptureNode cn) + TCaptureNode(CaptureFlow::SynthesizedCaptureNode cn) or + TExceptionOutNode(Call call) or + TExceptionReturnNode(Callable callable) or + TCatchTypeTestNode(CatchClause catch) or + TCatchParameterNode(CatchClause catch) or + TUncaughtNode(TryStmt try) { ExceptionFlow::tryCatch(try, _) } cached newtype TContent = @@ -133,6 +138,16 @@ module Public { result = this.(CaptureNode).getTypeImpl() or result = this.(FieldValueNode).getField().getType() + or + result instanceof TypeThrowable and this instanceof ExceptionOutNode + or + result instanceof TypeThrowable and this instanceof ExceptionReturnNode + or + result instanceof TypeThrowable and this instanceof CatchTypeTestNode + or + result = this.(CatchParameterNode).getVariable().getType() + or + result instanceof TypeThrowable and this instanceof UncaughtNode } /** Gets the callable in which this node occurs. */ @@ -335,6 +350,27 @@ module Public { /** Holds if this is an access to an object's own instance. */ predicate isOwnInstanceAccess() { this.getInstanceAccess().isOwnInstanceAccess() } } + + /** + * A node representing a thrown exception as the result of a call. + */ + class ExceptionOutNode extends Node, TExceptionOutNode { + override string toString() { result = "Exception out: " + this.getCall().toString() } + + override Location getLocation() { result = this.getCall().getLocation() } + + /** Gets the associated call. */ + Call getCall() { this = TExceptionOutNode(result) } + } + + /** + * A node representing a thrown exception being returned from a callable. + */ + class ExceptionReturnNode extends Node, TExceptionReturnNode { + override string toString() { result = "Exception return" } + + override Location getLocation() { result = this.getEnclosingCallable().getLocation() } + } } private import Public @@ -378,7 +414,12 @@ module Private { result = nodeGetEnclosingCallable(n.(ImplicitPostUpdateNode).getPreUpdateNode()) or result.asSummarizedCallable() = n.(FlowSummaryNode).getSummarizedCallable() or result.asCallable() = n.(CaptureNode).getSynthesizedCaptureNode().getEnclosingCallable() or - result.asFieldScope() = n.(FieldValueNode).getField() + result.asFieldScope() = n.(FieldValueNode).getField() or + result.asCallable() = n.(ExceptionOutNode).getCall().getEnclosingCallable() or + n = TExceptionReturnNode(result.asCallable()) or + result.asCallable() = n.(CatchTypeTestNode).getCatch().getEnclosingCallable() or + result.asCallable() = n.(CatchParameterNode).getCatch().getEnclosingCallable() or + result.asCallable() = n.(UncaughtNode).getTry().getEnclosingCallable() } /** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */ @@ -429,15 +470,23 @@ module Private { DataFlowCall getCall() { this.argumentOf(result, _) } } - /** A data flow node that occurs as the result of a `ReturnStmt`. */ + /** + * A data flow node that occurs as the result of a `ReturnStmt` or an + * exception being returned. + */ class ReturnNode extends Node { ReturnNode() { exists(ReturnStmt ret | this.asExpr() = ret.getResult()) or - this.(FlowSummaryNode).isReturn() + this.(FlowSummaryNode).isReturn() or + this instanceof ExceptionReturnNode } /** Gets the kind of this returned value. */ - ReturnKind getKind() { any() } + ReturnKind getKind() { + if this instanceof ExceptionReturnNode + then result instanceof ExceptionReturnKind + else result instanceof NormalReturnKind + } } /** A data flow node that represents the output of a call. */ @@ -446,6 +495,8 @@ module Private { this.asExpr() instanceof MethodAccess or this.(FlowSummaryNode).isOut(_) + or + this instanceof ExceptionOutNode } /** Gets the underlying call. */ @@ -453,6 +504,15 @@ module Private { result.asCall() = this.asExpr() or this.(FlowSummaryNode).isOut(result) + or + result.asCall() = this.(ExceptionOutNode).getCall() + } + + /** Gets the kind of this returned value. */ + ReturnKind getKind() { + if this instanceof ExceptionOutNode + then result instanceof ExceptionReturnKind + else result instanceof NormalReturnKind } } @@ -519,6 +579,66 @@ module Private { cn.isInstanceAccess() and result = cn.getEnclosingCallable().getDeclaringType() } } + + /** + * A data flow node that carries an exception and tests if it is caught in a + * given catch clause. + */ + class CatchTypeTestNode extends Node, TCatchTypeTestNode { + override string toString() { result = this.getCatch().toString() } + + override Location getLocation() { result = this.getCatch().getLocation() } + + /** Gets the catch clause associated with this node. */ + CatchClause getCatch() { this = TCatchTypeTestNode(result) } + + Node getSuccessor(boolean match) { + match = true and + this.getCatch() = result.(CatchParameterNode).getCatch() + or + match = false and + exists(TryStmt try, int i, CatchClause cc | + cc = this.getCatch() and + cc = try.getCatchClause(i) and + // A catch-all does not allow for uncaught exceptions. + not cc.getACaughtType() instanceof TypeThrowable + | + result.(CatchTypeTestNode).getCatch() = try.getCatchClause(i + 1) + or + not exists(try.getCatchClause(i + 1)) and + result.(UncaughtNode).getTry() = try + ) + } + } + + /** + * A data flow node that holds the value of a variable defined in a catch + * clause. + */ + class CatchParameterNode extends Node, TCatchParameterNode { + override string toString() { result = this.getVariable().toString() } + + override Location getLocation() { result = this.getVariable().getLocation() } + + /** Gets the catch clause associated with this node. */ + CatchClause getCatch() { this = TCatchParameterNode(result) } + + /** Gets the variable declaration associated with this node. */ + LocalVariableDeclExpr getVariable() { result = this.getCatch().getVariable() } + } + + /** + * A data flow node that carries an exception that is uncaught by a try-catch + * statement. + */ + class UncaughtNode extends Node, TUncaughtNode { + override string toString() { result = "Uncaught exception" } + + override Location getLocation() { result = this.getTry().getLocation() } + + /** Gets the try statement associated with this node. */ + TryStmt getTry() { this = TUncaughtNode(result) } + } } private import Private diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll index bca55c94adb3..d053871d12ad 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll @@ -12,15 +12,28 @@ private import DataFlowNodes private import codeql.dataflow.VariableCapture as VariableCapture import DataFlowNodes::Private -private newtype TReturnKind = TNormalReturnKind() +private newtype TReturnKind = + TNormalReturnKind() or + TExceptionReturnKind() /** - * A return kind. A return kind describes how a value can be returned - * from a callable. For Java, this is simply a method return. + * A return kind. A return kind describes how a value can be returned from a + * callable. For Java, this is either a normal method return or an exception + * being returned. */ class ReturnKind extends TReturnKind { /** Gets a textual representation of this return kind. */ - string toString() { result = "return" } + string toString() { none() } +} + +/** A return kind indicating normal method return. */ +class NormalReturnKind extends ReturnKind, TNormalReturnKind { + override string toString() { result = "return" } +} + +/** A return kind indicating exceptional method return. */ +class ExceptionReturnKind extends ReturnKind, TExceptionReturnKind { + override string toString() { result = "exception return" } } /** @@ -29,7 +42,7 @@ class ReturnKind extends TReturnKind { */ OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { result.getCall() = call and - kind = TNormalReturnKind() + result.getKind() = kind } /** @@ -169,6 +182,8 @@ private CaptureFlow::ClosureNode asClosureNode(Node n) { n.asExpr() = write.(VariableAssign).getSource() or n.asExpr() = write.(AssignOp) + or + n.(CatchParameterNode).getVariable() = write ) } @@ -198,6 +213,54 @@ predicate jumpStep(Node node1, Node node2) { node2.(FlowSummaryNode).getSummaryNode()) } +module ExceptionFlow { + /** + * Holds if `try` has at least one catch clause and `body` is either the main + * body of the `try` or one of its resource declarations. + */ + predicate tryCatch(TryStmt try, Stmt body) { + exists(try.getACatchClause()) and + ( + body = try.getBlock() or + body = try.getAResourceDecl() + ) + } + + /** + * Holds if `s2` is the enclosing statement of `s1` and `s1` is not directly + * wrapped in a try-catch. + */ + private predicate excStep(Stmt s1, Stmt s2) { + s1.getEnclosingStmt() = s2 and + not tryCatch(_, s1) + } + + pragma[nomagic] + private Callable excReturnGetCallable(ExceptionReturnNode n) { result = n.getEnclosingCallable() } + + /** Holds if a thrown exception can flow locally from `node1` to `node2`. */ + predicate localStep(Node node1, Node node2) { + exists(Stmt exc | + node1.asExpr() = exc.(ThrowStmt).getExpr() or + node1.(ExceptionOutNode).getCall().getEnclosingStmt() = exc or + node1.(UncaughtNode).getTry() = exc + | + exists(TryStmt try, Stmt body | + excStep+(exc, body) and + tryCatch(try, body) and + node2.(CatchTypeTestNode).getCatch() = try.getCatchClause(0) + ) + or + exists(Callable callable | + excStep+(exc, callable.getBody()) and + excReturnGetCallable(node2) = callable + ) + ) + or + node1.(CatchTypeTestNode).getSuccessor(_) = node2 + } +} + /** * Holds if `fa` is an access to an instance field that occurs as the * destination of an assignment of the value `src`. @@ -292,7 +355,7 @@ predicate expectsContent(Node n, ContentSet c) { * possible flow. A single type is used for all numeric types to account for * numeric conversions, and otherwise the erasure is used. */ -RefType getErasedRepr(Type t) { +RefType getErasedRepr0(Type t) { exists(Type e | e = t.getErasure() | if e instanceof NumericOrCharType then result.(BoxedType).getPrimitiveType().getName() = "double" @@ -305,30 +368,43 @@ RefType getErasedRepr(Type t) { t instanceof NullType and result instanceof TypeObject } -class DataFlowType extends SrcRefType { - DataFlowType() { this = getErasedRepr(_) } +DataFlowType getErasedRepr(Type t) { result = TType(getErasedRepr0(t)) } + +CatchClause getNegativeType(Node n) { + exists(CatchTypeTestNode ct | + n = ct.getSuccessor(false) and + result = ct.getCatch() + ) } pragma[nomagic] -predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) { t1.getASourceSupertype+() = t2 } +predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) { + t1.asType().getASourceSupertype+() = t2.asType() +} pragma[noinline] DataFlowType getNodeType(Node n) { + result.asNegType() = getNegativeType(n) + or + not exists(getNegativeType(n)) and result = getErasedRepr(n.getTypeBound()) or result = FlowSummaryImpl::Private::summaryNodeType(n.(FlowSummaryNode).getSummaryNode()) } /** Gets a string representation of a type returned by `getErasedRepr`. */ -string ppReprType(DataFlowType t) { +private string ppErasedReprType(Type t) { if t.(BoxedType).getPrimitiveType().getName() = "double" then result = "Number" else result = t.toString() } +/** Gets a string representation of a type returned by `getErasedRepr`. */ +string ppReprType(DataFlowType t) { result = t.toString() } + pragma[nomagic] private predicate compatibleTypes0(DataFlowType t1, DataFlowType t2) { - erasedHaveIntersection(t1, t2) + erasedHaveIntersection(t1.asType(), t2.asType()) } /** @@ -337,11 +413,25 @@ private predicate compatibleTypes0(DataFlowType t1, DataFlowType t2) { */ bindingset[t1, t2] pragma[inline_late] -predicate compatibleTypes(DataFlowType t1, DataFlowType t2) { compatibleTypes0(t1, t2) } +predicate compatibleTypes(DataFlowType t1, DataFlowType t2) { + compatibleTypes0(t1, t2) + or + exists(RefType pos, CatchClause neg | + pos = t1.asType() and neg = t2.asNegType() + or + pos = t2.asType() and neg = t1.asNegType() + | + not pos.getASourceSupertype*() = neg.getACaughtType() + ) +} /** A node that performs a type cast. */ -class CastNode extends ExprNode { - CastNode() { this.getExpr() instanceof CastingExpr } +class CastNode extends Node { + CastNode() { + this.asExpr() instanceof CastingExpr or + this instanceof CatchParameterNode or + exists(getNegativeType(this)) + } } private newtype TDataFlowCallable = @@ -381,6 +471,21 @@ class DataFlowCallable extends TDataFlowCallable { class DataFlowExpr = Expr; +private newtype TDataFlowType = + TType(RefType t) { t = getErasedRepr0(_) } or + TNegType(CatchClause cc) + +class DataFlowType extends TDataFlowType { + RefType asType() { this = TType(result) } + + CatchClause asNegType() { this = TNegType(result) } + + string toString() { + result = ppErasedReprType(this.asType()) or + result = "Not type: " + this.asNegType().getVariable().getTypeAccess() + } +} + private newtype TDataFlowCall = TCall(Call c) or TSummaryCall(SummarizedCallable c, FlowSummaryImpl::Private::SummaryNode receiver) { @@ -516,6 +621,7 @@ predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { receiver.(FlowSummaryNode).getSummaryNode() = call.(SummaryCall).getReceiver() and getNodeDataFlowType(receiver) + .asType() .getSourceDeclaration() .(FunctionalInterface) .getRunMethod() diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll index 4f48b066055e..462509e53851 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll @@ -197,7 +197,8 @@ private predicate simpleLocalFlowStep0(Node node1, Node node2) { // Variable flow steps through adjacent def-use and use-use pairs. exists(SsaExplicitUpdate upd | upd.getDefiningExpr().(VariableAssign).getSource() = node1.asExpr() or - upd.getDefiningExpr().(AssignOp) = node1.asExpr() + upd.getDefiningExpr().(AssignOp) = node1.asExpr() or + upd.getDefiningExpr() = node1.(CatchParameterNode).getVariable() | node2.asExpr() = upd.getAFirstUse() and not capturedVariableRead(node2) @@ -236,6 +237,8 @@ private predicate simpleLocalFlowStep0(Node node1, Node node2) { node2.(FlowSummaryNode).getSummaryNode(), true) or captureValueStep(node1, node2) + or + ExceptionFlow::localStep(node1, node2) } /** @@ -245,7 +248,7 @@ private predicate simpleLocalFlowStep0(Node node1, Node node2) { */ class Content extends TContent { /** Gets the type of the contained data for the purpose of type pruning. */ - abstract DataFlowType getType(); + abstract Type getType(); /** Gets a textual representation of this element. */ abstract string toString(); @@ -270,7 +273,7 @@ class FieldContent extends Content, TFieldContent { InstanceField getField() { result = f } - override DataFlowType getType() { result = getErasedRepr(f.getType()) } + override Type getType() { result = getErasedRepr0(f.getType()) } override string toString() { result = f.toString() } @@ -281,28 +284,28 @@ class FieldContent extends Content, TFieldContent { /** A reference through an array. */ class ArrayContent extends Content, TArrayContent { - override DataFlowType getType() { result instanceof TypeObject } + override Type getType() { result instanceof TypeObject } override string toString() { result = "[]" } } /** A reference through the contents of some collection-like container. */ class CollectionContent extends Content, TCollectionContent { - override DataFlowType getType() { result instanceof TypeObject } + override Type getType() { result instanceof TypeObject } override string toString() { result = "" } } /** A reference through a map key. */ class MapKeyContent extends Content, TMapKeyContent { - override DataFlowType getType() { result instanceof TypeObject } + override Type getType() { result instanceof TypeObject } override string toString() { result = "" } } /** A reference through a map value. */ class MapValueContent extends Content, TMapValueContent { - override DataFlowType getType() { result instanceof TypeObject } + override Type getType() { result instanceof TypeObject } override string toString() { result = "" } } @@ -315,7 +318,7 @@ class CapturedVariableContent extends Content, TCapturedVariableContent { CapturedVariable getVariable() { result = v } - override DataFlowType getType() { result = getErasedRepr(v.(Variable).getType()) } + override Type getType() { result = getErasedRepr0(v.(Variable).getType()) } override string toString() { result = v.toString() } } @@ -328,7 +331,7 @@ class SyntheticFieldContent extends Content, TSyntheticFieldContent { SyntheticField getField() { result = s } - override DataFlowType getType() { result = getErasedRepr(s.getType()) } + override Type getType() { result = getErasedRepr0(s.getType()) } override string toString() { result = s.toString() } } diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll b/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll index fb260d5cdd69..ee84f94533af 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll @@ -40,7 +40,7 @@ ArgumentPosition callbackSelfParameterPosition() { result = -1 } SummaryCall summaryDataFlowCall(SummaryNode receiver) { result.getReceiver() = receiver } /** Gets the type of content `c`. */ -DataFlowType getContentType(Content c) { result = c.getType() } +DataFlowType getContentType(Content c) { result.asType() = c.getType() } /** Gets the type of the parameter at the given position. */ DataFlowType getParameterType(SummarizedCallable c, ParameterPosition pos) { @@ -49,8 +49,12 @@ DataFlowType getParameterType(SummarizedCallable c, ParameterPosition pos) { /** Gets the return type of kind `rk` for callable `c`. */ DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) { - result = getErasedRepr(c.getReturnType()) and - exists(rk) + rk instanceof NormalReturnKind and + result = getErasedRepr(c.getReturnType()) + or + rk instanceof ExceptionReturnKind and + exists(c) and + result.asType() instanceof TypeThrowable } /** @@ -58,9 +62,9 @@ DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) { * callback of type `t`. */ DataFlowType getCallbackParameterType(DataFlowType t, int i) { - result = getErasedRepr(t.(FunctionalInterface).getRunMethod().getParameterType(i)) + result = getErasedRepr(t.asType().(FunctionalInterface).getRunMethod().getParameterType(i)) or - result = getErasedRepr(t.(FunctionalInterface)) and i = -1 + result = getErasedRepr(t.asType().(FunctionalInterface)) and i = -1 } /** @@ -68,14 +72,18 @@ DataFlowType getCallbackParameterType(DataFlowType t, int i) { * callback of type `t`. */ DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) { - result = getErasedRepr(t.(FunctionalInterface).getRunMethod().getReturnType()) and - exists(rk) + rk instanceof NormalReturnKind and + result = getErasedRepr(t.asType().(FunctionalInterface).getRunMethod().getReturnType()) + or + rk instanceof ExceptionReturnKind and + exists(t) and + result.asType() instanceof TypeThrowable } /** Gets the type of synthetic global `sg`. */ DataFlowType getSyntheticGlobalType(SummaryComponent::SyntheticGlobal sg) { exists(sg) and - result instanceof TypeObject + result.asType() instanceof TypeObject } private predicate relatedArgSpec(Callable c, string spec) { @@ -274,7 +282,7 @@ predicate sinkElement(SourceOrSinkElement e, string input, string kind, string p } /** Gets the return kind corresponding to specification `"ReturnValue"`. */ -ReturnKind getReturnValueKind() { any() } +ReturnKind getReturnValueKind() { result instanceof NormalReturnKind } private newtype TInterpretNode = TElement(SourceOrSinkElement n) or diff --git a/java/ql/test/library-tests/dataflow/capture/B.java b/java/ql/test/library-tests/dataflow/capture/B.java index 8909358b8a45..d62bb0f2305d 100644 --- a/java/ql/test/library-tests/dataflow/capture/B.java +++ b/java/ql/test/library-tests/dataflow/capture/B.java @@ -248,4 +248,15 @@ void run() { sink(l.get(0)); // $ hasValueFlow=src sink(l2.get(0)); // $ hasValueFlow=src } + + void testCapturedCatch() { + try { + throw new RuntimeException(source("rte")); + } catch (RuntimeException e) { + Runnable r = () -> { + sink(e.getMessage()); // $ hasValueFlow=rte + }; + r.run(); + } + } } diff --git a/java/ql/test/library-tests/dataflow/exceptions/A.java b/java/ql/test/library-tests/dataflow/exceptions/A.java new file mode 100644 index 000000000000..656b8c5a8e2a --- /dev/null +++ b/java/ql/test/library-tests/dataflow/exceptions/A.java @@ -0,0 +1,92 @@ +import java.util.*; + +public class A { + static String source(String tag) { return tag; } + + static void sink(Object o) { } + + static class MyException extends RuntimeException { + String msg; + MyException(String msg) { + this.msg = msg; + } + } + + static class MySubException extends MyException { + MySubException(String msg) { + super(msg); + } + } + + static class MyOtherException extends RuntimeException { + String msg; + MyOtherException(String msg) { + this.msg = msg; + } + } + + void throwSome(int i) { + if (i == 1) throw new MyException(source("my")); + if (i == 2) throw new MySubException(source("sub")); + try { + if (i == 3) throw new MyOtherException(source("other")); + } catch (ClassCastException e) { + } + } + + void foo(int i) { + try { + try { + throwSome(i); + } finally { + } + } catch (MySubException e) { + sink(e.msg); // $ hasValueFlow=sub SPURIOUS: hasValueFlow=my + } catch (MyException e) { + sink(e.msg); // $ hasValueFlow=my + } catch (MyOtherException e) { + sink(e.msg); // $ hasValueFlow=other + } catch (Exception e) { + sink(((MyOtherException)e).msg); // no flow + } + } + + void throwArg(String msg) { + throw new MyException(msg); + } + + void catchSummary() { + try { + throwArg(source("arg")); + } catch (MyException e) { + sink(e.msg); // $ hasValueFlow=arg + } + } + + void runCallback(Runnable r) { + r.run(); + } + + void catchCallback() { + try { + runCallback(() -> { throw new MyException(source("cb")); }); + } catch (MyException e) { + sink(e.msg); // $ hasValueFlow=cb + } + + try { + List l = Arrays.asList(new String[] { "s" }); + l.forEach(s -> { throw new MyException(source("cb2")); }); + } catch (MyException e) { + sink(e.msg); // $ MISSING: hasValueFlow=cb2 + } + } + + void catchRuntimeException() { + try { + throw new RuntimeException(source("rte")); + } catch (RuntimeException e) { + sink(e.getMessage()); // $ hasValueFlow=rte + } + } +} diff --git a/java/ql/test/library-tests/dataflow/exceptions/flow.expected b/java/ql/test/library-tests/dataflow/exceptions/flow.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/java/ql/test/library-tests/dataflow/exceptions/flow.ql b/java/ql/test/library-tests/dataflow/exceptions/flow.ql new file mode 100644 index 000000000000..50e3f8d2f7de --- /dev/null +++ b/java/ql/test/library-tests/dataflow/exceptions/flow.ql @@ -0,0 +1,2 @@ +import TestUtilities.InlineFlowTest +import DefaultFlowTest