Skip to content

Java: Make JumpStmt a proper superclass #8582

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 3 commits into from
Apr 25, 2022
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: fix
Copy link
Contributor Author

@Marcono1234 Marcono1234 Mar 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really a "fix", but the other categories do not seem to fit much better either. Maybe it should be "feature"?

---
* The QL class `JumpStmt` has been made the superclass of `BreakStmt`, `ContinueStmt` and `YieldStmt`. This allows directly using its inherited predicates without having to explicitly cast to `JumpStmt` first.
2 changes: 1 addition & 1 deletion java/ql/lib/semmle/code/java/Expr.qll
Original file line number Diff line number Diff line change
Expand Up @@ -1399,7 +1399,7 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
Expr getAResult() {
result = this.getACase().getRuleExpression()
or
exists(YieldStmt yield | yield.(JumpStmt).getTarget() = this and result = yield.getValue())
exists(YieldStmt yield | yield.getTarget() = this and result = yield.getValue())
}

/** Gets a printable representation of this expression. */
Expand Down
31 changes: 16 additions & 15 deletions java/ql/lib/semmle/code/java/Statement.qll
Original file line number Diff line number Diff line change
Expand Up @@ -556,14 +556,10 @@ class ThrowStmt extends Stmt, @throwstmt {
override string getAPrimaryQlClass() { result = "ThrowStmt" }
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We generally try to avoid exposing public abstract classes like this, since subclassing using extends can lead to surprising results. It is better to define the union type explicitly:

Suggested change
private class JumpStmt_ = @breakstmt or @yieldstmt or @continuestmt;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, thanks! I have implemented your suggestion manually, and added a small comment explaining the rationale; hopefully that is alright.

A bit unrelated, but I have created a #8713 to discuss making implementing abstract classes less error-prone.

/** A `break`, `yield` or `continue` statement. */
class JumpStmt extends Stmt {
JumpStmt() {
this instanceof BreakStmt or
this instanceof YieldStmt or
this instanceof ContinueStmt
}
private class JumpStmt_ = @breakstmt or @yieldstmt or @continuestmt;

/** A `break`, `yield` or `continue` statement. */
class JumpStmt extends Stmt, JumpStmt_ {
/**
* Gets the labeled statement that this `break` or
* `continue` statement refers to, if any.
Expand All @@ -584,12 +580,7 @@ class JumpStmt extends Stmt {
)
}

private SwitchExpr getSwitchExprTarget() { result = this.(YieldStmt).getParent+() }

private StmtParent getEnclosingTarget() {
result = this.getSwitchExprTarget()
or
not exists(this.getSwitchExprTarget()) and
result = this.getAPotentialTarget() and
not exists(Stmt other | other = this.getAPotentialTarget() | other.getEnclosingStmt+() = result)
}
Expand All @@ -598,14 +589,15 @@ class JumpStmt extends Stmt {
* Gets the statement or `switch` expression that this `break`, `yield` or `continue` jumps to.
*/
StmtParent getTarget() {
// Note: This implementation only considers `break` and `continue`; YieldStmt overrides this predicate
result = this.getLabelTarget()
or
not exists(this.getLabelTarget()) and result = this.getEnclosingTarget()
}
}

/** A `break` statement. */
class BreakStmt extends Stmt, @breakstmt {
class BreakStmt extends JumpStmt, @breakstmt {
/** Gets the label targeted by this `break` statement, if any. */
string getLabel() { namestrings(result, _, this) }

Expand All @@ -626,12 +618,21 @@ class BreakStmt extends Stmt, @breakstmt {
/**
* A `yield` statement.
*/
class YieldStmt extends Stmt, @yieldstmt {
class YieldStmt extends JumpStmt, @yieldstmt {
/**
* Gets the value of this `yield` statement.
*/
Expr getValue() { result.getParent() = this }

/**
* Gets the `switch` expression target of this `yield` statement.
*/
override SwitchExpr getTarget() {
// Get the innermost enclosing SwitchExpr; this works because getParent() is defined for Stmt and
// therefore won't proceed after the innermost SwitchExpr (due to it being an Expr)
result = this.getParent+()
}

override string pp() { result = "yield ..." }

override string toString() { result = "yield ..." }
Expand All @@ -642,7 +643,7 @@ class YieldStmt extends Stmt, @yieldstmt {
}

/** A `continue` statement. */
class ContinueStmt extends Stmt, @continuestmt {
class ContinueStmt extends JumpStmt, @continuestmt {
/** Gets the label targeted by this `continue` statement, if any. */
string getLabel() { namestrings(result, _, this) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ import java
from DoStmt do, ContinueStmt continue
where
do.getCondition().(BooleanLiteral).getBooleanValue() = false and
continue.(JumpStmt).getTarget() = do
continue.getTarget() = do
select continue, "This 'continue' never re-runs the loop - the $@ is always false.",
do.getCondition(), "loop condition"
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ predicate loopExit(LoopStmt loop, Stmt exit) {
exit.getEnclosingStmt*() = loop.getBody() and
(
exit instanceof ReturnStmt or
exit.(BreakStmt).(JumpStmt).getTarget() = loop.getEnclosingStmt*()
exit.(BreakStmt).getTarget() = loop.getEnclosingStmt*()
)
}

Expand Down
4 changes: 2 additions & 2 deletions java/ql/src/Security/CWE/CWE-129/ArraySizing.qll
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ class PointlessLoop extends WhileStmt {
PointlessLoop() {
this.getCondition().(BooleanLiteral).getBooleanValue() = true and
// The only `break` must be the last statement.
forall(BreakStmt break | break.(JumpStmt).getTarget() = this |
forall(BreakStmt break | break.getTarget() = this |
this.getStmt().(BlockStmt).getLastStmt() = break
) and
// No `continue` statements.
not exists(ContinueStmt continue | continue.(JumpStmt).getTarget() = this)
not exists(ContinueStmt continue | continue.getTarget() = this)
}
}

Expand Down
2 changes: 1 addition & 1 deletion java/ql/src/Security/CWE/CWE-835/InfiniteLoop.ql
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ predicate loopCondition(LoopStmt loop, Expr cond, boolean polarity) {
ifstmt.getEnclosingStmt*() = loop.getBody() and
ifstmt.getCondition() = cond and
(
exit.(BreakStmt).(JumpStmt).getTarget() = loop or
exit.(BreakStmt).getTarget() = loop or
exit.(ReturnStmt).getEnclosingStmt*() = loop.getBody()
) and
(
Expand Down
5 changes: 5 additions & 0 deletions java/ql/test/library-tests/stmts/JumpTargets.expected
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
| stmts/A.java:18:5:18:12 | yield ... | stmts/A.java:16:10:16:18 | switch (...) |
| stmts/A.java:22:6:22:13 | yield ... | stmts/A.java:20:14:20:22 | switch (...) |
| stmts/A.java:25:6:25:13 | yield ... | stmts/A.java:20:14:20:22 | switch (...) |
| stmts/A.java:34:7:34:14 | yield ... | stmts/A.java:16:10:16:18 | switch (...) |
| stmts/A.java:37:5:37:13 | yield ... | stmts/A.java:16:10:16:18 | switch (...) |
| stmts/B.java:8:5:8:10 | break | stmts/B.java:6:3:6:26 | for (...;...;...) |
| stmts/B.java:10:5:10:13 | continue | stmts/B.java:6:3:6:26 | for (...;...;...) |
| stmts/B.java:13:6:13:11 | break | stmts/B.java:11:4:11:17 | while (...) |
Expand Down
8 changes: 8 additions & 0 deletions java/ql/test/library-tests/stmts/SwitchCases.expected
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
| stmts/A.java:6:3:6:10 | case ... |
| stmts/A.java:8:3:8:10 | case ... |
| stmts/A.java:10:3:10:10 | default |
| stmts/A.java:17:4:17:12 | case ... |
| stmts/A.java:20:4:20:12 | case ... |
| stmts/A.java:21:5:21:13 | case ... |
| stmts/A.java:24:5:24:14 | default |
| stmts/A.java:28:4:28:12 | case ... |
| stmts/A.java:29:4:29:13 | default |
| stmts/A.java:32:6:32:14 | case ... |
| stmts/A.java:33:6:33:14 | case ... |
| stmts/B.java:21:5:21:12 | case ... |
| stmts/B.java:23:5:23:12 | case ... |
| stmts/B.java:25:5:25:12 | case ... |
1 change: 1 addition & 0 deletions java/ql/test/library-tests/stmts/options
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
//semmle-extractor-options: --javac-args -source 14 -target 14
27 changes: 27 additions & 0 deletions java/ql/test/library-tests/stmts/stmts/A.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,31 @@ public int foo(int x) {
return x;
}
}

public int nestedSwitchExpr(int x, int y) {
return switch(x) {
case 1 -> {
yield 1;
}
case 2 -> switch(y) {
case 0 -> {
yield 0;
}
default -> {
yield 1;
}
};
case 3 -> 3;
default -> {
// SwitchStmt inside SwitchExpr
switch (y) {
case 1 -> System.out.println("1");
case 2 -> {
yield 2;
}
}
yield -1;
}
};
}
}