Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions change-notes/1.21/analysis-java.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Improvements to Java analysis

## New queries

| **Query** | **Tags** | **Purpose** |
|-----------------------------|-----------|--------------------------------------------------------------------|

## Changes to existing queries

| **Query** | **Expected impact** | **Change** |
|----------------------------|------------------------|------------------------------------------------------------------|

## Changes to QL libraries

* The `Guards` library has been extended to account for method calls that check
conditions by conditionally throwing an exception. This includes the
`checkArgument` and `checkState` methods in
`com.google.common.base.Preconditions`, the `isTrue` and `validState` methods
in `org.apache.commons.lang3.Validate`, as well as any similar custom
methods. This means that more guards are recognized yielding precision
improvements in a number of queries including `java/index-out-of-bounds`,
`java/dereferenced-value-may-be-null`, and `java/useless-null-check`.


12 changes: 11 additions & 1 deletion java/ql/src/semmle/code/java/ControlFlowGraph.qll
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@

import java
private import Completion
private import controlflow.internal.Preconditions

/** A node in the expression-level control-flow graph. */
class ControlFlowNode extends Top, @exprparent {
Expand Down Expand Up @@ -169,7 +170,8 @@ private module ControlFlowGraphImpl {
exists(Call c | c = n |
t = c.getCallee().getAThrownExceptionType() or
uncheckedExceptionFromCatch(n, t) or
uncheckedExceptionFromFinally(n, t)
uncheckedExceptionFromFinally(n, t) or
uncheckedExceptionFromMethod(c, t)
)
or
exists(CastExpr c | c = n |
Expand All @@ -178,6 +180,14 @@ private module ControlFlowGraphImpl {
)
}

/**
* Bind `t` to an unchecked exception that may occur in a precondition check.
*/
private predicate uncheckedExceptionFromMethod(MethodAccess ma, ThrowableType t) {
conditionCheck(ma, _) and
(t instanceof TypeError or t instanceof TypeRuntimeException)
}

/**
* Bind `t` to an unchecked exception that may transfer control to a finally
* block inside which `n` is nested.
Expand Down
27 changes: 26 additions & 1 deletion java/ql/src/semmle/code/java/controlflow/Guards.qll
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import java
private import semmle.code.java.controlflow.Dominance
private import semmle.code.java.controlflow.internal.GuardsLogic
private import semmle.code.java.controlflow.internal.Preconditions

/**
* A basic block that terminates in a condition, splitting the subsequent control flow.
Expand Down Expand Up @@ -71,7 +72,7 @@ class ConditionBlock extends BasicBlock {
/**
* A condition that can be evaluated to either true or false. This can either
* be an `Expr` of boolean type that isn't a boolean literal, or a case of a
* switch statement.
* switch statement, or a method access that acts as a precondition check.
*
* Evaluating a switch case to true corresponds to taking that switch case, and
* evaluating it to false corresponds to taking some other branch.
Expand All @@ -81,6 +82,8 @@ class Guard extends ExprParent {
this.(Expr).getType() instanceof BooleanType and not this instanceof BooleanLiteral
or
this instanceof SwitchCase
or
conditionCheck(this, _)
}

/** Gets the immediately enclosing callable whose body contains this guard. */
Expand Down Expand Up @@ -128,6 +131,8 @@ class Guard extends ExprParent {
pred.(Expr).getParent*() = sc.getSwitch().getExpr() and
bb1 = pred.getBasicBlock()
)
or
preconditionBranchEdge(this, bb1, bb2, branch)
}

/**
Expand All @@ -142,6 +147,8 @@ class Guard extends ExprParent {
)
or
switchCaseControls(this, controlled) and branch = true
or
preconditionControls(this, controlled, branch)
}

/**
Expand All @@ -165,6 +172,24 @@ private predicate switchCaseControls(SwitchCase sc, BasicBlock bb) {
)
}

private predicate preconditionBranchEdge(
MethodAccess ma, BasicBlock bb1, BasicBlock bb2, boolean branch
) {
conditionCheck(ma, branch) and
bb1.getLastNode() = ma.getControlFlowNode() and
bb2 = bb1.getLastNode().getANormalSuccessor()
}

private predicate preconditionControls(MethodAccess ma, BasicBlock controlled, boolean branch) {
exists(BasicBlock check, BasicBlock succ |
preconditionBranchEdge(ma, check, succ, branch) and
succ.bbDominates(controlled) and
forall(BasicBlock pred | pred = succ.getABBPredecessor() and pred != check |
succ.bbDominates(pred)
)
)
}

/**
* INTERNAL: Use `Guards.controls` instead.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import java
import semmle.code.java.controlflow.Guards
private import Preconditions
private import semmle.code.java.dataflow.SSA
private import semmle.code.java.dataflow.internal.BaseSSA
private import semmle.code.java.dataflow.NullGuards
Expand Down Expand Up @@ -61,6 +62,13 @@ predicate implies_v1(Guard g1, boolean b1, Guard g2, boolean b2) {
or
g1.(DefaultCase).getSwitch().getAConstCase() = g2 and b1 = true and b2 = false
or
exists(MethodAccess check | check = g1 |
conditionCheck(check, _) and
g2 = check.getArgument(0) and
(b1 = true or b1 = false) and
b2 = b1
)
or
exists(BaseSsaUpdate vbool |
vbool.getAUse() = g1 and
vbool.getDefiningExpr().(VariableAssign).getSource() = g2 and
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* Provides predicates for identifying precondition checks like
* `com.google.common.base.Preconditions` and
* `org.apache.commons.lang3.Validate`.
*/
import java

/**
* Holds if `m` is a non-overridable method that checks that its first argument
* is equal to `checkTrue` and throws otherwise.
*/
predicate conditionCheckMethod(Method m, boolean checkTrue) {
m.getDeclaringType().hasQualifiedName("com.google.common.base", "Preconditions") and
checkTrue = true and
(m.hasName("checkArgument") or m.hasName("checkState"))
or
m.getDeclaringType().hasQualifiedName("org.apache.commons.lang3", "Validate") and
checkTrue = true and
(m.hasName("isTrue") or m.hasName("validState"))
or
exists(Parameter p, IfStmt ifstmt, Expr cond |
p = m.getParameter(0) and
not m.isOverridable() and
p.getType() instanceof BooleanType and
m.getBody().getStmt(0) = ifstmt and
ifstmt.getCondition().getProperExpr() = cond and
(
cond.(LogNotExpr).getExpr().getProperExpr().(VarAccess).getVariable() = p and checkTrue = true
or
cond.(VarAccess).getVariable() = p and checkTrue = false
) and
(
ifstmt.getThen() instanceof ThrowStmt or
ifstmt.getThen().(SingletonBlock).getStmt() instanceof ThrowStmt
)
)
or
exists(Parameter p, MethodAccess ma, boolean ct, Expr arg |
p = m.getParameter(0) and
not m.isOverridable() and
m.getBody().getStmt(0).(ExprStmt).getExpr() = ma and
conditionCheck(ma, ct) and
ma.getArgument(0).getProperExpr() = arg and
(
arg.(LogNotExpr).getExpr().getProperExpr().(VarAccess).getVariable() = p and
checkTrue = ct.booleanNot()
or
arg.(VarAccess).getVariable() = p and checkTrue = ct
)
)
}

/**
* Holds if `ma` is an access to a non-overridable method that checks that its
* first argument is equal to `checkTrue` and throws otherwise.
*/
predicate conditionCheck(MethodAccess ma, boolean checkTrue) {
conditionCheckMethod(ma.getMethod().getSourceDeclaration(), checkTrue)
}
4 changes: 0 additions & 4 deletions java/ql/src/semmle/code/java/frameworks/Assertions.qll
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,6 @@ private predicate assertionMethod(Method m, AssertKind kind) {
preconditions.hasQualifiedName("com.google.common.base", "Preconditions")
|
m.hasName("checkNotNull") and kind = AssertKindNotNull()
or
m.hasName("checkArgument") and kind = AssertKindTrue()
or
m.hasName("checkState") and kind = AssertKindTrue()
)
}

