Merge pull request #14671 from smowton/smowton/feature/jdk21-switch-pattern-matching

Java: Add support for Java 21 language features
This commit is contained in:
Chris Smowton
2023-11-30 14:11:28 +00:00
committed by GitHub
105 changed files with 7896 additions and 205 deletions

View File

@@ -0,0 +1,55 @@
class Expr extends @expr {
string toString() { result = "expr" }
}
class LocalVariableDeclExpr extends @localvariabledeclexpr, Expr { }
class InstanceOfExpr extends @instanceofexpr, Expr { }
class SimplePatternInstanceOfExpr extends InstanceOfExpr {
SimplePatternInstanceOfExpr() { getNthChild(this, 2) instanceof LocalVariableDeclExpr }
}
class Type extends @type {
string toString() { result = "type" }
}
class ExprParent extends @exprparent {
string toString() { result = "exprparent" }
}
Expr getNthChild(ExprParent parent, int i) { exprs(result, _, _, parent, i) }
// Where an InstanceOfExpr has a 2nd child that is a LocalVariableDeclExpr, that expression should becomes its 0th child and the existing 0th child should become its initialiser.
// Any RecordPatternExpr should be replaced with an error expression, as it can't be represented in the downgraded dbscheme.
// This reverts a reorganisation of the representation of "o instanceof String s", which used to be InstanceOfExpr -0-> LocalVariableDeclExpr --init-> o
// \-name-> s
// It is now InstanceOfExpr --0-> o
// \-2-> LocalVariableDeclExpr -name-> s
//
// Other children are unaffected.
predicate hasNewParent(Expr e, ExprParent newParent, int newIndex) {
exists(SimplePatternInstanceOfExpr oldParent, int oldIndex |
e = getNthChild(oldParent, oldIndex)
|
oldIndex = 0 and newParent = getNthChild(oldParent, 2) and newIndex = 0
or
oldIndex = 1 and newParent = oldParent and newIndex = oldIndex
or
oldIndex = 2 and newParent = oldParent and newIndex = 0
)
or
not exists(SimplePatternInstanceOfExpr oldParent | e = getNthChild(oldParent, _)) and
exprs(e, _, _, newParent, newIndex)
}
from Expr e, int oldKind, int newKind, Type typeid, ExprParent parent, int index
where
exprs(e, oldKind, typeid, _, _) and
hasNewParent(e, parent, index) and
(
if oldKind = /* record pattern */ 89
then newKind = /* error expression */ 74
else oldKind = newKind
)
select e, newKind, typeid, parent, index

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
description: Remove tables for canonical constructors and `case null, default`, and the expression kind for record patterns. Also revert the representation for instanceof with a binding pattern.
compatibility: backwards
exprs.rel: run exprs.qlo
isCanonicalConstr.rel: delete
isNullDefaultCase.rel: delete

View File

