From f4b45fa5110c33543f47d51e41a0a154111f233d Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 18 Oct 2023 18:40:47 +0100 Subject: [PATCH 001/115] Support switch cases with binding patterns --- java/ql/consistency-queries/children.ql | 4 +- .../lib/semmle/code/java/ControlFlowGraph.qll | 66 ++++++++- java/ql/lib/semmle/code/java/Expr.qll | 40 ++++- java/ql/lib/semmle/code/java/Statement.qll | 43 +++++- .../lib/semmle/code/java/dispatch/ObjFlow.qll | 9 ++ java/ql/test/library-tests/printAst/A.java | 30 +++- .../library-tests/printAst/PrintAst.expected | 137 ++++++++++++++---- java/ql/test/library-tests/printAst/options | 2 +- 8 files changed, 279 insertions(+), 52 deletions(-) diff --git a/java/ql/consistency-queries/children.ql b/java/ql/consistency-queries/children.ql index 7386ee79c00..4771110ecdb 100644 --- a/java/ql/consistency-queries/children.ql +++ b/java/ql/consistency-queries/children.ql @@ -46,7 +46,9 @@ 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()) } predicate lateFirstChild(Element e, int i) { diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index 572c8629626..e28b9f606b6 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -434,6 +434,15 @@ private module ControlFlowGraphImpl { ) } + /** + * Gets a SwitchCase's successor SwitchCase, if any. + */ + private predicate nextSwitchCase(SwitchCase pred, SwitchCase succ) { + exists(SwitchExpr se, int idx | se.getCase(idx) = pred and se.getCase(idx + 1) = succ) + or + exists(SwitchStmt ss, int idx | ss.getCase(idx) = pred and ss.getCase(idx + 1) = succ) + } + /** * Expressions and statements with CFG edges in post-order AST traversal. * @@ -467,7 +476,8 @@ private module ControlFlowGraphImpl { this instanceof NotInstanceOfExpr or this instanceof LocalVariableDeclExpr and - not this = any(InstanceOfExpr ioe).getLocalVariableDeclExpr() + not this = any(InstanceOfExpr ioe).getLocalVariableDeclExpr() and + not this = any(PatternCase pc).getDecl() or this instanceof StringTemplateExpr or @@ -493,7 +503,9 @@ 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 EmptyStmt or @@ -887,6 +899,14 @@ private module ControlFlowGraphImpl { else completion = caseCompletion ) or + // The last node in a case could always be a failing pattern check. + last = n.(PatternCase) and + completion = basicBooleanCompletion(false) + or + // The last node in a non-rule case is its variable declaration. + last = n.(PatternCase).getDecl() and + completion = NormalCompletion() + or // the last statement of a synchronized statement is the last statement of its body last(n.(SynchronizedStmt).getBlock(), last, completion) or @@ -1201,8 +1221,14 @@ private module ControlFlowGraphImpl { // 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()) + // ...and then for a vanilla switch to any case, or for a pattern switch to the first one. + exists(SwitchCase firstExecutedCase | + if switch.getACase() instanceof PatternCase + then firstExecutedCase = switch.getCase(0) + else firstExecutedCase = switch.getACase() + | + last(switch.getExpr(), n, completion) and result = first(firstExecutedCase) + ) or // Statements within a switch body execute sequentially. exists(int i | @@ -1216,7 +1242,13 @@ private module ControlFlowGraphImpl { 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()) + exists(SwitchCase firstExecutedCase | + if switch.getACase() instanceof PatternCase + then firstExecutedCase = switch.getCase(0) + else firstExecutedCase = switch.getACase() + | + last(switch.getExpr(), n, completion) and result = first(firstExecutedCase) + ) or // Statements within a switch body execute sequentially. exists(int i | @@ -1224,11 +1256,29 @@ private module ControlFlowGraphImpl { ) ) or + // Edge from rule SwitchCases to their body, after any variable assignment if applicable. // 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()) + exists(SwitchCase case, ControlFlowNode preBodyNode | + completion = NormalCompletion() and + if case instanceof PatternCase + then preBodyNode = case.(PatternCase).getDecl() + else preBodyNode = case + | + n = preBodyNode and result = first(case.getRuleExpression()) or - n = case and result = first(case.getRuleStatement()) + n = preBodyNode and result = first(case.getRuleStatement()) + ) + or + // A pattern case conducts a type test, then branches to either the next case or the assignment. + exists(PatternCase case | + n = case and + ( + completion = basicBooleanCompletion(false) and + nextSwitchCase(case, result) + or + completion = basicBooleanCompletion(true) and + result = case.getDecl() + ) ) or // Yield diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index 89e79b3ad0a..4d0936272f7 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1509,17 +1509,28 @@ 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](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 a (non-default) pattern `case` of this `switch` expression. */ + PatternCase getAPatternCase() { result = this.getACase() } /** Gets the `default` case of this switch expression, if any. */ - DefaultCase getDefaultCase() { result.getParent() = this } + DefaultCase getDefaultCase() { result = this.getACase() } /** Gets the expression of this `switch` expression. */ Expr getExpr() { result.getParent() = this } @@ -1592,7 +1603,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 +1625,33 @@ 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)) } /** Gets the name of the variable declared by this local variable declaration expression. */ string getName() { result = this.getVariable().getName() } + /** Gets the switch statement or expression whose pattern declares this identifier, if any. */ + StmtParent getAssociatedSwitch() { + result = this.getParent().(PatternCase).getParent() + } + + /** Holds if this is a declaration stemming from a pattern switch case. */ + predicate hasAssociatedSwitch() { + exists(this.getAssociatedSwitch()) + } + /** Gets the initializer expression of this local variable declaration expression, if any. */ - Expr getInit() { result.isNthChildOf(this, 0) } + Expr getInit() { + result.isNthChildOf(this, 0) + } /** Holds if this variable declaration implicitly initializes the variable. */ predicate hasImplicitInit() { exists(CatchClause cc | cc.getVariable() = this) or - exists(EnhancedForStmt efs | efs.getVariable() = this) + exists(EnhancedForStmt efs | efs.getVariable() = this) or + this.hasAssociatedSwitch() } /** Gets a printable representation of this expression. */ diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index fe0ba23093a..40f3c4c6f68 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -382,17 +382,28 @@ 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](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 a (non-default) pattern `case` of this `switch` statement. */ + PatternCase getAPatternCase() { result = this.getACase() } /** Gets the `default` case of this switch statement, if any. */ - DefaultCase getDefaultCase() { result.getParent() = this } + DefaultCase getDefaultCase() { result = this.getACase() } /** Gets the expression of this `switch` statement. */ Expr getExpr() { result.getParent() = this } @@ -464,15 +475,15 @@ class SwitchCase extends Stmt, @case { /** A constant `case` of a switch statement. */ class ConstCase extends SwitchCase { - ConstCase() { exists(Expr e | e.getParent() = this | e.getIndex() >= 0) } + ConstCase() { exists(Literal e | e.getParent() = this and e.getIndex() >= 0) } /** 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. */ - 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,6 +494,24 @@ class ConstCase extends SwitchCase { override string getAPrimaryQlClass() { result = "ConstCase" } } +/** A pattern case of a `switch` statement */ +class PatternCase extends SwitchCase { + LocalVariableDeclExpr patternVar; + + PatternCase() { patternVar.isNthChildOf(this, 0) } + + /** Gets the variable declared by this pattern case. */ + LocalVariableDeclExpr getDecl() { result.isNthChildOf(this, 0) } + + override string pp() { result = "case T t ..." } + + override string toString() { result = "case T t ..." } + + override string getHalsteadID() { result = "PatternCase" } + + override string getAPrimaryQlClass() { result = "PatternCase" } +} + /** A `default` case of a `switch` statement */ class DefaultCase extends SwitchCase { DefaultCase() { not exists(Expr e | e.getParent() = this | e.getIndex() >= 0) } diff --git a/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll b/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll index 293ba894fdf..c9ae5353127 100644 --- a/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll +++ b/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll @@ -80,6 +80,15 @@ private predicate step(Node n1, Node n2) { for.getVariable() = def.(BaseSsaUpdate).getDefiningExpr() and for.getExpr() = n1.asExpr() ) + or + exists(PatternCase pc | + pc.getDecl() = def.(BaseSsaUpdate).getDefiningExpr() and + ( + pc.getSwitch().getExpr() = n1.asExpr() + or + pc.getSwitchExpr().getExpr() = n1.asExpr() + ) + ) | v.getAnUltimateDefinition() = def and v.getAUse() = n2.asExpr() diff --git a/java/ql/test/library-tests/printAst/A.java b/java/ql/test/library-tests/printAst/A.java index 4cf4e8699da..cd47860f2ce 100644 --- a/java/ql/test/library-tests/printAst/A.java +++ b/java/ql/test/library-tests/printAst/A.java @@ -50,6 +50,34 @@ 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"; + }; } } catch (RuntimeException rte) { @@ -70,4 +98,4 @@ class A { * Javadoc for fields */ int i, j, k; -} \ No newline at end of file +} diff --git a/java/ql/test/library-tests/printAst/PrintAst.expected b/java/ql/test/library-tests/printAst/PrintAst.expected index 219889b35f6..28dfa0ed5c1 100644 --- a/java/ql/test/library-tests/printAst/PrintAst.expected +++ b/java/ql/test/library-tests/printAst/PrintAst.expected @@ -119,36 +119,117 @@ A.java: # 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 T t ... +# 54| -2: [TypeAccess] String +# 54| -1: [ExprStmt] ; +# 54| 0: [MethodAccess] println(...) +# 54| -1: [VarAccess] System.out +# 54| -1: [TypeAccess] System +# 54| 0: [VarAccess] s +# 54| 0: [LocalVariableDeclExpr] s +# 55| 1: [PatternCase] case T t ... +# 55| -2: [TypeAccess] Integer +# 55| -1: [ExprStmt] ; +# 55| 0: [MethodAccess] println(...) +# 55| -1: [VarAccess] System.out +# 55| -1: [TypeAccess] System +# 55| 0: [AddExpr] ... + ... +# 55| 0: [StringLiteral] "An integer: " +# 55| 1: [VarAccess] i +# 55| 0: [LocalVariableDeclExpr] i +# 56| 2: [DefaultCase] default +# 56| -1: [BlockStmt] { ... } +# 58| 3: [SwitchStmt] switch (...) +# 58| -1: [VarAccess] thing +# 59| 0: [PatternCase] case T t ... +# 59| -2: [TypeAccess] String +# 59| 0: [LocalVariableDeclExpr] s +# 60| 1: [ExprStmt] ; +# 60| 0: [MethodAccess] println(...) +# 60| -1: [VarAccess] System.out +# 60| -1: [TypeAccess] System +# 60| 0: [VarAccess] s +# 61| 2: [BreakStmt] break +# 62| 3: [PatternCase] case T t ... +# 62| -2: [TypeAccess] Integer +# 62| 0: [LocalVariableDeclExpr] i +# 63| 4: [ExprStmt] ; +# 63| 0: [MethodAccess] 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 T t ... +# 69| -2: [TypeAccess] String +# 69| -1: [VarAccess] s +# 69| 0: [LocalVariableDeclExpr] s +# 70| 1: [PatternCase] case T t ... +# 70| -2: [TypeAccess] Integer +# 70| -1: [AddExpr] ... + ... +# 70| 0: [StringLiteral] "An integer: " +# 70| 1: [VarAccess] i +# 70| 0: [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 T t ... +# 74| -2: [TypeAccess] String +# 74| 0: [LocalVariableDeclExpr] s +# 75| 1: [YieldStmt] yield ... +# 75| 0: [VarAccess] s +# 76| 2: [PatternCase] case T t ... +# 76| -2: [TypeAccess] Integer +# 76| 0: [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" +# 83| 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; +# 83| 0: [TypeAccess] RuntimeException +# 83| 1: [LocalVariableDeclExpr] rte +# 83| 1: [BlockStmt] { ... } +# 84| 0: [ReturnStmt] return ... +# 88| 10: [Class] E +# 92| 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; +# 89| 1: [Javadoc] /** Javadoc for enum constant */ +# 90| 0: [JavadocText] Javadoc for enum constant +# 92| -1: [TypeAccess] E +# 92| 0: [ClassInstanceExpr] new E(...) +# 92| -3: [TypeAccess] E +# 93| 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; +# 89| 1: [Javadoc] /** Javadoc for enum constant */ +# 90| 0: [JavadocText] Javadoc for enum constant +# 93| -1: [TypeAccess] E +# 93| 0: [ClassInstanceExpr] new E(...) +# 93| -3: [TypeAccess] E +# 94| 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, ...; +# 89| 1: [Javadoc] /** Javadoc for enum constant */ +# 90| 0: [JavadocText] Javadoc for enum constant +# 94| -1: [TypeAccess] E +# 94| 0: [ClassInstanceExpr] new E(...) +# 94| -3: [TypeAccess] E +# 100| 11: [FieldDeclaration] int i, ...; #-----| -3: (Javadoc) -# 69| 1: [Javadoc] /** Javadoc for fields */ -# 70| 0: [JavadocText] Javadoc for fields -# 72| -1: [TypeAccess] int +# 97| 1: [Javadoc] /** Javadoc for fields */ +# 98| 0: [JavadocText] Javadoc for fields +# 100| -1: [TypeAccess] int diff --git a/java/ql/test/library-tests/printAst/options b/java/ql/test/library-tests/printAst/options index a2f4d45311b..a0d1b7e7002 100644 --- a/java/ql/test/library-tests/printAst/options +++ b/java/ql/test/library-tests/printAst/options @@ -1 +1 @@ -//semmle-extractor-options: --javac-args -source 17 -target 17 +//semmle-extractor-options: --javac-args --release 21 From 6c990c2cf644bddfe8d16984f14feee76606fdb0 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 24 Oct 2023 15:13:10 +0100 Subject: [PATCH 002/115] Add pattern-case support and generally debug switch CFGs These were reasonably broken beforehand, due to not taking switch rules into account in enough places, and confusing the expression/statement switch rule distinction with the distinction between switch statements and expressions. (For example, `switch(x) { 1 -> System.out.println("Hello world") ... }` is a statement, but has a rule expression). --- .../lib/semmle/code/java/ControlFlowGraph.qll | 57 ++++++++----- java/ql/lib/semmle/code/java/Expr.qll | 2 +- java/ql/lib/semmle/code/java/Statement.qll | 15 +++- .../pattern-switch/cfg/Test.java | 39 +++++++++ .../library-tests/pattern-switch/cfg/options | 1 + .../pattern-switch/cfg/test.expected | 80 +++++++++++++++++++ .../library-tests/pattern-switch/cfg/test.ql | 5 ++ 7 files changed, 177 insertions(+), 22 deletions(-) create mode 100644 java/ql/test/library-tests/pattern-switch/cfg/Test.java create mode 100644 java/ql/test/library-tests/pattern-switch/cfg/options create mode 100644 java/ql/test/library-tests/pattern-switch/cfg/test.expected create mode 100644 java/ql/test/library-tests/pattern-switch/cfg/test.ql diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index e28b9f606b6..9fff5431cfe 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -435,7 +435,7 @@ private module ControlFlowGraphImpl { } /** - * Gets a SwitchCase's successor SwitchCase, if any. + * Holds if `succ` is `pred`'s successor `SwitchCase`. */ private predicate nextSwitchCase(SwitchCase pred, SwitchCase succ) { exists(SwitchExpr se, int idx | se.getCase(idx) = pred and se.getCase(idx + 1) = succ) @@ -723,7 +723,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 ) @@ -859,7 +859,7 @@ 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 @@ -879,32 +879,43 @@ 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 + // the last node in a case rule in statement context 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, SwitchStmt parent, SwitchCase case | + case = n and + case = parent.getACase() and + last(case.getRuleStatementOrExpressionStatement(), last, caseCompletion) and if caseCompletion instanceof NormalOrBooleanCompletion then completion = anonymousBreakCompletion() 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 + // ...and when a switch occurs in expression context, we wrap the RHS in a yield statement. + // Note the wrapping can only occur in the expression case, because a statement would need + // to have explicit `yield` statements. + exists(SwitchExpr parent, SwitchCase case | + case = n and + case = parent.getACase() and + ( + exists(Completion caseCompletion | + last(case.getRuleExpression(), last, caseCompletion) and + if caseCompletion instanceof NormalOrBooleanCompletion + then completion = YieldCompletion(caseCompletion) + else completion = caseCompletion + ) + or + last(case.getRuleStatement(), last, completion) + ) ) or - // The last node in a case could always be a failing pattern check. - last = n.(PatternCase) and - completion = basicBooleanCompletion(false) - or - // The last node in a non-rule case is its variable declaration. + // The normal last node in a non-rule pattern case is its variable declaration. + // Note that either rule or non-rule pattern cases can end with pattern match failure, whereupon + // they branch to the next candidate pattern. This is accounted for in the `succ` relation. last = n.(PatternCase).getDecl() and + not n.(PatternCase).isRule() and completion = NormalCompletion() or // the last statement of a synchronized statement is the last statement of its body @@ -1231,6 +1242,10 @@ private module ControlFlowGraphImpl { ) 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), and the case of pattern matching failure branching to the + // next case is specially handled in the `PatternCase` logic below. exists(int i | last(switch.getStmt(i), n, completion) and result = first(switch.getStmt(i + 1)) ) @@ -1251,6 +1266,10 @@ private module ControlFlowGraphImpl { ) 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), and the case of pattern matching failure branching to the + // next case is specially handled in the `PatternCase` logic below. exists(int i | last(switch.getStmt(i), n, completion) and result = first(switch.getStmt(i + 1)) ) diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index 4d0936272f7..7ef6e5dcc17 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1514,7 +1514,7 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr { * which may be either a normal `case` or a `default`. */ SwitchCase getCase(int i) { - result = rank[i](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx) + result = rank[i + 1](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx) } /** diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index 40f3c4c6f68..c6def1d9626 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -387,7 +387,8 @@ class SwitchStmt extends Stmt, @switchstmt { * which may be either a normal `case` or a `default`. */ SwitchCase getCase(int i) { - result = rank[i](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx) + result = + rank[i + 1](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx) } /** @@ -469,7 +470,17 @@ class SwitchCase extends Stmt, @case { * This predicate is mutually exclusive with `getRuleExpression`. */ Stmt getRuleStatement() { - result.getParent() = this and result.getIndex() = -1 and not result instanceof ExprStmt + result = this.getRuleStatementOrExpressionStatement() and not result instanceof ExprStmt + } + + /** + * Gets the statement, including an expression statement, on the RHS of the arrow, if any. + * + * This means this could be an explicit `case e1 -> { s1; ... }` or an implicit + * `case e1 -> stmt;` rule. + */ + Stmt getRuleStatementOrExpressionStatement() { + result.getParent() = this and result.getIndex() = -1 } } diff --git a/java/ql/test/library-tests/pattern-switch/cfg/Test.java b/java/ql/test/library-tests/pattern-switch/cfg/Test.java new file mode 100644 index 00000000000..1e27b9f01a2 --- /dev/null +++ b/java/ql/test/library-tests/pattern-switch/cfg/Test.java @@ -0,0 +1,39 @@ +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"; + }; + + } + +} diff --git a/java/ql/test/library-tests/pattern-switch/cfg/options b/java/ql/test/library-tests/pattern-switch/cfg/options new file mode 100644 index 00000000000..a0d1b7e7002 --- /dev/null +++ b/java/ql/test/library-tests/pattern-switch/cfg/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args --release 21 diff --git a/java/ql/test/library-tests/pattern-switch/cfg/test.expected b/java/ql/test/library-tests/pattern-switch/cfg/test.expected new file mode 100644 index 00000000000..db3e66ea3c1 --- /dev/null +++ b/java/ql/test/library-tests/pattern-switch/cfg/test.expected @@ -0,0 +1,80 @@ +| 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:37: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 T t ... | +| Test.java:6:8:6:23 | case T t ... | Test.java:6:20:6:20 | s | +| Test.java:6:8:6:23 | case T t ... | Test.java:7:8:7:24 | case T t ... | +| 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 | ; | 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 T t ... | Test.java:7:21:7:21 | i | +| Test.java:7:8:7:24 | case T t ... | 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 | ; | 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 T t ... | +| Test.java:12:8:12:21 | case T t ... | Test.java:12:20:12:20 | s | +| Test.java:12:8:12:21 | case T t ... | Test.java:15:8:15:22 | case T t ... | +| Test.java:12:20:12:20 | s | Test.java:13:10:13:31 | ; | +| 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 | ; | 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 T t ... | Test.java:15:21:15:21 | i | +| Test.java:15:8:15:22 | case T t ... | Test.java:18:8:18:15 | default | +| Test.java:15:21:15:21 | i | Test.java:16:10:16:47 | ; | +| 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 | ; | 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 T t ... | +| Test.java:23:8:23:23 | case T t ... | Test.java:23:20:23:20 | s | +| Test.java:23:8:23:23 | case T t ... | Test.java:24:8:24:24 | case T t ... | +| 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 T t ... | Test.java:24:21:24:21 | i | +| Test.java:24:8:24:24 | case T t ... | 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:3:22:3:25 | test | +| 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 T t ... | +| Test.java:29:8:29:21 | case T t ... | Test.java:29:20:29:20 | s | +| Test.java:29:8:29:21 | case T t ... | Test.java:31:8:31:22 | case T t ... | +| 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 T t ... | Test.java:31:21:31:21 | i | +| Test.java:31:8:31:22 | case T t ... | 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 | diff --git a/java/ql/test/library-tests/pattern-switch/cfg/test.ql b/java/ql/test/library-tests/pattern-switch/cfg/test.ql new file mode 100644 index 00000000000..0b07e8c4708 --- /dev/null +++ b/java/ql/test/library-tests/pattern-switch/cfg/test.ql @@ -0,0 +1,5 @@ +import java + +from ControlFlowNode cn +where cn.getFile().getBaseName() = "Test.java" +select cn, cn.getASuccessor() From 0f434e7f083f7be3ed1c33cad1767f6925c18e01 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 25 Oct 2023 15:18:23 +0100 Subject: [PATCH 003/115] Add test for dataflow vs. pattern-switch --- .../java/dataflow/internal/DataFlowUtil.qll | 4 +- .../pattern-switch/dfg/Test.java | 47 +++++++++++++++++++ .../library-tests/pattern-switch/dfg/options | 1 + .../pattern-switch/dfg/test.expected | 8 ++++ .../library-tests/pattern-switch/dfg/test.ql | 16 +++++++ 5 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 java/ql/test/library-tests/pattern-switch/dfg/Test.java create mode 100644 java/ql/test/library-tests/pattern-switch/dfg/options create mode 100644 java/ql/test/library-tests/pattern-switch/dfg/test.expected create mode 100644 java/ql/test/library-tests/pattern-switch/dfg/test.ql diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll index af86063cadd..3e7ca6daa12 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll @@ -197,7 +197,9 @@ 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().(LocalVariableDeclExpr).getAssociatedSwitch().(SwitchStmt).getExpr() = node1.asExpr() or + upd.getDefiningExpr().(LocalVariableDeclExpr).getAssociatedSwitch().(SwitchExpr).getExpr() = node1.asExpr() | node2.asExpr() = upd.getAFirstUse() and not capturedVariableRead(node2) diff --git a/java/ql/test/library-tests/pattern-switch/dfg/Test.java b/java/ql/test/library-tests/pattern-switch/dfg/Test.java new file mode 100644 index 00000000000..49271b0aa85 --- /dev/null +++ b/java/ql/test/library-tests/pattern-switch/dfg/Test.java @@ -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"; + }; + + } + +} diff --git a/java/ql/test/library-tests/pattern-switch/dfg/options b/java/ql/test/library-tests/pattern-switch/dfg/options new file mode 100644 index 00000000000..a0d1b7e7002 --- /dev/null +++ b/java/ql/test/library-tests/pattern-switch/dfg/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args --release 21 diff --git a/java/ql/test/library-tests/pattern-switch/dfg/test.expected b/java/ql/test/library-tests/pattern-switch/dfg/test.expected new file mode 100644 index 00000000000..87a466d3515 --- /dev/null +++ b/java/ql/test/library-tests/pattern-switch/dfg/test.expected @@ -0,0 +1,8 @@ +| 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(...) | diff --git a/java/ql/test/library-tests/pattern-switch/dfg/test.ql b/java/ql/test/library-tests/pattern-switch/dfg/test.ql new file mode 100644 index 00000000000..076821827b1 --- /dev/null +++ b/java/ql/test/library-tests/pattern-switch/dfg/test.ql @@ -0,0 +1,16 @@ +import java + +import semmle.code.java.dataflow.DataFlow + +module TestConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source.asExpr() instanceof StringLiteral } + + predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(MethodCall mc | mc.getMethod().getName() = "sink").getAnArgument() } +} + +module Flow = DataFlow::Global; + +from DataFlow::Node source, DataFlow::Node sink +where Flow::flow(source, sink) +select source, sink + From b6622d2f5bddd5a0eaeb1b8c27ce79fe11decb24 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 26 Oct 2023 14:47:50 +0100 Subject: [PATCH 004/115] usesType: support pattern cases --- java/ql/lib/semmle/code/java/Dependency.qll | 5 +++ .../lib/semmle/code/java/DependencyCounts.qll | 5 +++ .../library-tests/dependency/Depends.expected | 9 +++++ .../dependency/PrintAst.expected | 36 +++++++++++++++++++ .../dependency/UsesType.expected | 3 ++ .../dependency/dependency/A.java | 15 ++++++++ java/ql/test/library-tests/dependency/options | 1 + 7 files changed, 74 insertions(+) create mode 100644 java/ql/test/library-tests/dependency/options diff --git a/java/ql/lib/semmle/code/java/Dependency.qll b/java/ql/lib/semmle/code/java/Dependency.qll index 17236c6d05e..cbf753ce54c 100644 --- a/java/ql/lib/semmle/code/java/Dependency.qll +++ b/java/ql/lib/semmle/code/java/Dependency.qll @@ -79,6 +79,11 @@ predicate depends(RefType t, RefType dep) { exists(InstanceOfExpr ioe | t = ioe.getEnclosingCallable().getDeclaringType() | usesType(ioe.getCheckedType(), dep) ) + or + // the type accessed in a pattern-switch case statement in `t`. + exists(PatternCase pc | t = pc.getEnclosingCallable().getDeclaringType() | + usesType(pc.getDecl().getType(), dep) + ) ) } diff --git a/java/ql/lib/semmle/code/java/DependencyCounts.qll b/java/ql/lib/semmle/code/java/DependencyCounts.qll index b34e774f1e1..788c090bd1e 100644 --- a/java/ql/lib/semmle/code/java/DependencyCounts.qll +++ b/java/ql/lib/semmle/code/java/DependencyCounts.qll @@ -102,6 +102,11 @@ predicate numDepends(RefType t, RefType dep, int value) { | usesType(ioe.getCheckedType(), 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.getDecl().getType(), dep) + ) ) } diff --git a/java/ql/test/library-tests/dependency/Depends.expected b/java/ql/test/library-tests/dependency/Depends.expected index a3367e5fca1..4d5b4e5ada9 100644 --- a/java/ql/test/library-tests/dependency/Depends.expected +++ b/java/ql/test/library-tests/dependency/Depends.expected @@ -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 | diff --git a/java/ql/test/library-tests/dependency/PrintAst.expected b/java/ql/test/library-tests/dependency/PrintAst.expected index 6b16d893e55..e2b2cbb98f1 100644 --- a/java/ql/test/library-tests/dependency/PrintAst.expected +++ b/java/ql/test/library-tests/dependency/PrintAst.expected @@ -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 T t ... +#-----| 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 T t ... +#-----| 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 diff --git a/java/ql/test/library-tests/dependency/UsesType.expected b/java/ql/test/library-tests/dependency/UsesType.expected index a380a5bf6dc..792e20366d4 100644 --- a/java/ql/test/library-tests/dependency/UsesType.expected +++ b/java/ql/test/library-tests/dependency/UsesType.expected @@ -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 | diff --git a/java/ql/test/library-tests/dependency/dependency/A.java b/java/ql/test/library-tests/dependency/dependency/A.java index 98c625f96be..41f53ccfa55 100644 --- a/java/ql/test/library-tests/dependency/dependency/A.java +++ b/java/ql/test/library-tests/dependency/dependency/A.java @@ -26,4 +26,19 @@ class G extends Throwable { } class H { T test(T t) { return t; } void test2(java.util.Collection 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 { } } diff --git a/java/ql/test/library-tests/dependency/options b/java/ql/test/library-tests/dependency/options new file mode 100644 index 00000000000..a0d1b7e7002 --- /dev/null +++ b/java/ql/test/library-tests/dependency/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args --release 21 From a06ac425125eff1524a1df7aa26507f4f80ec9d1 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 26 Oct 2023 15:41:43 +0100 Subject: [PATCH 005/115] PrintAst: report pattern-cases similar to pattern-instanceof --- java/ql/lib/semmle/code/java/PrintAst.qll | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/ql/lib/semmle/code/java/PrintAst.qll b/java/ql/lib/semmle/code/java/PrintAst.qll index 44e5f9fa22e..4315e66ec06 100644 --- a/java/ql/lib/semmle/code/java/PrintAst.qll +++ b/java/ql/lib/semmle/code/java/PrintAst.qll @@ -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 */ From 7dd4030f51a804be71db7639b7b5a72a9197018a Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 26 Oct 2023 16:21:48 +0100 Subject: [PATCH 006/115] Pattern cases: support type-flow --- .../lib/semmle/code/java/dataflow/TypeFlow.qll | 18 +++++++++++++++++- .../library-tests/typeflow/UnionTypes.java | 4 ++++ .../library-tests/typeflow/typeflow.expected | 1 + .../typeflow/uniontypeflow.expected | 11 ++++++++--- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll index c4b95645bc8..acf704b3722 100644 --- a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll @@ -440,6 +440,21 @@ predicate arrayInstanceOfGuarded(ArrayAccess aa, RefType t) { ) } +/** + * Holds if `va` is an access to a value that is guarded by `case T t`. + */ +private predicate patternCaseGuarded(VarAccess va, RefType t) { + exists(PatternCase pc, BaseSsaVariable v | + va = v.getAUse() and + ( + pc.getSwitch().getExpr() = v.getAUse() or + pc.getSwitchExpr().getExpr() = v.getAUse() + ) and + pc.getDecl().getBasicBlock().bbDominates(va.getBasicBlock()) and + t = pc.getDecl().getType() + ) +} + /** * Holds if `t` is the type of the `this` value corresponding to the the * `SuperAccess`. As the `SuperAccess` expression has the type of the supertype, @@ -465,7 +480,8 @@ private predicate typeFlowBaseCand(TypeFlowNode n, RefType t) { instanceOfGuarded(n.asExpr(), srctype) or arrayInstanceOfGuarded(n.asExpr(), srctype) or n.asExpr().(FunctionalExpr).getConstructedType() = srctype or - superAccess(n.asExpr(), srctype) + superAccess(n.asExpr(), srctype) or + patternCaseGuarded(n.asExpr(), srctype) | t = srctype.(BoundedType).getAnUltimateUpperBoundType() or diff --git a/java/ql/test/library-tests/typeflow/UnionTypes.java b/java/ql/test/library-tests/typeflow/UnionTypes.java index a82b3828d2f..44fb54336c6 100644 --- a/java/ql/test/library-tests/typeflow/UnionTypes.java +++ b/java/ql/test/library-tests/typeflow/UnionTypes.java @@ -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) { diff --git a/java/ql/test/library-tests/typeflow/typeflow.expected b/java/ql/test/library-tests/typeflow/typeflow.expected index 021d04b55d3..0e4f3c9ff74 100644 --- a/java/ql/test/library-tests/typeflow/typeflow.expected +++ b/java/ql/test/library-tests/typeflow/typeflow.expected @@ -14,3 +14,4 @@ | A.java:70:23:70:24 | x2 | Integer | false | | A.java:92:18:92:18 | n | Integer | false | | UnionTypes.java:45:7:45:7 | x | Inter | false | +| UnionTypes.java:48:23:48:23 | x | Inter | false | diff --git a/java/ql/test/library-tests/typeflow/uniontypeflow.expected b/java/ql/test/library-tests/typeflow/uniontypeflow.expected index 7c595175301..c203583249d 100644 --- a/java/ql/test/library-tests/typeflow/uniontypeflow.expected +++ b/java/ql/test/library-tests/typeflow/uniontypeflow.expected @@ -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 | From b21aaa75bc8c45caa1456319144861d5424e89d2 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 26 Oct 2023 16:38:02 +0100 Subject: [PATCH 007/115] Type-flow: treat pattern-switch on an array index similar to instanceof --- .../semmle/code/java/controlflow/Guards.qll | 17 +++++-- .../semmle/code/java/dataflow/TypeFlow.qll | 51 +++++++++---------- java/ql/test/library-tests/typeflow/A.java | 10 ++++ .../library-tests/typeflow/typeflow.expected | 1 + 4 files changed, 46 insertions(+), 33 deletions(-) diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index 25809f4c16a..b11c2e91903 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -178,11 +178,18 @@ class Guard extends ExprParent { private predicate switchCaseControls(SwitchCase sc, BasicBlock bb) { exists(BasicBlock caseblock, Expr selector | selector = sc.getSelectorExpr() and - caseblock.getFirstNode() = sc.getControlFlowNode() and - caseblock.bbDominates(bb) and - forall(ControlFlowNode pred | pred = sc.getControlFlowNode().getAPredecessor() | - pred.(Expr).getParent*() = selector - ) + ( + if sc instanceof PatternCase + then caseblock.getFirstNode() = sc.(PatternCase).getDecl().getControlFlowNode() + else ( + caseblock.getFirstNode() = sc.getControlFlowNode() and + // Check there is no fall-through edge from a previous case: + forall(ControlFlowNode pred | pred = sc.getControlFlowNode().getAPredecessor() | + pred.(Expr).getParent*() = selector + ) + ) + ) and + caseblock.bbDominates(bb) ) } diff --git a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll index acf704b3722..47258af0a27 100644 --- a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll @@ -413,45 +413,41 @@ private predicate downcastSuccessor(VarAccess va, RefType t) { ) } -/** - * Holds if `va` is an access to a value that is guarded by `instanceof t`. - */ -private predicate instanceOfGuarded(VarAccess va, RefType t) { - exists(InstanceOfExpr ioe, BaseSsaVariable v | - ioe.getExpr() = v.getAUse() and - t = ioe.getCheckedType() and - va = v.getAUse() and - guardControls_v1(ioe, va.getBasicBlock(), true) +private Expr getAProbableAlias(Expr e) { + exists(BaseSsaVariable v | + e = v.getAUse() and + result = v.getAUse() + ) + or + exists(BaseSsaVariable v1, BaseSsaVariable v2, ArrayAccess aa1, ArrayAccess aa2 | + e = aa1 and + result = aa2 and + aa1.getArray() = v1.getAUse() and + aa1.getIndexExpr() = v2.getAUse() and + aa2.getArray() = v1.getAUse() and + aa2.getIndexExpr() = v2.getAUse() ) } /** - * Holds if `aa` is an access to a value that is guarded by `instanceof t`. + * Holds if `e` is an access to a value that is guarded by `instanceof t`. */ -predicate arrayInstanceOfGuarded(ArrayAccess aa, RefType t) { - exists(InstanceOfExpr ioe, BaseSsaVariable v1, BaseSsaVariable v2, ArrayAccess aa1 | - ioe.getExpr() = aa1 and +private predicate instanceOfGuarded(Expr e, RefType t) { + exists(InstanceOfExpr ioe | t = ioe.getCheckedType() 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) + e = getAProbableAlias(ioe.getExpr()) and + guardControls_v1(ioe, e.getBasicBlock(), true) ) } /** * Holds if `va` is an access to a value that is guarded by `case T t`. */ -private predicate patternCaseGuarded(VarAccess va, RefType t) { - exists(PatternCase pc, BaseSsaVariable v | - va = v.getAUse() and - ( - pc.getSwitch().getExpr() = v.getAUse() or - pc.getSwitchExpr().getExpr() = v.getAUse() - ) and - pc.getDecl().getBasicBlock().bbDominates(va.getBasicBlock()) and - t = pc.getDecl().getType() +private predicate patternCaseGuarded(Expr e, RefType t) { + exists(PatternCase pc | + e = getAProbableAlias([pc.getSwitch().getExpr(), pc.getSwitchExpr().getExpr()]) and + guardControls_v1(pc, e.getBasicBlock(), true) and + t = pc.getDecl().getType() ) } @@ -478,7 +474,6 @@ private predicate typeFlowBaseCand(TypeFlowNode n, RefType t) { upcastEnhancedForStmt(n.asSsa(), srctype) or downcastSuccessor(n.asExpr(), srctype) or instanceOfGuarded(n.asExpr(), srctype) or - arrayInstanceOfGuarded(n.asExpr(), srctype) or n.asExpr().(FunctionalExpr).getConstructedType() = srctype or superAccess(n.asExpr(), srctype) or patternCaseGuarded(n.asExpr(), srctype) diff --git a/java/ql/test/library-tests/typeflow/A.java b/java/ql/test/library-tests/typeflow/A.java index d4ed45df158..77cddd7c872 100644 --- a/java/ql/test/library-tests/typeflow/A.java +++ b/java/ql/test/library-tests/typeflow/A.java @@ -92,4 +92,14 @@ public class A extends ArrayList { Object r = n; } } + + public void m9(Object[] xs, int i) { + switch (xs[i]) { + case Integer i2 -> { + Object n = xs[i]; + Object r = n; + } + default -> { } + } + } } diff --git a/java/ql/test/library-tests/typeflow/typeflow.expected b/java/ql/test/library-tests/typeflow/typeflow.expected index 0e4f3c9ff74..97ba56e48e3 100644 --- a/java/ql/test/library-tests/typeflow/typeflow.expected +++ b/java/ql/test/library-tests/typeflow/typeflow.expected @@ -13,5 +13,6 @@ | 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 | | UnionTypes.java:45:7:45:7 | x | Inter | false | | UnionTypes.java:48:23:48:23 | x | Inter | false | From 30c588596699886a3b37dceae30db0b83025eb54 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 27 Oct 2023 12:46:47 +0100 Subject: [PATCH 008/115] Fix constant cases relating to enum types --- java/ql/lib/semmle/code/java/Statement.qll | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index c6def1d9626..7decc0f56ad 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -486,7 +486,11 @@ class SwitchCase extends Stmt, @case { /** A constant `case` of a switch statement. */ class ConstCase extends SwitchCase { - ConstCase() { exists(Literal e | e.getParent() = this and e.getIndex() >= 0) } + ConstCase() { + exists(Expr e | + e.getParent() = this and e.getIndex() >= 0 and not e instanceof LocalVariableDeclExpr + ) + } /** Gets the `case` constant at index 0. */ Expr getValue() { result.isNthChildOf(this, 0) } From 05caffc189805d33850d8d958441844228e5ccab Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 27 Oct 2023 15:58:54 +0100 Subject: [PATCH 009/115] Update printast expectation --- .../library-tests/printAst/PrintAst.expected | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/java/ql/test/library-tests/printAst/PrintAst.expected b/java/ql/test/library-tests/printAst/PrintAst.expected index 28dfa0ed5c1..514e4031d31 100644 --- a/java/ql/test/library-tests/printAst/PrintAst.expected +++ b/java/ql/test/library-tests/printAst/PrintAst.expected @@ -122,41 +122,45 @@ A.java: # 53| 2: [SwitchStmt] switch (...) # 53| -1: [VarAccess] thing # 54| 0: [PatternCase] case T t ... -# 54| -2: [TypeAccess] String # 54| -1: [ExprStmt] ; -# 54| 0: [MethodAccess] println(...) +# 54| 0: [MethodCall] println(...) # 54| -1: [VarAccess] System.out # 54| -1: [TypeAccess] System # 54| 0: [VarAccess] s -# 54| 0: [LocalVariableDeclExpr] s +#-----| 0: (Single Local Variable Declaration) +# 54| 0: [TypeAccess] String +# 54| 1: [LocalVariableDeclExpr] s # 55| 1: [PatternCase] case T t ... -# 55| -2: [TypeAccess] Integer # 55| -1: [ExprStmt] ; -# 55| 0: [MethodAccess] println(...) +# 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 -# 55| 0: [LocalVariableDeclExpr] 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 T t ... -# 59| -2: [TypeAccess] String -# 59| 0: [LocalVariableDeclExpr] s +#-----| 0: (Single Local Variable Declaration) +# 59| 0: [TypeAccess] String +# 59| 1: [LocalVariableDeclExpr] s # 60| 1: [ExprStmt] ; -# 60| 0: [MethodAccess] println(...) +# 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 T t ... -# 62| -2: [TypeAccess] Integer -# 62| 0: [LocalVariableDeclExpr] i +#-----| 0: (Single Local Variable Declaration) +# 62| 0: [TypeAccess] Integer +# 62| 1: [LocalVariableDeclExpr] i # 63| 4: [ExprStmt] ; -# 63| 0: [MethodAccess] println(...) +# 63| 0: [MethodCall] println(...) # 63| -1: [VarAccess] System.out # 63| -1: [TypeAccess] System # 63| 0: [AddExpr] ... + ... @@ -170,15 +174,17 @@ A.java: # 68| 0: [SwitchExpr] switch (...) # 68| -1: [VarAccess] thing # 69| 0: [PatternCase] case T t ... -# 69| -2: [TypeAccess] String # 69| -1: [VarAccess] s -# 69| 0: [LocalVariableDeclExpr] s +#-----| 0: (Single Local Variable Declaration) +# 69| 0: [TypeAccess] String +# 69| 1: [LocalVariableDeclExpr] s # 70| 1: [PatternCase] case T t ... -# 70| -2: [TypeAccess] Integer # 70| -1: [AddExpr] ... + ... # 70| 0: [StringLiteral] "An integer: " # 70| 1: [VarAccess] i -# 70| 0: [LocalVariableDeclExpr] 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 ...; @@ -186,13 +192,15 @@ A.java: # 73| 0: [SwitchExpr] switch (...) # 73| -1: [VarAccess] thing # 74| 0: [PatternCase] case T t ... -# 74| -2: [TypeAccess] String -# 74| 0: [LocalVariableDeclExpr] s +#-----| 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 T t ... -# 76| -2: [TypeAccess] Integer -# 76| 0: [LocalVariableDeclExpr] i +#-----| 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: " From 79b77ae805885f9bac1abe655ac52ebb2bd32b22 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 27 Oct 2023 16:10:41 +0100 Subject: [PATCH 010/115] Add AST test for switch with null case --- java/ql/test/library-tests/printAst/A.java | 4 ++ .../library-tests/printAst/PrintAst.expected | 65 +++++++++++-------- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/java/ql/test/library-tests/printAst/A.java b/java/ql/test/library-tests/printAst/A.java index cd47860f2ce..40e5eae0e29 100644 --- a/java/ql/test/library-tests/printAst/A.java +++ b/java/ql/test/library-tests/printAst/A.java @@ -78,6 +78,10 @@ class A { default: yield "Something else"; }; + var nullTest = switch(thing) { + case null -> "Null"; + default -> "Not null"; + }; } } catch (RuntimeException rte) { diff --git a/java/ql/test/library-tests/printAst/PrintAst.expected b/java/ql/test/library-tests/printAst/PrintAst.expected index 514e4031d31..2069e1b9391 100644 --- a/java/ql/test/library-tests/printAst/PrintAst.expected +++ b/java/ql/test/library-tests/printAst/PrintAst.expected @@ -208,36 +208,45 @@ A.java: # 78| 4: [DefaultCase] default # 79| 5: [YieldStmt] yield ... # 79| 0: [StringLiteral] "Something else" -# 83| 0: [CatchClause] catch (...) +# 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" +# 87| 0: [CatchClause] catch (...) #-----| 0: (Single Local Variable Declaration) -# 83| 0: [TypeAccess] RuntimeException -# 83| 1: [LocalVariableDeclExpr] rte -# 83| 1: [BlockStmt] { ... } -# 84| 0: [ReturnStmt] return ... -# 88| 10: [Class] E -# 92| 3: [FieldDeclaration] E A; +# 87| 0: [TypeAccess] RuntimeException +# 87| 1: [LocalVariableDeclExpr] rte +# 87| 1: [BlockStmt] { ... } +# 88| 0: [ReturnStmt] return ... +# 92| 10: [Class] E +# 96| 3: [FieldDeclaration] E A; #-----| -3: (Javadoc) -# 89| 1: [Javadoc] /** Javadoc for enum constant */ -# 90| 0: [JavadocText] Javadoc for enum constant -# 92| -1: [TypeAccess] E -# 92| 0: [ClassInstanceExpr] new E(...) -# 92| -3: [TypeAccess] E -# 93| 4: [FieldDeclaration] E B; +# 93| 1: [Javadoc] /** Javadoc for enum constant */ +# 94| 0: [JavadocText] Javadoc for enum constant +# 96| -1: [TypeAccess] E +# 96| 0: [ClassInstanceExpr] new E(...) +# 96| -3: [TypeAccess] E +# 97| 4: [FieldDeclaration] E B; #-----| -3: (Javadoc) -# 89| 1: [Javadoc] /** Javadoc for enum constant */ -# 90| 0: [JavadocText] Javadoc for enum constant -# 93| -1: [TypeAccess] E -# 93| 0: [ClassInstanceExpr] new E(...) -# 93| -3: [TypeAccess] E -# 94| 5: [FieldDeclaration] E C; +# 93| 1: [Javadoc] /** Javadoc for enum constant */ +# 94| 0: [JavadocText] Javadoc for enum constant +# 97| -1: [TypeAccess] E +# 97| 0: [ClassInstanceExpr] new E(...) +# 97| -3: [TypeAccess] E +# 98| 5: [FieldDeclaration] E C; #-----| -3: (Javadoc) -# 89| 1: [Javadoc] /** Javadoc for enum constant */ -# 90| 0: [JavadocText] Javadoc for enum constant -# 94| -1: [TypeAccess] E -# 94| 0: [ClassInstanceExpr] new E(...) -# 94| -3: [TypeAccess] E -# 100| 11: [FieldDeclaration] int i, ...; +# 93| 1: [Javadoc] /** Javadoc for enum constant */ +# 94| 0: [JavadocText] Javadoc for enum constant +# 98| -1: [TypeAccess] E +# 98| 0: [ClassInstanceExpr] new E(...) +# 98| -3: [TypeAccess] E +# 104| 11: [FieldDeclaration] int i, ...; #-----| -3: (Javadoc) -# 97| 1: [Javadoc] /** Javadoc for fields */ -# 98| 0: [JavadocText] Javadoc for fields -# 100| -1: [TypeAccess] int +# 101| 1: [Javadoc] /** Javadoc for fields */ +# 102| 0: [JavadocText] Javadoc for fields +# 104| -1: [TypeAccess] int From 6b9aed21dffb9bbfbaec824870688e760f822483 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 27 Oct 2023 16:14:00 +0100 Subject: [PATCH 011/115] Nullness library: recognise switches with null checks --- java/ql/lib/semmle/code/java/Expr.qll | 5 +++++ java/ql/lib/semmle/code/java/Statement.qll | 5 +++++ .../semmle/code/java/dataflow/Nullness.qll | 4 ++-- java/ql/test/query-tests/Nullness/G.java | 22 +++++++++++++++++++ .../query-tests/Nullness/NullMaybe.expected | 1 + java/ql/test/query-tests/Nullness/options | 2 +- 6 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 java/ql/test/query-tests/Nullness/G.java diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index 7ef6e5dcc17..e93349438f4 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1542,6 +1542,11 @@ 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 + } + /** Gets a printable representation of this expression. */ override string toString() { result = "switch (...)" } diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index 7decc0f56ad..63c9bd657be 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -409,6 +409,11 @@ class SwitchStmt extends Stmt, @switchstmt { /** 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 + } + override string pp() { result = "switch (...)" } override string toString() { result = "switch (...)" } diff --git a/java/ql/lib/semmle/code/java/dataflow/Nullness.qll b/java/ql/lib/semmle/code/java/dataflow/Nullness.qll index 4a759670506..01900fcbd64 100644 --- a/java/ql/lib/semmle/code/java/dataflow/Nullness.qll +++ b/java/ql/lib/semmle/code/java/dataflow/Nullness.qll @@ -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 diff --git a/java/ql/test/query-tests/Nullness/G.java b/java/ql/test/query-tests/Nullness/G.java new file mode 100644 index 00000000000..7fc99f2aea4 --- /dev/null +++ b/java/ql/test/query-tests/Nullness/G.java @@ -0,0 +1,22 @@ +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"); + } + + switch(s) { // BAD; lack of a null case means this may throw. + case "foo" -> System.out.println("Foo"); + default -> System.out.println("Something else"); + } + + } + +} diff --git a/java/ql/test/query-tests/Nullness/NullMaybe.expected b/java/ql/test/query-tests/Nullness/NullMaybe.expected index 8fc6aed34ea..b46356b3757 100644 --- a/java/ql/test/query-tests/Nullness/NullMaybe.expected +++ b/java/ql/test/query-tests/Nullness/NullMaybe.expected @@ -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:15:12:15: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 | diff --git a/java/ql/test/query-tests/Nullness/options b/java/ql/test/query-tests/Nullness/options index b996e88e0b0..c1e2dcae283 100644 --- a/java/ql/test/query-tests/Nullness/options +++ b/java/ql/test/query-tests/Nullness/options @@ -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 From ca43b9603abbed64a24ec2f11d423deedb9e4480 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 27 Oct 2023 16:14:55 +0100 Subject: [PATCH 012/115] Fixup typeflow test --- java/ql/test/library-tests/typeflow/options | 1 + 1 file changed, 1 insertion(+) create mode 100644 java/ql/test/library-tests/typeflow/options diff --git a/java/ql/test/library-tests/typeflow/options b/java/ql/test/library-tests/typeflow/options new file mode 100644 index 00000000000..a0d1b7e7002 --- /dev/null +++ b/java/ql/test/library-tests/typeflow/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args --release 21 From e94c5a772c7ed418e6de7d752bf4d27b9fa913b4 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 27 Oct 2023 16:23:12 +0100 Subject: [PATCH 013/115] Check nullness pass knows pattern case variables can't be null --- java/ql/test/query-tests/Nullness/G.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/test/query-tests/Nullness/G.java b/java/ql/test/query-tests/Nullness/G.java index 7fc99f2aea4..ebdce796c82 100644 --- a/java/ql/test/query-tests/Nullness/G.java +++ b/java/ql/test/query-tests/Nullness/G.java @@ -14,7 +14,7 @@ public class G { switch(s) { // BAD; lack of a null case means this may throw. case "foo" -> System.out.println("Foo"); - default -> System.out.println("Something else"); + case String s2 -> System.out.println("Other string of length " + s2.length()); } } From ba0f3cf71884c8addb771c462a40cd06a63a4c71 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 27 Oct 2023 17:31:47 +0100 Subject: [PATCH 014/115] Add basic support for case guards --- java/ql/lib/semmle/code/java/Statement.qll | 3 + java/ql/test/library-tests/printAst/A.java | 6 ++ .../library-tests/printAst/PrintAst.expected | 85 +++++++++++++------ 3 files changed, 66 insertions(+), 28 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index 63c9bd657be..14bd181baeb 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -523,6 +523,9 @@ class PatternCase extends SwitchCase { /** Gets the variable declared by this pattern case. */ LocalVariableDeclExpr getDecl() { result.isNthChildOf(this, 0) } + /** Gets the guard applicable to this pattern case, if any. */ + Expr getGuard() { result.isNthChildOf(this, -3) } + override string pp() { result = "case T t ..." } override string toString() { result = "case T t ..." } diff --git a/java/ql/test/library-tests/printAst/A.java b/java/ql/test/library-tests/printAst/A.java index 40e5eae0e29..0de15719a96 100644 --- a/java/ql/test/library-tests/printAst/A.java +++ b/java/ql/test/library-tests/printAst/A.java @@ -82,6 +82,12 @@ class A { 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"; + }; } } catch (RuntimeException rte) { diff --git a/java/ql/test/library-tests/printAst/PrintAst.expected b/java/ql/test/library-tests/printAst/PrintAst.expected index 2069e1b9391..83346159e06 100644 --- a/java/ql/test/library-tests/printAst/PrintAst.expected +++ b/java/ql/test/library-tests/printAst/PrintAst.expected @@ -217,36 +217,65 @@ A.java: # 82| 0: [NullLiteral] null # 83| 1: [DefaultCase] default # 83| -1: [StringLiteral] "Not null" -# 87| 0: [CatchClause] catch (...) +# 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 T t ... +# 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 T t ... +# 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" +# 93| 0: [CatchClause] catch (...) #-----| 0: (Single Local Variable Declaration) -# 87| 0: [TypeAccess] RuntimeException -# 87| 1: [LocalVariableDeclExpr] rte -# 87| 1: [BlockStmt] { ... } -# 88| 0: [ReturnStmt] return ... -# 92| 10: [Class] E -# 96| 3: [FieldDeclaration] E A; +# 93| 0: [TypeAccess] RuntimeException +# 93| 1: [LocalVariableDeclExpr] rte +# 93| 1: [BlockStmt] { ... } +# 94| 0: [ReturnStmt] return ... +# 98| 10: [Class] E +# 102| 3: [FieldDeclaration] E A; #-----| -3: (Javadoc) -# 93| 1: [Javadoc] /** Javadoc for enum constant */ -# 94| 0: [JavadocText] Javadoc for enum constant -# 96| -1: [TypeAccess] E -# 96| 0: [ClassInstanceExpr] new E(...) -# 96| -3: [TypeAccess] E -# 97| 4: [FieldDeclaration] E B; +# 99| 1: [Javadoc] /** Javadoc for enum constant */ +# 100| 0: [JavadocText] Javadoc for enum constant +# 102| -1: [TypeAccess] E +# 102| 0: [ClassInstanceExpr] new E(...) +# 102| -3: [TypeAccess] E +# 103| 4: [FieldDeclaration] E B; #-----| -3: (Javadoc) -# 93| 1: [Javadoc] /** Javadoc for enum constant */ -# 94| 0: [JavadocText] Javadoc for enum constant -# 97| -1: [TypeAccess] E -# 97| 0: [ClassInstanceExpr] new E(...) -# 97| -3: [TypeAccess] E -# 98| 5: [FieldDeclaration] E C; +# 99| 1: [Javadoc] /** Javadoc for enum constant */ +# 100| 0: [JavadocText] Javadoc for enum constant +# 103| -1: [TypeAccess] E +# 103| 0: [ClassInstanceExpr] new E(...) +# 103| -3: [TypeAccess] E +# 104| 5: [FieldDeclaration] E C; #-----| -3: (Javadoc) -# 93| 1: [Javadoc] /** Javadoc for enum constant */ -# 94| 0: [JavadocText] Javadoc for enum constant -# 98| -1: [TypeAccess] E -# 98| 0: [ClassInstanceExpr] new E(...) -# 98| -3: [TypeAccess] E -# 104| 11: [FieldDeclaration] int i, ...; +# 99| 1: [Javadoc] /** Javadoc for enum constant */ +# 100| 0: [JavadocText] Javadoc for enum constant +# 104| -1: [TypeAccess] E +# 104| 0: [ClassInstanceExpr] new E(...) +# 104| -3: [TypeAccess] E +# 110| 11: [FieldDeclaration] int i, ...; #-----| -3: (Javadoc) -# 101| 1: [Javadoc] /** Javadoc for fields */ -# 102| 0: [JavadocText] Javadoc for fields -# 104| -1: [TypeAccess] int +# 107| 1: [Javadoc] /** Javadoc for fields */ +# 108| 0: [JavadocText] Javadoc for fields +# 110| -1: [TypeAccess] int From 2b16121638854bbd3248fdc9a59003ed96db9abc Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 27 Oct 2023 18:31:27 +0100 Subject: [PATCH 015/115] CFG: Support guarded patterns --- .../lib/semmle/code/java/ControlFlowGraph.qll | 52 ++++++++++++--- .../pattern-switch/cfg/Test.java | 18 +++++ .../pattern-switch/cfg/test.expected | 66 ++++++++++++++++++- 3 files changed, 125 insertions(+), 11 deletions(-) diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index 9fff5431cfe..4fec17f9fb7 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -317,6 +317,8 @@ private module ControlFlowGraphImpl { whenexpr.getBranch(_).getAResult() = b ) or + b = any(PatternCase pc).getGuard() + or inBooleanContext(b.(ExprStmt).getExpr()) or inBooleanContext(b.(StmtExpr).getStmt()) @@ -911,12 +913,19 @@ private module ControlFlowGraphImpl { ) ) or - // The normal last node in a non-rule pattern case is its variable declaration. + // The normal last node in a non-rule pattern case is its variable declaration, or the successful + // matching of its guard if it has one. // Note that either rule or non-rule pattern cases can end with pattern match failure, whereupon // they branch to the next candidate pattern. This is accounted for in the `succ` relation. - last = n.(PatternCase).getDecl() and - not n.(PatternCase).isRule() and - completion = NormalCompletion() + exists(PatternCase pc | n = pc | + ( + if exists(pc.getGuard()) + then last(pc.getGuard(), last, BooleanCompletion(true, _)) + else last = pc.getDecl() + ) and + not pc.isRule() and + completion = NormalCompletion() + ) or // the last statement of a synchronized statement is the last statement of its body last(n.(SynchronizedStmt).getBlock(), last, completion) @@ -1275,20 +1284,28 @@ private module ControlFlowGraphImpl { ) ) or - // Edge from rule SwitchCases to their body, after any variable assignment if applicable. + // Edge from rule SwitchCases to their body, after any variable assignment and/or guard test if applicable. // No edges in a non-rule SwitchCase - the constant expression in a ConstCase isn't included in the CFG. exists(SwitchCase case, ControlFlowNode preBodyNode | - completion = NormalCompletion() and if case instanceof PatternCase - then preBodyNode = case.(PatternCase).getDecl() - else preBodyNode = case + then ( + if exists(case.(PatternCase).getGuard()) + then ( + last(case.(PatternCase).getGuard(), preBodyNode, completion) and + completion = basicBooleanCompletion(true) + ) else ( + preBodyNode = case.(PatternCase).getDecl() and completion = NormalCompletion() + ) + ) else ( + preBodyNode = case and completion = NormalCompletion() + ) | n = preBodyNode and result = first(case.getRuleExpression()) or n = preBodyNode and result = first(case.getRuleStatement()) ) or - // A pattern case conducts a type test, then branches to either the next case or the assignment. + // A pattern case conducts a type test, then branches to the next case or the assignment. exists(PatternCase case | n = case and ( @@ -1300,6 +1317,23 @@ private module ControlFlowGraphImpl { ) ) or + // A pattern case with a guard evaluates that guard after declaring its pattern variable, + // and thereafter if the guard doesn't match will branch to the next case. + // The case of a matching guard is accounted for in the case-with-rule logic above, or for + // non-rule case statements in `last`. + exists(PatternCase case, Expr guard | + guard = case.getGuard() and + ( + n = case.getDecl() and + result = first(guard) and + completion = NormalCompletion() + or + last(guard, n, completion) and + completion = basicBooleanCompletion(false) and + nextSwitchCase(case, result) + ) + ) + or // Yield exists(YieldStmt yield | completion = NormalCompletion() | n = yield and result = first(yield.getValue()) diff --git a/java/ql/test/library-tests/pattern-switch/cfg/Test.java b/java/ql/test/library-tests/pattern-switch/cfg/Test.java index 1e27b9f01a2..536a60593be 100644 --- a/java/ql/test/library-tests/pattern-switch/cfg/Test.java +++ b/java/ql/test/library-tests/pattern-switch/cfg/Test.java @@ -34,6 +34,24 @@ public class Test { 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 -> { } + } + } } diff --git a/java/ql/test/library-tests/pattern-switch/cfg/test.expected b/java/ql/test/library-tests/pattern-switch/cfg/test.expected index db3e66ea3c1..008b2693ae0 100644 --- a/java/ql/test/library-tests/pattern-switch/cfg/test.expected +++ b/java/ql/test/library-tests/pattern-switch/cfg/test.expected @@ -1,6 +1,6 @@ | 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:37:3 | { ... } | Test.java:5:6:5:19 | switch (...) | +| Test.java:3:41:55: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 T t ... | | Test.java:6:8:6:23 | case T t ... | Test.java:6:20:6:20 | s | @@ -60,7 +60,7 @@ | 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:3:22:3:25 | test | +| 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 T t ... | | Test.java:29:8:29:21 | case T t ... | Test.java:29:20:29:20 | s | @@ -78,3 +78,65 @@ | 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 T t ... | +| Test.java:38:8:38:42 | case T t ... | Test.java:38:20:38:20 | s | +| Test.java:38:8:38:42 | case T t ... | Test.java:41:8:41:42 | case T t ... | +| 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 | ; | +| Test.java:38:27:38:41 | ... == ... | Test.java:41:8:41:42 | case T t ... | +| 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 | ; | 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 T t ... | Test.java:41:20:41:20 | s | +| Test.java:41:8:41:42 | case T t ... | 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 | ; | +| 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 | ; | 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 | ; | +| 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 | ; | 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 T t ... | +| Test.java:50:8:50:44 | case T t ... | Test.java:50:20:50:20 | s | +| Test.java:50:8:50:44 | case T t ... | Test.java:51:8:51:44 | case T t ... | +| 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 T t ... | +| 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:3:22:3:25 | test | +| Test.java:50:46:50:76 | ; | 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 T t ... | Test.java:51:20:51:20 | s | +| Test.java:51:8:51:44 | case T t ... | 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:3:22:3:25 | test | +| Test.java:51:46:51:76 | ; | 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:3:22:3:25 | test | From 9a450b09bed11cfc7f7f422fedc022fb086ae580 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Mon, 30 Oct 2023 12:19:15 +0000 Subject: [PATCH 016/115] Account for pattern-cases in more places --- java/ql/lib/semmle/code/java/metrics/MetricCallable.qll | 9 ++++++--- .../Declarations/BreakInSwitchCase.ql | 3 +-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/java/ql/lib/semmle/code/java/metrics/MetricCallable.qll b/java/ql/lib/semmle/code/java/metrics/MetricCallable.qll index b6df9769ab3..018ca4fae1b 100644 --- a/java/ql/lib/semmle/code/java/metrics/MetricCallable.qll +++ b/java/ql/lib/semmle/code/java/metrics/MetricCallable.qll @@ -73,13 +73,15 @@ 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(SwitchCase default | default.hasDefaultLabel() | + default.(ControlFlowNode).getASuccessor() = sc + ) + or defaultFallThrough(sc.(ControlFlowNode).getAPredecessor()) } @@ -90,6 +92,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 } diff --git a/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.ql b/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.ql index bfcd7bdaf71..312a77878fe 100644 --- a/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.ql +++ b/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.ql @@ -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, From 54a89d6fef1f6be7853e9c61d9b44b18d302c4b0 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 31 Oct 2023 14:53:49 +0000 Subject: [PATCH 017/115] Handle 'case null, default:' --- java/ql/examples/snippets/switchcase.ql | 2 +- java/ql/lib/config/semmlecode.dbscheme | 4 ++ java/ql/lib/semmle/code/java/Expr.qll | 22 +++--- .../lib/semmle/code/java/PrettyPrintAst.qll | 11 ++- java/ql/lib/semmle/code/java/Statement.qll | 38 ++++++++++- .../java/controlflow/UnreachableBlocks.qll | 3 +- .../java/controlflow/internal/GuardsLogic.qll | 8 ++- .../Statements/MissingDefaultInSwitch.ql | 2 +- .../Statements/MissingEnumInSwitch.ql | 2 +- .../pattern-switch/cfg/Test.java | 18 +++++ java/ql/test/library-tests/printAst/A.java | 4 ++ .../library-tests/printAst/PrintAst.expected | 68 +++++++++++-------- 12 files changed, 131 insertions(+), 51 deletions(-) diff --git a/java/ql/examples/snippets/switchcase.ql b/java/ql/examples/snippets/switchcase.ql index d425d5686f4..12a6ae021f3 100644 --- a/java/ql/examples/snippets/switchcase.ql +++ b/java/ql/examples/snippets/switchcase.ql @@ -14,5 +14,5 @@ where switch.getExpr().getType() = enum and missing.getDeclaringType() = enum and not switch.getAConstCase().getValue() = missing.getAnAccess() and - not exists(switch.getDefaultCase()) + not exists(switch.getDefaultOrNullDefaultCase()) select switch diff --git a/java/ql/lib/config/semmlecode.dbscheme b/java/ql/lib/config/semmlecode.dbscheme index ecfcf050952..53c0d42e216 100644 --- a/java/ql/lib/config/semmlecode.dbscheme +++ b/java/ql/lib/config/semmlecode.dbscheme @@ -992,6 +992,10 @@ providesWith( string serviceImpl: string ref ); +isNullDefaultCase( + int id: @case ref +); + /* * Javadoc */ diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index e93349438f4..a16aac6f990 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1514,7 +1514,8 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr { * 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) + result = + rank[i + 1](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx) } /** @@ -1532,6 +1533,9 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr { /** Gets the `default` case of this switch expression, if any. */ DefaultCase getDefaultCase() { result = this.getACase() } + /** Gets the `default` or `case null, default` case of this switch statement, if any. */ + SwitchCase getDefaultOrNullDefaultCase() { result = this.getACase() and result.hasDefaultLabel() } + /** Gets the expression of this `switch` expression. */ Expr getExpr() { result.getParent() = this } @@ -1543,9 +1547,7 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr { } /** Holds if this switch has a case handling a null literal. */ - predicate hasNullCase() { - this.getAConstCase().getValue(_) instanceof NullLiteral - } + predicate hasNullCase() { this.getAConstCase().getValue(_) instanceof NullLiteral } /** Gets a printable representation of this expression. */ override string toString() { result = "switch (...)" } @@ -1638,19 +1640,13 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr { string getName() { result = this.getVariable().getName() } /** Gets the switch statement or expression whose pattern declares this identifier, if any. */ - StmtParent getAssociatedSwitch() { - result = this.getParent().(PatternCase).getParent() - } + StmtParent getAssociatedSwitch() { result = this.getParent().(PatternCase).getParent() } /** Holds if this is a declaration stemming from a pattern switch case. */ - predicate hasAssociatedSwitch() { - exists(this.getAssociatedSwitch()) - } + predicate hasAssociatedSwitch() { exists(this.getAssociatedSwitch()) } /** Gets the initializer expression of this local variable declaration expression, if any. */ - Expr getInit() { - result.isNthChildOf(this, 0) - } + Expr getInit() { result.isNthChildOf(this, 0) } /** Holds if this variable declaration implicitly initializes the variable. */ predicate hasImplicitInit() { diff --git a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll index f7ddbee4abc..ca08643190e 100644 --- a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll +++ b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll @@ -746,7 +746,9 @@ private class PpSwitchCase extends PpAst, SwitchCase { override string getPart(int i) { i = 0 and result = "default" and this instanceof DefaultCase or - i = 0 and result = "case " and this instanceof ConstCase + i = 0 and result = "case " and not this instanceof DefaultCase + or + i = this.lastConstCaseValueIndex() and result = "default" and this instanceof NullDefaultCase or exists(int j | i = 2 * j and j != 0 and result = ", " and exists(this.(ConstCase).getValue(j))) or @@ -757,8 +759,13 @@ private class PpSwitchCase extends PpAst, SwitchCase { i = 3 + this.lastConstCaseValueIndex() and result = ";" and exists(this.getRuleExpression()) } + private int getCaseDefaultOffset() { + if this instanceof NullDefaultCase then result = 1 else result = 0 + } + 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))) + this.getCaseDefaultOffset()) } override PpAst getChild(int i) { diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index 14bd181baeb..c5002356108 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -406,6 +406,9 @@ class SwitchStmt extends Stmt, @switchstmt { /** Gets the `default` case of this switch statement, if any. */ DefaultCase getDefaultCase() { result = this.getACase() } + /** Gets the `default` or `case null, default` case of this switch statement, if any. */ + SwitchCase getDefaultOrNullDefaultCase() { result = this.getACase() and result.hasDefaultLabel() } + /** Gets the expression of this `switch` statement. */ Expr getExpr() { result.getParent() = this } @@ -487,14 +490,28 @@ class SwitchCase extends Stmt, @case { Stmt getRuleStatementOrExpressionStatement() { result.getParent() = this and result.getIndex() = -1 } + + /** + * Holds if this case statement includes the default label, i.e. it is either `default` + * or `case null, default`. + */ + predicate hasDefaultLabel() { this instanceof DefaultCase or this instanceof NullDefaultCase } } -/** 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 and e.getIndex() >= 0 and not e instanceof LocalVariableDeclExpr ) + // 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. + and not isNullDefaultCase(this) } /** Gets the `case` constant at index 0. */ @@ -535,7 +552,11 @@ class PatternCase extends SwitchCase { override string getAPrimaryQlClass() { result = "PatternCase" } } -/** A `default` case of a `switch` statement */ +/** + * A `default` case of a `switch` statement. + * + * Note this does not include `case null, default` -- for that, see `NullDefaultCase`. + */ class DefaultCase extends SwitchCase { DefaultCase() { not exists(Expr e | e.getParent() = this | e.getIndex() >= 0) } @@ -548,6 +569,19 @@ class DefaultCase extends SwitchCase { override string getAPrimaryQlClass() { result = "DefaultCase" } } +/** A `case null, default` statement of a `switch` statement or expression. */ +class NullDefaultCase extends SwitchCase { + 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. */ diff --git a/java/ql/lib/semmle/code/java/controlflow/UnreachableBlocks.qll b/java/ql/lib/semmle/code/java/controlflow/UnreachableBlocks.qll index 89a9f08850d..4a13ad93e9f 100644 --- a/java/ql/lib/semmle/code/java/controlflow/UnreachableBlocks.qll +++ b/java/ql/lib/semmle/code/java/controlflow/UnreachableBlocks.qll @@ -170,10 +170,11 @@ 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() - else result = this.getDefaultCase() + else result = this.getDefaultOrNullDefaultCase() } /** diff --git a/java/ql/lib/semmle/code/java/controlflow/internal/GuardsLogic.qll b/java/ql/lib/semmle/code/java/controlflow/internal/GuardsLogic.qll index 9fed7516ba3..377c043a052 100644 --- a/java/ql/lib/semmle/code/java/controlflow/internal/GuardsLogic.qll +++ b/java/ql/lib/semmle/code/java/controlflow/internal/GuardsLogic.qll @@ -55,9 +55,13 @@ predicate implies_v1(Guard g1, boolean b1, Guard g2, boolean b2) { ) ) or - g1.(DefaultCase).getSwitch().getAConstCase() = g2 and b1 = true and b2 = false + exists(SwitchCase sc | g1 = sc and sc.hasDefaultLabel() | + sc.getSwitch().getAConstCase() = g2 and b1 = true and b2 = false + ) or - g1.(DefaultCase).getSwitchExpr().getAConstCase() = g2 and b1 = true and b2 = false + exists(SwitchCase sc | g1 = sc and sc.hasDefaultLabel() | + sc.getSwitchExpr().getAConstCase() = g2 and b1 = true and b2 = false + ) or exists(MethodCall check, int argIndex | check = g1 | conditionCheckArgument(check, argIndex, _) and diff --git a/java/ql/src/Advisory/Statements/MissingDefaultInSwitch.ql b/java/ql/src/Advisory/Statements/MissingDefaultInSwitch.ql index eab8a596f0e..ce2aa08035b 100644 --- a/java/ql/src/Advisory/Statements/MissingDefaultInSwitch.ql +++ b/java/ql/src/Advisory/Statements/MissingDefaultInSwitch.ql @@ -15,5 +15,5 @@ import java from SwitchStmt switch where not switch.getExpr().getType() instanceof EnumType and - not exists(switch.getDefaultCase()) + not exists(switch.getDefaultOrNullDefaultCase()) select switch, "Switch statement does not have a default case." diff --git a/java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.ql b/java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.ql index dfea3ad72d9..2f76e6f2e31 100644 --- a/java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.ql +++ b/java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.ql @@ -17,6 +17,6 @@ from SwitchStmt switch, EnumType enum, EnumConstant missing where switch.getExpr().getType() = enum and missing.getDeclaringType() = enum and - not exists(switch.getDefaultCase()) and + not exists(switch.getDefaultOrNullDefaultCase()) and not switch.getAConstCase().getValue() = missing.getAnAccess() select switch, "Switch statement does not have a case for $@.", missing, missing.getName() diff --git a/java/ql/test/library-tests/pattern-switch/cfg/Test.java b/java/ql/test/library-tests/pattern-switch/cfg/Test.java index 536a60593be..80d0da34d13 100644 --- a/java/ql/test/library-tests/pattern-switch/cfg/Test.java +++ b/java/ql/test/library-tests/pattern-switch/cfg/Test.java @@ -52,6 +52,24 @@ public class Test { 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"); + } + } } diff --git a/java/ql/test/library-tests/printAst/A.java b/java/ql/test/library-tests/printAst/A.java index 0de15719a96..6bce5cc771b 100644 --- a/java/ql/test/library-tests/printAst/A.java +++ b/java/ql/test/library-tests/printAst/A.java @@ -88,6 +88,10 @@ class A { 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"; + }; } } catch (RuntimeException rte) { diff --git a/java/ql/test/library-tests/printAst/PrintAst.expected b/java/ql/test/library-tests/printAst/PrintAst.expected index 83346159e06..43b89a366f3 100644 --- a/java/ql/test/library-tests/printAst/PrintAst.expected +++ b/java/ql/test/library-tests/printAst/PrintAst.expected @@ -246,36 +246,48 @@ A.java: # 88| 1: [LocalVariableDeclExpr] s # 89| 3: [DefaultCase] default # 89| -1: [StringLiteral] "It's something else" -# 93| 0: [CatchClause] catch (...) +# 91| 8: [LocalVariableDeclStmt] var ...; +# 91| 1: [LocalVariableDeclExpr] nullDefaultTest +# 91| 0: [SwitchExpr] switch (...) +# 91| -1: [VarAccess] thing +# 92| 0: [PatternCase] case T t ... +# 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 +# 97| 0: [CatchClause] catch (...) #-----| 0: (Single Local Variable Declaration) -# 93| 0: [TypeAccess] RuntimeException -# 93| 1: [LocalVariableDeclExpr] rte -# 93| 1: [BlockStmt] { ... } -# 94| 0: [ReturnStmt] return ... -# 98| 10: [Class] E -# 102| 3: [FieldDeclaration] E A; +# 97| 0: [TypeAccess] RuntimeException +# 97| 1: [LocalVariableDeclExpr] rte +# 97| 1: [BlockStmt] { ... } +# 98| 0: [ReturnStmt] return ... +# 102| 10: [Class] E +# 106| 3: [FieldDeclaration] E A; #-----| -3: (Javadoc) -# 99| 1: [Javadoc] /** Javadoc for enum constant */ -# 100| 0: [JavadocText] Javadoc for enum constant -# 102| -1: [TypeAccess] E -# 102| 0: [ClassInstanceExpr] new E(...) -# 102| -3: [TypeAccess] E -# 103| 4: [FieldDeclaration] E B; +# 103| 1: [Javadoc] /** Javadoc for enum constant */ +# 104| 0: [JavadocText] Javadoc for enum constant +# 106| -1: [TypeAccess] E +# 106| 0: [ClassInstanceExpr] new E(...) +# 106| -3: [TypeAccess] E +# 107| 4: [FieldDeclaration] E B; #-----| -3: (Javadoc) -# 99| 1: [Javadoc] /** Javadoc for enum constant */ -# 100| 0: [JavadocText] Javadoc for enum constant -# 103| -1: [TypeAccess] E -# 103| 0: [ClassInstanceExpr] new E(...) -# 103| -3: [TypeAccess] E -# 104| 5: [FieldDeclaration] E C; +# 103| 1: [Javadoc] /** Javadoc for enum constant */ +# 104| 0: [JavadocText] Javadoc for enum constant +# 107| -1: [TypeAccess] E +# 107| 0: [ClassInstanceExpr] new E(...) +# 107| -3: [TypeAccess] E +# 108| 5: [FieldDeclaration] E C; #-----| -3: (Javadoc) -# 99| 1: [Javadoc] /** Javadoc for enum constant */ -# 100| 0: [JavadocText] Javadoc for enum constant -# 104| -1: [TypeAccess] E -# 104| 0: [ClassInstanceExpr] new E(...) -# 104| -3: [TypeAccess] E -# 110| 11: [FieldDeclaration] int i, ...; +# 103| 1: [Javadoc] /** Javadoc for enum constant */ +# 104| 0: [JavadocText] Javadoc for enum constant +# 108| -1: [TypeAccess] E +# 108| 0: [ClassInstanceExpr] new E(...) +# 108| -3: [TypeAccess] E +# 114| 11: [FieldDeclaration] int i, ...; #-----| -3: (Javadoc) -# 107| 1: [Javadoc] /** Javadoc for fields */ -# 108| 0: [JavadocText] Javadoc for fields -# 110| -1: [TypeAccess] int +# 111| 1: [Javadoc] /** Javadoc for fields */ +# 112| 0: [JavadocText] Javadoc for fields +# 114| -1: [TypeAccess] int From 144218e2f714c93887a0b6afe83c7b237fd837e4 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 31 Oct 2023 16:31:57 +0000 Subject: [PATCH 018/115] Implement switch CFG when there are mixed constant and pattern cases --- .../lib/semmle/code/java/ControlFlowGraph.qll | 86 +++++++++++++------ .../pattern-switch/cfg/Test.java | 14 +++ .../pattern-switch/cfg/test.expected | 86 ++++++++++++++++++- 3 files changed, 158 insertions(+), 28 deletions(-) diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index 4fec17f9fb7..837cc576a3f 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -437,12 +437,62 @@ private module ControlFlowGraphImpl { } /** - * Holds if `succ` is `pred`'s successor `SwitchCase`. + * Gets the `i`th `SwitchCase` defined on `switch`, if one exists. */ - private predicate nextSwitchCase(SwitchCase pred, SwitchCase succ) { - exists(SwitchExpr se, int idx | se.getCase(idx) = pred and se.getCase(idx + 1) = succ) - or - exists(SwitchStmt ss, int idx | ss.getCase(idx) = pred and ss.getCase(idx + 1) = succ) + private SwitchCase getCase(StmtParent switch, int i) { + result = switch.(SwitchExpr).getCase(i) or result = switch.(SwitchStmt).getCase(i) + } + + /** + * Gets the `i`th `PatternCase` defined on `switch`, if one exists. + */ + private PatternCase getPatternCase(StmtParent switch, int i) { + result = rank[i + 1](PatternCase pc, int caseIdx | pc = getCase(switch, caseIdx) | pc order by caseIdx asc) + } + + /** + * Gets the PatternCase after pc, if one exists. + */ + private PatternCase getNextPatternCase(PatternCase pc) { + exists(int idx, StmtParent switch | pc = getPatternCase(switch, idx) and result = getPatternCase(switch, idx + 1)) + } + + /** + * Gets a `SwitchCase` that may be `pred`'s direct successor. + * + * 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) { + result.getParent() = pred.getParent() and + result.getIndex() > pred.getIndex() and + not result.(ConstCase).getValue(_) instanceof NullLiteral and + ( + result.getIndex() <= getNextPatternCase(pred).getIndex() + or + not exists(getNextPatternCase(pred)) + ) + } + + /** + * 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(StmtParent switch) { + result.getParent() = switch and + ( + result.(ConstCase).getValue(_) instanceof NullLiteral + or + not exists(getPatternCase(switch, _)) + or + result.getIndex() <= getPatternCase(switch, 0).getIndex() + ) } /** @@ -868,7 +918,7 @@ private module ControlFlowGraphImpl { completion = NormalCompletion() or // if no default case exists, then normal completion of the expression may terminate the switch - not exists(switch.getDefaultCase()) and + not exists(switch.getDefaultOrNullDefaultCase()) and last(switch.getExpr(), last, completion) and completion = NormalCompletion() ) @@ -1241,14 +1291,8 @@ private module ControlFlowGraphImpl { // From the entry point control is transferred first to the expression... n = switch and result = first(switch.getExpr()) or - // ...and then for a vanilla switch to any case, or for a pattern switch to the first one. - exists(SwitchCase firstExecutedCase | - if switch.getACase() instanceof PatternCase - then firstExecutedCase = switch.getCase(0) - else firstExecutedCase = switch.getACase() - | - last(switch.getExpr(), n, completion) and result = first(firstExecutedCase) - ) + // ...and then to any case up to and including the first pattern case, if any. + last(switch.getExpr(), n, completion) and result = first(getAFirstSwitchCase(switch)) or // Statements within a switch body execute sequentially. // Note this includes non-rule case statements and the successful pattern match successor @@ -1265,14 +1309,8 @@ private module ControlFlowGraphImpl { // 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. - exists(SwitchCase firstExecutedCase | - if switch.getACase() instanceof PatternCase - then firstExecutedCase = switch.getCase(0) - else firstExecutedCase = switch.getACase() - | - last(switch.getExpr(), n, completion) and result = first(firstExecutedCase) - ) + // ...and then to any case up to and including the first pattern case, if any. + last(switch.getExpr(), n, completion) and result = first(getAFirstSwitchCase(switch)) or // Statements within a switch body execute sequentially. // Note this includes non-rule case statements and the successful pattern match successor @@ -1310,7 +1348,7 @@ private module ControlFlowGraphImpl { n = case and ( completion = basicBooleanCompletion(false) and - nextSwitchCase(case, result) + result = getASuccessorSwitchCase(case) or completion = basicBooleanCompletion(true) and result = case.getDecl() @@ -1330,7 +1368,7 @@ private module ControlFlowGraphImpl { or last(guard, n, completion) and completion = basicBooleanCompletion(false) and - nextSwitchCase(case, result) + result = getASuccessorSwitchCase(case) ) ) or diff --git a/java/ql/test/library-tests/pattern-switch/cfg/Test.java b/java/ql/test/library-tests/pattern-switch/cfg/Test.java index 80d0da34d13..a681bda0f48 100644 --- a/java/ql/test/library-tests/pattern-switch/cfg/Test.java +++ b/java/ql/test/library-tests/pattern-switch/cfg/Test.java @@ -70,6 +70,20 @@ public class Test { 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; + } + } } diff --git a/java/ql/test/library-tests/pattern-switch/cfg/test.expected b/java/ql/test/library-tests/pattern-switch/cfg/test.expected index 008b2693ae0..ba6d82ef2be 100644 --- a/java/ql/test/library-tests/pattern-switch/cfg/test.expected +++ b/java/ql/test/library-tests/pattern-switch/cfg/test.expected @@ -1,6 +1,6 @@ | 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:55:3 | { ... } | Test.java:5:6:5:19 | switch (...) | +| Test.java:3:41:87: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 T t ... | | Test.java:6:8:6:23 | case T t ... | Test.java:6:20:6:20 | s | @@ -123,7 +123,7 @@ | Test.java:50:27:50:41 | ... == ... | Test.java:51:8:51:44 | case T t ... | | 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:3:22:3:25 | test | +| Test.java:50:46:50:75 | println(...) | Test.java:55:6:55:26 | switch (...) | | Test.java:50:46:50:76 | ; | 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 T t ... | Test.java:51:20:51:20 | s | @@ -135,8 +135,86 @@ | 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:3:22:3:25 | test | +| Test.java:51:46:51:75 | println(...) | Test.java:55:6:55:26 | switch (...) | | Test.java:51:46:51:76 | ; | 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:3:22:3:25 | test | +| 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 T t ... | +| 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 | ; | +| 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 | ; | 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 | ; | +| 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 | ; | 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 T t ... | Test.java:61:20:61:20 | s | +| Test.java:61:8:61:42 | case T t ... | Test.java:63:8:63:21 | case ... | +| Test.java:61:8:61:42 | case T t ... | Test.java:66:8:66:22 | case ... | +| Test.java:61:8:61:42 | case T t ... | 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 | ; | +| 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 | ; | 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 | ; | +| 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 | ; | 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 | ; | +| 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 | ; | 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 | ; | +| 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 | ; | 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 T t ... | +| Test.java:73:13:73:17 | thing | Test.java:77:8:77:17 | case ... | +| Test.java:74:8:74:21 | case T t ... | Test.java:74:20:74:20 | s | +| Test.java:74:8:74:21 | case T t ... | Test.java:80:8:80:22 | case T t ... | +| Test.java:74:20:74:20 | s | Test.java:75:10:75:31 | ; | +| 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 | ; | 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:3:22:3:25 | test | +| Test.java:77:8:77:17 | case ... | Test.java:78:10:78:41 | ; | +| 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 | ; | 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:3:22:3:25 | test | +| Test.java:80:8:80:22 | case T t ... | Test.java:80:21:80:21 | i | +| Test.java:80:8:80:22 | case T t ... | Test.java:83:8:83:15 | default | +| Test.java:80:21:80:21 | i | Test.java:81:10:81:47 | ; | +| 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 | ; | 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:3:22:3:25 | test | +| Test.java:83:8:83:15 | default | Test.java:84:10:84:15 | break | +| Test.java:84:10:84:15 | break | Test.java:3:22:3:25 | test | From 8406ee7ed561706dbf56a5ca5260c9b6ec05f9a8 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 1 Nov 2023 09:58:39 +0000 Subject: [PATCH 019/115] Add test for a pattern-switch guard acting as a data-flow guard --- .../pattern-switch/dfg/test.expected | 1 + .../test/library-tests/pattern-switch/dfg/test.ql | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/java/ql/test/library-tests/pattern-switch/dfg/test.expected b/java/ql/test/library-tests/pattern-switch/dfg/test.expected index 87a466d3515..fbf31076bb8 100644 --- a/java/ql/test/library-tests/pattern-switch/dfg/test.expected +++ b/java/ql/test/library-tests/pattern-switch/dfg/test.expected @@ -1,3 +1,4 @@ +| GuardTest.java:6:27:6:34 | o | GuardTest.java:11:14:11:14 | s | | 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(...) | diff --git a/java/ql/test/library-tests/pattern-switch/dfg/test.ql b/java/ql/test/library-tests/pattern-switch/dfg/test.ql index 076821827b1..958eac76c9a 100644 --- a/java/ql/test/library-tests/pattern-switch/dfg/test.ql +++ b/java/ql/test/library-tests/pattern-switch/dfg/test.ql @@ -1,11 +1,24 @@ 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 } + 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::getABarrierNode() + } } module Flow = DataFlow::Global; From 3cb01002dc3f744b0c0fe10cff66305822f51914 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 1 Nov 2023 11:22:46 +0000 Subject: [PATCH 020/115] Add test for usage of qualified enum constants in switch --- java/ql/test/library-tests/printAst/A.java | 9 +++ .../library-tests/printAst/PrintAst.expected | 81 ++++++++++++------- 2 files changed, 62 insertions(+), 28 deletions(-) diff --git a/java/ql/test/library-tests/printAst/A.java b/java/ql/test/library-tests/printAst/A.java index 6bce5cc771b..23b5732d26b 100644 --- a/java/ql/test/library-tests/printAst/A.java +++ b/java/ql/test/library-tests/printAst/A.java @@ -92,6 +92,15 @@ class A { 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"; + }; } } catch (RuntimeException rte) { diff --git a/java/ql/test/library-tests/printAst/PrintAst.expected b/java/ql/test/library-tests/printAst/PrintAst.expected index 43b89a366f3..436699f7e46 100644 --- a/java/ql/test/library-tests/printAst/PrintAst.expected +++ b/java/ql/test/library-tests/printAst/PrintAst.expected @@ -258,36 +258,61 @@ A.java: # 93| 1: [NullDefaultCase] case null, default # 93| -1: [StringLiteral] "It's something else" # 93| 0: [NullLiteral] null -# 97| 0: [CatchClause] catch (...) +# 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" +# 106| 0: [CatchClause] catch (...) #-----| 0: (Single Local Variable Declaration) -# 97| 0: [TypeAccess] RuntimeException -# 97| 1: [LocalVariableDeclExpr] rte -# 97| 1: [BlockStmt] { ... } -# 98| 0: [ReturnStmt] return ... -# 102| 10: [Class] E -# 106| 3: [FieldDeclaration] E A; +# 106| 0: [TypeAccess] RuntimeException +# 106| 1: [LocalVariableDeclExpr] rte +# 106| 1: [BlockStmt] { ... } +# 107| 0: [ReturnStmt] return ... +# 111| 10: [Class] E +# 115| 3: [FieldDeclaration] E A; #-----| -3: (Javadoc) -# 103| 1: [Javadoc] /** Javadoc for enum constant */ -# 104| 0: [JavadocText] Javadoc for enum constant -# 106| -1: [TypeAccess] E -# 106| 0: [ClassInstanceExpr] new E(...) -# 106| -3: [TypeAccess] E -# 107| 4: [FieldDeclaration] E B; +# 112| 1: [Javadoc] /** Javadoc for enum constant */ +# 113| 0: [JavadocText] Javadoc for enum constant +# 115| -1: [TypeAccess] E +# 115| 0: [ClassInstanceExpr] new E(...) +# 115| -3: [TypeAccess] E +# 116| 4: [FieldDeclaration] E B; #-----| -3: (Javadoc) -# 103| 1: [Javadoc] /** Javadoc for enum constant */ -# 104| 0: [JavadocText] Javadoc for enum constant -# 107| -1: [TypeAccess] E -# 107| 0: [ClassInstanceExpr] new E(...) -# 107| -3: [TypeAccess] E -# 108| 5: [FieldDeclaration] E C; +# 112| 1: [Javadoc] /** Javadoc for enum constant */ +# 113| 0: [JavadocText] Javadoc for enum constant +# 116| -1: [TypeAccess] E +# 116| 0: [ClassInstanceExpr] new E(...) +# 116| -3: [TypeAccess] E +# 117| 5: [FieldDeclaration] E C; #-----| -3: (Javadoc) -# 103| 1: [Javadoc] /** Javadoc for enum constant */ -# 104| 0: [JavadocText] Javadoc for enum constant -# 108| -1: [TypeAccess] E -# 108| 0: [ClassInstanceExpr] new E(...) -# 108| -3: [TypeAccess] E -# 114| 11: [FieldDeclaration] int i, ...; +# 112| 1: [Javadoc] /** Javadoc for enum constant */ +# 113| 0: [JavadocText] Javadoc for enum constant +# 117| -1: [TypeAccess] E +# 117| 0: [ClassInstanceExpr] new E(...) +# 117| -3: [TypeAccess] E +# 123| 11: [FieldDeclaration] int i, ...; #-----| -3: (Javadoc) -# 111| 1: [Javadoc] /** Javadoc for fields */ -# 112| 0: [JavadocText] Javadoc for fields -# 114| -1: [TypeAccess] int +# 120| 1: [Javadoc] /** Javadoc for fields */ +# 121| 0: [JavadocText] Javadoc for fields +# 123| -1: [TypeAccess] int From 293cc674948346c2b3442e481de2087c8b838a81 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 1 Nov 2023 16:04:21 +0000 Subject: [PATCH 021/115] Fix stringifying record fields --- java/ql/lib/semmle/code/java/Member.qll | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Member.qll b/java/ql/lib/semmle/code/java/Member.qll index 49c9107d5d1..7c625ae48c6 100644 --- a/java/ql/lib/semmle/code/java/Member.qll +++ b/java/ql/lib/semmle/code/java/Member.qll @@ -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" } From daccd040874cfc1093e25daba414524bda52069b Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 1 Nov 2023 16:05:53 +0000 Subject: [PATCH 022/115] Basic extraction of record patterns --- java/ql/lib/config/semmlecode.dbscheme | 1 + .../lib/semmle/code/java/ControlFlowGraph.qll | 10 +-- java/ql/lib/semmle/code/java/Dependency.qll | 2 +- java/ql/lib/semmle/code/java/Expr.qll | 7 ++ java/ql/lib/semmle/code/java/Statement.qll | 31 ++++++-- .../semmle/code/java/controlflow/Guards.qll | 2 +- .../semmle/code/java/dataflow/TypeFlow.qll | 2 +- .../lib/semmle/code/java/dispatch/ObjFlow.qll | 2 +- java/ql/test/library-tests/printAst/A.java | 7 ++ .../library-tests/printAst/PrintAst.expected | 71 +++++++++++-------- 10 files changed, 93 insertions(+), 42 deletions(-) diff --git a/java/ql/lib/config/semmlecode.dbscheme b/java/ql/lib/config/semmlecode.dbscheme index 53c0d42e216..0486bc8cd10 100644 --- a/java/ql/lib/config/semmlecode.dbscheme +++ b/java/ql/lib/config/semmlecode.dbscheme @@ -774,6 +774,7 @@ case @expr.kind of | 86 = @valueeqexpr | 87 = @valueneexpr | 88 = @propertyref +| 89 = @recordpatternexpr ; /** Holds if this `when` expression was written as an `if` expression. */ diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index 837cc576a3f..54798ee3b10 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -529,7 +529,7 @@ private module ControlFlowGraphImpl { or this instanceof LocalVariableDeclExpr and not this = any(InstanceOfExpr ioe).getLocalVariableDeclExpr() and - not this = any(PatternCase pc).getDecl() + not this = any(PatternCase pc).getPattern() or this instanceof StringTemplateExpr or @@ -971,7 +971,7 @@ private module ControlFlowGraphImpl { ( if exists(pc.getGuard()) then last(pc.getGuard(), last, BooleanCompletion(true, _)) - else last = pc.getDecl() + else last = pc.getPattern() ) and not pc.isRule() and completion = NormalCompletion() @@ -1332,7 +1332,7 @@ private module ControlFlowGraphImpl { last(case.(PatternCase).getGuard(), preBodyNode, completion) and completion = basicBooleanCompletion(true) ) else ( - preBodyNode = case.(PatternCase).getDecl() and completion = NormalCompletion() + preBodyNode = case.(PatternCase).getPattern() and completion = NormalCompletion() ) ) else ( preBodyNode = case and completion = NormalCompletion() @@ -1351,7 +1351,7 @@ private module ControlFlowGraphImpl { result = getASuccessorSwitchCase(case) or completion = basicBooleanCompletion(true) and - result = case.getDecl() + result = case.getPattern() ) ) or @@ -1362,7 +1362,7 @@ private module ControlFlowGraphImpl { exists(PatternCase case, Expr guard | guard = case.getGuard() and ( - n = case.getDecl() and + n = case.getPattern() and result = first(guard) and completion = NormalCompletion() or diff --git a/java/ql/lib/semmle/code/java/Dependency.qll b/java/ql/lib/semmle/code/java/Dependency.qll index cbf753ce54c..7c01a43e186 100644 --- a/java/ql/lib/semmle/code/java/Dependency.qll +++ b/java/ql/lib/semmle/code/java/Dependency.qll @@ -82,7 +82,7 @@ predicate depends(RefType t, RefType dep) { or // the type accessed in a pattern-switch case statement in `t`. exists(PatternCase pc | t = pc.getEnclosingCallable().getDeclaringType() | - usesType(pc.getDecl().getType(), dep) + usesType(pc.getPattern().getType(), dep) ) ) } diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index a16aac6f990..358c413807d 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -2541,3 +2541,10 @@ class NotNullExpr extends UnaryExpr, @notnullexpr { override string getAPrimaryQlClass() { result = "NotNullExpr" } } + +/** 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" } +} diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index c5002356108..467239839ad 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -507,7 +507,7 @@ class SwitchCase extends Stmt, @case { class ConstCase extends SwitchCase { ConstCase() { exists(Expr e | - e.getParent() = this and e.getIndex() >= 0 and not e instanceof LocalVariableDeclExpr + e.getParent() = this and e.getIndex() >= 0 and not e instanceof Pattern ) // 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. @@ -531,14 +531,35 @@ class ConstCase extends SwitchCase { override string getAPrimaryQlClass() { result = "ConstCase" } } +/** + * A binding or record pattern. + * + * Note binding patterns are represented as `LocalVariableDeclExpr`s. + */ +class Pattern extends Expr { + Pattern() { + (this.getParent() instanceof SwitchCase or this.getParent() instanceof InstanceOfExpr) + and + (this instanceof LocalVariableDeclExpr or this instanceof RecordPatternExpr) + } + + LocalVariableDeclExpr asBindingPattern() { + result = this + } + + RecordPatternExpr asRecordPattern() { + result = this + } +} + /** A pattern case of a `switch` statement */ class PatternCase extends SwitchCase { - LocalVariableDeclExpr patternVar; + Pattern pattern; - PatternCase() { patternVar.isNthChildOf(this, 0) } + PatternCase() { pattern.isNthChildOf(this, 0) } - /** Gets the variable declared by this pattern case. */ - LocalVariableDeclExpr getDecl() { result.isNthChildOf(this, 0) } + /** Gets this case's pattern. */ + Pattern getPattern() { result.isNthChildOf(this, 0) } /** Gets the guard applicable to this pattern case, if any. */ Expr getGuard() { result.isNthChildOf(this, -3) } diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index b11c2e91903..157bb0d9a99 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -180,7 +180,7 @@ private predicate switchCaseControls(SwitchCase sc, BasicBlock bb) { selector = sc.getSelectorExpr() and ( if sc instanceof PatternCase - then caseblock.getFirstNode() = sc.(PatternCase).getDecl().getControlFlowNode() + then caseblock.getFirstNode() = sc.(PatternCase).getPattern().getControlFlowNode() else ( caseblock.getFirstNode() = sc.getControlFlowNode() and // Check there is no fall-through edge from a previous case: diff --git a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll index 47258af0a27..19014d18752 100644 --- a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll @@ -447,7 +447,7 @@ private predicate patternCaseGuarded(Expr e, RefType t) { exists(PatternCase pc | e = getAProbableAlias([pc.getSwitch().getExpr(), pc.getSwitchExpr().getExpr()]) and guardControls_v1(pc, e.getBasicBlock(), true) and - t = pc.getDecl().getType() + t = pc.getPattern().getType() ) } diff --git a/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll b/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll index c9ae5353127..1cfc327a3a5 100644 --- a/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll +++ b/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll @@ -82,7 +82,7 @@ private predicate step(Node n1, Node n2) { ) or exists(PatternCase pc | - pc.getDecl() = def.(BaseSsaUpdate).getDefiningExpr() and + pc.getPattern() = def.(BaseSsaUpdate).getDefiningExpr() and ( pc.getSwitch().getExpr() = n1.asExpr() or diff --git a/java/ql/test/library-tests/printAst/A.java b/java/ql/test/library-tests/printAst/A.java index 23b5732d26b..219fe13c5f1 100644 --- a/java/ql/test/library-tests/printAst/A.java +++ b/java/ql/test/library-tests/printAst/A.java @@ -101,6 +101,10 @@ class 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) { @@ -122,3 +126,6 @@ class A { */ int i, j, k; } + +record Inner(String field) { } +record Middle(Inner inner) { } diff --git a/java/ql/test/library-tests/printAst/PrintAst.expected b/java/ql/test/library-tests/printAst/PrintAst.expected index 436699f7e46..62b8b44fee3 100644 --- a/java/ql/test/library-tests/printAst/PrintAst.expected +++ b/java/ql/test/library-tests/printAst/PrintAst.expected @@ -283,36 +283,51 @@ A.java: # 101| -1: [TypeAccess] E # 102| 2: [DefaultCase] default # 102| -1: [StringLiteral] "It's something else" -# 106| 0: [CatchClause] catch (...) +# 104| 11: [LocalVariableDeclStmt] var ...; +# 104| 1: [LocalVariableDeclExpr] recordPatterntest +# 104| 0: [SwitchExpr] switch (...) +# 104| -1: [VarAccess] thing +# 105| 0: [PatternCase] case T t ... +# 105| -1: [VarAccess] field +# 105| 0: [RecordPatternExpr] Middle(...) +# 105| 0: [RecordPatternExpr] Inner(...) +# 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) -# 106| 0: [TypeAccess] RuntimeException -# 106| 1: [LocalVariableDeclExpr] rte -# 106| 1: [BlockStmt] { ... } -# 107| 0: [ReturnStmt] return ... -# 111| 10: [Class] E -# 115| 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) -# 112| 1: [Javadoc] /** Javadoc for enum constant */ -# 113| 0: [JavadocText] Javadoc for enum constant -# 115| -1: [TypeAccess] E -# 115| 0: [ClassInstanceExpr] new E(...) -# 115| -3: [TypeAccess] E -# 116| 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) -# 112| 1: [Javadoc] /** Javadoc for enum constant */ -# 113| 0: [JavadocText] Javadoc for enum constant -# 116| -1: [TypeAccess] E -# 116| 0: [ClassInstanceExpr] new E(...) -# 116| -3: [TypeAccess] E -# 117| 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) -# 112| 1: [Javadoc] /** Javadoc for enum constant */ -# 113| 0: [JavadocText] Javadoc for enum constant -# 117| -1: [TypeAccess] E -# 117| 0: [ClassInstanceExpr] new E(...) -# 117| -3: [TypeAccess] E -# 123| 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) -# 120| 1: [Javadoc] /** Javadoc for fields */ -# 121| 0: [JavadocText] Javadoc for fields -# 123| -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; From 936c0206ea4849b37511b6897ce101c2b7555745 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 1 Nov 2023 18:21:11 +0000 Subject: [PATCH 023/115] Adapt ancillary analyses to record patterns --- java/ql/lib/semmle/code/java/Dependency.qll | 4 ++-- java/ql/lib/semmle/code/java/DependencyCounts.qll | 2 +- java/ql/lib/semmle/code/java/controlflow/Guards.qll | 2 +- java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Dependency.qll b/java/ql/lib/semmle/code/java/Dependency.qll index 7c01a43e186..5378569dd67 100644 --- a/java/ql/lib/semmle/code/java/Dependency.qll +++ b/java/ql/lib/semmle/code/java/Dependency.qll @@ -80,9 +80,9 @@ predicate depends(RefType t, RefType dep) { usesType(ioe.getCheckedType(), dep) ) or - // the type accessed in a pattern-switch case statement in `t`. + // A type accessed in a pattern-switch case statement in `t`. exists(PatternCase pc | t = pc.getEnclosingCallable().getDeclaringType() | - usesType(pc.getPattern().getType(), dep) + usesType(pc.getPattern().getAChildExpr*().getType(), dep) ) ) } diff --git a/java/ql/lib/semmle/code/java/DependencyCounts.qll b/java/ql/lib/semmle/code/java/DependencyCounts.qll index 788c090bd1e..05a7be6e8f7 100644 --- a/java/ql/lib/semmle/code/java/DependencyCounts.qll +++ b/java/ql/lib/semmle/code/java/DependencyCounts.qll @@ -105,7 +105,7 @@ predicate numDepends(RefType t, RefType dep, int value) { or // the type accessed in a pattern-switch case statement in `t`. exists(PatternCase pc | elem = pc and t = pc.getEnclosingCallable().getDeclaringType() | - usesType(pc.getDecl().getType(), dep) + usesType(pc.getPattern().getAChildExpr*().getType(), dep) ) ) } diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index 157bb0d9a99..b0e1d40ffcc 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -180,7 +180,7 @@ private predicate switchCaseControls(SwitchCase sc, BasicBlock bb) { selector = sc.getSelectorExpr() and ( if sc instanceof PatternCase - then caseblock.getFirstNode() = sc.(PatternCase).getPattern().getControlFlowNode() + then caseblock.getANode() = sc.(PatternCase).getPattern().getControlFlowNode() else ( caseblock.getFirstNode() = sc.getControlFlowNode() and // Check there is no fall-through edge from a previous case: diff --git a/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll b/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll index 1cfc327a3a5..b06e9a2097f 100644 --- a/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll +++ b/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll @@ -82,7 +82,7 @@ private predicate step(Node n1, Node n2) { ) or exists(PatternCase pc | - pc.getPattern() = def.(BaseSsaUpdate).getDefiningExpr() and + pc.getPattern().asBindingPattern() = def.(BaseSsaUpdate).getDefiningExpr() and ( pc.getSwitch().getExpr() = n1.asExpr() or From 556feb31f08323471b6dd1d18f397aef16daf32a Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 1 Nov 2023 18:21:54 +0000 Subject: [PATCH 024/115] Autoformat --- java/ql/lib/semmle/code/java/Statement.qll | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index 467239839ad..453decd8323 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -413,9 +413,7 @@ class SwitchStmt extends Stmt, @switchstmt { Expr getExpr() { result.getParent() = this } /** Holds if this switch has a case handling a null literal. */ - predicate hasNullCase() { - this.getAConstCase().getValue(_) instanceof NullLiteral - } + predicate hasNullCase() { this.getAConstCase().getValue(_) instanceof NullLiteral } override string pp() { result = "switch (...)" } @@ -506,12 +504,10 @@ class SwitchCase extends Stmt, @case { */ class ConstCase extends SwitchCase { ConstCase() { - exists(Expr e | - e.getParent() = this and e.getIndex() >= 0 and not e instanceof Pattern - ) + exists(Expr e | e.getParent() = this and e.getIndex() >= 0 and not e instanceof Pattern) 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. - and not isNullDefaultCase(this) + not isNullDefaultCase(this) } /** Gets the `case` constant at index 0. */ @@ -543,13 +539,9 @@ class Pattern extends Expr { (this instanceof LocalVariableDeclExpr or this instanceof RecordPatternExpr) } - LocalVariableDeclExpr asBindingPattern() { - result = this - } + LocalVariableDeclExpr asBindingPattern() { result = this } - RecordPatternExpr asRecordPattern() { - result = this - } + RecordPatternExpr asRecordPattern() { result = this } } /** A pattern case of a `switch` statement */ From 05addde9570ea00651681a268292bbd3b5d13426 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 1 Nov 2023 18:22:25 +0000 Subject: [PATCH 025/115] Adapt control-flow graph to record patterns --- .../lib/semmle/code/java/ControlFlowGraph.qll | 24 +++++---- java/ql/lib/semmle/code/java/Expr.qll | 4 ++ java/ql/lib/semmle/code/java/Statement.qll | 7 ++- .../pattern-switch/cfg/Test.java | 17 ++++++ .../pattern-switch/cfg/test.expected | 54 +++++++++++++++++-- 5 files changed, 89 insertions(+), 17 deletions(-) diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index 54798ee3b10..c3c6d981e57 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -528,8 +528,7 @@ private module ControlFlowGraphImpl { this instanceof NotInstanceOfExpr or this instanceof LocalVariableDeclExpr and - not this = any(InstanceOfExpr ioe).getLocalVariableDeclExpr() and - not this = any(PatternCase pc).getPattern() + not this = any(InstanceOfExpr ioe).getLocalVariableDeclExpr() or this instanceof StringTemplateExpr or @@ -559,6 +558,8 @@ private module ControlFlowGraphImpl { not this.(SwitchCase).isRule() and not this instanceof PatternCase or + this instanceof RecordPatternExpr + or this instanceof EmptyStmt or this instanceof LocalTypeDeclStmt @@ -635,6 +636,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. */ @@ -963,15 +966,15 @@ private module ControlFlowGraphImpl { ) ) or - // The normal last node in a non-rule pattern case is its variable declaration, or the successful - // matching of its guard if it has one. + // The normal last node in a non-rule pattern case is the last of its variable declaration(s), + // or the successful matching of its guard if it has one. // Note that either rule or non-rule pattern cases can end with pattern match failure, whereupon // they branch to the next candidate pattern. This is accounted for in the `succ` relation. exists(PatternCase pc | n = pc | ( if exists(pc.getGuard()) then last(pc.getGuard(), last, BooleanCompletion(true, _)) - else last = pc.getPattern() + else last(pc.getPattern(), last, NormalCompletion()) ) and not pc.isRule() and completion = NormalCompletion() @@ -1332,7 +1335,8 @@ private module ControlFlowGraphImpl { last(case.(PatternCase).getGuard(), preBodyNode, completion) and completion = basicBooleanCompletion(true) ) else ( - preBodyNode = case.(PatternCase).getPattern() and completion = NormalCompletion() + last(case.(PatternCase).getPattern(), preBodyNode, completion) and + completion = NormalCompletion() ) ) else ( preBodyNode = case and completion = NormalCompletion() @@ -1343,7 +1347,7 @@ private module ControlFlowGraphImpl { n = preBodyNode and result = first(case.getRuleStatement()) ) or - // A pattern case conducts a type test, then branches to the next case or the assignment. + // A pattern case conducts a type test, then branches to the next case or the pattern assignment(s). exists(PatternCase case | n = case and ( @@ -1351,18 +1355,18 @@ private module ControlFlowGraphImpl { result = getASuccessorSwitchCase(case) or completion = basicBooleanCompletion(true) and - result = case.getPattern() + result = first(case.getPattern()) ) ) or - // A pattern case with a guard evaluates that guard after declaring its pattern variable, + // A pattern case with a guard evaluates that guard after declaring its pattern variable(s), // and thereafter if the guard doesn't match will branch to the next case. // The case of a matching guard is accounted for in the case-with-rule logic above, or for // non-rule case statements in `last`. exists(PatternCase case, Expr guard | guard = case.getGuard() and ( - n = case.getPattern() and + last(case.getPattern(), n, NormalCompletion()) and result = first(guard) and completion = NormalCompletion() or diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index 358c413807d..b9e9ade81e0 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -2547,4 +2547,8 @@ class RecordPatternExpr extends Expr, @recordpatternexpr { override string toString() { result = this.getType().toString() + "(...)" } override string getAPrimaryQlClass() { result = "RecordPatternExpr" } + + Pattern getSubPattern(int i) { + result.isNthChildOf(this, i) + } } diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index 453decd8323..f274a9a4bfc 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -534,8 +534,11 @@ class ConstCase extends SwitchCase { */ class Pattern extends Expr { Pattern() { - (this.getParent() instanceof SwitchCase or this.getParent() instanceof InstanceOfExpr) - and + ( + this.getParent() instanceof SwitchCase or + this.getParent() instanceof InstanceOfExpr or + this.getParent() instanceof Pattern + ) and (this instanceof LocalVariableDeclExpr or this instanceof RecordPatternExpr) } diff --git a/java/ql/test/library-tests/pattern-switch/cfg/Test.java b/java/ql/test/library-tests/pattern-switch/cfg/Test.java index a681bda0f48..9894d7fd197 100644 --- a/java/ql/test/library-tests/pattern-switch/cfg/Test.java +++ b/java/ql/test/library-tests/pattern-switch/cfg/Test.java @@ -84,6 +84,23 @@ public class Test { 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) { } diff --git a/java/ql/test/library-tests/pattern-switch/cfg/test.expected b/java/ql/test/library-tests/pattern-switch/cfg/test.expected index ba6d82ef2be..d5337fcd0b3 100644 --- a/java/ql/test/library-tests/pattern-switch/cfg/test.expected +++ b/java/ql/test/library-tests/pattern-switch/cfg/test.expected @@ -1,6 +1,6 @@ | 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:87:3 | { ... } | Test.java:5:6:5:19 | switch (...) | +| 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 T t ... | | Test.java:6:8:6:23 | case T t ... | Test.java:6:20:6:20 | s | @@ -199,13 +199,13 @@ | Test.java:75:10:75:30 | println(...) | Test.java:76:10:76:15 | break | | Test.java:75:10:75:31 | ; | 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:3:22:3:25 | test | +| 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 | ; | | 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 | ; | 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:3:22:3:25 | test | +| Test.java:79:10:79:15 | break | Test.java:87:6:87:18 | switch (...) | | Test.java:80:8:80:22 | case T t ... | Test.java:80:21:80:21 | i | | Test.java:80:8:80:22 | case T t ... | Test.java:83:8:83:15 | default | | Test.java:80:21:80:21 | i | Test.java:81:10:81:47 | ; | @@ -215,6 +215,50 @@ | 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:3:22:3:25 | test | +| 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:3:22:3:25 | test | +| 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 T t ... | +| Test.java:88:8:88:43 | case T t ... | Test.java:88:21:88:21 | x | +| Test.java:88:8:88:43 | case T t ... | 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 T t ... | +| Test.java:95:8:95:38 | case T t ... | Test.java:95:21:95:21 | x | +| Test.java:95:8:95:38 | case T t ... | 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 | ; | +| Test.java:105:8:105:8 | ...=... | Test.java:105:8:105:8 | A | +| Test.java:105:8:105:8 | ; | Test.java:105:8:105:8 | this | +| Test.java:105:8:105:8 | ; | 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 | ; | +| 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 | ; | +| Test.java:106:8:106:8 | ...=... | Test.java:106:8:106:8 | B | +| Test.java:106:8:106:8 | ; | Test.java:106:8:106:8 | this | +| Test.java:106:8:106:8 | ; | 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 | ; | +| 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(...) | From 20b97af02f40a798cd19b9e397fc149407db3324 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 1 Nov 2023 20:33:09 +0000 Subject: [PATCH 026/115] Implement dataflow for record patterns Strategy: there is now a regular flow step from an instance-of LHS / switch expr to the pattern, 0 or more read steps corresponding to record pattern destructors, and then finally a normal SSA def/use step connecting the binding patterns to their first uses. --- java/ql/lib/config/semmlecode.dbscheme | 4 ++++ java/ql/lib/semmle/code/java/Expr.qll | 6 +++++- java/ql/lib/semmle/code/java/Type.qll | 7 +++++++ .../java/dataflow/internal/DataFlowPrivate.qll | 18 ++++++++++++++++++ .../java/dataflow/internal/DataFlowUtil.qll | 15 +++++++++++++-- .../pattern-switch/dfg/test.expected | 8 ++++++++ 6 files changed, 55 insertions(+), 3 deletions(-) diff --git a/java/ql/lib/config/semmlecode.dbscheme b/java/ql/lib/config/semmlecode.dbscheme index 0486bc8cd10..dee651b58d1 100644 --- a/java/ql/lib/config/semmlecode.dbscheme +++ b/java/ql/lib/config/semmlecode.dbscheme @@ -576,6 +576,10 @@ lambdaKind( int bodyKind: int ref ); +isCanonicalConstr( + int constructorid: @constructor ref +); + arrays( unique int id: @array, string nodeName: string ref, diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index b9e9ade81e0..db3e8a6db3c 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1639,7 +1639,11 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr { /** Gets the name of the variable declared by this local variable declaration expression. */ string getName() { result = this.getVariable().getName() } - /** Gets the switch statement or expression whose pattern declares this identifier, if any. */ + /** + * Gets the switch statement or expression whose pattern declares this identifier, if any. + * + * Note this only applies to a direct binding pattern, such as `case T t`, not a record pattern. + */ StmtParent getAssociatedSwitch() { result = this.getParent().(PatternCase).getParent() } /** Holds if this is a declaration stemming from a pattern switch case. */ diff --git a/java/ql/lib/semmle/code/java/Type.qll b/java/ql/lib/semmle/code/java/Type.qll index afe78d522f2..5976bc12f00 100644 --- a/java/ql/lib/semmle/code/java/Type.qll +++ b/java/ql/lib/semmle/code/java/Type.qll @@ -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. */ diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll index 79e507dd598..1091c3d4278 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll @@ -232,6 +232,17 @@ predicate storeStep(Node node1, ContentSet f, Node node2) { pragma[only_bind_out](node2.getEnclosingCallable()) } +private Field getLexicallyOrderedRecordField(Record r, int idx) { + result = + rank[idx + 1](Field f, int i, Parameter p | + f = r.getAField() and + p = r.getCanonicalConstructor().getParameter(i) and + f.getName() = p.getName() + | + 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 +267,13 @@ predicate readStep(Node node1, ContentSet f, Node node2) { node2.asExpr() = get ) or + exists(RecordPatternExpr rpe, Pattern 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) diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll index 3e7ca6daa12..36bff2688a3 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll @@ -190,6 +190,18 @@ predicate simpleAstFlowStep(Expr e1, Expr e2) { e2 = any(NotNullExpr nne | e1 = nne.getExpr()) or e2.(WhenExpr).getBranch(_).getAResult() = e1 + or + exists(SwitchExpr se | + e1 = se.getExpr() and e2 = se.getACase().(PatternCase).getPattern() + ) + or + exists(SwitchStmt ss | + e1 = ss.getExpr() and e2 = ss.getACase().(PatternCase).getPattern() + ) + or + exists(InstanceOfExpr ioe | + e1 = ioe.getExpr() and e2 = ioe.getLocalVariableDeclExpr() + ) } private predicate simpleLocalFlowStep0(Node node1, Node node2) { @@ -198,8 +210,7 @@ private predicate simpleLocalFlowStep0(Node node1, Node node2) { exists(SsaExplicitUpdate upd | upd.getDefiningExpr().(VariableAssign).getSource() = node1.asExpr() or upd.getDefiningExpr().(AssignOp) = node1.asExpr() or - upd.getDefiningExpr().(LocalVariableDeclExpr).getAssociatedSwitch().(SwitchStmt).getExpr() = node1.asExpr() or - upd.getDefiningExpr().(LocalVariableDeclExpr).getAssociatedSwitch().(SwitchExpr).getExpr() = node1.asExpr() + upd.getDefiningExpr().(Pattern).asBindingPattern() = node1.asExpr() | node2.asExpr() = upd.getAFirstUse() and not capturedVariableRead(node2) diff --git a/java/ql/test/library-tests/pattern-switch/dfg/test.expected b/java/ql/test/library-tests/pattern-switch/dfg/test.expected index fbf31076bb8..29e53bbd346 100644 --- a/java/ql/test/library-tests/pattern-switch/dfg/test.expected +++ b/java/ql/test/library-tests/pattern-switch/dfg/test.expected @@ -1,4 +1,12 @@ | 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(...) | From f037030c264ba32524bd556d4487dd0c21c538ba Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 2 Nov 2023 13:45:58 +0000 Subject: [PATCH 027/115] Adapt instanceof CFG and DFG to general patterns --- .../lib/semmle/code/java/ControlFlowGraph.qll | 7 +++---- java/ql/lib/semmle/code/java/Expr.qll | 18 ++++++++++++------ .../java/dataflow/internal/DataFlowUtil.qll | 2 +- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index c3c6d981e57..60b616d2b4f 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -527,8 +527,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 @@ -832,7 +831,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. @@ -1128,7 +1127,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 diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index db3e8a6db3c..991d82da16e 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1559,20 +1559,26 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr { 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) + result.isNthChildOf(this, 0) } + /** + * Gets the pattern of an `x instanceof T pattern` expression, if any. + */ + Pattern 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. */ Expr getTypeName() { result.isNthChildOf(this, 1) } diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll index 36bff2688a3..0447b8e92ce 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll @@ -200,7 +200,7 @@ predicate simpleAstFlowStep(Expr e1, Expr e2) { ) or exists(InstanceOfExpr ioe | - e1 = ioe.getExpr() and e2 = ioe.getLocalVariableDeclExpr() + e1 = ioe.getExpr() and e2 = ioe.getPattern() ) } From 5b734fe93772da68e46264de7b3c5f3f959de684 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 2 Nov 2023 13:48:34 +0000 Subject: [PATCH 028/115] Pretty-print AST: handle instanceof with record pattern --- java/ql/lib/semmle/code/java/PrettyPrintAst.qll | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll index ca08643190e..83a6385fbc2 100644 --- a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll +++ b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll @@ -385,7 +385,12 @@ private class PpInstanceOfExpr extends PpAst, InstanceOfExpr { or i = 3 and result = " " and this.isPattern() or - i = 4 and result = this.getLocalVariableDeclExpr().getName() + i = 4 and + ( + result = this.getPattern().asBindingPattern().getName() + or + result = this.getPattern().asRecordPattern().toString() + ) } override PpAst getChild(int i) { From e41da3b10ae67dc8b9a8f5c843aa91372acc4233 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 2 Nov 2023 13:50:13 +0000 Subject: [PATCH 029/115] Add missing test Java files --- .../pattern-switch/dfg/GuardTest.java | 30 ++++++++++++ .../pattern-switch/dfg/RecordTest.java | 48 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 java/ql/test/library-tests/pattern-switch/dfg/GuardTest.java create mode 100644 java/ql/test/library-tests/pattern-switch/dfg/RecordTest.java diff --git a/java/ql/test/library-tests/pattern-switch/dfg/GuardTest.java b/java/ql/test/library-tests/pattern-switch/dfg/GuardTest.java new file mode 100644 index 00000000000..d0550e4b695 --- /dev/null +++ b/java/ql/test/library-tests/pattern-switch/dfg/GuardTest.java @@ -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; + + } + + } + +} diff --git a/java/ql/test/library-tests/pattern-switch/dfg/RecordTest.java b/java/ql/test/library-tests/pattern-switch/dfg/RecordTest.java new file mode 100644 index 00000000000..608e173dd57 --- /dev/null +++ b/java/ql/test/library-tests/pattern-switch/dfg/RecordTest.java @@ -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"; + }; + + } + +} From 32416f0fdc7f00b9ceb1f0baddd7bf60663a7296 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 2 Nov 2023 13:50:37 +0000 Subject: [PATCH 030/115] Add test for record-pattern instanceof --- .../pattern-instanceof/PrintAst.expected | 75 +++++++++++++++++++ .../pattern-instanceof/PrintAst.qlref | 1 + .../pattern-instanceof/Test.java | 28 +++++++ .../pattern-instanceof/cfg.expected | 72 ++++++++++++++++++ .../library-tests/pattern-instanceof/cfg.ql | 5 ++ .../pattern-instanceof/dfg.expected | 2 + .../library-tests/pattern-instanceof/dfg.ql | 29 +++++++ .../library-tests/pattern-instanceof/options | 1 + 8 files changed, 213 insertions(+) create mode 100644 java/ql/test/library-tests/pattern-instanceof/PrintAst.expected create mode 100644 java/ql/test/library-tests/pattern-instanceof/PrintAst.qlref create mode 100644 java/ql/test/library-tests/pattern-instanceof/Test.java create mode 100644 java/ql/test/library-tests/pattern-instanceof/cfg.expected create mode 100644 java/ql/test/library-tests/pattern-instanceof/cfg.ql create mode 100644 java/ql/test/library-tests/pattern-instanceof/dfg.expected create mode 100644 java/ql/test/library-tests/pattern-instanceof/dfg.ql create mode 100644 java/ql/test/library-tests/pattern-instanceof/options diff --git a/java/ql/test/library-tests/pattern-instanceof/PrintAst.expected b/java/ql/test/library-tests/pattern-instanceof/PrintAst.expected new file mode 100644 index 00000000000..f01bbc602d3 --- /dev/null +++ b/java/ql/test/library-tests/pattern-instanceof/PrintAst.expected @@ -0,0 +1,75 @@ +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] ; +# 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| 0: [RecordPatternExpr] Inner(...) +# 14| 0: [LocalVariableDeclExpr] tainted +# 14| 1: [LocalVariableDeclExpr] notTainted +# 14| 1: [LocalVariableDeclExpr] alsoNotTainted +# 14| 1: [BlockStmt] { ... } +# 15| 0: [ExprStmt] ; +# 15| 0: [MethodCall] sink(...) +# 15| 0: [VarAccess] tainted +# 16| 1: [ExprStmt] ; +# 16| 0: [MethodCall] sink(...) +# 16| 0: [VarAccess] notTainted +# 17| 2: [ExprStmt] ; +# 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; diff --git a/java/ql/test/library-tests/pattern-instanceof/PrintAst.qlref b/java/ql/test/library-tests/pattern-instanceof/PrintAst.qlref new file mode 100644 index 00000000000..c7fd5faf239 --- /dev/null +++ b/java/ql/test/library-tests/pattern-instanceof/PrintAst.qlref @@ -0,0 +1 @@ +semmle/code/java/PrintAst.ql \ No newline at end of file diff --git a/java/ql/test/library-tests/pattern-instanceof/Test.java b/java/ql/test/library-tests/pattern-instanceof/Test.java new file mode 100644 index 00000000000..1cff6cf254f --- /dev/null +++ b/java/ql/test/library-tests/pattern-instanceof/Test.java @@ -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) { } diff --git a/java/ql/test/library-tests/pattern-instanceof/cfg.expected b/java/ql/test/library-tests/pattern-instanceof/cfg.expected new file mode 100644 index 00000000000..29de1e4a3a8 --- /dev/null +++ b/java/ql/test/library-tests/pattern-instanceof/cfg.expected @@ -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 | ; | +| Test.java:11:7:11:13 | sink(...) | Test.java:14:5:14:92 | if (...) | +| Test.java:11:7:11:14 | ; | 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 | ; | +| Test.java:15:7:15:19 | sink(...) | Test.java:16:7:16:23 | ; | +| Test.java:15:7:15:20 | ; | 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 | ; | +| Test.java:16:7:16:23 | ; | 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 | ; | 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 | ; | +| Test.java:27:8:27:12 | ...=... | Test.java:27:8:27:12 | Outer | +| Test.java:27:8:27:12 | ; | Test.java:27:8:27:12 | this | +| Test.java:27:8:27:12 | ; | 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 | ; | +| 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 | ; | +| Test.java:28:8:28:12 | ...=... | Test.java:28:8:28:12 | Inner | +| Test.java:28:8:28:12 | ; | Test.java:28:8:28:12 | this | +| 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 | ...=... | +| Test.java:28:8:28:12 | super(...) | Test.java:28:8:28:12 | ; | +| 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(...) | diff --git a/java/ql/test/library-tests/pattern-instanceof/cfg.ql b/java/ql/test/library-tests/pattern-instanceof/cfg.ql new file mode 100644 index 00000000000..0b07e8c4708 --- /dev/null +++ b/java/ql/test/library-tests/pattern-instanceof/cfg.ql @@ -0,0 +1,5 @@ +import java + +from ControlFlowNode cn +where cn.getFile().getBaseName() = "Test.java" +select cn, cn.getASuccessor() diff --git a/java/ql/test/library-tests/pattern-instanceof/dfg.expected b/java/ql/test/library-tests/pattern-instanceof/dfg.expected new file mode 100644 index 00000000000..db1abed3fa6 --- /dev/null +++ b/java/ql/test/library-tests/pattern-instanceof/dfg.expected @@ -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 | diff --git a/java/ql/test/library-tests/pattern-instanceof/dfg.ql b/java/ql/test/library-tests/pattern-instanceof/dfg.ql new file mode 100644 index 00000000000..7bd55b00ab5 --- /dev/null +++ b/java/ql/test/library-tests/pattern-instanceof/dfg.ql @@ -0,0 +1,29 @@ +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::getABarrierNode() + } +} + +module Flow = DataFlow::Global; + +from DataFlow::Node source, DataFlow::Node sink +where Flow::flow(source, sink) +select source, sink + diff --git a/java/ql/test/library-tests/pattern-instanceof/options b/java/ql/test/library-tests/pattern-instanceof/options new file mode 100644 index 00000000000..a0d1b7e7002 --- /dev/null +++ b/java/ql/test/library-tests/pattern-instanceof/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args --release 21 From 330a5b8c6c8da7bb929d938b4a01eb9e29520e2e Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 2 Nov 2023 13:57:03 +0000 Subject: [PATCH 031/115] autoformat ql --- java/ql/lib/semmle/code/java/ControlFlowGraph.qll | 7 +++++-- java/ql/lib/semmle/code/java/Expr.qll | 8 ++------ java/ql/lib/semmle/code/java/PrettyPrintAst.qll | 4 +++- .../code/java/dataflow/internal/DataFlowUtil.qll | 12 +++--------- java/ql/test/library-tests/pattern-instanceof/dfg.ql | 10 ++++++---- .../ql/test/library-tests/pattern-switch/dfg/test.ql | 10 ++++++---- 6 files changed, 25 insertions(+), 26 deletions(-) diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index 60b616d2b4f..fc443fbb06f 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -447,14 +447,17 @@ private module ControlFlowGraphImpl { * Gets the `i`th `PatternCase` defined on `switch`, if one exists. */ private PatternCase getPatternCase(StmtParent switch, int i) { - result = rank[i + 1](PatternCase pc, int caseIdx | pc = getCase(switch, caseIdx) | pc order by caseIdx asc) + result = + rank[i + 1](PatternCase pc, int caseIdx | pc = getCase(switch, caseIdx) | pc order by caseIdx) } /** * Gets the PatternCase after pc, if one exists. */ private PatternCase getNextPatternCase(PatternCase pc) { - exists(int idx, StmtParent switch | pc = getPatternCase(switch, idx) and result = getPatternCase(switch, idx + 1)) + exists(int idx, StmtParent switch | + pc = getPatternCase(switch, idx) and result = getPatternCase(switch, idx + 1) + ) } /** diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index 991d82da16e..16dbf84c556 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1558,9 +1558,7 @@ 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() { - result.isNthChildOf(this, 0) - } + Expr getExpr() { result.isNthChildOf(this, 0) } /** * Gets the pattern of an `x instanceof T pattern` expression, if any. @@ -2558,7 +2556,5 @@ class RecordPatternExpr extends Expr, @recordpatternexpr { override string getAPrimaryQlClass() { result = "RecordPatternExpr" } - Pattern getSubPattern(int i) { - result.isNthChildOf(this, i) - } + Pattern getSubPattern(int i) { result.isNthChildOf(this, i) } } diff --git a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll index 83a6385fbc2..0e92bcc4407 100644 --- a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll +++ b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll @@ -770,7 +770,9 @@ private class PpSwitchCase extends PpAst, SwitchCase { private int lastConstCaseValueIndex() { result = - 1 + 2 * (max(int j | j = 0 or exists(this.(ConstCase).getValue(j))) + this.getCaseDefaultOffset()) + 1 + + 2 * + (max(int j | j = 0 or exists(this.(ConstCase).getValue(j))) + this.getCaseDefaultOffset()) } override PpAst getChild(int i) { diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll index 0447b8e92ce..d587070acaf 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll @@ -191,17 +191,11 @@ predicate simpleAstFlowStep(Expr e1, Expr e2) { or e2.(WhenExpr).getBranch(_).getAResult() = e1 or - exists(SwitchExpr se | - e1 = se.getExpr() and e2 = se.getACase().(PatternCase).getPattern() - ) + exists(SwitchExpr se | e1 = se.getExpr() and e2 = se.getACase().(PatternCase).getPattern()) or - exists(SwitchStmt ss | - e1 = ss.getExpr() and e2 = ss.getACase().(PatternCase).getPattern() - ) + exists(SwitchStmt ss | e1 = ss.getExpr() and e2 = ss.getACase().(PatternCase).getPattern()) or - exists(InstanceOfExpr ioe | - e1 = ioe.getExpr() and e2 = ioe.getPattern() - ) + exists(InstanceOfExpr ioe | e1 = ioe.getExpr() and e2 = ioe.getPattern()) } private predicate simpleLocalFlowStep0(Node node1, Node node2) { diff --git a/java/ql/test/library-tests/pattern-instanceof/dfg.ql b/java/ql/test/library-tests/pattern-instanceof/dfg.ql index 7bd55b00ab5..8c4a082dd33 100644 --- a/java/ql/test/library-tests/pattern-instanceof/dfg.ql +++ b/java/ql/test/library-tests/pattern-instanceof/dfg.ql @@ -1,5 +1,4 @@ import java - import semmle.code.java.controlflow.Guards import semmle.code.java.dataflow.DataFlow @@ -12,9 +11,13 @@ private predicate isSafe(Guard g, Expr checked, boolean branch) { } module TestConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { source.asExpr() = any(MethodCall mc | mc.getMethod().getName() = "source") } + 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 isSink(DataFlow::Node sink) { + sink.asExpr() = any(MethodCall mc | mc.getMethod().getName() = "sink").getAnArgument() + } predicate isBarrier(DataFlow::Node node) { node = DataFlow::BarrierGuard::getABarrierNode() @@ -26,4 +29,3 @@ module Flow = DataFlow::Global; from DataFlow::Node source, DataFlow::Node sink where Flow::flow(source, sink) select source, sink - diff --git a/java/ql/test/library-tests/pattern-switch/dfg/test.ql b/java/ql/test/library-tests/pattern-switch/dfg/test.ql index 958eac76c9a..3e76b82f221 100644 --- a/java/ql/test/library-tests/pattern-switch/dfg/test.ql +++ b/java/ql/test/library-tests/pattern-switch/dfg/test.ql @@ -1,5 +1,4 @@ import java - import semmle.code.java.controlflow.Guards import semmle.code.java.dataflow.DataFlow @@ -12,9 +11,13 @@ private predicate isSafe(Guard g, Expr checked, boolean branch) { } module TestConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { source.asExpr() instanceof StringLiteral or source.asParameter().getCallable().hasName("test") } + 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 isSink(DataFlow::Node sink) { + sink.asExpr() = any(MethodCall mc | mc.getMethod().getName() = "sink").getAnArgument() + } predicate isBarrier(DataFlow::Node node) { node = DataFlow::BarrierGuard::getABarrierNode() @@ -26,4 +29,3 @@ module Flow = DataFlow::Global; from DataFlow::Node source, DataFlow::Node sink where Flow::flow(source, sink) select source, sink - From 3cdb1d29f12e98f7dbf0eeca4e9c401dcab33e58 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 2 Nov 2023 22:41:16 +0000 Subject: [PATCH 032/115] Add upgrade and downgrade scripts for latest dbscheme --- .../exprs.ql | 41 + .../old.dbscheme | 1265 +++++++++++++++++ .../semmlecode.dbscheme | 1256 ++++++++++++++++ .../upgrade.properties | 5 + .../exprs.ql | 31 + .../old.dbscheme | 1256 ++++++++++++++++ .../semmlecode.dbscheme | 1265 +++++++++++++++++ .../upgrade.properties | 3 + 8 files changed, 5122 insertions(+) create mode 100644 java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql create mode 100644 java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/old.dbscheme create mode 100644 java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/semmlecode.dbscheme create mode 100644 java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/upgrade.properties create mode 100644 java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/exprs.ql create mode 100644 java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/old.dbscheme create mode 100644 java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/semmlecode.dbscheme create mode 100644 java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/upgrade.properties diff --git a/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql new file mode 100644 index 00000000000..2a6d49da2c0 --- /dev/null +++ b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql @@ -0,0 +1,41 @@ +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, kind, typeid, _, _) and +hasNewParent(e, parent, index) and +(if oldKind = 89 /* record pattern */ then newKind = 74 /* error expression */ else oldKind = newKind) +select e, newKind, typeid, parent, index diff --git a/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/old.dbscheme b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/old.dbscheme new file mode 100644 index 00000000000..dee651b58d1 --- /dev/null +++ b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/old.dbscheme @@ -0,0 +1,1265 @@ +/** + * An invocation of the compiler. Note that more than one file may be + * compiled per invocation. For example, this command compiles three + * source files: + * + * javac A.java B.java C.java + * + * The `id` simply identifies the invocation, while `cwd` is the working + * directory from which the compiler was invoked. + */ +compilations( + /** + * An invocation of the compiler. Note that more than one file may + * be compiled per invocation. For example, this command compiles + * three source files: + * + * javac A.java B.java C.java + */ + unique int id : @compilation, + int kind: int ref, + string cwd : string ref, + string name : string ref +); + +case @compilation.kind of + 1 = @javacompilation +| 2 = @kotlincompilation +; + +compilation_started( + int id : @compilation ref +) + +compilation_info( + int id : @compilation ref, + string info_key: string ref, + string info_value: string ref +) + +/** + * The arguments that were passed to the extractor for a compiler + * invocation. If `id` is for the compiler invocation + * + * javac A.java B.java C.java + * + * then typically there will be rows for + * + * num | arg + * --- | --- + * 0 | *path to extractor* + * 1 | `--javac-args` + * 2 | A.java + * 3 | B.java + * 4 | C.java + */ +#keyset[id, num] +compilation_args( + int id : @compilation ref, + int num : int ref, + string arg : string ref +); + +/** + * The expanded arguments that were passed to the extractor for a + * compiler invocation. This is similar to `compilation_args`, but + * for a `@@@someFile` argument, it includes the arguments from that + * file, rather than just taking the argument literally. + */ +#keyset[id, num] +compilation_expanded_args( + int id : @compilation ref, + int num : int ref, + string arg : string ref +); + +/** + * The source files that are compiled by a compiler invocation. + * If `id` is for the compiler invocation + * + * javac A.java B.java C.java + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | A.java + * 1 | B.java + * 2 | C.java + */ +#keyset[id, num] +compilation_compiling_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * For each file recorded in `compilation_compiling_files`, + * there will be a corresponding row in + * `compilation_compiling_files_completed` once extraction + * of that file is complete. The `result` will indicate the + * extraction result: + * + * 0: Successfully extracted + * 1: Errors were encountered, but extraction recovered + * 2: Errors were encountered, and extraction could not recover + */ +#keyset[id, num] +compilation_compiling_files_completed( + int id : @compilation ref, + int num : int ref, + int result : int ref +); + +/** + * The time taken by the extractor for a compiler invocation. + * + * For each file `num`, there will be rows for + * + * kind | seconds + * ---- | --- + * 1 | CPU seconds used by the extractor frontend + * 2 | Elapsed seconds during the extractor frontend + * 3 | CPU seconds used by the extractor backend + * 4 | Elapsed seconds during the extractor backend + */ +#keyset[id, num, kind] +compilation_time( + int id : @compilation ref, + int num : int ref, + /* kind: + 1 = frontend_cpu_seconds + 2 = frontend_elapsed_seconds + 3 = extractor_cpu_seconds + 4 = extractor_elapsed_seconds + */ + int kind : int ref, + float seconds : float ref +); + +/** + * An error or warning generated by the extractor. + * The diagnostic message `diagnostic` was generated during compiler + * invocation `compilation`, and is the `file_number_diagnostic_number`th + * message generated while extracting the `file_number`th file of that + * invocation. + */ +#keyset[compilation, file_number, file_number_diagnostic_number] +diagnostic_for( + unique int diagnostic : @diagnostic ref, + int compilation : @compilation ref, + int file_number : int ref, + int file_number_diagnostic_number : int ref +); + +/** + * The `cpu_seconds` and `elapsed_seconds` are the CPU time and elapsed + * time (respectively) that the original compilation (not the extraction) + * took for compiler invocation `id`. + */ +compilation_compiler_times( + unique int id : @compilation ref, + float cpu_seconds : float ref, + float elapsed_seconds : float ref +); + +/** + * If extraction was successful, then `cpu_seconds` and + * `elapsed_seconds` are the CPU time and elapsed time (respectively) + * that extraction took for compiler invocation `id`. + * The `result` will indicate the extraction result: + * + * 0: Successfully extracted + * 1: Errors were encountered, but extraction recovered + * 2: Errors were encountered, and extraction could not recover + */ +compilation_finished( + unique int id : @compilation ref, + float cpu_seconds : float ref, + float elapsed_seconds : float ref, + int result : int ref +); + +diagnostics( + unique int id: @diagnostic, + string generated_by: string ref, // TODO: Sync this with the other languages? + int severity: int ref, + string error_tag: string ref, + string error_message: string ref, + string full_error_message: string ref, + int location: @location_default ref +); + +/* + * External artifacts + */ + +externalData( + int id : @externalDataElement, + string path : string ref, + int column: int ref, + string value : string ref +); + +snapshotDate( + unique date snapshotDate : date ref +); + +sourceLocationPrefix( + string prefix : string ref +); + +/* + * Duplicate code + */ + +duplicateCode( + unique int id : @duplication, + string relativePath : string ref, + int equivClass : int ref +); + +similarCode( + unique int id : @similarity, + string relativePath : string ref, + int equivClass : int ref +); + +@duplication_or_similarity = @duplication | @similarity + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref +); + +/* + * SMAP + */ + +smap_header( + int outputFileId: @file ref, + string outputFilename: string ref, + string defaultStratum: string ref +); + +smap_files( + int outputFileId: @file ref, + string stratum: string ref, + int inputFileNum: int ref, + string inputFileName: string ref, + int inputFileId: @file ref +); + +smap_lines( + int outputFileId: @file ref, + string stratum: string ref, + int inputFileNum: int ref, + int inputStartLine: int ref, + int inputLineCount: int ref, + int outputStartLine: int ref, + int outputLineIncrement: int ref +); + +/* + * Locations and files + */ + +@location = @location_default ; + +locations_default( + unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref +); + +hasLocation( + int locatableid: @locatable ref, + int id: @location ref +); + +@sourceline = @locatable ; + +#keyset[element_id] +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref +); + +files( + unique int id: @file, + string name: string ref +); + +folders( + unique int id: @folder, + string name: string ref +); + +@container = @folder | @file + +containerparent( + int parent: @container ref, + unique int child: @container ref +); + +/* + * Java + */ + +cupackage( + unique int id: @file ref, + int packageid: @package ref +); + +#keyset[fileid,keyName] +jarManifestMain( + int fileid: @file ref, + string keyName: string ref, + string value: string ref +); + +#keyset[fileid,entryName,keyName] +jarManifestEntries( + int fileid: @file ref, + string entryName: string ref, + string keyName: string ref, + string value: string ref +); + +packages( + unique int id: @package, + string nodeName: string ref +); + +primitives( + unique int id: @primitive, + string nodeName: string ref +); + +modifiers( + unique int id: @modifier, + string nodeName: string ref +); + +/** + * An errortype is used when the extractor is unable to extract a type + * correctly for some reason. + */ +error_type( + unique int id: @errortype +); + +classes_or_interfaces( + unique int id: @classorinterface, + string nodeName: string ref, + int parentid: @package ref, + int sourceid: @classorinterface ref +); + +file_class( + int id: @classorinterface ref +); + +class_object( + unique int id: @classorinterface ref, + unique int instance: @field ref +); + +type_companion_object( + unique int id: @classorinterface ref, + unique int instance: @field ref, + unique int companion_object: @classorinterface ref +); + +kt_nullable_types( + unique int id: @kt_nullable_type, + int classid: @reftype ref +) + +kt_notnull_types( + unique int id: @kt_notnull_type, + int classid: @reftype ref +) + +kt_type_alias( + unique int id: @kt_type_alias, + string name: string ref, + int kttypeid: @kt_type ref +) + +@kt_type = @kt_nullable_type | @kt_notnull_type + +isInterface( + unique int id: @classorinterface ref +); + +isRecord( + unique int id: @classorinterface ref +); + +fielddecls( + unique int id: @fielddecl, + int parentid: @reftype ref +); + +#keyset[fieldId] #keyset[fieldDeclId,pos] +fieldDeclaredIn( + int fieldId: @field ref, + int fieldDeclId: @fielddecl ref, + int pos: int ref +); + +fields( + unique int id: @field, + string nodeName: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @field ref +); + +fieldsKotlinType( + unique int id: @field ref, + int kttypeid: @kt_type ref +); + +constrs( + unique int id: @constructor, + string nodeName: string ref, + string signature: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @constructor ref +); + +constrsKotlinType( + unique int id: @constructor ref, + int kttypeid: @kt_type ref +); + +methods( + unique int id: @method, + string nodeName: string ref, + string signature: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @method ref +); + +methodsKotlinType( + unique int id: @method ref, + int kttypeid: @kt_type ref +); + +#keyset[parentid,pos] +params( + unique int id: @param, + int typeid: @type ref, + int pos: int ref, + int parentid: @callable ref, + int sourceid: @param ref +); + +paramsKotlinType( + unique int id: @param ref, + int kttypeid: @kt_type ref +); + +paramName( + unique int id: @param ref, + string nodeName: string ref +); + +isVarargsParam( + int param: @param ref +); + +exceptions( + unique int id: @exception, + int typeid: @type ref, + int parentid: @callable ref +); + +isAnnotType( + int interfaceid: @classorinterface ref +); + +isAnnotElem( + int methodid: @method ref +); + +annotValue( + int parentid: @annotation ref, + int id2: @method ref, + unique int value: @expr ref +); + +isEnumType( + int classid: @classorinterface ref +); + +isEnumConst( + int fieldid: @field ref +); + +#keyset[parentid,pos] +typeVars( + unique int id: @typevariable, + string nodeName: string ref, + int pos: int ref, + int kind: int ref, // deprecated + int parentid: @classorinterfaceorcallable ref +); + +wildcards( + unique int id: @wildcard, + string nodeName: string ref, + int kind: int ref +); + +#keyset[parentid,pos] +typeBounds( + unique int id: @typebound, + int typeid: @reftype ref, + int pos: int ref, + int parentid: @boundedtype ref +); + +#keyset[parentid,pos] +typeArgs( + int argumentid: @reftype ref, + int pos: int ref, + int parentid: @classorinterfaceorcallable ref +); + +isParameterized( + int memberid: @member ref +); + +isRaw( + int memberid: @member ref +); + +erasure( + unique int memberid: @member ref, + int erasureid: @member ref +); + +#keyset[classid] #keyset[parent] +isAnonymClass( + int classid: @classorinterface ref, + int parent: @classinstancexpr ref +); + +#keyset[typeid] #keyset[parent] +isLocalClassOrInterface( + int typeid: @classorinterface ref, + int parent: @localtypedeclstmt ref +); + +isDefConstr( + int constructorid: @constructor ref +); + +#keyset[exprId] +lambdaKind( + int exprId: @lambdaexpr ref, + int bodyKind: int ref +); + +isCanonicalConstr( + int constructorid: @constructor ref +); + +arrays( + unique int id: @array, + string nodeName: string ref, + int elementtypeid: @type ref, + int dimension: int ref, + int componenttypeid: @type ref +); + +enclInReftype( + unique int child: @reftype ref, + int parent: @reftype ref +); + +extendsReftype( + int id1: @reftype ref, + int id2: @classorinterface ref +); + +implInterface( + int id1: @classorarray ref, + int id2: @classorinterface ref +); + +permits( + int id1: @classorinterface ref, + int id2: @classorinterface ref +); + +hasModifier( + int id1: @modifiable ref, + int id2: @modifier ref +); + +imports( + unique int id: @import, + int holder: @classorinterfaceorpackage ref, + string name: string ref, + int kind: int ref +); + +#keyset[parent,idx] +stmts( + unique int id: @stmt, + int kind: int ref, + int parent: @stmtparent ref, + int idx: int ref, + int bodydecl: @callable ref +); + +@stmtparent = @callable | @stmt | @switchexpr | @whenexpr| @stmtexpr; + +case @stmt.kind of + 0 = @block +| 1 = @ifstmt +| 2 = @forstmt +| 3 = @enhancedforstmt +| 4 = @whilestmt +| 5 = @dostmt +| 6 = @trystmt +| 7 = @switchstmt +| 8 = @synchronizedstmt +| 9 = @returnstmt +| 10 = @throwstmt +| 11 = @breakstmt +| 12 = @continuestmt +| 13 = @emptystmt +| 14 = @exprstmt +| 15 = @labeledstmt +| 16 = @assertstmt +| 17 = @localvariabledeclstmt +| 18 = @localtypedeclstmt +| 19 = @constructorinvocationstmt +| 20 = @superconstructorinvocationstmt +| 21 = @case +| 22 = @catchclause +| 23 = @yieldstmt +| 24 = @errorstmt +| 25 = @whenbranch +; + +#keyset[parent,idx] +exprs( + unique int id: @expr, + int kind: int ref, + int typeid: @type ref, + int parent: @exprparent ref, + int idx: int ref +); + +exprsKotlinType( + unique int id: @expr ref, + int kttypeid: @kt_type ref +); + +callableEnclosingExpr( + unique int id: @expr ref, + int callable_id: @callable ref +); + +statementEnclosingExpr( + unique int id: @expr ref, + int statement_id: @stmt ref +); + +isParenthesized( + unique int id: @expr ref, + int parentheses: int ref +); + +case @expr.kind of + 1 = @arrayaccess +| 2 = @arraycreationexpr +| 3 = @arrayinit +| 4 = @assignexpr +| 5 = @assignaddexpr +| 6 = @assignsubexpr +| 7 = @assignmulexpr +| 8 = @assigndivexpr +| 9 = @assignremexpr +| 10 = @assignandexpr +| 11 = @assignorexpr +| 12 = @assignxorexpr +| 13 = @assignlshiftexpr +| 14 = @assignrshiftexpr +| 15 = @assignurshiftexpr +| 16 = @booleanliteral +| 17 = @integerliteral +| 18 = @longliteral +| 19 = @floatingpointliteral +| 20 = @doubleliteral +| 21 = @characterliteral +| 22 = @stringliteral +| 23 = @nullliteral +| 24 = @mulexpr +| 25 = @divexpr +| 26 = @remexpr +| 27 = @addexpr +| 28 = @subexpr +| 29 = @lshiftexpr +| 30 = @rshiftexpr +| 31 = @urshiftexpr +| 32 = @andbitexpr +| 33 = @orbitexpr +| 34 = @xorbitexpr +| 35 = @andlogicalexpr +| 36 = @orlogicalexpr +| 37 = @ltexpr +| 38 = @gtexpr +| 39 = @leexpr +| 40 = @geexpr +| 41 = @eqexpr +| 42 = @neexpr +| 43 = @postincexpr +| 44 = @postdecexpr +| 45 = @preincexpr +| 46 = @predecexpr +| 47 = @minusexpr +| 48 = @plusexpr +| 49 = @bitnotexpr +| 50 = @lognotexpr +| 51 = @castexpr +| 52 = @newexpr +| 53 = @conditionalexpr +| 54 = @parexpr // deprecated +| 55 = @instanceofexpr +| 56 = @localvariabledeclexpr +| 57 = @typeliteral +| 58 = @thisaccess +| 59 = @superaccess +| 60 = @varaccess +| 61 = @methodaccess +| 62 = @unannotatedtypeaccess +| 63 = @arraytypeaccess +| 64 = @packageaccess +| 65 = @wildcardtypeaccess +| 66 = @declannotation +| 67 = @uniontypeaccess +| 68 = @lambdaexpr +| 69 = @memberref +| 70 = @annotatedtypeaccess +| 71 = @typeannotation +| 72 = @intersectiontypeaccess +| 73 = @switchexpr +| 74 = @errorexpr +| 75 = @whenexpr +| 76 = @getclassexpr +| 77 = @safecastexpr +| 78 = @implicitcastexpr +| 79 = @implicitnotnullexpr +| 80 = @implicitcoerciontounitexpr +| 81 = @notinstanceofexpr +| 82 = @stmtexpr +| 83 = @stringtemplateexpr +| 84 = @notnullexpr +| 85 = @unsafecoerceexpr +| 86 = @valueeqexpr +| 87 = @valueneexpr +| 88 = @propertyref +| 89 = @recordpatternexpr +; + +/** Holds if this `when` expression was written as an `if` expression. */ +when_if(unique int id: @whenexpr ref); + +/** Holds if this `when` branch was written as an `else` branch. */ +when_branch_else(unique int id: @whenbranch ref); + +@classinstancexpr = @newexpr | @lambdaexpr | @memberref | @propertyref + +@annotation = @declannotation | @typeannotation +@typeaccess = @unannotatedtypeaccess | @annotatedtypeaccess + +@assignment = @assignexpr + | @assignop; + +@unaryassignment = @postincexpr + | @postdecexpr + | @preincexpr + | @predecexpr; + +@assignop = @assignaddexpr + | @assignsubexpr + | @assignmulexpr + | @assigndivexpr + | @assignremexpr + | @assignandexpr + | @assignorexpr + | @assignxorexpr + | @assignlshiftexpr + | @assignrshiftexpr + | @assignurshiftexpr; + +@literal = @booleanliteral + | @integerliteral + | @longliteral + | @floatingpointliteral + | @doubleliteral + | @characterliteral + | @stringliteral + | @nullliteral; + +@binaryexpr = @mulexpr + | @divexpr + | @remexpr + | @addexpr + | @subexpr + | @lshiftexpr + | @rshiftexpr + | @urshiftexpr + | @andbitexpr + | @orbitexpr + | @xorbitexpr + | @andlogicalexpr + | @orlogicalexpr + | @ltexpr + | @gtexpr + | @leexpr + | @geexpr + | @eqexpr + | @neexpr + | @valueeqexpr + | @valueneexpr; + +@unaryexpr = @postincexpr + | @postdecexpr + | @preincexpr + | @predecexpr + | @minusexpr + | @plusexpr + | @bitnotexpr + | @lognotexpr + | @notnullexpr; + +@caller = @classinstancexpr + | @methodaccess + | @constructorinvocationstmt + | @superconstructorinvocationstmt; + +callableBinding( + unique int callerid: @caller ref, + int callee: @callable ref +); + +memberRefBinding( + unique int id: @expr ref, + int callable: @callable ref +); + +propertyRefGetBinding( + unique int id: @expr ref, + int getter: @callable ref +); + +propertyRefFieldBinding( + unique int id: @expr ref, + int field: @field ref +); + +propertyRefSetBinding( + unique int id: @expr ref, + int setter: @callable ref +); + +@exprparent = @stmt | @expr | @whenbranch | @callable | @field | @fielddecl | @classorinterface | @param | @localvar | @typevariable; + +variableBinding( + unique int expr: @varaccess ref, + int variable: @variable ref +); + +@variable = @localscopevariable | @field; + +@localscopevariable = @localvar | @param; + +localvars( + unique int id: @localvar, + string nodeName: string ref, + int typeid: @type ref, + int parentid: @localvariabledeclexpr ref +); + +localvarsKotlinType( + unique int id: @localvar ref, + int kttypeid: @kt_type ref +); + +@namedexprorstmt = @breakstmt + | @continuestmt + | @labeledstmt + | @literal; + +namestrings( + string name: string ref, + string value: string ref, + unique int parent: @namedexprorstmt ref +); + +/* + * Modules + */ + +#keyset[name] +modules( + unique int id: @module, + string name: string ref +); + +isOpen( + int id: @module ref +); + +#keyset[fileId] +cumodule( + int fileId: @file ref, + int moduleId: @module ref +); + +@directive = @requires + | @exports + | @opens + | @uses + | @provides + +#keyset[directive] +directives( + int id: @module ref, + int directive: @directive ref +); + +requires( + unique int id: @requires, + int target: @module ref +); + +isTransitive( + int id: @requires ref +); + +isStatic( + int id: @requires ref +); + +exports( + unique int id: @exports, + int target: @package ref +); + +exportsTo( + int id: @exports ref, + int target: @module ref +); + +opens( + unique int id: @opens, + int target: @package ref +); + +opensTo( + int id: @opens ref, + int target: @module ref +); + +uses( + unique int id: @uses, + string serviceInterface: string ref +); + +provides( + unique int id: @provides, + string serviceInterface: string ref +); + +providesWith( + int id: @provides ref, + string serviceImpl: string ref +); + +isNullDefaultCase( + int id: @case ref +); + +/* + * Javadoc + */ + +javadoc( + unique int id: @javadoc +); + +isNormalComment( + int commentid : @javadoc ref +); + +isEolComment( + int commentid : @javadoc ref +); + +hasJavadoc( + int documentableid: @member ref, + int javadocid: @javadoc ref +); + +#keyset[parentid,idx] +javadocTag( + unique int id: @javadocTag, + string name: string ref, + int parentid: @javadocParent ref, + int idx: int ref +); + +#keyset[parentid,idx] +javadocText( + unique int id: @javadocText, + string text: string ref, + int parentid: @javadocParent ref, + int idx: int ref +); + +@javadocParent = @javadoc | @javadocTag; +@javadocElement = @javadocTag | @javadocText; + +@classorinterfaceorpackage = @classorinterface | @package; +@classorinterfaceorcallable = @classorinterface | @callable; +@boundedtype = @typevariable | @wildcard; +@reftype = @classorinterface | @array | @boundedtype | @errortype; +@classorarray = @classorinterface | @array; +@type = @primitive | @reftype; +@callable = @method | @constructor; + +/** A program element that has a name. */ +@element = @package | @modifier | @annotation | @errortype | + @locatableElement; + +@locatableElement = @file | @primitive | @classorinterface | @method | @constructor | @param | @exception | @field | + @boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl | @kt_type | @kt_type_alias | + @kt_property; + +@modifiable = @member_modifiable| @param | @localvar | @typevariable; + +@member_modifiable = @classorinterface | @method | @constructor | @field | @kt_property; + +@member = @method | @constructor | @field | @reftype ; + +/** A program element that has a location. */ +@locatable = @typebound | @javadoc | @javadocTag | @javadocText | @xmllocatable | @ktcomment | + @locatableElement; + +@top = @element | @locatable | @folder; + +/* + * XML Files + */ + +xmlEncoding( + unique int id: @file ref, + string encoding: string ref +); + +xmlDTDs( + unique int id: @xmldtd, + string root: string ref, + string publicId: string ref, + string systemId: string ref, + int fileid: @file ref +); + +xmlElements( + unique int id: @xmlelement, + string name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref +); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + string name: string ref, + string value: string ref, + int idx: int ref, + int fileid: @file ref +); + +xmlNs( + int id: @xmlnamespace, + string prefixName: string ref, + string URI: string ref, + int fileid: @file ref +); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref +); + +xmlComments( + unique int id: @xmlcomment, + string text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref +); + +xmlChars( + unique int id: @xmlcharacters, + string text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref +); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref +); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +/* + * configuration files with key value pairs + */ + +configs( + unique int id: @config +); + +configNames( + unique int id: @configName, + int config: @config ref, + string name: string ref +); + +configValues( + unique int id: @configValue, + int config: @config ref, + string value: string ref +); + +configLocations( + int locatable: @configLocatable ref, + int location: @location_default ref +); + +@configLocatable = @config | @configName | @configValue; + +ktComments( + unique int id: @ktcomment, + int kind: int ref, + string text : string ref +) + +ktCommentSections( + unique int id: @ktcommentsection, + int comment: @ktcomment ref, + string content : string ref +) + +ktCommentSectionNames( + unique int id: @ktcommentsection ref, + string name : string ref +) + +ktCommentSectionSubjectNames( + unique int id: @ktcommentsection ref, + string subjectname : string ref +) + +#keyset[id, owner] +ktCommentOwners( + int id: @ktcomment ref, + int owner: @top ref +) + +ktExtensionFunctions( + unique int id: @method ref, + int typeid: @type ref, + int kttypeid: @kt_type ref +) + +ktProperties( + unique int id: @kt_property, + string nodeName: string ref +) + +ktPropertyGetters( + unique int id: @kt_property ref, + int getter: @method ref +) + +ktPropertySetters( + unique int id: @kt_property ref, + int setter: @method ref +) + +ktPropertyBackingFields( + unique int id: @kt_property ref, + int backingField: @field ref +) + +ktSyntheticBody( + unique int id: @callable ref, + int kind: int ref + // 1: ENUM_VALUES + // 2: ENUM_VALUEOF + // 3: ENUM_ENTRIES +) + +ktLocalFunction( + unique int id: @method ref +) + +ktInitializerAssignment( + unique int id: @assignexpr ref +) + +ktPropertyDelegates( + unique int id: @kt_property ref, + unique int variableId: @variable ref +) + +/** + * If `id` is a compiler generated element, then the kind indicates the + * reason that the compiler generated it. + * See `Element.compilerGeneratedReason()` for an explanation of what + * each `kind` means. + */ +compiler_generated( + unique int id: @element ref, + int kind: int ref +) + +ktFunctionOriginalNames( + unique int id: @method ref, + string name: string ref +) + +ktDataClasses( + unique int id: @classorinterface ref +) diff --git a/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/semmlecode.dbscheme b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/semmlecode.dbscheme new file mode 100644 index 00000000000..ecfcf050952 --- /dev/null +++ b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/semmlecode.dbscheme @@ -0,0 +1,1256 @@ +/** + * An invocation of the compiler. Note that more than one file may be + * compiled per invocation. For example, this command compiles three + * source files: + * + * javac A.java B.java C.java + * + * The `id` simply identifies the invocation, while `cwd` is the working + * directory from which the compiler was invoked. + */ +compilations( + /** + * An invocation of the compiler. Note that more than one file may + * be compiled per invocation. For example, this command compiles + * three source files: + * + * javac A.java B.java C.java + */ + unique int id : @compilation, + int kind: int ref, + string cwd : string ref, + string name : string ref +); + +case @compilation.kind of + 1 = @javacompilation +| 2 = @kotlincompilation +; + +compilation_started( + int id : @compilation ref +) + +compilation_info( + int id : @compilation ref, + string info_key: string ref, + string info_value: string ref +) + +/** + * The arguments that were passed to the extractor for a compiler + * invocation. If `id` is for the compiler invocation + * + * javac A.java B.java C.java + * + * then typically there will be rows for + * + * num | arg + * --- | --- + * 0 | *path to extractor* + * 1 | `--javac-args` + * 2 | A.java + * 3 | B.java + * 4 | C.java + */ +#keyset[id, num] +compilation_args( + int id : @compilation ref, + int num : int ref, + string arg : string ref +); + +/** + * The expanded arguments that were passed to the extractor for a + * compiler invocation. This is similar to `compilation_args`, but + * for a `@@@someFile` argument, it includes the arguments from that + * file, rather than just taking the argument literally. + */ +#keyset[id, num] +compilation_expanded_args( + int id : @compilation ref, + int num : int ref, + string arg : string ref +); + +/** + * The source files that are compiled by a compiler invocation. + * If `id` is for the compiler invocation + * + * javac A.java B.java C.java + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | A.java + * 1 | B.java + * 2 | C.java + */ +#keyset[id, num] +compilation_compiling_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * For each file recorded in `compilation_compiling_files`, + * there will be a corresponding row in + * `compilation_compiling_files_completed` once extraction + * of that file is complete. The `result` will indicate the + * extraction result: + * + * 0: Successfully extracted + * 1: Errors were encountered, but extraction recovered + * 2: Errors were encountered, and extraction could not recover + */ +#keyset[id, num] +compilation_compiling_files_completed( + int id : @compilation ref, + int num : int ref, + int result : int ref +); + +/** + * The time taken by the extractor for a compiler invocation. + * + * For each file `num`, there will be rows for + * + * kind | seconds + * ---- | --- + * 1 | CPU seconds used by the extractor frontend + * 2 | Elapsed seconds during the extractor frontend + * 3 | CPU seconds used by the extractor backend + * 4 | Elapsed seconds during the extractor backend + */ +#keyset[id, num, kind] +compilation_time( + int id : @compilation ref, + int num : int ref, + /* kind: + 1 = frontend_cpu_seconds + 2 = frontend_elapsed_seconds + 3 = extractor_cpu_seconds + 4 = extractor_elapsed_seconds + */ + int kind : int ref, + float seconds : float ref +); + +/** + * An error or warning generated by the extractor. + * The diagnostic message `diagnostic` was generated during compiler + * invocation `compilation`, and is the `file_number_diagnostic_number`th + * message generated while extracting the `file_number`th file of that + * invocation. + */ +#keyset[compilation, file_number, file_number_diagnostic_number] +diagnostic_for( + unique int diagnostic : @diagnostic ref, + int compilation : @compilation ref, + int file_number : int ref, + int file_number_diagnostic_number : int ref +); + +/** + * The `cpu_seconds` and `elapsed_seconds` are the CPU time and elapsed + * time (respectively) that the original compilation (not the extraction) + * took for compiler invocation `id`. + */ +compilation_compiler_times( + unique int id : @compilation ref, + float cpu_seconds : float ref, + float elapsed_seconds : float ref +); + +/** + * If extraction was successful, then `cpu_seconds` and + * `elapsed_seconds` are the CPU time and elapsed time (respectively) + * that extraction took for compiler invocation `id`. + * The `result` will indicate the extraction result: + * + * 0: Successfully extracted + * 1: Errors were encountered, but extraction recovered + * 2: Errors were encountered, and extraction could not recover + */ +compilation_finished( + unique int id : @compilation ref, + float cpu_seconds : float ref, + float elapsed_seconds : float ref, + int result : int ref +); + +diagnostics( + unique int id: @diagnostic, + string generated_by: string ref, // TODO: Sync this with the other languages? + int severity: int ref, + string error_tag: string ref, + string error_message: string ref, + string full_error_message: string ref, + int location: @location_default ref +); + +/* + * External artifacts + */ + +externalData( + int id : @externalDataElement, + string path : string ref, + int column: int ref, + string value : string ref +); + +snapshotDate( + unique date snapshotDate : date ref +); + +sourceLocationPrefix( + string prefix : string ref +); + +/* + * Duplicate code + */ + +duplicateCode( + unique int id : @duplication, + string relativePath : string ref, + int equivClass : int ref +); + +similarCode( + unique int id : @similarity, + string relativePath : string ref, + int equivClass : int ref +); + +@duplication_or_similarity = @duplication | @similarity + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref +); + +/* + * SMAP + */ + +smap_header( + int outputFileId: @file ref, + string outputFilename: string ref, + string defaultStratum: string ref +); + +smap_files( + int outputFileId: @file ref, + string stratum: string ref, + int inputFileNum: int ref, + string inputFileName: string ref, + int inputFileId: @file ref +); + +smap_lines( + int outputFileId: @file ref, + string stratum: string ref, + int inputFileNum: int ref, + int inputStartLine: int ref, + int inputLineCount: int ref, + int outputStartLine: int ref, + int outputLineIncrement: int ref +); + +/* + * Locations and files + */ + +@location = @location_default ; + +locations_default( + unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref +); + +hasLocation( + int locatableid: @locatable ref, + int id: @location ref +); + +@sourceline = @locatable ; + +#keyset[element_id] +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref +); + +files( + unique int id: @file, + string name: string ref +); + +folders( + unique int id: @folder, + string name: string ref +); + +@container = @folder | @file + +containerparent( + int parent: @container ref, + unique int child: @container ref +); + +/* + * Java + */ + +cupackage( + unique int id: @file ref, + int packageid: @package ref +); + +#keyset[fileid,keyName] +jarManifestMain( + int fileid: @file ref, + string keyName: string ref, + string value: string ref +); + +#keyset[fileid,entryName,keyName] +jarManifestEntries( + int fileid: @file ref, + string entryName: string ref, + string keyName: string ref, + string value: string ref +); + +packages( + unique int id: @package, + string nodeName: string ref +); + +primitives( + unique int id: @primitive, + string nodeName: string ref +); + +modifiers( + unique int id: @modifier, + string nodeName: string ref +); + +/** + * An errortype is used when the extractor is unable to extract a type + * correctly for some reason. + */ +error_type( + unique int id: @errortype +); + +classes_or_interfaces( + unique int id: @classorinterface, + string nodeName: string ref, + int parentid: @package ref, + int sourceid: @classorinterface ref +); + +file_class( + int id: @classorinterface ref +); + +class_object( + unique int id: @classorinterface ref, + unique int instance: @field ref +); + +type_companion_object( + unique int id: @classorinterface ref, + unique int instance: @field ref, + unique int companion_object: @classorinterface ref +); + +kt_nullable_types( + unique int id: @kt_nullable_type, + int classid: @reftype ref +) + +kt_notnull_types( + unique int id: @kt_notnull_type, + int classid: @reftype ref +) + +kt_type_alias( + unique int id: @kt_type_alias, + string name: string ref, + int kttypeid: @kt_type ref +) + +@kt_type = @kt_nullable_type | @kt_notnull_type + +isInterface( + unique int id: @classorinterface ref +); + +isRecord( + unique int id: @classorinterface ref +); + +fielddecls( + unique int id: @fielddecl, + int parentid: @reftype ref +); + +#keyset[fieldId] #keyset[fieldDeclId,pos] +fieldDeclaredIn( + int fieldId: @field ref, + int fieldDeclId: @fielddecl ref, + int pos: int ref +); + +fields( + unique int id: @field, + string nodeName: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @field ref +); + +fieldsKotlinType( + unique int id: @field ref, + int kttypeid: @kt_type ref +); + +constrs( + unique int id: @constructor, + string nodeName: string ref, + string signature: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @constructor ref +); + +constrsKotlinType( + unique int id: @constructor ref, + int kttypeid: @kt_type ref +); + +methods( + unique int id: @method, + string nodeName: string ref, + string signature: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @method ref +); + +methodsKotlinType( + unique int id: @method ref, + int kttypeid: @kt_type ref +); + +#keyset[parentid,pos] +params( + unique int id: @param, + int typeid: @type ref, + int pos: int ref, + int parentid: @callable ref, + int sourceid: @param ref +); + +paramsKotlinType( + unique int id: @param ref, + int kttypeid: @kt_type ref +); + +paramName( + unique int id: @param ref, + string nodeName: string ref +); + +isVarargsParam( + int param: @param ref +); + +exceptions( + unique int id: @exception, + int typeid: @type ref, + int parentid: @callable ref +); + +isAnnotType( + int interfaceid: @classorinterface ref +); + +isAnnotElem( + int methodid: @method ref +); + +annotValue( + int parentid: @annotation ref, + int id2: @method ref, + unique int value: @expr ref +); + +isEnumType( + int classid: @classorinterface ref +); + +isEnumConst( + int fieldid: @field ref +); + +#keyset[parentid,pos] +typeVars( + unique int id: @typevariable, + string nodeName: string ref, + int pos: int ref, + int kind: int ref, // deprecated + int parentid: @classorinterfaceorcallable ref +); + +wildcards( + unique int id: @wildcard, + string nodeName: string ref, + int kind: int ref +); + +#keyset[parentid,pos] +typeBounds( + unique int id: @typebound, + int typeid: @reftype ref, + int pos: int ref, + int parentid: @boundedtype ref +); + +#keyset[parentid,pos] +typeArgs( + int argumentid: @reftype ref, + int pos: int ref, + int parentid: @classorinterfaceorcallable ref +); + +isParameterized( + int memberid: @member ref +); + +isRaw( + int memberid: @member ref +); + +erasure( + unique int memberid: @member ref, + int erasureid: @member ref +); + +#keyset[classid] #keyset[parent] +isAnonymClass( + int classid: @classorinterface ref, + int parent: @classinstancexpr ref +); + +#keyset[typeid] #keyset[parent] +isLocalClassOrInterface( + int typeid: @classorinterface ref, + int parent: @localtypedeclstmt ref +); + +isDefConstr( + int constructorid: @constructor ref +); + +#keyset[exprId] +lambdaKind( + int exprId: @lambdaexpr ref, + int bodyKind: int ref +); + +arrays( + unique int id: @array, + string nodeName: string ref, + int elementtypeid: @type ref, + int dimension: int ref, + int componenttypeid: @type ref +); + +enclInReftype( + unique int child: @reftype ref, + int parent: @reftype ref +); + +extendsReftype( + int id1: @reftype ref, + int id2: @classorinterface ref +); + +implInterface( + int id1: @classorarray ref, + int id2: @classorinterface ref +); + +permits( + int id1: @classorinterface ref, + int id2: @classorinterface ref +); + +hasModifier( + int id1: @modifiable ref, + int id2: @modifier ref +); + +imports( + unique int id: @import, + int holder: @classorinterfaceorpackage ref, + string name: string ref, + int kind: int ref +); + +#keyset[parent,idx] +stmts( + unique int id: @stmt, + int kind: int ref, + int parent: @stmtparent ref, + int idx: int ref, + int bodydecl: @callable ref +); + +@stmtparent = @callable | @stmt | @switchexpr | @whenexpr| @stmtexpr; + +case @stmt.kind of + 0 = @block +| 1 = @ifstmt +| 2 = @forstmt +| 3 = @enhancedforstmt +| 4 = @whilestmt +| 5 = @dostmt +| 6 = @trystmt +| 7 = @switchstmt +| 8 = @synchronizedstmt +| 9 = @returnstmt +| 10 = @throwstmt +| 11 = @breakstmt +| 12 = @continuestmt +| 13 = @emptystmt +| 14 = @exprstmt +| 15 = @labeledstmt +| 16 = @assertstmt +| 17 = @localvariabledeclstmt +| 18 = @localtypedeclstmt +| 19 = @constructorinvocationstmt +| 20 = @superconstructorinvocationstmt +| 21 = @case +| 22 = @catchclause +| 23 = @yieldstmt +| 24 = @errorstmt +| 25 = @whenbranch +; + +#keyset[parent,idx] +exprs( + unique int id: @expr, + int kind: int ref, + int typeid: @type ref, + int parent: @exprparent ref, + int idx: int ref +); + +exprsKotlinType( + unique int id: @expr ref, + int kttypeid: @kt_type ref +); + +callableEnclosingExpr( + unique int id: @expr ref, + int callable_id: @callable ref +); + +statementEnclosingExpr( + unique int id: @expr ref, + int statement_id: @stmt ref +); + +isParenthesized( + unique int id: @expr ref, + int parentheses: int ref +); + +case @expr.kind of + 1 = @arrayaccess +| 2 = @arraycreationexpr +| 3 = @arrayinit +| 4 = @assignexpr +| 5 = @assignaddexpr +| 6 = @assignsubexpr +| 7 = @assignmulexpr +| 8 = @assigndivexpr +| 9 = @assignremexpr +| 10 = @assignandexpr +| 11 = @assignorexpr +| 12 = @assignxorexpr +| 13 = @assignlshiftexpr +| 14 = @assignrshiftexpr +| 15 = @assignurshiftexpr +| 16 = @booleanliteral +| 17 = @integerliteral +| 18 = @longliteral +| 19 = @floatingpointliteral +| 20 = @doubleliteral +| 21 = @characterliteral +| 22 = @stringliteral +| 23 = @nullliteral +| 24 = @mulexpr +| 25 = @divexpr +| 26 = @remexpr +| 27 = @addexpr +| 28 = @subexpr +| 29 = @lshiftexpr +| 30 = @rshiftexpr +| 31 = @urshiftexpr +| 32 = @andbitexpr +| 33 = @orbitexpr +| 34 = @xorbitexpr +| 35 = @andlogicalexpr +| 36 = @orlogicalexpr +| 37 = @ltexpr +| 38 = @gtexpr +| 39 = @leexpr +| 40 = @geexpr +| 41 = @eqexpr +| 42 = @neexpr +| 43 = @postincexpr +| 44 = @postdecexpr +| 45 = @preincexpr +| 46 = @predecexpr +| 47 = @minusexpr +| 48 = @plusexpr +| 49 = @bitnotexpr +| 50 = @lognotexpr +| 51 = @castexpr +| 52 = @newexpr +| 53 = @conditionalexpr +| 54 = @parexpr // deprecated +| 55 = @instanceofexpr +| 56 = @localvariabledeclexpr +| 57 = @typeliteral +| 58 = @thisaccess +| 59 = @superaccess +| 60 = @varaccess +| 61 = @methodaccess +| 62 = @unannotatedtypeaccess +| 63 = @arraytypeaccess +| 64 = @packageaccess +| 65 = @wildcardtypeaccess +| 66 = @declannotation +| 67 = @uniontypeaccess +| 68 = @lambdaexpr +| 69 = @memberref +| 70 = @annotatedtypeaccess +| 71 = @typeannotation +| 72 = @intersectiontypeaccess +| 73 = @switchexpr +| 74 = @errorexpr +| 75 = @whenexpr +| 76 = @getclassexpr +| 77 = @safecastexpr +| 78 = @implicitcastexpr +| 79 = @implicitnotnullexpr +| 80 = @implicitcoerciontounitexpr +| 81 = @notinstanceofexpr +| 82 = @stmtexpr +| 83 = @stringtemplateexpr +| 84 = @notnullexpr +| 85 = @unsafecoerceexpr +| 86 = @valueeqexpr +| 87 = @valueneexpr +| 88 = @propertyref +; + +/** Holds if this `when` expression was written as an `if` expression. */ +when_if(unique int id: @whenexpr ref); + +/** Holds if this `when` branch was written as an `else` branch. */ +when_branch_else(unique int id: @whenbranch ref); + +@classinstancexpr = @newexpr | @lambdaexpr | @memberref | @propertyref + +@annotation = @declannotation | @typeannotation +@typeaccess = @unannotatedtypeaccess | @annotatedtypeaccess + +@assignment = @assignexpr + | @assignop; + +@unaryassignment = @postincexpr + | @postdecexpr + | @preincexpr + | @predecexpr; + +@assignop = @assignaddexpr + | @assignsubexpr + | @assignmulexpr + | @assigndivexpr + | @assignremexpr + | @assignandexpr + | @assignorexpr + | @assignxorexpr + | @assignlshiftexpr + | @assignrshiftexpr + | @assignurshiftexpr; + +@literal = @booleanliteral + | @integerliteral + | @longliteral + | @floatingpointliteral + | @doubleliteral + | @characterliteral + | @stringliteral + | @nullliteral; + +@binaryexpr = @mulexpr + | @divexpr + | @remexpr + | @addexpr + | @subexpr + | @lshiftexpr + | @rshiftexpr + | @urshiftexpr + | @andbitexpr + | @orbitexpr + | @xorbitexpr + | @andlogicalexpr + | @orlogicalexpr + | @ltexpr + | @gtexpr + | @leexpr + | @geexpr + | @eqexpr + | @neexpr + | @valueeqexpr + | @valueneexpr; + +@unaryexpr = @postincexpr + | @postdecexpr + | @preincexpr + | @predecexpr + | @minusexpr + | @plusexpr + | @bitnotexpr + | @lognotexpr + | @notnullexpr; + +@caller = @classinstancexpr + | @methodaccess + | @constructorinvocationstmt + | @superconstructorinvocationstmt; + +callableBinding( + unique int callerid: @caller ref, + int callee: @callable ref +); + +memberRefBinding( + unique int id: @expr ref, + int callable: @callable ref +); + +propertyRefGetBinding( + unique int id: @expr ref, + int getter: @callable ref +); + +propertyRefFieldBinding( + unique int id: @expr ref, + int field: @field ref +); + +propertyRefSetBinding( + unique int id: @expr ref, + int setter: @callable ref +); + +@exprparent = @stmt | @expr | @whenbranch | @callable | @field | @fielddecl | @classorinterface | @param | @localvar | @typevariable; + +variableBinding( + unique int expr: @varaccess ref, + int variable: @variable ref +); + +@variable = @localscopevariable | @field; + +@localscopevariable = @localvar | @param; + +localvars( + unique int id: @localvar, + string nodeName: string ref, + int typeid: @type ref, + int parentid: @localvariabledeclexpr ref +); + +localvarsKotlinType( + unique int id: @localvar ref, + int kttypeid: @kt_type ref +); + +@namedexprorstmt = @breakstmt + | @continuestmt + | @labeledstmt + | @literal; + +namestrings( + string name: string ref, + string value: string ref, + unique int parent: @namedexprorstmt ref +); + +/* + * Modules + */ + +#keyset[name] +modules( + unique int id: @module, + string name: string ref +); + +isOpen( + int id: @module ref +); + +#keyset[fileId] +cumodule( + int fileId: @file ref, + int moduleId: @module ref +); + +@directive = @requires + | @exports + | @opens + | @uses + | @provides + +#keyset[directive] +directives( + int id: @module ref, + int directive: @directive ref +); + +requires( + unique int id: @requires, + int target: @module ref +); + +isTransitive( + int id: @requires ref +); + +isStatic( + int id: @requires ref +); + +exports( + unique int id: @exports, + int target: @package ref +); + +exportsTo( + int id: @exports ref, + int target: @module ref +); + +opens( + unique int id: @opens, + int target: @package ref +); + +opensTo( + int id: @opens ref, + int target: @module ref +); + +uses( + unique int id: @uses, + string serviceInterface: string ref +); + +provides( + unique int id: @provides, + string serviceInterface: string ref +); + +providesWith( + int id: @provides ref, + string serviceImpl: string ref +); + +/* + * Javadoc + */ + +javadoc( + unique int id: @javadoc +); + +isNormalComment( + int commentid : @javadoc ref +); + +isEolComment( + int commentid : @javadoc ref +); + +hasJavadoc( + int documentableid: @member ref, + int javadocid: @javadoc ref +); + +#keyset[parentid,idx] +javadocTag( + unique int id: @javadocTag, + string name: string ref, + int parentid: @javadocParent ref, + int idx: int ref +); + +#keyset[parentid,idx] +javadocText( + unique int id: @javadocText, + string text: string ref, + int parentid: @javadocParent ref, + int idx: int ref +); + +@javadocParent = @javadoc | @javadocTag; +@javadocElement = @javadocTag | @javadocText; + +@classorinterfaceorpackage = @classorinterface | @package; +@classorinterfaceorcallable = @classorinterface | @callable; +@boundedtype = @typevariable | @wildcard; +@reftype = @classorinterface | @array | @boundedtype | @errortype; +@classorarray = @classorinterface | @array; +@type = @primitive | @reftype; +@callable = @method | @constructor; + +/** A program element that has a name. */ +@element = @package | @modifier | @annotation | @errortype | + @locatableElement; + +@locatableElement = @file | @primitive | @classorinterface | @method | @constructor | @param | @exception | @field | + @boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl | @kt_type | @kt_type_alias | + @kt_property; + +@modifiable = @member_modifiable| @param | @localvar | @typevariable; + +@member_modifiable = @classorinterface | @method | @constructor | @field | @kt_property; + +@member = @method | @constructor | @field | @reftype ; + +/** A program element that has a location. */ +@locatable = @typebound | @javadoc | @javadocTag | @javadocText | @xmllocatable | @ktcomment | + @locatableElement; + +@top = @element | @locatable | @folder; + +/* + * XML Files + */ + +xmlEncoding( + unique int id: @file ref, + string encoding: string ref +); + +xmlDTDs( + unique int id: @xmldtd, + string root: string ref, + string publicId: string ref, + string systemId: string ref, + int fileid: @file ref +); + +xmlElements( + unique int id: @xmlelement, + string name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref +); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + string name: string ref, + string value: string ref, + int idx: int ref, + int fileid: @file ref +); + +xmlNs( + int id: @xmlnamespace, + string prefixName: string ref, + string URI: string ref, + int fileid: @file ref +); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref +); + +xmlComments( + unique int id: @xmlcomment, + string text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref +); + +xmlChars( + unique int id: @xmlcharacters, + string text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref +); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref +); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +/* + * configuration files with key value pairs + */ + +configs( + unique int id: @config +); + +configNames( + unique int id: @configName, + int config: @config ref, + string name: string ref +); + +configValues( + unique int id: @configValue, + int config: @config ref, + string value: string ref +); + +configLocations( + int locatable: @configLocatable ref, + int location: @location_default ref +); + +@configLocatable = @config | @configName | @configValue; + +ktComments( + unique int id: @ktcomment, + int kind: int ref, + string text : string ref +) + +ktCommentSections( + unique int id: @ktcommentsection, + int comment: @ktcomment ref, + string content : string ref +) + +ktCommentSectionNames( + unique int id: @ktcommentsection ref, + string name : string ref +) + +ktCommentSectionSubjectNames( + unique int id: @ktcommentsection ref, + string subjectname : string ref +) + +#keyset[id, owner] +ktCommentOwners( + int id: @ktcomment ref, + int owner: @top ref +) + +ktExtensionFunctions( + unique int id: @method ref, + int typeid: @type ref, + int kttypeid: @kt_type ref +) + +ktProperties( + unique int id: @kt_property, + string nodeName: string ref +) + +ktPropertyGetters( + unique int id: @kt_property ref, + int getter: @method ref +) + +ktPropertySetters( + unique int id: @kt_property ref, + int setter: @method ref +) + +ktPropertyBackingFields( + unique int id: @kt_property ref, + int backingField: @field ref +) + +ktSyntheticBody( + unique int id: @callable ref, + int kind: int ref + // 1: ENUM_VALUES + // 2: ENUM_VALUEOF + // 3: ENUM_ENTRIES +) + +ktLocalFunction( + unique int id: @method ref +) + +ktInitializerAssignment( + unique int id: @assignexpr ref +) + +ktPropertyDelegates( + unique int id: @kt_property ref, + unique int variableId: @variable ref +) + +/** + * If `id` is a compiler generated element, then the kind indicates the + * reason that the compiler generated it. + * See `Element.compilerGeneratedReason()` for an explanation of what + * each `kind` means. + */ +compiler_generated( + unique int id: @element ref, + int kind: int ref +) + +ktFunctionOriginalNames( + unique int id: @method ref, + string name: string ref +) + +ktDataClasses( + unique int id: @classorinterface ref +) diff --git a/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/upgrade.properties b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/upgrade.properties new file mode 100644 index 00000000000..b60dcc89cc7 --- /dev/null +++ b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/upgrade.properties @@ -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 diff --git a/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/exprs.ql b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/exprs.ql new file mode 100644 index 00000000000..d59acad79fa --- /dev/null +++ b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/exprs.ql @@ -0,0 +1,31 @@ +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 (newParent = getParent(getParent(e)) and newIndex = 0) // Initialiser moves to hang directly off the instanceof expression + else ( + if (e instanceof LocalVariableDeclExpr and getParent(e) instanceof InstanceOfExpr) + then (newParent = getParent(e) and newIndex = 2) // Variable declaration moves to be the instanceof expression's 2nd child + 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 diff --git a/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/old.dbscheme b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/old.dbscheme new file mode 100644 index 00000000000..ecfcf050952 --- /dev/null +++ b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/old.dbscheme @@ -0,0 +1,1256 @@ +/** + * An invocation of the compiler. Note that more than one file may be + * compiled per invocation. For example, this command compiles three + * source files: + * + * javac A.java B.java C.java + * + * The `id` simply identifies the invocation, while `cwd` is the working + * directory from which the compiler was invoked. + */ +compilations( + /** + * An invocation of the compiler. Note that more than one file may + * be compiled per invocation. For example, this command compiles + * three source files: + * + * javac A.java B.java C.java + */ + unique int id : @compilation, + int kind: int ref, + string cwd : string ref, + string name : string ref +); + +case @compilation.kind of + 1 = @javacompilation +| 2 = @kotlincompilation +; + +compilation_started( + int id : @compilation ref +) + +compilation_info( + int id : @compilation ref, + string info_key: string ref, + string info_value: string ref +) + +/** + * The arguments that were passed to the extractor for a compiler + * invocation. If `id` is for the compiler invocation + * + * javac A.java B.java C.java + * + * then typically there will be rows for + * + * num | arg + * --- | --- + * 0 | *path to extractor* + * 1 | `--javac-args` + * 2 | A.java + * 3 | B.java + * 4 | C.java + */ +#keyset[id, num] +compilation_args( + int id : @compilation ref, + int num : int ref, + string arg : string ref +); + +/** + * The expanded arguments that were passed to the extractor for a + * compiler invocation. This is similar to `compilation_args`, but + * for a `@@@someFile` argument, it includes the arguments from that + * file, rather than just taking the argument literally. + */ +#keyset[id, num] +compilation_expanded_args( + int id : @compilation ref, + int num : int ref, + string arg : string ref +); + +/** + * The source files that are compiled by a compiler invocation. + * If `id` is for the compiler invocation + * + * javac A.java B.java C.java + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | A.java + * 1 | B.java + * 2 | C.java + */ +#keyset[id, num] +compilation_compiling_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * For each file recorded in `compilation_compiling_files`, + * there will be a corresponding row in + * `compilation_compiling_files_completed` once extraction + * of that file is complete. The `result` will indicate the + * extraction result: + * + * 0: Successfully extracted + * 1: Errors were encountered, but extraction recovered + * 2: Errors were encountered, and extraction could not recover + */ +#keyset[id, num] +compilation_compiling_files_completed( + int id : @compilation ref, + int num : int ref, + int result : int ref +); + +/** + * The time taken by the extractor for a compiler invocation. + * + * For each file `num`, there will be rows for + * + * kind | seconds + * ---- | --- + * 1 | CPU seconds used by the extractor frontend + * 2 | Elapsed seconds during the extractor frontend + * 3 | CPU seconds used by the extractor backend + * 4 | Elapsed seconds during the extractor backend + */ +#keyset[id, num, kind] +compilation_time( + int id : @compilation ref, + int num : int ref, + /* kind: + 1 = frontend_cpu_seconds + 2 = frontend_elapsed_seconds + 3 = extractor_cpu_seconds + 4 = extractor_elapsed_seconds + */ + int kind : int ref, + float seconds : float ref +); + +/** + * An error or warning generated by the extractor. + * The diagnostic message `diagnostic` was generated during compiler + * invocation `compilation`, and is the `file_number_diagnostic_number`th + * message generated while extracting the `file_number`th file of that + * invocation. + */ +#keyset[compilation, file_number, file_number_diagnostic_number] +diagnostic_for( + unique int diagnostic : @diagnostic ref, + int compilation : @compilation ref, + int file_number : int ref, + int file_number_diagnostic_number : int ref +); + +/** + * The `cpu_seconds` and `elapsed_seconds` are the CPU time and elapsed + * time (respectively) that the original compilation (not the extraction) + * took for compiler invocation `id`. + */ +compilation_compiler_times( + unique int id : @compilation ref, + float cpu_seconds : float ref, + float elapsed_seconds : float ref +); + +/** + * If extraction was successful, then `cpu_seconds` and + * `elapsed_seconds` are the CPU time and elapsed time (respectively) + * that extraction took for compiler invocation `id`. + * The `result` will indicate the extraction result: + * + * 0: Successfully extracted + * 1: Errors were encountered, but extraction recovered + * 2: Errors were encountered, and extraction could not recover + */ +compilation_finished( + unique int id : @compilation ref, + float cpu_seconds : float ref, + float elapsed_seconds : float ref, + int result : int ref +); + +diagnostics( + unique int id: @diagnostic, + string generated_by: string ref, // TODO: Sync this with the other languages? + int severity: int ref, + string error_tag: string ref, + string error_message: string ref, + string full_error_message: string ref, + int location: @location_default ref +); + +/* + * External artifacts + */ + +externalData( + int id : @externalDataElement, + string path : string ref, + int column: int ref, + string value : string ref +); + +snapshotDate( + unique date snapshotDate : date ref +); + +sourceLocationPrefix( + string prefix : string ref +); + +/* + * Duplicate code + */ + +duplicateCode( + unique int id : @duplication, + string relativePath : string ref, + int equivClass : int ref +); + +similarCode( + unique int id : @similarity, + string relativePath : string ref, + int equivClass : int ref +); + +@duplication_or_similarity = @duplication | @similarity + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref +); + +/* + * SMAP + */ + +smap_header( + int outputFileId: @file ref, + string outputFilename: string ref, + string defaultStratum: string ref +); + +smap_files( + int outputFileId: @file ref, + string stratum: string ref, + int inputFileNum: int ref, + string inputFileName: string ref, + int inputFileId: @file ref +); + +smap_lines( + int outputFileId: @file ref, + string stratum: string ref, + int inputFileNum: int ref, + int inputStartLine: int ref, + int inputLineCount: int ref, + int outputStartLine: int ref, + int outputLineIncrement: int ref +); + +/* + * Locations and files + */ + +@location = @location_default ; + +locations_default( + unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref +); + +hasLocation( + int locatableid: @locatable ref, + int id: @location ref +); + +@sourceline = @locatable ; + +#keyset[element_id] +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref +); + +files( + unique int id: @file, + string name: string ref +); + +folders( + unique int id: @folder, + string name: string ref +); + +@container = @folder | @file + +containerparent( + int parent: @container ref, + unique int child: @container ref +); + +/* + * Java + */ + +cupackage( + unique int id: @file ref, + int packageid: @package ref +); + +#keyset[fileid,keyName] +jarManifestMain( + int fileid: @file ref, + string keyName: string ref, + string value: string ref +); + +#keyset[fileid,entryName,keyName] +jarManifestEntries( + int fileid: @file ref, + string entryName: string ref, + string keyName: string ref, + string value: string ref +); + +packages( + unique int id: @package, + string nodeName: string ref +); + +primitives( + unique int id: @primitive, + string nodeName: string ref +); + +modifiers( + unique int id: @modifier, + string nodeName: string ref +); + +/** + * An errortype is used when the extractor is unable to extract a type + * correctly for some reason. + */ +error_type( + unique int id: @errortype +); + +classes_or_interfaces( + unique int id: @classorinterface, + string nodeName: string ref, + int parentid: @package ref, + int sourceid: @classorinterface ref +); + +file_class( + int id: @classorinterface ref +); + +class_object( + unique int id: @classorinterface ref, + unique int instance: @field ref +); + +type_companion_object( + unique int id: @classorinterface ref, + unique int instance: @field ref, + unique int companion_object: @classorinterface ref +); + +kt_nullable_types( + unique int id: @kt_nullable_type, + int classid: @reftype ref +) + +kt_notnull_types( + unique int id: @kt_notnull_type, + int classid: @reftype ref +) + +kt_type_alias( + unique int id: @kt_type_alias, + string name: string ref, + int kttypeid: @kt_type ref +) + +@kt_type = @kt_nullable_type | @kt_notnull_type + +isInterface( + unique int id: @classorinterface ref +); + +isRecord( + unique int id: @classorinterface ref +); + +fielddecls( + unique int id: @fielddecl, + int parentid: @reftype ref +); + +#keyset[fieldId] #keyset[fieldDeclId,pos] +fieldDeclaredIn( + int fieldId: @field ref, + int fieldDeclId: @fielddecl ref, + int pos: int ref +); + +fields( + unique int id: @field, + string nodeName: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @field ref +); + +fieldsKotlinType( + unique int id: @field ref, + int kttypeid: @kt_type ref +); + +constrs( + unique int id: @constructor, + string nodeName: string ref, + string signature: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @constructor ref +); + +constrsKotlinType( + unique int id: @constructor ref, + int kttypeid: @kt_type ref +); + +methods( + unique int id: @method, + string nodeName: string ref, + string signature: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @method ref +); + +methodsKotlinType( + unique int id: @method ref, + int kttypeid: @kt_type ref +); + +#keyset[parentid,pos] +params( + unique int id: @param, + int typeid: @type ref, + int pos: int ref, + int parentid: @callable ref, + int sourceid: @param ref +); + +paramsKotlinType( + unique int id: @param ref, + int kttypeid: @kt_type ref +); + +paramName( + unique int id: @param ref, + string nodeName: string ref +); + +isVarargsParam( + int param: @param ref +); + +exceptions( + unique int id: @exception, + int typeid: @type ref, + int parentid: @callable ref +); + +isAnnotType( + int interfaceid: @classorinterface ref +); + +isAnnotElem( + int methodid: @method ref +); + +annotValue( + int parentid: @annotation ref, + int id2: @method ref, + unique int value: @expr ref +); + +isEnumType( + int classid: @classorinterface ref +); + +isEnumConst( + int fieldid: @field ref +); + +#keyset[parentid,pos] +typeVars( + unique int id: @typevariable, + string nodeName: string ref, + int pos: int ref, + int kind: int ref, // deprecated + int parentid: @classorinterfaceorcallable ref +); + +wildcards( + unique int id: @wildcard, + string nodeName: string ref, + int kind: int ref +); + +#keyset[parentid,pos] +typeBounds( + unique int id: @typebound, + int typeid: @reftype ref, + int pos: int ref, + int parentid: @boundedtype ref +); + +#keyset[parentid,pos] +typeArgs( + int argumentid: @reftype ref, + int pos: int ref, + int parentid: @classorinterfaceorcallable ref +); + +isParameterized( + int memberid: @member ref +); + +isRaw( + int memberid: @member ref +); + +erasure( + unique int memberid: @member ref, + int erasureid: @member ref +); + +#keyset[classid] #keyset[parent] +isAnonymClass( + int classid: @classorinterface ref, + int parent: @classinstancexpr ref +); + +#keyset[typeid] #keyset[parent] +isLocalClassOrInterface( + int typeid: @classorinterface ref, + int parent: @localtypedeclstmt ref +); + +isDefConstr( + int constructorid: @constructor ref +); + +#keyset[exprId] +lambdaKind( + int exprId: @lambdaexpr ref, + int bodyKind: int ref +); + +arrays( + unique int id: @array, + string nodeName: string ref, + int elementtypeid: @type ref, + int dimension: int ref, + int componenttypeid: @type ref +); + +enclInReftype( + unique int child: @reftype ref, + int parent: @reftype ref +); + +extendsReftype( + int id1: @reftype ref, + int id2: @classorinterface ref +); + +implInterface( + int id1: @classorarray ref, + int id2: @classorinterface ref +); + +permits( + int id1: @classorinterface ref, + int id2: @classorinterface ref +); + +hasModifier( + int id1: @modifiable ref, + int id2: @modifier ref +); + +imports( + unique int id: @import, + int holder: @classorinterfaceorpackage ref, + string name: string ref, + int kind: int ref +); + +#keyset[parent,idx] +stmts( + unique int id: @stmt, + int kind: int ref, + int parent: @stmtparent ref, + int idx: int ref, + int bodydecl: @callable ref +); + +@stmtparent = @callable | @stmt | @switchexpr | @whenexpr| @stmtexpr; + +case @stmt.kind of + 0 = @block +| 1 = @ifstmt +| 2 = @forstmt +| 3 = @enhancedforstmt +| 4 = @whilestmt +| 5 = @dostmt +| 6 = @trystmt +| 7 = @switchstmt +| 8 = @synchronizedstmt +| 9 = @returnstmt +| 10 = @throwstmt +| 11 = @breakstmt +| 12 = @continuestmt +| 13 = @emptystmt +| 14 = @exprstmt +| 15 = @labeledstmt +| 16 = @assertstmt +| 17 = @localvariabledeclstmt +| 18 = @localtypedeclstmt +| 19 = @constructorinvocationstmt +| 20 = @superconstructorinvocationstmt +| 21 = @case +| 22 = @catchclause +| 23 = @yieldstmt +| 24 = @errorstmt +| 25 = @whenbranch +; + +#keyset[parent,idx] +exprs( + unique int id: @expr, + int kind: int ref, + int typeid: @type ref, + int parent: @exprparent ref, + int idx: int ref +); + +exprsKotlinType( + unique int id: @expr ref, + int kttypeid: @kt_type ref +); + +callableEnclosingExpr( + unique int id: @expr ref, + int callable_id: @callable ref +); + +statementEnclosingExpr( + unique int id: @expr ref, + int statement_id: @stmt ref +); + +isParenthesized( + unique int id: @expr ref, + int parentheses: int ref +); + +case @expr.kind of + 1 = @arrayaccess +| 2 = @arraycreationexpr +| 3 = @arrayinit +| 4 = @assignexpr +| 5 = @assignaddexpr +| 6 = @assignsubexpr +| 7 = @assignmulexpr +| 8 = @assigndivexpr +| 9 = @assignremexpr +| 10 = @assignandexpr +| 11 = @assignorexpr +| 12 = @assignxorexpr +| 13 = @assignlshiftexpr +| 14 = @assignrshiftexpr +| 15 = @assignurshiftexpr +| 16 = @booleanliteral +| 17 = @integerliteral +| 18 = @longliteral +| 19 = @floatingpointliteral +| 20 = @doubleliteral +| 21 = @characterliteral +| 22 = @stringliteral +| 23 = @nullliteral +| 24 = @mulexpr +| 25 = @divexpr +| 26 = @remexpr +| 27 = @addexpr +| 28 = @subexpr +| 29 = @lshiftexpr +| 30 = @rshiftexpr +| 31 = @urshiftexpr +| 32 = @andbitexpr +| 33 = @orbitexpr +| 34 = @xorbitexpr +| 35 = @andlogicalexpr +| 36 = @orlogicalexpr +| 37 = @ltexpr +| 38 = @gtexpr +| 39 = @leexpr +| 40 = @geexpr +| 41 = @eqexpr +| 42 = @neexpr +| 43 = @postincexpr +| 44 = @postdecexpr +| 45 = @preincexpr +| 46 = @predecexpr +| 47 = @minusexpr +| 48 = @plusexpr +| 49 = @bitnotexpr +| 50 = @lognotexpr +| 51 = @castexpr +| 52 = @newexpr +| 53 = @conditionalexpr +| 54 = @parexpr // deprecated +| 55 = @instanceofexpr +| 56 = @localvariabledeclexpr +| 57 = @typeliteral +| 58 = @thisaccess +| 59 = @superaccess +| 60 = @varaccess +| 61 = @methodaccess +| 62 = @unannotatedtypeaccess +| 63 = @arraytypeaccess +| 64 = @packageaccess +| 65 = @wildcardtypeaccess +| 66 = @declannotation +| 67 = @uniontypeaccess +| 68 = @lambdaexpr +| 69 = @memberref +| 70 = @annotatedtypeaccess +| 71 = @typeannotation +| 72 = @intersectiontypeaccess +| 73 = @switchexpr +| 74 = @errorexpr +| 75 = @whenexpr +| 76 = @getclassexpr +| 77 = @safecastexpr +| 78 = @implicitcastexpr +| 79 = @implicitnotnullexpr +| 80 = @implicitcoerciontounitexpr +| 81 = @notinstanceofexpr +| 82 = @stmtexpr +| 83 = @stringtemplateexpr +| 84 = @notnullexpr +| 85 = @unsafecoerceexpr +| 86 = @valueeqexpr +| 87 = @valueneexpr +| 88 = @propertyref +; + +/** Holds if this `when` expression was written as an `if` expression. */ +when_if(unique int id: @whenexpr ref); + +/** Holds if this `when` branch was written as an `else` branch. */ +when_branch_else(unique int id: @whenbranch ref); + +@classinstancexpr = @newexpr | @lambdaexpr | @memberref | @propertyref + +@annotation = @declannotation | @typeannotation +@typeaccess = @unannotatedtypeaccess | @annotatedtypeaccess + +@assignment = @assignexpr + | @assignop; + +@unaryassignment = @postincexpr + | @postdecexpr + | @preincexpr + | @predecexpr; + +@assignop = @assignaddexpr + | @assignsubexpr + | @assignmulexpr + | @assigndivexpr + | @assignremexpr + | @assignandexpr + | @assignorexpr + | @assignxorexpr + | @assignlshiftexpr + | @assignrshiftexpr + | @assignurshiftexpr; + +@literal = @booleanliteral + | @integerliteral + | @longliteral + | @floatingpointliteral + | @doubleliteral + | @characterliteral + | @stringliteral + | @nullliteral; + +@binaryexpr = @mulexpr + | @divexpr + | @remexpr + | @addexpr + | @subexpr + | @lshiftexpr + | @rshiftexpr + | @urshiftexpr + | @andbitexpr + | @orbitexpr + | @xorbitexpr + | @andlogicalexpr + | @orlogicalexpr + | @ltexpr + | @gtexpr + | @leexpr + | @geexpr + | @eqexpr + | @neexpr + | @valueeqexpr + | @valueneexpr; + +@unaryexpr = @postincexpr + | @postdecexpr + | @preincexpr + | @predecexpr + | @minusexpr + | @plusexpr + | @bitnotexpr + | @lognotexpr + | @notnullexpr; + +@caller = @classinstancexpr + | @methodaccess + | @constructorinvocationstmt + | @superconstructorinvocationstmt; + +callableBinding( + unique int callerid: @caller ref, + int callee: @callable ref +); + +memberRefBinding( + unique int id: @expr ref, + int callable: @callable ref +); + +propertyRefGetBinding( + unique int id: @expr ref, + int getter: @callable ref +); + +propertyRefFieldBinding( + unique int id: @expr ref, + int field: @field ref +); + +propertyRefSetBinding( + unique int id: @expr ref, + int setter: @callable ref +); + +@exprparent = @stmt | @expr | @whenbranch | @callable | @field | @fielddecl | @classorinterface | @param | @localvar | @typevariable; + +variableBinding( + unique int expr: @varaccess ref, + int variable: @variable ref +); + +@variable = @localscopevariable | @field; + +@localscopevariable = @localvar | @param; + +localvars( + unique int id: @localvar, + string nodeName: string ref, + int typeid: @type ref, + int parentid: @localvariabledeclexpr ref +); + +localvarsKotlinType( + unique int id: @localvar ref, + int kttypeid: @kt_type ref +); + +@namedexprorstmt = @breakstmt + | @continuestmt + | @labeledstmt + | @literal; + +namestrings( + string name: string ref, + string value: string ref, + unique int parent: @namedexprorstmt ref +); + +/* + * Modules + */ + +#keyset[name] +modules( + unique int id: @module, + string name: string ref +); + +isOpen( + int id: @module ref +); + +#keyset[fileId] +cumodule( + int fileId: @file ref, + int moduleId: @module ref +); + +@directive = @requires + | @exports + | @opens + | @uses + | @provides + +#keyset[directive] +directives( + int id: @module ref, + int directive: @directive ref +); + +requires( + unique int id: @requires, + int target: @module ref +); + +isTransitive( + int id: @requires ref +); + +isStatic( + int id: @requires ref +); + +exports( + unique int id: @exports, + int target: @package ref +); + +exportsTo( + int id: @exports ref, + int target: @module ref +); + +opens( + unique int id: @opens, + int target: @package ref +); + +opensTo( + int id: @opens ref, + int target: @module ref +); + +uses( + unique int id: @uses, + string serviceInterface: string ref +); + +provides( + unique int id: @provides, + string serviceInterface: string ref +); + +providesWith( + int id: @provides ref, + string serviceImpl: string ref +); + +/* + * Javadoc + */ + +javadoc( + unique int id: @javadoc +); + +isNormalComment( + int commentid : @javadoc ref +); + +isEolComment( + int commentid : @javadoc ref +); + +hasJavadoc( + int documentableid: @member ref, + int javadocid: @javadoc ref +); + +#keyset[parentid,idx] +javadocTag( + unique int id: @javadocTag, + string name: string ref, + int parentid: @javadocParent ref, + int idx: int ref +); + +#keyset[parentid,idx] +javadocText( + unique int id: @javadocText, + string text: string ref, + int parentid: @javadocParent ref, + int idx: int ref +); + +@javadocParent = @javadoc | @javadocTag; +@javadocElement = @javadocTag | @javadocText; + +@classorinterfaceorpackage = @classorinterface | @package; +@classorinterfaceorcallable = @classorinterface | @callable; +@boundedtype = @typevariable | @wildcard; +@reftype = @classorinterface | @array | @boundedtype | @errortype; +@classorarray = @classorinterface | @array; +@type = @primitive | @reftype; +@callable = @method | @constructor; + +/** A program element that has a name. */ +@element = @package | @modifier | @annotation | @errortype | + @locatableElement; + +@locatableElement = @file | @primitive | @classorinterface | @method | @constructor | @param | @exception | @field | + @boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl | @kt_type | @kt_type_alias | + @kt_property; + +@modifiable = @member_modifiable| @param | @localvar | @typevariable; + +@member_modifiable = @classorinterface | @method | @constructor | @field | @kt_property; + +@member = @method | @constructor | @field | @reftype ; + +/** A program element that has a location. */ +@locatable = @typebound | @javadoc | @javadocTag | @javadocText | @xmllocatable | @ktcomment | + @locatableElement; + +@top = @element | @locatable | @folder; + +/* + * XML Files + */ + +xmlEncoding( + unique int id: @file ref, + string encoding: string ref +); + +xmlDTDs( + unique int id: @xmldtd, + string root: string ref, + string publicId: string ref, + string systemId: string ref, + int fileid: @file ref +); + +xmlElements( + unique int id: @xmlelement, + string name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref +); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + string name: string ref, + string value: string ref, + int idx: int ref, + int fileid: @file ref +); + +xmlNs( + int id: @xmlnamespace, + string prefixName: string ref, + string URI: string ref, + int fileid: @file ref +); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref +); + +xmlComments( + unique int id: @xmlcomment, + string text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref +); + +xmlChars( + unique int id: @xmlcharacters, + string text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref +); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref +); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +/* + * configuration files with key value pairs + */ + +configs( + unique int id: @config +); + +configNames( + unique int id: @configName, + int config: @config ref, + string name: string ref +); + +configValues( + unique int id: @configValue, + int config: @config ref, + string value: string ref +); + +configLocations( + int locatable: @configLocatable ref, + int location: @location_default ref +); + +@configLocatable = @config | @configName | @configValue; + +ktComments( + unique int id: @ktcomment, + int kind: int ref, + string text : string ref +) + +ktCommentSections( + unique int id: @ktcommentsection, + int comment: @ktcomment ref, + string content : string ref +) + +ktCommentSectionNames( + unique int id: @ktcommentsection ref, + string name : string ref +) + +ktCommentSectionSubjectNames( + unique int id: @ktcommentsection ref, + string subjectname : string ref +) + +#keyset[id, owner] +ktCommentOwners( + int id: @ktcomment ref, + int owner: @top ref +) + +ktExtensionFunctions( + unique int id: @method ref, + int typeid: @type ref, + int kttypeid: @kt_type ref +) + +ktProperties( + unique int id: @kt_property, + string nodeName: string ref +) + +ktPropertyGetters( + unique int id: @kt_property ref, + int getter: @method ref +) + +ktPropertySetters( + unique int id: @kt_property ref, + int setter: @method ref +) + +ktPropertyBackingFields( + unique int id: @kt_property ref, + int backingField: @field ref +) + +ktSyntheticBody( + unique int id: @callable ref, + int kind: int ref + // 1: ENUM_VALUES + // 2: ENUM_VALUEOF + // 3: ENUM_ENTRIES +) + +ktLocalFunction( + unique int id: @method ref +) + +ktInitializerAssignment( + unique int id: @assignexpr ref +) + +ktPropertyDelegates( + unique int id: @kt_property ref, + unique int variableId: @variable ref +) + +/** + * If `id` is a compiler generated element, then the kind indicates the + * reason that the compiler generated it. + * See `Element.compilerGeneratedReason()` for an explanation of what + * each `kind` means. + */ +compiler_generated( + unique int id: @element ref, + int kind: int ref +) + +ktFunctionOriginalNames( + unique int id: @method ref, + string name: string ref +) + +ktDataClasses( + unique int id: @classorinterface ref +) diff --git a/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/semmlecode.dbscheme b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/semmlecode.dbscheme new file mode 100644 index 00000000000..dee651b58d1 --- /dev/null +++ b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/semmlecode.dbscheme @@ -0,0 +1,1265 @@ +/** + * An invocation of the compiler. Note that more than one file may be + * compiled per invocation. For example, this command compiles three + * source files: + * + * javac A.java B.java C.java + * + * The `id` simply identifies the invocation, while `cwd` is the working + * directory from which the compiler was invoked. + */ +compilations( + /** + * An invocation of the compiler. Note that more than one file may + * be compiled per invocation. For example, this command compiles + * three source files: + * + * javac A.java B.java C.java + */ + unique int id : @compilation, + int kind: int ref, + string cwd : string ref, + string name : string ref +); + +case @compilation.kind of + 1 = @javacompilation +| 2 = @kotlincompilation +; + +compilation_started( + int id : @compilation ref +) + +compilation_info( + int id : @compilation ref, + string info_key: string ref, + string info_value: string ref +) + +/** + * The arguments that were passed to the extractor for a compiler + * invocation. If `id` is for the compiler invocation + * + * javac A.java B.java C.java + * + * then typically there will be rows for + * + * num | arg + * --- | --- + * 0 | *path to extractor* + * 1 | `--javac-args` + * 2 | A.java + * 3 | B.java + * 4 | C.java + */ +#keyset[id, num] +compilation_args( + int id : @compilation ref, + int num : int ref, + string arg : string ref +); + +/** + * The expanded arguments that were passed to the extractor for a + * compiler invocation. This is similar to `compilation_args`, but + * for a `@@@someFile` argument, it includes the arguments from that + * file, rather than just taking the argument literally. + */ +#keyset[id, num] +compilation_expanded_args( + int id : @compilation ref, + int num : int ref, + string arg : string ref +); + +/** + * The source files that are compiled by a compiler invocation. + * If `id` is for the compiler invocation + * + * javac A.java B.java C.java + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | A.java + * 1 | B.java + * 2 | C.java + */ +#keyset[id, num] +compilation_compiling_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * For each file recorded in `compilation_compiling_files`, + * there will be a corresponding row in + * `compilation_compiling_files_completed` once extraction + * of that file is complete. The `result` will indicate the + * extraction result: + * + * 0: Successfully extracted + * 1: Errors were encountered, but extraction recovered + * 2: Errors were encountered, and extraction could not recover + */ +#keyset[id, num] +compilation_compiling_files_completed( + int id : @compilation ref, + int num : int ref, + int result : int ref +); + +/** + * The time taken by the extractor for a compiler invocation. + * + * For each file `num`, there will be rows for + * + * kind | seconds + * ---- | --- + * 1 | CPU seconds used by the extractor frontend + * 2 | Elapsed seconds during the extractor frontend + * 3 | CPU seconds used by the extractor backend + * 4 | Elapsed seconds during the extractor backend + */ +#keyset[id, num, kind] +compilation_time( + int id : @compilation ref, + int num : int ref, + /* kind: + 1 = frontend_cpu_seconds + 2 = frontend_elapsed_seconds + 3 = extractor_cpu_seconds + 4 = extractor_elapsed_seconds + */ + int kind : int ref, + float seconds : float ref +); + +/** + * An error or warning generated by the extractor. + * The diagnostic message `diagnostic` was generated during compiler + * invocation `compilation`, and is the `file_number_diagnostic_number`th + * message generated while extracting the `file_number`th file of that + * invocation. + */ +#keyset[compilation, file_number, file_number_diagnostic_number] +diagnostic_for( + unique int diagnostic : @diagnostic ref, + int compilation : @compilation ref, + int file_number : int ref, + int file_number_diagnostic_number : int ref +); + +/** + * The `cpu_seconds` and `elapsed_seconds` are the CPU time and elapsed + * time (respectively) that the original compilation (not the extraction) + * took for compiler invocation `id`. + */ +compilation_compiler_times( + unique int id : @compilation ref, + float cpu_seconds : float ref, + float elapsed_seconds : float ref +); + +/** + * If extraction was successful, then `cpu_seconds` and + * `elapsed_seconds` are the CPU time and elapsed time (respectively) + * that extraction took for compiler invocation `id`. + * The `result` will indicate the extraction result: + * + * 0: Successfully extracted + * 1: Errors were encountered, but extraction recovered + * 2: Errors were encountered, and extraction could not recover + */ +compilation_finished( + unique int id : @compilation ref, + float cpu_seconds : float ref, + float elapsed_seconds : float ref, + int result : int ref +); + +diagnostics( + unique int id: @diagnostic, + string generated_by: string ref, // TODO: Sync this with the other languages? + int severity: int ref, + string error_tag: string ref, + string error_message: string ref, + string full_error_message: string ref, + int location: @location_default ref +); + +/* + * External artifacts + */ + +externalData( + int id : @externalDataElement, + string path : string ref, + int column: int ref, + string value : string ref +); + +snapshotDate( + unique date snapshotDate : date ref +); + +sourceLocationPrefix( + string prefix : string ref +); + +/* + * Duplicate code + */ + +duplicateCode( + unique int id : @duplication, + string relativePath : string ref, + int equivClass : int ref +); + +similarCode( + unique int id : @similarity, + string relativePath : string ref, + int equivClass : int ref +); + +@duplication_or_similarity = @duplication | @similarity + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref +); + +/* + * SMAP + */ + +smap_header( + int outputFileId: @file ref, + string outputFilename: string ref, + string defaultStratum: string ref +); + +smap_files( + int outputFileId: @file ref, + string stratum: string ref, + int inputFileNum: int ref, + string inputFileName: string ref, + int inputFileId: @file ref +); + +smap_lines( + int outputFileId: @file ref, + string stratum: string ref, + int inputFileNum: int ref, + int inputStartLine: int ref, + int inputLineCount: int ref, + int outputStartLine: int ref, + int outputLineIncrement: int ref +); + +/* + * Locations and files + */ + +@location = @location_default ; + +locations_default( + unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref +); + +hasLocation( + int locatableid: @locatable ref, + int id: @location ref +); + +@sourceline = @locatable ; + +#keyset[element_id] +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref +); + +files( + unique int id: @file, + string name: string ref +); + +folders( + unique int id: @folder, + string name: string ref +); + +@container = @folder | @file + +containerparent( + int parent: @container ref, + unique int child: @container ref +); + +/* + * Java + */ + +cupackage( + unique int id: @file ref, + int packageid: @package ref +); + +#keyset[fileid,keyName] +jarManifestMain( + int fileid: @file ref, + string keyName: string ref, + string value: string ref +); + +#keyset[fileid,entryName,keyName] +jarManifestEntries( + int fileid: @file ref, + string entryName: string ref, + string keyName: string ref, + string value: string ref +); + +packages( + unique int id: @package, + string nodeName: string ref +); + +primitives( + unique int id: @primitive, + string nodeName: string ref +); + +modifiers( + unique int id: @modifier, + string nodeName: string ref +); + +/** + * An errortype is used when the extractor is unable to extract a type + * correctly for some reason. + */ +error_type( + unique int id: @errortype +); + +classes_or_interfaces( + unique int id: @classorinterface, + string nodeName: string ref, + int parentid: @package ref, + int sourceid: @classorinterface ref +); + +file_class( + int id: @classorinterface ref +); + +class_object( + unique int id: @classorinterface ref, + unique int instance: @field ref +); + +type_companion_object( + unique int id: @classorinterface ref, + unique int instance: @field ref, + unique int companion_object: @classorinterface ref +); + +kt_nullable_types( + unique int id: @kt_nullable_type, + int classid: @reftype ref +) + +kt_notnull_types( + unique int id: @kt_notnull_type, + int classid: @reftype ref +) + +kt_type_alias( + unique int id: @kt_type_alias, + string name: string ref, + int kttypeid: @kt_type ref +) + +@kt_type = @kt_nullable_type | @kt_notnull_type + +isInterface( + unique int id: @classorinterface ref +); + +isRecord( + unique int id: @classorinterface ref +); + +fielddecls( + unique int id: @fielddecl, + int parentid: @reftype ref +); + +#keyset[fieldId] #keyset[fieldDeclId,pos] +fieldDeclaredIn( + int fieldId: @field ref, + int fieldDeclId: @fielddecl ref, + int pos: int ref +); + +fields( + unique int id: @field, + string nodeName: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @field ref +); + +fieldsKotlinType( + unique int id: @field ref, + int kttypeid: @kt_type ref +); + +constrs( + unique int id: @constructor, + string nodeName: string ref, + string signature: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @constructor ref +); + +constrsKotlinType( + unique int id: @constructor ref, + int kttypeid: @kt_type ref +); + +methods( + unique int id: @method, + string nodeName: string ref, + string signature: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @method ref +); + +methodsKotlinType( + unique int id: @method ref, + int kttypeid: @kt_type ref +); + +#keyset[parentid,pos] +params( + unique int id: @param, + int typeid: @type ref, + int pos: int ref, + int parentid: @callable ref, + int sourceid: @param ref +); + +paramsKotlinType( + unique int id: @param ref, + int kttypeid: @kt_type ref +); + +paramName( + unique int id: @param ref, + string nodeName: string ref +); + +isVarargsParam( + int param: @param ref +); + +exceptions( + unique int id: @exception, + int typeid: @type ref, + int parentid: @callable ref +); + +isAnnotType( + int interfaceid: @classorinterface ref +); + +isAnnotElem( + int methodid: @method ref +); + +annotValue( + int parentid: @annotation ref, + int id2: @method ref, + unique int value: @expr ref +); + +isEnumType( + int classid: @classorinterface ref +); + +isEnumConst( + int fieldid: @field ref +); + +#keyset[parentid,pos] +typeVars( + unique int id: @typevariable, + string nodeName: string ref, + int pos: int ref, + int kind: int ref, // deprecated + int parentid: @classorinterfaceorcallable ref +); + +wildcards( + unique int id: @wildcard, + string nodeName: string ref, + int kind: int ref +); + +#keyset[parentid,pos] +typeBounds( + unique int id: @typebound, + int typeid: @reftype ref, + int pos: int ref, + int parentid: @boundedtype ref +); + +#keyset[parentid,pos] +typeArgs( + int argumentid: @reftype ref, + int pos: int ref, + int parentid: @classorinterfaceorcallable ref +); + +isParameterized( + int memberid: @member ref +); + +isRaw( + int memberid: @member ref +); + +erasure( + unique int memberid: @member ref, + int erasureid: @member ref +); + +#keyset[classid] #keyset[parent] +isAnonymClass( + int classid: @classorinterface ref, + int parent: @classinstancexpr ref +); + +#keyset[typeid] #keyset[parent] +isLocalClassOrInterface( + int typeid: @classorinterface ref, + int parent: @localtypedeclstmt ref +); + +isDefConstr( + int constructorid: @constructor ref +); + +#keyset[exprId] +lambdaKind( + int exprId: @lambdaexpr ref, + int bodyKind: int ref +); + +isCanonicalConstr( + int constructorid: @constructor ref +); + +arrays( + unique int id: @array, + string nodeName: string ref, + int elementtypeid: @type ref, + int dimension: int ref, + int componenttypeid: @type ref +); + +enclInReftype( + unique int child: @reftype ref, + int parent: @reftype ref +); + +extendsReftype( + int id1: @reftype ref, + int id2: @classorinterface ref +); + +implInterface( + int id1: @classorarray ref, + int id2: @classorinterface ref +); + +permits( + int id1: @classorinterface ref, + int id2: @classorinterface ref +); + +hasModifier( + int id1: @modifiable ref, + int id2: @modifier ref +); + +imports( + unique int id: @import, + int holder: @classorinterfaceorpackage ref, + string name: string ref, + int kind: int ref +); + +#keyset[parent,idx] +stmts( + unique int id: @stmt, + int kind: int ref, + int parent: @stmtparent ref, + int idx: int ref, + int bodydecl: @callable ref +); + +@stmtparent = @callable | @stmt | @switchexpr | @whenexpr| @stmtexpr; + +case @stmt.kind of + 0 = @block +| 1 = @ifstmt +| 2 = @forstmt +| 3 = @enhancedforstmt +| 4 = @whilestmt +| 5 = @dostmt +| 6 = @trystmt +| 7 = @switchstmt +| 8 = @synchronizedstmt +| 9 = @returnstmt +| 10 = @throwstmt +| 11 = @breakstmt +| 12 = @continuestmt +| 13 = @emptystmt +| 14 = @exprstmt +| 15 = @labeledstmt +| 16 = @assertstmt +| 17 = @localvariabledeclstmt +| 18 = @localtypedeclstmt +| 19 = @constructorinvocationstmt +| 20 = @superconstructorinvocationstmt +| 21 = @case +| 22 = @catchclause +| 23 = @yieldstmt +| 24 = @errorstmt +| 25 = @whenbranch +; + +#keyset[parent,idx] +exprs( + unique int id: @expr, + int kind: int ref, + int typeid: @type ref, + int parent: @exprparent ref, + int idx: int ref +); + +exprsKotlinType( + unique int id: @expr ref, + int kttypeid: @kt_type ref +); + +callableEnclosingExpr( + unique int id: @expr ref, + int callable_id: @callable ref +); + +statementEnclosingExpr( + unique int id: @expr ref, + int statement_id: @stmt ref +); + +isParenthesized( + unique int id: @expr ref, + int parentheses: int ref +); + +case @expr.kind of + 1 = @arrayaccess +| 2 = @arraycreationexpr +| 3 = @arrayinit +| 4 = @assignexpr +| 5 = @assignaddexpr +| 6 = @assignsubexpr +| 7 = @assignmulexpr +| 8 = @assigndivexpr +| 9 = @assignremexpr +| 10 = @assignandexpr +| 11 = @assignorexpr +| 12 = @assignxorexpr +| 13 = @assignlshiftexpr +| 14 = @assignrshiftexpr +| 15 = @assignurshiftexpr +| 16 = @booleanliteral +| 17 = @integerliteral +| 18 = @longliteral +| 19 = @floatingpointliteral +| 20 = @doubleliteral +| 21 = @characterliteral +| 22 = @stringliteral +| 23 = @nullliteral +| 24 = @mulexpr +| 25 = @divexpr +| 26 = @remexpr +| 27 = @addexpr +| 28 = @subexpr +| 29 = @lshiftexpr +| 30 = @rshiftexpr +| 31 = @urshiftexpr +| 32 = @andbitexpr +| 33 = @orbitexpr +| 34 = @xorbitexpr +| 35 = @andlogicalexpr +| 36 = @orlogicalexpr +| 37 = @ltexpr +| 38 = @gtexpr +| 39 = @leexpr +| 40 = @geexpr +| 41 = @eqexpr +| 42 = @neexpr +| 43 = @postincexpr +| 44 = @postdecexpr +| 45 = @preincexpr +| 46 = @predecexpr +| 47 = @minusexpr +| 48 = @plusexpr +| 49 = @bitnotexpr +| 50 = @lognotexpr +| 51 = @castexpr +| 52 = @newexpr +| 53 = @conditionalexpr +| 54 = @parexpr // deprecated +| 55 = @instanceofexpr +| 56 = @localvariabledeclexpr +| 57 = @typeliteral +| 58 = @thisaccess +| 59 = @superaccess +| 60 = @varaccess +| 61 = @methodaccess +| 62 = @unannotatedtypeaccess +| 63 = @arraytypeaccess +| 64 = @packageaccess +| 65 = @wildcardtypeaccess +| 66 = @declannotation +| 67 = @uniontypeaccess +| 68 = @lambdaexpr +| 69 = @memberref +| 70 = @annotatedtypeaccess +| 71 = @typeannotation +| 72 = @intersectiontypeaccess +| 73 = @switchexpr +| 74 = @errorexpr +| 75 = @whenexpr +| 76 = @getclassexpr +| 77 = @safecastexpr +| 78 = @implicitcastexpr +| 79 = @implicitnotnullexpr +| 80 = @implicitcoerciontounitexpr +| 81 = @notinstanceofexpr +| 82 = @stmtexpr +| 83 = @stringtemplateexpr +| 84 = @notnullexpr +| 85 = @unsafecoerceexpr +| 86 = @valueeqexpr +| 87 = @valueneexpr +| 88 = @propertyref +| 89 = @recordpatternexpr +; + +/** Holds if this `when` expression was written as an `if` expression. */ +when_if(unique int id: @whenexpr ref); + +/** Holds if this `when` branch was written as an `else` branch. */ +when_branch_else(unique int id: @whenbranch ref); + +@classinstancexpr = @newexpr | @lambdaexpr | @memberref | @propertyref + +@annotation = @declannotation | @typeannotation +@typeaccess = @unannotatedtypeaccess | @annotatedtypeaccess + +@assignment = @assignexpr + | @assignop; + +@unaryassignment = @postincexpr + | @postdecexpr + | @preincexpr + | @predecexpr; + +@assignop = @assignaddexpr + | @assignsubexpr + | @assignmulexpr + | @assigndivexpr + | @assignremexpr + | @assignandexpr + | @assignorexpr + | @assignxorexpr + | @assignlshiftexpr + | @assignrshiftexpr + | @assignurshiftexpr; + +@literal = @booleanliteral + | @integerliteral + | @longliteral + | @floatingpointliteral + | @doubleliteral + | @characterliteral + | @stringliteral + | @nullliteral; + +@binaryexpr = @mulexpr + | @divexpr + | @remexpr + | @addexpr + | @subexpr + | @lshiftexpr + | @rshiftexpr + | @urshiftexpr + | @andbitexpr + | @orbitexpr + | @xorbitexpr + | @andlogicalexpr + | @orlogicalexpr + | @ltexpr + | @gtexpr + | @leexpr + | @geexpr + | @eqexpr + | @neexpr + | @valueeqexpr + | @valueneexpr; + +@unaryexpr = @postincexpr + | @postdecexpr + | @preincexpr + | @predecexpr + | @minusexpr + | @plusexpr + | @bitnotexpr + | @lognotexpr + | @notnullexpr; + +@caller = @classinstancexpr + | @methodaccess + | @constructorinvocationstmt + | @superconstructorinvocationstmt; + +callableBinding( + unique int callerid: @caller ref, + int callee: @callable ref +); + +memberRefBinding( + unique int id: @expr ref, + int callable: @callable ref +); + +propertyRefGetBinding( + unique int id: @expr ref, + int getter: @callable ref +); + +propertyRefFieldBinding( + unique int id: @expr ref, + int field: @field ref +); + +propertyRefSetBinding( + unique int id: @expr ref, + int setter: @callable ref +); + +@exprparent = @stmt | @expr | @whenbranch | @callable | @field | @fielddecl | @classorinterface | @param | @localvar | @typevariable; + +variableBinding( + unique int expr: @varaccess ref, + int variable: @variable ref +); + +@variable = @localscopevariable | @field; + +@localscopevariable = @localvar | @param; + +localvars( + unique int id: @localvar, + string nodeName: string ref, + int typeid: @type ref, + int parentid: @localvariabledeclexpr ref +); + +localvarsKotlinType( + unique int id: @localvar ref, + int kttypeid: @kt_type ref +); + +@namedexprorstmt = @breakstmt + | @continuestmt + | @labeledstmt + | @literal; + +namestrings( + string name: string ref, + string value: string ref, + unique int parent: @namedexprorstmt ref +); + +/* + * Modules + */ + +#keyset[name] +modules( + unique int id: @module, + string name: string ref +); + +isOpen( + int id: @module ref +); + +#keyset[fileId] +cumodule( + int fileId: @file ref, + int moduleId: @module ref +); + +@directive = @requires + | @exports + | @opens + | @uses + | @provides + +#keyset[directive] +directives( + int id: @module ref, + int directive: @directive ref +); + +requires( + unique int id: @requires, + int target: @module ref +); + +isTransitive( + int id: @requires ref +); + +isStatic( + int id: @requires ref +); + +exports( + unique int id: @exports, + int target: @package ref +); + +exportsTo( + int id: @exports ref, + int target: @module ref +); + +opens( + unique int id: @opens, + int target: @package ref +); + +opensTo( + int id: @opens ref, + int target: @module ref +); + +uses( + unique int id: @uses, + string serviceInterface: string ref +); + +provides( + unique int id: @provides, + string serviceInterface: string ref +); + +providesWith( + int id: @provides ref, + string serviceImpl: string ref +); + +isNullDefaultCase( + int id: @case ref +); + +/* + * Javadoc + */ + +javadoc( + unique int id: @javadoc +); + +isNormalComment( + int commentid : @javadoc ref +); + +isEolComment( + int commentid : @javadoc ref +); + +hasJavadoc( + int documentableid: @member ref, + int javadocid: @javadoc ref +); + +#keyset[parentid,idx] +javadocTag( + unique int id: @javadocTag, + string name: string ref, + int parentid: @javadocParent ref, + int idx: int ref +); + +#keyset[parentid,idx] +javadocText( + unique int id: @javadocText, + string text: string ref, + int parentid: @javadocParent ref, + int idx: int ref +); + +@javadocParent = @javadoc | @javadocTag; +@javadocElement = @javadocTag | @javadocText; + +@classorinterfaceorpackage = @classorinterface | @package; +@classorinterfaceorcallable = @classorinterface | @callable; +@boundedtype = @typevariable | @wildcard; +@reftype = @classorinterface | @array | @boundedtype | @errortype; +@classorarray = @classorinterface | @array; +@type = @primitive | @reftype; +@callable = @method | @constructor; + +/** A program element that has a name. */ +@element = @package | @modifier | @annotation | @errortype | + @locatableElement; + +@locatableElement = @file | @primitive | @classorinterface | @method | @constructor | @param | @exception | @field | + @boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl | @kt_type | @kt_type_alias | + @kt_property; + +@modifiable = @member_modifiable| @param | @localvar | @typevariable; + +@member_modifiable = @classorinterface | @method | @constructor | @field | @kt_property; + +@member = @method | @constructor | @field | @reftype ; + +/** A program element that has a location. */ +@locatable = @typebound | @javadoc | @javadocTag | @javadocText | @xmllocatable | @ktcomment | + @locatableElement; + +@top = @element | @locatable | @folder; + +/* + * XML Files + */ + +xmlEncoding( + unique int id: @file ref, + string encoding: string ref +); + +xmlDTDs( + unique int id: @xmldtd, + string root: string ref, + string publicId: string ref, + string systemId: string ref, + int fileid: @file ref +); + +xmlElements( + unique int id: @xmlelement, + string name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref +); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + string name: string ref, + string value: string ref, + int idx: int ref, + int fileid: @file ref +); + +xmlNs( + int id: @xmlnamespace, + string prefixName: string ref, + string URI: string ref, + int fileid: @file ref +); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref +); + +xmlComments( + unique int id: @xmlcomment, + string text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref +); + +xmlChars( + unique int id: @xmlcharacters, + string text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref +); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref +); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +/* + * configuration files with key value pairs + */ + +configs( + unique int id: @config +); + +configNames( + unique int id: @configName, + int config: @config ref, + string name: string ref +); + +configValues( + unique int id: @configValue, + int config: @config ref, + string value: string ref +); + +configLocations( + int locatable: @configLocatable ref, + int location: @location_default ref +); + +@configLocatable = @config | @configName | @configValue; + +ktComments( + unique int id: @ktcomment, + int kind: int ref, + string text : string ref +) + +ktCommentSections( + unique int id: @ktcommentsection, + int comment: @ktcomment ref, + string content : string ref +) + +ktCommentSectionNames( + unique int id: @ktcommentsection ref, + string name : string ref +) + +ktCommentSectionSubjectNames( + unique int id: @ktcommentsection ref, + string subjectname : string ref +) + +#keyset[id, owner] +ktCommentOwners( + int id: @ktcomment ref, + int owner: @top ref +) + +ktExtensionFunctions( + unique int id: @method ref, + int typeid: @type ref, + int kttypeid: @kt_type ref +) + +ktProperties( + unique int id: @kt_property, + string nodeName: string ref +) + +ktPropertyGetters( + unique int id: @kt_property ref, + int getter: @method ref +) + +ktPropertySetters( + unique int id: @kt_property ref, + int setter: @method ref +) + +ktPropertyBackingFields( + unique int id: @kt_property ref, + int backingField: @field ref +) + +ktSyntheticBody( + unique int id: @callable ref, + int kind: int ref + // 1: ENUM_VALUES + // 2: ENUM_VALUEOF + // 3: ENUM_ENTRIES +) + +ktLocalFunction( + unique int id: @method ref +) + +ktInitializerAssignment( + unique int id: @assignexpr ref +) + +ktPropertyDelegates( + unique int id: @kt_property ref, + unique int variableId: @variable ref +) + +/** + * If `id` is a compiler generated element, then the kind indicates the + * reason that the compiler generated it. + * See `Element.compilerGeneratedReason()` for an explanation of what + * each `kind` means. + */ +compiler_generated( + unique int id: @element ref, + int kind: int ref +) + +ktFunctionOriginalNames( + unique int id: @method ref, + string name: string ref +) + +ktDataClasses( + unique int id: @classorinterface ref +) diff --git a/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/upgrade.properties b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/upgrade.properties new file mode 100644 index 00000000000..baa3016cf03 --- /dev/null +++ b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/upgrade.properties @@ -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 From 04c9f60d96e3db9770d9f0b7b248f2123c9e75dc Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 2 Nov 2023 22:48:40 +0000 Subject: [PATCH 033/115] Make up some stats for new expressions, setting canonical constructors as common as records and new expression kinds initially rare --- java/ql/lib/config/semmlecode.dbscheme.stats | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/java/ql/lib/config/semmlecode.dbscheme.stats b/java/ql/lib/config/semmlecode.dbscheme.stats index f069e1773d8..c7b35b17ef7 100644 --- a/java/ql/lib/config/semmlecode.dbscheme.stats +++ b/java/ql/lib/config/semmlecode.dbscheme.stats @@ -568,6 +568,10 @@ @propertyref 8439 + + @recordpatternexpr + 50 + @localvar 385272 @@ -9478,6 +9482,17 @@ + + isCanonicalConstr + 417 + + + constructorid + 417 + + + + fielddecls 210035 @@ -26950,5 +26965,16 @@ + + isNullDefaultCase + 50 + + + id + 50 + + + + From 1d82756dc8f1c82bbf77005c8267204bb50a0e53 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 3 Nov 2023 16:24:09 +0000 Subject: [PATCH 034/115] Fix downgrade script --- .../dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql index 2a6d49da2c0..6063f7dca75 100644 --- a/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql +++ b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql @@ -35,7 +35,7 @@ predicate hasNewParent(Expr e, ExprParent newParent, int newIndex) { } from Expr e, int oldKind, int newKind, Type typeid, ExprParent parent, int index -where exprs(e, kind, typeid, _, _) and +where exprs(e, oldKind, typeid, _, _) and hasNewParent(e, parent, index) and (if oldKind = 89 /* record pattern */ then newKind = 74 /* error expression */ else oldKind = newKind) select e, newKind, typeid, parent, index From db5979f1ac66e80c8702cb1ee4d3bb56706649f4 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 3 Nov 2023 16:26:40 +0000 Subject: [PATCH 035/115] Autoformat upgrade/downgrade scripts --- .../exprs.ql | 40 +++++++++++++------ .../exprs.ql | 40 +++++++++++++------ 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql index 6063f7dca75..aba4fd5a39d 100644 --- a/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql +++ b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql @@ -1,27 +1,37 @@ -class Expr extends @expr { string toString() { result = "expr" } } +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" } } + +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) | + e = getNthChild(oldParent, oldIndex) + | oldIndex = 0 and newParent = getNthChild(oldParent, 2) and newIndex = 0 or oldIndex = 1 and newParent = oldParent and newIndex = oldIndex @@ -31,11 +41,15 @@ predicate hasNewParent(Expr e, ExprParent newParent, int newIndex) { 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 = 89 /* record pattern */ then newKind = 74 /* error expression */ else oldKind = newKind) +where + exprs(e, oldKind, typeid, _, _) and + hasNewParent(e, parent, index) and + ( + if oldKind = 89 + then /* record pattern */ newKind = 74 + else /* error expression */ oldKind = newKind + ) select e, newKind, typeid, parent, index diff --git a/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/exprs.ql b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/exprs.ql index d59acad79fa..31493cb5723 100644 --- a/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/exprs.ql +++ b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/exprs.ql @@ -1,8 +1,18 @@ -class Expr extends @expr { string toString() { result = "expr" } } +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" } } + +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. @@ -12,20 +22,26 @@ class ExprParent extends @exprparent { string toString() { result = "exprparent" // \-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 (newParent = getParent(getParent(e)) and newIndex = 0) // Initialiser moves to hang directly off the instanceof expression - else ( - if (e instanceof LocalVariableDeclExpr and getParent(e) instanceof InstanceOfExpr) - then (newParent = getParent(e) and newIndex = 2) // Variable declaration moves to be the instanceof expression's 2nd child - else exprs(e, _, _, newParent, newIndex) // Other expressions unchanged + if + getParent(e) instanceof LocalVariableDeclExpr and + getParent(getParent(e)) instanceof InstanceOfExpr + then ( + newParent = getParent(getParent(e)) and newIndex = 0 + ) else ( + // Initialiser moves to hang directly off the instanceof expression + if e instanceof LocalVariableDeclExpr and getParent(e) instanceof InstanceOfExpr + then newParent = getParent(e) and newIndex = 2 + else + // Variable declaration moves to be the instanceof expression's 2nd child + 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) +where + exprs(e, kind, typeid, _, _) and + hasNewParent(e, parent, index) select e, kind, typeid, parent, index From ded8deceaac474903115116c7b55c8763c70ac48 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 3 Nov 2023 16:35:13 +0000 Subject: [PATCH 036/115] Add missing qldoc --- java/ql/lib/semmle/code/java/Expr.qll | 3 +++ java/ql/lib/semmle/code/java/Statement.qll | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index 16dbf84c556..3a2ca2d7239 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -2556,5 +2556,8 @@ class RecordPatternExpr extends Expr, @recordpatternexpr { override string getAPrimaryQlClass() { result = "RecordPatternExpr" } + /** + * Gets the `i`th subpattern of this record pattern. + */ Pattern getSubPattern(int i) { result.isNthChildOf(this, i) } } diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index f274a9a4bfc..8017b73c46b 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -542,8 +542,14 @@ class Pattern extends Expr { (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 } } From 023615386b6a96412facfef36e11e9f219acafc7 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 3 Nov 2023 16:44:36 +0000 Subject: [PATCH 037/115] Add change note --- java/ql/lib/change-notes/2023-11-03-jdk21-support.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 java/ql/lib/change-notes/2023-11-03-jdk21-support.yml diff --git a/java/ql/lib/change-notes/2023-11-03-jdk21-support.yml b/java/ql/lib/change-notes/2023-11-03-jdk21-support.yml new file mode 100644 index 00000000000..bf11bac4f8e --- /dev/null +++ b/java/ql/lib/change-notes/2023-11-03-jdk21-support.yml @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Switch cases using patterns and both cases and instanceof expressions using record patterns are now supported. The new class `RecordPatternExpr` is introduced to represent patterns, `PatternCase` and `CaseNullDefault` to represent new kinds of case statement, `InstanceOfExpr` gains `getPattern` to replace `getLocalVariableDeclExpr`. From 11444a3ae70cc72b6b784f70a34c5d4969b690c1 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 3 Nov 2023 16:49:23 +0000 Subject: [PATCH 038/115] Rename Pattern to PatternExpr to avoid clashing with Regex::Pattern --- java/ql/lib/semmle/code/java/Expr.qll | 4 ++-- java/ql/lib/semmle/code/java/Statement.qll | 12 ++++++------ .../code/java/dataflow/internal/DataFlowPrivate.qll | 2 +- .../code/java/dataflow/internal/DataFlowUtil.qll | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index 3a2ca2d7239..38261d68252 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1563,7 +1563,7 @@ class InstanceOfExpr extends Expr, @instanceofexpr { /** * Gets the pattern of an `x instanceof T pattern` expression, if any. */ - Pattern getPattern() { result.isNthChildOf(this, 2) } + PatternExpr getPattern() { result.isNthChildOf(this, 2) } /** * Holds if this `instanceof` expression uses pattern matching. @@ -2559,5 +2559,5 @@ class RecordPatternExpr extends Expr, @recordpatternexpr { /** * Gets the `i`th subpattern of this record pattern. */ - Pattern getSubPattern(int i) { result.isNthChildOf(this, i) } + PatternExpr getSubPattern(int i) { result.isNthChildOf(this, i) } } diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index 8017b73c46b..d56fa52bcc5 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -504,7 +504,7 @@ class SwitchCase extends Stmt, @case { */ class ConstCase extends SwitchCase { ConstCase() { - exists(Expr e | e.getParent() = this and e.getIndex() >= 0 and not e instanceof Pattern) and + 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) @@ -532,12 +532,12 @@ class ConstCase extends SwitchCase { * * Note binding patterns are represented as `LocalVariableDeclExpr`s. */ -class Pattern extends Expr { - Pattern() { +class PatternExpr extends Expr { + PatternExpr() { ( this.getParent() instanceof SwitchCase or this.getParent() instanceof InstanceOfExpr or - this.getParent() instanceof Pattern + this.getParent() instanceof PatternExpr ) and (this instanceof LocalVariableDeclExpr or this instanceof RecordPatternExpr) } @@ -555,12 +555,12 @@ class Pattern extends Expr { /** A pattern case of a `switch` statement */ class PatternCase extends SwitchCase { - Pattern pattern; + PatternExpr pattern; PatternCase() { pattern.isNthChildOf(this, 0) } /** Gets this case's pattern. */ - Pattern getPattern() { result.isNthChildOf(this, 0) } + PatternExpr getPattern() { result = pattern } /** Gets the guard applicable to this pattern case, if any. */ Expr getGuard() { result.isNthChildOf(this, -3) } diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll index 1091c3d4278..b6c06f8dde3 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll @@ -267,7 +267,7 @@ predicate readStep(Node node1, ContentSet f, Node node2) { node2.asExpr() = get ) or - exists(RecordPatternExpr rpe, Pattern subPattern, int i | + exists(RecordPatternExpr rpe, PatternExpr subPattern, int i | node1.asExpr() = rpe and subPattern = rpe.getSubPattern(i) and node2.asExpr() = subPattern and diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll index d587070acaf..3a2cf884bed 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll @@ -204,7 +204,7 @@ private predicate simpleLocalFlowStep0(Node node1, Node node2) { exists(SsaExplicitUpdate upd | upd.getDefiningExpr().(VariableAssign).getSource() = node1.asExpr() or upd.getDefiningExpr().(AssignOp) = node1.asExpr() or - upd.getDefiningExpr().(Pattern).asBindingPattern() = node1.asExpr() + upd.getDefiningExpr().(PatternExpr).asBindingPattern() = node1.asExpr() | node2.asExpr() = upd.getAFirstUse() and not capturedVariableRead(node2) From a335109a202bde02a368e60eb1f4a5b452950dbf Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 3 Nov 2023 17:21:28 +0000 Subject: [PATCH 039/115] Note that instanceof with a record pattern doesn't have a type access --- java/ql/consistency-queries/children.ql | 4 +++- java/ql/lib/semmle/code/java/Expr.qll | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/java/ql/consistency-queries/children.ql b/java/ql/consistency-queries/children.ql index 4771110ecdb..726b9eddfc1 100644 --- a/java/ql/consistency-queries/children.ql +++ b/java/ql/consistency-queries/children.ql @@ -48,7 +48,9 @@ predicate gapInChildren(Element e, int i) { // is able. 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()) + 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) } predicate lateFirstChild(Element e, int i) { diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index 38261d68252..7831146ee7a 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1578,11 +1578,23 @@ class InstanceOfExpr extends Expr, @instanceofexpr { */ 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. + */ + RefType getCheckedType() { + result = this.getTypeName().getType() + or + result = this.getPattern().asRecordPattern().getType() + } /** Gets a printable representation of this expression. */ override string toString() { result = "...instanceof..." } From e5fdf4dd5092a3f4dee598be814f4e6a081a6f2c Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 3 Nov 2023 17:24:50 +0000 Subject: [PATCH 040/115] Update test expectation --- java/ql/test/library-tests/printAst/PrintAst.expected | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/ql/test/library-tests/printAst/PrintAst.expected b/java/ql/test/library-tests/printAst/PrintAst.expected index 62b8b44fee3..2288b40ba76 100644 --- a/java/ql/test/library-tests/printAst/PrintAst.expected +++ b/java/ql/test/library-tests/printAst/PrintAst.expected @@ -110,10 +110,10 @@ 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(...) From 2a6e86633d99450ea27a0c0361d1e8ec222231d0 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 3 Nov 2023 17:27:57 +0000 Subject: [PATCH 041/115] Improve qldoc --- java/ql/lib/semmle/code/java/Statement.qll | 2 +- java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index d56fa52bcc5..3255f7a2f2f 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -514,7 +514,7 @@ class ConstCase extends SwitchCase { 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.isNthChildOf(this, i) and i >= 0 } diff --git a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll index 19014d18752..543bc5d3f44 100644 --- a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll @@ -441,7 +441,7 @@ private predicate instanceOfGuarded(Expr e, RefType t) { } /** - * Holds if `va` is an access to a value that is guarded by `case T t`. + * Holds if `e` is an access to a value that is guarded by `case T t`. */ private predicate patternCaseGuarded(Expr e, RefType t) { exists(PatternCase pc | From 7106ec77bcfedfd36eae7fd65fa84f6443dea830 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 3 Nov 2023 17:32:44 +0000 Subject: [PATCH 042/115] Fix change note --- .../{2023-11-03-jdk21-support.yml => 2023-11-03-jdk21-support.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename java/ql/lib/change-notes/{2023-11-03-jdk21-support.yml => 2023-11-03-jdk21-support.md} (100%) diff --git a/java/ql/lib/change-notes/2023-11-03-jdk21-support.yml b/java/ql/lib/change-notes/2023-11-03-jdk21-support.md similarity index 100% rename from java/ql/lib/change-notes/2023-11-03-jdk21-support.yml rename to java/ql/lib/change-notes/2023-11-03-jdk21-support.md From 9035ba1f30e50e09732748f50c4c254869ed60a5 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Mon, 6 Nov 2023 11:49:44 +0000 Subject: [PATCH 043/115] Fix isImplicitInit; use it in empty-container query --- java/ql/lib/semmle/code/java/Expr.qll | 27 ++++++++++++++++--- .../Collections/ReadOnlyContainer.ql | 4 +-- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index 7831146ee7a..d691bd3fe20 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1657,14 +1657,32 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr { /** * Gets the switch statement or expression whose pattern declares this identifier, if any. - * - * Note this only applies to a direct binding pattern, such as `case T t`, not a record pattern. */ - StmtParent getAssociatedSwitch() { result = this.getParent().(PatternCase).getParent() } + StmtParent 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 f 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. */ Expr getInit() { result.isNthChildOf(this, 0) } @@ -1672,7 +1690,8 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr { predicate hasImplicitInit() { exists(CatchClause cc | cc.getVariable() = this) or exists(EnhancedForStmt efs | efs.getVariable() = this) or - this.hasAssociatedSwitch() + this.hasAssociatedSwitch() or + this.hasAssociatedInstanceOfExpr() } /** Gets a printable representation of this expression. */ diff --git a/java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.ql b/java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.ql index a3fb91e99b6..5b90341660b 100644 --- a/java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.ql +++ b/java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.ql @@ -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." From 0e3f6f78730d70e7a2e56dff29a27365c5b0dc87 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Mon, 6 Nov 2023 12:16:40 +0000 Subject: [PATCH 044/115] autoformat --- java/ql/lib/semmle/code/java/Expr.qll | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index d691bd3fe20..5e0ae454214 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1674,14 +1674,10 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr { /** * Gets the instanceof expression whose pattern declares this identifier, if any. */ - InstanceOfExpr getAssociatedInstanceOfExpr() { - result.getPattern().getAChildExpr*() = this - } + InstanceOfExpr getAssociatedInstanceOfExpr() { result.getPattern().getAChildExpr*() = this } /** Holds f this is a declaration stemming from a pattern instanceof expression. */ - predicate hasAssociatedInstanceOfExpr() { - exists(this.getAssociatedInstanceOfExpr()) - } + predicate hasAssociatedInstanceOfExpr() { exists(this.getAssociatedInstanceOfExpr()) } /** Gets the initializer expression of this local variable declaration expression, if any. */ Expr getInit() { result.isNthChildOf(this, 0) } From bb6e04456af32b0ce4a12f91bc88ab533a6fb7f1 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Mon, 6 Nov 2023 17:55:31 +0000 Subject: [PATCH 045/115] Boxed variable query: account for implicit-init variables --- .../Violations of Best Practice/Boxed Types/BoxedVariable.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.ql b/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.ql index 33c16eb598c..1749dc2ffa4 100644 --- a/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.ql +++ b/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.ql @@ -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 From 91774099fad685edd5a0fb022e5e1ef7904a82b8 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 9 Nov 2023 15:29:57 +0000 Subject: [PATCH 046/115] Write-only container query: account for implicitly-initialised variables --- java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.ql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.ql b/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.ql index 8002c8d6841..8c8cb6105b3 100644 --- a/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.ql +++ b/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.ql @@ -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." From 88d9caff8c15e6e6d758a965669a2c7837959e0b Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 9 Nov 2023 15:47:52 +0000 Subject: [PATCH 047/115] Unused local query: exclude mandatory declarations --- .../Dead Code/UnusedLocal.ql | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.ql b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.ql index dbed42872c9..b96ce5069ac 100644 --- a/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.ql +++ b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.ql @@ -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." From fa09be04592164dc5555d4b8636737b633b2ace7 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 9 Nov 2023 15:48:24 +0000 Subject: [PATCH 048/115] Ensure pattern-case and binding-instanceof are covered in all of type, dispatch and object flow --- .../lib/semmle/code/java/dataflow/TypeFlow.qll | 16 ++++++++++++++++ .../semmle/code/java/dispatch/DispatchFlow.qll | 14 ++++++++++++++ .../ql/lib/semmle/code/java/dispatch/ObjFlow.qll | 5 +++++ 3 files changed, 35 insertions(+) diff --git a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll index 543bc5d3f44..dfbf957f18c 100644 --- a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll @@ -116,6 +116,22 @@ private predicate step(TypeFlowNode n1, TypeFlowNode n2) { n2.asSsa().(BaseSsaUpdate).getDefiningExpr().(VariableAssign).getSource() = n1.asExpr() or n2.asSsa().(BaseSsaImplicitInit).captures(n1.asSsa()) + or + exists(PatternCase pc, LocalVariableDeclExpr patternVar | + patternVar = pc.getPattern().asBindingPattern() and + n2.asSsa().(BaseSsaUpdate).getDefiningExpr() = patternVar and + ( + pc.getSwitch().getExpr() = n1.asExpr() + or + pc.getSwitchExpr().getExpr() = n1.asExpr() + ) + ) + or + exists(InstanceOfExpr ioe, LocalVariableDeclExpr patternVar | + patternVar = ioe.getPattern().asBindingPattern() and + n2.asSsa().(BaseSsaUpdate).getDefiningExpr() = patternVar and + ioe.getExpr() = n1.asExpr() + ) } /** diff --git a/java/ql/lib/semmle/code/java/dispatch/DispatchFlow.qll b/java/ql/lib/semmle/code/java/dispatch/DispatchFlow.qll index 82bda033bc6..ae7ef09275a 100644 --- a/java/ql/lib/semmle/code/java/dispatch/DispatchFlow.qll +++ b/java/ql/lib/semmle/code/java/dispatch/DispatchFlow.qll @@ -167,6 +167,20 @@ private module TypeTrackingSteps { def.(BaseSsaUpdate).getDefiningExpr().(VariableAssign).getSource() = n1.asExpr() or def.(BaseSsaImplicitInit).isParameterDefinition(n1.asParameter()) + or + exists(PatternCase pc | + pc.getPattern().asBindingPattern() = def.(BaseSsaUpdate).getDefiningExpr() and + ( + pc.getSwitch().getExpr() = n1.asExpr() + or + pc.getSwitchExpr().getExpr() = n1.asExpr() + ) + ) + or + exists(InstanceOfExpr ioe | + ioe.getPattern().asBindingPattern() = def.(BaseSsaUpdate).getDefiningExpr() and + ioe.getExpr() = n1.asExpr() + ) | v.getAnUltimateDefinition() = def and v.getAUse() = n2.asExpr() diff --git a/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll b/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll index b06e9a2097f..446885252f6 100644 --- a/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll +++ b/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll @@ -89,6 +89,11 @@ private predicate step(Node n1, Node n2) { pc.getSwitchExpr().getExpr() = n1.asExpr() ) ) + or + exists(InstanceOfExpr ioe | + ioe.getPattern().asBindingPattern() = def.(BaseSsaUpdate).getDefiningExpr() and + ioe.getExpr() = n1.asExpr() + ) | v.getAnUltimateDefinition() = def and v.getAUse() = n2.asExpr() From 43c935024a319d27440ebf3318a5428d82a82165 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 9 Nov 2023 16:19:37 +0000 Subject: [PATCH 049/115] Add test for typeflow propagation through instanceof and pattern-case --- java/ql/test/library-tests/typeflow/A.java | 16 ++++++++++++++++ .../library-tests/typeflow/typeflow.expected | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/java/ql/test/library-tests/typeflow/A.java b/java/ql/test/library-tests/typeflow/A.java index 77cddd7c872..ad19901f151 100644 --- a/java/ql/test/library-tests/typeflow/A.java +++ b/java/ql/test/library-tests/typeflow/A.java @@ -102,4 +102,20 @@ public class A extends ArrayList { 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 -> { } + } + } } diff --git a/java/ql/test/library-tests/typeflow/typeflow.expected b/java/ql/test/library-tests/typeflow/typeflow.expected index 97ba56e48e3..f0cb2356cb8 100644 --- a/java/ql/test/library-tests/typeflow/typeflow.expected +++ b/java/ql/test/library-tests/typeflow/typeflow.expected @@ -14,5 +14,9 @@ | 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 | From 4cf511e26a447d601751a5f9f781ef812737dbd5 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 9 Nov 2023 18:17:07 +0000 Subject: [PATCH 050/115] Add test for virtual-dispatch flow through binding patterns --- .../dataflow/collections/options | 1 + .../Test.java | 29 +++++++++++++++++++ .../virtual-dispatch-binding-patterns/options | 1 + .../test.expected | 8 +++++ .../virtual-dispatch-binding-patterns/test.ql | 7 +++++ 5 files changed, 46 insertions(+) create mode 100644 java/ql/test/library-tests/dataflow/collections/options create mode 100644 java/ql/test/library-tests/virtual-dispatch-binding-patterns/Test.java create mode 100644 java/ql/test/library-tests/virtual-dispatch-binding-patterns/options create mode 100644 java/ql/test/library-tests/virtual-dispatch-binding-patterns/test.expected create mode 100644 java/ql/test/library-tests/virtual-dispatch-binding-patterns/test.ql diff --git a/java/ql/test/library-tests/dataflow/collections/options b/java/ql/test/library-tests/dataflow/collections/options new file mode 100644 index 00000000000..a0d1b7e7002 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/collections/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args --release 21 diff --git a/java/ql/test/library-tests/virtual-dispatch-binding-patterns/Test.java b/java/ql/test/library-tests/virtual-dispatch-binding-patterns/Test.java new file mode 100644 index 00000000000..47b333c408b --- /dev/null +++ b/java/ql/test/library-tests/virtual-dispatch-binding-patterns/Test.java @@ -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"; } + } + + } + +} + diff --git a/java/ql/test/library-tests/virtual-dispatch-binding-patterns/options b/java/ql/test/library-tests/virtual-dispatch-binding-patterns/options new file mode 100644 index 00000000000..a0d1b7e7002 --- /dev/null +++ b/java/ql/test/library-tests/virtual-dispatch-binding-patterns/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args --release 21 diff --git a/java/ql/test/library-tests/virtual-dispatch-binding-patterns/test.expected b/java/ql/test/library-tests/virtual-dispatch-binding-patterns/test.expected new file mode 100644 index 00000000000..6cbaf7579f7 --- /dev/null +++ b/java/ql/test/library-tests/virtual-dispatch-binding-patterns/test.expected @@ -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 | diff --git a/java/ql/test/library-tests/virtual-dispatch-binding-patterns/test.ql b/java/ql/test/library-tests/virtual-dispatch-binding-patterns/test.ql new file mode 100644 index 00000000000..3e255270d87 --- /dev/null +++ b/java/ql/test/library-tests/virtual-dispatch-binding-patterns/test.ql @@ -0,0 +1,7 @@ +import java + +import semmle.code.java.dispatch.VirtualDispatch + +from Call c, Callable c2 +where c2 = viableCallable(c) +select c, c2.getQualifiedName() From 011eb2201e1914542716c306cc025a22cf92f4ea Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 10 Nov 2023 10:17:18 +0000 Subject: [PATCH 051/115] Add test for ObjFlow over binding patterns --- .../Test.java | 29 +++++++++++++++++++ .../options | 1 + .../test.expected | 9 ++++++ .../test.ql | 7 +++++ 4 files changed, 46 insertions(+) create mode 100644 java/ql/test/library-tests/object-tostring-flow-binding-patterns/Test.java create mode 100644 java/ql/test/library-tests/object-tostring-flow-binding-patterns/options create mode 100644 java/ql/test/library-tests/object-tostring-flow-binding-patterns/test.expected create mode 100644 java/ql/test/library-tests/object-tostring-flow-binding-patterns/test.ql diff --git a/java/ql/test/library-tests/object-tostring-flow-binding-patterns/Test.java b/java/ql/test/library-tests/object-tostring-flow-binding-patterns/Test.java new file mode 100644 index 00000000000..445682392ad --- /dev/null +++ b/java/ql/test/library-tests/object-tostring-flow-binding-patterns/Test.java @@ -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"; } + } + + } + +} + diff --git a/java/ql/test/library-tests/object-tostring-flow-binding-patterns/options b/java/ql/test/library-tests/object-tostring-flow-binding-patterns/options new file mode 100644 index 00000000000..a0d1b7e7002 --- /dev/null +++ b/java/ql/test/library-tests/object-tostring-flow-binding-patterns/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args --release 21 diff --git a/java/ql/test/library-tests/object-tostring-flow-binding-patterns/test.expected b/java/ql/test/library-tests/object-tostring-flow-binding-patterns/test.expected new file mode 100644 index 00000000000..be4c24ea044 --- /dev/null +++ b/java/ql/test/library-tests/object-tostring-flow-binding-patterns/test.expected @@ -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 | diff --git a/java/ql/test/library-tests/object-tostring-flow-binding-patterns/test.ql b/java/ql/test/library-tests/object-tostring-flow-binding-patterns/test.ql new file mode 100644 index 00000000000..3e255270d87 --- /dev/null +++ b/java/ql/test/library-tests/object-tostring-flow-binding-patterns/test.ql @@ -0,0 +1,7 @@ +import java + +import semmle.code.java.dispatch.VirtualDispatch + +from Call c, Callable c2 +where c2 = viableCallable(c) +select c, c2.getQualifiedName() From 158f4bff7a57a725e3d761fd62bf84801609bd8f Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 10 Nov 2023 10:21:02 +0000 Subject: [PATCH 052/115] Note specific switch bugfix --- java/ql/lib/change-notes/2023-11-03-jdk21-support.md | 1 + 1 file changed, 1 insertion(+) diff --git a/java/ql/lib/change-notes/2023-11-03-jdk21-support.md b/java/ql/lib/change-notes/2023-11-03-jdk21-support.md index bf11bac4f8e..7f3b5c39a21 100644 --- a/java/ql/lib/change-notes/2023-11-03-jdk21-support.md +++ b/java/ql/lib/change-notes/2023-11-03-jdk21-support.md @@ -2,3 +2,4 @@ category: minorAnalysis --- * Switch cases using patterns and both cases and instanceof expressions using record patterns are now supported. The new class `RecordPatternExpr` is introduced to represent patterns, `PatternCase` and `CaseNullDefault` to represent new kinds of case statement, `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. From 480781b049dc0f7e9989d74e73a3736ed9825c87 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 10 Nov 2023 10:37:01 +0000 Subject: [PATCH 053/115] autoformat --- .../library-tests/object-tostring-flow-binding-patterns/test.ql | 1 - .../test/library-tests/virtual-dispatch-binding-patterns/test.ql | 1 - 2 files changed, 2 deletions(-) diff --git a/java/ql/test/library-tests/object-tostring-flow-binding-patterns/test.ql b/java/ql/test/library-tests/object-tostring-flow-binding-patterns/test.ql index 3e255270d87..dea1e994a93 100644 --- a/java/ql/test/library-tests/object-tostring-flow-binding-patterns/test.ql +++ b/java/ql/test/library-tests/object-tostring-flow-binding-patterns/test.ql @@ -1,5 +1,4 @@ import java - import semmle.code.java.dispatch.VirtualDispatch from Call c, Callable c2 diff --git a/java/ql/test/library-tests/virtual-dispatch-binding-patterns/test.ql b/java/ql/test/library-tests/virtual-dispatch-binding-patterns/test.ql index 3e255270d87..dea1e994a93 100644 --- a/java/ql/test/library-tests/virtual-dispatch-binding-patterns/test.ql +++ b/java/ql/test/library-tests/virtual-dispatch-binding-patterns/test.ql @@ -1,5 +1,4 @@ import java - import semmle.code.java.dispatch.VirtualDispatch from Call c, Callable c2 From de2b98f4a10ee3bb78790abcf5cecf8008621d18 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 15 Nov 2023 15:03:42 +0000 Subject: [PATCH 054/115] Fix hasNullCase --- java/ql/lib/semmle/code/java/Expr.qll | 5 ++++- java/ql/lib/semmle/code/java/Statement.qll | 5 ++++- java/ql/test/query-tests/Nullness/G.java | 5 +++++ java/ql/test/query-tests/Nullness/NullMaybe.expected | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index 5e0ae454214..eb63efc2f25 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1547,7 +1547,10 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr { } /** Holds if this switch has a case handling a null literal. */ - predicate hasNullCase() { this.getAConstCase().getValue(_) instanceof NullLiteral } + predicate hasNullCase() { + this.getAConstCase().getValue(_) instanceof NullLiteral or + this.getACase() instanceof NullDefaultCase + } /** Gets a printable representation of this expression. */ override string toString() { result = "switch (...)" } diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index 3255f7a2f2f..273a51a9c01 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -413,7 +413,10 @@ class SwitchStmt extends Stmt, @switchstmt { Expr getExpr() { result.getParent() = this } /** Holds if this switch has a case handling a null literal. */ - predicate hasNullCase() { this.getAConstCase().getValue(_) instanceof NullLiteral } + predicate hasNullCase() { + this.getAConstCase().getValue(_) instanceof NullLiteral or + this.getACase() instanceof NullDefaultCase + } override string pp() { result = "switch (...)" } diff --git a/java/ql/test/query-tests/Nullness/G.java b/java/ql/test/query-tests/Nullness/G.java index ebdce796c82..9a525e8d14b 100644 --- a/java/ql/test/query-tests/Nullness/G.java +++ b/java/ql/test/query-tests/Nullness/G.java @@ -12,6 +12,11 @@ public class G { 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()); diff --git a/java/ql/test/query-tests/Nullness/NullMaybe.expected b/java/ql/test/query-tests/Nullness/NullMaybe.expected index b46356b3757..80cf8f00f8d 100644 --- a/java/ql/test/query-tests/Nullness/NullMaybe.expected +++ b/java/ql/test/query-tests/Nullness/NullMaybe.expected @@ -35,4 +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:15:12:15: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 | +| 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 | From 8f10d29f684e8d2815347b06c717fca898921648 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 15 Nov 2023 15:08:30 +0000 Subject: [PATCH 055/115] Typo --- java/ql/lib/semmle/code/java/Expr.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index eb63efc2f25..0e07a7da654 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1679,7 +1679,7 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr { */ InstanceOfExpr getAssociatedInstanceOfExpr() { result.getPattern().getAChildExpr*() = this } - /** Holds f this is a declaration stemming from a pattern instanceof expression. */ + /** 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. */ From 176adf437643cc24942940bb9daa712eb7f7456b Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 15 Nov 2023 15:09:49 +0000 Subject: [PATCH 056/115] Move PatternExpr to correct file --- java/ql/lib/semmle/code/java/Expr.qll | 26 ++++++++++++++++++++++ java/ql/lib/semmle/code/java/Statement.qll | 26 ---------------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index 0e07a7da654..27bb2c4f1e2 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -2580,6 +2580,32 @@ 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 PatternExpr + ) 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() + "(...)" } diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index 273a51a9c01..87605e57691 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -530,32 +530,6 @@ class ConstCase extends SwitchCase { override string getAPrimaryQlClass() { result = "ConstCase" } } -/** - * 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 PatternExpr - ) 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 pattern case of a `switch` statement */ class PatternCase extends SwitchCase { PatternExpr pattern; From 3d980b168476a40cc4d00580243e2a1b2b7bc174 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 15 Nov 2023 15:59:21 +0000 Subject: [PATCH 057/115] Switch to using VariableAssign for instanceof and switch dataflow --- java/ql/lib/semmle/code/java/Expr.qll | 59 ++++++++++++++++--- .../semmle/code/java/dataflow/TypeFlow.qll | 16 ----- .../java/dataflow/internal/DataFlowUtil.qll | 11 ++-- .../code/java/dispatch/DispatchFlow.qll | 14 ----- .../lib/semmle/code/java/dispatch/ObjFlow.qll | 14 ----- 5 files changed, 58 insertions(+), 56 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index 27bb2c4f1e2..f849fe62f4e 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1682,15 +1682,51 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr { /** 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. */ + /** + * 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(EnhancedForStmt efs | efs.getVariable() = this) or - this.hasAssociatedSwitch() or - this.hasAssociatedInstanceOfExpr() + 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. */ @@ -1699,6 +1735,13 @@ 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() { @@ -1727,12 +1770,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() } } diff --git a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll index dfbf957f18c..543bc5d3f44 100644 --- a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll @@ -116,22 +116,6 @@ private predicate step(TypeFlowNode n1, TypeFlowNode n2) { n2.asSsa().(BaseSsaUpdate).getDefiningExpr().(VariableAssign).getSource() = n1.asExpr() or n2.asSsa().(BaseSsaImplicitInit).captures(n1.asSsa()) - or - exists(PatternCase pc, LocalVariableDeclExpr patternVar | - patternVar = pc.getPattern().asBindingPattern() and - n2.asSsa().(BaseSsaUpdate).getDefiningExpr() = patternVar and - ( - pc.getSwitch().getExpr() = n1.asExpr() - or - pc.getSwitchExpr().getExpr() = n1.asExpr() - ) - ) - or - exists(InstanceOfExpr ioe, LocalVariableDeclExpr patternVar | - patternVar = ioe.getPattern().asBindingPattern() and - n2.asSsa().(BaseSsaUpdate).getDefiningExpr() = patternVar and - ioe.getExpr() = n1.asExpr() - ) } /** diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll index 3a2cf884bed..0fa9412cb5b 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll @@ -191,11 +191,14 @@ predicate simpleAstFlowStep(Expr e1, Expr e2) { or e2.(WhenExpr).getBranch(_).getAResult() = e1 or - exists(SwitchExpr se | e1 = se.getExpr() and e2 = se.getACase().(PatternCase).getPattern()) + // 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()) + exists(SwitchStmt ss | e1 = ss.getExpr() and e2 = ss.getACase().(PatternCase).getPattern().asRecordPattern()) or - exists(InstanceOfExpr ioe | e1 = ioe.getExpr() and e2 = ioe.getPattern()) + exists(InstanceOfExpr ioe | e1 = ioe.getExpr() and e2 = ioe.getPattern().asRecordPattern()) } private predicate simpleLocalFlowStep0(Node node1, Node node2) { @@ -204,7 +207,7 @@ private predicate simpleLocalFlowStep0(Node node1, Node node2) { exists(SsaExplicitUpdate upd | upd.getDefiningExpr().(VariableAssign).getSource() = node1.asExpr() or upd.getDefiningExpr().(AssignOp) = node1.asExpr() or - upd.getDefiningExpr().(PatternExpr).asBindingPattern() = node1.asExpr() + upd.getDefiningExpr().(RecordBindingVariableExpr) = node1.asExpr() | node2.asExpr() = upd.getAFirstUse() and not capturedVariableRead(node2) diff --git a/java/ql/lib/semmle/code/java/dispatch/DispatchFlow.qll b/java/ql/lib/semmle/code/java/dispatch/DispatchFlow.qll index ae7ef09275a..82bda033bc6 100644 --- a/java/ql/lib/semmle/code/java/dispatch/DispatchFlow.qll +++ b/java/ql/lib/semmle/code/java/dispatch/DispatchFlow.qll @@ -167,20 +167,6 @@ private module TypeTrackingSteps { def.(BaseSsaUpdate).getDefiningExpr().(VariableAssign).getSource() = n1.asExpr() or def.(BaseSsaImplicitInit).isParameterDefinition(n1.asParameter()) - or - exists(PatternCase pc | - pc.getPattern().asBindingPattern() = def.(BaseSsaUpdate).getDefiningExpr() and - ( - pc.getSwitch().getExpr() = n1.asExpr() - or - pc.getSwitchExpr().getExpr() = n1.asExpr() - ) - ) - or - exists(InstanceOfExpr ioe | - ioe.getPattern().asBindingPattern() = def.(BaseSsaUpdate).getDefiningExpr() and - ioe.getExpr() = n1.asExpr() - ) | v.getAnUltimateDefinition() = def and v.getAUse() = n2.asExpr() diff --git a/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll b/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll index 446885252f6..293ba894fdf 100644 --- a/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll +++ b/java/ql/lib/semmle/code/java/dispatch/ObjFlow.qll @@ -80,20 +80,6 @@ private predicate step(Node n1, Node n2) { for.getVariable() = def.(BaseSsaUpdate).getDefiningExpr() and for.getExpr() = n1.asExpr() ) - or - exists(PatternCase pc | - pc.getPattern().asBindingPattern() = def.(BaseSsaUpdate).getDefiningExpr() and - ( - pc.getSwitch().getExpr() = n1.asExpr() - or - pc.getSwitchExpr().getExpr() = n1.asExpr() - ) - ) - or - exists(InstanceOfExpr ioe | - ioe.getPattern().asBindingPattern() = def.(BaseSsaUpdate).getDefiningExpr() and - ioe.getExpr() = n1.asExpr() - ) | v.getAnUltimateDefinition() = def and v.getAUse() = n2.asExpr() From b731b8d30ae6f5c6039204c414bcd49d9fda5c00 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 15 Nov 2023 16:01:17 +0000 Subject: [PATCH 058/115] Simplify PatternExpr definition --- java/ql/lib/semmle/code/java/Expr.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index f849fe62f4e..59a4652f449 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -2633,7 +2633,7 @@ class PatternExpr extends Expr { ( this.getParent() instanceof SwitchCase or this.getParent() instanceof InstanceOfExpr or - this.getParent() instanceof PatternExpr + this.getParent() instanceof RecordPatternExpr ) and (this instanceof LocalVariableDeclExpr or this instanceof RecordPatternExpr) } From 06d5233523c38d24d75565605cd17f175aa13c34 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 15 Nov 2023 16:04:41 +0000 Subject: [PATCH 059/115] Use SwitchCase.getSelectorExpr --- java/ql/lib/semmle/code/java/controlflow/Guards.qll | 3 +-- java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index b0e1d40ffcc..8634d6248f0 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -108,8 +108,7 @@ class Guard extends ExprParent { */ BasicBlock getBasicBlock() { result = this.(Expr).getBasicBlock() or - result = this.(SwitchCase).getSwitch().getExpr().getBasicBlock() or - result = this.(SwitchCase).getSwitchExpr().getExpr().getBasicBlock() + result = this.(SwitchCase).getSelectorExpr().getBasicBlock() } /** diff --git a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll index 543bc5d3f44..d0f4217133a 100644 --- a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll @@ -445,7 +445,7 @@ private predicate instanceOfGuarded(Expr e, RefType t) { */ private predicate patternCaseGuarded(Expr e, RefType t) { exists(PatternCase pc | - e = getAProbableAlias([pc.getSwitch().getExpr(), pc.getSwitchExpr().getExpr()]) and + e = getAProbableAlias(pc.getSelectorExpr()) and guardControls_v1(pc, e.getBasicBlock(), true) and t = pc.getPattern().getType() ) From 6fb33e0bde41a9f1e5154944f682964ea77ffbc3 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 15 Nov 2023 16:23:47 +0000 Subject: [PATCH 060/115] Re-re-factor instanceOfGuarded et al --- .../semmle/code/java/dataflow/TypeFlow.qll | 69 ++++++++++--------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll index d0f4217133a..939f15aac59 100644 --- a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll @@ -413,41 +413,42 @@ private predicate downcastSuccessor(VarAccess va, RefType t) { ) } -private Expr getAProbableAlias(Expr e) { - exists(BaseSsaVariable v | - e = v.getAUse() and - result = v.getAUse() +private predicate isTypeTestGuard(Guard test, Expr tested, Type t) { + exists(InstanceOfExpr ioe | + test = ioe and + ioe.getExpr() = tested and + t = ioe.getCheckedType() ) or - exists(BaseSsaVariable v1, BaseSsaVariable v2, ArrayAccess aa1, ArrayAccess aa2 | - e = aa1 and - result = aa2 and + exists(PatternCase pc | + test = pc and + pc.getSelectorExpr() = tested and + t = pc.getPattern().getType() + ) +} + +/** + * Holds if `va` is an access to a value that is guarded by `instanceof t` or `case e t`. + */ +private predicate typeTestGuarded(VarAccess va, RefType t) { + exists(Guard typeTest, BaseSsaVariable v | + isTypeTestGuard(typeTest, v.getAUse(), t) and + va = v.getAUse() and + guardControls_v1(typeTest, va.getBasicBlock(), true) + ) +} + +/** + * Holds if `aa` is an access to a value that is guarded by `instanceof t` or `case e t`. + */ +predicate arrayTypeTestGuarded(ArrayAccess aa, RefType t) { + exists(Guard typeTest, BaseSsaVariable v1, BaseSsaVariable v2, ArrayAccess aa1 | + isTypeTestGuard(typeTest, aa1, t) and aa1.getArray() = v1.getAUse() and aa1.getIndexExpr() = v2.getAUse() and - aa2.getArray() = v1.getAUse() and - aa2.getIndexExpr() = v2.getAUse() - ) -} - -/** - * Holds if `e` is an access to a value that is guarded by `instanceof t`. - */ -private predicate instanceOfGuarded(Expr e, RefType t) { - exists(InstanceOfExpr ioe | - t = ioe.getCheckedType() and - e = getAProbableAlias(ioe.getExpr()) and - guardControls_v1(ioe, e.getBasicBlock(), true) - ) -} - -/** - * Holds if `e` is an access to a value that is guarded by `case T t`. - */ -private predicate patternCaseGuarded(Expr e, RefType t) { - exists(PatternCase pc | - e = getAProbableAlias(pc.getSelectorExpr()) and - guardControls_v1(pc, e.getBasicBlock(), true) and - t = pc.getPattern().getType() + aa.getArray() = v1.getAUse() and + aa.getIndexExpr() = v2.getAUse() and + guardControls_v1(typeTest, aa.getBasicBlock(), true) ) } @@ -473,10 +474,10 @@ 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 + typeTestGuarded(n.asExpr(), srctype) or + arrayTypeTestGuarded(n.asExpr(), srctype) or n.asExpr().(FunctionalExpr).getConstructedType() = srctype or - superAccess(n.asExpr(), srctype) or - patternCaseGuarded(n.asExpr(), srctype) + superAccess(n.asExpr(), srctype) | t = srctype.(BoundedType).getAnUltimateUpperBoundType() or From cc373e322fb05310c9fa1441032ef635fe6bcf5f Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 15 Nov 2023 17:37:12 +0000 Subject: [PATCH 061/115] Engineer join order for getLexicallyOrderedRecordField --- .../java/dataflow/internal/DataFlowPrivate.qll | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll index b6c06f8dde3..a69da595a94 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll @@ -232,12 +232,22 @@ predicate storeStep(Node node1, ContentSet f, Node node2) { pragma[only_bind_out](node2.getEnclosingCallable()) } +// Manual join hacking, to avoid a paramters 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 | - f = r.getAField() and - p = r.getCanonicalConstructor().getParameter(i) and - f.getName() = p.getName() + 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 ) From ab9f2a77e442207afde360ad894b2bb63584b3fe Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 15 Nov 2023 17:44:51 +0000 Subject: [PATCH 062/115] Move comments --- .../ecfcf050952e54b1155fc89525db84af6ad34aaf/exprs.ql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/exprs.ql b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/exprs.ql index 31493cb5723..f5f10c8783a 100644 --- a/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/exprs.ql +++ b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/exprs.ql @@ -29,14 +29,14 @@ predicate hasNewParent(Expr e, ExprParent newParent, int newIndex) { 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 ( - // Initialiser moves to hang directly off the instanceof expression if e instanceof LocalVariableDeclExpr and getParent(e) instanceof InstanceOfExpr - then newParent = getParent(e) and newIndex = 2 - else + then // Variable declaration moves to be the instanceof expression's 2nd child - exprs(e, _, _, newParent, newIndex) // Other expressions unchanged + newParent = getParent(e) and newIndex = 2 + else exprs(e, _, _, newParent, newIndex) // Other expressions unchanged ) } From d7a517a98946a1fa277ef83c3826f0db503c718d Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 15 Nov 2023 17:57:46 +0000 Subject: [PATCH 063/115] Remove needless test options --- java/ql/test/library-tests/dataflow/collections/options | 1 - 1 file changed, 1 deletion(-) delete mode 100644 java/ql/test/library-tests/dataflow/collections/options diff --git a/java/ql/test/library-tests/dataflow/collections/options b/java/ql/test/library-tests/dataflow/collections/options deleted file mode 100644 index a0d1b7e7002..00000000000 --- a/java/ql/test/library-tests/dataflow/collections/options +++ /dev/null @@ -1 +0,0 @@ -//semmle-extractor-options: --javac-args --release 21 From b11a17db2116d861f4fb663185b8ee1a7faf3520 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 15 Nov 2023 18:00:40 +0000 Subject: [PATCH 064/115] Improve change note --- java/ql/lib/change-notes/2023-11-03-jdk21-support.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/ql/lib/change-notes/2023-11-03-jdk21-support.md b/java/ql/lib/change-notes/2023-11-03-jdk21-support.md index 7f3b5c39a21..33d5019a442 100644 --- a/java/ql/lib/change-notes/2023-11-03-jdk21-support.md +++ b/java/ql/lib/change-notes/2023-11-03-jdk21-support.md @@ -1,5 +1,6 @@ --- category: minorAnalysis --- -* Switch cases using patterns and both cases and instanceof expressions using record patterns are now supported. The new class `RecordPatternExpr` is introduced to represent patterns, `PatternCase` and `CaseNullDefault` to represent new kinds of case statement, `InstanceOfExpr` gains `getPattern` to replace `getLocalVariableDeclExpr`. +* Switch cases using binding patterns and `case null[, default]` are now supported. Classes `PatternCase` and `CaseNullDefault` 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. From 8fd4f99a39327a7d0f8eeb58621804dd9db45944 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 15 Nov 2023 18:10:37 +0000 Subject: [PATCH 065/115] Fix autoformat comment translocation --- .../dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql index aba4fd5a39d..49d6bb31c4c 100644 --- a/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql +++ b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql @@ -48,8 +48,8 @@ where exprs(e, oldKind, typeid, _, _) and hasNewParent(e, parent, index) and ( - if oldKind = 89 - then /* record pattern */ newKind = 74 - else /* error expression */ oldKind = newKind + if oldKind = /* record pattern */ 89 + then newKind = /* error expression */ 74 + else oldKind = newKind ) select e, newKind, typeid, parent, index From 0bb051e08c9e50da500c8eb937da0220265f4248 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 15 Nov 2023 19:13:48 +0000 Subject: [PATCH 066/115] First stab at implementing negative type-test logic for pattern-case --- .../lib/semmle/code/java/ControlFlowGraph.qll | 2 +- .../semmle/code/java/controlflow/Guards.qll | 26 +++++++++++++++- .../semmle/code/java/dataflow/TypeFlow.qll | 22 +++---------- .../code/java/dispatch/VirtualDispatch.qll | 9 +++--- .../Test.java | 31 +++++++++++++++++++ .../options | 1 + .../test.expected | 4 +++ .../test.ql | 18 +++++++++++ 8 files changed, 88 insertions(+), 25 deletions(-) create mode 100644 java/ql/test/library-tests/switch-default-impossible-dispatch/Test.java create mode 100644 java/ql/test/library-tests/switch-default-impossible-dispatch/options create mode 100644 java/ql/test/library-tests/switch-default-impossible-dispatch/test.expected create mode 100644 java/ql/test/library-tests/switch-default-impossible-dispatch/test.ql diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index fc443fbb06f..7486736ecfc 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -1512,5 +1512,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 } } diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index 8634d6248f0..c418c469286 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -18,7 +18,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) { @@ -174,6 +174,30 @@ class Guard extends ExprParent { } } +/** + * A `Guard` that tests an expression's type -- that is, an `instanceof T` or a + * `case T varname` pattern case. + */ +class TypeTestGuard extends Guard { + Expr testedExpr; + Type testedType; + + TypeTestGuard() { + exists(InstanceOfExpr ioe | this = ioe | + testedExpr = ioe.getExpr() and + testedType = ioe.getCheckedType() + ) + or + exists(PatternCase pc | this = pc | + pc.getSelectorExpr() = testedExpr and + testedType = pc.getPattern().getType() + ) + } + + /** Holds if this guard tests whether `e` has type `t`. */ + predicate appliesTypeTest(Expr e, Type t) { e = testedExpr and t = testedType } +} + private predicate switchCaseControls(SwitchCase sc, BasicBlock bb) { exists(BasicBlock caseblock, Expr selector | selector = sc.getSelectorExpr() and diff --git a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll index 939f15aac59..354b3396a37 100644 --- a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll @@ -413,26 +413,12 @@ private predicate downcastSuccessor(VarAccess va, RefType t) { ) } -private predicate isTypeTestGuard(Guard test, Expr tested, Type t) { - exists(InstanceOfExpr ioe | - test = ioe and - ioe.getExpr() = tested and - t = ioe.getCheckedType() - ) - or - exists(PatternCase pc | - test = pc and - pc.getSelectorExpr() = tested and - t = pc.getPattern().getType() - ) -} - /** * Holds if `va` is an access to a value that is guarded by `instanceof t` or `case e t`. */ private predicate typeTestGuarded(VarAccess va, RefType t) { - exists(Guard typeTest, BaseSsaVariable v | - isTypeTestGuard(typeTest, v.getAUse(), t) and + exists(TypeTestGuard typeTest, BaseSsaVariable v | + typeTest.appliesTypeTest(v.getAUse(), t) and va = v.getAUse() and guardControls_v1(typeTest, va.getBasicBlock(), true) ) @@ -442,8 +428,8 @@ private predicate typeTestGuarded(VarAccess va, RefType t) { * Holds if `aa` is an access to a value that is guarded by `instanceof t` or `case e t`. */ predicate arrayTypeTestGuarded(ArrayAccess aa, RefType t) { - exists(Guard typeTest, BaseSsaVariable v1, BaseSsaVariable v2, ArrayAccess aa1 | - isTypeTestGuard(typeTest, aa1, t) and + exists(TypeTestGuard 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 diff --git a/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll b/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll index 3fe6f9ad303..58bc1f0b934 100644 --- a/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll +++ b/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll @@ -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(TypeTestGuard 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) and + guardControls_v1(typeTest, q.getBasicBlock(), false) and + tgt.getDeclaringType().getSourceDeclaration().getASourceSupertype*() = t.getErasure() ) } diff --git a/java/ql/test/library-tests/switch-default-impossible-dispatch/Test.java b/java/ql/test/library-tests/switch-default-impossible-dispatch/Test.java new file mode 100644 index 00000000000..a93dc2c5b6a --- /dev/null +++ b/java/ql/test/library-tests/switch-default-impossible-dispatch/Test.java @@ -0,0 +1,31 @@ +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); } } + + public static void test(boolean unknown, int alsoUnknown) { + + I c1or2 = unknown ? new C1() : new C2(); + + switch(c1or2) { + case C1 c1 when alsoUnknown == 1 -> { } + default -> c1or2.take(source()); // Could call either implementation + } + + switch(c1or2) { + case C1 c1 -> { } + default -> c1or2.take(source()); // Can't call C1.take + } + + switch(c1or2) { + case C1 c1 -> { } + case null, default -> c1or2.take(source()); // Can't call C1.take + } + + } + +} diff --git a/java/ql/test/library-tests/switch-default-impossible-dispatch/options b/java/ql/test/library-tests/switch-default-impossible-dispatch/options new file mode 100644 index 00000000000..a0d1b7e7002 --- /dev/null +++ b/java/ql/test/library-tests/switch-default-impossible-dispatch/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args --release 21 diff --git a/java/ql/test/library-tests/switch-default-impossible-dispatch/test.expected b/java/ql/test/library-tests/switch-default-impossible-dispatch/test.expected new file mode 100644 index 00000000000..777ebbc121a --- /dev/null +++ b/java/ql/test/library-tests/switch-default-impossible-dispatch/test.expected @@ -0,0 +1,4 @@ +| Test.java:16:29:16:36 | source(...) | Test.java:7:65:7:65 | x | +| Test.java:16:29:16:36 | source(...) | Test.java:8:65:8:65 | x | +| Test.java:21:29:21:36 | source(...) | Test.java:8:65:8:65 | x | +| Test.java:26:40:26:47 | source(...) | Test.java:8:65:8:65 | x | diff --git a/java/ql/test/library-tests/switch-default-impossible-dispatch/test.ql b/java/ql/test/library-tests/switch-default-impossible-dispatch/test.ql new file mode 100644 index 00000000000..c8e832e9f26 --- /dev/null +++ b/java/ql/test/library-tests/switch-default-impossible-dispatch/test.ql @@ -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; + +from DataFlow::Node source, DataFlow::Node sink +where Flow::flow(source, sink) +select source, sink From d2ff1baff0bbffce1c27172f4db622c36083d1d9 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 16 Nov 2023 14:23:56 +0000 Subject: [PATCH 067/115] Replace getDefaultOrNullDefaultCase with getDefaultCase --- java/ql/examples/snippets/switchcase.ql | 2 +- java/ql/lib/semmle/code/java/ControlFlowGraph.qll | 2 +- java/ql/lib/semmle/code/java/Expr.qll | 11 ++++++----- java/ql/lib/semmle/code/java/Statement.qll | 11 ++++++----- .../code/java/controlflow/UnreachableBlocks.qll | 2 +- .../src/Advisory/Statements/MissingDefaultInSwitch.ql | 2 +- .../src/Likely Bugs/Statements/MissingEnumInSwitch.ql | 2 +- 7 files changed, 17 insertions(+), 15 deletions(-) diff --git a/java/ql/examples/snippets/switchcase.ql b/java/ql/examples/snippets/switchcase.ql index 12a6ae021f3..d425d5686f4 100644 --- a/java/ql/examples/snippets/switchcase.ql +++ b/java/ql/examples/snippets/switchcase.ql @@ -14,5 +14,5 @@ where switch.getExpr().getType() = enum and missing.getDeclaringType() = enum and not switch.getAConstCase().getValue() = missing.getAnAccess() and - not exists(switch.getDefaultOrNullDefaultCase()) + not exists(switch.getDefaultCase()) select switch diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index 7486736ecfc..9b6e17634d9 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -923,7 +923,7 @@ private module ControlFlowGraphImpl { completion = NormalCompletion() or // if no default case exists, then normal completion of the expression may terminate the switch - not exists(switch.getDefaultOrNullDefaultCase()) and + not exists(switch.getDefaultCase()) and last(switch.getExpr(), last, completion) and completion = NormalCompletion() ) diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index 59a4652f449..1ea7899d0da 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1530,11 +1530,12 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr { /** Gets a (non-default) pattern `case` of this `switch` expression. */ PatternCase getAPatternCase() { result = this.getACase() } - /** Gets the `default` case of this switch expression, if any. */ - DefaultCase getDefaultCase() { result = this.getACase() } - - /** Gets the `default` or `case null, default` case of this switch statement, if any. */ - SwitchCase getDefaultOrNullDefaultCase() { result = this.getACase() and result.hasDefaultLabel() } + /** + * Gets the `default` case of this switch statement, if any. + * + * Note this may be `default` or `case null, default`. + */ + SwitchCase getDefaultCase() { result = this.getACase() and result.hasDefaultLabel() } /** Gets the expression of this `switch` expression. */ Expr getExpr() { result.getParent() = this } diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index 87605e57691..fccf1cec0d4 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -403,11 +403,12 @@ class SwitchStmt extends Stmt, @switchstmt { /** 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. */ - DefaultCase getDefaultCase() { result = this.getACase() } - - /** Gets the `default` or `case null, default` case of this switch statement, if any. */ - SwitchCase getDefaultOrNullDefaultCase() { result = this.getACase() and result.hasDefaultLabel() } + /** + * Gets the `default` case of this switch statement, if any. + * + * Note this may be `default` or `case null, default`. + */ + SwitchCase getDefaultCase() { result = this.getACase() and result.hasDefaultLabel() } /** Gets the expression of this `switch` statement. */ Expr getExpr() { result.getParent() = this } diff --git a/java/ql/lib/semmle/code/java/controlflow/UnreachableBlocks.qll b/java/ql/lib/semmle/code/java/controlflow/UnreachableBlocks.qll index 4a13ad93e9f..3145371561a 100644 --- a/java/ql/lib/semmle/code/java/controlflow/UnreachableBlocks.qll +++ b/java/ql/lib/semmle/code/java/controlflow/UnreachableBlocks.qll @@ -174,7 +174,7 @@ class ConstSwitchStmt extends SwitchStmt { exists(this.getExpr().(ConstantExpr).getIntValue()) and if exists(this.getMatchingConstCase()) then result = this.getMatchingConstCase() - else result = this.getDefaultOrNullDefaultCase() + else result = this.getDefaultCase() } /** diff --git a/java/ql/src/Advisory/Statements/MissingDefaultInSwitch.ql b/java/ql/src/Advisory/Statements/MissingDefaultInSwitch.ql index ce2aa08035b..eab8a596f0e 100644 --- a/java/ql/src/Advisory/Statements/MissingDefaultInSwitch.ql +++ b/java/ql/src/Advisory/Statements/MissingDefaultInSwitch.ql @@ -15,5 +15,5 @@ import java from SwitchStmt switch where not switch.getExpr().getType() instanceof EnumType and - not exists(switch.getDefaultOrNullDefaultCase()) + not exists(switch.getDefaultCase()) select switch, "Switch statement does not have a default case." diff --git a/java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.ql b/java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.ql index 2f76e6f2e31..dfea3ad72d9 100644 --- a/java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.ql +++ b/java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.ql @@ -17,6 +17,6 @@ from SwitchStmt switch, EnumType enum, EnumConstant missing where switch.getExpr().getType() = enum and missing.getDeclaringType() = enum and - not exists(switch.getDefaultOrNullDefaultCase()) and + not exists(switch.getDefaultCase()) and not switch.getAConstCase().getValue() = missing.getAnAccess() select switch, "Switch statement does not have a case for $@.", missing, missing.getName() From 6e868d21bd08ea27db2d36236af1f24e843c32c1 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 16 Nov 2023 14:32:06 +0000 Subject: [PATCH 068/115] Make DefaultCase include NullDefaultCase --- java/ql/lib/semmle/code/java/Expr.qll | 6 ++---- java/ql/lib/semmle/code/java/Statement.qll | 20 ++++++++----------- .../java/controlflow/internal/GuardsLogic.qll | 4 ++-- .../code/java/metrics/MetricCallable.qll | 4 +--- 4 files changed, 13 insertions(+), 21 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index 1ea7899d0da..8be8279473e 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1535,7 +1535,7 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr { * * Note this may be `default` or `case null, default`. */ - SwitchCase getDefaultCase() { result = this.getACase() and result.hasDefaultLabel() } + DefaultCase getDefaultCase() { result = this.getACase() } /** Gets the expression of this `switch` expression. */ Expr getExpr() { result.getParent() = this } @@ -1738,9 +1738,7 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr { /** A local variable declaration that occurs within a record pattern. */ class RecordBindingVariableExpr extends LocalVariableDeclExpr { - RecordBindingVariableExpr() { - this.getParent() instanceof RecordPatternExpr - } + RecordBindingVariableExpr() { this.getParent() instanceof RecordPatternExpr } } /** An update of a variable or an initialization of the variable. */ diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index fccf1cec0d4..43f72a94f78 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -408,7 +408,7 @@ class SwitchStmt extends Stmt, @switchstmt { * * Note this may be `default` or `case null, default`. */ - SwitchCase getDefaultCase() { result = this.getACase() and result.hasDefaultLabel() } + DefaultCase getDefaultCase() { result = this.getACase() } /** Gets the expression of this `switch` statement. */ Expr getExpr() { result.getParent() = this } @@ -492,12 +492,6 @@ class SwitchCase extends Stmt, @case { Stmt getRuleStatementOrExpressionStatement() { result.getParent() = this and result.getIndex() = -1 } - - /** - * Holds if this case statement includes the default label, i.e. it is either `default` - * or `case null, default`. - */ - predicate hasDefaultLabel() { this instanceof DefaultCase or this instanceof NullDefaultCase } } /** @@ -553,12 +547,14 @@ class PatternCase extends SwitchCase { } /** - * A `default` case of a `switch` statement. - * - * Note this does not include `case null, default` -- for that, see `NullDefaultCase`. + * 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" } @@ -570,7 +566,7 @@ class DefaultCase extends SwitchCase { } /** A `case null, default` statement of a `switch` statement or expression. */ -class NullDefaultCase extends SwitchCase { +class NullDefaultCase extends DefaultCase { NullDefaultCase() { isNullDefaultCase(this) } override string pp() { result = "case null, default" } diff --git a/java/ql/lib/semmle/code/java/controlflow/internal/GuardsLogic.qll b/java/ql/lib/semmle/code/java/controlflow/internal/GuardsLogic.qll index 377c043a052..f96c6cdb93a 100644 --- a/java/ql/lib/semmle/code/java/controlflow/internal/GuardsLogic.qll +++ b/java/ql/lib/semmle/code/java/controlflow/internal/GuardsLogic.qll @@ -55,11 +55,11 @@ predicate implies_v1(Guard g1, boolean b1, Guard g2, boolean b2) { ) ) or - exists(SwitchCase sc | g1 = sc and sc.hasDefaultLabel() | + exists(DefaultCase sc | g1 = sc | sc.getSwitch().getAConstCase() = g2 and b1 = true and b2 = false ) or - exists(SwitchCase sc | g1 = sc and sc.hasDefaultLabel() | + exists(DefaultCase sc | g1 = sc | sc.getSwitchExpr().getAConstCase() = g2 and b1 = true and b2 = false ) or diff --git a/java/ql/lib/semmle/code/java/metrics/MetricCallable.qll b/java/ql/lib/semmle/code/java/metrics/MetricCallable.qll index 018ca4fae1b..a888050185e 100644 --- a/java/ql/lib/semmle/code/java/metrics/MetricCallable.qll +++ b/java/ql/lib/semmle/code/java/metrics/MetricCallable.qll @@ -78,9 +78,7 @@ private predicate branchingSwitchCase(ConstCase sc) { } private predicate defaultFallThrough(ConstCase sc) { - exists(SwitchCase default | default.hasDefaultLabel() | - default.(ControlFlowNode).getASuccessor() = sc - ) + exists(DefaultCase default | default.(ControlFlowNode).getASuccessor() = sc) or defaultFallThrough(sc.(ControlFlowNode).getAPredecessor()) } From d40311efe9c2fc20afe6977e4fe05b222eebeb23 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 16 Nov 2023 14:33:59 +0000 Subject: [PATCH 069/115] Spelling --- .../lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll index a69da595a94..bdfa9507673 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll @@ -232,7 +232,7 @@ predicate storeStep(Node node1, ContentSet f, Node node2) { pragma[only_bind_out](node2.getEnclosingCallable()) } -// Manual join hacking, to avoid a paramters x fields product. +// 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() From 6583c72c5d20c276d454138bf4d1a0eda585c57f Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 16 Nov 2023 15:15:34 +0000 Subject: [PATCH 070/115] Restrict pattern type guards to account for nested record matching failures --- java/ql/lib/semmle/code/java/Expr.qll | 18 ++++++++ .../semmle/code/java/controlflow/Guards.qll | 34 ++++++++++++++- .../semmle/code/java/dataflow/TypeFlow.qll | 4 +- .../code/java/dispatch/VirtualDispatch.qll | 2 +- .../Test.java | 43 +++++++++++++++---- .../test.expected | 31 +++++++++++-- 6 files changed, 115 insertions(+), 17 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index 8be8279473e..ec791b443a0 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -2658,4 +2658,22 @@ class RecordPatternExpr extends Expr, @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() + ) + ) + } } diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index c418c469286..284c2278732 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -194,8 +194,38 @@ class TypeTestGuard extends Guard { ) } - /** Holds if this guard tests whether `e` has type `t`. */ - predicate appliesTypeTest(Expr e, Type t) { e = testedExpr and t = testedType } + /** + * Gets the record pattern this type test binds to, if any. + */ + PatternExpr getPattern() { + result = this.(InstanceOfExpr).getPattern() + or + result = this.(PatternCase).getPattern() + } + + /** + * Holds if this guard tests whether `e` has type `t`. + * + * Note that record patterns that make at least one tighter restriction than the record's definition + * (e.g. matching `record R(Object)` with `case R(String)`) means this only guarantees the tested type + * on the true branch (i.e., entering such a case guarantees `testedExpr` is a `testedType`, but failing + * the type test could mean a nested record or binding pattern didn't match but `testedExpr` is still + * of type `testedType`.) + */ + predicate appliesTypeTest(Expr e, Type t, boolean testedBranch) { + e = testedExpr and + t = testedType and + ( + testedBranch = true + or + testedBranch = false and + ( + this.getPattern().asRecordPattern().isUnrestricted() + or + not this.getPattern() instanceof RecordPatternExpr + ) + ) + } } private predicate switchCaseControls(SwitchCase sc, BasicBlock bb) { diff --git a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll index 354b3396a37..b16cfb9262e 100644 --- a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll @@ -418,7 +418,7 @@ private predicate downcastSuccessor(VarAccess va, RefType t) { */ private predicate typeTestGuarded(VarAccess va, RefType t) { exists(TypeTestGuard typeTest, BaseSsaVariable v | - typeTest.appliesTypeTest(v.getAUse(), t) and + typeTest.appliesTypeTest(v.getAUse(), t, true) and va = v.getAUse() and guardControls_v1(typeTest, va.getBasicBlock(), true) ) @@ -429,7 +429,7 @@ private predicate typeTestGuarded(VarAccess va, RefType t) { */ predicate arrayTypeTestGuarded(ArrayAccess aa, RefType t) { exists(TypeTestGuard typeTest, BaseSsaVariable v1, BaseSsaVariable v2, ArrayAccess aa1 | - typeTest.appliesTypeTest(aa1, t) and + typeTest.appliesTypeTest(aa1, t, true) and aa1.getArray() = v1.getAUse() and aa1.getIndexExpr() = v2.getAUse() and aa.getArray() = v1.getAUse() and diff --git a/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll b/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll index 58bc1f0b934..aa83d03f646 100644 --- a/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll +++ b/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll @@ -197,7 +197,7 @@ private module Dispatch { exists(TypeTestGuard typeTest, BaseSsaVariable v, Expr q, RefType t | source.getQualifier() = q and v.getAUse() = q and - typeTest.appliesTypeTest(v.getAUse(), t) and + typeTest.appliesTypeTest(v.getAUse(), t, false) and guardControls_v1(typeTest, q.getBasicBlock(), false) and tgt.getDeclaringType().getSourceDeclaration().getASourceSupertype*() = t.getErasure() ) diff --git a/java/ql/test/library-tests/switch-default-impossible-dispatch/Test.java b/java/ql/test/library-tests/switch-default-impossible-dispatch/Test.java index a93dc2c5b6a..b8b0123fdd4 100644 --- a/java/ql/test/library-tests/switch-default-impossible-dispatch/Test.java +++ b/java/ql/test/library-tests/switch-default-impossible-dispatch/Test.java @@ -6,24 +6,51 @@ public class Test { 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(boolean unknown, int alsoUnknown) { + public static void test(int unknown, int alsoUnknown) { - I c1or2 = unknown ? new C1() : new C2(); + I i = unknown == 0 ? new C1() : unknown == 1 ? new C2() : unknown == 2 ? new Wrapper(new Object()) : new WrapperWrapper(new Wrapper(new Object())); - switch(c1or2) { + switch(i) { case C1 c1 when alsoUnknown == 1 -> { } - default -> c1or2.take(source()); // Could call either implementation + default -> i.take(source()); // Could call any implementation } - switch(c1or2) { + switch(i) { case C1 c1 -> { } - default -> c1or2.take(source()); // Can't call C1.take + default -> i.take(source()); // Can't call C1.take } - switch(c1or2) { + switch(i) { case C1 c1 -> { } - case null, default -> c1or2.take(source()); // Can't call C1.take + case null, default -> i.take(source()); // Can't call C1.take + } + + 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. } } diff --git a/java/ql/test/library-tests/switch-default-impossible-dispatch/test.expected b/java/ql/test/library-tests/switch-default-impossible-dispatch/test.expected index 777ebbc121a..8171281ad6c 100644 --- a/java/ql/test/library-tests/switch-default-impossible-dispatch/test.expected +++ b/java/ql/test/library-tests/switch-default-impossible-dispatch/test.expected @@ -1,4 +1,27 @@ -| Test.java:16:29:16:36 | source(...) | Test.java:7:65:7:65 | x | -| Test.java:16:29:16:36 | source(...) | Test.java:8:65:8:65 | x | -| Test.java:21:29:21:36 | source(...) | Test.java:8:65:8:65 | x | -| Test.java:26:40:26:47 | source(...) | Test.java:8:65:8:65 | x | +| 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: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 | From c1814408f08a3159ddebb136f1927961067aca60 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 16 Nov 2023 16:49:19 +0000 Subject: [PATCH 071/115] Fix guard basic block for switch cases --- java/ql/lib/semmle/code/java/Statement.qll | 14 ++++++++++++ .../semmle/code/java/controlflow/Guards.qll | 22 ++++++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index 43f72a94f78..05922649031 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -450,6 +450,20 @@ class SwitchCase extends Stmt, @case { result = this.getSwitch().getExpr() or result = this.getSwitchExpr().getExpr() } + /** + * Gets the `i`th case in this case's switch block. + */ + SwitchCase getSiblingCase(int i) { + result = this.getSwitch().getCase(i) or result = this.getSwitchExpr().getCase(i) + } + + /** + * Gets this case's ordinal in its switch block. + */ + int getCaseIndex() { + this = this.getSwitch().getCase(result) or this = this.getSwitchExpr().getCase(result) + } + /** * Holds if this `case` is a switch labeled rule of the form `... -> ...`. */ diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index 284c2278732..a3a378bcf28 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -103,11 +103,27 @@ 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 + // Not a switch case + result = this.(Expr).getBasicBlock() + or + // Return the closest pattern case statement before this one, including this one. + result = + max(int i, PatternCase c | + c = this.(SwitchCase).getSiblingCase(i) and i <= this.(SwitchCase).getCaseIndex() + | + c order by i + ).getBasicBlock() + or + // Not a pattern case and no preceding pattern case -- return the top of the switch block. + not exists(PatternCase c, int i | + c = this.(SwitchCase).getSiblingCase(i) and i <= this.(SwitchCase).getCaseIndex() + ) and result = this.(SwitchCase).getSelectorExpr().getBasicBlock() } From 668f445fb42cfdfd230495f6182fc6e7fa8d38dd Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 16 Nov 2023 17:29:08 +0000 Subject: [PATCH 072/115] Fix switchCaseControls and hasBranchEdge to account for mixed patterns and constant cases --- .../semmle/code/java/controlflow/Guards.qll | 39 ++++++++++--------- .../library-tests/guards12/PrintAst.expected | 16 ++++++++ java/ql/test/library-tests/guards12/Test.java | 5 +++ .../library-tests/guards12/guard.expected | 2 + java/ql/test/library-tests/guards12/options | 2 +- 5 files changed, 45 insertions(+), 19 deletions(-) diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index a3a378bcf28..69105a4a4db 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -152,13 +152,16 @@ class Guard extends ExprParent { bb2 = cb.getTestSuccessor(branch) ) or - exists(SwitchCase sc, ControlFlowNode pred | + exists(SwitchCase sc | 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 - bb1 = pred.getBasicBlock() + bb1 = sc.getControlFlowNode().getAPredecessor().getBasicBlock() and + // This is either the top of the switch block, or a preceding pattern case + // if one exists. + this.getBasicBlock() = bb1 ) or preconditionBranchEdge(this, bb1, bb2, branch) @@ -245,20 +248,20 @@ class TypeTestGuard extends Guard { } private predicate switchCaseControls(SwitchCase sc, BasicBlock bb) { - exists(BasicBlock caseblock, Expr selector | - selector = sc.getSelectorExpr() and - ( - if sc instanceof PatternCase - then caseblock.getANode() = sc.(PatternCase).getPattern().getControlFlowNode() - else ( - caseblock.getFirstNode() = sc.getControlFlowNode() and - // Check there is no fall-through edge from a previous case: - forall(ControlFlowNode pred | pred = sc.getControlFlowNode().getAPredecessor() | - pred.(Expr).getParent*() = selector - ) - ) - ) and - caseblock.bbDominates(bb) + exists(BasicBlock caseblock | + 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() | + // Branch straight from the switch selector: + pred.(Expr).getParent*() = sc.getSelectorExpr() + or + // Branch from a predecessor pattern case (note pattern cases cannot ever fall through) + pred = sc.getSiblingCase(_).(PatternCase) + or + // Branch from a predecessor pattern case's guard test, which also can't be a fallthrough edge + pred.(Expr).getParent*() = sc.getSiblingCase(_).(PatternCase).getGuard() + ) ) } diff --git a/java/ql/test/library-tests/guards12/PrintAst.expected b/java/ql/test/library-tests/guards12/PrintAst.expected index a8a96261ec0..668b4184537 100644 --- a/java/ql/test/library-tests/guards12/PrintAst.expected +++ b/java/ql/test/library-tests/guards12/PrintAst.expected @@ -38,3 +38,19 @@ Test.java: # 12| 0: [StringLiteral] "d" # 13| 3: [DefaultCase] default # 13| -1: [BlockStmt] { ... } +# 15| 2: [SwitchStmt] switch (...) +# 15| -1: [VarAccess] s +# 16| 0: [PatternCase] case T t ... +# 16| -3: [EQExpr] ... == ... +# 16| 0: [MethodCall] length(...) +# 16| -1: [VarAccess] s +# 16| 1: [IntegerLiteral] 4 +# 16| -1: [BlockStmt] { ... } +#-----| 0: (Single Local Variable Declaration) +# 16| 0: [TypeAccess] String +# 16| 1: [LocalVariableDeclExpr] s2 +# 17| 1: [ConstCase] case ... +# 17| -1: [BlockStmt] { ... } +# 17| 0: [StringLiteral] "e" +# 18| 2: [DefaultCase] default +# 18| -1: [BlockStmt] { ... } diff --git a/java/ql/test/library-tests/guards12/Test.java b/java/ql/test/library-tests/guards12/Test.java index 3ce8f6f4828..0803d68b4ee 100644 --- a/java/ql/test/library-tests/guards12/Test.java +++ b/java/ql/test/library-tests/guards12/Test.java @@ -12,5 +12,10 @@ class Test { case "d" -> { } default -> { } } + switch (s) { + case String s2 when s.length() == 4 -> { } + case "e" -> { } + default -> { } + } } } diff --git a/java/ql/test/library-tests/guards12/guard.expected b/java/ql/test/library-tests/guards12/guard.expected index 71d1818f29c..b10270b5071 100644 --- a/java/ql/test/library-tests/guards12/guard.expected +++ b/java/ql/test/library-tests/guards12/guard.expected @@ -6,3 +6,5 @@ | 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:7:17:17 | case ... | Test.java:15:13:15:13 | s | Test.java:17:12:17:14 | "e" | true | false | Test.java:18:7:18:16 | default | +| Test.java:17:7:17:17 | case ... | Test.java:15:13:15:13 | s | Test.java:17:12:17:14 | "e" | true | true | Test.java:17:7:17:17 | case ... | diff --git a/java/ql/test/library-tests/guards12/options b/java/ql/test/library-tests/guards12/options index 3f12170222c..a0d1b7e7002 100644 --- a/java/ql/test/library-tests/guards12/options +++ b/java/ql/test/library-tests/guards12/options @@ -1 +1 @@ -//semmle-extractor-options: --javac-args -source 14 -target 14 +//semmle-extractor-options: --javac-args --release 21 From ba0a05c8043dda860872b8b8d80a2a0a2e3bd684 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 16 Nov 2023 18:09:49 +0000 Subject: [PATCH 073/115] Add pretty-printing for patterns --- .../lib/semmle/code/java/PrettyPrintAst.qll | 58 +++++++++++++++++-- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll index 0e92bcc4407..3406d3e29e3 100644 --- a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll +++ b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll @@ -383,20 +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.getPattern().asBindingPattern().getName() - or - result = this.getPattern().asRecordPattern().toString() - ) + 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() } } @@ -748,6 +746,8 @@ private class PpSwitchStmt extends PpAst, SwitchStmt { } 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 or @@ -784,6 +784,36 @@ 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 = 2 + this.getPatternOffset() and result = ":" and not this.isRule() + or + i = 2 + this.getPatternOffset() and result = " -> " and this.isRule() + or + i = 4 + this.getPatternOffset() and result = ";" and exists(this.getRuleExpression()) + } + + private int getPatternOffset() { + if this.getPattern() instanceof LocalVariableDeclExpr then result = 2 else result = 0 + } + + override PpAst getChild(int i) { + i = 1 and result = this.getPattern().asBindingPattern().getTypeAccess() + or + i = 1 and result = this.getPattern().asRecordPattern() + or + i = 4 and result = this.getRuleExpression() + or + i = 4 and result = this.getRuleStatement() + } +} + private class PpSynchronizedStmt extends PpAst, SynchronizedStmt { override string getPart(int i) { i = 0 and result = "synchronized (" @@ -1039,3 +1069,19 @@ 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)))) * 2) and result = ", " + or + i = 1 + (count(this.getSubPattern(_)) * 2) and result = ")" + } + + override PpAst getChild(int i) { + exists(int x | result = this.getSubPattern(x) | i = 2 + (x * 2)) + } +} From 0b08507033fbe6e0f6b74678f97f72348a5853fe Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 16 Nov 2023 18:11:25 +0000 Subject: [PATCH 074/115] Document testedBranch --- java/ql/lib/semmle/code/java/PrettyPrintAst.qll | 2 +- java/ql/lib/semmle/code/java/controlflow/Guards.qll | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll index 3406d3e29e3..afeb17b6846 100644 --- a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll +++ b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll @@ -16,7 +16,7 @@ predicate pp(ClassOrInterface c, string s, int line) { exists(PpAst e | getEnclosingAst*(e) = c | ppPart(e, part, line, i)) | part order by i - ) + ) } private PpAst getEnclosingAst(PpAst e) { diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index 69105a4a4db..d9def6ab99f 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -223,7 +223,7 @@ class TypeTestGuard extends Guard { } /** - * Holds if this guard tests whether `e` has type `t`. + * Holds if this guard tests whether `e` has type `t` on `testedBranch`. * * Note that record patterns that make at least one tighter restriction than the record's definition * (e.g. matching `record R(Object)` with `case R(String)`) means this only guarantees the tested type From 6b3080ae92142b9de8555cf4b1f48a0ca9b97ed7 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 17 Nov 2023 09:22:08 +0000 Subject: [PATCH 075/115] Allow case null, default to be the first switch case This is consistent with existing treatment of `case null: default:` --- java/ql/lib/semmle/code/java/ControlFlowGraph.qll | 3 +++ java/ql/test/library-tests/pattern-switch/cfg/test.expected | 1 + 2 files changed, 4 insertions(+) diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index 9b6e17634d9..aeab011085e 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -471,6 +471,7 @@ private module ControlFlowGraphImpl { private SwitchCase getASuccessorSwitchCase(PatternCase pred) { result.getParent() = pred.getParent() and result.getIndex() > pred.getIndex() and + // Note we do include `case null, default` (as well as plain old `default`) here. not result.(ConstCase).getValue(_) instanceof NullLiteral and ( result.getIndex() <= getNextPatternCase(pred).getIndex() @@ -492,6 +493,8 @@ private module ControlFlowGraphImpl { ( result.(ConstCase).getValue(_) instanceof NullLiteral or + result instanceof NullDefaultCase + or not exists(getPatternCase(switch, _)) or result.getIndex() <= getPatternCase(switch, 0).getIndex() diff --git a/java/ql/test/library-tests/pattern-switch/cfg/test.expected b/java/ql/test/library-tests/pattern-switch/cfg/test.expected index d5337fcd0b3..d35738fd4a0 100644 --- a/java/ql/test/library-tests/pattern-switch/cfg/test.expected +++ b/java/ql/test/library-tests/pattern-switch/cfg/test.expected @@ -144,6 +144,7 @@ | 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 T t ... | +| 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 | ; | | Test.java:57:10:57:19 | System.out | Test.java:57:29:57:42 | "It's Const1!" | From 68fe7efd9e7816566f8db3fbd83eace0b314c3bc Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 17 Nov 2023 09:37:45 +0000 Subject: [PATCH 076/115] autoformat --- java/ql/lib/semmle/code/java/PrettyPrintAst.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll index afeb17b6846..3406d3e29e3 100644 --- a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll +++ b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll @@ -16,7 +16,7 @@ predicate pp(ClassOrInterface c, string s, int line) { exists(PpAst e | getEnclosingAst*(e) = c | ppPart(e, part, line, i)) | part order by i - ) + ) } private PpAst getEnclosingAst(PpAst e) { From dd41f50fbfb1dcc9f84407414ab1b4cd16243acd Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 17 Nov 2023 10:06:20 +0000 Subject: [PATCH 077/115] Fix uses of ConditionBlock that require a condition expression (not a switch case statement) --- java/ql/lib/semmle/code/java/dataflow/Nullness.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/ql/lib/semmle/code/java/dataflow/Nullness.qll b/java/ql/lib/semmle/code/java/dataflow/Nullness.qll index 01900fcbd64..fb2fc668cf3 100644 --- a/java/ql/lib/semmle/code/java/dataflow/Nullness.qll +++ b/java/ql/lib/semmle/code/java/dataflow/Nullness.qll @@ -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 | From 89f7e7f76abd848b127901e862ef2c45089620c1 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 17 Nov 2023 10:20:10 +0000 Subject: [PATCH 078/115] autoformat --- .../semmle/code/java/dataflow/internal/DataFlowUtil.qll | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll index 0fa9412cb5b..723b7784b1e 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll @@ -194,9 +194,13 @@ predicate simpleAstFlowStep(Expr e1, Expr e2) { // 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()) + 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()) + 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()) } From c11a260369abb0a9dde45257ddfb7862afbc8f9c Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 17 Nov 2023 11:10:06 +0000 Subject: [PATCH 079/115] Note we can't prove certain unreachable callables when 'case null' is present --- .../switch-default-impossible-dispatch/Test.java | 7 ++++++- .../switch-default-impossible-dispatch/test.expected | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/java/ql/test/library-tests/switch-default-impossible-dispatch/Test.java b/java/ql/test/library-tests/switch-default-impossible-dispatch/Test.java index b8b0123fdd4..cf6e67e5847 100644 --- a/java/ql/test/library-tests/switch-default-impossible-dispatch/Test.java +++ b/java/ql/test/library-tests/switch-default-impossible-dispatch/Test.java @@ -25,7 +25,7 @@ public class Test { switch(i) { case C1 c1 -> { } - case null, default -> i.take(source()); // Can't call C1.take + case null, default -> i.take(source()); // Can't call C1.take (but we don't currently notice) } switch(i) { @@ -53,6 +53,11 @@ public class Test { 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) + } + } } diff --git a/java/ql/test/library-tests/switch-default-impossible-dispatch/test.expected b/java/ql/test/library-tests/switch-default-impossible-dispatch/test.expected index 8171281ad6c..17d478d4298 100644 --- a/java/ql/test/library-tests/switch-default-impossible-dispatch/test.expected +++ b/java/ql/test/library-tests/switch-default-impossible-dispatch/test.expected @@ -5,6 +5,7 @@ | 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 | @@ -25,3 +26,7 @@ | 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 | From da62a046537c65918c94e322e6720a24a1e435bb Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Mon, 20 Nov 2023 18:14:36 +0000 Subject: [PATCH 080/115] Note that binding variables may be casting nodes --- .../dataflow/internal/DataFlowPrivate.qll | 13 +++++- .../flow-through-binding/Test.java | 42 +++++++++++++++++++ .../flow-through-binding/options | 1 + .../flow-through-binding/test.expected | 3 ++ .../flow-through-binding/test.ql | 18 ++++++++ 5 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 java/ql/test/library-tests/flow-through-binding/Test.java create mode 100644 java/ql/test/library-tests/flow-through-binding/options create mode 100644 java/ql/test/library-tests/flow-through-binding/test.expected create mode 100644 java/ql/test/library-tests/flow-through-binding/test.ql diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll index bdfa9507673..f5466b2d739 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll @@ -375,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 = diff --git a/java/ql/test/library-tests/flow-through-binding/Test.java b/java/ql/test/library-tests/flow-through-binding/Test.java new file mode 100644 index 00000000000..b3e4cbef73c --- /dev/null +++ b/java/ql/test/library-tests/flow-through-binding/Test.java @@ -0,0 +1,42 @@ +public class Test { + + public static Object testFlowThroughSwitchStmt(String s, Integer i, boolean unknown) { + Object o = unknown ? s : i; + switch (o) { + case Integer i2 -> { return i2; } + default -> { return null; } + } + } + + public static Object testFlowThroughSwitchExpr(String s, Integer i, boolean unknown) { + Object o = unknown ? s : i; + Integer toRet = switch (o) { + case Integer i2 -> 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 i2; + else + return null; + } + + public static 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)); + + } + +} diff --git a/java/ql/test/library-tests/flow-through-binding/options b/java/ql/test/library-tests/flow-through-binding/options new file mode 100644 index 00000000000..a0d1b7e7002 --- /dev/null +++ b/java/ql/test/library-tests/flow-through-binding/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args --release 21 diff --git a/java/ql/test/library-tests/flow-through-binding/test.expected b/java/ql/test/library-tests/flow-through-binding/test.expected new file mode 100644 index 00000000000..8fa7e64d645 --- /dev/null +++ b/java/ql/test/library-tests/flow-through-binding/test.expected @@ -0,0 +1,3 @@ +| Test.java:35:23:35:30 | source(...) | Test.java:36:10:36:61 | testFlowThroughSwitchStmt(...) | +| Test.java:35:23:35:30 | source(...) | Test.java:37:10:37:61 | testFlowThroughSwitchExpr(...) | +| Test.java:35:23:35:30 | source(...) | Test.java:38:10:38:68 | testFlowThroughBindingInstanceOf(...) | diff --git a/java/ql/test/library-tests/flow-through-binding/test.ql b/java/ql/test/library-tests/flow-through-binding/test.ql new file mode 100644 index 00000000000..26c4f88db06 --- /dev/null +++ b/java/ql/test/library-tests/flow-through-binding/test.ql @@ -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; + +from DataFlow::Node source, DataFlow::Node sink +where Flow::flow(source, sink) +select source, sink From f0144d6a3d2e3463f8576656d401e1ade355f513 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 21 Nov 2023 14:44:19 +0000 Subject: [PATCH 081/115] Expose that case guard test controls its case body --- .../library-tests/guards12/PrintAst.expected | 32 +++++++++++-------- java/ql/test/library-tests/guards12/Test.java | 3 +- .../library-tests/guards12/guard.expected | 5 +-- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/java/ql/test/library-tests/guards12/PrintAst.expected b/java/ql/test/library-tests/guards12/PrintAst.expected index 668b4184537..79b478f6aeb 100644 --- a/java/ql/test/library-tests/guards12/PrintAst.expected +++ b/java/ql/test/library-tests/guards12/PrintAst.expected @@ -38,19 +38,23 @@ Test.java: # 12| 0: [StringLiteral] "d" # 13| 3: [DefaultCase] default # 13| -1: [BlockStmt] { ... } -# 15| 2: [SwitchStmt] switch (...) -# 15| -1: [VarAccess] s -# 16| 0: [PatternCase] case T t ... -# 16| -3: [EQExpr] ... == ... -# 16| 0: [MethodCall] length(...) -# 16| -1: [VarAccess] s -# 16| 1: [IntegerLiteral] 4 -# 16| -1: [BlockStmt] { ... } -#-----| 0: (Single Local Variable Declaration) -# 16| 0: [TypeAccess] String -# 16| 1: [LocalVariableDeclExpr] s2 -# 17| 1: [ConstCase] case ... +# 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 T t ... +# 17| -3: [EQExpr] ... == ... +# 17| 0: [VarAccess] len +# 17| 1: [IntegerLiteral] 4 # 17| -1: [BlockStmt] { ... } -# 17| 0: [StringLiteral] "e" -# 18| 2: [DefaultCase] default +#-----| 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] { ... } diff --git a/java/ql/test/library-tests/guards12/Test.java b/java/ql/test/library-tests/guards12/Test.java index 0803d68b4ee..1071030a1fa 100644 --- a/java/ql/test/library-tests/guards12/Test.java +++ b/java/ql/test/library-tests/guards12/Test.java @@ -12,8 +12,9 @@ class Test { case "d" -> { } default -> { } } + int len = s.length(); switch (s) { - case String s2 when s.length() == 4 -> { } + case String s2 when len == 4 -> { } case "e" -> { } default -> { } } diff --git a/java/ql/test/library-tests/guards12/guard.expected b/java/ql/test/library-tests/guards12/guard.expected index b10270b5071..7144889ed34 100644 --- a/java/ql/test/library-tests/guards12/guard.expected +++ b/java/ql/test/library-tests/guards12/guard.expected @@ -6,5 +6,6 @@ | 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:7:17:17 | case ... | Test.java:15:13:15:13 | s | Test.java:17:12:17:14 | "e" | true | false | Test.java:18:7:18:16 | default | -| Test.java:17:7:17:17 | case ... | Test.java:15:13:15:13 | s | Test.java:17:12:17:14 | "e" | true | true | Test.java:17:7:17: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 ... | From 47e3d7d8a58f5b4f0039c1dff3c7c8288691eba5 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 21 Nov 2023 14:47:00 +0000 Subject: [PATCH 082/115] Cast back to Object in advance of returning, to ensure the test doesn't mask a shortcoming of type pruning by pruning at the return site --- java/ql/test/library-tests/flow-through-binding/Test.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/java/ql/test/library-tests/flow-through-binding/Test.java b/java/ql/test/library-tests/flow-through-binding/Test.java index b3e4cbef73c..04cde964538 100644 --- a/java/ql/test/library-tests/flow-through-binding/Test.java +++ b/java/ql/test/library-tests/flow-through-binding/Test.java @@ -3,15 +3,15 @@ public class Test { public static Object testFlowThroughSwitchStmt(String s, Integer i, boolean unknown) { Object o = unknown ? s : i; switch (o) { - case Integer i2 -> { return i2; } + case Integer i2 -> { return (Object)i2; } default -> { return null; } } } public static Object testFlowThroughSwitchExpr(String s, Integer i, boolean unknown) { Object o = unknown ? s : i; - Integer toRet = switch (o) { - case Integer i2 -> i2; + Object toRet = switch (o) { + case Integer i2 -> (Object)i2; default -> null; }; return toRet; @@ -20,7 +20,7 @@ public class Test { public static Object testFlowThroughBindingInstanceOf(String s, Integer i, boolean unknown) { Object o = unknown ? s : i; if (o instanceof Integer i2) - return i2; + return (Object)i2; else return null; } From ef6ea71e433d55e945fa2a784b37eb14adae3a3c Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 21 Nov 2023 14:51:35 +0000 Subject: [PATCH 083/115] Revert unnecessary exists statement --- .../semmle/code/java/controlflow/internal/GuardsLogic.qll | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/java/ql/lib/semmle/code/java/controlflow/internal/GuardsLogic.qll b/java/ql/lib/semmle/code/java/controlflow/internal/GuardsLogic.qll index f96c6cdb93a..9fed7516ba3 100644 --- a/java/ql/lib/semmle/code/java/controlflow/internal/GuardsLogic.qll +++ b/java/ql/lib/semmle/code/java/controlflow/internal/GuardsLogic.qll @@ -55,13 +55,9 @@ predicate implies_v1(Guard g1, boolean b1, Guard g2, boolean b2) { ) ) or - exists(DefaultCase sc | g1 = sc | - sc.getSwitch().getAConstCase() = g2 and b1 = true and b2 = false - ) + g1.(DefaultCase).getSwitch().getAConstCase() = g2 and b1 = true and b2 = false or - exists(DefaultCase sc | g1 = sc | - sc.getSwitchExpr().getAConstCase() = g2 and b1 = true and b2 = false - ) + g1.(DefaultCase).getSwitchExpr().getAConstCase() = g2 and b1 = true and b2 = false or exists(MethodCall check, int argIndex | check = g1 | conditionCheckArgument(check, argIndex, _) and From 29fdd04eb0441e3e31abf2c8be779f80c95d28a3 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 21 Nov 2023 15:54:52 +0000 Subject: [PATCH 084/115] Include switch and instanceof binding in Variable.getAnAssignedValue, and test via endsInQuote --- java/ql/lib/semmle/code/java/Variable.qll | 7 ++-- .../semmle/examples/SqlConcatenated.expected | 1 + .../semmle/examples/SqlTaintedLocal.expected | 35 +++++++++++-------- .../CWE-089/semmle/examples/Test.java | 15 ++++++++ .../semmle/examples/controlledString.expected | 10 ++++++ .../semmle/examples/endsInQuote.expected | 4 +++ .../security/CWE-089/semmle/examples/options | 2 +- .../semmle/examples/taintedString.expected | 12 +++++-- 8 files changed, 66 insertions(+), 20 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Variable.qll b/java/ql/lib/semmle/code/java/Variable.qll index 82314824395..8ed650d5f16 100644 --- a/java/ql/lib/semmle/code/java/Variable.qll +++ b/java/ql/lib/semmle/code/java/Variable.qll @@ -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()) } diff --git a/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlConcatenated.expected b/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlConcatenated.expected index 66dfeacc496..fc1d87f06b1 100644 --- a/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlConcatenated.expected +++ b/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlConcatenated.expected @@ -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 | diff --git a/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected b/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected index 1884d76f811..45577278a7e 100644 --- a/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected +++ b/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected @@ -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 | diff --git a/java/ql/test/query-tests/security/CWE-089/semmle/examples/Test.java b/java/ql/test/query-tests/security/CWE-089/semmle/examples/Test.java index f6e4ff61dc4..dee0db129eb 100644 --- a/java/ql/test/query-tests/security/CWE-089/semmle/examples/Test.java +++ b/java/ql/test/query-tests/security/CWE-089/semmle/examples/Test.java @@ -210,12 +210,27 @@ abstract class Test { } } + private static void bindingVars(String[] args) throws IOException, SQLException { + // BAD: the category might have SQL special characters in it + { + String category = args[1]; + Statement statement = connection.createStatement(); + String prefix = "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='"; + String suffix = "' ORDER BY PRICE"; + switch(prefix) { + case String prefixAlias when prefix.length() > 10 -> statement.executeQuery(prefixAlias + category + suffix); + default -> { } + } + } + } + public static void main(String[] args) throws IOException, SQLException { tainted(args); unescaped(); good(args); controlledStrings(); tableNames(args); + bindingVars(args); } } diff --git a/java/ql/test/query-tests/security/CWE-089/semmle/examples/controlledString.expected b/java/ql/test/query-tests/security/CWE-089/semmle/examples/controlledString.expected index cdb777f659e..ad632dfc8c2 100644 --- a/java/ql/test/query-tests/security/CWE-089/semmle/examples/controlledString.expected +++ b/java/ql/test/query-tests/security/CWE-089/semmle/examples/controlledString.expected @@ -1,6 +1,16 @@ | | 1 | Test.java:20:2:20:9 | FloorWax | | | 1 | Test.java:20:12:20:18 | Topping | | | 1 | Test.java:20:21:20:28 | Biscuits | +| bindingVars | 3 | Test.java:216:48:216:48 | 1 | +| bindingVars | 5 | Test.java:218:20:218:73 | "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='" | +| bindingVars | 6 | Test.java:219:20:219:37 | "' ORDER BY PRICE" | +| bindingVars | 7 | Test.java:220:11:220:16 | prefix | +| bindingVars | 8 | Test.java:221:34:221:39 | prefix | +| bindingVars | 8 | Test.java:221:34:221:48 | length(...) | +| bindingVars | 8 | Test.java:221:34:221:53 | ... > ... | +| bindingVars | 8 | Test.java:221:52:221:53 | 10 | +| bindingVars | 8 | Test.java:221:81:221:91 | prefixAlias | +| bindingVars | 8 | Test.java:221:106:221:111 | suffix | | checkIdentifier | 1 | Validation.java:7:12:7:16 | i | | checkIdentifier | 1 | Validation.java:7:16:7:16 | 0 | | checkIdentifier | 1 | Validation.java:7:19:7:19 | i | diff --git a/java/ql/test/query-tests/security/CWE-089/semmle/examples/endsInQuote.expected b/java/ql/test/query-tests/security/CWE-089/semmle/examples/endsInQuote.expected index 2b6d12b426d..fe8b3aede67 100644 --- a/java/ql/test/query-tests/security/CWE-089/semmle/examples/endsInQuote.expected +++ b/java/ql/test/query-tests/security/CWE-089/semmle/examples/endsInQuote.expected @@ -1,3 +1,7 @@ +| bindingVars | 5 | Test.java:218:20:218:73 | "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='" | +| bindingVars | 7 | Test.java:220:11:220:16 | prefix | +| bindingVars | 8 | Test.java:221:34:221:39 | prefix | +| bindingVars | 8 | Test.java:221:81:221:91 | prefixAlias | | controlledStrings | 4 | Test.java:137:26:137:79 | "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='" | | controlledStrings | 12 | Test.java:145:27:145:80 | "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='" | | controlledStrings | 20 | Test.java:153:35:153:88 | "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='" | diff --git a/java/ql/test/query-tests/security/CWE-089/semmle/examples/options b/java/ql/test/query-tests/security/CWE-089/semmle/examples/options index 1b24aa77776..8c08f833401 100644 --- a/java/ql/test/query-tests/security/CWE-089/semmle/examples/options +++ b/java/ql/test/query-tests/security/CWE-089/semmle/examples/options @@ -1 +1 @@ -//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/mongodbClient:${testdir}/../../../../../stubs/springframework-5.3.8:${testdir}/../../../../../stubs/apache-hive +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/mongodbClient:${testdir}/../../../../../stubs/springframework-5.3.8:${testdir}/../../../../../stubs/apache-hive --release 21 diff --git a/java/ql/test/query-tests/security/CWE-089/semmle/examples/taintedString.expected b/java/ql/test/query-tests/security/CWE-089/semmle/examples/taintedString.expected index 67c6629093a..e6f4d14eaad 100644 --- a/java/ql/test/query-tests/security/CWE-089/semmle/examples/taintedString.expected +++ b/java/ql/test/query-tests/security/CWE-089/semmle/examples/taintedString.expected @@ -59,9 +59,15 @@ | Test.java:183:22:183:31 | tableNames | 23 | Test.java:206:36:208:55 | ... + ... | | Test.java:183:22:183:31 | tableNames | 24 | Test.java:207:8:207:18 | userTabName | | Test.java:183:22:183:31 | tableNames | 26 | Test.java:209:47:209:68 | queryWithUserTableName | -| Test.java:213:21:213:24 | main | 1 | Test.java:214:11:214:14 | args | -| Test.java:213:21:213:24 | main | 3 | Test.java:216:8:216:11 | args | -| Test.java:213:21:213:24 | main | 5 | Test.java:218:14:218:17 | args | +| Test.java:213:22:213:32 | bindingVars | 3 | Test.java:216:43:216:46 | args | +| Test.java:213:22:213:32 | bindingVars | 3 | Test.java:216:43:216:49 | ...[...] | +| Test.java:213:22:213:32 | bindingVars | 8 | Test.java:221:81:221:102 | ... + ... | +| Test.java:213:22:213:32 | bindingVars | 8 | Test.java:221:81:221:111 | ... + ... | +| Test.java:213:22:213:32 | bindingVars | 8 | Test.java:221:95:221:102 | category | +| Test.java:227:21:227:24 | main | 1 | Test.java:228:11:228:14 | args | +| Test.java:227:21:227:24 | main | 3 | Test.java:230:8:230:11 | args | +| Test.java:227:21:227:24 | main | 5 | Test.java:232:14:232:17 | args | +| Test.java:227:21:227:24 | main | 6 | Test.java:233:15:233:18 | args | | Validation.java:6:21:6:35 | checkIdentifier | 1 | Validation.java:7:23:7:24 | id | | Validation.java:6:21:6:35 | checkIdentifier | 2 | Validation.java:8:13:8:14 | id | | Validation.java:6:21:6:35 | checkIdentifier | 2 | Validation.java:8:13:8:24 | charAt(...) | From bbc0f29f161743a42e0985c3b73a6d05cd81ce44 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 21 Nov 2023 16:50:41 +0000 Subject: [PATCH 085/115] Restrict getCheckedType to unrestricted records, introduce getSyntacticCheckedType and use that where appropriate --- java/ql/lib/semmle/code/java/Dependency.qll | 2 ++ .../lib/semmle/code/java/DependencyCounts.qll | 2 ++ java/ql/lib/semmle/code/java/Expr.qll | 20 ++++++++++++++++++- .../semmle/code/java/controlflow/Guards.qll | 2 +- .../semmle/code/java/dataflow/TypeFlow.qll | 2 +- .../Language Abuse/DubiousTypeTestOfThis.ql | 2 +- .../Comparison/EqualsUsesInstanceOf.ql | 2 +- 7 files changed, 27 insertions(+), 5 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Dependency.qll b/java/ql/lib/semmle/code/java/Dependency.qll index 5378569dd67..1bdf0140079 100644 --- a/java/ql/lib/semmle/code/java/Dependency.qll +++ b/java/ql/lib/semmle/code/java/Dependency.qll @@ -78,6 +78,8 @@ 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`. diff --git a/java/ql/lib/semmle/code/java/DependencyCounts.qll b/java/ql/lib/semmle/code/java/DependencyCounts.qll index 05a7be6e8f7..ca0acdedcde 100644 --- a/java/ql/lib/semmle/code/java/DependencyCounts.qll +++ b/java/ql/lib/semmle/code/java/DependencyCounts.qll @@ -101,6 +101,8 @@ 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`. diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index ec791b443a0..53026610257 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1592,9 +1592,27 @@ class InstanceOfExpr extends Expr, @instanceofexpr { /** * Gets the type this `instanceof` expression checks for. * - * For a match against a record pattern, this is the type of the outermost record type. + * 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() diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index d9def6ab99f..e670c692749 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -204,7 +204,7 @@ class TypeTestGuard extends Guard { TypeTestGuard() { exists(InstanceOfExpr ioe | this = ioe | testedExpr = ioe.getExpr() and - testedType = ioe.getCheckedType() + testedType = ioe.getSyntacticCheckedType() ) or exists(PatternCase pc | this = pc | diff --git a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll index b16cfb9262e..be7d499f68a 100644 --- a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll @@ -627,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 ) } diff --git a/java/ql/src/Language Abuse/DubiousTypeTestOfThis.ql b/java/ql/src/Language Abuse/DubiousTypeTestOfThis.ql index ef9957685d7..55cfe5d6b00 100644 --- a/java/ql/src/Language Abuse/DubiousTypeTestOfThis.ql +++ b/java/ql/src/Language Abuse/DubiousTypeTestOfThis.ql @@ -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.", diff --git a/java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.ql b/java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.ql index b1b23038262..417c299fcf2 100644 --- a/java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.ql +++ b/java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.ql @@ -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() ) } From 5511955b604f7d92141cfa46e6e9ea8fd80dcd2f Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 21 Nov 2023 16:54:46 +0000 Subject: [PATCH 086/115] Simplify getCaseIndex --- java/ql/lib/semmle/code/java/Statement.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index 05922649031..59a4671d742 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -461,7 +461,7 @@ class SwitchCase extends Stmt, @case { * Gets this case's ordinal in its switch block. */ int getCaseIndex() { - this = this.getSwitch().getCase(result) or this = this.getSwitchExpr().getCase(result) + this = any(SwitchStmt ss).getCase(result) or this = any(SwitchExpr se).getCase(result) } /** From 07d2ce7a4123937b5d0199b2836fd4862674e9d5 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 21 Nov 2023 16:55:49 +0000 Subject: [PATCH 087/115] Change pretty-printing of PatternCase --- java/ql/lib/semmle/code/java/Statement.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index 59a4671d742..bd30b235b46 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -551,9 +551,9 @@ class PatternCase extends SwitchCase { /** Gets the guard applicable to this pattern case, if any. */ Expr getGuard() { result.isNthChildOf(this, -3) } - override string pp() { result = "case T t ..." } + override string pp() { result = "case " } - override string toString() { result = "case T t ..." } + override string toString() { result = "case " } override string getHalsteadID() { result = "PatternCase" } From 3bde66adfb915160bb1844d6ea41fb7e7a1ec5ef Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 21 Nov 2023 18:42:06 +0000 Subject: [PATCH 088/115] Pretty-print 'var' statements --- java/ql/lib/semmle/code/java/PrettyPrintAst.qll | 2 ++ 1 file changed, 2 insertions(+) diff --git a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll index 3406d3e29e3..a2dc3bb2ece 100644 --- a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll +++ b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll @@ -929,6 +929,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()) From a11c5c725773580e5fe74bda82e2b3e07e176516 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 21 Nov 2023 18:42:31 +0000 Subject: [PATCH 089/115] Fixup pretty-printer and add test --- .../lib/semmle/code/java/PrettyPrintAst.qll | 22 ++--- .../test/library-tests/prettyprint/Test.java | 43 ++++++++++ .../ql/test/library-tests/prettyprint/options | 1 + .../library-tests/prettyprint/pp.expected | 80 +++++++++++++++++++ java/ql/test/library-tests/prettyprint/pp.ql | 5 ++ 5 files changed, 140 insertions(+), 11 deletions(-) create mode 100644 java/ql/test/library-tests/prettyprint/Test.java create mode 100644 java/ql/test/library-tests/prettyprint/options create mode 100644 java/ql/test/library-tests/prettyprint/pp.expected create mode 100644 java/ql/test/library-tests/prettyprint/pp.ql diff --git a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll index a2dc3bb2ece..3e5b47a5369 100644 --- a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll +++ b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll @@ -745,13 +745,17 @@ 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 not this instanceof DefaultCase + i = 0 and result = "case " and not isNonNullDefaultCase(this) or i = this.lastConstCaseValueIndex() and result = "default" and this instanceof NullDefaultCase or @@ -792,15 +796,11 @@ private class PpPatternCase extends PpAst, PatternCase { or i = 3 and result = this.getPattern().asBindingPattern().getName() or - i = 2 + this.getPatternOffset() and result = ":" and not this.isRule() + i = 4 and result = ":" and not this.isRule() or - i = 2 + this.getPatternOffset() and result = " -> " and this.isRule() + i = 4 and result = " -> " and this.isRule() or - i = 4 + this.getPatternOffset() and result = ";" and exists(this.getRuleExpression()) - } - - private int getPatternOffset() { - if this.getPattern() instanceof LocalVariableDeclExpr then result = 2 else result = 0 + i = 6 and result = ";" and exists(this.getRuleExpression()) } override PpAst getChild(int i) { @@ -808,9 +808,9 @@ private class PpPatternCase extends PpAst, PatternCase { or i = 1 and result = this.getPattern().asRecordPattern() or - i = 4 and result = this.getRuleExpression() + i = 5 and result = this.getRuleExpression() or - i = 4 and result = this.getRuleStatement() + i = 5 and result = this.getRuleStatement() } } diff --git a/java/ql/test/library-tests/prettyprint/Test.java b/java/ql/test/library-tests/prettyprint/Test.java new file mode 100644 index 00000000000..1ad32d45f3e --- /dev/null +++ b/java/ql/test/library-tests/prettyprint/Test.java @@ -0,0 +1,43 @@ +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)) { } + + } + +} diff --git a/java/ql/test/library-tests/prettyprint/options b/java/ql/test/library-tests/prettyprint/options new file mode 100644 index 00000000000..a0d1b7e7002 --- /dev/null +++ b/java/ql/test/library-tests/prettyprint/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args --release 21 diff --git a/java/ql/test/library-tests/prettyprint/pp.expected b/java/ql/test/library-tests/prettyprint/pp.expected new file mode 100644 index 00000000000..b8aadaf38b4 --- /dev/null +++ b/java/ql/test/library-tests/prettyprint/pp.expected @@ -0,0 +1,80 @@ +| 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) { } | +| Test.java:1:14:1:17 | Test | 7 | | +| Test.java:1:14:1:17 | Test | 8 | public final int hashCode() { } | +| Test.java:1:14:1:17 | Test | 9 | | +| Test.java:1:14:1:17 | Test | 10 | public final String toString() { } | +| Test.java:1:14:1:17 | Test | 11 | | +| Test.java:1:14:1:17 | Test | 12 | public int x() { } | +| 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) { } | +| Test.java:1:14:1:17 | Test | 24 | | +| Test.java:1:14:1:17 | Test | 25 | public final int hashCode() { } | +| Test.java:1:14:1:17 | Test | 26 | | +| Test.java:1:14:1:17 | Test | 27 | public S s() { } | +| Test.java:1:14:1:17 | Test | 28 | | +| Test.java:1:14:1:17 | Test | 29 | public final String toString() { } | +| Test.java:1:14:1:17 | Test | 30 | | +| Test.java:1:14:1:17 | Test | 31 | public String y() { } | +| 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(x), 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(x), y) -> { | +| Test.java:1:14:1:17 | Test | 57 | } | +| Test.java:1:14:1:17 | Test | 58 | case 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(x), y): | +| Test.java:1:14:1:17 | Test | 65 | yield x; | +| Test.java:1:14:1:17 | Test | 66 | case 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(x), 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(x), y)) { | +| Test.java:1:14:1:17 | Test | 77 | } | +| Test.java:1:14:1:17 | Test | 78 | } | +| Test.java:1:14:1:17 | Test | 79 | } | diff --git a/java/ql/test/library-tests/prettyprint/pp.ql b/java/ql/test/library-tests/prettyprint/pp.ql new file mode 100644 index 00000000000..baa92fc6a86 --- /dev/null +++ b/java/ql/test/library-tests/prettyprint/pp.ql @@ -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 From 087be2cca8b9c54a0c5d862074a0a55f939f51d4 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 21 Nov 2023 19:12:02 +0000 Subject: [PATCH 090/115] Adjust test expectations --- .../dependency/PrintAst.expected | 4 +- .../library-tests/guards12/PrintAst.expected | 2 +- .../pattern-switch/cfg/test.expected | 96 +++++++++---------- .../library-tests/printAst/PrintAst.expected | 24 ++--- 4 files changed, 63 insertions(+), 63 deletions(-) diff --git a/java/ql/test/library-tests/dependency/PrintAst.expected b/java/ql/test/library-tests/dependency/PrintAst.expected index e2b2cbb98f1..ab71b03fe55 100644 --- a/java/ql/test/library-tests/dependency/PrintAst.expected +++ b/java/ql/test/library-tests/dependency/PrintAst.expected @@ -62,7 +62,7 @@ dependency/A.java: # 30| 1: [ReturnStmt] return ... # 31| 1: [SwitchStmt] switch (...) # 31| -1: [VarAccess] o -# 32| 0: [PatternCase] case T t ... +# 32| 0: [PatternCase] case #-----| 0: (Single Local Variable Declaration) # 32| 0: [TypeAccess] Used2 # 32| 1: [LocalVariableDeclExpr] u2 @@ -73,7 +73,7 @@ dependency/A.java: # 35| 1: [LocalVariableDeclExpr] x # 35| 0: [SwitchExpr] switch (...) # 35| -1: [VarAccess] o -# 36| 0: [PatternCase] case T t ... +# 36| 0: [PatternCase] case #-----| 0: (Single Local Variable Declaration) # 36| 0: [TypeAccess] Used3 # 36| 1: [LocalVariableDeclExpr] u3 diff --git a/java/ql/test/library-tests/guards12/PrintAst.expected b/java/ql/test/library-tests/guards12/PrintAst.expected index 79b478f6aeb..9296be2f783 100644 --- a/java/ql/test/library-tests/guards12/PrintAst.expected +++ b/java/ql/test/library-tests/guards12/PrintAst.expected @@ -45,7 +45,7 @@ Test.java: # 15| -1: [VarAccess] s # 16| 3: [SwitchStmt] switch (...) # 16| -1: [VarAccess] s -# 17| 0: [PatternCase] case T t ... +# 17| 0: [PatternCase] case # 17| -3: [EQExpr] ... == ... # 17| 0: [VarAccess] len # 17| 1: [IntegerLiteral] 4 diff --git a/java/ql/test/library-tests/pattern-switch/cfg/test.expected b/java/ql/test/library-tests/pattern-switch/cfg/test.expected index d35738fd4a0..b4b88b88c63 100644 --- a/java/ql/test/library-tests/pattern-switch/cfg/test.expected +++ b/java/ql/test/library-tests/pattern-switch/cfg/test.expected @@ -2,16 +2,16 @@ | 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 T t ... | -| Test.java:6:8:6:23 | case T t ... | Test.java:6:20:6:20 | s | -| Test.java:6:8:6:23 | case T t ... | Test.java:7:8:7:24 | case T t ... | +| Test.java:5:14:5:18 | thing | Test.java:6:8:6:23 | case | +| Test.java:6:8:6:23 | case | Test.java:6:20:6:20 | s | +| Test.java:6:8:6:23 | case | Test.java:7:8:7:24 | case | | 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 | ; | 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 T t ... | Test.java:7:21:7:21 | i | -| Test.java:7:8:7:24 | case T t ... | Test.java:8:8:8:17 | default | +| Test.java:7:8:7:24 | case | Test.java:7:21:7:21 | i | +| Test.java:7:8:7:24 | case | 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 (...) | @@ -22,17 +22,17 @@ | 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 T t ... | -| Test.java:12:8:12:21 | case T t ... | Test.java:12:20:12:20 | s | -| Test.java:12:8:12:21 | case T t ... | Test.java:15:8:15:22 | case T t ... | +| Test.java:11:14:11:18 | thing | Test.java:12:8:12:21 | case | +| Test.java:12:8:12:21 | case | Test.java:12:20:12:20 | s | +| Test.java:12:8:12:21 | case | Test.java:15:8:15:22 | case | | Test.java:12:20:12:20 | s | Test.java:13:10:13:31 | ; | | 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 | ; | 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 T t ... | Test.java:15:21:15:21 | i | -| Test.java:15:8:15:22 | case T t ... | Test.java:18:8:18:15 | default | +| Test.java:15:8:15:22 | case | Test.java:15:21:15:21 | i | +| Test.java:15:8:15:22 | case | Test.java:18:8:18:15 | default | | Test.java:15:21:15:21 | i | Test.java:16:10:16:47 | ; | | 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 | @@ -46,13 +46,13 @@ | 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 T t ... | -| Test.java:23:8:23:23 | case T t ... | Test.java:23:20:23:20 | s | -| Test.java:23:8:23:23 | case T t ... | Test.java:24:8:24:24 | case T t ... | +| Test.java:22:33:22:37 | thing | Test.java:23:8:23:23 | case | +| Test.java:23:8:23:23 | case | Test.java:23:20:23:20 | s | +| Test.java:23:8:23:23 | case | Test.java:24:8:24:24 | case | | 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 T t ... | Test.java:24:21:24:21 | i | -| Test.java:24:8:24:24 | case T t ... | Test.java:25:8:25:17 | default | +| Test.java:24:8:24:24 | case | Test.java:24:21:24:21 | i | +| Test.java:24:8:24:24 | case | 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 | @@ -62,14 +62,14 @@ | 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 T t ... | -| Test.java:29:8:29:21 | case T t ... | Test.java:29:20:29:20 | s | -| Test.java:29:8:29:21 | case T t ... | Test.java:31:8:31:22 | case T t ... | +| Test.java:28:34:28:38 | thing | Test.java:29:8:29:21 | case | +| Test.java:29:8:29:21 | case | Test.java:29:20:29:20 | s | +| Test.java:29:8:29:21 | case | Test.java:31:8:31:22 | case | | 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 T t ... | Test.java:31:21:31:21 | i | -| Test.java:31:8:31:22 | case T t ... | Test.java:33:8:33:15 | default | +| Test.java:31:8:31:22 | case | Test.java:31:21:31:21 | i | +| Test.java:31:8:31:22 | case | 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 | @@ -79,22 +79,22 @@ | 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 T t ... | -| Test.java:38:8:38:42 | case T t ... | Test.java:38:20:38:20 | s | -| Test.java:38:8:38:42 | case T t ... | Test.java:41:8:41:42 | case T t ... | +| Test.java:37:13:37:17 | thing | Test.java:38:8:38:42 | case | +| Test.java:38:8:38:42 | case | Test.java:38:20:38:20 | s | +| Test.java:38:8:38:42 | case | Test.java:41:8:41:42 | case | | 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 | ; | -| Test.java:38:27:38:41 | ... == ... | Test.java:41:8:41:42 | case T t ... | +| Test.java:38:27:38:41 | ... == ... | Test.java:41:8:41:42 | case | | 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 | ; | 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 T t ... | Test.java:41:20:41:20 | s | -| Test.java:41:8:41:42 | case T t ... | Test.java:44:8:44:15 | default | +| Test.java:41:8:41:42 | case | Test.java:41:20:41:20 | s | +| Test.java:41:8:41:42 | case | 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 | @@ -113,21 +113,21 @@ | 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 T t ... | -| Test.java:50:8:50:44 | case T t ... | Test.java:50:20:50:20 | s | -| Test.java:50:8:50:44 | case T t ... | Test.java:51:8:51:44 | case T t ... | +| Test.java:49:13:49:17 | thing | Test.java:50:8:50:44 | case | +| Test.java:50:8:50:44 | case | Test.java:50:20:50:20 | s | +| Test.java:50:8:50:44 | case | Test.java:51:8:51:44 | case | | 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 T t ... | +| Test.java:50:27:50:41 | ... == ... | Test.java:51:8:51:44 | case | | 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 | ; | 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 T t ... | Test.java:51:20:51:20 | s | -| Test.java:51:8:51:44 | case T t ... | Test.java:52:8:52:17 | default | +| Test.java:51:8:51:44 | case | Test.java:51:20:51:20 | s | +| Test.java:51:8:51:44 | case | 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 | @@ -143,7 +143,7 @@ | 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 T t ... | +| Test.java:55:13:55:25 | (...)... | Test.java:61:8:61:42 | case | | 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 | ; | @@ -157,10 +157,10 @@ | Test.java:59:10:59:54 | ; | 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 T t ... | Test.java:61:20:61:20 | s | -| Test.java:61:8:61:42 | case T t ... | Test.java:63:8:63:21 | case ... | -| Test.java:61:8:61:42 | case T t ... | Test.java:66:8:66:22 | case ... | -| Test.java:61:8:61:42 | case T t ... | Test.java:69:8:69:26 | case null, default | +| Test.java:61:8:61:42 | case | Test.java:61:20:61:20 | s | +| Test.java:61:8:61:42 | case | Test.java:63:8:63:21 | case ... | +| Test.java:61:8:61:42 | case | Test.java:66:8:66:22 | case ... | +| Test.java:61:8:61:42 | case | 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 | @@ -191,10 +191,10 @@ | Test.java:70:10:70:60 | ; | 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 T t ... | +| Test.java:73:13:73:17 | thing | Test.java:74:8:74:21 | case | | Test.java:73:13:73:17 | thing | Test.java:77:8:77:17 | case ... | -| Test.java:74:8:74:21 | case T t ... | Test.java:74:20:74:20 | s | -| Test.java:74:8:74:21 | case T t ... | Test.java:80:8:80:22 | case T t ... | +| Test.java:74:8:74:21 | case | Test.java:74:20:74:20 | s | +| Test.java:74:8:74:21 | case | Test.java:80:8:80:22 | case | | Test.java:74:20:74:20 | s | Test.java:75:10:75:31 | ; | | 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 | @@ -207,8 +207,8 @@ | Test.java:78:10:78:41 | ; | 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 T t ... | Test.java:80:21:80:21 | i | -| Test.java:80:8:80:22 | case T t ... | Test.java:83:8:83:15 | default | +| Test.java:80:8:80:22 | case | Test.java:80:21:80:21 | i | +| Test.java:80:8:80:22 | case | Test.java:83:8:83:15 | default | | Test.java:80:21:80:21 | i | Test.java:81:10:81:47 | ; | | 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 | @@ -220,9 +220,9 @@ | 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 T t ... | -| Test.java:88:8:88:43 | case T t ... | Test.java:88:21:88:21 | x | -| Test.java:88:8:88:43 | case T t ... | Test.java:90:8:90:15 | default | +| Test.java:87:13:87:17 | thing | Test.java:88:8:88:43 | case | +| Test.java:88:8:88:43 | case | Test.java:88:21:88:21 | x | +| Test.java:88:8:88:43 | case | 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 | @@ -232,9 +232,9 @@ | 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 T t ... | -| Test.java:95:8:95:38 | case T t ... | Test.java:95:21:95:21 | x | -| Test.java:95:8:95:38 | case T t ... | Test.java:97:8:97:15 | default | +| Test.java:94:13:94:17 | thing | Test.java:95:8:95:38 | case | +| Test.java:95:8:95:38 | case | Test.java:95:21:95:21 | x | +| Test.java:95:8:95:38 | case | 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 | diff --git a/java/ql/test/library-tests/printAst/PrintAst.expected b/java/ql/test/library-tests/printAst/PrintAst.expected index 2288b40ba76..ebcb9f2cbee 100644 --- a/java/ql/test/library-tests/printAst/PrintAst.expected +++ b/java/ql/test/library-tests/printAst/PrintAst.expected @@ -121,7 +121,7 @@ A.java: # 51| 0: [VarAccess] s # 53| 2: [SwitchStmt] switch (...) # 53| -1: [VarAccess] thing -# 54| 0: [PatternCase] case T t ... +# 54| 0: [PatternCase] case # 54| -1: [ExprStmt] ; # 54| 0: [MethodCall] println(...) # 54| -1: [VarAccess] System.out @@ -130,7 +130,7 @@ A.java: #-----| 0: (Single Local Variable Declaration) # 54| 0: [TypeAccess] String # 54| 1: [LocalVariableDeclExpr] s -# 55| 1: [PatternCase] case T t ... +# 55| 1: [PatternCase] case # 55| -1: [ExprStmt] ; # 55| 0: [MethodCall] println(...) # 55| -1: [VarAccess] System.out @@ -145,7 +145,7 @@ A.java: # 56| -1: [BlockStmt] { ... } # 58| 3: [SwitchStmt] switch (...) # 58| -1: [VarAccess] thing -# 59| 0: [PatternCase] case T t ... +# 59| 0: [PatternCase] case #-----| 0: (Single Local Variable Declaration) # 59| 0: [TypeAccess] String # 59| 1: [LocalVariableDeclExpr] s @@ -155,7 +155,7 @@ A.java: # 60| -1: [TypeAccess] System # 60| 0: [VarAccess] s # 61| 2: [BreakStmt] break -# 62| 3: [PatternCase] case T t ... +# 62| 3: [PatternCase] case #-----| 0: (Single Local Variable Declaration) # 62| 0: [TypeAccess] Integer # 62| 1: [LocalVariableDeclExpr] i @@ -173,12 +173,12 @@ A.java: # 68| 1: [LocalVariableDeclExpr] thingAsString # 68| 0: [SwitchExpr] switch (...) # 68| -1: [VarAccess] thing -# 69| 0: [PatternCase] case T t ... +# 69| 0: [PatternCase] case # 69| -1: [VarAccess] s #-----| 0: (Single Local Variable Declaration) # 69| 0: [TypeAccess] String # 69| 1: [LocalVariableDeclExpr] s -# 70| 1: [PatternCase] case T t ... +# 70| 1: [PatternCase] case # 70| -1: [AddExpr] ... + ... # 70| 0: [StringLiteral] "An integer: " # 70| 1: [VarAccess] i @@ -191,13 +191,13 @@ A.java: # 73| 1: [LocalVariableDeclExpr] thingAsString2 # 73| 0: [SwitchExpr] switch (...) # 73| -1: [VarAccess] thing -# 74| 0: [PatternCase] case T t ... +# 74| 0: [PatternCase] case #-----| 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 T t ... +# 76| 2: [PatternCase] case #-----| 0: (Single Local Variable Declaration) # 76| 0: [TypeAccess] Integer # 76| 1: [LocalVariableDeclExpr] i @@ -226,7 +226,7 @@ A.java: # 86| 0: [ConstCase] case ... # 86| -1: [StringLiteral] "It's constant" # 86| 0: [StringLiteral] "constant" -# 87| 1: [PatternCase] case T t ... +# 87| 1: [PatternCase] case # 87| -3: [EQExpr] ... == ... # 87| 0: [MethodCall] length(...) # 87| -1: [VarAccess] s @@ -235,7 +235,7 @@ A.java: #-----| 0: (Single Local Variable Declaration) # 87| 0: [TypeAccess] String # 87| 1: [LocalVariableDeclExpr] s -# 88| 2: [PatternCase] case T t ... +# 88| 2: [PatternCase] case # 88| -3: [EQExpr] ... == ... # 88| 0: [MethodCall] length(...) # 88| -1: [VarAccess] s @@ -250,7 +250,7 @@ A.java: # 91| 1: [LocalVariableDeclExpr] nullDefaultTest # 91| 0: [SwitchExpr] switch (...) # 91| -1: [VarAccess] thing -# 92| 0: [PatternCase] case T t ... +# 92| 0: [PatternCase] case # 92| -1: [StringLiteral] "It's a string" #-----| 0: (Single Local Variable Declaration) # 92| 0: [TypeAccess] String @@ -287,7 +287,7 @@ A.java: # 104| 1: [LocalVariableDeclExpr] recordPatterntest # 104| 0: [SwitchExpr] switch (...) # 104| -1: [VarAccess] thing -# 105| 0: [PatternCase] case T t ... +# 105| 0: [PatternCase] case # 105| -1: [VarAccess] field # 105| 0: [RecordPatternExpr] Middle(...) # 105| 0: [RecordPatternExpr] Inner(...) From 419d530a06988c2e6170db9fe443e5a54d3586c0 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 21 Nov 2023 19:23:49 +0000 Subject: [PATCH 091/115] Add test ensuring read steps via record patterns lead to type filtering --- .../flow-through-binding/Test.java | 33 +++++++++++++++++++ .../flow-through-binding/test.expected | 9 +++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/java/ql/test/library-tests/flow-through-binding/Test.java b/java/ql/test/library-tests/flow-through-binding/Test.java index 04cde964538..5a026a8ce22 100644 --- a/java/ql/test/library-tests/flow-through-binding/Test.java +++ b/java/ql/test/library-tests/flow-through-binding/Test.java @@ -25,6 +25,31 @@ public class Test { 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 source() { return null; } public static void sink(Object o) { } @@ -37,6 +62,14 @@ public class Test { 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) { } + } diff --git a/java/ql/test/library-tests/flow-through-binding/test.expected b/java/ql/test/library-tests/flow-through-binding/test.expected index 8fa7e64d645..b1931300279 100644 --- a/java/ql/test/library-tests/flow-through-binding/test.expected +++ b/java/ql/test/library-tests/flow-through-binding/test.expected @@ -1,3 +1,6 @@ -| Test.java:35:23:35:30 | source(...) | Test.java:36:10:36:61 | testFlowThroughSwitchStmt(...) | -| Test.java:35:23:35:30 | source(...) | Test.java:37:10:37:61 | testFlowThroughSwitchExpr(...) | -| Test.java:35:23:35:30 | source(...) | Test.java:38:10:38:68 | testFlowThroughBindingInstanceOf(...) | +| 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(...) | From 4bff7953fc1f86e44cbb777fc35d7c3d33f53930 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 22 Nov 2023 14:36:21 +0000 Subject: [PATCH 092/115] Fix record pattern and pretty-printing --- java/ql/lib/semmle/code/java/Expr.qll | 2 ++ .../lib/semmle/code/java/PrettyPrintAst.qll | 24 ++++++++++++++++--- .../test/library-tests/prettyprint/Test.java | 7 ++++++ .../library-tests/prettyprint/pp.expected | 22 +++++++++++------ 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index 53026610257..c10756bef18 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1672,6 +1672,8 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr { 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. */ diff --git a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll index 3e5b47a5369..0c6bd7fcff4 100644 --- a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll +++ b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll @@ -1078,12 +1078,30 @@ private class PpRecordPattern extends PpAst, RecordPatternExpr { or i = 1 and result = "(" or - i = 1 + ((any(int x | x >= 1 and exists(this.getSubPattern(x)))) * 2) and result = ", " + i = 1 + ((any(int x | x >= 1 and exists(this.getSubPattern(x)))) * 4) and result = ", " or - i = 1 + (count(this.getSubPattern(_)) * 2) and result = ")" + 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 | result = this.getSubPattern(x) | i = 2 + (x * 2)) + 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 + ) + ) } } diff --git a/java/ql/test/library-tests/prettyprint/Test.java b/java/ql/test/library-tests/prettyprint/Test.java index 1ad32d45f3e..c643955751e 100644 --- a/java/ql/test/library-tests/prettyprint/Test.java +++ b/java/ql/test/library-tests/prettyprint/Test.java @@ -38,6 +38,13 @@ public class Test { 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)) { } + } } diff --git a/java/ql/test/library-tests/prettyprint/pp.expected b/java/ql/test/library-tests/prettyprint/pp.expected index b8aadaf38b4..37d92354cff 100644 --- a/java/ql/test/library-tests/prettyprint/pp.expected +++ b/java/ql/test/library-tests/prettyprint/pp.expected @@ -46,7 +46,7 @@ | 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(x), y): | +| 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; | @@ -54,7 +54,7 @@ | 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(x), y) -> { | +| 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 default -> { | | Test.java:1:14:1:17 | Test | 59 | } | @@ -62,19 +62,27 @@ | 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(x), y): | +| 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 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(x), y) -> x; | +| 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(x), y)) { | +| 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 | } | -| Test.java:1:14:1:17 | Test | 79 | } | +| 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 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 | } | From d99a005b424b6e9fcdf49da1da4a6287914f01a3 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 22 Nov 2023 16:16:10 +0000 Subject: [PATCH 093/115] Fix pretty-printing `case null, default` --- java/ql/lib/semmle/code/java/PrettyPrintAst.qll | 13 +++---------- java/ql/test/library-tests/prettyprint/pp.expected | 6 +++--- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll index 0c6bd7fcff4..6a5e5aa698b 100644 --- a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll +++ b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll @@ -755,9 +755,9 @@ private class PpSwitchCase extends PpAst, SwitchCase { override string getPart(int i) { i = 0 and result = "default" and isNonNullDefaultCase(this) or - i = 0 and result = "case " and not isNonNullDefaultCase(this) + i = 0 and result = "case null, default" and this instanceof NullDefaultCase or - i = this.lastConstCaseValueIndex() and result = "default" and this instanceof NullDefaultCase + 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 @@ -768,15 +768,8 @@ private class PpSwitchCase extends PpAst, SwitchCase { i = 3 + this.lastConstCaseValueIndex() and result = ";" and exists(this.getRuleExpression()) } - private int getCaseDefaultOffset() { - if this instanceof NullDefaultCase then result = 1 else result = 0 - } - private int lastConstCaseValueIndex() { - result = - 1 + - 2 * - (max(int j | j = 0 or exists(this.(ConstCase).getValue(j))) + this.getCaseDefaultOffset()) + result = 1 + 2 * (max(int j | j = 0 or exists(this.(ConstCase).getValue(j)))) } override PpAst getChild(int i) { diff --git a/java/ql/test/library-tests/prettyprint/pp.expected b/java/ql/test/library-tests/prettyprint/pp.expected index 37d92354cff..eb4fbaf0633 100644 --- a/java/ql/test/library-tests/prettyprint/pp.expected +++ b/java/ql/test/library-tests/prettyprint/pp.expected @@ -56,7 +56,7 @@ | 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 default -> { | +| 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) { | @@ -64,7 +64,7 @@ | 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 default: | +| 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) { | @@ -79,7 +79,7 @@ | 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 default -> { | +| 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)) { | From 1047a89613227450f840e720d85b2f754a16107c Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 22 Nov 2023 19:37:34 +0000 Subject: [PATCH 094/115] Improve complexity class of getASuccessorSwitchCase --- .../lib/semmle/code/java/ControlFlowGraph.qll | 22 ++++++++++++++----- java/ql/lib/semmle/code/java/Statement.qll | 8 +++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index aeab011085e..09020f3935c 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -460,6 +460,15 @@ private module ControlFlowGraphImpl { ) } + private int lastCaseIndex(StmtParent switch) { + result = max(int i | any(SwitchCase c).isNthCaseOf(switch, i)) + } + + // 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(StmtParent switch, SwitchCase c, int i) { c.isNthCaseOf(switch, i) } + /** * Gets a `SwitchCase` that may be `pred`'s direct successor. * @@ -469,14 +478,15 @@ private module ControlFlowGraphImpl { * that any default case comes after the last pattern case. */ private SwitchCase getASuccessorSwitchCase(PatternCase pred) { - result.getParent() = pred.getParent() and - result.getIndex() > pred.getIndex() and // Note we do include `case null, default` (as well as plain old `default`) here. not result.(ConstCase).getValue(_) instanceof NullLiteral and - ( - result.getIndex() <= getNextPatternCase(pred).getIndex() - or - not exists(getNextPatternCase(pred)) + exists(int maxCaseIndex, StmtParent switch | + switch = pred.getParent() and + if exists(getNextPatternCase(pred)) + then maxCaseIndex = getNextPatternCase(pred).getCaseIndex() + else maxCaseIndex = lastCaseIndex(switch) + | + isNthCaseOf(switch, result, [pred.getCaseIndex() + 1 .. maxCaseIndex]) ) } diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index bd30b235b46..0e0178f4ca1 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -464,6 +464,14 @@ class SwitchCase extends Stmt, @case { 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(StmtParent parent, int n) { + this.getCaseIndex() = n and this.getParent() = parent + } + /** * Holds if this `case` is a switch labeled rule of the form `... -> ...`. */ From aa5f7352e2ff3350964501876677d1c101361238 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 22 Nov 2023 20:21:39 +0000 Subject: [PATCH 095/115] Remove fall-through CFG edge for exhaustive switch statements --- .../lib/semmle/code/java/ControlFlowGraph.qll | 5 ++ .../pattern-switch/cfg/Exhaustive.java | 31 ++++++++++++ .../pattern-switch/cfg/test.expected | 48 +++++++++++++++++++ .../library-tests/pattern-switch/cfg/test.ql | 2 +- 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 java/ql/test/library-tests/pattern-switch/cfg/Exhaustive.java diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index 09020f3935c..ab0abac7b65 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -936,7 +936,12 @@ private module ControlFlowGraphImpl { 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() ) diff --git a/java/ql/test/library-tests/pattern-switch/cfg/Exhaustive.java b/java/ql/test/library-tests/pattern-switch/cfg/Exhaustive.java new file mode 100644 index 00000000000..c438b8253a2 --- /dev/null +++ b/java/ql/test/library-tests/pattern-switch/cfg/Exhaustive.java @@ -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 -> { } + } + + } + +} diff --git a/java/ql/test/library-tests/pattern-switch/cfg/test.expected b/java/ql/test/library-tests/pattern-switch/cfg/test.expected index b4b88b88c63..31605711281 100644 --- a/java/ql/test/library-tests/pattern-switch/cfg/test.expected +++ b/java/ql/test/library-tests/pattern-switch/cfg/test.expected @@ -1,3 +1,51 @@ +| 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 | ; | +| Exhaustive.java:3:12:3:12 | ...=... | Exhaustive.java:3:15:3:15 | ; | +| Exhaustive.java:3:12:3:12 | ; | 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 | ; | +| Exhaustive.java:3:15:3:15 | ; | 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 | | +| Exhaustive.java:3:18:3:18 | ; | 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 | +| Exhaustive.java:12:7:12:22 | case | Exhaustive.java:12:19:12:19 | s | +| Exhaustive.java:12:7:12:22 | case | Exhaustive.java:13:7:13:23 | case | +| 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 | 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 | +| Exhaustive.java:25:7:25:17 | case | Exhaustive.java:25:14:25:14 | x | +| Exhaustive.java:25:7:25:17 | case | Exhaustive.java:26:7:26:17 | case | +| 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 | 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 (...) | diff --git a/java/ql/test/library-tests/pattern-switch/cfg/test.ql b/java/ql/test/library-tests/pattern-switch/cfg/test.ql index 0b07e8c4708..4511277ee7d 100644 --- a/java/ql/test/library-tests/pattern-switch/cfg/test.ql +++ b/java/ql/test/library-tests/pattern-switch/cfg/test.ql @@ -1,5 +1,5 @@ import java from ControlFlowNode cn -where cn.getFile().getBaseName() = "Test.java" +where cn.getFile().getBaseName() = ["Test.java", "Exhaustive.java"] select cn, cn.getASuccessor() From 1cb5efa1ecebb1f36bcd5867494c066848e1a46f Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 24 Nov 2023 15:06:23 +0000 Subject: [PATCH 096/115] Simplify last nodes of rule cases --- .../lib/semmle/code/java/ControlFlowGraph.qll | 33 ++++++------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index ab0abac7b65..bb8320672ba 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -957,33 +957,20 @@ private module ControlFlowGraphImpl { not completion instanceof NormalOrBooleanCompletion ) or - // the last node in a case rule in statement context 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, SwitchStmt parent, SwitchCase case | + exists(Completion caseCompletion, SwitchCase case | case = n and - case = parent.getACase() and - last(case.getRuleStatementOrExpressionStatement(), last, caseCompletion) and - if caseCompletion instanceof NormalOrBooleanCompletion - then completion = anonymousBreakCompletion() - else completion = caseCompletion - ) - or - // ...and when a switch occurs in expression context, we wrap the RHS in a yield statement. - // Note the wrapping can only occur in the expression case, because a statement would need - // to have explicit `yield` statements. - exists(SwitchExpr parent, SwitchCase case | - case = n and - case = parent.getACase() and ( - exists(Completion caseCompletion | - last(case.getRuleExpression(), last, caseCompletion) and - if caseCompletion instanceof NormalOrBooleanCompletion - then completion = YieldCompletion(caseCompletion) - else completion = caseCompletion - ) + last(case.getRuleStatement(), last, caseCompletion) or - last(case.getRuleStatement(), last, completion) + last(case.getRuleExpression(), last, caseCompletion) ) + | + if caseCompletion instanceof NormalOrBooleanCompletion + then + case.getParent() instanceof SwitchStmt and completion = anonymousBreakCompletion() + or + case.getParent() instanceof SwitchExpr and completion = YieldCompletion(caseCompletion) + else completion = caseCompletion ) or // The normal last node in a non-rule pattern case is the last of its variable declaration(s), From beb827b1d02b11ef826ca9d333f8678f3bba8452 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 24 Nov 2023 15:08:15 +0000 Subject: [PATCH 097/115] Remove unused predicate --- java/ql/lib/semmle/code/java/Statement.qll | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index 0e0178f4ca1..a43bbfa5916 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -502,17 +502,7 @@ class SwitchCase extends Stmt, @case { * This predicate is mutually exclusive with `getRuleExpression`. */ Stmt getRuleStatement() { - result = this.getRuleStatementOrExpressionStatement() and not result instanceof ExprStmt - } - - /** - * Gets the statement, including an expression statement, on the RHS of the arrow, if any. - * - * This means this could be an explicit `case e1 -> { s1; ... }` or an implicit - * `case e1 -> stmt;` rule. - */ - Stmt getRuleStatementOrExpressionStatement() { - result.getParent() = this and result.getIndex() = -1 + result.getParent() = this and result.getIndex() = -1 and not result instanceof ExprStmt } } From d1e16ada4cdcd03d475771c20e8ef63b71cdd7c4 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 24 Nov 2023 16:44:17 +0000 Subject: [PATCH 098/115] Tidy up pattern case CFG logic --- .../lib/semmle/code/java/ControlFlowGraph.qll | 194 +++++++++--------- 1 file changed, 100 insertions(+), 94 deletions(-) diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index bb8320672ba..8c8fafbc9c9 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -470,17 +470,17 @@ private module ControlFlowGraphImpl { private predicate isNthCaseOf(StmtParent switch, SwitchCase c, int i) { c.isNthCaseOf(switch, i) } /** - * Gets a `SwitchCase` that may be `pred`'s direct successor. + * 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) { + private SwitchCase getASuccessorSwitchCase(PatternCase pred, StmtParent 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, StmtParent switch | + exists(int maxCaseIndex | switch = pred.getParent() and if exists(getNextPatternCase(pred)) then maxCaseIndex = getNextPatternCase(pred).getCaseIndex() @@ -511,6 +511,24 @@ private module ControlFlowGraphImpl { ) } + private Stmt getSwitchStatement(StmtParent 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. * @@ -808,7 +826,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. @@ -957,6 +978,9 @@ private module ControlFlowGraphImpl { not completion instanceof NormalOrBooleanCompletion ) or + // 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 ( @@ -973,18 +997,24 @@ private module ControlFlowGraphImpl { else completion = caseCompletion ) or - // The normal last node in a non-rule pattern case is the last of its variable declaration(s), - // or the successful matching of its guard if it has one. - // Note that either rule or non-rule pattern cases can end with pattern match failure, whereupon - // they branch to the next candidate pattern. This is accounted for in the `succ` relation. + // 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 any abrupt completion of its guard + // * 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 ( - if exists(pc.getGuard()) - then last(pc.getGuard(), last, BooleanCompletion(true, _)) - else last(pc.getPattern(), last, NormalCompletion()) - ) and + completion = BooleanCompletion(false, _) or + not completion instanceof NormalOrBooleanCompletion + ) + or not pc.isRule() and - completion = NormalCompletion() + lastPatternCaseMatchingOp(pc, last, completion) ) or // the last statement of a synchronized statement is the last statement of its body @@ -1296,90 +1326,66 @@ 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 any case up to and including the first pattern case, if any. - last(switch.getExpr(), n, completion) and result = first(getAFirstSwitchCase(switch)) - 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), and the case of pattern matching failure branching to the - // next case is specially handled in the `PatternCase` logic below. - exists(int i | - last(switch.getStmt(i), n, completion) and result = first(switch.getStmt(i + 1)) - ) - ) - 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()) - or - // ...and then to any case up to and including the first pattern case, if any. - last(switch.getExpr(), n, completion) and result = first(getAFirstSwitchCase(switch)) - 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), and the case of pattern matching failure branching to the - // next case is specially handled in the `PatternCase` logic below. - exists(int i | - last(switch.getStmt(i), n, completion) and result = first(switch.getStmt(i + 1)) - ) - ) - or - // Edge from rule SwitchCases to their body, after any variable assignment and/or guard test if applicable. - // No edges in a non-rule SwitchCase - the constant expression in a ConstCase isn't included in the CFG. - exists(SwitchCase case, ControlFlowNode preBodyNode | - if case instanceof PatternCase - then ( - if exists(case.(PatternCase).getGuard()) - then ( - last(case.(PatternCase).getGuard(), preBodyNode, completion) and - completion = basicBooleanCompletion(true) - ) else ( - last(case.(PatternCase).getPattern(), preBodyNode, completion) and - completion = NormalCompletion() - ) - ) else ( - preBodyNode = case and completion = NormalCompletion() - ) - | - n = preBodyNode and result = first(case.getRuleExpression()) - or - n = preBodyNode and result = first(case.getRuleStatement()) - ) - or - // A pattern case conducts a type test, then branches to the next case or the pattern assignment(s). - exists(PatternCase case | - n = case and - ( - completion = basicBooleanCompletion(false) and - result = getASuccessorSwitchCase(case) + // Switch statements and expressions + exists(StmtParent 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 - completion = basicBooleanCompletion(true) and - result = first(case.getPattern()) - ) - ) - or - // A pattern case with a guard evaluates that guard after declaring its pattern variable(s), - // and thereafter if the guard doesn't match will branch to the next case. - // The case of a matching guard is accounted for in the case-with-rule logic above, or for - // non-rule case statements in `last`. - exists(PatternCase case, Expr guard | - guard = case.getGuard() and - ( - last(case.getPattern(), n, NormalCompletion()) and - result = first(guard) and + // ...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(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 + // 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 + last(pc.getPattern(), n, completion) and + completion = NormalCompletion() and + result = first(pc.getGuard()) + or + lastPatternCaseMatchingOp(pc, n, completion) and + ( + result = first(pc.getRuleExpression()) or - last(guard, n, completion) and - completion = basicBooleanCompletion(false) and - result = getASuccessorSwitchCase(case) + result = first(pc.getRuleStatement()) + ) + ) + or + // 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 From 9b5b49646240433c5d1e1643cf9bc1ce125f8c56 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 24 Nov 2023 17:26:31 +0000 Subject: [PATCH 099/115] Avoid quadratic switch case intermediate --- .../lib/semmle/code/java/ControlFlowGraph.qll | 29 +------------ .../semmle/code/java/controlflow/Guards.qll | 41 +++++++++++++++---- .../java/controlflow/internal/SwitchCases.qll | 31 ++++++++++++++ 3 files changed, 64 insertions(+), 37 deletions(-) create mode 100644 java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index 8c8fafbc9c9..3a3849a256e 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -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 { @@ -436,34 +437,6 @@ private module ControlFlowGraphImpl { ) } - /** - * Gets the `i`th `SwitchCase` defined on `switch`, if one exists. - */ - private SwitchCase getCase(StmtParent switch, int i) { - result = switch.(SwitchExpr).getCase(i) or result = switch.(SwitchStmt).getCase(i) - } - - /** - * Gets the `i`th `PatternCase` defined on `switch`, if one exists. - */ - private PatternCase getPatternCase(StmtParent switch, int i) { - result = - rank[i + 1](PatternCase pc, int caseIdx | pc = getCase(switch, caseIdx) | pc order by caseIdx) - } - - /** - * Gets the PatternCase after pc, if one exists. - */ - private PatternCase getNextPatternCase(PatternCase pc) { - exists(int idx, StmtParent switch | - pc = getPatternCase(switch, idx) and result = getPatternCase(switch, idx + 1) - ) - } - - private int lastCaseIndex(StmtParent switch) { - result = max(int i | any(SwitchCase c).isNthCaseOf(switch, i)) - } - // Join order engineering -- first determine the switch block and the case indices required, then retrieve them. bindingset[switch, i] pragma[inline_late] diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index e670c692749..cd301372c85 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -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. @@ -72,6 +73,35 @@ 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(StmtParent 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, StmtParent 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, _) +} + /** * 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 @@ -113,17 +143,10 @@ class Guard extends ExprParent { result = this.(Expr).getBasicBlock() or // Return the closest pattern case statement before this one, including this one. - result = - max(int i, PatternCase c | - c = this.(SwitchCase).getSiblingCase(i) and i <= this.(SwitchCase).getCaseIndex() - | - c order by i - ).getBasicBlock() + result = getClosestPrecedingPatternCase(this).getBasicBlock() or // Not a pattern case and no preceding pattern case -- return the top of the switch block. - not exists(PatternCase c, int i | - c = this.(SwitchCase).getSiblingCase(i) and i <= this.(SwitchCase).getCaseIndex() - ) and + not exists(getClosestPrecedingPatternCase(this)) and result = this.(SwitchCase).getSelectorExpr().getBasicBlock() } diff --git a/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll b/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll new file mode 100644 index 00000000000..e0aad28568c --- /dev/null +++ b/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll @@ -0,0 +1,31 @@ +/** Provides utility predicates relating to switch cases. */ + +import java + +/** + * Gets the `i`th `SwitchCase` defined on `switch`, if one exists. + */ +SwitchCase getCase(StmtParent switch, int i) { + result = switch.(SwitchExpr).getCase(i) or result = switch.(SwitchStmt).getCase(i) +} + +/** + * Gets the `i`th `PatternCase` defined on `switch`, if one exists. + */ +PatternCase getPatternCase(StmtParent switch, int i) { + result = + rank[i + 1](PatternCase pc, int caseIdx | pc = getCase(switch, caseIdx) | pc order by caseIdx) +} + +/** + * Gets the PatternCase after pc, if one exists. + */ +PatternCase getNextPatternCase(PatternCase pc) { + exists(int idx, StmtParent switch | + pc = getPatternCase(switch, idx) and result = getPatternCase(switch, idx + 1) + ) +} + +int lastCaseIndex(StmtParent switch) { + result = max(int i | any(SwitchCase c).isNthCaseOf(switch, i)) +} From b33dc38a65fd935adc9481946297cc3f43db188b Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 24 Nov 2023 18:29:54 +0000 Subject: [PATCH 100/115] Fix hasBranchEdge for switch exprs with an internal CFG and incoming edges from a passing case guard --- .../semmle/code/java/controlflow/Guards.qll | 15 +++++-- .../library-tests/guards12/PrintAst.expected | 24 ++++++++++++ java/ql/test/library-tests/guards12/Test.java | 8 +++- .../library-tests/guards12/guard.expected | 39 +++++++++++++++++++ java/ql/test/library-tests/guards12/guard.ql | 9 ++++- 5 files changed, 88 insertions(+), 7 deletions(-) diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index cd301372c85..8237634595c 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -175,16 +175,23 @@ class Guard extends ExprParent { bb2 = cb.getTestSuccessor(branch) ) or - exists(SwitchCase sc | + 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 - bb1 = sc.getControlFlowNode().getAPredecessor().getBasicBlock() and + pred = sc.getControlFlowNode().getAPredecessor() and // This is either the top of the switch block, or a preceding pattern case - // if one exists. - this.getBasicBlock() = bb1 + // if one exists (in which case the edge might come from the case itself or its guard) + ( + pred.(Expr).getParent*() = sc.getSelectorExpr() + or + pred.(Expr).getParent*() = getClosestPrecedingPatternCase(sc).getGuard() + or + pred = getClosestPrecedingPatternCase(sc) + ) and + bb1 = pred.getBasicBlock() ) or preconditionBranchEdge(this, bb1, bb2, branch) diff --git a/java/ql/test/library-tests/guards12/PrintAst.expected b/java/ql/test/library-tests/guards12/PrintAst.expected index 9296be2f783..86a8ed3b778 100644 --- a/java/ql/test/library-tests/guards12/PrintAst.expected +++ b/java/ql/test/library-tests/guards12/PrintAst.expected @@ -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 @@ -58,3 +60,25 @@ Test.java: # 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 +# 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] { ... } diff --git a/java/ql/test/library-tests/guards12/Test.java b/java/ql/test/library-tests/guards12/Test.java index 1071030a1fa..ee80e17df8d 100644 --- a/java/ql/test/library-tests/guards12/Test.java +++ b/java/ql/test/library-tests/guards12/Test.java @@ -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; @@ -18,5 +18,11 @@ class Test { case "e" -> { } default -> { } } + switch (unknown ? s : s.toLowerCase()) { + case "f" -> { } + case String s2 when len == 4 -> { } + case "g" -> { } + default -> { } + } } } diff --git a/java/ql/test/library-tests/guards12/guard.expected b/java/ql/test/library-tests/guards12/guard.expected index 7144889ed34..29ca4cafb41 100644 --- a/java/ql/test/library-tests/guards12/guard.expected +++ b/java/ql/test/library-tests/guards12/guard.expected @@ -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 | Test.java:15:5:15:25 | var ...; | Test.java:17:19:17:20 | s2 | true | +| Test.java:17:7:17:37 | case | Test.java:15:5:15:25 | var ...; | Test.java:18:7:18:17 | case ... | false | +| Test.java:17:7:17:37 | case | 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 | Test.java:23:7:23:37 | case | Test.java:23:19:23:20 | s2 | true | +| Test.java:23:7:23:37 | case | Test.java:23:7:23:37 | case | Test.java:24:7:24:17 | case ... | false | +| Test.java:23:7:23:37 | case | Test.java:23:7:23:37 | case | 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 | 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 | 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 | @@ -9,3 +43,8 @@ | 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 ... | diff --git a/java/ql/test/library-tests/guards12/guard.ql b/java/ql/test/library-tests/guards12/guard.ql index 206e85f8bb8..cff2845ad9f 100644 --- a/java/ql/test/library-tests/guards12/guard.ql +++ b/java/ql/test/library-tests/guards12/guard.ql @@ -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 From 77b1721542c5aaef06a9a0b0fec570a7cad2b224 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 24 Nov 2023 18:52:59 +0000 Subject: [PATCH 101/115] Move TypeTestGuard's logic into Guard.appliesTypeTest --- .../semmle/code/java/controlflow/Guards.qll | 87 +++++++------------ .../semmle/code/java/dataflow/TypeFlow.qll | 8 +- .../code/java/dispatch/VirtualDispatch.qll | 2 +- 3 files changed, 38 insertions(+), 59 deletions(-) diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index 8237634595c..71f2ccb0567 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -164,6 +164,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`. @@ -223,60 +256,6 @@ class Guard extends ExprParent { } } -/** - * A `Guard` that tests an expression's type -- that is, an `instanceof T` or a - * `case T varname` pattern case. - */ -class TypeTestGuard extends Guard { - Expr testedExpr; - Type testedType; - - TypeTestGuard() { - 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() - ) - } - - /** - * Gets the record pattern this type test binds to, if any. - */ - PatternExpr getPattern() { - result = this.(InstanceOfExpr).getPattern() - or - result = this.(PatternCase).getPattern() - } - - /** - * Holds if this guard tests whether `e` has type `t` on `testedBranch`. - * - * Note that record patterns that make at least one tighter restriction than the record's definition - * (e.g. matching `record R(Object)` with `case R(String)`) means this only guarantees the tested type - * on the true branch (i.e., entering such a case guarantees `testedExpr` is a `testedType`, but failing - * the type test could mean a nested record or binding pattern didn't match but `testedExpr` is still - * of type `testedType`.) - */ - predicate appliesTypeTest(Expr e, Type t, boolean testedBranch) { - e = testedExpr and - t = testedType and - ( - testedBranch = true - or - testedBranch = false and - ( - this.getPattern().asRecordPattern().isUnrestricted() - or - not this.getPattern() instanceof RecordPatternExpr - ) - ) - } -} - private predicate switchCaseControls(SwitchCase sc, BasicBlock bb) { exists(BasicBlock caseblock | caseblock.getFirstNode() = sc.getControlFlowNode() and diff --git a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll index be7d499f68a..ea0df55d60f 100644 --- a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll @@ -417,8 +417,8 @@ private predicate downcastSuccessor(VarAccess va, RefType t) { * Holds if `va` is an access to a value that is guarded by `instanceof t` or `case e t`. */ private predicate typeTestGuarded(VarAccess va, RefType t) { - exists(TypeTestGuard typeTest, BaseSsaVariable v | - typeTest.appliesTypeTest(v.getAUse(), t, true) and + exists(Guard typeTest, BaseSsaVariable v | + typeTest.appliesTypeTest(v.getAUse(), t, _) and va = v.getAUse() and guardControls_v1(typeTest, va.getBasicBlock(), true) ) @@ -428,8 +428,8 @@ private predicate typeTestGuarded(VarAccess va, RefType t) { * Holds if `aa` is an access to a value that is guarded by `instanceof t` or `case e t`. */ predicate arrayTypeTestGuarded(ArrayAccess aa, RefType t) { - exists(TypeTestGuard typeTest, BaseSsaVariable v1, BaseSsaVariable v2, ArrayAccess aa1 | - typeTest.appliesTypeTest(aa1, t, true) and + 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 diff --git a/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll b/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll index aa83d03f646..2510149141f 100644 --- a/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll +++ b/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll @@ -194,7 +194,7 @@ private module Dispatch { */ private predicate impossibleDispatchTarget(MethodCall source, Method tgt) { tgt = viableImpl_v1_cand(source) and - exists(TypeTestGuard typeTest, BaseSsaVariable v, Expr q, RefType t | + exists(Guard typeTest, BaseSsaVariable v, Expr q, RefType t | source.getQualifier() = q and v.getAUse() = q and typeTest.appliesTypeTest(v.getAUse(), t, false) and From 84ec453a808d5f88fbe049a16a8979066a4753cd Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 24 Nov 2023 18:55:12 +0000 Subject: [PATCH 102/115] Explicitly rule out switchCaseControls for PatternCase --- java/ql/lib/semmle/code/java/controlflow/Guards.qll | 2 ++ 1 file changed, 2 insertions(+) diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index 71f2ccb0567..854a106bbb8 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -258,6 +258,8 @@ class Guard extends ExprParent { private predicate switchCaseControls(SwitchCase sc, BasicBlock bb) { 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: From e9603f0abae4751b898a6aa3e20f2b6eabfc5095 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 24 Nov 2023 19:03:23 +0000 Subject: [PATCH 103/115] Factor out `isNonFallThroughPredecessor` --- .../semmle/code/java/controlflow/Guards.qll | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index 854a106bbb8..9353223392d 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -102,6 +102,22 @@ 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.(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 @@ -215,15 +231,8 @@ class Guard extends ExprParent { branch = true and bb2.getFirstNode() = sc.getControlFlowNode() and pred = sc.getControlFlowNode().getAPredecessor() and - // This is either the top of the switch block, or a preceding pattern case - // if one exists (in which case the edge might come from the case itself or its guard) - ( - pred.(Expr).getParent*() = sc.getSelectorExpr() - or - pred.(Expr).getParent*() = getClosestPrecedingPatternCase(sc).getGuard() - or - pred = getClosestPrecedingPatternCase(sc) - ) and + isNonFallThroughPredecessor(sc, pred) + and bb1 = pred.getBasicBlock() ) or @@ -264,14 +273,7 @@ private predicate switchCaseControls(SwitchCase sc, BasicBlock bb) { caseblock.bbDominates(bb) and // Check we can't fall through from a previous block: forall(ControlFlowNode pred | pred = sc.getControlFlowNode().getAPredecessor() | - // Branch straight from the switch selector: - pred.(Expr).getParent*() = sc.getSelectorExpr() - or - // Branch from a predecessor pattern case (note pattern cases cannot ever fall through) - pred = sc.getSiblingCase(_).(PatternCase) - or - // Branch from a predecessor pattern case's guard test, which also can't be a fallthrough edge - pred.(Expr).getParent*() = sc.getSiblingCase(_).(PatternCase).getGuard() + isNonFallThroughPredecessor(sc, pred) ) ) } From 561f06a4bdbb9630f765634011100e8594c2eeff Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 24 Nov 2023 19:04:46 +0000 Subject: [PATCH 104/115] Remove unused predicate --- java/ql/lib/semmle/code/java/Statement.qll | 7 ------- 1 file changed, 7 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index a43bbfa5916..696020e19e2 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -450,13 +450,6 @@ class SwitchCase extends Stmt, @case { result = this.getSwitch().getExpr() or result = this.getSwitchExpr().getExpr() } - /** - * Gets the `i`th case in this case's switch block. - */ - SwitchCase getSiblingCase(int i) { - result = this.getSwitch().getCase(i) or result = this.getSwitchExpr().getCase(i) - } - /** * Gets this case's ordinal in its switch block. */ From b1cea1d91ef9cf2ab8a71343f9e2f06321f1331f Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 24 Nov 2023 19:05:33 +0000 Subject: [PATCH 105/115] autoformat --- java/ql/lib/semmle/code/java/Expr.qll | 4 +++- java/ql/lib/semmle/code/java/controlflow/Guards.qll | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index c10756bef18..a2e5589f49d 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1673,7 +1673,9 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr { 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)) ) + 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. */ diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index 9353223392d..dc009713be2 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -231,8 +231,7 @@ class Guard extends ExprParent { branch = true and bb2.getFirstNode() = sc.getControlFlowNode() and pred = sc.getControlFlowNode().getAPredecessor() and - isNonFallThroughPredecessor(sc, pred) - and + isNonFallThroughPredecessor(sc, pred) and bb1 = pred.getBasicBlock() ) or From cc68169f430ffe90fbd27cd95b3e633b20f61264 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Fri, 24 Nov 2023 19:15:45 +0000 Subject: [PATCH 106/115] Update test expectations re: record-pattern type accesses --- java/ql/consistency-queries/children.ql | 4 +++- .../test/library-tests/pattern-instanceof/PrintAst.expected | 3 +++ java/ql/test/library-tests/printAst/PrintAst.expected | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/java/ql/consistency-queries/children.ql b/java/ql/consistency-queries/children.ql index 726b9eddfc1..755c680e790 100644 --- a/java/ql/consistency-queries/children.ql +++ b/java/ql/consistency-queries/children.ql @@ -50,7 +50,9 @@ predicate gapInChildren(Element e, int i) { // 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) + 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) { diff --git a/java/ql/test/library-tests/pattern-instanceof/PrintAst.expected b/java/ql/test/library-tests/pattern-instanceof/PrintAst.expected index f01bbc602d3..e36a0688199 100644 --- a/java/ql/test/library-tests/pattern-instanceof/PrintAst.expected +++ b/java/ql/test/library-tests/pattern-instanceof/PrintAst.expected @@ -42,7 +42,10 @@ Test.java: # 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 diff --git a/java/ql/test/library-tests/printAst/PrintAst.expected b/java/ql/test/library-tests/printAst/PrintAst.expected index ebcb9f2cbee..be523390620 100644 --- a/java/ql/test/library-tests/printAst/PrintAst.expected +++ b/java/ql/test/library-tests/printAst/PrintAst.expected @@ -291,6 +291,7 @@ A.java: # 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(...))" From 3971817c925abb036090823f133a27e255658cc7 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Mon, 27 Nov 2023 12:35:52 +0000 Subject: [PATCH 107/115] Fix: ensure is a switch block --- java/ql/lib/semmle/code/java/ControlFlowGraph.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index 3a3849a256e..c84ea5eb8a4 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -1300,7 +1300,7 @@ private module ControlFlowGraphImpl { ) or // Switch statements and expressions - exists(StmtParent switch | + exists(StmtParent switch | switch instanceof SwitchStmt or switch instanceof SwitchExpr | exists(Expr switchExpr | switchExpr = switch.(SwitchStmt).getExpr() or switchExpr = switch.(SwitchExpr).getExpr() | From 40464ed1f956a531d42e9540e46491cf8f279678 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Mon, 27 Nov 2023 14:04:17 +0000 Subject: [PATCH 108/115] Eliminate duplicate predicate --- .../code/java/controlflow/internal/SwitchCases.qll | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll b/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll index e0aad28568c..b3b295dbecb 100644 --- a/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll +++ b/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll @@ -2,19 +2,12 @@ import java -/** - * Gets the `i`th `SwitchCase` defined on `switch`, if one exists. - */ -SwitchCase getCase(StmtParent switch, int i) { - result = switch.(SwitchExpr).getCase(i) or result = switch.(SwitchStmt).getCase(i) -} - /** * Gets the `i`th `PatternCase` defined on `switch`, if one exists. */ PatternCase getPatternCase(StmtParent switch, int i) { result = - rank[i + 1](PatternCase pc, int caseIdx | pc = getCase(switch, caseIdx) | pc order by caseIdx) + rank[i + 1](PatternCase pc, int caseIdx | pc.isNthCaseOf(switch, caseIdx) | pc order by caseIdx) } /** From 6f3bff19ccabe670a55bd3d8e1c712430084281b Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Mon, 27 Nov 2023 14:07:18 +0000 Subject: [PATCH 109/115] Expose getFirstPatternCase, not getPatternCase/2 --- java/ql/lib/semmle/code/java/ControlFlowGraph.qll | 4 ++-- .../code/java/controlflow/internal/SwitchCases.qll | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index c84ea5eb8a4..dc3ae295c97 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -478,9 +478,9 @@ private module ControlFlowGraphImpl { or result instanceof NullDefaultCase or - not exists(getPatternCase(switch, _)) + not exists(getFirstPatternCase(switch)) or - result.getIndex() <= getPatternCase(switch, 0).getIndex() + result.getIndex() <= getFirstPatternCase(switch).getIndex() ) } diff --git a/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll b/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll index b3b295dbecb..66975f12290 100644 --- a/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll +++ b/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll @@ -5,11 +5,18 @@ import java /** * Gets the `i`th `PatternCase` defined on `switch`, if one exists. */ -PatternCase getPatternCase(StmtParent switch, int i) { +private PatternCase getPatternCase(StmtParent 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(StmtParent switch) { + result = getPatternCase(switch, 0) +} + /** * Gets the PatternCase after pc, if one exists. */ From e50a0eee59e2b1f75f3653f36dc50ef84f7808c6 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Mon, 27 Nov 2023 14:10:32 +0000 Subject: [PATCH 110/115] Remove duplicate of expr exception propagation logic --- java/ql/lib/semmle/code/java/ControlFlowGraph.qll | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index dc3ae295c97..d5a819bd8f8 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -973,7 +973,6 @@ private module ControlFlowGraphImpl { // 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 any abrupt completion of its guard // * 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) @@ -981,10 +980,7 @@ private module ControlFlowGraphImpl { last = pc and completion = basicBooleanCompletion(false) or last(pc.getGuard(), last, completion) and - ( - completion = BooleanCompletion(false, _) or - not completion instanceof NormalOrBooleanCompletion - ) + completion = BooleanCompletion(false, _) or not pc.isRule() and lastPatternCaseMatchingOp(pc, last, completion) From 633b92da626d179bf95b473f11033b02d61db9b2 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Mon, 27 Nov 2023 14:16:54 +0000 Subject: [PATCH 111/115] Introduce and use SwitchBlock instead of StmtParent for switch-statement-or-expression --- java/ql/lib/semmle/code/java/ControlFlowGraph.qll | 10 +++++----- java/ql/lib/semmle/code/java/Statement.qll | 7 +++++++ java/ql/lib/semmle/code/java/controlflow/Guards.qll | 4 ++-- .../code/java/controlflow/internal/SwitchCases.qll | 8 ++++---- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index d5a819bd8f8..995a3d2456a 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -440,7 +440,7 @@ 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(StmtParent switch, SwitchCase c, int i) { c.isNthCaseOf(switch, i) } + 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`. @@ -450,7 +450,7 @@ private module ControlFlowGraphImpl { * 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, StmtParent switch) { + 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 | @@ -471,7 +471,7 @@ private module ControlFlowGraphImpl { * * Otherwise it is any case in the switch block. */ - private SwitchCase getAFirstSwitchCase(StmtParent switch) { + private SwitchCase getAFirstSwitchCase(SwitchBlock switch) { result.getParent() = switch and ( result.(ConstCase).getValue(_) instanceof NullLiteral @@ -484,7 +484,7 @@ private module ControlFlowGraphImpl { ) } - private Stmt getSwitchStatement(StmtParent switch, int i) { result.isNthChildOf(switch, i) } + 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, @@ -1296,7 +1296,7 @@ private module ControlFlowGraphImpl { ) or // Switch statements and expressions - exists(StmtParent switch | switch instanceof SwitchStmt or switch instanceof SwitchExpr | + exists(SwitchBlock switch | exists(Expr switchExpr | switchExpr = switch.(SwitchStmt).getExpr() or switchExpr = switch.(SwitchExpr).getExpr() | diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index 696020e19e2..e1dafe7a5a1 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -428,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. * diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index dc009713be2..0f0c938ac19 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -76,13 +76,13 @@ 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(StmtParent switch, SwitchCase c, int i) { c.isNthCaseOf(switch, i) } +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, StmtParent 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 | diff --git a/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll b/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll index 66975f12290..f7a045b4c2b 100644 --- a/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll +++ b/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll @@ -5,7 +5,7 @@ import java /** * Gets the `i`th `PatternCase` defined on `switch`, if one exists. */ -private PatternCase getPatternCase(StmtParent switch, int i) { +private PatternCase getPatternCase(SwitchBlock switch, int i) { result = rank[i + 1](PatternCase pc, int caseIdx | pc.isNthCaseOf(switch, caseIdx) | pc order by caseIdx) } @@ -13,7 +13,7 @@ private PatternCase getPatternCase(StmtParent switch, int i) { /** * Gets the first `PatternCase` defined on `switch`, if one exists. */ -PatternCase getFirstPatternCase(StmtParent switch) { +PatternCase getFirstPatternCase(SwitchBlock switch) { result = getPatternCase(switch, 0) } @@ -21,11 +21,11 @@ PatternCase getFirstPatternCase(StmtParent switch) { * Gets the PatternCase after pc, if one exists. */ PatternCase getNextPatternCase(PatternCase pc) { - exists(int idx, StmtParent switch | + exists(int idx, SwitchBlock switch | pc = getPatternCase(switch, idx) and result = getPatternCase(switch, idx + 1) ) } -int lastCaseIndex(StmtParent switch) { +int lastCaseIndex(SwitchBlock switch) { result = max(int i | any(SwitchCase c).isNthCaseOf(switch, i)) } From 53ca8e5fe9bcc404a6e624041f521dbbca9e8c0a Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Mon, 27 Nov 2023 14:30:07 +0000 Subject: [PATCH 112/115] autoformat --- java/ql/lib/semmle/code/java/ControlFlowGraph.qll | 4 +++- .../lib/semmle/code/java/controlflow/internal/SwitchCases.qll | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index 995a3d2456a..24a506f21ce 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -440,7 +440,9 @@ 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) } + 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`. diff --git a/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll b/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll index f7a045b4c2b..1d94f075abb 100644 --- a/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll +++ b/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll @@ -13,9 +13,7 @@ private PatternCase getPatternCase(SwitchBlock switch, int i) { /** * Gets the first `PatternCase` defined on `switch`, if one exists. */ -PatternCase getFirstPatternCase(SwitchBlock switch) { - result = getPatternCase(switch, 0) -} +PatternCase getFirstPatternCase(SwitchBlock switch) { result = getPatternCase(switch, 0) } /** * Gets the PatternCase after pc, if one exists. From e93fe8d614ef3393403631051605e4a2e27b05c3 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 28 Nov 2023 12:06:30 +0000 Subject: [PATCH 113/115] Update change note --- java/ql/lib/change-notes/2023-11-03-jdk21-support.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/lib/change-notes/2023-11-03-jdk21-support.md b/java/ql/lib/change-notes/2023-11-03-jdk21-support.md index 33d5019a442..f561b3211ca 100644 --- a/java/ql/lib/change-notes/2023-11-03-jdk21-support.md +++ b/java/ql/lib/change-notes/2023-11-03-jdk21-support.md @@ -1,6 +1,6 @@ --- category: minorAnalysis --- -* Switch cases using binding patterns and `case null[, default]` are now supported. Classes `PatternCase` and `CaseNullDefault` are introduced to represent new kinds of case statement. +* 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. From 94819e37c43eb231b7fc6c182ab41af9b3b730f5 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 28 Nov 2023 12:06:45 +0000 Subject: [PATCH 114/115] More StmtParent -> SwitchBlock --- java/ql/lib/semmle/code/java/Expr.qll | 2 +- java/ql/lib/semmle/code/java/Statement.qll | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index a2e5589f49d..be3976b8458 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1684,7 +1684,7 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr { /** * Gets the switch statement or expression whose pattern declares this identifier, if any. */ - StmtParent getAssociatedSwitch() { + SwitchBlock getAssociatedSwitch() { exists(PatternCase pc | pc = result.(SwitchStmt).getAPatternCase() or diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index e1dafe7a5a1..05d105e4de3 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -468,7 +468,7 @@ class SwitchCase extends Stmt, @case { * Holds if this is the `n`th case of switch block `parent`. */ pragma[nomagic] - predicate isNthCaseOf(StmtParent parent, int n) { + predicate isNthCaseOf(SwitchBlock parent, int n) { this.getCaseIndex() = n and this.getParent() = parent } From aa8f79885347323461d6962ccfa5068fd08e7052 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 28 Nov 2023 12:10:53 +0000 Subject: [PATCH 115/115] Move condition into isNonFallThroughPredecessor --- .../ql/lib/semmle/code/java/controlflow/Guards.qll | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index 0f0c938ac19..a97cf1f8f57 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -111,11 +111,14 @@ private PatternCase getClosestPrecedingPatternCase(SwitchCase case) { * test or guard failing and proceeding to then consider subsequent cases. */ private predicate isNonFallThroughPredecessor(SwitchCase sc, ControlFlowNode pred) { - pred.(Expr).getParent*() = sc.getSelectorExpr() - or - pred.(Expr).getParent*() = getClosestPrecedingPatternCase(sc).getGuard() - or - pred = getClosestPrecedingPatternCase(sc) + pred = sc.getControlFlowNode().getAPredecessor() and + ( + pred.(Expr).getParent*() = sc.getSelectorExpr() + or + pred.(Expr).getParent*() = getClosestPrecedingPatternCase(sc).getGuard() + or + pred = getClosestPrecedingPatternCase(sc) + ) } /** @@ -230,7 +233,6 @@ class Guard extends ExprParent { not sc instanceof PatternCase and branch = true and bb2.getFirstNode() = sc.getControlFlowNode() and - pred = sc.getControlFlowNode().getAPredecessor() and isNonFallThroughPredecessor(sc, pred) and bb1 = pred.getBasicBlock() )