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
4 changes: 4 additions & 0 deletions java/ql/lib/change-notes/2025-06-12-assert-cfg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Java `assert` statements are now assumed to be executed for the purpose of analysing control flow. This improves precision for a number of queries.
52 changes: 41 additions & 11 deletions java/ql/lib/semmle/code/java/ControlFlowGraph.qll
Original file line number Diff line number Diff line change
Expand Up @@ -327,12 +327,18 @@ private module ControlFlowGraphImpl {
)
}

private ThrowableType assertionError() { result.hasQualifiedName("java.lang", "AssertionError") }

/**
* Gets an exception type that may be thrown during execution of the
* body or the resources (if any) of `try`.
*/
private ThrowableType thrownInBody(TryStmt try) {
exists(AstNode n | mayThrow(n, result) |
exists(AstNode n |
mayThrow(n, result)
or
n instanceof AssertStmt and result = assertionError()
|
n.getEnclosingStmt().getEnclosingStmt+() = try.getBlock() or
n.(Expr).getParent*() = try.getAResource()
)
Expand Down Expand Up @@ -394,10 +400,7 @@ private module ControlFlowGraphImpl {
exists(LogicExpr logexpr |
logexpr.(BinaryExpr).getLeftOperand() = b
or
// Cannot use LogicExpr.getAnOperand or BinaryExpr.getAnOperand as they remove parentheses.
logexpr.(BinaryExpr).getRightOperand() = b and inBooleanContext(logexpr)
or
logexpr.(UnaryExpr).getExpr() = b and inBooleanContext(logexpr)
logexpr.getAnOperand() = b and inBooleanContext(logexpr)
)
or
exists(ConditionalExpr condexpr |
Expand All @@ -407,6 +410,8 @@ private module ControlFlowGraphImpl {
inBooleanContext(condexpr)
)
or
exists(AssertStmt assertstmt | assertstmt.getExpr() = b)
or
exists(SwitchExpr switch |
inBooleanContext(switch) and
switch.getAResult() = b
Expand Down Expand Up @@ -672,8 +677,6 @@ private module ControlFlowGraphImpl {
this instanceof EmptyStmt
or
this instanceof LocalTypeDeclStmt
or
this instanceof AssertStmt
}

/** Gets child nodes in their order of execution. Indexing starts at either -1 or 0. */
Expand Down Expand Up @@ -744,8 +747,6 @@ private module ControlFlowGraphImpl {
or
index = 0 and result = this.(ThrowStmt).getExpr()
or
index = 0 and result = this.(AssertStmt).getExpr()
or
result = this.(RecordPatternExpr).getSubPattern(index)
}

Expand Down Expand Up @@ -807,9 +808,12 @@ private module ControlFlowGraphImpl {
or
result = first(n.(SynchronizedStmt).getExpr())
or
result = first(n.(AssertStmt).getExpr())
or
result.asStmt() = n and
not n instanceof PostOrderNode and
not n instanceof SynchronizedStmt
not n instanceof SynchronizedStmt and
not n instanceof AssertStmt
or
result.asExpr() = n and n instanceof SwitchExpr
}
Expand Down Expand Up @@ -1112,7 +1116,22 @@ private module ControlFlowGraphImpl {
// `return` statements give rise to a `Return` completion
last.asStmt() = n.(ReturnStmt) and completion = ReturnCompletion()
or
// `throw` statements or throwing calls give rise to ` Throw` completion
exists(AssertStmt assertstmt | assertstmt = n |
// `assert` statements may complete normally - we use the `AssertStmt` itself
// to represent this outcome
last.asStmt() = assertstmt and completion = NormalCompletion()
or
// `assert` statements may throw
completion = ThrowCompletion(assertionError()) and
(
last(assertstmt.getMessage(), last, NormalCompletion())
or
not exists(assertstmt.getMessage()) and
last(assertstmt.getExpr(), last, BooleanCompletion(false, _))
)
)
or
// `throw` statements or throwing calls give rise to `Throw` completion
exists(ThrowableType tt | mayThrow(n, tt) |
last = n.getCfgNode() and completion = ThrowCompletion(tt)
)
Expand Down Expand Up @@ -1520,6 +1539,17 @@ private module ControlFlowGraphImpl {
exists(int i | last(s.getVariable(i), n, completion) and result = first(s.getVariable(i + 1)))
)
or
// Assert statements:
exists(AssertStmt assertstmt |
last(assertstmt.getExpr(), n, completion) and
completion = BooleanCompletion(true, _) and
result.asStmt() = assertstmt
or
last(assertstmt.getExpr(), n, completion) and
completion = BooleanCompletion(false, _) and
result = first(assertstmt.getMessage())
)
or
// When expressions:
exists(WhenExpr whenexpr |
n.asExpr() = whenexpr and
Expand Down
2 changes: 0 additions & 2 deletions java/ql/lib/semmle/code/java/dataflow/Nullness.qll
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,6 @@ private ControlFlowNode varDereference(SsaVariable v, VarAccess va) {
private ControlFlowNode ensureNotNull(SsaVariable v) {
result = varDereference(v, _)
or
result.asStmt().(AssertStmt).getExpr() = nullGuard(v, true, false)
or
exists(AssertTrueMethod m | result.asCall() = m.getACheck(nullGuard(v, true, false)))
or
exists(AssertFalseMethod m | result.asCall() = m.getACheck(nullGuard(v, false, false)))
Expand Down
14 changes: 10 additions & 4 deletions java/ql/lib/semmle/code/java/frameworks/Assertions.qll
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,17 @@ predicate assertFail(BasicBlock bb, ControlFlowNode n) {
(
exists(AssertTrueMethod m |
n.asExpr() = m.getACheck(any(BooleanLiteral b | b.getBooleanValue() = false))
) or
)
or
exists(AssertFalseMethod m |
n.asExpr() = m.getACheck(any(BooleanLiteral b | b.getBooleanValue() = true))
) or
exists(AssertFailMethod m | n.asExpr() = m.getACheck()) or
n.asStmt().(AssertStmt).getExpr().(BooleanLiteral).getBooleanValue() = false
)
or
exists(AssertFailMethod m | n.asExpr() = m.getACheck())
or
exists(AssertStmt a |
n.asExpr() = a.getExpr() and
a.getExpr().(BooleanLiteral).getBooleanValue() = false
)
)
}