@@ -46,7 +46,13 @@ predicate gapInChildren(Element e, int i) {
// value should be, because kotlinc doesn't load annotation defaults and we
// want to leave a space for another extractor to fill in the default if it
// is able.
not e instanceof Annotation
not e instanceof Annotation and
// Pattern case statements legitimately have a TypeAccess (-2) and a pattern (0) but not a rule (-1)
not (i = -1 and e instanceof PatternCase and not e.(PatternCase).isRule()) and
// Instanceof with a record pattern is not expected to have a type access in position 1
not (i = 1 and e.(InstanceOfExpr).getPattern() instanceof RecordPatternExpr) and
// RecordPatternExpr extracts type-accesses only for its LocalVariableDeclExpr children
not (i < 0 and e instanceof RecordPatternExpr)
}
predicate lateFirstChild(Element e, int i) {

View File

@@ -0,0 +1,6 @@
---
category: minorAnalysis
---
* Switch cases using binding patterns and `case null[, default]` are now supported. Classes `PatternCase` and `NullDefaultCase` are introduced to represent new kinds of case statement.
* Both switch cases and instanceof expressions using record patterns are now supported. The new class `RecordPatternExpr` is introduced to represent record patterns, and `InstanceOfExpr` gains `getPattern` to replace `getLocalVariableDeclExpr`.
* The control-flow graph and therefore dominance information regarding switch blocks in statement context but with an expression rule (e.g. `switch(...) { case 1 -> System.out.println("Hello world!") }`) has been fixed. This reduces false positives and negatives from various queries relating to functions featuring such statements.

View File

@@ -576,6 +576,10 @@ lambdaKind(
int bodyKind: int ref
);
isCanonicalConstr(
int constructorid: @constructor ref
);
arrays(
unique int id: @array,
string nodeName: string ref,
@@ -774,6 +778,7 @@ case @expr.kind of
| 86 = @valueeqexpr
| 87 = @valueneexpr
| 88 = @propertyref
| 89 = @recordpatternexpr
;
/** Holds if this `when` expression was written as an `if` expression. */
@@ -992,6 +997,10 @@ providesWith(
string serviceImpl: string ref
);
isNullDefaultCase(
int id: @case ref
);
/*
* Javadoc
*/

View File

@@ -568,6 +568,10 @@
<k>@propertyref</k>
<v>8439</v>
</e>
<e>
<k>@recordpatternexpr</k>
<v>50</v>
</e>
<e>
<k>@localvar</k>
<v>385272</v>
@@ -9478,6 +9482,17 @@
</columnsizes>
<dependencies/>
</relation>
<relation>
<name>isCanonicalConstr</name>
<cardinality>417</cardinality>
<columnsizes>
<e>
<k>constructorid</k>
<v>417</v>
</e>
</columnsizes>
<dependencies/>
</relation>
<relation>
<name>fielddecls</name>
<cardinality>210035</cardinality>
@@ -26950,5 +26965,16 @@
</columnsizes>
<dependencies/>
</relation>
<relation>
<name>isNullDefaultCase</name>
<cardinality>50</cardinality>
<columnsizes>
<e>
<k>id</k>
<v>50</v>
</e>
</columnsizes>
<dependencies/>
</relation>
</stats>
</dbstats>

View File

@@ -82,6 +82,7 @@
import java
private import Completion
private import controlflow.internal.Preconditions
private import controlflow.internal.SwitchCases
/** A node in the expression-level control-flow graph. */
class ControlFlowNode extends Top, @exprparent {
@@ -317,6 +318,8 @@ private module ControlFlowGraphImpl {
whenexpr.getBranch(_).getAResult() = b
)
or
b = any(PatternCase pc).getGuard()
or
inBooleanContext(b.(ExprStmt).getExpr())
or
inBooleanContext(b.(StmtExpr).getStmt())
@@ -434,6 +437,73 @@ private module ControlFlowGraphImpl {
)
}
// Join order engineering -- first determine the switch block and the case indices required, then retrieve them.
bindingset[switch, i]
pragma[inline_late]
private predicate isNthCaseOf(SwitchBlock switch, SwitchCase c, int i) {
c.isNthCaseOf(switch, i)
}
/**
* Gets a `SwitchCase` that may be `pred`'s direct successor, where `pred` is declared in block `switch`.
*
* This means any switch case that comes after `pred` up to the next pattern case, if any, except for `case null`.
*
* Because we know the switch block contains at least one pattern, we know by https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.11
* that any default case comes after the last pattern case.
*/
private SwitchCase getASuccessorSwitchCase(PatternCase pred, SwitchBlock switch) {
// Note we do include `case null, default` (as well as plain old `default`) here.
not result.(ConstCase).getValue(_) instanceof NullLiteral and
exists(int maxCaseIndex |
switch = pred.getParent() and
if exists(getNextPatternCase(pred))
then maxCaseIndex = getNextPatternCase(pred).getCaseIndex()
else maxCaseIndex = lastCaseIndex(switch)
|
isNthCaseOf(switch, result, [pred.getCaseIndex() + 1 .. maxCaseIndex])
)
}
/**
* Gets a `SwitchCase` that may occur first in `switch`.
*
* If the block contains at least one PatternCase, this is any case up to and including that case, or
* the case handling the null literal if any.
*
* Otherwise it is any case in the switch block.
*/
private SwitchCase getAFirstSwitchCase(SwitchBlock switch) {
result.getParent() = switch and
(
result.(ConstCase).getValue(_) instanceof NullLiteral
or
result instanceof NullDefaultCase
or
not exists(getFirstPatternCase(switch))
or
result.getIndex() <= getFirstPatternCase(switch).getIndex()
)
}
private Stmt getSwitchStatement(SwitchBlock switch, int i) { result.isNthChildOf(switch, i) }
/**
* Holds if `last` is the last node in a pattern case `pc`'s succeeding bind-and-test operation,
* immediately before either falling through to execute successor statements or execute a rule body
* if present. `completion` is the completion kind of the last operation.
*/
private predicate lastPatternCaseMatchingOp(
PatternCase pc, ControlFlowNode last, Completion completion
) {
last(pc.getPattern(), last, completion) and
completion = NormalCompletion() and
not exists(pc.getGuard())
or
last(pc.getGuard(), last, completion) and
completion = BooleanCompletion(true, _)
}
/**
* Expressions and statements with CFG edges in post-order AST traversal.
*
@@ -466,8 +536,7 @@ private module ControlFlowGraphImpl {
or
this instanceof NotInstanceOfExpr
or
this instanceof LocalVariableDeclExpr and
not this = any(InstanceOfExpr ioe).getLocalVariableDeclExpr()
this instanceof LocalVariableDeclExpr
or
this instanceof StringTemplateExpr
or
@@ -493,7 +562,11 @@ private module ControlFlowGraphImpl {
or
this.(BlockStmt).getNumStmt() = 0
or
this instanceof SwitchCase and not this.(SwitchCase).isRule()
this instanceof SwitchCase and
not this.(SwitchCase).isRule() and
not this instanceof PatternCase
or
this instanceof RecordPatternExpr
or
this instanceof EmptyStmt
or
@@ -571,6 +644,8 @@ private module ControlFlowGraphImpl {
index = 0 and result = this.(ThrowStmt).getExpr()
or
index = 0 and result = this.(AssertStmt).getExpr()
or
result = this.(RecordPatternExpr).getSubPattern(index)
}
/** Gets the first child node, if any. */
@@ -711,7 +786,7 @@ private module ControlFlowGraphImpl {
*/
private predicate last(ControlFlowNode n, ControlFlowNode last, Completion completion) {
// Exceptions are propagated from any sub-expression.
// As are any break, continue, or return completions.
// As are any break, yield, continue, or return completions.
exists(Expr e | e.getParent() = n |
last(e, last, completion) and not completion instanceof NormalOrBooleanCompletion
)
@@ -726,7 +801,10 @@ private module ControlFlowGraphImpl {
or
last(n, last, BooleanCompletion(_, _)) and
not inBooleanContext(n) and
completion = NormalCompletion()
completion = NormalCompletion() and
// PatternCase has both a boolean-true completion (guard success) and a normal one
// (variable declaration completion, when no guard is present).
not n instanceof PatternCase
or
// Logic expressions and conditional expressions are executed in AST pre-order to facilitate
// proper short-circuit representation. All other expressions are executed in post-order.
@@ -765,7 +843,7 @@ private module ControlFlowGraphImpl {
exists(InstanceOfExpr ioe | ioe.isPattern() and ioe = n |
last = n and completion = basicBooleanCompletion(false)
or
last = ioe.getLocalVariableDeclExpr() and completion = basicBooleanCompletion(true)
last(ioe.getPattern(), last, NormalCompletion()) and completion = basicBooleanCompletion(true)
)
or
// The last node of a node executed in post-order is the node itself.
@@ -847,14 +925,19 @@ private module ControlFlowGraphImpl {
// any other abnormal completion is propagated
last(switch.getAStmt(), last, completion) and
completion != anonymousBreakCompletion() and
completion != NormalCompletion()
not completion instanceof NormalOrBooleanCompletion
or
// if the last case completes normally, then so does the switch
last(switch.getStmt(strictcount(switch.getAStmt()) - 1), last, NormalCompletion()) and
completion = NormalCompletion()
or
// if no default case exists, then normal completion of the expression may terminate the switch
// Note this can't happen if there are pattern cases or a null literal, as
// https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.11.2 requires that such
// an enhanced switch block is exhaustive.
not exists(switch.getDefaultCase()) and
not exists(switch.getAPatternCase()) and
not switch.hasNullCase() and
last(switch.getExpr(), last, completion) and
completion = NormalCompletion()
)
@@ -867,24 +950,42 @@ private module ControlFlowGraphImpl {
// any other abnormal completion is propagated
last(switch.getAStmt(), last, completion) and
not completion instanceof YieldCompletion and
completion != NormalCompletion()
not completion instanceof NormalOrBooleanCompletion
)
or
// the last node in a case rule is the last node in the right-hand side
// if the rhs is a statement we wrap the completion as a break
exists(Completion caseCompletion |
last(n.(SwitchCase).getRuleStatement(), last, caseCompletion) and
// If a case rule right-hand-side completes then the switch breaks or yields, depending
// on whether this is a switch expression or statement. If it completes abruptly then the
// switch completes the same way.
exists(Completion caseCompletion, SwitchCase case |
case = n and
(
last(case.getRuleStatement(), last, caseCompletion)
or
last(case.getRuleExpression(), last, caseCompletion)
)
|
if caseCompletion instanceof NormalOrBooleanCompletion
then completion = anonymousBreakCompletion()
then
case.getParent() instanceof SwitchStmt and completion = anonymousBreakCompletion()
or
case.getParent() instanceof SwitchExpr and completion = YieldCompletion(caseCompletion)
else completion = caseCompletion
)
or
// ...and if the rhs is an expression we wrap the completion as a yield
exists(Completion caseCompletion |
last(n.(SwitchCase).getRuleExpression(), last, caseCompletion) and
if caseCompletion instanceof NormalOrBooleanCompletion
then completion = YieldCompletion(caseCompletion)
else completion = caseCompletion
// A pattern case statement can complete:
// * On failure of its type test (boolean false)
// * On failure of its guard test if any (boolean false)
// * On completion of its variable declarations, if it is not a rule and has no guard (normal completion)
// * On success of its guard test, if it is not a rule (boolean true)
// (the latter two cases are accounted for by lastPatternCaseMatchingOp)
exists(PatternCase pc | n = pc |
last = pc and completion = basicBooleanCompletion(false)
or
last(pc.getGuard(), last, completion) and
completion = BooleanCompletion(false, _)
or
not pc.isRule() and
lastPatternCaseMatchingOp(pc, last, completion)
)
or
// the last statement of a synchronized statement is the last statement of its body
@@ -1035,7 +1136,7 @@ private module ControlFlowGraphImpl {
last(ioe.getExpr(), n, completion) and completion = NormalCompletion() and result = ioe
or
n = ioe and
result = ioe.getLocalVariableDeclExpr() and
result = first(ioe.getPattern()) and
completion = basicBooleanCompletion(true)
)
or
@@ -1196,39 +1297,67 @@ private module ControlFlowGraphImpl {
last(cc.getVariable(), n, completion) and result = first(cc.getBlock())
)
or
// Switch statements
exists(SwitchStmt switch | completion = NormalCompletion() |
// From the entry point control is transferred first to the expression...
n = switch and result = first(switch.getExpr())
or
// ...and then to one of the cases.
last(switch.getExpr(), n, completion) and result = first(switch.getACase())
// Switch statements and expressions
exists(SwitchBlock switch |
exists(Expr switchExpr |
switchExpr = switch.(SwitchStmt).getExpr() or switchExpr = switch.(SwitchExpr).getExpr()
|
// From the entry point control is transferred first to the expression...
n = switch and result = first(switchExpr) and completion = NormalCompletion()
or
// ...and then to any case up to and including the first pattern case, if any.
last(switchExpr, n, completion) and
result = first(getAFirstSwitchCase(switch)) and
completion = NormalCompletion()
)
or
// Statements within a switch body execute sequentially.
// Note this includes non-rule case statements and the successful pattern match successor
// of a non-rule pattern case statement. Rule case statements do not complete normally
// (they always break or yield).
exists(int i |
last(switch.getStmt(i), n, completion) and result = first(switch.getStmt(i + 1))
last(getSwitchStatement(switch, i), n, completion) and
result = first(getSwitchStatement(switch, i + 1)) and
(completion = NormalCompletion() or completion = BooleanCompletion(true, _))
)
or
// A pattern case that completes boolean false (type test or guard failure) continues to consider other cases:
exists(PatternCase case | completion = BooleanCompletion(false, _) |
last(case, n, completion) and result = getASuccessorSwitchCase(case, switch)
)
)
or
// Switch expressions
exists(SwitchExpr switch | completion = NormalCompletion() |
// From the entry point control is transferred first to the expression...
n = switch and result = first(switch.getExpr())
// Pattern cases have internal edges:
// * Type test success -true-> variable declarations
// * Variable declarations -normal-> guard evaluation
// * Variable declarations -normal-> rule execution (when there is no guard)
// * Guard success -true-> rule execution
exists(PatternCase pc |
n = pc and
completion = basicBooleanCompletion(true) and
result = first(pc.getPattern())
or
// ...and then to one of the cases.
last(switch.getExpr(), n, completion) and result = first(switch.getACase())
last(pc.getPattern(), n, completion) and
completion = NormalCompletion() and
result = first(pc.getGuard())
or
// Statements within a switch body execute sequentially.
exists(int i |
last(switch.getStmt(i), n, completion) and result = first(switch.getStmt(i + 1))
lastPatternCaseMatchingOp(pc, n, completion) and
(
result = first(pc.getRuleExpression())
or
result = first(pc.getRuleStatement())
)
)
or
// No edges in a non-rule SwitchCase - the constant expression in a ConstCase isn't included in the CFG.
exists(SwitchCase case | completion = NormalCompletion() |
n = case and result = first(case.getRuleExpression())
or
n = case and result = first(case.getRuleStatement())
// Non-pattern cases have an internal edge leading to their rule body if any when the case matches.
exists(SwitchCase case | n = case |
not case instanceof PatternCase and
completion = NormalCompletion() and
(
result = first(case.getRuleExpression())
or
result = first(case.getRuleStatement())
)
)
or
// Yield
@@ -1365,5 +1494,5 @@ class ConditionNode extends ControlFlowNode {
ControlFlowNode getAFalseSuccessor() { result = this.getABranchSuccessor(false) }
/** Gets the condition of this `ConditionNode`. This is equal to the node itself. */
Expr getCondition() { result = this }
ExprParent getCondition() { result = this }
}

View File

@@ -78,6 +78,13 @@ predicate depends(RefType t, RefType dep) {
// the type accessed in an `instanceof` expression in `t`.
exists(InstanceOfExpr ioe | t = ioe.getEnclosingCallable().getDeclaringType() |
usesType(ioe.getCheckedType(), dep)
or
usesType(ioe.getPattern().getAChildExpr*().getType(), dep)
)
or
// A type accessed in a pattern-switch case statement in `t`.
exists(PatternCase pc | t = pc.getEnclosingCallable().getDeclaringType() |
usesType(pc.getPattern().getAChildExpr*().getType(), dep)
)
)
}

View File

@@ -101,6 +101,13 @@ predicate numDepends(RefType t, RefType dep, int value) {
t = ioe.getEnclosingCallable().getDeclaringType()
|
usesType(ioe.getCheckedType(), dep)
or
usesType(ioe.getPattern().getAChildExpr*().getType(), dep)
)
or
// the type accessed in a pattern-switch case statement in `t`.
exists(PatternCase pc | elem = pc and t = pc.getEnclosingCallable().getDeclaringType() |
usesType(pc.getPattern().getAChildExpr*().getType(), dep)
)
)
}

View File

@@ -1509,17 +1509,33 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
*/
Stmt getStmt(int index) { result = this.getAStmt() and result.getIndex() = index }
/**
* Gets the `i`th case of this `switch` expression,
* which may be either a normal `case` or a `default`.
*/
SwitchCase getCase(int i) {
result =
rank[i + 1](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx)
}
/**
* Gets a case of this `switch` expression,
* which may be either a normal `case` or a `default`.
*/
SwitchCase getACase() { result = this.getAConstCase() or result = this.getDefaultCase() }
SwitchCase getACase() { result.getParent() = this }
/** Gets a (non-default) `case` of this `switch` expression. */
ConstCase getAConstCase() { result.getParent() = this }
ConstCase getAConstCase() { result = this.getACase() }
/** Gets the `default` case of this switch expression, if any. */
DefaultCase getDefaultCase() { result.getParent() = this }
/** Gets a (non-default) pattern `case` of this `switch` expression. */
PatternCase getAPatternCase() { result = this.getACase() }
/**
* Gets the `default` case of this switch statement, if any.
*
* Note this may be `default` or `case null, default`.
*/
DefaultCase getDefaultCase() { result = this.getACase() }
/** Gets the expression of this `switch` expression. */
Expr getExpr() { result.getParent() = this }
@@ -1531,6 +1547,12 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
exists(YieldStmt yield | yield.getTarget() = this and result = yield.getValue())
}
/** Holds if this switch has a case handling a null literal. */
predicate hasNullCase() {
this.getAConstCase().getValue(_) instanceof NullLiteral or
this.getACase() instanceof NullDefaultCase
}
/** Gets a printable representation of this expression. */
override string toString() { result = "switch (...)" }
@@ -1540,27 +1562,61 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
/** An `instanceof` expression. */
class InstanceOfExpr extends Expr, @instanceofexpr {
/** Gets the expression on the left-hand side of the `instanceof` operator. */
Expr getExpr() {
if this.isPattern()
then result = this.getLocalVariableDeclExpr().getInit()
else result.isNthChildOf(this, 0)
}
Expr getExpr() { result.isNthChildOf(this, 0) }
/**
* Gets the pattern of an `x instanceof T pattern` expression, if any.
*/
PatternExpr getPattern() { result.isNthChildOf(this, 2) }
/**
* Holds if this `instanceof` expression uses pattern matching.
*/
predicate isPattern() { exists(this.getLocalVariableDeclExpr()) }
predicate isPattern() { exists(this.getPattern()) }
/**
* Gets the local variable declaration of this `instanceof` expression if pattern matching is used.
* Gets the local variable declaration of this `instanceof` expression if simple pattern matching is used.
*
* Note that this won't get anything when record pattern matching is used-- for more general patterns,
* use `getPattern`.
*/
LocalVariableDeclExpr getLocalVariableDeclExpr() { result.isNthChildOf(this, 0) }
LocalVariableDeclExpr getLocalVariableDeclExpr() { result = this.getPattern().asBindingPattern() }
/** Gets the access to the type on the right-hand side of the `instanceof` operator. */
/**
* Gets the access to the type on the right-hand side of the `instanceof` operator.
*
* This does not match record patterns, which have a record pattern (use `getPattern`) not a type access.
*/
Expr getTypeName() { result.isNthChildOf(this, 1) }
/** Gets the type this `instanceof` expression checks for. */
RefType getCheckedType() { result = this.getTypeName().getType() }
/**
* Gets the type this `instanceof` expression checks for.
*
* For a match against a record pattern, this is the type of the outermost record type, and only holds if
* the record pattern matches that type unconditionally, i.e. it does not restrict field types more tightly
* than the fields' declared types and therefore match a subset of `rpe.getType()`.
*/
RefType getCheckedType() {
result = this.getTypeName().getType()
or
exists(RecordPatternExpr rpe | rpe = this.getPattern().asRecordPattern() |
result = rpe.getType() and rpe.isUnrestricted()
)
}
/**
* Gets the type this `instanceof` expression checks for.
*
* For a match against a record pattern, this is the type of the outermost record type. Note that because
* the record match might additionally constrain field or sub-record fields to have a more specific type,
* and so while if the `instanceof` test passes we know that `this.getExpr()` has this type, if it fails
* we do not know that it doesn't.
*/
RefType getSyntacticCheckedType() {
result = this.getTypeName().getType()
or
result = this.getPattern().asRecordPattern().getType()
}
/** Gets a printable representation of this expression. */
override string toString() { result = "...instanceof..." }
@@ -1592,7 +1648,9 @@ class NotInstanceOfExpr extends Expr, @notinstanceofexpr {
* A local variable declaration expression.
*
* Contexts in which such expressions may occur include
* local variable declaration statements and `for` loops.
* local variable declaration statements, `for` loops,
* and binding patterns such as `if (x instanceof T t)` and
* `case String s:`.
*/
class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr {
/** Gets an access to the variable declared by this local variable declaration expression. */
@@ -1612,18 +1670,86 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr {
exists(EnhancedForStmt efs | efs.getVariable() = this | result.isNthChildOf(efs, -1))
or
exists(InstanceOfExpr ioe | this.getParent() = ioe | result.isNthChildOf(ioe, 1))
or
exists(PatternCase pc | this.getParent() = pc | result.isNthChildOf(pc, -2))
or
exists(RecordPatternExpr rpe, int index |
this.isNthChildOf(rpe, index) and result.isNthChildOf(rpe, -(index + 1))
)
}
/** Gets the name of the variable declared by this local variable declaration expression. */
string getName() { result = this.getVariable().getName() }
/** Gets the initializer expression of this local variable declaration expression, if any. */
/**
* Gets the switch statement or expression whose pattern declares this identifier, if any.
*/
SwitchBlock getAssociatedSwitch() {
exists(PatternCase pc |
pc = result.(SwitchStmt).getAPatternCase()
or
pc = result.(SwitchExpr).getAPatternCase()
|
this = pc.getPattern().getAChildExpr*()
)
}
/** Holds if this is a declaration stemming from a pattern switch case. */
predicate hasAssociatedSwitch() { exists(this.getAssociatedSwitch()) }
/**
* Gets the instanceof expression whose pattern declares this identifier, if any.
*/
InstanceOfExpr getAssociatedInstanceOfExpr() { result.getPattern().getAChildExpr*() = this }
/** Holds if this is a declaration stemming from a pattern instanceof expression. */
predicate hasAssociatedInstanceOfExpr() { exists(this.getAssociatedInstanceOfExpr()) }
/**
* Gets the initializer expression of this local variable declaration expression, if any.
*
* Note this applies specifically to a syntactic initialization like `T varname = init`;
* to include also `e instanceof T varname` and `switch(e) ... case T varname`, which both
* have the effect of initializing `varname` to a known local expression without using
* that syntax, use `getInitOrPatternSource`.
*/
Expr getInit() { result.isNthChildOf(this, 0) }
/**
* Gets the local expression that initializes this variable declaration, if any.
*
* Note this includes explicit `T varname = init;`, as well as `e instanceof T varname`
* and `switch(e) ... case T varname`. To get only explicit initializers, use `getInit`.
*
* Note that record pattern variables like `e instance of T Record(T varname)` do not have
* either an explicit initializer or a pattern source.
*/
Expr getInitOrPatternSource() {
result = this.getInit()
or
exists(SwitchStmt switch |
result = switch.getExpr() and
this = switch.getAPatternCase().getPattern().asBindingPattern()
)
or
exists(SwitchExpr switch |
result = switch.getExpr() and
this = switch.getAPatternCase().getPattern().asBindingPattern()
)
or
exists(InstanceOfExpr ioe |
result = ioe.getExpr() and
this = ioe.getPattern().asBindingPattern()
)
}
/** Holds if this variable declaration implicitly initializes the variable. */
predicate hasImplicitInit() {
exists(CatchClause cc | cc.getVariable() = this) or
exists(CatchClause cc | cc.getVariable() = this)
or
exists(EnhancedForStmt efs | efs.getVariable() = this)
or
this.getParent() instanceof RecordPatternExpr
}
/** Gets a printable representation of this expression. */
@@ -1632,6 +1758,11 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr {
override string getAPrimaryQlClass() { result = "LocalVariableDeclExpr" }
}
/** A local variable declaration that occurs within a record pattern. */
class RecordBindingVariableExpr extends LocalVariableDeclExpr {
RecordBindingVariableExpr() { this.getParent() instanceof RecordPatternExpr }
}
/** An update of a variable or an initialization of the variable. */
class VariableUpdate extends Expr {
VariableUpdate() {
@@ -1660,12 +1791,12 @@ class VariableAssign extends VariableUpdate {
/**
* Gets the source (right-hand side) of this assignment, if any.
*
* An initialization in a `CatchClause` or `EnhancedForStmt` is implicit and
* does not have a source.
* An initialization in a `CatchClause`, `EnhancedForStmt` or `RecordPatternExpr`
* is implicit and does not have a source.
*/
Expr getSource() {
result = this.(AssignExpr).getSource() or
result = this.(LocalVariableDeclExpr).getInit()
result = this.(LocalVariableDeclExpr).getInitOrPatternSource()
}
}
@@ -2512,3 +2643,59 @@ class NotNullExpr extends UnaryExpr, @notnullexpr {
override string getAPrimaryQlClass() { result = "NotNullExpr" }
}
/**
* A binding or record pattern.
*
* Note binding patterns are represented as `LocalVariableDeclExpr`s.
*/
class PatternExpr extends Expr {
PatternExpr() {
(
this.getParent() instanceof SwitchCase or
this.getParent() instanceof InstanceOfExpr or
this.getParent() instanceof RecordPatternExpr
) and
(this instanceof LocalVariableDeclExpr or this instanceof RecordPatternExpr)
}
/**
* Gets this pattern cast to a binding pattern.
*/
LocalVariableDeclExpr asBindingPattern() { result = this }
/**
* Gets this pattern cast to a record pattern.
*/
RecordPatternExpr asRecordPattern() { result = this }
}
/** A record pattern expr, as in `if (x instanceof SomeRecord(int field))`. */
class RecordPatternExpr extends Expr, @recordpatternexpr {
override string toString() { result = this.getType().toString() + "(...)" }
override string getAPrimaryQlClass() { result = "RecordPatternExpr" }
/**
* Gets the `i`th subpattern of this record pattern.
*/
PatternExpr getSubPattern(int i) { result.isNthChildOf(this, i) }
/**
* Holds if this record pattern matches any record of its type.
*
* For example, for `record R(Object o) { }`, pattern `R(Object o)` is unrestricted, whereas
* pattern `R(String s)` is not because it matches a subset of `R` instances, those containing `String`s.
*/
predicate isUnrestricted() {
forall(PatternExpr subPattern, int idx | subPattern = this.getSubPattern(idx) |
subPattern.getType() =
this.getType().(Record).getCanonicalConstructor().getParameter(idx).getType() and
(
subPattern instanceof LocalVariableDeclExpr
or
subPattern.(RecordPatternExpr).isUnrestricted()
)
)
}
}

View File

@@ -737,10 +737,17 @@ class FieldDeclaration extends ExprParent, @fielddecl, Annotatable {
/** Gets the number of fields declared in this declaration. */
int getNumField() { result = max(int idx | fieldDeclaredIn(_, this, idx) | idx) + 1 }
private string stringifyType() {
// Necessary because record fields are missing their type access.
if exists(this.getTypeAccess())
then result = this.getTypeAccess().toString()
else result = this.getAField().getType().toString()
}
override string toString() {
if this.getNumField() = 1
then result = this.getTypeAccess() + " " + this.getField(0) + ";"
else result = this.getTypeAccess() + " " + this.getField(0) + ", ...;"
then result = this.stringifyType() + " " + this.getField(0) + ";"
else result = this.stringifyType() + " " + this.getField(0) + ", ...;"
}
override string getAPrimaryQlClass() { result = "FieldDeclaration" }

View File

@@ -383,15 +383,18 @@ private class PpInstanceOfExpr extends PpAst, InstanceOfExpr {
override string getPart(int i) {
i = 1 and result = " instanceof "
or
i = 3 and result = " " and this.isPattern()
i = 3 and result = " " and this.getPattern() instanceof LocalVariableDeclExpr
or
i = 4 and result = this.getLocalVariableDeclExpr().getName()
i = 4 and
result = this.getPattern().asBindingPattern().getName()
}
override PpAst getChild(int i) {
i = 0 and result = this.getExpr()
or
i = 2 and result = this.getTypeName()
or
i = 2 and result = this.getPattern().asRecordPattern()
}
}
@@ -742,11 +745,19 @@ private class PpSwitchStmt extends PpAst, SwitchStmt {
}
}
private predicate isNonNullDefaultCase(SwitchCase sc) {
sc instanceof DefaultCase and not sc instanceof NullDefaultCase
}
private class PpSwitchCase extends PpAst, SwitchCase {
PpSwitchCase() { not this instanceof PatternCase }
override string getPart(int i) {
i = 0 and result = "default" and this instanceof DefaultCase
i = 0 and result = "default" and isNonNullDefaultCase(this)
or
i = 0 and result = "case " and this instanceof ConstCase
i = 0 and result = "case null, default" and this instanceof NullDefaultCase
or
i = 0 and result = "case " and not this instanceof DefaultCase
or
exists(int j | i = 2 * j and j != 0 and result = ", " and exists(this.(ConstCase).getValue(j)))
or
@@ -758,7 +769,7 @@ private class PpSwitchCase extends PpAst, SwitchCase {
}
private int lastConstCaseValueIndex() {
result = 1 + 2 * max(int j | j = 0 or exists(this.(ConstCase).getValue(j)))
result = 1 + 2 * (max(int j | j = 0 or exists(this.(ConstCase).getValue(j))))
}
override PpAst getChild(int i) {
@@ -770,6 +781,32 @@ private class PpSwitchCase extends PpAst, SwitchCase {
}
}
private class PpPatternCase extends PpAst, PatternCase {
override string getPart(int i) {
i = 0 and result = "case "
or
i = 2 and this.getPattern() instanceof LocalVariableDeclExpr and result = " "
or
i = 3 and result = this.getPattern().asBindingPattern().getName()
or
i = 4 and result = ":" and not this.isRule()
or
i = 4 and result = " -> " and this.isRule()
or
i = 6 and result = ";" and exists(this.getRuleExpression())
}
override PpAst getChild(int i) {
i = 1 and result = this.getPattern().asBindingPattern().getTypeAccess()
or
i = 1 and result = this.getPattern().asRecordPattern()
or
i = 5 and result = this.getRuleExpression()
or
i = 5 and result = this.getRuleStatement()
}
}
private class PpSynchronizedStmt extends PpAst, SynchronizedStmt {
override string getPart(int i) {
i = 0 and result = "synchronized ("
@@ -885,6 +922,8 @@ private class PpAssertStmt extends PpAst, AssertStmt {
private class PpLocalVariableDeclStmt extends PpAst, LocalVariableDeclStmt {
override string getPart(int i) {
i = 0 and not exists(this.getAVariable().getTypeAccess()) and result = "var"
or
i = 1 and result = " "
or
exists(int v | v > 1 and i = 2 * v - 1 and result = ", " and v = this.getAVariableIndex())
@@ -1025,3 +1064,37 @@ private class PpCallable extends PpAst, Callable {
i = 5 + 4 * this.getNumberOfParameters() and result = this.getBody()
}
}
private class PpRecordPattern extends PpAst, RecordPatternExpr {
override string getPart(int i) {
i = 0 and result = this.getType().getName()
or
i = 1 and result = "("
or
i = 1 + ((any(int x | x >= 1 and exists(this.getSubPattern(x)))) * 4) and result = ", "
or
i = 1 + (count(this.getSubPattern(_)) * 4) and result = ")"
or
exists(int x, LocalVariableDeclExpr v, int offset | v = this.getSubPattern(x) |
i = (offset) + (x * 4) and
(
offset = 2 and not exists(v.getTypeAccess()) and result = "var"
or
offset = 3 and result = " "
)
)
}
override PpAst getChild(int i) {
exists(int x, PatternExpr subPattern, int offset | subPattern = this.getSubPattern(x) |
i = (offset) + (x * 4) and
(
result = subPattern.(RecordPatternExpr) and offset = 2
or
result = subPattern.(LocalVariableDeclExpr).getTypeAccess() and offset = 2
or
result = subPattern.(LocalVariableDeclExpr) and offset = 4
)
)
}
}

View File

@@ -423,7 +423,8 @@ private class SingleLocalVarDeclParent extends ExprOrStmt {
SingleLocalVarDeclParent() {
this instanceof EnhancedForStmt or
this instanceof CatchClause or
this.(InstanceOfExpr).isPattern()
this.(InstanceOfExpr).isPattern() or
this instanceof PatternCase
}
/** Gets the variable declaration that this element contains */

View File

@@ -382,21 +382,43 @@ class SwitchStmt extends Stmt, @switchstmt {
*/
Stmt getStmt(int index) { result = this.getAStmt() and result.getIndex() = index }
/**
* Gets the `i`th case of this `switch` statement,
* which may be either a normal `case` or a `default`.
*/
SwitchCase getCase(int i) {
result =
rank[i + 1](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx)
}
/**
* Gets a case of this `switch` statement,
* which may be either a normal `case` or a `default`.
*/
SwitchCase getACase() { result = this.getAConstCase() or result = this.getDefaultCase() }
SwitchCase getACase() { result.getParent() = this }
/** Gets a (non-default) `case` of this `switch` statement. */
ConstCase getAConstCase() { result.getParent() = this }
/** Gets a (non-default) constant `case` of this `switch` statement. */
ConstCase getAConstCase() { result = this.getACase() }
/** Gets the `default` case of this switch statement, if any. */
DefaultCase getDefaultCase() { result.getParent() = this }
/** Gets a (non-default) pattern `case` of this `switch` statement. */
PatternCase getAPatternCase() { result = this.getACase() }
/**
* Gets the `default` case of this switch statement, if any.
*
* Note this may be `default` or `case null, default`.
*/
DefaultCase getDefaultCase() { result = this.getACase() }
/** Gets the expression of this `switch` statement. */
Expr getExpr() { result.getParent() = this }
/** Holds if this switch has a case handling a null literal. */
predicate hasNullCase() {
this.getAConstCase().getValue(_) instanceof NullLiteral or
this.getACase() instanceof NullDefaultCase
}
override string pp() { result = "switch (...)" }
override string toString() { result = "switch (...)" }
@@ -406,6 +428,13 @@ class SwitchStmt extends Stmt, @switchstmt {
override string getAPrimaryQlClass() { result = "SwitchStmt" }
}
/**
* A `switch` statement or expression.
*/
class SwitchBlock extends StmtParent {
SwitchBlock() { this instanceof SwitchStmt or this instanceof SwitchExpr }
}
/**
* A case of a `switch` statement or expression.
*
@@ -428,6 +457,21 @@ class SwitchCase extends Stmt, @case {
result = this.getSwitch().getExpr() or result = this.getSwitchExpr().getExpr()
}
/**
* Gets this case's ordinal in its switch block.
*/
int getCaseIndex() {
this = any(SwitchStmt ss).getCase(result) or this = any(SwitchExpr se).getCase(result)
}
/**
* Holds if this is the `n`th case of switch block `parent`.
*/
pragma[nomagic]
predicate isNthCaseOf(SwitchBlock parent, int n) {
this.getCaseIndex() = n and this.getParent() = parent
}
/**
* Holds if this `case` is a switch labeled rule of the form `... -> ...`.
*/
@@ -462,17 +506,27 @@ class SwitchCase extends Stmt, @case {
}
}
/** A constant `case` of a switch statement. */
/**
* A constant `case` of a switch statement.
*
* Note this excludes `case null, default` even though that includes a null constant. It
* does however include plain `case null`.
*/
class ConstCase extends SwitchCase {
ConstCase() { exists(Expr e | e.getParent() = this | e.getIndex() >= 0) }
ConstCase() {
exists(Expr e | e.getParent() = this and e.getIndex() >= 0 and not e instanceof PatternExpr) and
// For backward compatibility, we don't include `case null, default:` here, on the assumption
// this will come as a surprise to CodeQL that predates that statement's validity.
not isNullDefaultCase(this)
}
/** Gets the `case` constant at index 0. */
Expr getValue() { result.getParent() = this and result.getIndex() = 0 }
Expr getValue() { result.isNthChildOf(this, 0) }
/**
* Gets the `case` constant at the specified index.
* Gets the `case` constant at index `i`.
*/
Expr getValue(int i) { result.getParent() = this and result.getIndex() = i and i >= 0 }
Expr getValue(int i) { result.isNthChildOf(this, i) and i >= 0 }
override string pp() { result = "case ..." }
@@ -483,9 +537,36 @@ class ConstCase extends SwitchCase {
override string getAPrimaryQlClass() { result = "ConstCase" }
}
/** A `default` case of a `switch` statement */
/** A pattern case of a `switch` statement */
class PatternCase extends SwitchCase {
PatternExpr pattern;
PatternCase() { pattern.isNthChildOf(this, 0) }
/** Gets this case's pattern. */
PatternExpr getPattern() { result = pattern }
/** Gets the guard applicable to this pattern case, if any. */
Expr getGuard() { result.isNthChildOf(this, -3) }
override string pp() { result = "case <Pattern>" }
override string toString() { result = "case <Pattern>" }
override string getHalsteadID() { result = "PatternCase" }
override string getAPrimaryQlClass() { result = "PatternCase" }
}
/**
* A `default` or `case null, default` case of a `switch` statement or expression.
*/
class DefaultCase extends SwitchCase {
DefaultCase() { not exists(Expr e | e.getParent() = this | e.getIndex() >= 0) }
DefaultCase() {
isNullDefaultCase(this)
or
not exists(Expr e | e.getParent() = this | e.getIndex() >= 0)
}
override string pp() { result = "default" }
@@ -496,6 +577,19 @@ class DefaultCase extends SwitchCase {
override string getAPrimaryQlClass() { result = "DefaultCase" }
}
/** A `case null, default` statement of a `switch` statement or expression. */
class NullDefaultCase extends DefaultCase {
NullDefaultCase() { isNullDefaultCase(this) }
override string pp() { result = "case null, default" }
override string toString() { result = "case null, default" }
override string getHalsteadID() { result = "NullDefaultCase" }
override string getAPrimaryQlClass() { result = "NullDefaultCase" }
}
/** A `synchronized` statement. */
class SynchronizedStmt extends Stmt, @synchronizedstmt {
/** Gets the expression on which this `synchronized` statement synchronizes. */

View File

@@ -746,6 +746,13 @@ class DataClass extends Class {
*/
class Record extends Class {
Record() { isRecord(this) }
/**
* Gets the canonical constructor of this record.
*/
Constructor getCanonicalConstructor() {
result = this.getAConstructor() and isCanonicalConstr(result)
}
}
/** An intersection type. */

View File

@@ -15,9 +15,12 @@ class Variable extends @variable, Annotatable, Element, Modifiable {
/** Gets an access to this variable. */
VarAccess getAnAccess() { variableBinding(result, this) }
/** Gets an expression on the right-hand side of an assignment to this variable. */
/**
* Gets an expression assigned to this variable, either appearing on the right-hand side of an
* assignment or bound to it via a binding `instanceof` expression or `switch` block.
*/
Expr getAnAssignedValue() {
exists(LocalVariableDeclExpr e | e.getVariable() = this and result = e.getInit())
exists(LocalVariableDeclExpr e | e.getVariable() = this and result = e.getInitOrPatternSource())
or
exists(AssignExpr e | e.getDest() = this.getAnAccess() and result = e.getSource())
}

View File

@@ -7,6 +7,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
private import semmle.code.java.controlflow.internal.SwitchCases
/**
* A basic block that terminates in a condition, splitting the subsequent control flow.
@@ -18,7 +19,7 @@ class ConditionBlock extends BasicBlock {
ConditionNode getConditionNode() { result = this.getLastNode() }
/** Gets the condition of the last node of this basic block. */
Expr getCondition() { result = this.getConditionNode().getCondition() }
ExprParent getCondition() { result = this.getConditionNode().getCondition() }
/** Gets a `true`- or `false`-successor of the last node of this basic block. */
BasicBlock getTestSuccessor(boolean testIsTrue) {
@@ -72,6 +73,54 @@ class ConditionBlock extends BasicBlock {
}
}
// Join order engineering -- first determine the switch block and the case indices required, then retrieve them.
bindingset[switch, i]
pragma[inline_late]
private predicate isNthCaseOf(SwitchBlock switch, SwitchCase c, int i) { c.isNthCaseOf(switch, i) }
/**
* Gets a switch case >= pred, up to but not including `pred`'s successor pattern case,
* where `pred` is declared on `switch`.
*/
private SwitchCase getACaseUpToNextPattern(PatternCase pred, SwitchBlock switch) {
// Note we do include `case null, default` (as well as plain old `default`) here.
not result.(ConstCase).getValue(_) instanceof NullLiteral and
exists(int maxCaseIndex |
switch = pred.getParent() and
if exists(getNextPatternCase(pred))
then maxCaseIndex = getNextPatternCase(pred).getCaseIndex() - 1
else maxCaseIndex = lastCaseIndex(switch)
|
isNthCaseOf(switch, result, [pred.getCaseIndex() .. maxCaseIndex])
)
}
/**
* Gets the closest pattern case preceding `case`, including `case` itself, if any.
*/
private PatternCase getClosestPrecedingPatternCase(SwitchCase case) {
case = getACaseUpToNextPattern(result, _)
}
/**
* Holds if `pred` is a control-flow predecessor of switch case `sc` that is not a
* fall-through from a previous case.
*
* For classic switches that means flow from the selector expression; for switches
* involving pattern cases it can also mean flow from a previous pattern case's type
* test or guard failing and proceeding to then consider subsequent cases.
*/
private predicate isNonFallThroughPredecessor(SwitchCase sc, ControlFlowNode pred) {
pred = sc.getControlFlowNode().getAPredecessor() and
(
pred.(Expr).getParent*() = sc.getSelectorExpr()
or
pred.(Expr).getParent*() = getClosestPrecedingPatternCase(sc).getGuard()
or
pred = getClosestPrecedingPatternCase(sc)
)
}
/**
* 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
@@ -103,13 +152,21 @@ class Guard extends ExprParent {
}
/**
* Gets the basic block containing this guard or the basic block containing
* the switch expression if the guard is a switch case.
* Gets the basic block containing this guard or the basic block that tests the
* applicability of this switch case -- for a pattern case this is the case statement
* itself; for a non-pattern case this is the most recent pattern case or the top of
* the switch block if there is none.
*/
BasicBlock getBasicBlock() {
result = this.(Expr).getBasicBlock() or
result = this.(SwitchCase).getSwitch().getExpr().getBasicBlock() or
result = this.(SwitchCase).getSwitchExpr().getExpr().getBasicBlock()
// Not a switch case
result = this.(Expr).getBasicBlock()
or
// Return the closest pattern case statement before this one, including this one.
result = getClosestPrecedingPatternCase(this).getBasicBlock()
or
// Not a pattern case and no preceding pattern case -- return the top of the switch block.
not exists(getClosestPrecedingPatternCase(this)) and
result = this.(SwitchCase).getSelectorExpr().getBasicBlock()
}
/**
@@ -126,6 +183,39 @@ class Guard extends ExprParent {
)
}
/**
* Holds if this guard tests whether `testedExpr` has type `testedType`.
*
* `restricted` is true if the test applies additional restrictions on top of just `testedType`, and so
* this guard failing does not guarantee `testedExpr` is *not* a `testedType`-- for example,
* matching `record R(Object o)` with `case R(String s)` is a guard with an additional restriction on the
* type of field `o`, so the guard passing guarantees `testedExpr` is an `R`, but it failing does not
* guarantee `testedExpr` is not an `R`.
*/
predicate appliesTypeTest(Expr testedExpr, Type testedType, boolean restricted) {
(
exists(InstanceOfExpr ioe | this = ioe |
testedExpr = ioe.getExpr() and
testedType = ioe.getSyntacticCheckedType()
)
or
exists(PatternCase pc | this = pc |
pc.getSelectorExpr() = testedExpr and
testedType = pc.getPattern().getType()
)
) and
(
if
exists(RecordPatternExpr rpe |
rpe = [this.(InstanceOfExpr).getPattern(), this.(PatternCase).getPattern()]
|
not rpe.isUnrestricted()
)
then restricted = true
else restricted = false
)
}
/**
* Holds if the evaluation of this guard to `branch` corresponds to the edge
* from `bb1` to `bb2`.
@@ -139,10 +229,11 @@ class Guard extends ExprParent {
or
exists(SwitchCase sc, ControlFlowNode pred |
sc = this and
// Pattern cases are handled as ConditionBlocks above.
not sc instanceof PatternCase and
branch = true and
bb2.getFirstNode() = sc.getControlFlowNode() and
pred = sc.getControlFlowNode().getAPredecessor() and
pred.(Expr).getParent*() = sc.getSelectorExpr() and
isNonFallThroughPredecessor(sc, pred) and
bb1 = pred.getBasicBlock()
)
or
@@ -176,12 +267,14 @@ class Guard extends ExprParent {
}
private predicate switchCaseControls(SwitchCase sc, BasicBlock bb) {
exists(BasicBlock caseblock, Expr selector |
selector = sc.getSelectorExpr() and
exists(BasicBlock caseblock |
// Pattern cases are handled as condition blocks
not sc instanceof PatternCase and
caseblock.getFirstNode() = sc.getControlFlowNode() and
caseblock.bbDominates(bb) and
// Check we can't fall through from a previous block:
forall(ControlFlowNode pred | pred = sc.getControlFlowNode().getAPredecessor() |
pred.(Expr).getParent*() = selector
isNonFallThroughPredecessor(sc, pred)
)
)
}

View File

@@ -170,6 +170,7 @@ class ConstSwitchStmt extends SwitchStmt {
/** Gets the matching case, if it can be deduced. */
SwitchCase getMatchingCase() {
// Must be a value we can deduce
// TODO: handle other known constants (enum constants, String constants)
exists(this.getExpr().(ConstantExpr).getIntValue()) and
if exists(this.getMatchingConstCase())
then result = this.getMatchingConstCase()

View File

@@ -0,0 +1,29 @@
/** Provides utility predicates relating to switch cases. */
import java
/**
* Gets the `i`th `PatternCase` defined on `switch`, if one exists.
*/
private PatternCase getPatternCase(SwitchBlock switch, int i) {
result =
rank[i + 1](PatternCase pc, int caseIdx | pc.isNthCaseOf(switch, caseIdx) | pc order by caseIdx)
}
/**
* Gets the first `PatternCase` defined on `switch`, if one exists.
*/
PatternCase getFirstPatternCase(SwitchBlock switch) { result = getPatternCase(switch, 0) }
/**
* Gets the PatternCase after pc, if one exists.
*/
PatternCase getNextPatternCase(PatternCase pc) {
exists(int idx, SwitchBlock switch |
pc = getPatternCase(switch, idx) and result = getPatternCase(switch, idx + 1)
)
}
int lastCaseIndex(SwitchBlock switch) {
result = max(int i | any(SwitchCase c).isNthCaseOf(switch, i))
}

View File

@@ -100,9 +100,9 @@ predicate dereference(Expr e) {
or
exists(SynchronizedStmt synch | synch.getExpr() = e)
or
exists(SwitchStmt switch | switch.getExpr() = e)
exists(SwitchStmt switch | switch.getExpr() = e and not switch.hasNullCase())
or
exists(SwitchExpr switch | switch.getExpr() = e)
exists(SwitchExpr switch | switch.getExpr() = e and not switch.hasNullCase())
or
exists(FieldAccess fa, Field f | fa.getQualifier() = e and fa.getField() = f and not f.isStatic())
or
@@ -460,7 +460,7 @@ private predicate interestingCond(SsaSourceVariable npecand, ConditionBlock cond
varMaybeNullInBlock(_, npecand, cond, _) or
varConditionallyNull(npecand.getAnSsaVariable(), cond, _)
) and
not cond.getCondition().getAChildExpr*() = npecand.getAnAccess()
not cond.getCondition().(Expr).getAChildExpr*() = npecand.getAnAccess()
}
/** A pair of correlated conditions for a given NPE candidate. */
@@ -588,7 +588,7 @@ private predicate trackingVar(
exists(ConditionBlock cond |
interestingCond(npecand, cond) and
varMaybeNullInBlock(_, npecand, cond, _) and
cond.getCondition().getAChildExpr*() = trackvar.getAnAccess() and
cond.getCondition().(Expr).getAChildExpr*() = trackvar.getAnAccess() and
trackssa.getSourceVariable() = trackvar and
trackssa.getDefiningExpr().(VariableAssign).getSource() = init
|

View File

@@ -414,29 +414,27 @@ private predicate downcastSuccessor(VarAccess va, RefType t) {
}
/**
* Holds if `va` is an access to a value that is guarded by `instanceof t`.
* Holds if `va` is an access to a value that is guarded by `instanceof t` or `case e t`.
*/
private predicate instanceOfGuarded(VarAccess va, RefType t) {
exists(InstanceOfExpr ioe, BaseSsaVariable v |
ioe.getExpr() = v.getAUse() and
t = ioe.getCheckedType() and
private predicate typeTestGuarded(VarAccess va, RefType t) {
exists(Guard typeTest, BaseSsaVariable v |
typeTest.appliesTypeTest(v.getAUse(), t, _) and
va = v.getAUse() and
guardControls_v1(ioe, va.getBasicBlock(), true)
guardControls_v1(typeTest, va.getBasicBlock(), true)
)
}
/**
* Holds if `aa` is an access to a value that is guarded by `instanceof t`.
* Holds if `aa` is an access to a value that is guarded by `instanceof t` or `case e t`.
*/
predicate arrayInstanceOfGuarded(ArrayAccess aa, RefType t) {
exists(InstanceOfExpr ioe, BaseSsaVariable v1, BaseSsaVariable v2, ArrayAccess aa1 |
ioe.getExpr() = aa1 and
t = ioe.getCheckedType() and
predicate arrayTypeTestGuarded(ArrayAccess aa, RefType t) {
exists(Guard typeTest, BaseSsaVariable v1, BaseSsaVariable v2, ArrayAccess aa1 |
typeTest.appliesTypeTest(aa1, t, _) and
aa1.getArray() = v1.getAUse() and
aa1.getIndexExpr() = v2.getAUse() and
aa.getArray() = v1.getAUse() and
aa.getIndexExpr() = v2.getAUse() and
guardControls_v1(ioe, aa.getBasicBlock(), true)
guardControls_v1(typeTest, aa.getBasicBlock(), true)
)
}
@@ -462,8 +460,8 @@ private predicate typeFlowBaseCand(TypeFlowNode n, RefType t) {
upcast(n, srctype) or
upcastEnhancedForStmt(n.asSsa(), srctype) or
downcastSuccessor(n.asExpr(), srctype) or
instanceOfGuarded(n.asExpr(), srctype) or
arrayInstanceOfGuarded(n.asExpr(), srctype) or
typeTestGuarded(n.asExpr(), srctype) or
arrayTypeTestGuarded(n.asExpr(), srctype) or
n.asExpr().(FunctionalExpr).getConstructedType() = srctype or
superAccess(n.asExpr(), srctype)
|
@@ -629,7 +627,7 @@ private predicate instanceofDisjunctionGuarded(TypeFlowNode n, RefType t) {
bb.bbDominates(va.getBasicBlock()) and
va = v.getAUse() and
instanceofDisjunct(ioe, bb, v) and
t = ioe.getCheckedType() and
t = ioe.getSyntacticCheckedType() and
n.asExpr() = va
)
}

View File

@@ -232,6 +232,27 @@ predicate storeStep(Node node1, ContentSet f, Node node2) {
pragma[only_bind_out](node2.getEnclosingCallable())
}
// Manual join hacking, to avoid a parameters x fields product.
pragma[noinline]
private predicate hasNamedField(Record r, Field f, string name) {
f = r.getAField() and name = f.getName()
}
pragma[noinline]
private predicate hasNamedCanonicalParameter(Record r, Parameter p, int idx, string name) {
p = r.getCanonicalConstructor().getParameter(idx) and name = p.getName()
}
private Field getLexicallyOrderedRecordField(Record r, int idx) {
result =
rank[idx + 1](Field f, int i, Parameter p, string name |
hasNamedCanonicalParameter(r, p, i, name) and
hasNamedField(r, f, name)
|
f order by i
)
}
/**
* Holds if data can flow from `node1` to `node2` via a read of `f`.
* Thus, `node1` references an object with a field `f` whose value ends up in
@@ -256,6 +277,13 @@ predicate readStep(Node node1, ContentSet f, Node node2) {
node2.asExpr() = get
)
or
exists(RecordPatternExpr rpe, PatternExpr subPattern, int i |
node1.asExpr() = rpe and
subPattern = rpe.getSubPattern(i) and
node2.asExpr() = subPattern and
f.(FieldContent).getField() = getLexicallyOrderedRecordField(rpe.getType(), i)
)
or
f instanceof ArrayContent and arrayReadStep(node1, node2, _)
or
f instanceof CollectionContent and collectionReadStep(node1, node2)
@@ -347,7 +375,18 @@ predicate compatibleTypes(DataFlowType t1, DataFlowType t2) { compatibleTypes0(t
/** A node that performs a type cast. */
class CastNode extends ExprNode {
CastNode() { this.getExpr() instanceof CastingExpr }
CastNode() {
this.getExpr() instanceof CastingExpr
or
exists(SsaExplicitUpdate upd |
upd.getDefiningExpr().(VariableAssign).getSource() =
[
any(SwitchStmt ss).getExpr(), any(SwitchExpr se).getExpr(),
any(InstanceOfExpr ioe).getExpr()
] and
this.asExpr() = upd.getAFirstUse()
)
}
}
private newtype TDataFlowCallable =

View File

@@ -190,6 +190,19 @@ predicate simpleAstFlowStep(Expr e1, Expr e2) {
e2 = any(NotNullExpr nne | e1 = nne.getExpr())
or
e2.(WhenExpr).getBranch(_).getAResult() = e1
or
// In the following three cases only record patterns need this flow edge, leading from the bound instanceof
// or switch tested expression to a record pattern that will read its fields. Simple binding patterns are
// handled via VariableAssign.getSource instead.
exists(SwitchExpr se |
e1 = se.getExpr() and e2 = se.getACase().(PatternCase).getPattern().asRecordPattern()
)
or
exists(SwitchStmt ss |
e1 = ss.getExpr() and e2 = ss.getACase().(PatternCase).getPattern().asRecordPattern()
)
or
exists(InstanceOfExpr ioe | e1 = ioe.getExpr() and e2 = ioe.getPattern().asRecordPattern())
}
private predicate simpleLocalFlowStep0(Node node1, Node node2) {
@@ -197,7 +210,8 @@ private predicate simpleLocalFlowStep0(Node node1, Node node2) {
// Variable flow steps through adjacent def-use and use-use pairs.
exists(SsaExplicitUpdate upd |
upd.getDefiningExpr().(VariableAssign).getSource() = node1.asExpr() or
upd.getDefiningExpr().(AssignOp) = node1.asExpr()
upd.getDefiningExpr().(AssignOp) = node1.asExpr() or
upd.getDefiningExpr().(RecordBindingVariableExpr) = node1.asExpr()
|
node2.asExpr() = upd.getAFirstUse() and
not capturedVariableRead(node2)

View File

@@ -194,13 +194,12 @@ private module Dispatch {
*/
private predicate impossibleDispatchTarget(MethodCall source, Method tgt) {
tgt = viableImpl_v1_cand(source) and
exists(InstanceOfExpr ioe, BaseSsaVariable v, Expr q, RefType t |
exists(Guard typeTest, BaseSsaVariable v, Expr q, RefType t |
source.getQualifier() = q and
v.getAUse() = q and
guardControls_v1(ioe, q.getBasicBlock(), false) and
ioe.getExpr() = v.getAUse() and
ioe.getCheckedType().getErasure() = t and
tgt.getDeclaringType().getSourceDeclaration().getASourceSupertype*() = t
typeTest.appliesTypeTest(v.getAUse(), t, false) and
guardControls_v1(typeTest, q.getBasicBlock(), false) and
tgt.getDeclaringType().getSourceDeclaration().getASourceSupertype*() = t.getErasure()
)
}

View File

@@ -73,13 +73,13 @@ class MetricCallable extends Callable {
// so there should be a branching point for each non-default switch
// case (ignoring those that just fall through to the next case).
private predicate branchingSwitchCase(ConstCase sc) {
not sc.(ControlFlowNode).getASuccessor() instanceof ConstCase and
not sc.(ControlFlowNode).getASuccessor() instanceof DefaultCase and
not sc.(ControlFlowNode).getASuccessor() instanceof SwitchCase and
not defaultFallThrough(sc)
}
private predicate defaultFallThrough(ConstCase sc) {
exists(DefaultCase default | default.(ControlFlowNode).getASuccessor() = sc) or
exists(DefaultCase default | default.(ControlFlowNode).getASuccessor() = sc)
or
defaultFallThrough(sc.(ControlFlowNode).getAPredecessor())
}
@@ -90,6 +90,7 @@ private predicate branchingStmt(Stmt stmt) {
stmt instanceof DoStmt or
stmt instanceof ForStmt or
stmt instanceof EnhancedForStmt or
stmt instanceof PatternCase or
branchingSwitchCase(stmt) or
stmt instanceof CatchClause
}

View File

@@ -0,0 +1,47 @@
class Expr extends @expr {
string toString() { result = "expr" }
}
class LocalVariableDeclExpr extends @localvariabledeclexpr, Expr { }
class InstanceOfExpr extends @instanceofexpr, Expr { }
class Type extends @type {
string toString() { result = "type" }
}
class ExprParent extends @exprparent {
string toString() { result = "exprparent" }
}
// Initialisers of local variable declarations that occur as the 0th child of an instanceof expression should be reparented to be the 0th child of the instanceof itself,
// while the LocalVariableDeclExpr, now without an initialiser, should become its 2nd child.
// This implements a reorganisation of the representation of "o instanceof String s", which used to be InstanceOfExpr -0-> LocalVariableDeclExpr --init-> o
// \-name-> s
// It is now InstanceOfExpr --0-> o
// \-2-> LocalVariableDeclExpr -name-> s
//
// Other children are unaffected.
ExprParent getParent(Expr e) { exprs(e, _, _, result, _) }
predicate hasNewParent(Expr e, ExprParent newParent, int newIndex) {
if
getParent(e) instanceof LocalVariableDeclExpr and
getParent(getParent(e)) instanceof InstanceOfExpr
then (
// Initialiser moves to hang directly off the instanceof expression
newParent = getParent(getParent(e)) and newIndex = 0
) else (
if e instanceof LocalVariableDeclExpr and getParent(e) instanceof InstanceOfExpr
then
// Variable declaration moves to be the instanceof expression's 2nd child
newParent = getParent(e) and newIndex = 2
else exprs(e, _, _, newParent, newIndex) // Other expressions unchanged
)
}
from Expr e, int kind, Type typeid, ExprParent parent, int index
where
exprs(e, kind, typeid, _, _) and
hasNewParent(e, parent, index)
select e, kind, typeid, parent, index

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
description: Add tables for canonical constructors and `case null, default`, plus an expression kind for record patterns. Also alter the representation for instanceof with a binding pattern.
compatibility: backwards
exprs.rel: run exprs.qlo

View File

@@ -17,7 +17,7 @@ from InstanceOfExpr ioe, RefType t, RefType ct
where
ioe.getExpr() instanceof ThisAccess and
t = ioe.getExpr().getType() and
ct = ioe.getCheckedType() and
ct = ioe.getSyntacticCheckedType() and
ct.getAnAncestor() = t
select ioe,
"Testing whether 'this' is an instance of $@ in $@ introduces a dependency cycle between the two types.",

View File

@@ -40,6 +40,6 @@ where
) and
// Also, any value that `v` is initialized to is a fresh container,
forall(Expr e | e = v.getAnAssignedValue() | e instanceof FreshContainer) and
// and `v` is not implicitly initialized by a for-each loop.
not exists(EnhancedForStmt efs | efs.getVariable().getVariable() = v)
// and `v` is not implicitly initialized.
not v.(LocalVariableDecl).getDeclExpr().hasImplicitInit()
select v, "The contents of this container are never initialized."

View File

@@ -39,6 +39,6 @@ where
) and
// Also, any value that `v` is initialized to is a new container,
forall(Expr e | e = v.getAnAssignedValue() | e instanceof ClassInstanceExpr) and
// and `v` is not implicitly initialized by a for-each loop.
not exists(EnhancedForStmt efs | efs.getVariable().getVariable() = v)
// and `v` is not implicitly initialized
not v.(LocalVariableDecl).getDeclExpr().hasImplicitInit()
select v, "The contents of this container are never accessed."

View File

@@ -18,7 +18,7 @@ predicate instanceofInEquals(EqualsMethod m, InstanceOfExpr e) {
e.getEnclosingCallable() = m and
e.getExpr().(VarAccess).getVariable() = m.getParameter() and
exists(RefType instanceofType |
instanceofType = e.getCheckedType() and
instanceofType = e.getSyntacticCheckedType() and
not instanceofType.isFinal()
)
}

View File

@@ -63,7 +63,7 @@ from LocalBoxedVar v
where
forall(Expr e | e = v.getAnAssignedValue() | e.getType() = v.getPrimitiveType()) and
(
not v.getDeclExpr().getParent() instanceof EnhancedForStmt or
not v.getDeclExpr().hasImplicitInit() or
v.getDeclExpr().getParent().(EnhancedForStmt).getExpr().getType().(Array).getComponentType() =
v.getPrimitiveType()
) and

View File

@@ -11,22 +11,13 @@
import java
import DeadLocals
predicate exceptionVariable(LocalVariableDeclExpr ve) {
exists(CatchClause catch | catch.getVariable() = ve)
}
predicate enhancedForVariable(LocalVariableDeclExpr ve) {
exists(EnhancedForStmt for | for.getVariable() = ve)
}
from LocalVariableDeclExpr ve, LocalVariableDecl v
where
v = ve.getVariable() and
not assigned(v) and
not read(v) and
(not exists(ve.getInit()) or exprHasNoEffect(ve.getInit())) and
// Remove contexts where Java forces a variable declaration: enhanced-for and catch clauses.
// Remove contexts where Java forces a variable declaration: enhanced-for, catch clauses and pattern cases.
// Rules about catch clauses belong in an exception handling query
not exceptionVariable(ve) and
not enhancedForVariable(ve)
not ve.hasImplicitInit()
select v, "Variable " + v.getName() + " is not used."

View File

@@ -17,8 +17,7 @@ import Common
from SwitchStmt s, Stmt c
where
c = s.getACase() and
not c.(ControlFlowNode).getASuccessor() instanceof ConstCase and
not c.(ControlFlowNode).getASuccessor() instanceof DefaultCase and
not c.(ControlFlowNode).getASuccessor() instanceof SwitchCase and
not s.(Annotatable).suppressesWarningsAbout("fallthrough") and
mayDropThroughWithoutComment(s, c)
select c,

View File

@@ -14,8 +14,17 @@
| dependency/A.java:15:8:15:8 | E | java.lang.Object |
| dependency/A.java:22:7:22:7 | F | java.lang.Object |
| dependency/A.java:24:7:24:7 | G | java.lang.Throwable |
| dependency/A.java:26:7:26:7 | H | dependency.H$Used1 |
| dependency/A.java:26:7:26:7 | H | dependency.H$Used2 |
| dependency/A.java:26:7:26:7 | H | dependency.H$Used3 |
| dependency/A.java:26:7:26:7 | H | java.lang.Number |
| dependency/A.java:26:7:26:7 | H | java.lang.Object |
| dependency/A.java:26:7:26:7 | H | java.lang.String |
| dependency/A.java:26:7:26:7 | H | java.util.Collection |
| dependency/A.java:27:3:27:18 | T | java.lang.String |
| dependency/A.java:41:22:41:26 | Used1 | dependency.H |
| dependency/A.java:41:22:41:26 | Used1 | java.lang.Object |
| dependency/A.java:42:22:42:26 | Used2 | dependency.H |
| dependency/A.java:42:22:42:26 | Used2 | java.lang.Object |
| dependency/A.java:43:22:43:26 | Used3 | dependency.H |
| dependency/A.java:43:22:43:26 | Used3 | java.lang.Object |

View File

@@ -49,3 +49,39 @@ dependency/A.java:
# 28| 0: [WildcardTypeAccess] ? ...
# 28| 0: [TypeAccess] Number
# 28| 5: [BlockStmt] { ... }
# 29| 4: [Method] test3
# 29| 3: [TypeAccess] void
#-----| 4: (Parameters)
# 29| 0: [Parameter] o
# 29| 0: [TypeAccess] Object
# 29| 5: [BlockStmt] { ... }
# 30| 0: [IfStmt] if (...)
# 30| 0: [InstanceOfExpr] ...instanceof...
# 30| 0: [VarAccess] o
# 30| 1: [TypeAccess] Used1
# 30| 1: [ReturnStmt] return ...
# 31| 1: [SwitchStmt] switch (...)
# 31| -1: [VarAccess] o
# 32| 0: [PatternCase] case <Pattern>
#-----| 0: (Single Local Variable Declaration)
# 32| 0: [TypeAccess] Used2
# 32| 1: [LocalVariableDeclExpr] u2
# 32| 1: [BreakStmt] break
# 33| 2: [DefaultCase] default
# 33| 3: [BreakStmt] break
# 35| 2: [LocalVariableDeclStmt] var ...;
# 35| 1: [LocalVariableDeclExpr] x
# 35| 0: [SwitchExpr] switch (...)
# 35| -1: [VarAccess] o
# 36| 0: [PatternCase] case <Pattern>
#-----| 0: (Single Local Variable Declaration)
# 36| 0: [TypeAccess] Used3
# 36| 1: [LocalVariableDeclExpr] u3
# 36| 1: [YieldStmt] yield ...
# 36| 0: [IntegerLiteral] 1
# 37| 2: [DefaultCase] default
# 37| 3: [YieldStmt] yield ...
# 37| 0: [IntegerLiteral] 2
# 41| 5: [Class] Used1
# 42| 6: [Class] Used2
# 43| 7: [Class] Used3

View File

@@ -10,4 +10,7 @@
| dependency/A.java:22:7:22:7 | F | dependency/A.java:22:7:22:7 | F |
| dependency/A.java:24:7:24:7 | G | dependency/A.java:24:7:24:7 | G |
| dependency/A.java:26:7:26:7 | H | dependency/A.java:26:7:26:7 | H |
| dependency/A.java:41:22:41:26 | Used1 | dependency/A.java:41:22:41:26 | Used1 |
| dependency/A.java:42:22:42:26 | Used2 | dependency/A.java:42:22:42:26 | Used2 |
| dependency/A.java:43:22:43:26 | Used3 | dependency/A.java:43:22:43:26 | Used3 |
| file://:0:0:0:0 | C[] | dependency/A.java:9:7:9:7 | C |

View File

@@ -26,4 +26,19 @@ class G extends Throwable { }
class H {
<T extends String> T test(T t) { return t; }
void test2(java.util.Collection<? extends Number> t) {}
void test3(Object o) {
if (o instanceof Used1) return;
switch (o) {
case Used2 u2: break;
default: break;
}
var x = switch (o) {
case Used3 u3: yield 1;
default: yield 2;
};
}
static class Used1 { }
static class Used2 { }
static class Used3 { }
}

View File

@@ -0,0 +1 @@
//semmle-extractor-options: --javac-args --release 21

View File

@@ -0,0 +1,75 @@
public class Test {
public static Object testFlowThroughSwitchStmt(String s, Integer i, boolean unknown) {
Object o = unknown ? s : i;
switch (o) {
case Integer i2 -> { return (Object)i2; }
default -> { return null; }
}
}
public static Object testFlowThroughSwitchExpr(String s, Integer i, boolean unknown) {
Object o = unknown ? s : i;
Object toRet = switch (o) {
case Integer i2 -> (Object)i2;
default -> null;
};
return toRet;
}
public static Object testFlowThroughBindingInstanceOf(String s, Integer i, boolean unknown) {
Object o = unknown ? s : i;
if (o instanceof Integer i2)
return (Object)i2;
else
return null;
}
public static Object testFlowThroughSwitchStmtWrapper(Wrapper s, Wrapper i, boolean unknown) {
Wrapper o = unknown ? s : i;
switch (o) {
case Wrapper(Integer i2) -> { return (Object)i2; }
default -> { return null; }
}
}
public static Object testFlowThroughSwitchExprWrapper(Wrapper s, Wrapper i, boolean unknown) {
Wrapper o = unknown ? s : i;
Object toRet = switch (o) {
case Wrapper(Integer i2) -> (Object)i2;
default -> null;
};
return toRet;
}
public static Object testFlowThroughBindingInstanceOfWrapper(Wrapper s, Wrapper i, boolean unknown) {
Wrapper o = unknown ? s : i;
if (o instanceof Wrapper(Integer i2))
return (Object)i2;
else
return null;
}
public static <T> T source() { return null; }
public static void sink(Object o) { }
public static void test(boolean unknown, boolean unknown2) {
String source1 = source();
Integer source2 = source();
sink(testFlowThroughSwitchStmt(source1, source2, unknown));
sink(testFlowThroughSwitchExpr(source1, source2, unknown));
sink(testFlowThroughBindingInstanceOf(source1, source2, unknown));
Wrapper source1Wrapper = new Wrapper((String)source());
Wrapper source2Wrapper = new Wrapper((Integer)source());
sink(testFlowThroughSwitchStmtWrapper(source1Wrapper, source2Wrapper, unknown));
sink(testFlowThroughSwitchExprWrapper(source1Wrapper, source2Wrapper, unknown));
sink(testFlowThroughBindingInstanceOfWrapper(source1Wrapper, source2Wrapper, unknown));
}
record Wrapper(Object o) { }
}

View File

@@ -0,0 +1 @@
//semmle-extractor-options: --javac-args --release 21

View File

@@ -0,0 +1,6 @@
| Test.java:60:23:60:30 | source(...) | Test.java:61:10:61:61 | testFlowThroughSwitchStmt(...) |
| Test.java:60:23:60:30 | source(...) | Test.java:62:10:62:61 | testFlowThroughSwitchExpr(...) |
| Test.java:60:23:60:30 | source(...) | Test.java:63:10:63:68 | testFlowThroughBindingInstanceOf(...) |
| Test.java:66:51:66:58 | source(...) | Test.java:67:10:67:82 | testFlowThroughSwitchStmtWrapper(...) |
| Test.java:66:51:66:58 | source(...) | Test.java:68:10:68:82 | testFlowThroughSwitchExprWrapper(...) |
| Test.java:66:51:66:58 | source(...) | Test.java:69:10:69:89 | testFlowThroughBindingInstanceOfWrapper(...) |

View File

@@ -0,0 +1,18 @@
import java
import semmle.code.java.dataflow.DataFlow
module TestConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source.asExpr() = any(MethodCall mc | mc.getCallee().getName() = "source")
}
predicate isSink(DataFlow::Node sink) {
sink.asExpr() = any(MethodCall mc | mc.getMethod().getName() = "sink").getAnArgument()
}
}
module Flow = DataFlow::Global<TestConfig>;
from DataFlow::Node source, DataFlow::Node sink
where Flow::flow(source, sink)
select source, sink

View File

@@ -6,6 +6,8 @@ Test.java:
#-----| 4: (Parameters)
# 2| 0: [Parameter] s
# 2| 0: [TypeAccess] String
# 2| 1: [Parameter] unknown
# 2| 0: [TypeAccess] boolean
# 2| 5: [BlockStmt] { ... }
# 3| 0: [LocalVariableDeclStmt] var ...;
# 3| 0: [TypeAccess] int
@@ -38,3 +40,45 @@ Test.java:
# 12| 0: [StringLiteral] "d"
# 13| 3: [DefaultCase] default
# 13| -1: [BlockStmt] { ... }
# 15| 2: [LocalVariableDeclStmt] var ...;
# 15| 0: [TypeAccess] int
# 15| 1: [LocalVariableDeclExpr] len
# 15| 0: [MethodCall] length(...)
# 15| -1: [VarAccess] s
# 16| 3: [SwitchStmt] switch (...)
# 16| -1: [VarAccess] s
# 17| 0: [PatternCase] case <Pattern>
# 17| -3: [EQExpr] ... == ...
# 17| 0: [VarAccess] len
# 17| 1: [IntegerLiteral] 4
# 17| -1: [BlockStmt] { ... }
#-----| 0: (Single Local Variable Declaration)
# 17| 0: [TypeAccess] String
# 17| 1: [LocalVariableDeclExpr] s2
# 18| 1: [ConstCase] case ...
# 18| -1: [BlockStmt] { ... }
# 18| 0: [StringLiteral] "e"
# 19| 2: [DefaultCase] default
# 19| -1: [BlockStmt] { ... }
# 21| 4: [SwitchStmt] switch (...)
# 21| -1: [ConditionalExpr] ...?...:...
# 21| 0: [VarAccess] unknown
# 21| 1: [VarAccess] s
# 21| 2: [MethodCall] toLowerCase(...)
# 21| -1: [VarAccess] s
# 22| 0: [ConstCase] case ...
# 22| -1: [BlockStmt] { ... }
# 22| 0: [StringLiteral] "f"
# 23| 1: [PatternCase] case <Pattern>
# 23| -3: [EQExpr] ... == ...
# 23| 0: [VarAccess] len
# 23| 1: [IntegerLiteral] 4
# 23| -1: [BlockStmt] { ... }
#-----| 0: (Single Local Variable Declaration)
# 23| 0: [TypeAccess] String
# 23| 1: [LocalVariableDeclExpr] s2
# 24| 2: [ConstCase] case ...
# 24| -1: [BlockStmt] { ... }
# 24| 0: [StringLiteral] "g"
# 25| 3: [DefaultCase] default
# 25| -1: [BlockStmt] { ... }

View File

@@ -1,5 +1,5 @@
class Test {
void foo(String s) {
void foo(String s, boolean unknown) {
int x = switch(s) {
case "a", "b" -> 1;
case "c" -> 2;
@@ -12,5 +12,17 @@ class Test {
case "d" -> { }
default -> { }
}
int len = s.length();
switch (s) {
case String s2 when len == 4 -> { }
case "e" -> { }
default -> { }
}
switch (unknown ? s : s.toLowerCase()) {
case "f" -> { }
case String s2 when len == 4 -> { }
case "g" -> { }
default -> { }
}
}
}

View File

@@ -1,3 +1,37 @@
hasBranchEdge
| Test.java:4:7:4:22 | case ... | Test.java:2:39:27:3 | { ... } | Test.java:4:7:4:22 | case ... | true |
| Test.java:5:7:5:17 | case ... | Test.java:2:39:27:3 | { ... } | Test.java:5:7:5:17 | case ... | true |
| Test.java:6:7:6:17 | case ... | Test.java:2:39:27:3 | { ... } | Test.java:6:7:6:17 | case ... | true |
| Test.java:7:7:7:16 | default | Test.java:2:39:27:3 | { ... } | Test.java:7:7:7:16 | default | true |
| Test.java:10:7:10:22 | case ... | Test.java:3:9:3:21 | x | Test.java:10:7:10:22 | case ... | true |
| Test.java:11:7:11:17 | case ... | Test.java:3:9:3:21 | x | Test.java:11:7:11:17 | case ... | true |
| Test.java:12:7:12:17 | case ... | Test.java:3:9:3:21 | x | Test.java:12:7:12:17 | case ... | true |
| Test.java:13:7:13:16 | default | Test.java:3:9:3:21 | x | Test.java:13:7:13:16 | default | true |
| Test.java:17:7:17:37 | case <Pattern> | Test.java:15:5:15:25 | var ...; | Test.java:17:19:17:20 | s2 | true |
| Test.java:17:7:17:37 | case <Pattern> | Test.java:15:5:15:25 | var ...; | Test.java:18:7:18:17 | case ... | false |
| Test.java:17:7:17:37 | case <Pattern> | Test.java:15:5:15:25 | var ...; | Test.java:19:7:19:16 | default | false |
| Test.java:17:27:17:34 | ... == ... | Test.java:17:19:17:20 | s2 | Test.java:17:39:17:41 | { ... } | true |
| Test.java:17:27:17:34 | ... == ... | Test.java:17:19:17:20 | s2 | Test.java:18:7:18:17 | case ... | false |
| Test.java:17:27:17:34 | ... == ... | Test.java:17:19:17:20 | s2 | Test.java:19:7:19:16 | default | false |
| Test.java:18:7:18:17 | case ... | Test.java:15:5:15:25 | var ...; | Test.java:18:7:18:17 | case ... | true |
| Test.java:18:7:18:17 | case ... | Test.java:17:19:17:20 | s2 | Test.java:18:7:18:17 | case ... | true |
| Test.java:19:7:19:16 | default | Test.java:15:5:15:25 | var ...; | Test.java:19:7:19:16 | default | true |
| Test.java:19:7:19:16 | default | Test.java:17:19:17:20 | s2 | Test.java:19:7:19:16 | default | true |
| Test.java:21:13:21:19 | unknown | Test.java:21:5:21:42 | switch (...) | Test.java:21:23:21:23 | s | true |
| Test.java:21:13:21:19 | unknown | Test.java:21:5:21:42 | switch (...) | Test.java:21:27:21:27 | s | false |
| Test.java:22:7:22:17 | case ... | Test.java:21:23:21:23 | s | Test.java:22:7:22:17 | case ... | true |
| Test.java:22:7:22:17 | case ... | Test.java:21:27:21:27 | s | Test.java:22:7:22:17 | case ... | true |
| Test.java:23:7:23:37 | case <Pattern> | Test.java:23:7:23:37 | case <Pattern> | Test.java:23:19:23:20 | s2 | true |
| Test.java:23:7:23:37 | case <Pattern> | Test.java:23:7:23:37 | case <Pattern> | Test.java:24:7:24:17 | case ... | false |
| Test.java:23:7:23:37 | case <Pattern> | Test.java:23:7:23:37 | case <Pattern> | Test.java:25:7:25:16 | default | false |
| Test.java:23:27:23:34 | ... == ... | Test.java:23:19:23:20 | s2 | Test.java:23:39:23:41 | { ... } | true |
| Test.java:23:27:23:34 | ... == ... | Test.java:23:19:23:20 | s2 | Test.java:24:7:24:17 | case ... | false |
| Test.java:23:27:23:34 | ... == ... | Test.java:23:19:23:20 | s2 | Test.java:25:7:25:16 | default | false |
| Test.java:24:7:24:17 | case ... | Test.java:23:7:23:37 | case <Pattern> | Test.java:24:7:24:17 | case ... | true |
| Test.java:24:7:24:17 | case ... | Test.java:23:19:23:20 | s2 | Test.java:24:7:24:17 | case ... | true |
| Test.java:25:7:25:16 | default | Test.java:23:7:23:37 | case <Pattern> | Test.java:25:7:25:16 | default | true |
| Test.java:25:7:25:16 | default | Test.java:23:19:23:20 | s2 | Test.java:25:7:25:16 | default | true |
#select
| Test.java:5:7:5:17 | case ... | Test.java:3:20:3:20 | s | Test.java:5:12:5:14 | "c" | true | false | Test.java:7:7:7:16 | default |
| Test.java:5:7:5:17 | case ... | Test.java:3:20:3:20 | s | Test.java:5:12:5:14 | "c" | true | true | Test.java:5:7:5:17 | case ... |
| Test.java:6:7:6:17 | case ... | Test.java:3:20:3:20 | s | Test.java:6:12:6:14 | "d" | true | false | Test.java:7:7:7:16 | default |
@@ -6,3 +40,11 @@
| Test.java:11:7:11:17 | case ... | Test.java:9:13:9:13 | s | Test.java:11:12:11:14 | "c" | true | true | Test.java:11:7:11:17 | case ... |
| Test.java:12:7:12:17 | case ... | Test.java:9:13:9:13 | s | Test.java:12:12:12:14 | "d" | true | false | Test.java:13:7:13:16 | default |
| Test.java:12:7:12:17 | case ... | Test.java:9:13:9:13 | s | Test.java:12:12:12:14 | "d" | true | true | Test.java:12:7:12:17 | case ... |
| Test.java:17:27:17:34 | ... == ... | Test.java:17:27:17:29 | len | Test.java:17:34:17:34 | 4 | true | true | Test.java:17:39:17:41 | { ... } |
| Test.java:18:7:18:17 | case ... | Test.java:16:13:16:13 | s | Test.java:18:12:18:14 | "e" | true | false | Test.java:19:7:19:16 | default |
| Test.java:18:7:18:17 | case ... | Test.java:16:13:16:13 | s | Test.java:18:12:18:14 | "e" | true | true | Test.java:18:7:18:17 | case ... |
| Test.java:22:7:22:17 | case ... | Test.java:21:13:21:41 | ...?...:... | Test.java:22:12:22:14 | "f" | true | false | Test.java:25:7:25:16 | default |
| Test.java:22:7:22:17 | case ... | Test.java:21:13:21:41 | ...?...:... | Test.java:22:12:22:14 | "f" | true | true | Test.java:22:7:22:17 | case ... |
| Test.java:23:27:23:34 | ... == ... | Test.java:23:27:23:29 | len | Test.java:23:34:23:34 | 4 | true | true | Test.java:23:39:23:41 | { ... } |
| Test.java:24:7:24:17 | case ... | Test.java:21:13:21:41 | ...?...:... | Test.java:24:12:24:14 | "g" | true | false | Test.java:25:7:25:16 | default |
| Test.java:24:7:24:17 | case ... | Test.java:21:13:21:41 | ...?...:... | Test.java:24:12:24:14 | "g" | true | true | Test.java:24:7:24:17 | case ... |

View File

@@ -1,8 +1,13 @@
import java
import semmle.code.java.controlflow.Guards
from Guard g, BasicBlock bb, boolean branch, VarAccess e1, Expr e2, boolean pol
query predicate hasBranchEdge(Guard g, BasicBlock bb1, BasicBlock bb2, boolean branch) {
g.hasBranchEdge(bb1, bb2, branch)
}
from Guard g, BasicBlock bb, boolean branch, Expr e1, Expr e2, boolean pol
where
g.controls(bb, branch) and
g.isEquality(e1, e2, pol)
g.isEquality(e1, e2, pol) and
not e1 instanceof Literal
select g, e1, e2, pol, branch, bb

View File

@@ -1 +1 @@
//semmle-extractor-options: --javac-args -source 14 -target 14
//semmle-extractor-options: --javac-args --release 21

View File

@@ -0,0 +1,29 @@
public class Test {
interface Intf { }
static class Specific implements Intf { public String toString() { return "Specific"; } }
static class Alternative implements Intf { public String toString() { return "Alternative"; } }
public static String caller() {
Alternative a = new Alternative(); // Instantiate this somewhere so there are at least two candidate types in general
return test(new Specific());
}
public static String test(Object o) {
if (o instanceof Object o2) {
// So we should know o2.toString is really Specific.toString():
return o2.toString();
}
switch (o) {
case Object o2 when o2.hashCode() > 0 -> { return o2.toString(); } // Same goes for this `o2`
default -> { return "Not an Intf"; }
}
}
}

View File

@@ -0,0 +1 @@
//semmle-extractor-options: --javac-args --release 21

View File

@@ -0,0 +1,9 @@
| Test.java:1:14:1:17 | super(...) | java.lang.Object.Object |
| Test.java:4:16:4:23 | super(...) | java.lang.Object.Object |
| Test.java:5:16:5:26 | super(...) | java.lang.Object.Object |
| Test.java:9:21:9:37 | new Alternative(...) | Test$Alternative.Alternative |
| Test.java:10:12:10:31 | test(...) | Test.test |
| Test.java:10:17:10:30 | new Specific(...) | Test$Specific.Specific |
| Test.java:18:14:18:26 | toString(...) | Test$Specific.toString |
| Test.java:22:27:22:39 | hashCode(...) | java.lang.Object.hashCode |
| Test.java:22:57:22:69 | toString(...) | Test$Specific.toString |

View File

@@ -0,0 +1,6 @@
import java
import semmle.code.java.dispatch.VirtualDispatch
from Call c, Callable c2
where c2 = viableCallable(c)
select c, c2.getQualifiedName()

View File

@@ -0,0 +1,78 @@
Test.java:
# 0| [CompilationUnit] Test
# 1| 1: [Class] Test
# 3| 2: [Method] test
# 3| 3: [TypeAccess] void
#-----| 4: (Parameters)
# 3| 0: [Parameter] inp
# 3| 0: [TypeAccess] boolean
# 3| 5: [BlockStmt] { ... }
# 5| 0: [LocalVariableDeclStmt] var ...;
# 5| 0: [TypeAccess] String
# 5| 1: [LocalVariableDeclExpr] directTaint
# 5| 0: [MethodCall] source(...)
# 6| 1: [LocalVariableDeclStmt] var ...;
# 6| 0: [TypeAccess] String
# 6| 1: [LocalVariableDeclExpr] indirectTaint
# 6| 0: [MethodCall] source(...)
# 8| 2: [LocalVariableDeclStmt] var ...;
# 8| 0: [TypeAccess] Object
# 8| 1: [LocalVariableDeclExpr] o
# 8| 0: [ConditionalExpr] ...?...:...
# 8| 0: [VarAccess] inp
# 8| 1: [VarAccess] directTaint
# 8| 2: [ClassInstanceExpr] new Outer(...)
# 8| -3: [TypeAccess] Outer
# 8| 0: [ClassInstanceExpr] new Inner(...)
# 8| -3: [TypeAccess] Inner
# 8| 0: [VarAccess] indirectTaint
# 8| 1: [StringLiteral] "not tainted"
# 8| 1: [StringLiteral] "not tainted 2"
# 10| 3: [IfStmt] if (...)
# 10| 0: [InstanceOfExpr] ...instanceof...
# 10| 0: [VarAccess] o
#-----| 2: (Single Local Variable Declaration)
# 10| 0: [TypeAccess] String
# 10| 1: [LocalVariableDeclExpr] s
# 10| 1: [BlockStmt] { ... }
# 11| 0: [ExprStmt] <Expr>;
# 11| 0: [MethodCall] sink(...)
# 11| 0: [VarAccess] s
# 14| 4: [IfStmt] if (...)
# 14| 0: [InstanceOfExpr] ...instanceof...
# 14| 0: [VarAccess] o
# 14| 2: [RecordPatternExpr] Outer(...)
# 14| -2: [TypeAccess] String
# 14| 0: [RecordPatternExpr] Inner(...)
# 14| -2: [TypeAccess] String
# 14| -1: [TypeAccess] String
# 14| 0: [LocalVariableDeclExpr] tainted
# 14| 1: [LocalVariableDeclExpr] notTainted
# 14| 1: [LocalVariableDeclExpr] alsoNotTainted
# 14| 1: [BlockStmt] { ... }
# 15| 0: [ExprStmt] <Expr>;
# 15| 0: [MethodCall] sink(...)
# 15| 0: [VarAccess] tainted
# 16| 1: [ExprStmt] <Expr>;
# 16| 0: [MethodCall] sink(...)
# 16| 0: [VarAccess] notTainted
# 17| 2: [ExprStmt] <Expr>;
# 17| 0: [MethodCall] sink(...)
# 17| 0: [VarAccess] alsoNotTainted
# 22| 3: [Method] source
# 22| 3: [TypeAccess] String
# 22| 5: [BlockStmt] { ... }
# 22| 0: [ReturnStmt] return ...
# 22| 0: [StringLiteral] "tainted"
# 23| 4: [Method] sink
# 23| 3: [TypeAccess] void
#-----| 4: (Parameters)
# 23| 0: [Parameter] sunk
# 23| 0: [TypeAccess] String
# 23| 5: [BlockStmt] { ... }
# 27| 2: [Class] Outer
# 27| 7: [FieldDeclaration] Inner i;
# 27| 8: [FieldDeclaration] String otherField;
# 28| 3: [Class] Inner
# 28| 7: [FieldDeclaration] String taintedField;
# 28| 8: [FieldDeclaration] String nonTaintedField;

View File

@@ -0,0 +1 @@
semmle/code/java/PrintAst.ql

View File

@@ -0,0 +1,28 @@
public class Test {
public static void test(boolean inp) {
String directTaint = source();
String indirectTaint = source();
Object o = inp ? directTaint : new Outer(new Inner(indirectTaint, "not tainted"), "not tainted 2");
if (o instanceof String s) {
sink(s);
}
if (o instanceof Outer(Inner(String tainted, String notTainted), String alsoNotTainted)) {
sink(tainted);
sink(notTainted);
sink(alsoNotTainted);
}
}
public static String source() { return "tainted"; }
public static void sink(String sunk) { }
}
record Outer(Inner i, String otherField) { }
record Inner(String taintedField, String nonTaintedField) { }

View File

@@ -0,0 +1,72 @@
| Test.java:1:14:1:17 | super(...) | Test.java:1:14:1:17 | Test |
| Test.java:1:14:1:17 | { ... } | Test.java:1:14:1:17 | super(...) |
| Test.java:3:40:20:3 | { ... } | Test.java:5:5:5:34 | var ...; |
| Test.java:5:5:5:34 | var ...; | Test.java:5:26:5:33 | source(...) |
| Test.java:5:12:5:33 | directTaint | Test.java:6:5:6:36 | var ...; |
| Test.java:5:26:5:33 | source(...) | Test.java:5:12:5:33 | directTaint |
| Test.java:6:5:6:36 | var ...; | Test.java:6:28:6:35 | source(...) |
| Test.java:6:12:6:35 | indirectTaint | Test.java:8:5:8:103 | var ...; |
| Test.java:6:28:6:35 | source(...) | Test.java:6:12:6:35 | indirectTaint |
| Test.java:8:5:8:103 | var ...; | Test.java:8:16:8:102 | ...?...:... |
| Test.java:8:12:8:102 | o | Test.java:10:5:10:30 | if (...) |
| Test.java:8:16:8:18 | inp | Test.java:8:22:8:32 | directTaint |
| Test.java:8:16:8:18 | inp | Test.java:8:56:8:68 | indirectTaint |
| Test.java:8:16:8:102 | ...?...:... | Test.java:8:16:8:18 | inp |
| Test.java:8:22:8:32 | directTaint | Test.java:8:12:8:102 | o |
| Test.java:8:36:8:102 | new Outer(...) | Test.java:8:12:8:102 | o |
| Test.java:8:46:8:84 | new Inner(...) | Test.java:8:87:8:101 | "not tainted 2" |
| Test.java:8:56:8:68 | indirectTaint | Test.java:8:71:8:83 | "not tainted" |
| Test.java:8:71:8:83 | "not tainted" | Test.java:8:46:8:84 | new Inner(...) |
| Test.java:8:87:8:101 | "not tainted 2" | Test.java:8:36:8:102 | new Outer(...) |
| Test.java:10:5:10:30 | if (...) | Test.java:10:9:10:9 | o |
| Test.java:10:9:10:9 | o | Test.java:10:9:10:29 | ...instanceof... |
| Test.java:10:9:10:29 | ...instanceof... | Test.java:10:29:10:29 | s |
| Test.java:10:9:10:29 | ...instanceof... | Test.java:14:5:14:92 | if (...) |
| Test.java:10:29:10:29 | s | Test.java:10:32:12:5 | { ... } |
| Test.java:10:32:12:5 | { ... } | Test.java:11:7:11:14 | <Expr>; |
| Test.java:11:7:11:13 | sink(...) | Test.java:14:5:14:92 | if (...) |
| Test.java:11:7:11:14 | <Expr>; | Test.java:11:12:11:12 | s |
| Test.java:11:12:11:12 | s | Test.java:11:7:11:13 | sink(...) |
| Test.java:14:5:14:92 | if (...) | Test.java:14:9:14:9 | o |
| Test.java:14:9:14:9 | o | Test.java:14:9:14:91 | ...instanceof... |
| Test.java:14:9:14:91 | ...instanceof... | Test.java:3:22:3:25 | test |
| Test.java:14:9:14:91 | ...instanceof... | Test.java:14:41:14:47 | tainted |
| Test.java:14:22:14:91 | Outer(...) | Test.java:14:94:18:5 | { ... } |
| Test.java:14:28:14:67 | Inner(...) | Test.java:14:77:14:90 | alsoNotTainted |
| Test.java:14:41:14:47 | tainted | Test.java:14:57:14:66 | notTainted |
| Test.java:14:57:14:66 | notTainted | Test.java:14:28:14:67 | Inner(...) |
| Test.java:14:77:14:90 | alsoNotTainted | Test.java:14:22:14:91 | Outer(...) |
| Test.java:14:94:18:5 | { ... } | Test.java:15:7:15:20 | <Expr>; |
| Test.java:15:7:15:19 | sink(...) | Test.java:16:7:16:23 | <Expr>; |
| Test.java:15:7:15:20 | <Expr>; | Test.java:15:12:15:18 | tainted |
| Test.java:15:12:15:18 | tainted | Test.java:15:7:15:19 | sink(...) |
| Test.java:16:7:16:22 | sink(...) | Test.java:17:7:17:27 | <Expr>; |
| Test.java:16:7:16:23 | <Expr>; | Test.java:16:12:16:21 | notTainted |
| Test.java:16:12:16:21 | notTainted | Test.java:16:7:16:22 | sink(...) |
| Test.java:17:7:17:26 | sink(...) | Test.java:3:22:3:25 | test |
| Test.java:17:7:17:27 | <Expr>; | Test.java:17:12:17:25 | alsoNotTainted |
| Test.java:17:12:17:25 | alsoNotTainted | Test.java:17:7:17:26 | sink(...) |
| Test.java:22:33:22:53 | { ... } | Test.java:22:42:22:50 | "tainted" |
| Test.java:22:35:22:51 | return ... | Test.java:22:24:22:29 | source |
| Test.java:22:42:22:50 | "tainted" | Test.java:22:35:22:51 | return ... |
| Test.java:23:40:23:42 | { ... } | Test.java:23:22:23:25 | sink |
| Test.java:27:8:27:12 | ...=... | Test.java:27:8:27:12 | <Expr>; |
| Test.java:27:8:27:12 | ...=... | Test.java:27:8:27:12 | Outer |
| Test.java:27:8:27:12 | <Expr>; | Test.java:27:8:27:12 | this |
| Test.java:27:8:27:12 | <Expr>; | Test.java:27:8:27:12 | this |
| Test.java:27:8:27:12 | i | Test.java:27:8:27:12 | ...=... |
| Test.java:27:8:27:12 | otherField | Test.java:27:8:27:12 | ...=... |
| Test.java:27:8:27:12 | super(...) | Test.java:27:8:27:12 | <Expr>; |
| Test.java:27:8:27:12 | this | Test.java:27:8:27:12 | i |
| Test.java:27:8:27:12 | this | Test.java:27:8:27:12 | otherField |
| Test.java:27:8:27:12 | { ... } | Test.java:27:8:27:12 | super(...) |
| Test.java:28:8:28:12 | ...=... | Test.java:28:8:28:12 | <Expr>; |
| Test.java:28:8:28:12 | ...=... | Test.java:28:8:28:12 | Inner |
| Test.java:28:8:28:12 | <Expr>; | Test.java:28:8:28:12 | this |
| Test.java:28:8:28:12 | <Expr>; | Test.java:28:8:28:12 | this |
| Test.java:28:8:28:12 | nonTaintedField | Test.java:28:8:28:12 | ...=... |
| Test.java:28:8:28:12 | super(...) | Test.java:28:8:28:12 | <Expr>; |
| Test.java:28:8:28:12 | taintedField | Test.java:28:8:28:12 | ...=... |
| Test.java:28:8:28:12 | this | Test.java:28:8:28:12 | nonTaintedField |
| Test.java:28:8:28:12 | this | Test.java:28:8:28:12 | taintedField |
| Test.java:28:8:28:12 | { ... } | Test.java:28:8:28:12 | super(...) |

View File

@@ -0,0 +1,5 @@
import java
from ControlFlowNode cn
where cn.getFile().getBaseName() = "Test.java"
select cn, cn.getASuccessor()

View File

@@ -0,0 +1,2 @@
| Test.java:5:26:5:33 | source(...) | Test.java:11:12:11:12 | s |
| Test.java:6:28:6:35 | source(...) | Test.java:15:12:15:18 | tainted |

View File

@@ -0,0 +1,31 @@
import java
import semmle.code.java.controlflow.Guards
import semmle.code.java.dataflow.DataFlow
private predicate isSafe(Guard g, Expr checked, boolean branch) {
exists(MethodCall mc | g = mc |
mc.getMethod().hasName("isSafe") and
checked = mc.getAnArgument() and
branch = true
)
}
module TestConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source.asExpr() = any(MethodCall mc | mc.getMethod().getName() = "source")
}
predicate isSink(DataFlow::Node sink) {
sink.asExpr() = any(MethodCall mc | mc.getMethod().getName() = "sink").getAnArgument()
}
predicate isBarrier(DataFlow::Node node) {
node = DataFlow::BarrierGuard<isSafe/3>::getABarrierNode()
}
}
module Flow = DataFlow::Global<TestConfig>;
from DataFlow::Node source, DataFlow::Node sink
where Flow::flow(source, sink)
select source, sink

View File

@@ -0,0 +1 @@
//semmle-extractor-options: --javac-args --release 21

View File

@@ -0,0 +1,31 @@
public class Exhaustive {
enum E { A, B, C };
sealed interface I permits X, Y { }
final class X implements I { }
final class Y implements I { }
public static void test(E e, I i, Object o) {
// Check the CFGs of three different ways to be exhaustive -- in particular there shouldn't be a fall-through nothing-matched edge.
switch (o) {
case String s -> { }
case Object o2 -> { }
}
// Exhaustiveness not yet detected by CodeQL, because it is legal to omit some enum entries without a `default` case,
// so we'd need to check every enum entry in the type of E occurs in some case.
switch (e) {
case A -> { }
case B -> { }
case C -> { }
}
switch (i) {
case X x -> { }
case Y y -> { }
}
}
}

View File

@@ -0,0 +1,106 @@
public class Test {
public static void test(Object thing) {
switch (thing) {
case String s -> System.out.println(s);
case Integer i -> System.out.println("An integer: " + i);
default -> { }
}
switch (thing) {
case String s:
System.out.println(s);
break;
case Integer i:
System.out.println("An integer:" + i);
break;
default:
break;
}
var thingAsString = switch(thing) {
case String s -> s;
case Integer i -> "An integer: " + i;
default -> "Something else";
};
var thingAsString2 = switch(thing) {
case String s:
yield s;
case Integer i:
yield "An integer: " + i;
default:
yield "Something else";
};
switch(thing) {
case String s when s.length() == 3:
System.out.println("Length 3");
break;
case String s when s.length() == 5:
System.out.println("Length 5");
break;
default:
System.out.println("Anything else");
break;
}
switch(thing) {
case String s when s.length() == 3 -> System.out.println("Length 3");
case String s when s.length() == 5 -> System.out.println("Length 5");
default -> { }
}
switch((String)thing) {
case "Const1":
System.out.println("It's Const1!");
case "Const2":
System.out.println("It's Const1 or Const2!");
break;
case String s when s.length() <= 6:
System.out.println("It's <= 6 chars long, and neither Const1 nor Const2");
case "Const3":
System.out.println("It's (<= 6 chars long, and neither Const1 nor Const2), or Const3");
break;
case "Const30":
System.out.println("It's Const30");
break;
case null, default:
System.out.println("It's null, or something else");
}
switch(thing) {
case String s:
System.out.println(s);
break;
case null:
System.out.println("It's null");
break;
case Integer i:
System.out.println("An integer:" + i);
break;
default:
break;
}
switch(thing) {
case A(B(int x, String y), float z):
break;
default:
break;
}
switch(thing) {
case A(B(var x, var y), var z):
break;
default:
break;
}
}
}
record A(B b, float field3) { }
record B(int field1, String field2) { }

View File

@@ -0,0 +1 @@
//semmle-extractor-options: --javac-args --release 21

View File

@@ -0,0 +1,313 @@
| Exhaustive.java:1:14:1:23 | super(...) | Exhaustive.java:1:14:1:23 | Exhaustive |
| Exhaustive.java:1:14:1:23 | { ... } | Exhaustive.java:1:14:1:23 | super(...) |
| Exhaustive.java:3:8:3:8 | super(...) | Exhaustive.java:3:8:3:8 | E |
| Exhaustive.java:3:8:3:8 | { ... } | Exhaustive.java:3:8:3:8 | super(...) |
| Exhaustive.java:3:8:3:8 | { ... } | Exhaustive.java:3:12:3:12 | <Expr>; |
| Exhaustive.java:3:12:3:12 | ...=... | Exhaustive.java:3:15:3:15 | <Expr>; |
| Exhaustive.java:3:12:3:12 | <Expr>; | Exhaustive.java:3:12:3:12 | new E(...) |
| Exhaustive.java:3:12:3:12 | new E(...) | Exhaustive.java:3:12:3:12 | ...=... |
| Exhaustive.java:3:15:3:15 | ...=... | Exhaustive.java:3:18:3:18 | <Expr>; |
| Exhaustive.java:3:15:3:15 | <Expr>; | Exhaustive.java:3:15:3:15 | new E(...) |
| Exhaustive.java:3:15:3:15 | new E(...) | Exhaustive.java:3:15:3:15 | ...=... |
| Exhaustive.java:3:18:3:18 | ...=... | Exhaustive.java:3:8:3:8 | <clinit> |
| Exhaustive.java:3:18:3:18 | <Expr>; | Exhaustive.java:3:18:3:18 | new E(...) |
| Exhaustive.java:3:18:3:18 | new E(...) | Exhaustive.java:3:18:3:18 | ...=... |
| Exhaustive.java:5:15:5:15 | super(...) | Exhaustive.java:5:15:5:15 | X |
| Exhaustive.java:5:15:5:15 | { ... } | Exhaustive.java:5:15:5:15 | super(...) |
| Exhaustive.java:6:15:6:15 | super(...) | Exhaustive.java:6:15:6:15 | Y |
| Exhaustive.java:6:15:6:15 | { ... } | Exhaustive.java:6:15:6:15 | super(...) |
| Exhaustive.java:8:47:29:3 | { ... } | Exhaustive.java:11:5:11:14 | switch (...) |
| Exhaustive.java:11:5:11:14 | switch (...) | Exhaustive.java:11:13:11:13 | o |
| Exhaustive.java:11:13:11:13 | o | Exhaustive.java:12:7:12:22 | case <Pattern> |
| Exhaustive.java:12:7:12:22 | case <Pattern> | Exhaustive.java:12:19:12:19 | s |
| Exhaustive.java:12:7:12:22 | case <Pattern> | Exhaustive.java:13:7:13:23 | case <Pattern> |
| Exhaustive.java:12:19:12:19 | s | Exhaustive.java:12:24:12:26 | { ... } |
| Exhaustive.java:12:24:12:26 | { ... } | Exhaustive.java:18:5:18:14 | switch (...) |
| Exhaustive.java:13:7:13:23 | case <Pattern> | Exhaustive.java:13:19:13:20 | o2 |
| Exhaustive.java:13:19:13:20 | o2 | Exhaustive.java:13:25:13:27 | { ... } |
| Exhaustive.java:13:25:13:27 | { ... } | Exhaustive.java:18:5:18:14 | switch (...) |
| Exhaustive.java:18:5:18:14 | switch (...) | Exhaustive.java:18:13:18:13 | e |
| Exhaustive.java:18:13:18:13 | e | Exhaustive.java:19:7:19:15 | case ... |
| Exhaustive.java:18:13:18:13 | e | Exhaustive.java:20:7:20:15 | case ... |
| Exhaustive.java:18:13:18:13 | e | Exhaustive.java:21:7:21:15 | case ... |
| Exhaustive.java:18:13:18:13 | e | Exhaustive.java:24:5:24:14 | switch (...) |
| Exhaustive.java:19:7:19:15 | case ... | Exhaustive.java:19:17:19:19 | { ... } |
| Exhaustive.java:19:17:19:19 | { ... } | Exhaustive.java:24:5:24:14 | switch (...) |
| Exhaustive.java:20:7:20:15 | case ... | Exhaustive.java:20:17:20:19 | { ... } |
| Exhaustive.java:20:17:20:19 | { ... } | Exhaustive.java:24:5:24:14 | switch (...) |
| Exhaustive.java:21:7:21:15 | case ... | Exhaustive.java:21:17:21:19 | { ... } |
| Exhaustive.java:21:17:21:19 | { ... } | Exhaustive.java:24:5:24:14 | switch (...) |
| Exhaustive.java:24:5:24:14 | switch (...) | Exhaustive.java:24:13:24:13 | i |
| Exhaustive.java:24:13:24:13 | i | Exhaustive.java:25:7:25:17 | case <Pattern> |
| Exhaustive.java:25:7:25:17 | case <Pattern> | Exhaustive.java:25:14:25:14 | x |
| Exhaustive.java:25:7:25:17 | case <Pattern> | Exhaustive.java:26:7:26:17 | case <Pattern> |
| Exhaustive.java:25:14:25:14 | x | Exhaustive.java:25:19:25:21 | { ... } |
| Exhaustive.java:25:19:25:21 | { ... } | Exhaustive.java:8:22:8:25 | test |
| Exhaustive.java:26:7:26:17 | case <Pattern> | Exhaustive.java:26:14:26:14 | y |
| Exhaustive.java:26:14:26:14 | y | Exhaustive.java:26:19:26:21 | { ... } |
| Exhaustive.java:26:19:26:21 | { ... } | Exhaustive.java:8:22:8:25 | test |
| Test.java:1:14:1:17 | super(...) | Test.java:1:14:1:17 | Test |
| Test.java:1:14:1:17 | { ... } | Test.java:1:14:1:17 | super(...) |
| Test.java:3:41:101:3 | { ... } | Test.java:5:6:5:19 | switch (...) |
| Test.java:5:6:5:19 | switch (...) | Test.java:5:14:5:18 | thing |
| Test.java:5:14:5:18 | thing | Test.java:6:8:6:23 | case <Pattern> |
| Test.java:6:8:6:23 | case <Pattern> | Test.java:6:20:6:20 | s |
| Test.java:6:8:6:23 | case <Pattern> | Test.java:7:8:7:24 | case <Pattern> |
| Test.java:6:20:6:20 | s | Test.java:6:25:6:34 | System.out |
| Test.java:6:25:6:34 | System.out | Test.java:6:44:6:44 | s |
| Test.java:6:25:6:45 | println(...) | Test.java:11:6:11:19 | switch (...) |
| Test.java:6:25:6:46 | <Expr>; | Test.java:6:25:6:34 | System.out |
| Test.java:6:44:6:44 | s | Test.java:6:25:6:45 | println(...) |
| Test.java:7:8:7:24 | case <Pattern> | Test.java:7:21:7:21 | i |
| Test.java:7:8:7:24 | case <Pattern> | Test.java:8:8:8:17 | default |
| Test.java:7:21:7:21 | i | Test.java:7:26:7:35 | System.out |
| Test.java:7:26:7:35 | System.out | Test.java:7:45:7:58 | "An integer: " |
| Test.java:7:26:7:63 | println(...) | Test.java:11:6:11:19 | switch (...) |
| Test.java:7:26:7:64 | <Expr>; | Test.java:7:26:7:35 | System.out |
| Test.java:7:45:7:58 | "An integer: " | Test.java:7:62:7:62 | i |
| Test.java:7:45:7:62 | ... + ... | Test.java:7:26:7:63 | println(...) |
| Test.java:7:62:7:62 | i | Test.java:7:45:7:62 | ... + ... |
| Test.java:8:8:8:17 | default | Test.java:8:19:8:21 | { ... } |
| Test.java:8:19:8:21 | { ... } | Test.java:11:6:11:19 | switch (...) |
| Test.java:11:6:11:19 | switch (...) | Test.java:11:14:11:18 | thing |
| Test.java:11:14:11:18 | thing | Test.java:12:8:12:21 | case <Pattern> |
| Test.java:12:8:12:21 | case <Pattern> | Test.java:12:20:12:20 | s |
| Test.java:12:8:12:21 | case <Pattern> | Test.java:15:8:15:22 | case <Pattern> |
| Test.java:12:20:12:20 | s | Test.java:13:10:13:31 | <Expr>; |
| Test.java:13:10:13:19 | System.out | Test.java:13:29:13:29 | s |
| Test.java:13:10:13:30 | println(...) | Test.java:14:10:14:15 | break |
| Test.java:13:10:13:31 | <Expr>; | Test.java:13:10:13:19 | System.out |
| Test.java:13:29:13:29 | s | Test.java:13:10:13:30 | println(...) |
| Test.java:14:10:14:15 | break | Test.java:22:6:26:7 | var ...; |
| Test.java:15:8:15:22 | case <Pattern> | Test.java:15:21:15:21 | i |
| Test.java:15:8:15:22 | case <Pattern> | Test.java:18:8:18:15 | default |
| Test.java:15:21:15:21 | i | Test.java:16:10:16:47 | <Expr>; |
| Test.java:16:10:16:19 | System.out | Test.java:16:29:16:41 | "An integer:" |
| Test.java:16:10:16:46 | println(...) | Test.java:17:10:17:15 | break |
| Test.java:16:10:16:47 | <Expr>; | Test.java:16:10:16:19 | System.out |
| Test.java:16:29:16:41 | "An integer:" | Test.java:16:45:16:45 | i |
| Test.java:16:29:16:45 | ... + ... | Test.java:16:10:16:46 | println(...) |
| Test.java:16:45:16:45 | i | Test.java:16:29:16:45 | ... + ... |
| Test.java:17:10:17:15 | break | Test.java:22:6:26:7 | var ...; |
| Test.java:18:8:18:15 | default | Test.java:19:10:19:15 | break |
| Test.java:19:10:19:15 | break | Test.java:22:6:26:7 | var ...; |
| Test.java:22:6:26:7 | var ...; | Test.java:22:26:22:38 | switch (...) |
| Test.java:22:10:22:38 | thingAsString | Test.java:28:6:35:7 | var ...; |
| Test.java:22:26:22:38 | switch (...) | Test.java:22:33:22:37 | thing |
| Test.java:22:33:22:37 | thing | Test.java:23:8:23:23 | case <Pattern> |
| Test.java:23:8:23:23 | case <Pattern> | Test.java:23:20:23:20 | s |
| Test.java:23:8:23:23 | case <Pattern> | Test.java:24:8:24:24 | case <Pattern> |
| Test.java:23:20:23:20 | s | Test.java:23:25:23:25 | s |
| Test.java:23:25:23:25 | s | Test.java:22:10:22:38 | thingAsString |
| Test.java:24:8:24:24 | case <Pattern> | Test.java:24:21:24:21 | i |
| Test.java:24:8:24:24 | case <Pattern> | Test.java:25:8:25:17 | default |
| Test.java:24:21:24:21 | i | Test.java:24:26:24:39 | "An integer: " |
| Test.java:24:26:24:39 | "An integer: " | Test.java:24:43:24:43 | i |
| Test.java:24:26:24:43 | ... + ... | Test.java:22:10:22:38 | thingAsString |
| Test.java:24:43:24:43 | i | Test.java:24:26:24:43 | ... + ... |
| Test.java:25:8:25:17 | default | Test.java:25:19:25:34 | "Something else" |
| Test.java:25:19:25:34 | "Something else" | Test.java:22:10:22:38 | thingAsString |
| Test.java:28:6:35:7 | var ...; | Test.java:28:27:28:39 | switch (...) |
| Test.java:28:10:28:39 | thingAsString2 | Test.java:37:6:37:18 | switch (...) |
| Test.java:28:27:28:39 | switch (...) | Test.java:28:34:28:38 | thing |
| Test.java:28:34:28:38 | thing | Test.java:29:8:29:21 | case <Pattern> |
| Test.java:29:8:29:21 | case <Pattern> | Test.java:29:20:29:20 | s |
| Test.java:29:8:29:21 | case <Pattern> | Test.java:31:8:31:22 | case <Pattern> |
| Test.java:29:20:29:20 | s | Test.java:30:10:30:17 | yield ... |
| Test.java:30:10:30:17 | yield ... | Test.java:30:16:30:16 | s |
| Test.java:30:16:30:16 | s | Test.java:28:10:28:39 | thingAsString2 |
| Test.java:31:8:31:22 | case <Pattern> | Test.java:31:21:31:21 | i |
| Test.java:31:8:31:22 | case <Pattern> | Test.java:33:8:33:15 | default |
| Test.java:31:21:31:21 | i | Test.java:32:10:32:34 | yield ... |
| Test.java:32:10:32:34 | yield ... | Test.java:32:16:32:29 | "An integer: " |
| Test.java:32:16:32:29 | "An integer: " | Test.java:32:33:32:33 | i |
| Test.java:32:16:32:33 | ... + ... | Test.java:28:10:28:39 | thingAsString2 |
| Test.java:32:33:32:33 | i | Test.java:32:16:32:33 | ... + ... |
| Test.java:33:8:33:15 | default | Test.java:34:10:34:32 | yield ... |
| Test.java:34:10:34:32 | yield ... | Test.java:34:16:34:31 | "Something else" |
| Test.java:34:16:34:31 | "Something else" | Test.java:28:10:28:39 | thingAsString2 |
| Test.java:37:6:37:18 | switch (...) | Test.java:37:13:37:17 | thing |
| Test.java:37:13:37:17 | thing | Test.java:38:8:38:42 | case <Pattern> |
| Test.java:38:8:38:42 | case <Pattern> | Test.java:38:20:38:20 | s |
| Test.java:38:8:38:42 | case <Pattern> | Test.java:41:8:41:42 | case <Pattern> |
| Test.java:38:20:38:20 | s | Test.java:38:27:38:27 | s |
| Test.java:38:27:38:27 | s | Test.java:38:27:38:36 | length(...) |
| Test.java:38:27:38:36 | length(...) | Test.java:38:41:38:41 | 3 |
| Test.java:38:27:38:41 | ... == ... | Test.java:39:10:39:40 | <Expr>; |
| Test.java:38:27:38:41 | ... == ... | Test.java:41:8:41:42 | case <Pattern> |
| Test.java:38:41:38:41 | 3 | Test.java:38:27:38:41 | ... == ... |
| Test.java:39:10:39:19 | System.out | Test.java:39:29:39:38 | "Length 3" |
| Test.java:39:10:39:39 | println(...) | Test.java:40:10:40:15 | break |
| Test.java:39:10:39:40 | <Expr>; | Test.java:39:10:39:19 | System.out |
| Test.java:39:29:39:38 | "Length 3" | Test.java:39:10:39:39 | println(...) |
| Test.java:40:10:40:15 | break | Test.java:49:6:49:18 | switch (...) |
| Test.java:41:8:41:42 | case <Pattern> | Test.java:41:20:41:20 | s |
| Test.java:41:8:41:42 | case <Pattern> | Test.java:44:8:44:15 | default |
| Test.java:41:20:41:20 | s | Test.java:41:27:41:27 | s |
| Test.java:41:27:41:27 | s | Test.java:41:27:41:36 | length(...) |
| Test.java:41:27:41:36 | length(...) | Test.java:41:41:41:41 | 5 |
| Test.java:41:27:41:41 | ... == ... | Test.java:42:10:42:40 | <Expr>; |
| Test.java:41:27:41:41 | ... == ... | Test.java:44:8:44:15 | default |
| Test.java:41:41:41:41 | 5 | Test.java:41:27:41:41 | ... == ... |
| Test.java:42:10:42:19 | System.out | Test.java:42:29:42:38 | "Length 5" |
| Test.java:42:10:42:39 | println(...) | Test.java:43:10:43:15 | break |
| Test.java:42:10:42:40 | <Expr>; | Test.java:42:10:42:19 | System.out |
| Test.java:42:29:42:38 | "Length 5" | Test.java:42:10:42:39 | println(...) |
| Test.java:43:10:43:15 | break | Test.java:49:6:49:18 | switch (...) |
| Test.java:44:8:44:15 | default | Test.java:45:10:45:45 | <Expr>; |
| Test.java:45:10:45:19 | System.out | Test.java:45:29:45:43 | "Anything else" |
| Test.java:45:10:45:44 | println(...) | Test.java:46:10:46:15 | break |
| Test.java:45:10:45:45 | <Expr>; | Test.java:45:10:45:19 | System.out |
| Test.java:45:29:45:43 | "Anything else" | Test.java:45:10:45:44 | println(...) |
| Test.java:46:10:46:15 | break | Test.java:49:6:49:18 | switch (...) |
| Test.java:49:6:49:18 | switch (...) | Test.java:49:13:49:17 | thing |
| Test.java:49:13:49:17 | thing | Test.java:50:8:50:44 | case <Pattern> |
| Test.java:50:8:50:44 | case <Pattern> | Test.java:50:20:50:20 | s |
| Test.java:50:8:50:44 | case <Pattern> | Test.java:51:8:51:44 | case <Pattern> |
| Test.java:50:20:50:20 | s | Test.java:50:27:50:27 | s |
| Test.java:50:27:50:27 | s | Test.java:50:27:50:36 | length(...) |
| Test.java:50:27:50:36 | length(...) | Test.java:50:41:50:41 | 3 |
| Test.java:50:27:50:41 | ... == ... | Test.java:50:46:50:55 | System.out |
| Test.java:50:27:50:41 | ... == ... | Test.java:51:8:51:44 | case <Pattern> |
| Test.java:50:41:50:41 | 3 | Test.java:50:27:50:41 | ... == ... |
| Test.java:50:46:50:55 | System.out | Test.java:50:65:50:74 | "Length 3" |
| Test.java:50:46:50:75 | println(...) | Test.java:55:6:55:26 | switch (...) |
| Test.java:50:46:50:76 | <Expr>; | Test.java:50:46:50:55 | System.out |
| Test.java:50:65:50:74 | "Length 3" | Test.java:50:46:50:75 | println(...) |
| Test.java:51:8:51:44 | case <Pattern> | Test.java:51:20:51:20 | s |
| Test.java:51:8:51:44 | case <Pattern> | Test.java:52:8:52:17 | default |
| Test.java:51:20:51:20 | s | Test.java:51:27:51:27 | s |
| Test.java:51:27:51:27 | s | Test.java:51:27:51:36 | length(...) |
| Test.java:51:27:51:36 | length(...) | Test.java:51:41:51:41 | 5 |
| Test.java:51:27:51:41 | ... == ... | Test.java:51:46:51:55 | System.out |
| Test.java:51:27:51:41 | ... == ... | Test.java:52:8:52:17 | default |
| Test.java:51:41:51:41 | 5 | Test.java:51:27:51:41 | ... == ... |
| Test.java:51:46:51:55 | System.out | Test.java:51:65:51:74 | "Length 5" |
| Test.java:51:46:51:75 | println(...) | Test.java:55:6:55:26 | switch (...) |
| Test.java:51:46:51:76 | <Expr>; | Test.java:51:46:51:55 | System.out |
| Test.java:51:65:51:74 | "Length 5" | Test.java:51:46:51:75 | println(...) |
| Test.java:52:8:52:17 | default | Test.java:52:19:52:21 | { ... } |
| Test.java:52:19:52:21 | { ... } | Test.java:55:6:55:26 | switch (...) |
| Test.java:55:6:55:26 | switch (...) | Test.java:55:21:55:25 | thing |
| Test.java:55:13:55:25 | (...)... | Test.java:56:8:56:21 | case ... |
| Test.java:55:13:55:25 | (...)... | Test.java:58:8:58:21 | case ... |
| Test.java:55:13:55:25 | (...)... | Test.java:61:8:61:42 | case <Pattern> |
| Test.java:55:13:55:25 | (...)... | Test.java:69:8:69:26 | case null, default |
| Test.java:55:21:55:25 | thing | Test.java:55:13:55:25 | (...)... |
| Test.java:56:8:56:21 | case ... | Test.java:57:10:57:44 | <Expr>; |
| Test.java:57:10:57:19 | System.out | Test.java:57:29:57:42 | "It's Const1!" |
| Test.java:57:10:57:43 | println(...) | Test.java:58:8:58:21 | case ... |
| Test.java:57:10:57:44 | <Expr>; | Test.java:57:10:57:19 | System.out |
| Test.java:57:29:57:42 | "It's Const1!" | Test.java:57:10:57:43 | println(...) |
| Test.java:58:8:58:21 | case ... | Test.java:59:10:59:54 | <Expr>; |
| Test.java:59:10:59:19 | System.out | Test.java:59:29:59:52 | "It's Const1 or Const2!" |
| Test.java:59:10:59:53 | println(...) | Test.java:60:10:60:15 | break |
| Test.java:59:10:59:54 | <Expr>; | Test.java:59:10:59:19 | System.out |
| Test.java:59:29:59:52 | "It's Const1 or Const2!" | Test.java:59:10:59:53 | println(...) |
| Test.java:60:10:60:15 | break | Test.java:73:6:73:18 | switch (...) |
| Test.java:61:8:61:42 | case <Pattern> | Test.java:61:20:61:20 | s |
| Test.java:61:8:61:42 | case <Pattern> | Test.java:63:8:63:21 | case ... |
| Test.java:61:8:61:42 | case <Pattern> | Test.java:66:8:66:22 | case ... |
| Test.java:61:8:61:42 | case <Pattern> | Test.java:69:8:69:26 | case null, default |
| Test.java:61:20:61:20 | s | Test.java:61:27:61:27 | s |
| Test.java:61:27:61:27 | s | Test.java:61:27:61:36 | length(...) |
| Test.java:61:27:61:36 | length(...) | Test.java:61:41:61:41 | 6 |
| Test.java:61:27:61:41 | ... <= ... | Test.java:62:10:62:83 | <Expr>; |
| Test.java:61:27:61:41 | ... <= ... | Test.java:63:8:63:21 | case ... |
| Test.java:61:27:61:41 | ... <= ... | Test.java:66:8:66:22 | case ... |
| Test.java:61:27:61:41 | ... <= ... | Test.java:69:8:69:26 | case null, default |
| Test.java:61:41:61:41 | 6 | Test.java:61:27:61:41 | ... <= ... |
| Test.java:62:10:62:19 | System.out | Test.java:62:29:62:81 | "It's <= 6 chars long, and neither Const1 nor Const2" |
| Test.java:62:10:62:82 | println(...) | Test.java:63:8:63:21 | case ... |
| Test.java:62:10:62:83 | <Expr>; | Test.java:62:10:62:19 | System.out |
| Test.java:62:29:62:81 | "It's <= 6 chars long, and neither Const1 nor Const2" | Test.java:62:10:62:82 | println(...) |
| Test.java:63:8:63:21 | case ... | Test.java:64:10:64:96 | <Expr>; |
| Test.java:64:10:64:19 | System.out | Test.java:64:29:64:94 | "It's (<= 6 chars long, and neither Const1 nor Const2), or Const3" |
| Test.java:64:10:64:95 | println(...) | Test.java:65:10:65:15 | break |
| Test.java:64:10:64:96 | <Expr>; | Test.java:64:10:64:19 | System.out |
| Test.java:64:29:64:94 | "It's (<= 6 chars long, and neither Const1 nor Const2), or Const3" | Test.java:64:10:64:95 | println(...) |
| Test.java:65:10:65:15 | break | Test.java:73:6:73:18 | switch (...) |
| Test.java:66:8:66:22 | case ... | Test.java:67:10:67:44 | <Expr>; |
| Test.java:67:10:67:19 | System.out | Test.java:67:29:67:42 | "It's Const30" |
| Test.java:67:10:67:43 | println(...) | Test.java:68:10:68:15 | break |
| Test.java:67:10:67:44 | <Expr>; | Test.java:67:10:67:19 | System.out |
| Test.java:67:29:67:42 | "It's Const30" | Test.java:67:10:67:43 | println(...) |
| Test.java:68:10:68:15 | break | Test.java:73:6:73:18 | switch (...) |
| Test.java:69:8:69:26 | case null, default | Test.java:70:10:70:60 | <Expr>; |
| Test.java:70:10:70:19 | System.out | Test.java:70:29:70:58 | "It's null, or something else" |
| Test.java:70:10:70:59 | println(...) | Test.java:73:6:73:18 | switch (...) |
| Test.java:70:10:70:60 | <Expr>; | Test.java:70:10:70:19 | System.out |
| Test.java:70:29:70:58 | "It's null, or something else" | Test.java:70:10:70:59 | println(...) |
| Test.java:73:6:73:18 | switch (...) | Test.java:73:13:73:17 | thing |
| Test.java:73:13:73:17 | thing | Test.java:74:8:74:21 | case <Pattern> |
| Test.java:73:13:73:17 | thing | Test.java:77:8:77:17 | case ... |
| Test.java:74:8:74:21 | case <Pattern> | Test.java:74:20:74:20 | s |
| Test.java:74:8:74:21 | case <Pattern> | Test.java:80:8:80:22 | case <Pattern> |
| Test.java:74:20:74:20 | s | Test.java:75:10:75:31 | <Expr>; |
| Test.java:75:10:75:19 | System.out | Test.java:75:29:75:29 | s |
| Test.java:75:10:75:30 | println(...) | Test.java:76:10:76:15 | break |
| Test.java:75:10:75:31 | <Expr>; | Test.java:75:10:75:19 | System.out |
| Test.java:75:29:75:29 | s | Test.java:75:10:75:30 | println(...) |
| Test.java:76:10:76:15 | break | Test.java:87:6:87:18 | switch (...) |
| Test.java:77:8:77:17 | case ... | Test.java:78:10:78:41 | <Expr>; |
| Test.java:78:10:78:19 | System.out | Test.java:78:29:78:39 | "It's null" |
| Test.java:78:10:78:40 | println(...) | Test.java:79:10:79:15 | break |
| Test.java:78:10:78:41 | <Expr>; | Test.java:78:10:78:19 | System.out |
| Test.java:78:29:78:39 | "It's null" | Test.java:78:10:78:40 | println(...) |
| Test.java:79:10:79:15 | break | Test.java:87:6:87:18 | switch (...) |
| Test.java:80:8:80:22 | case <Pattern> | Test.java:80:21:80:21 | i |
| Test.java:80:8:80:22 | case <Pattern> | Test.java:83:8:83:15 | default |
| Test.java:80:21:80:21 | i | Test.java:81:10:81:47 | <Expr>; |
| Test.java:81:10:81:19 | System.out | Test.java:81:29:81:41 | "An integer:" |
| Test.java:81:10:81:46 | println(...) | Test.java:82:10:82:15 | break |
| Test.java:81:10:81:47 | <Expr>; | Test.java:81:10:81:19 | System.out |
| Test.java:81:29:81:41 | "An integer:" | Test.java:81:45:81:45 | i |
| Test.java:81:29:81:45 | ... + ... | Test.java:81:10:81:46 | println(...) |
| Test.java:81:45:81:45 | i | Test.java:81:29:81:45 | ... + ... |
| Test.java:82:10:82:15 | break | Test.java:87:6:87:18 | switch (...) |
| Test.java:83:8:83:15 | default | Test.java:84:10:84:15 | break |
| Test.java:84:10:84:15 | break | Test.java:87:6:87:18 | switch (...) |
| Test.java:87:6:87:18 | switch (...) | Test.java:87:13:87:17 | thing |
| Test.java:87:13:87:17 | thing | Test.java:88:8:88:43 | case <Pattern> |
| Test.java:88:8:88:43 | case <Pattern> | Test.java:88:21:88:21 | x |
| Test.java:88:8:88:43 | case <Pattern> | Test.java:90:8:90:15 | default |
| Test.java:88:13:88:42 | A(...) | Test.java:89:10:89:15 | break |
| Test.java:88:15:88:32 | B(...) | Test.java:88:41:88:41 | z |
| Test.java:88:21:88:21 | x | Test.java:88:31:88:31 | y |
| Test.java:88:31:88:31 | y | Test.java:88:15:88:32 | B(...) |
| Test.java:88:41:88:41 | z | Test.java:88:13:88:42 | A(...) |
| Test.java:89:10:89:15 | break | Test.java:94:6:94:18 | switch (...) |
| Test.java:90:8:90:15 | default | Test.java:91:10:91:15 | break |
| Test.java:91:10:91:15 | break | Test.java:94:6:94:18 | switch (...) |
| Test.java:94:6:94:18 | switch (...) | Test.java:94:13:94:17 | thing |
| Test.java:94:13:94:17 | thing | Test.java:95:8:95:38 | case <Pattern> |
| Test.java:95:8:95:38 | case <Pattern> | Test.java:95:21:95:21 | x |
| Test.java:95:8:95:38 | case <Pattern> | Test.java:97:8:97:15 | default |
| Test.java:95:13:95:37 | A(...) | Test.java:96:10:96:15 | break |
| Test.java:95:15:95:29 | B(...) | Test.java:95:36:95:36 | z |
| Test.java:95:21:95:21 | x | Test.java:95:28:95:28 | y |
| Test.java:95:28:95:28 | y | Test.java:95:15:95:29 | B(...) |
| Test.java:95:36:95:36 | z | Test.java:95:13:95:37 | A(...) |
| Test.java:96:10:96:15 | break | Test.java:3:22:3:25 | test |
| Test.java:97:8:97:15 | default | Test.java:98:10:98:15 | break |
| Test.java:98:10:98:15 | break | Test.java:3:22:3:25 | test |
| Test.java:105:8:105:8 | ...=... | Test.java:105:8:105:8 | <Expr>; |
| Test.java:105:8:105:8 | ...=... | Test.java:105:8:105:8 | A |
| Test.java:105:8:105:8 | <Expr>; | Test.java:105:8:105:8 | this |
| Test.java:105:8:105:8 | <Expr>; | Test.java:105:8:105:8 | this |
| Test.java:105:8:105:8 | b | Test.java:105:8:105:8 | ...=... |
| Test.java:105:8:105:8 | field3 | Test.java:105:8:105:8 | ...=... |
| Test.java:105:8:105:8 | super(...) | Test.java:105:8:105:8 | <Expr>; |
| Test.java:105:8:105:8 | this | Test.java:105:8:105:8 | b |
| Test.java:105:8:105:8 | this | Test.java:105:8:105:8 | field3 |
| Test.java:105:8:105:8 | { ... } | Test.java:105:8:105:8 | super(...) |
| Test.java:106:8:106:8 | ...=... | Test.java:106:8:106:8 | <Expr>; |
| Test.java:106:8:106:8 | ...=... | Test.java:106:8:106:8 | B |
| Test.java:106:8:106:8 | <Expr>; | Test.java:106:8:106:8 | this |
| Test.java:106:8:106:8 | <Expr>; | Test.java:106:8:106:8 | this |
| Test.java:106:8:106:8 | field1 | Test.java:106:8:106:8 | ...=... |
| Test.java:106:8:106:8 | field2 | Test.java:106:8:106:8 | ...=... |
| Test.java:106:8:106:8 | super(...) | Test.java:106:8:106:8 | <Expr>; |
| Test.java:106:8:106:8 | this | Test.java:106:8:106:8 | field1 |
| Test.java:106:8:106:8 | this | Test.java:106:8:106:8 | field2 |
| Test.java:106:8:106:8 | { ... } | Test.java:106:8:106:8 | super(...) |

View File

@@ -0,0 +1,5 @@
import java
from ControlFlowNode cn
where cn.getFile().getBaseName() = ["Test.java", "Exhaustive.java"]
select cn, cn.getASuccessor()

View File

@@ -0,0 +1,30 @@
public class GuardTest {
public static void sink(String s) { }
public static boolean isSafe(String s) { return s.length() < 10; }
public static void test(Object o) {
switch (o) {
case String s:
sink(s);
break;
default:
break;
}
switch (o) {
case String s when isSafe(s):
sink(s);
break;
default:
break;
}
}
}

View File

@@ -0,0 +1,48 @@
public class RecordTest {
interface I { }
record Middle(String field) { }
record A(Middle afield) implements I { }
record B(Middle bfield) implements I { }
public static String sink(String s) { return s; }
public static void test(boolean inp) {
I i = inp ? new A(new Middle("A")) : new B(new Middle("B"));
switch(i) {
case A(Middle(String field)):
sink(field);
break;
case B(Middle(String field)):
sink(field);
break;
default:
break;
}
switch(i) {
case A(Middle(String field)) -> sink(field);
case B(Middle(String field)) -> sink(field);
default -> { }
}
var x = switch(i) {
case A(Middle(String field)):
yield sink(field);
case B(Middle(String field)):
yield sink(field);
default:
yield "Default case";
};
var y = switch(i) {
case A(Middle(String field)) -> sink(field);
case B(Middle(String field)) -> sink(field);
default -> "Default case";
};
}
}

View File

@@ -0,0 +1,47 @@
public class Test {
interface I { String get(); }
static class A implements I { public String afield; public A(String a) { this.afield = a; } public String get() { return afield; } }
static class B implements I { public String bfield; public B(String b) { this.bfield = b; } public String get() { return bfield; } }
public static String sink(String s) { return s; }
public static void test(boolean inp) {
I i = inp ? new A("A") : new B("B");
switch(i) {
case A a:
sink(a.get());
break;
case B b:
sink(b.get());
break;
default:
break;
}
switch(i) {
case A a -> sink(a.get());
case B b -> sink(b.get());
default -> { }
}
var x = switch(i) {
case A a:
yield sink(a.get());
case B b:
yield sink(b.get());
default:
yield "Default case";
};
var y = switch(i) {
case A a -> sink(a.get());
case B b -> sink(b.get());
default -> "Default case";
};
}
}

View File

@@ -0,0 +1 @@
//semmle-extractor-options: --javac-args --release 21

View File

@@ -0,0 +1,17 @@
| GuardTest.java:6:27:6:34 | o | GuardTest.java:11:14:11:14 | s |
| RecordTest.java:12:34:12:36 | "A" | RecordTest.java:16:14:16:18 | field |
| RecordTest.java:12:34:12:36 | "A" | RecordTest.java:26:44:26:48 | field |
| RecordTest.java:12:34:12:36 | "A" | RecordTest.java:33:20:33:24 | field |
| RecordTest.java:12:34:12:36 | "A" | RecordTest.java:41:44:41:48 | field |
| RecordTest.java:12:59:12:61 | "B" | RecordTest.java:19:14:19:18 | field |
| RecordTest.java:12:59:12:61 | "B" | RecordTest.java:27:44:27:48 | field |
| RecordTest.java:12:59:12:61 | "B" | RecordTest.java:35:20:35:24 | field |
| RecordTest.java:12:59:12:61 | "B" | RecordTest.java:42:44:42:48 | field |
| Test.java:11:23:11:25 | "A" | Test.java:15:14:15:20 | get(...) |
| Test.java:11:23:11:25 | "A" | Test.java:25:24:25:30 | get(...) |
| Test.java:11:23:11:25 | "A" | Test.java:32:20:32:26 | get(...) |
| Test.java:11:23:11:25 | "A" | Test.java:40:24:40:30 | get(...) |
| Test.java:11:36:11:38 | "B" | Test.java:18:14:18:20 | get(...) |
| Test.java:11:36:11:38 | "B" | Test.java:26:24:26:30 | get(...) |
| Test.java:11:36:11:38 | "B" | Test.java:34:20:34:26 | get(...) |
| Test.java:11:36:11:38 | "B" | Test.java:41:24:41:30 | get(...) |

View File

@@ -0,0 +1,31 @@
import java
import semmle.code.java.controlflow.Guards
import semmle.code.java.dataflow.DataFlow
private predicate isSafe(Guard g, Expr checked, boolean branch) {
exists(MethodCall mc | g = mc |
mc.getMethod().hasName("isSafe") and
checked = mc.getAnArgument() and
branch = true
)
}
module TestConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source.asExpr() instanceof StringLiteral or source.asParameter().getCallable().hasName("test")
}
predicate isSink(DataFlow::Node sink) {
sink.asExpr() = any(MethodCall mc | mc.getMethod().getName() = "sink").getAnArgument()
}
predicate isBarrier(DataFlow::Node node) {
node = DataFlow::BarrierGuard<isSafe/3>::getABarrierNode()
}
}
module Flow = DataFlow::Global<TestConfig>;
from DataFlow::Node source, DataFlow::Node sink
where Flow::flow(source, sink)
select source, sink

View File

@@ -0,0 +1,50 @@
public class Test {
record S(int x) { }
record R(S s, String y) { }
public static void test(Object o) {
switch(o) {
case String s:
break;
case R(S(int x), String y):
break;
default:
break;
}
switch(o) {
case String s -> { }
case R(S(int x), String y) -> { }
case null, default -> { }
}
var a = switch(o) {
case String s:
yield 1;
case R(S(int x), String y):
yield x;
case null, default:
yield 2;
};
var b = switch(o) {
case String s -> 1;
case R(S(int x), String y) -> x;
default -> 2;
};
if (o instanceof String s) { }
if (o instanceof R(S(int x), String y)) { }
switch(o) {
case R(S(var x), var y) -> { }
case null, default -> { }
}
if (o instanceof R(S(var x), var y)) { }
}
}

View File

@@ -0,0 +1 @@
//semmle-extractor-options: --javac-args --release 21

View File

@@ -0,0 +1,88 @@
| Test.java:1:14:1:17 | Test | 0 | public class Test { |
| Test.java:1:14:1:17 | Test | 1 | public Test() { |
| Test.java:1:14:1:17 | Test | 2 | super(); |
| Test.java:1:14:1:17 | Test | 3 | } |
| Test.java:1:14:1:17 | Test | 4 | |
| Test.java:1:14:1:17 | Test | 5 | static final class S { |
| Test.java:1:14:1:17 | Test | 6 | public final boolean equals(Object p0) { <missing body> } |
| Test.java:1:14:1:17 | Test | 7 | |
| Test.java:1:14:1:17 | Test | 8 | public final int hashCode() { <missing body> } |
| Test.java:1:14:1:17 | Test | 9 | |
| Test.java:1:14:1:17 | Test | 10 | public final String toString() { <missing body> } |
| Test.java:1:14:1:17 | Test | 11 | |
| Test.java:1:14:1:17 | Test | 12 | public int x() { <missing body> } |
| Test.java:1:14:1:17 | Test | 13 | |
| Test.java:1:14:1:17 | Test | 14 | S(int x) { |
| Test.java:1:14:1:17 | Test | 15 | super(); |
| Test.java:1:14:1:17 | Test | 16 | this.x = x; |
| Test.java:1:14:1:17 | Test | 17 | } |
| Test.java:1:14:1:17 | Test | 18 | |
| Test.java:1:14:1:17 | Test | 19 | private final int x; |
| Test.java:1:14:1:17 | Test | 20 | } |
| Test.java:1:14:1:17 | Test | 21 | |
| Test.java:1:14:1:17 | Test | 22 | static final class R { |
| Test.java:1:14:1:17 | Test | 23 | public final boolean equals(Object p0) { <missing body> } |
| Test.java:1:14:1:17 | Test | 24 | |
| Test.java:1:14:1:17 | Test | 25 | public final int hashCode() { <missing body> } |
| Test.java:1:14:1:17 | Test | 26 | |
| Test.java:1:14:1:17 | Test | 27 | public S s() { <missing body> } |
| Test.java:1:14:1:17 | Test | 28 | |
| Test.java:1:14:1:17 | Test | 29 | public final String toString() { <missing body> } |
| Test.java:1:14:1:17 | Test | 30 | |
| Test.java:1:14:1:17 | Test | 31 | public String y() { <missing body> } |
| Test.java:1:14:1:17 | Test | 32 | |
| Test.java:1:14:1:17 | Test | 33 | R(S s, String y) { |
| Test.java:1:14:1:17 | Test | 34 | super(); |
| Test.java:1:14:1:17 | Test | 35 | this.s = s; |
| Test.java:1:14:1:17 | Test | 36 | this.y = y; |
| Test.java:1:14:1:17 | Test | 37 | } |
| Test.java:1:14:1:17 | Test | 38 | |
| Test.java:1:14:1:17 | Test | 39 | private final S s; |
| Test.java:1:14:1:17 | Test | 40 | |
| Test.java:1:14:1:17 | Test | 41 | private final String y; |
| Test.java:1:14:1:17 | Test | 42 | } |
| Test.java:1:14:1:17 | Test | 43 | |
| Test.java:1:14:1:17 | Test | 44 | public static void test(Object o) { |
| Test.java:1:14:1:17 | Test | 45 | switch (o) { |
| Test.java:1:14:1:17 | Test | 46 | case String s: |
| Test.java:1:14:1:17 | Test | 47 | break; |
| Test.java:1:14:1:17 | Test | 48 | case R(S(int x), String y): |
| Test.java:1:14:1:17 | Test | 49 | break; |
| Test.java:1:14:1:17 | Test | 50 | default: |
| Test.java:1:14:1:17 | Test | 51 | break; |
| Test.java:1:14:1:17 | Test | 52 | } |
| Test.java:1:14:1:17 | Test | 53 | switch (o) { |
| Test.java:1:14:1:17 | Test | 54 | case String s -> { |
| Test.java:1:14:1:17 | Test | 55 | } |
| Test.java:1:14:1:17 | Test | 56 | case R(S(int x), String y) -> { |
| Test.java:1:14:1:17 | Test | 57 | } |
| Test.java:1:14:1:17 | Test | 58 | case null, default -> { |
| Test.java:1:14:1:17 | Test | 59 | } |
| Test.java:1:14:1:17 | Test | 60 | } |
| Test.java:1:14:1:17 | Test | 61 | var a = switch (o) { |
| Test.java:1:14:1:17 | Test | 62 | case String s: |
| Test.java:1:14:1:17 | Test | 63 | yield 1; |
| Test.java:1:14:1:17 | Test | 64 | case R(S(int x), String y): |
| Test.java:1:14:1:17 | Test | 65 | yield x; |
| Test.java:1:14:1:17 | Test | 66 | case null, default: |
| Test.java:1:14:1:17 | Test | 67 | yield 2; |
| Test.java:1:14:1:17 | Test | 68 | }; |
| Test.java:1:14:1:17 | Test | 69 | var b = switch (o) { |
| Test.java:1:14:1:17 | Test | 70 | case String s -> 1; |
| Test.java:1:14:1:17 | Test | 71 | case R(S(int x), String y) -> x; |
| Test.java:1:14:1:17 | Test | 72 | default -> 2; |
| Test.java:1:14:1:17 | Test | 73 | }; |
| Test.java:1:14:1:17 | Test | 74 | if (o instanceof String s) { |
| Test.java:1:14:1:17 | Test | 75 | } |
| Test.java:1:14:1:17 | Test | 76 | if (o instanceof R(S(int x), String y)) { |
| Test.java:1:14:1:17 | Test | 77 | } |
| Test.java:1:14:1:17 | Test | 78 | switch (o) { |
| Test.java:1:14:1:17 | Test | 79 | case R(S(var x), var y) -> { |
| Test.java:1:14:1:17 | Test | 80 | } |
| Test.java:1:14:1:17 | Test | 81 | case null, default -> { |
| Test.java:1:14:1:17 | Test | 82 | } |
| Test.java:1:14:1:17 | Test | 83 | } |
| Test.java:1:14:1:17 | Test | 84 | if (o instanceof R(S(var x), var y)) { |
| Test.java:1:14:1:17 | Test | 85 | } |
| Test.java:1:14:1:17 | Test | 86 | } |
| Test.java:1:14:1:17 | Test | 87 | } |

View File

@@ -0,0 +1,5 @@
import semmle.code.java.PrettyPrintAst
from ClassOrInterface cori, string s, int line
where pp(cori, s, line) and cori.fromSource()
select cori, line, s order by line

View File

@@ -50,6 +50,61 @@ class A {
if (thing instanceof String s) {
throw new RuntimeException(s);
}
switch (thing) {
case String s -> System.out.println(s);
case Integer i -> System.out.println("An integer: " + i);
default -> { }
}
switch (thing) {
case String s:
System.out.println(s);
break;
case Integer i:
System.out.println("An integer:" + i);
break;
default:
break;
}
var thingAsString = switch(thing) {
case String s -> s;
case Integer i -> "An integer: " + i;
default -> "Something else";
};
var thingAsString2 = switch(thing) {
case String s:
yield s;
case Integer i:
yield "An integer: " + i;
default:
yield "Something else";
};
var nullTest = switch(thing) {
case null -> "Null";
default -> "Not null";
};
var whenTest = switch((String)thing) {
case "constant" -> "It's constant";
case String s when s.length() == 3 -> "It's 3 letters long";
case String s when s.length() == 5 -> "it's 5 letters long";
default -> "It's something else";
};
var nullDefaultTest = switch(thing) {
case String s -> "It's a string";
case null, default -> "It's something else";
};
var qualifiedEnumTest = switch(thing) {
case E.A -> "It's E.A";
default -> "It's something else";
};
var unnecessaryQualifiedEnumTest = switch((E)thing) {
case A -> "It's E.A";
case E.B -> "It's E.B";
default -> "It's something else";
};
var recordPatterntest = switch(thing) {
case Middle(Inner(String field)) -> field;
default -> "Doesn't match pattern Middle(Inner(...))";
};
}
}
catch (RuntimeException rte) {
@@ -70,4 +125,7 @@ class A {
* Javadoc for fields
*/
int i, j, k;
}
}
record Inner(String field) { }
record Middle(Inner inner) { }

View File

@@ -110,45 +110,225 @@ A.java:
# 48| 0: [ReturnStmt] return ...
# 50| 1: [IfStmt] if (...)
# 50| 0: [InstanceOfExpr] ...instanceof...
#-----| 0: (Single Local Variable Declaration)
# 50| 0: [VarAccess] thing
#-----| 2: (Single Local Variable Declaration)
# 50| 0: [TypeAccess] String
# 50| 1: [LocalVariableDeclExpr] s
# 50| 0: [VarAccess] thing
# 50| 1: [BlockStmt] { ... }
# 51| 0: [ThrowStmt] throw ...
# 51| 0: [ClassInstanceExpr] new RuntimeException(...)
# 51| -3: [TypeAccess] RuntimeException
# 51| 0: [VarAccess] s
# 55| 0: [CatchClause] catch (...)
# 53| 2: [SwitchStmt] switch (...)
# 53| -1: [VarAccess] thing
# 54| 0: [PatternCase] case <Pattern>
# 54| -1: [ExprStmt] <Expr>;
# 54| 0: [MethodCall] println(...)
# 54| -1: [VarAccess] System.out
# 54| -1: [TypeAccess] System
# 54| 0: [VarAccess] s
#-----| 0: (Single Local Variable Declaration)
# 54| 0: [TypeAccess] String
# 54| 1: [LocalVariableDeclExpr] s
# 55| 1: [PatternCase] case <Pattern>
# 55| -1: [ExprStmt] <Expr>;
# 55| 0: [MethodCall] println(...)
# 55| -1: [VarAccess] System.out
# 55| -1: [TypeAccess] System
# 55| 0: [AddExpr] ... + ...
# 55| 0: [StringLiteral] "An integer: "
# 55| 1: [VarAccess] i
#-----| 0: (Single Local Variable Declaration)
# 55| 0: [TypeAccess] Integer
# 55| 1: [LocalVariableDeclExpr] i
# 56| 2: [DefaultCase] default
# 56| -1: [BlockStmt] { ... }
# 58| 3: [SwitchStmt] switch (...)
# 58| -1: [VarAccess] thing
# 59| 0: [PatternCase] case <Pattern>
#-----| 0: (Single Local Variable Declaration)
# 59| 0: [TypeAccess] String
# 59| 1: [LocalVariableDeclExpr] s
# 60| 1: [ExprStmt] <Expr>;
# 60| 0: [MethodCall] println(...)
# 60| -1: [VarAccess] System.out
# 60| -1: [TypeAccess] System
# 60| 0: [VarAccess] s
# 61| 2: [BreakStmt] break
# 62| 3: [PatternCase] case <Pattern>
#-----| 0: (Single Local Variable Declaration)
# 62| 0: [TypeAccess] Integer
# 62| 1: [LocalVariableDeclExpr] i
# 63| 4: [ExprStmt] <Expr>;
# 63| 0: [MethodCall] println(...)
# 63| -1: [VarAccess] System.out
# 63| -1: [TypeAccess] System
# 63| 0: [AddExpr] ... + ...
# 63| 0: [StringLiteral] "An integer:"
# 63| 1: [VarAccess] i
# 64| 5: [BreakStmt] break
# 65| 6: [DefaultCase] default
# 66| 7: [BreakStmt] break
# 68| 4: [LocalVariableDeclStmt] var ...;
# 68| 1: [LocalVariableDeclExpr] thingAsString
# 68| 0: [SwitchExpr] switch (...)
# 68| -1: [VarAccess] thing
# 69| 0: [PatternCase] case <Pattern>
# 69| -1: [VarAccess] s
#-----| 0: (Single Local Variable Declaration)
# 69| 0: [TypeAccess] String
# 69| 1: [LocalVariableDeclExpr] s
# 70| 1: [PatternCase] case <Pattern>
# 70| -1: [AddExpr] ... + ...
# 70| 0: [StringLiteral] "An integer: "
# 70| 1: [VarAccess] i
#-----| 0: (Single Local Variable Declaration)
# 70| 0: [TypeAccess] Integer
# 70| 1: [LocalVariableDeclExpr] i
# 71| 2: [DefaultCase] default
# 71| -1: [StringLiteral] "Something else"
# 73| 5: [LocalVariableDeclStmt] var ...;
# 73| 1: [LocalVariableDeclExpr] thingAsString2
# 73| 0: [SwitchExpr] switch (...)
# 73| -1: [VarAccess] thing
# 74| 0: [PatternCase] case <Pattern>
#-----| 0: (Single Local Variable Declaration)
# 74| 0: [TypeAccess] String
# 74| 1: [LocalVariableDeclExpr] s
# 75| 1: [YieldStmt] yield ...
# 75| 0: [VarAccess] s
# 76| 2: [PatternCase] case <Pattern>
#-----| 0: (Single Local Variable Declaration)
# 76| 0: [TypeAccess] Integer
# 76| 1: [LocalVariableDeclExpr] i
# 77| 3: [YieldStmt] yield ...
# 77| 0: [AddExpr] ... + ...
# 77| 0: [StringLiteral] "An integer: "
# 77| 1: [VarAccess] i
# 78| 4: [DefaultCase] default
# 79| 5: [YieldStmt] yield ...
# 79| 0: [StringLiteral] "Something else"
# 81| 6: [LocalVariableDeclStmt] var ...;
# 81| 1: [LocalVariableDeclExpr] nullTest
# 81| 0: [SwitchExpr] switch (...)
# 81| -1: [VarAccess] thing
# 82| 0: [ConstCase] case ...
# 82| -1: [StringLiteral] "Null"
# 82| 0: [NullLiteral] null
# 83| 1: [DefaultCase] default
# 83| -1: [StringLiteral] "Not null"
# 85| 7: [LocalVariableDeclStmt] var ...;
# 85| 1: [LocalVariableDeclExpr] whenTest
# 85| 0: [SwitchExpr] switch (...)
# 85| -1: [CastExpr] (...)...
# 85| 0: [TypeAccess] String
# 85| 1: [VarAccess] thing
# 86| 0: [ConstCase] case ...
# 86| -1: [StringLiteral] "It's constant"
# 86| 0: [StringLiteral] "constant"
# 87| 1: [PatternCase] case <Pattern>
# 87| -3: [EQExpr] ... == ...
# 87| 0: [MethodCall] length(...)
# 87| -1: [VarAccess] s
# 87| 1: [IntegerLiteral] 3
# 87| -1: [StringLiteral] "It's 3 letters long"
#-----| 0: (Single Local Variable Declaration)
# 87| 0: [TypeAccess] String
# 87| 1: [LocalVariableDeclExpr] s
# 88| 2: [PatternCase] case <Pattern>
# 88| -3: [EQExpr] ... == ...
# 88| 0: [MethodCall] length(...)
# 88| -1: [VarAccess] s
# 88| 1: [IntegerLiteral] 5
# 88| -1: [StringLiteral] "it's 5 letters long"
#-----| 0: (Single Local Variable Declaration)
# 88| 0: [TypeAccess] String
# 88| 1: [LocalVariableDeclExpr] s
# 89| 3: [DefaultCase] default
# 89| -1: [StringLiteral] "It's something else"
# 91| 8: [LocalVariableDeclStmt] var ...;
# 91| 1: [LocalVariableDeclExpr] nullDefaultTest
# 91| 0: [SwitchExpr] switch (...)
# 91| -1: [VarAccess] thing
# 92| 0: [PatternCase] case <Pattern>
# 92| -1: [StringLiteral] "It's a string"
#-----| 0: (Single Local Variable Declaration)
# 92| 0: [TypeAccess] String
# 92| 1: [LocalVariableDeclExpr] s
# 93| 1: [NullDefaultCase] case null, default
# 93| -1: [StringLiteral] "It's something else"
# 93| 0: [NullLiteral] null
# 95| 9: [LocalVariableDeclStmt] var ...;
# 95| 1: [LocalVariableDeclExpr] qualifiedEnumTest
# 95| 0: [SwitchExpr] switch (...)
# 95| -1: [VarAccess] thing
# 96| 0: [ConstCase] case ...
# 96| -1: [StringLiteral] "It's E.A"
# 96| 0: [VarAccess] E.A
# 96| -1: [TypeAccess] E
# 97| 1: [DefaultCase] default
# 97| -1: [StringLiteral] "It's something else"
# 99| 10: [LocalVariableDeclStmt] var ...;
# 99| 1: [LocalVariableDeclExpr] unnecessaryQualifiedEnumTest
# 99| 0: [SwitchExpr] switch (...)
# 99| -1: [CastExpr] (...)...
# 99| 0: [TypeAccess] E
# 99| 1: [VarAccess] thing
# 100| 0: [ConstCase] case ...
# 100| -1: [StringLiteral] "It's E.A"
# 100| 0: [VarAccess] A
# 101| 1: [ConstCase] case ...
# 101| -1: [StringLiteral] "It's E.B"
# 101| 0: [VarAccess] E.B
# 101| -1: [TypeAccess] E
# 102| 2: [DefaultCase] default
# 102| -1: [StringLiteral] "It's something else"
# 104| 11: [LocalVariableDeclStmt] var ...;
# 104| 1: [LocalVariableDeclExpr] recordPatterntest
# 104| 0: [SwitchExpr] switch (...)
# 104| -1: [VarAccess] thing
# 105| 0: [PatternCase] case <Pattern>
# 105| -1: [VarAccess] field
# 105| 0: [RecordPatternExpr] Middle(...)
# 105| 0: [RecordPatternExpr] Inner(...)
# 105| -1: [TypeAccess] String
# 105| 0: [LocalVariableDeclExpr] field
# 106| 1: [DefaultCase] default
# 106| -1: [StringLiteral] "Doesn't match pattern Middle(Inner(...))"
# 110| 0: [CatchClause] catch (...)
#-----| 0: (Single Local Variable Declaration)
# 55| 0: [TypeAccess] RuntimeException
# 55| 1: [LocalVariableDeclExpr] rte
# 55| 1: [BlockStmt] { ... }
# 56| 0: [ReturnStmt] return ...
# 60| 10: [Class] E
# 64| 3: [FieldDeclaration] E A;
# 110| 0: [TypeAccess] RuntimeException
# 110| 1: [LocalVariableDeclExpr] rte
# 110| 1: [BlockStmt] { ... }
# 111| 0: [ReturnStmt] return ...
# 115| 10: [Class] E
# 119| 3: [FieldDeclaration] E A;
#-----| -3: (Javadoc)
# 61| 1: [Javadoc] /** Javadoc for enum constant */
# 62| 0: [JavadocText] Javadoc for enum constant
# 64| -1: [TypeAccess] E
# 64| 0: [ClassInstanceExpr] new E(...)
# 64| -3: [TypeAccess] E
# 65| 4: [FieldDeclaration] E B;
# 116| 1: [Javadoc] /** Javadoc for enum constant */
# 117| 0: [JavadocText] Javadoc for enum constant
# 119| -1: [TypeAccess] E
# 119| 0: [ClassInstanceExpr] new E(...)
# 119| -3: [TypeAccess] E
# 120| 4: [FieldDeclaration] E B;
#-----| -3: (Javadoc)
# 61| 1: [Javadoc] /** Javadoc for enum constant */
# 62| 0: [JavadocText] Javadoc for enum constant
# 65| -1: [TypeAccess] E
# 65| 0: [ClassInstanceExpr] new E(...)
# 65| -3: [TypeAccess] E
# 66| 5: [FieldDeclaration] E C;
# 116| 1: [Javadoc] /** Javadoc for enum constant */
# 117| 0: [JavadocText] Javadoc for enum constant
# 120| -1: [TypeAccess] E
# 120| 0: [ClassInstanceExpr] new E(...)
# 120| -3: [TypeAccess] E
# 121| 5: [FieldDeclaration] E C;
#-----| -3: (Javadoc)
# 61| 1: [Javadoc] /** Javadoc for enum constant */
# 62| 0: [JavadocText] Javadoc for enum constant
# 66| -1: [TypeAccess] E
# 66| 0: [ClassInstanceExpr] new E(...)
# 66| -3: [TypeAccess] E
# 72| 11: [FieldDeclaration] int i, ...;
# 116| 1: [Javadoc] /** Javadoc for enum constant */
# 117| 0: [JavadocText] Javadoc for enum constant
# 121| -1: [TypeAccess] E
# 121| 0: [ClassInstanceExpr] new E(...)
# 121| -3: [TypeAccess] E
# 127| 11: [FieldDeclaration] int i, ...;
#-----| -3: (Javadoc)
# 69| 1: [Javadoc] /** Javadoc for fields */
# 70| 0: [JavadocText] Javadoc for fields
# 72| -1: [TypeAccess] int
# 124| 1: [Javadoc] /** Javadoc for fields */
# 125| 0: [JavadocText] Javadoc for fields
# 127| -1: [TypeAccess] int
# 130| 2: [Class] Inner
# 130| 2: [FieldDeclaration] String field;
# 131| 3: [Class] Middle
# 131| 2: [FieldDeclaration] Inner inner;

View File

@@ -1 +1 @@
//semmle-extractor-options: --javac-args -source 17 -target 17
//semmle-extractor-options: --javac-args --release 21

View File

@@ -0,0 +1,63 @@
public class Test {
public static int source() { return 0; }
public static void sink(int x) { }
interface I { void take(int x); }
static class C1 implements I { public void take(int x) { sink(x); } }
static class C2 implements I { public void take(int x) { sink(x); } }
record Wrapper(Object o) implements I { public void take(int x) { sink(x); } }
record WrapperWrapper(Wrapper w) implements I { public void take(int x) { sink(x); } }
public static void test(int unknown, int alsoUnknown) {
I i = unknown == 0 ? new C1() : unknown == 1 ? new C2() : unknown == 2 ? new Wrapper(new Object()) : new WrapperWrapper(new Wrapper(new Object()));
switch(i) {
case C1 c1 when alsoUnknown == 1 -> { }
default -> i.take(source()); // Could call any implementation
}
switch(i) {
case C1 c1 -> { }
default -> i.take(source()); // Can't call C1.take
}
switch(i) {
case C1 c1 -> { }
case null, default -> i.take(source()); // Can't call C1.take (but we don't currently notice)
}
switch(i) {
case Wrapper w -> { }
default -> i.take(source()); // Can't call Wrapper.take
}
switch(i) {
case Wrapper(Object o) -> { }
default -> i.take(source()); // Can't call Wrapper.take
}
switch(i) {
case Wrapper(String s) -> { }
default -> i.take(source()); // Could call any implementation, because this might be a Wrapper(Integer) for example.
}
switch(i) {
case WrapperWrapper(Wrapper(Object o)) -> { }
default -> i.take(source()); // Can't call WrapperWrapper.take
}
switch(i) {
case WrapperWrapper(Wrapper(String s)) -> { }
default -> i.take(source()); // Could call any implementation, because this might be a WrapperWrapper(Wrapper((Integer)) for example.
}
switch(i) {
case C1 c1: break;
case null: default: i.take(source()); // Can't call C1.take (but we don't currently notice)
}
}
}

View File

@@ -0,0 +1 @@
//semmle-extractor-options: --javac-args --release 21

View File

@@ -0,0 +1,32 @@
| Test.java:18:25:18:32 | source(...) | Test.java:7:65:7:65 | x |
| Test.java:18:25:18:32 | source(...) | Test.java:8:65:8:65 | x |
| Test.java:18:25:18:32 | source(...) | Test.java:9:74:9:74 | x |
| Test.java:18:25:18:32 | source(...) | Test.java:10:82:10:82 | x |
| Test.java:23:25:23:32 | source(...) | Test.java:8:65:8:65 | x |
| Test.java:23:25:23:32 | source(...) | Test.java:9:74:9:74 | x |
| Test.java:23:25:23:32 | source(...) | Test.java:10:82:10:82 | x |
| Test.java:28:36:28:43 | source(...) | Test.java:7:65:7:65 | x |
| Test.java:28:36:28:43 | source(...) | Test.java:8:65:8:65 | x |
| Test.java:28:36:28:43 | source(...) | Test.java:9:74:9:74 | x |
| Test.java:28:36:28:43 | source(...) | Test.java:10:82:10:82 | x |
| Test.java:33:25:33:32 | source(...) | Test.java:7:65:7:65 | x |
| Test.java:33:25:33:32 | source(...) | Test.java:8:65:8:65 | x |
| Test.java:33:25:33:32 | source(...) | Test.java:10:82:10:82 | x |
| Test.java:38:25:38:32 | source(...) | Test.java:7:65:7:65 | x |
| Test.java:38:25:38:32 | source(...) | Test.java:8:65:8:65 | x |
| Test.java:38:25:38:32 | source(...) | Test.java:10:82:10:82 | x |
| Test.java:43:25:43:32 | source(...) | Test.java:7:65:7:65 | x |
| Test.java:43:25:43:32 | source(...) | Test.java:8:65:8:65 | x |
| Test.java:43:25:43:32 | source(...) | Test.java:9:74:9:74 | x |
| Test.java:43:25:43:32 | source(...) | Test.java:10:82:10:82 | x |
| Test.java:48:25:48:32 | source(...) | Test.java:7:65:7:65 | x |
| Test.java:48:25:48:32 | source(...) | Test.java:8:65:8:65 | x |
| Test.java:48:25:48:32 | source(...) | Test.java:9:74:9:74 | x |
| Test.java:53:25:53:32 | source(...) | Test.java:7:65:7:65 | x |
| Test.java:53:25:53:32 | source(...) | Test.java:8:65:8:65 | x |
| Test.java:53:25:53:32 | source(...) | Test.java:9:74:9:74 | x |
| Test.java:53:25:53:32 | source(...) | Test.java:10:82:10:82 | x |
| Test.java:58:34:58:41 | source(...) | Test.java:7:65:7:65 | x |
| Test.java:58:34:58:41 | source(...) | Test.java:8:65:8:65 | x |
| Test.java:58:34:58:41 | source(...) | Test.java:9:74:9:74 | x |
| Test.java:58:34:58:41 | source(...) | Test.java:10:82:10:82 | x |

View File

@@ -0,0 +1,18 @@
import java
import semmle.code.java.dataflow.DataFlow
module TestConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source.asExpr() = any(MethodCall mc | mc.getMethod().getName() = "source")
}
predicate isSink(DataFlow::Node sink) {
sink.asExpr() = any(MethodCall mc | mc.getMethod().getName() = "sink").getAnArgument()
}
}
module Flow = DataFlow::Global<TestConfig>;
from DataFlow::Node source, DataFlow::Node sink
where Flow::flow(source, sink)
select source, sink

View File

@@ -92,4 +92,30 @@ public class A extends ArrayList<Long> {
Object r = n;
}
}
public void m9(Object[] xs, int i) {
switch (xs[i]) {
case Integer i2 -> {
Object n = xs[i];
Object r = n;
}
default -> { }
}
}
public void m10(Object o) {
String s = "Hello world!";
Object o2 = s; // Alas, the type information, it is lost
if (o2 instanceof CharSequence cs) {
// Partially recovered statically, but we should know cs is an alias of o and therefore it's really a string.
Object target = cs;
}
// The same applies to a pattern case
switch (o2) {
case CharSequence cs -> { Object target = cs; }
default -> { }
}
}
}

View File

@@ -44,6 +44,10 @@ public class UnionTypes {
if (x instanceof Inter) {
x.hashCode();
}
var hashCode = switch (x) {
case Inter i -> x.hashCode();
default -> 0;
};
}
void m3(Object d) {

View File

@@ -0,0 +1 @@
//semmle-extractor-options: --javac-args --release 21

View File

@@ -13,4 +13,10 @@
| A.java:67:22:67:22 | x | Integer | false |
| A.java:70:23:70:24 | x2 | Integer | false |
| A.java:92:18:92:18 | n | Integer | false |
| A.java:100:20:100:20 | n | Integer | false |
| A.java:110:9:110:10 | o2 | String | false |
| A.java:112:23:112:24 | cs | String | false |
| A.java:116:13:116:14 | o2 | String | false |
| A.java:117:49:117:50 | cs | String | false |
| UnionTypes.java:45:7:45:7 | x | Inter | false |
| UnionTypes.java:48:23:48:23 | x | Inter | false |

View File

@@ -19,6 +19,11 @@
| UnionTypes.java:44:9:44:9 | x | 3 | A3 | false |
| UnionTypes.java:45:7:45:7 | x | 2 | A1 | false |
| UnionTypes.java:45:7:45:7 | x | 2 | A2 | true |
| UnionTypes.java:51:7:51:7 | d | 3 | A1 | false |
| UnionTypes.java:51:7:51:7 | d | 3 | A2 | false |
| UnionTypes.java:51:7:51:7 | d | 3 | A3 | false |
| UnionTypes.java:47:28:47:28 | x | 3 | A1 | false |
| UnionTypes.java:47:28:47:28 | x | 3 | A2 | true |
| UnionTypes.java:47:28:47:28 | x | 3 | A3 | false |
| UnionTypes.java:48:23:48:23 | x | 2 | A1 | false |
| UnionTypes.java:48:23:48:23 | x | 2 | A2 | true |
| UnionTypes.java:55:7:55:7 | d | 3 | A1 | false |
| UnionTypes.java:55:7:55:7 | d | 3 | A2 | false |
| UnionTypes.java:55:7:55:7 | d | 3 | A3 | false |

View File

@@ -0,0 +1,29 @@
public class Test {
private interface Intf { String get(); }
private static class Specific implements Intf { public String get() { return "Specific"; } }
private static class Alternative implements Intf { public String get() { return "Alternative"; } }
public static String caller() {
Alternative a = new Alternative(); // Instantiate this somewhere so there are at least two candidate types in general
return test(new Specific());
}
public static String test(Object o) {
if (o instanceof Intf i) {
// So we should know i.get is really Specific.get():
return i.get();
}
switch (o) {
case Intf i -> { return i.get(); } // Same goes for this `i`
default -> { return "Not an Intf"; }
}
}
}

View File

@@ -0,0 +1 @@
//semmle-extractor-options: --javac-args --release 21

View File

@@ -0,0 +1,8 @@
| Test.java:1:14:1:17 | super(...) | java.lang.Object.Object |
| Test.java:4:24:4:31 | super(...) | java.lang.Object.Object |
| Test.java:5:24:5:34 | super(...) | java.lang.Object.Object |
| Test.java:9:21:9:37 | new Alternative(...) | Test$Alternative.Alternative |
| Test.java:10:12:10:31 | test(...) | Test.test |
| Test.java:10:17:10:30 | new Specific(...) | Test$Specific.Specific |
| Test.java:18:14:18:20 | get(...) | Test$Specific.get |
| Test.java:22:31:22:37 | get(...) | Test$Specific.get |

View File

@@ -0,0 +1,6 @@
import java
import semmle.code.java.dispatch.VirtualDispatch
from Call c, Callable c2
where c2 = viableCallable(c)
select c, c2.getQualifiedName()

View File

@@ -0,0 +1,27 @@
public class G {
public static void test(String s) {
if (s == null) {
System.out.println("Is null");
}
switch(s) { // OK; null case means this doesn't throw.
case null -> System.out.println("Null");
case "foo" -> System.out.println("Foo");
default -> System.out.println("Something else");
}
var x = switch(s) { // OK; null case (combined with default) means this doesn't throw.
case "foo" -> "foo";
case null, default -> "bar";
};
switch(s) { // BAD; lack of a null case means this may throw.
case "foo" -> System.out.println("Foo");
case String s2 -> System.out.println("Other string of length " + s2.length());
}
}
}

View File

@@ -35,3 +35,4 @@
| C.java:233:7:233:8 | xs | Variable $@ may be null at this access because of $@ assignment. | C.java:231:5:231:56 | int[] xs | xs | C.java:231:11:231:55 | xs | this |
| F.java:11:5:11:7 | obj | Variable $@ may be null at this access as suggested by $@ null guard. | F.java:8:18:8:27 | obj | obj | F.java:9:9:9:19 | ... == ... | this |
| F.java:17:5:17:7 | obj | Variable $@ may be null at this access as suggested by $@ null guard. | F.java:14:18:14:27 | obj | obj | F.java:15:9:15:19 | ... == ... | this |
| G.java:20:12:20:12 | s | Variable $@ may be null at this access as suggested by $@ null guard. | G.java:3:27:3:34 | s | s | G.java:5:9:5:17 | ... == ... | this |

View File

@@ -1 +1 @@
//semmle-extractor-options: --javac-args -cp ${testdir}/../../stubs/junit-4.11:${testdir}/../../stubs/hamcrest-2.2:${testdir}/../../stubs/junit-jupiter-api-5.2.0
//semmle-extractor-options: --javac-args --release 21 -cp ${testdir}/../../stubs/junit-4.11:${testdir}/../../stubs/hamcrest-2.2:${testdir}/../../stubs/junit-jupiter-api-5.2.0

View File

@@ -9,3 +9,4 @@
| Test.java:98:47:98:60 | queryFromField | Query built by concatenation with $@, which may be untrusted. | Test.java:97:8:97:19 | categoryName | this expression |
| Test.java:108:47:108:61 | querySbToString | Query built by concatenation with $@, which may be untrusted. | Test.java:104:19:104:30 | categoryName | this expression |
| Test.java:118:47:118:62 | querySb2ToString | Query built by concatenation with $@, which may be untrusted. | Test.java:114:46:114:57 | categoryName | this expression |
| Test.java:221:81:221:111 | ... + ... | Query built by concatenation with $@, which may be untrusted. | Test.java:221:95:221:102 | category | this expression |

View File

@@ -13,10 +13,13 @@ edges
| Test.java:60:29:60:35 | querySb : StringBuilder | Test.java:60:29:60:46 | toString(...) : String |
| Test.java:60:29:60:46 | toString(...) : String | Test.java:62:47:62:61 | querySbToString |
| Test.java:183:33:183:45 | args : String[] | Test.java:209:47:209:68 | queryWithUserTableName |
| Test.java:213:26:213:38 | args : String[] | Test.java:214:11:214:14 | args : String[] |
| Test.java:213:26:213:38 | args : String[] | Test.java:218:14:218:17 | args : String[] |
| Test.java:214:11:214:14 | args : String[] | Test.java:29:30:29:42 | args : String[] |
| Test.java:218:14:218:17 | args : String[] | Test.java:183:33:183:45 | args : String[] |
| Test.java:213:34:213:46 | args : String[] | Test.java:221:81:221:111 | ... + ... |
| Test.java:227:26:227:38 | args : String[] | Test.java:228:11:228:14 | args : String[] |
| Test.java:227:26:227:38 | args : String[] | Test.java:232:14:232:17 | args : String[] |
| Test.java:227:26:227:38 | args : String[] | Test.java:233:15:233:18 | args : String[] |
| Test.java:228:11:228:14 | args : String[] | Test.java:29:30:29:42 | args : String[] |
| Test.java:232:14:232:17 | args : String[] | Test.java:183:33:183:45 | args : String[] |
| Test.java:233:15:233:18 | args : String[] | Test.java:213:34:213:46 | args : String[] |
nodes
| Mongo.java:10:29:10:41 | args : String[] | semmle.label | args : String[] |
| Mongo.java:17:45:17:67 | parse(...) | semmle.label | parse(...) |
@@ -35,17 +38,21 @@ nodes
| Test.java:78:46:78:50 | query | semmle.label | query |
| Test.java:183:33:183:45 | args : String[] | semmle.label | args : String[] |
| Test.java:209:47:209:68 | queryWithUserTableName | semmle.label | queryWithUserTableName |
| Test.java:213:26:213:38 | args : String[] | semmle.label | args : String[] |
| Test.java:214:11:214:14 | args : String[] | semmle.label | args : String[] |
| Test.java:218:14:218:17 | args : String[] | semmle.label | args : String[] |
| Test.java:213:34:213:46 | args : String[] | semmle.label | args : String[] |
| Test.java:221:81:221:111 | ... + ... | semmle.label | ... + ... |
| Test.java:227:26:227:38 | args : String[] | semmle.label | args : String[] |
| Test.java:228:11:228:14 | args : String[] | semmle.label | args : String[] |
| Test.java:232:14:232:17 | args : String[] | semmle.label | args : String[] |
| Test.java:233:15:233:18 | args : String[] | semmle.label | args : String[] |
subpaths
#select
| Mongo.java:17:45:17:67 | parse(...) | Mongo.java:10:29:10:41 | args : String[] | Mongo.java:17:45:17:67 | parse(...) | This query depends on a $@. | Mongo.java:10:29:10:41 | args | user-provided value |
| Mongo.java:21:49:21:52 | json | Mongo.java:10:29:10:41 | args : String[] | Mongo.java:21:49:21:52 | json | This query depends on a $@. | Mongo.java:10:29:10:41 | args | user-provided value |
| Test.java:36:47:36:52 | query1 | Test.java:213:26:213:38 | args : String[] | Test.java:36:47:36:52 | query1 | This query depends on a $@. | Test.java:213:26:213:38 | args | user-provided value |
| Test.java:42:57:42:62 | query2 | Test.java:213:26:213:38 | args : String[] | Test.java:42:57:42:62 | query2 | This query depends on a $@. | Test.java:213:26:213:38 | args | user-provided value |
| Test.java:50:62:50:67 | query3 | Test.java:213:26:213:38 | args : String[] | Test.java:50:62:50:67 | query3 | This query depends on a $@. | Test.java:213:26:213:38 | args | user-provided value |
| Test.java:62:47:62:61 | querySbToString | Test.java:213:26:213:38 | args : String[] | Test.java:62:47:62:61 | querySbToString | This query depends on a $@. | Test.java:213:26:213:38 | args | user-provided value |
| Test.java:70:40:70:44 | query | Test.java:213:26:213:38 | args : String[] | Test.java:70:40:70:44 | query | This query depends on a $@. | Test.java:213:26:213:38 | args | user-provided value |
| Test.java:78:46:78:50 | query | Test.java:213:26:213:38 | args : String[] | Test.java:78:46:78:50 | query | This query depends on a $@. | Test.java:213:26:213:38 | args | user-provided value |
| Test.java:209:47:209:68 | queryWithUserTableName | Test.java:213:26:213:38 | args : String[] | Test.java:209:47:209:68 | queryWithUserTableName | This query depends on a $@. | Test.java:213:26:213:38 | args | user-provided value |
| Test.java:36:47:36:52 | query1 | Test.java:227:26:227:38 | args : String[] | Test.java:36:47:36:52 | query1 | This query depends on a $@. | Test.java:227:26:227:38 | args | user-provided value |
| Test.java:42:57:42:62 | query2 | Test.java:227:26:227:38 | args : String[] | Test.java:42:57:42:62 | query2 | This query depends on a $@. | Test.java:227:26:227:38 | args | user-provided value |
| Test.java:50:62:50:67 | query3 | Test.java:227:26:227:38 | args : String[] | Test.java:50:62:50:67 | query3 | This query depends on a $@. | Test.java:227:26:227:38 | args | user-provided value |
| Test.java:62:47:62:61 | querySbToString | Test.java:227:26:227:38 | args : String[] | Test.java:62:47:62:61 | querySbToString | This query depends on a $@. | Test.java:227:26:227:38 | args | user-provided value |
| Test.java:70:40:70:44 | query | Test.java:227:26:227:38 | args : String[] | Test.java:70:40:70:44 | query | This query depends on a $@. | Test.java:227:26:227:38 | args | user-provided value |
| Test.java:78:46:78:50 | query | Test.java:227:26:227:38 | args : String[] | Test.java:78:46:78:50 | query | This query depends on a $@. | Test.java:227:26:227:38 | args | user-provided value |
| Test.java:209:47:209:68 | queryWithUserTableName | Test.java:227:26:227:38 | args : String[] | Test.java:209:47:209:68 | queryWithUserTableName | This query depends on a $@. | Test.java:227:26:227:38 | args | user-provided value |
| Test.java:221:81:221:111 | ... + ... | Test.java:227:26:227:38 | args : String[] | Test.java:221:81:221:111 | ... + ... | This query depends on a $@. | Test.java:227:26:227:38 | args | user-provided value |

Some files were not shown because too many files have changed in this diff Show More