diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplSpecific.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplSpecific.qll index e269b56e3aa4b..9b24e77181377 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplSpecific.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplSpecific.qll @@ -19,6 +19,8 @@ module JavaDataFlow implements InputSig { Node exprNode(DataFlowExpr e) { result = Public::exprNode(e) } + predicate getSecondLevelScope = Private::getSecondLevelScope/1; + predicate mayBenefitFromCallContext = Private::mayBenefitFromCallContext/1; predicate viableImplInCallContext = Private::viableImplInCallContext/2; 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 63fc8e50b28e9..3cae8afc8dfc7 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll @@ -576,7 +576,80 @@ predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { /** Extra data-flow steps needed for lambda flow analysis. */ predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() } -class DataFlowSecondLevelScope = Unit; +private predicate isTopLevel(Stmt s) { + any(Callable c).getBody() = s + or + exists(BlockStmt b | s = b.getAStmt() and isTopLevel(b)) +} + +private Stmt getAChainedBranch(IfStmt s) { + result = s.getThen() + or + exists(Stmt elseBranch | s.getElse() = elseBranch | + result = getAChainedBranch(elseBranch) + or + result = elseBranch and not elseBranch instanceof IfStmt + ) +} + +private newtype TDataFlowSecondLevelScope = + TTopLevelIfBranch(Stmt s) { + exists(IfStmt ifstmt | s = getAChainedBranch(ifstmt) and isTopLevel(ifstmt)) + } or + TTopLevelSwitchCase(SwitchCase s) { + exists(SwitchStmt switchstmt | s = switchstmt.getACase() and isTopLevel(switchstmt)) + } + +private SwitchCase getPrecedingCase(Stmt s) { + result = s + or + exists(SwitchStmt switch, int i | + s = switch.getStmt(i) and + not s instanceof SwitchCase and + result = getPrecedingCase(switch.getStmt(i - 1)) + ) +} + +/** + * A second-level control-flow scope in a `switch` or a chained `if` statement. + * + * This is a `switch` case or a branch of a chained `if` statement, given that + * the `switch` or `if` statement is top level, that is, it is not nested inside + * other CFG constructs. + */ +class DataFlowSecondLevelScope extends TDataFlowSecondLevelScope { + /** Gets a textual representation of this element. */ + string toString() { + exists(Stmt s | this = TTopLevelIfBranch(s) | result = s.toString()) + or + exists(SwitchCase s | this = TTopLevelSwitchCase(s) | result = s.toString()) + } + + /** + * Gets a statement directly contained in this scope. For an `if` branch, this + * is the branch itself, and for a `switch case`, this is one the statements + * of that case branch. + */ + private Stmt getAStmt() { + exists(Stmt s | this = TTopLevelIfBranch(s) | result = s) + or + exists(SwitchCase s | this = TTopLevelSwitchCase(s) | + result = s.getRuleStatement() or + s = getPrecedingCase(result) + ) + } + + /** Gets a data-flow node nested within this scope. */ + Node getANode() { getRelatedExpr(result).getAnEnclosingStmt() = this.getAStmt() } +} + +private Expr getRelatedExpr(Node n) { + n.asExpr() = result or + n.(PostUpdateNode).getPreUpdateNode().asExpr() = result +} + +/** Gets the second-level scope containing the node `n`, if any. */ +DataFlowSecondLevelScope getSecondLevelScope(Node n) { result.getANode() = n } /** * Holds if flow is allowed to pass from parameter `p` and back to itself as a