Skip to content

Java: Update the CFG for assert statements to make them proper guards. #19733

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 13, 2025
Merged
Show file tree
Hide file tree
Changes from 3 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.
49 changes: 38 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,19 @@ 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
if exists(assertstmt.getMessage())
then last(assertstmt.getMessage(), last, NormalCompletion())
else 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 +1536,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
)
)
}