Expand Down
17 changes: 17 additions & 0 deletions java/ql/test/library-tests/guards/Logic.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,21 @@ void f(int[] a, String s) {
if (o instanceof String) {
}
}

void f2(int i) {
checkTrue(i > 0, "i pos");
checkFalse(g(100), "g");
if (i > 10) {
checkTrue(i > 20, "");
}
int dummy = 0;
}

private static void checkTrue(boolean b, String msg) {
if (!b) throw new Exception(msg);
}

private static void checkFalse(boolean b, String msg) {
checkTrue(!b, msg);
}
}
16 changes: 16 additions & 0 deletions java/ql/test/library-tests/guards/guardslogic.expected
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,19 @@
| Logic.java:29:16:29:19 | g(...) | false | Logic.java:30:30:31:5 | stmt |
| Logic.java:29:16:29:19 | g(...) | true | Logic.java:29:23:29:26 | null |
| Logic.java:30:9:30:27 | ...instanceof... | true | Logic.java:30:30:31:5 | stmt |
| Logic.java:35:5:35:29 | checkTrue(...) | true | Logic.java:36:5:36:28 | stmt |
| Logic.java:35:5:35:29 | checkTrue(...) | true | Logic.java:37:5:37:15 | stmt |
| Logic.java:35:5:35:29 | checkTrue(...) | true | Logic.java:37:17:39:5 | stmt |
| Logic.java:35:5:35:29 | checkTrue(...) | true | Logic.java:40:5:40:18 | stmt |
| Logic.java:35:15:35:19 | ... > ... | true | Logic.java:36:5:36:28 | stmt |
| Logic.java:35:15:35:19 | ... > ... | true | Logic.java:37:5:37:15 | stmt |
| Logic.java:35:15:35:19 | ... > ... | true | Logic.java:37:17:39:5 | stmt |
| Logic.java:35:15:35:19 | ... > ... | true | Logic.java:40:5:40:18 | stmt |
| Logic.java:36:5:36:27 | checkFalse(...) | false | Logic.java:37:5:37:15 | stmt |
| Logic.java:36:5:36:27 | checkFalse(...) | false | Logic.java:37:17:39:5 | stmt |
| Logic.java:36:5:36:27 | checkFalse(...) | false | Logic.java:40:5:40:18 | stmt |
| Logic.java:36:16:36:21 | g(...) | false | Logic.java:37:5:37:15 | stmt |
| Logic.java:36:16:36:21 | g(...) | false | Logic.java:37:17:39:5 | stmt |
| Logic.java:36:16:36:21 | g(...) | false | Logic.java:40:5:40:18 | stmt |
| Logic.java:37:9:37:14 | ... > ... | true | Logic.java:37:17:39:5 | stmt |
| Logic.java:44:10:44:10 | b | false | Logic.java:44:33:44:35 | msg |