From 7c503120ae63d34e36ccaeb68469671bd20846bd Mon Sep 17 00:00:00 2001 From: Nick Rolfe Date: Tue, 5 Jan 2021 16:08:33 +0000 Subject: [PATCH] Add AST library for control expressions (conditionals and loops) --- Cargo.lock | 2 +- extractor/Cargo.toml | 2 +- generator/Cargo.toml | 2 +- ql/src/codeql_ruby/AST.qll | 1 + ql/src/codeql_ruby/ast/Control.qll | 404 ++++++++++++++++++ ql/src/codeql_ruby/ast/Expr.qll | 56 +++ ql/src/codeql_ruby/ast/internal/Control.qll | 176 ++++++++ ql/src/codeql_ruby/ast/internal/Expr.qll | 32 +- ql/src/codeql_ruby/ast/internal/Pattern.qll | 2 +- .../codeql_ruby/ast/internal/TreeSitter.qll | 28 +- .../internal/ControlFlowGraphImpl.qll | 15 +- ql/src/ruby.dbscheme | 16 +- .../ast/control/CaseExpr.expected | 22 + ql/test/library-tests/ast/control/CaseExpr.ql | 17 + .../ast/control/ConditionalExpr.expected | 31 ++ .../ast/control/ConditionalExpr.ql | 59 +++ .../ast/control/ControlExpr.expected | 24 ++ .../library-tests/ast/control/ControlExpr.ql | 3 + .../library-tests/ast/control/Loop.expected | 38 ++ ql/test/library-tests/ast/control/Loop.ql | 38 ++ ql/test/library-tests/ast/control/cases.rb | 22 + .../library-tests/ast/control/conditionals.rb | 58 +++ ql/test/library-tests/ast/control/loops.rb | 61 +++ 23 files changed, 1070 insertions(+), 39 deletions(-) create mode 100644 ql/src/codeql_ruby/ast/Control.qll create mode 100644 ql/src/codeql_ruby/ast/internal/Control.qll create mode 100644 ql/test/library-tests/ast/control/CaseExpr.expected create mode 100644 ql/test/library-tests/ast/control/CaseExpr.ql create mode 100644 ql/test/library-tests/ast/control/ConditionalExpr.expected create mode 100644 ql/test/library-tests/ast/control/ConditionalExpr.ql create mode 100644 ql/test/library-tests/ast/control/ControlExpr.expected create mode 100644 ql/test/library-tests/ast/control/ControlExpr.ql create mode 100644 ql/test/library-tests/ast/control/Loop.expected create mode 100644 ql/test/library-tests/ast/control/Loop.ql create mode 100644 ql/test/library-tests/ast/control/cases.rb create mode 100644 ql/test/library-tests/ast/control/conditionals.rb create mode 100644 ql/test/library-tests/ast/control/loops.rb diff --git a/Cargo.lock b/Cargo.lock index a493f322a57..4f62e2b3694 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -535,7 +535,7 @@ dependencies = [ [[package]] name = "tree-sitter-ruby" version = "0.17.0" -source = "git+https://github.com/tree-sitter/tree-sitter-ruby.git?rev=49c5f6e9cc9ea1a3b9fb5414ba0c2d697acb2448#49c5f6e9cc9ea1a3b9fb5414ba0c2d697acb2448" +source = "git+https://github.com/tree-sitter/tree-sitter-ruby.git?rev=5021a6a6eda24e10f954dcfec00e7a7adafba8ba#5021a6a6eda24e10f954dcfec00e7a7adafba8ba" dependencies = [ "cc", "tree-sitter", diff --git a/extractor/Cargo.toml b/extractor/Cargo.toml index 180e767b53b..49579e22bb3 100644 --- a/extractor/Cargo.toml +++ b/extractor/Cargo.toml @@ -10,7 +10,7 @@ edition = "2018" flate2 = "1.0" node-types = { path = "../node-types" } tree-sitter = "0.17" -tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "49c5f6e9cc9ea1a3b9fb5414ba0c2d697acb2448" } +tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "5021a6a6eda24e10f954dcfec00e7a7adafba8ba" } clap = "2.33" tracing = "0.1" tracing-subscriber = { version = "0.2", features = ["env-filter"] } diff --git a/generator/Cargo.toml b/generator/Cargo.toml index e751b444fe7..a62603a2f75 100644 --- a/generator/Cargo.toml +++ b/generator/Cargo.toml @@ -10,4 +10,4 @@ edition = "2018" node-types = { path = "../node-types" } tracing = "0.1" tracing-subscriber = { version = "0.2", features = ["env-filter"] } -tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "49c5f6e9cc9ea1a3b9fb5414ba0c2d697acb2448" } +tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "5021a6a6eda24e10f954dcfec00e7a7adafba8ba" } diff --git a/ql/src/codeql_ruby/AST.qll b/ql/src/codeql_ruby/AST.qll index 1300f291969..08a5b7a315a 100644 --- a/ql/src/codeql_ruby/AST.qll +++ b/ql/src/codeql_ruby/AST.qll @@ -1,4 +1,5 @@ import codeql.Locations +import ast.Control import ast.Expr import ast.Method import ast.Parameter diff --git a/ql/src/codeql_ruby/ast/Control.qll b/ql/src/codeql_ruby/ast/Control.qll new file mode 100644 index 00000000000..a1208c24668 --- /dev/null +++ b/ql/src/codeql_ruby/ast/Control.qll @@ -0,0 +1,404 @@ +private import codeql_ruby.AST +private import internal.Control + +/** + * A control expression that can be any of the following: + * - `case` + * - `if`/`unless` (including expression-modifier variants) + * - ternary-if (`?:`) + * - `while`/`until` (including expression-modifier variants) + * - `for` + */ +class ControlExpr extends Expr { + override ControlExpr::Range range; +} + +/** + * A conditional expression: `if`/`unless` (including expression-modifier + * variants), and ternary-if (`?:`) expressions. + */ +class ConditionalExpr extends Expr { + override ConditionalExpr::Range range; + + /** + * Gets the condition expression. For example, the result is `foo` in the + * following: + * ```rb + * if foo + * bar = 1 + * end + * ``` + */ + final Expr getCondition() { result = range.getCondition() } + + /** Gets the 'then' branch of this conditional expression. */ + Expr getThen() { result = range.getThen() } + + /** Gets the 'else' branch of this conditional expression, if any. */ + Expr getElse() { result = range.getElse() } +} + +/** + * An `if` or `elsif` expression. + * ``` + */ +class IfOrElsifExpr extends ConditionalExpr { + override IfOrElsifExpr::Range range; + + /** Gets the 'then' branch of this `if`/`elsif` expression. */ + final override ThenExpr getThen() { result = range.getThen() } + + /** + * Gets the `elsif`/`else` branch of this `if`/`elsif` expression, if any. In + * the following example, the result is an `ElseExpr` containing `b`. + * ```rb + * if foo + * a + * else + * b + * end + * ``` + * But there is no result for the following: + * ```rb + * if foo + * a + * end + * ``` + * There can be at most one result, since `elsif` branches nest. In the + * following example, `ifExpr.getElse()` returns an `ElsifExpr`, and the + * `else` branch is nested inside that. To get the `ElseExpr` for the `else` + * branch, i.e. the one containing `c`, use + * `getElse().(ElsifExpr).getElse()`. + * ```rb + * if foo + * a + * elsif bar + * b + * else + * c + * end + * ``` + */ + final override Expr getElse() { result = range.getElse() } +} + +/** + * An `if` expression. + * ```rb + * if x + * y += 1 + * end + * ``` + */ +class IfExpr extends IfOrElsifExpr, @if { + final override IfExpr::Range range; + + final override string getAPrimaryQlClass() { result = "IfExpr" } + + final override string toString() { result = "if ..." } +} + +/** + * An `elsif` expression. + * ```rb + * if x + * a += 1 + * elsif y + * a += 2 + * end + * ``` + */ +class ElsifExpr extends ConditionalExpr { + final override ElsifExpr::Range range; + + final override string getAPrimaryQlClass() { result = "ElsifExpr" } + + final override string toString() { result = "elsif ..." } +} + +/** + * An `unless` expression. + * ```rb + * unless x == 0 + * y /= x + * end + * ``` + */ +class UnlessExpr extends ConditionalExpr, @unless { + final override UnlessExpr::Range range; + + final override string getAPrimaryQlClass() { result = "UnlessExpr" } + + final override string toString() { result = "unless ..." } +} + +/** + * An expression modified using `if`. In the following example, `getCondition` + * returns the `Expr` for `bar`, and `getThen` returns the `Expr` for `foo`. + * ```rb + * foo if bar + * ``` + */ +class IfModifierExpr extends ConditionalExpr, @if_modifier { + final override IfModifierExpr::Range range; + + final override string getAPrimaryQlClass() { result = "IfModifierExpr" } + + final override string toString() { result = "... if ..." } + + /** + * Does not hold, since `if`-modified expressions cannot have `else` + * branches. + */ + final override Expr getElse() { none() } +} + +/** + * An expression modified using `unless`. For example, in: + * ```rb + * y /= x unless x == 0 + * ``` + * `getCondition` returns the `x == 0` expression, and `getThen` returns the + * `y /= x` expression. + */ +class UnlessModifierExpr extends ConditionalExpr, @unless_modifier { + final override UnlessModifierExpr::Range range; + + final override string getAPrimaryQlClass() { result = "UnlessModifierExpr" } + + final override string toString() { result = "... unless ..." } + + /** + * Does not hold, since `unless`-modified expressions cannot have `else` + * branches. + */ + final override Expr getElse() { none() } +} + +/** + * A conditional expression using the ternary (`?:`) operator. + * ```rb + * (a > b) ? a : b + * ``` + */ +class TernaryIfExpr extends ConditionalExpr, @conditional { + final override TernaryIfExpr::Range range; + + final override string getAPrimaryQlClass() { result = "TernaryIfExpr" } + + final override string toString() { result = "... ? ... : ..." } +} + +class CaseExpr extends ControlExpr, @case__ { + final override CaseExpr::Range range; + + final override string getAPrimaryQlClass() { result = "CaseExpr" } + + final override string toString() { result = "case ..." } + + /** + * Gets the expression being compared, if any. For example, `foo` in the following example. + * ```rb + * case foo + * when 0 + * puts 'zero' + * when 1 + * puts 'one' + * end + * ``` + * There is no result for the following example: + * ```rb + * case + * when a then 0 + * when b then 1 + * else 2 + * end + * ``` + */ + final Expr getValue() { result = range.getValue() } + + /** + * Gets the `n`th branch of this case expression, either a `WhenExpr` or an + * `ElseExpr`. + */ + final Expr getBranch(int n) { result = range.getBranch(n) } + + /** + * Gets a branch of this case expression, either a `WhenExpr` or an + * `ElseExpr`. + */ + final Expr getABranch() { result = this.getBranch(_) } + + /** Gets a `when` branch of this case expression. */ + final WhenExpr getAWhenBranch() { result = range.getAWhenBranch() } + + /** Gets the `else` branch of this case expression, if any. */ + final ElseExpr getElseBranch() { result = range.getElseBranch() } + + /** + * Gets the number of branches of this case expression. + */ + final int getNumberOfBranches() { result = count(this.getBranch(_)) } +} + +/** + * A `when` branch of a `case` expression. + * ```rb + * case + * when a>b then x + * end + * ``` + */ +class WhenExpr extends Expr, @when { + final override WhenExpr::Range range; + + final override string getAPrimaryQlClass() { result = "WhenExpr" } + + final override string toString() { result = "when ..." } + + /** Gets the body of this case-when expression. */ + final ThenExpr getBody() { result = range.getBody() } + + /** + * Gets the `n`th pattern (or condition) in this case-when expression. + */ + final Expr getPattern(int n) { result = range.getPattern(n) } + + /** + * Gets a pattern (or condition) in this case-when expression. + */ + final Expr getAPattern() { result = this.getPattern(_) } + + /** + * Gets the number of patterns in this case-when expression. + */ + final int getNumberOfPatterns() { result = count(this.getPattern(_)) } +} + +/** + * A loop. That is, a `for` loop, a `while` or `until` loop, or their + * expression-modifier variants. + */ +class Loop extends ControlExpr { + override Loop::Range range; + + /** Gets the body of this loop. */ + Expr getBody() { result = range.getBody() } +} + +/** + * A `while` loop. + * ```rb + * while a < b + * p a + * a += 2 + * end + * ``` + */ +class WhileExpr extends Loop, @while { + final override WhileExpr::Range range; + + final override string getAPrimaryQlClass() { result = "WhileExpr" } + + final override string toString() { result = "while ..." } + + /** Gets the body of this `while` loop. */ + final override DoExpr getBody() { result = range.getBody() } + + /** Gets the condition expression of this `while` loop. */ + final Expr getCondition() { result = range.getCondition() } +} + +/** + * An `until` loop. + * ```rb + * until a >= b + * p a + * a += 1 + * end + * ``` + */ +class UntilExpr extends Loop, @until { + final override UntilExpr::Range range; + + final override string getAPrimaryQlClass() { result = "UntilExpr" } + + final override string toString() { result = "until ..." } + + /** Gets the body of this `until` loop. */ + final override DoExpr getBody() { result = range.getBody() } + + /** Gets the condition expression of this `until` loop. */ + final Expr getCondition() { result = range.getCondition() } +} + +/** + * An expression looped using the `while` modifier. In the following example, + * `getCondition` returns the `Expr` for `bar`, and `getBody` returns the + * `Expr` for `foo`. + * ```rb + * foo while bar + * ``` + */ +class WhileModifierExpr extends Loop, @while_modifier { + final override WhileModifierExpr::Range range; + + final override string getAPrimaryQlClass() { result = "WhileModifierExpr" } + + final override string toString() { result = "... while ..." } + + /** Gets the condition expression of this `while`-modifier. */ + final Expr getCondition() { result = range.getCondition() } +} + +/** + * An expression looped using the `until` modifier. In the following example, + * `getCondition` returns the `Expr` for `bar`, and `getBody` returns the + * `Expr` for `foo`. + * ```rb + * foo until bar + * ``` + */ +class UntilModifierExpr extends Loop, @until_modifier { + final override UntilModifierExpr::Range range; + + final override string getAPrimaryQlClass() { result = "UntilModifierExpr" } + + final override string toString() { result = "... until ..." } + + /** Gets the condition expression of this `until`-modifier. */ + final Expr getCondition() { result = range.getCondition() } +} + +/** + * A `for` loop. + * ```rb + * for val in 1..n + * sum += val + * end + * ``` + */ +class ForExpr extends Loop, @for { + final override ForExpr::Range range; + + final override string getAPrimaryQlClass() { result = "ForExpr" } + + final override string toString() { result = "for ... in ..." } + + /** Gets the body of this `for` loop. */ + final override Expr getBody() { result = range.getBody() } + + /** Gets the pattern representing the iteration argument. */ + final Pattern getPattern() { result = range.getPattern() } + + /** + * Gets the value being iterated over. In the following example, the result + * is the expression `1..10`: + * ```rb + * for n in 1..10 do + * puts n + * end + * ``` + */ + final Expr getValue() { result = range.getValue() } +} diff --git a/ql/src/codeql_ruby/ast/Expr.qll b/ql/src/codeql_ruby/ast/Expr.qll index 5523c9f5f9f..35ea19287f3 100644 --- a/ql/src/codeql_ruby/ast/Expr.qll +++ b/ql/src/codeql_ruby/ast/Expr.qll @@ -73,3 +73,59 @@ class RegexLiteral extends Literal, @regex { final override string getAPrimaryQlClass() { result = "RegexLiteral" } } + +/** A sequence of expressions. */ +class ExprSequence extends Expr { + override ExprSequence::Range range; + + /** Gets the `n`th expression in this sequence. */ + final Expr getExpr(int n) { result = this.(ExprSequence::Range).getExpr(n) } + + /** Gets an expression in this sequence. */ + final Expr getAnExpr() { result = this.getExpr(_) } + + /** Gets the last expression in this sequence. */ + final Expr getLastExpr() { result = this.getExpr(this.getNumberOfExpressions() - 1) } + + /** Gets the number of expressions in this sequence. */ + final int getNumberOfExpressions() { result = count(this.getAnExpr()) } + + /** Holds if this sequence has no expressions. */ + final predicate isEmpty() { this.getNumberOfExpressions() = 0 } +} + +/** + * A sequence of expressions in a `then` branch of an `if`, `unless`, or `when` + * expression. + */ +class ThenExpr extends ExprSequence, @then { + final override ThenExpr::Range range; + + final override string getAPrimaryQlClass() { result = "ThenExpr" } + + final override string toString() { result = "then ..." } +} + +/** + * A sequence of expressions in an `else` branch of an `if`, `unless`, or + * `case` expression. + */ +class ElseExpr extends ExprSequence, @else { + final override ElseExpr::Range range; + + final override string getAPrimaryQlClass() { result = "ElseExpr" } + + final override string toString() { result = "else ..." } +} + +/** + * A sequence of expressions representing the body of a `for`, `while`, or + * `until` loop. + */ +class DoExpr extends ExprSequence, @do { + final override DoExpr::Range range; + + final override string getAPrimaryQlClass() { result = "DoExpr" } + + final override string toString() { result = "do ..." } +} diff --git a/ql/src/codeql_ruby/ast/internal/Control.qll b/ql/src/codeql_ruby/ast/internal/Control.qll new file mode 100644 index 00000000000..be7ed2c3a7a --- /dev/null +++ b/ql/src/codeql_ruby/ast/internal/Control.qll @@ -0,0 +1,176 @@ +private import codeql_ruby.AST +private import codeql_ruby.ast.internal.Expr +private import codeql_ruby.ast.internal.Pattern +private import codeql_ruby.ast.internal.TreeSitter + +module ControlExpr { + abstract class Range extends Expr::Range { } +} + +module ConditionalExpr { + abstract class Range extends ControlExpr::Range { + abstract Expr getCondition(); + + abstract Expr getThen(); + + abstract Expr getElse(); + } +} + +module IfOrElsifExpr { + abstract class Range extends ConditionalExpr::Range { } +} + +module IfExpr { + class Range extends IfOrElsifExpr::Range, @if { + final override Generated::If generated; + + final override Expr getCondition() { result = generated.getCondition() } + + final override ThenExpr getThen() { result = generated.getConsequence() } + + final override Expr getElse() { result = generated.getAlternative() } + } +} + +module ElsifExpr { + class Range extends IfOrElsifExpr::Range, @elsif { + final override Generated::Elsif generated; + + final override Expr getCondition() { result = generated.getCondition() } + + final override ThenExpr getThen() { result = generated.getConsequence() } + + final override Expr getElse() { result = generated.getAlternative() } + } +} + +module UnlessExpr { + class Range extends ConditionalExpr::Range, @unless { + final override Generated::Unless generated; + + final override Expr getCondition() { result = generated.getCondition() } + + final override ThenExpr getThen() { result = generated.getConsequence() } + + final override ElseExpr getElse() { result = generated.getAlternative() } + } +} + +module IfModifierExpr { + class Range extends ConditionalExpr::Range, @if_modifier { + final override Generated::IfModifier generated; + + final override Expr getCondition() { result = generated.getCondition() } + + final override Expr getThen() { result = generated.getBody() } + + final override Expr getElse() { none() } + } +} + +module UnlessModifierExpr { + class Range extends ConditionalExpr::Range, @unless_modifier { + final override Generated::UnlessModifier generated; + + final override Expr getCondition() { result = generated.getCondition() } + + final override Expr getThen() { result = generated.getBody() } + + final override Expr getElse() { none() } + } +} + +module TernaryIfExpr { + class Range extends ConditionalExpr::Range, @conditional { + final override Generated::Conditional generated; + + final override Expr getCondition() { result = generated.getCondition() } + + final override Expr getThen() { result = generated.getConsequence() } + + final override Expr getElse() { result = generated.getAlternative() } + } +} + +module CaseExpr { + class Range extends ControlExpr::Range { + final override Generated::Case generated; + + final Expr getValue() { result = generated.getValue() } + + final Expr getBranch(int n) { result = generated.getChild(n) } + + final WhenExpr getAWhenBranch() { result = this.getBranch(_) } + + final ElseExpr getElseBranch() { result = this.getBranch(_) } + } +} + +module WhenExpr { + class Range extends Expr::Range, @when { + final override Generated::When generated; + + final ThenExpr getBody() { result = generated.getBody() } + + final Expr getPattern(int n) { result = generated.getPattern(n).getChild() } + } +} + +module Loop { + abstract class Range extends ControlExpr::Range { + abstract Expr getBody(); + } +} + +module WhileExpr { + class Range extends Loop::Range, @while { + final override Generated::While generated; + + final override DoExpr getBody() { result = generated.getBody() } + + final Expr getCondition() { result = generated.getCondition() } + } +} + +module UntilExpr { + class Range extends Loop::Range, @until { + final override Generated::Until generated; + + final override DoExpr getBody() { result = generated.getBody() } + + final Expr getCondition() { result = generated.getCondition() } + } +} + +module WhileModifierExpr { + class Range extends Loop::Range, @while_modifier { + final override Generated::WhileModifier generated; + + final override Expr getBody() { result = generated.getBody() } + + final Expr getCondition() { result = generated.getCondition() } + } +} + +module UntilModifierExpr { + class Range extends Loop::Range, @until_modifier { + final override Generated::UntilModifier generated; + + final override Expr getBody() { result = generated.getBody() } + + final Expr getCondition() { result = generated.getCondition() } + } +} + +module ForExpr { + class Range extends Loop::Range, @for { + final override Generated::For generated; + + final override DoExpr getBody() { result = generated.getBody() } + + final Pattern getPattern() { result = generated.getPattern() } + + final Expr getValue() { result = generated.getValue().getChild() } + } +} diff --git a/ql/src/codeql_ruby/ast/internal/Expr.qll b/ql/src/codeql_ruby/ast/internal/Expr.qll index 9acf5421730..5580bc19a26 100644 --- a/ql/src/codeql_ruby/ast/internal/Expr.qll +++ b/ql/src/codeql_ruby/ast/internal/Expr.qll @@ -1,4 +1,4 @@ -import codeql_ruby.AST +private import codeql_ruby.AST private import codeql_ruby.ast.internal.TreeSitter module Expr { @@ -59,3 +59,33 @@ module RegexLiteral { } } } + +module ExprSequence { + abstract class Range extends Expr::Range { + abstract Expr getExpr(int n); + } +} + +module ThenExpr { + class Range extends ExprSequence::Range, @then { + final override Generated::Then generated; + + final override Expr getExpr(int n) { result = generated.getChild(n) } + } +} + +module ElseExpr { + class Range extends ExprSequence::Range, @else { + final override Generated::Else generated; + + final override Expr getExpr(int n) { result = generated.getChild(n) } + } +} + +module DoExpr { + class Range extends ExprSequence::Range, @do { + final override Generated::Do generated; + + final override Expr getExpr(int n) { result = generated.getChild(n) } + } +} diff --git a/ql/src/codeql_ruby/ast/internal/Pattern.qll b/ql/src/codeql_ruby/ast/internal/Pattern.qll index 7da1409bb61..75c348e1a58 100644 --- a/ql/src/codeql_ruby/ast/internal/Pattern.qll +++ b/ql/src/codeql_ruby/ast/internal/Pattern.qll @@ -27,7 +27,7 @@ private predicate patternNode(Generated::AstNode n, boolean parameter) { any(Generated::Assignment assign).getLeft(), any(Generated::OperatorAssignment assign).getLeft(), any(Generated::ExceptionVariable exceptionVariable).getChild(), - any(Generated::For for).getPattern(_) + any(Generated::For for).getPattern() ] } diff --git a/ql/src/codeql_ruby/ast/internal/TreeSitter.qll b/ql/src/codeql_ruby/ast/internal/TreeSitter.qll index f30dace0f45..5fc45ebfc16 100644 --- a/ql/src/codeql_ruby/ast/internal/TreeSitter.qll +++ b/ql/src/codeql_ruby/ast/internal/TreeSitter.qll @@ -621,22 +621,22 @@ module Generated { class For extends @for, AstNode { override string getAPrimaryQlClass() { result = "For" } - override Location getLocation() { for_def(this, _, _, _, _, result) } + override Location getLocation() { for_def(this, _, _, _, _, _, result) } - Do getBody() { for_def(this, _, _, result, _, _) } + Do getBody() { for_def(this, _, _, result, _, _, _) } - AstNode getPattern(int i) { for_pattern(this, i, result) } + AstNode getPattern() { for_def(this, _, _, _, result, _, _) } - In getValue() { for_def(this, _, _, _, result, _) } + In getValue() { for_def(this, _, _, _, _, result, _) } - override AstNode getParent() { for_def(this, result, _, _, _, _) } + override AstNode getParent() { for_def(this, result, _, _, _, _, _) } - override int getParentIndex() { for_def(this, _, result, _, _, _) } + override int getParentIndex() { for_def(this, _, result, _, _, _, _) } override AstNode getAFieldOrChild() { - for_def(this, _, _, result, _, _) or - for_pattern(this, _, result) or - for_def(this, _, _, _, result, _) + for_def(this, _, _, result, _, _, _) or + for_def(this, _, _, _, result, _, _) or + for_def(this, _, _, _, _, result, _) } } @@ -779,15 +779,15 @@ module Generated { class Interpolation extends @interpolation, AstNode { override string getAPrimaryQlClass() { result = "Interpolation" } - override Location getLocation() { interpolation_def(this, _, _, _, result) } + override Location getLocation() { interpolation_def(this, _, _, result) } - UnderscoreStatement getChild() { interpolation_def(this, _, _, result, _) } + UnderscoreStatement getChild() { interpolation_child(this, result) } - override AstNode getParent() { interpolation_def(this, result, _, _, _) } + override AstNode getParent() { interpolation_def(this, result, _, _) } - override int getParentIndex() { interpolation_def(this, _, result, _, _) } + override int getParentIndex() { interpolation_def(this, _, result, _) } - override AstNode getAFieldOrChild() { interpolation_def(this, _, _, result, _) } + override AstNode getAFieldOrChild() { interpolation_child(this, result) } } class KeywordParameter extends @keyword_parameter, AstNode { diff --git a/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll b/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll index 1b446dcf145..a5475b6af66 100644 --- a/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll +++ b/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll @@ -608,7 +608,7 @@ module Trees { */ private class ForTree extends ControlFlowTree, For { final override predicate propagatesAbnormal(AstNode child) { - child = this.getPattern(_) or child = this.getValue() + child = this.getPattern() or child = this.getValue() } final override predicate first(AstNode node) { node = this.getValue() } @@ -638,18 +638,11 @@ module Trees { c instanceof SimpleCompletion or pred = this and - first(this.getPattern(0), succ) and + first(this.getPattern(), succ) and c.(EmptinessCompletion).getValue() = false or - exists(int i, ControlFlowTree next | - last(this.getPattern(i), pred, c) and - first(next, succ) and - c instanceof SimpleCompletion - | - next = this.getPattern(i + 1) - or - not exists(this.getPattern(i + 1)) and next = this.getBody() - ) + first(this.getBody(), succ) and + last(this.getPattern(), pred, c) or last(this.getBody(), pred, c) and succ = this and diff --git a/ql/src/ruby.dbscheme b/ql/src/ruby.dbscheme index bc791968207..235c3e83f29 100644 --- a/ql/src/ruby.dbscheme +++ b/ql/src/ruby.dbscheme @@ -585,14 +585,7 @@ exceptions_def( int loc: @location ref ); -@for_pattern_type = @destructured_left_assignment | @rest_assignment | @underscore_lhs - -#keyset[for, index] -for_pattern( - int for: @for ref, - int index: int ref, - unique int pattern: @for_pattern_type ref -); +@for_pattern_type = @left_assignment_list | @underscore_lhs #keyset[parent, parent_index] for_def( @@ -600,6 +593,7 @@ for_def( int parent: @ast_node_parent ref, int parent_index: int ref, int body: @do ref, + int pattern: @for_pattern_type ref, int value: @in ref, int loc: @location ref ); @@ -702,12 +696,16 @@ in_def( int loc: @location ref ); +interpolation_child( + unique int interpolation: @interpolation ref, + unique int child: @underscore_statement ref +); + #keyset[parent, parent_index] interpolation_def( unique int id: @interpolation, int parent: @ast_node_parent ref, int parent_index: int ref, - int child: @underscore_statement ref, int loc: @location ref ); diff --git a/ql/test/library-tests/ast/control/CaseExpr.expected b/ql/test/library-tests/ast/control/CaseExpr.expected new file mode 100644 index 00000000000..ff2b223690a --- /dev/null +++ b/ql/test/library-tests/ast/control/CaseExpr.expected @@ -0,0 +1,22 @@ +caseValues +| cases.rb:8:1:15:3 | case ... | cases.rb:8:6:8:6 | a | +caseNoValues +| cases.rb:18:1:22:3 | case ... | +caseElseBranches +| cases.rb:8:1:15:3 | case ... | cases.rb:13:1:14:7 | else ... | +caseNoElseBranches +| cases.rb:18:1:22:3 | case ... | +caseWhenBranches +| cases.rb:8:1:15:3 | case ... | cases.rb:9:1:10:7 | when ... | 0 | cases.rb:9:6:9:6 | b | cases.rb:9:7:10:7 | then ... | +| cases.rb:8:1:15:3 | case ... | cases.rb:11:1:12:7 | when ... | 0 | cases.rb:11:6:11:6 | c | cases.rb:11:10:12:7 | then ... | +| cases.rb:8:1:15:3 | case ... | cases.rb:11:1:12:7 | when ... | 1 | cases.rb:11:9:11:9 | d | cases.rb:11:10:12:7 | then ... | +| cases.rb:18:1:22:3 | case ... | cases.rb:19:1:19:19 | when ... | 0 | cases.rb:19:6:19:10 | ... > ... | cases.rb:19:13:19:19 | then ... | +| cases.rb:18:1:22:3 | case ... | cases.rb:20:1:20:19 | when ... | 0 | cases.rb:20:6:20:11 | ... == ... | cases.rb:20:13:20:19 | then ... | +| cases.rb:18:1:22:3 | case ... | cases.rb:21:1:21:19 | when ... | 0 | cases.rb:21:6:21:10 | ... < ... | cases.rb:21:13:21:19 | then ... | +caseAllBranches +| cases.rb:8:1:15:3 | case ... | 0 | cases.rb:9:1:10:7 | when ... | +| cases.rb:8:1:15:3 | case ... | 1 | cases.rb:11:1:12:7 | when ... | +| cases.rb:8:1:15:3 | case ... | 2 | cases.rb:13:1:14:7 | else ... | +| cases.rb:18:1:22:3 | case ... | 0 | cases.rb:19:1:19:19 | when ... | +| cases.rb:18:1:22:3 | case ... | 1 | cases.rb:20:1:20:19 | when ... | +| cases.rb:18:1:22:3 | case ... | 2 | cases.rb:21:1:21:19 | when ... | diff --git a/ql/test/library-tests/ast/control/CaseExpr.ql b/ql/test/library-tests/ast/control/CaseExpr.ql new file mode 100644 index 00000000000..38fcfc8c828 --- /dev/null +++ b/ql/test/library-tests/ast/control/CaseExpr.ql @@ -0,0 +1,17 @@ +import ruby + +query predicate caseValues(CaseExpr c, Expr value) { value = c.getValue() } + +query predicate caseNoValues(CaseExpr c) { not exists(c.getValue()) } + +query predicate caseElseBranches(CaseExpr c, ElseExpr elseBranch) { elseBranch = c.getElseBranch() } + +query predicate caseNoElseBranches(CaseExpr c) { not exists(c.getElseBranch()) } + +query predicate caseWhenBranches(CaseExpr c, WhenExpr when, int pIndex, Expr p, ExprSequence body) { + when = c.getAWhenBranch() and + p = when.getPattern(pIndex) and + body = when.getBody() +} + +query predicate caseAllBranches(CaseExpr c, int n, Expr branch) { branch = c.getBranch(n) } diff --git a/ql/test/library-tests/ast/control/ConditionalExpr.expected b/ql/test/library-tests/ast/control/ConditionalExpr.expected new file mode 100644 index 00000000000..792b5565bb9 --- /dev/null +++ b/ql/test/library-tests/ast/control/ConditionalExpr.expected @@ -0,0 +1,31 @@ +conditionalExprsWithElse +| conditionals.rb:15:1:19:3 | if ... | conditionals.rb:15:4:15:9 | ... == ... | conditionals.rb:15:10:16:5 | then ... | conditionals.rb:17:1:18:5 | else ... | IfExpr | +| conditionals.rb:22:1:30:3 | if ... | conditionals.rb:22:4:22:9 | ... == ... | conditionals.rb:22:11:23:5 | then ... | conditionals.rb:24:1:29:5 | elsif ... | IfExpr | +| conditionals.rb:24:1:29:5 | elsif ... | conditionals.rb:24:7:24:12 | ... == ... | conditionals.rb:24:14:25:5 | then ... | conditionals.rb:26:1:29:5 | elsif ... | ElsifExpr | +| conditionals.rb:26:1:29:5 | elsif ... | conditionals.rb:26:7:26:12 | ... == ... | conditionals.rb:26:14:27:5 | then ... | conditionals.rb:28:1:29:5 | else ... | ElsifExpr | +| conditionals.rb:33:1:37:3 | if ... | conditionals.rb:33:4:33:9 | ... == ... | conditionals.rb:33:10:34:5 | then ... | conditionals.rb:35:1:36:5 | elsif ... | IfExpr | +| conditionals.rb:45:1:49:3 | unless ... | conditionals.rb:45:8:45:13 | ... == ... | conditionals.rb:45:14:46:5 | then ... | conditionals.rb:47:1:48:5 | else ... | UnlessExpr | +| conditionals.rb:58:5:58:25 | ... ? ... : ... | conditionals.rb:58:5:58:9 | ... > ... | conditionals.rb:58:13:58:17 | ... + ... | conditionals.rb:58:21:58:25 | ... - ... | TernaryIfExpr | +conditionalExprsWithoutElse +| conditionals.rb:10:1:12:3 | if ... | conditionals.rb:10:4:10:8 | ... > ... | conditionals.rb:10:10:11:5 | then ... | IfExpr | +| conditionals.rb:35:1:36:5 | elsif ... | conditionals.rb:35:7:35:12 | ... == ... | conditionals.rb:35:13:36:5 | then ... | ElsifExpr | +| conditionals.rb:40:1:42:3 | unless ... | conditionals.rb:40:8:40:12 | ... > ... | conditionals.rb:40:14:41:5 | then ... | UnlessExpr | +| conditionals.rb:52:1:52:14 | ... if ... | conditionals.rb:52:10:52:14 | ... > ... | conditionals.rb:52:1:52:5 | ... = ... | IfModifierExpr | +| conditionals.rb:55:1:55:18 | ... unless ... | conditionals.rb:55:14:55:18 | ... < ... | conditionals.rb:55:1:55:5 | ... = ... | UnlessModifierExpr | +ifOrElsifExprs +| conditionals.rb:10:1:12:3 | if ... | IfExpr | conditionals.rb:10:4:10:8 | ... > ... | conditionals.rb:10:10:11:5 | then ... | (none) | +| conditionals.rb:15:1:19:3 | if ... | IfExpr | conditionals.rb:15:4:15:9 | ... == ... | conditionals.rb:15:10:16:5 | then ... | else ... | +| conditionals.rb:22:1:30:3 | if ... | IfExpr | conditionals.rb:22:4:22:9 | ... == ... | conditionals.rb:22:11:23:5 | then ... | elsif ... | +| conditionals.rb:24:1:29:5 | elsif ... | ElsifExpr | conditionals.rb:24:7:24:12 | ... == ... | conditionals.rb:24:14:25:5 | then ... | elsif ... | +| conditionals.rb:26:1:29:5 | elsif ... | ElsifExpr | conditionals.rb:26:7:26:12 | ... == ... | conditionals.rb:26:14:27:5 | then ... | else ... | +| conditionals.rb:33:1:37:3 | if ... | IfExpr | conditionals.rb:33:4:33:9 | ... == ... | conditionals.rb:33:10:34:5 | then ... | elsif ... | +| conditionals.rb:35:1:36:5 | elsif ... | ElsifExpr | conditionals.rb:35:7:35:12 | ... == ... | conditionals.rb:35:13:36:5 | then ... | (none) | +unlessExprs +| conditionals.rb:40:1:42:3 | unless ... | UnlessExpr | conditionals.rb:40:8:40:12 | ... > ... | conditionals.rb:40:14:41:5 | then ... | (none) | +| conditionals.rb:45:1:49:3 | unless ... | UnlessExpr | conditionals.rb:45:8:45:13 | ... == ... | conditionals.rb:45:14:46:5 | then ... | else ... | +ifModifierExprs +| conditionals.rb:52:1:52:14 | ... if ... | IfModifierExpr | conditionals.rb:52:10:52:14 | ... > ... | conditionals.rb:52:1:52:5 | ... = ... | (none) | +unlessModifierExprs +| conditionals.rb:55:1:55:18 | ... unless ... | UnlessModifierExpr | conditionals.rb:55:14:55:18 | ... < ... | conditionals.rb:55:1:55:5 | ... = ... | (none) | +ternaryIfExprs +| conditionals.rb:58:5:58:25 | ... ? ... : ... | TernaryIfExpr | conditionals.rb:58:5:58:9 | ... > ... | conditionals.rb:58:13:58:17 | ... + ... | conditionals.rb:58:21:58:25 | ... - ... | diff --git a/ql/test/library-tests/ast/control/ConditionalExpr.ql b/ql/test/library-tests/ast/control/ConditionalExpr.ql new file mode 100644 index 00000000000..4d540a4c37a --- /dev/null +++ b/ql/test/library-tests/ast/control/ConditionalExpr.ql @@ -0,0 +1,59 @@ +import ruby + +query predicate conditionalExprsWithElse( + ConditionalExpr e, Expr cond, Expr thenExpr, Expr elseExpr, string pClass +) { + pClass = e.getAPrimaryQlClass() and + cond = e.getCondition() and + thenExpr = e.getThen() and + elseExpr = e.getElse() +} + +query predicate conditionalExprsWithoutElse( + ConditionalExpr e, Expr cond, Expr thenExpr, string pClass +) { + pClass = e.getAPrimaryQlClass() and + cond = e.getCondition() and + thenExpr = e.getThen() and + not exists(e.getElse()) +} + +predicate helper(ConditionalExpr e, string pClass, Expr cond, Expr thenExpr, string elseStr) { + pClass = e.getAPrimaryQlClass() and + cond = e.getCondition() and + thenExpr = e.getThen() and + if exists(e.getElse()) then elseStr = e.getElse().toString() else elseStr = "(none)" +} + +query predicate ifOrElsifExprs( + IfOrElsifExpr e, string pClass, Expr cond, ThenExpr thenExpr, string elseStr +) { + helper(e, pClass, cond, thenExpr, elseStr) +} + +query predicate unlessExprs( + UnlessExpr e, string pClass, Expr cond, ThenExpr thenExpr, string elseStr +) { + helper(e, pClass, cond, thenExpr, elseStr) +} + +query predicate ifModifierExprs( + IfModifierExpr e, string pClass, Expr cond, Expr thenExpr, string elseStr +) { + helper(e, pClass, cond, thenExpr, elseStr) +} + +query predicate unlessModifierExprs( + UnlessModifierExpr e, string pClass, Expr cond, Expr thenExpr, string elseStr +) { + helper(e, pClass, cond, thenExpr, elseStr) +} + +query predicate ternaryIfExprs( + TernaryIfExpr e, string pClass, Expr cond, Expr thenExpr, Expr elseExpr +) { + pClass = e.getAPrimaryQlClass() and + cond = e.getCondition() and + thenExpr = e.getThen() and + elseExpr = e.getElse() +} diff --git a/ql/test/library-tests/ast/control/ControlExpr.expected b/ql/test/library-tests/ast/control/ControlExpr.expected new file mode 100644 index 00000000000..6d6160fb180 --- /dev/null +++ b/ql/test/library-tests/ast/control/ControlExpr.expected @@ -0,0 +1,24 @@ +| cases.rb:8:1:15:3 | case ... | CaseExpr | +| cases.rb:18:1:22:3 | case ... | CaseExpr | +| conditionals.rb:10:1:12:3 | if ... | IfExpr | +| conditionals.rb:15:1:19:3 | if ... | IfExpr | +| conditionals.rb:22:1:30:3 | if ... | IfExpr | +| conditionals.rb:24:1:29:5 | elsif ... | ElsifExpr | +| conditionals.rb:26:1:29:5 | elsif ... | ElsifExpr | +| conditionals.rb:33:1:37:3 | if ... | IfExpr | +| conditionals.rb:35:1:36:5 | elsif ... | ElsifExpr | +| conditionals.rb:40:1:42:3 | unless ... | UnlessExpr | +| conditionals.rb:45:1:49:3 | unless ... | UnlessExpr | +| conditionals.rb:52:1:52:14 | ... if ... | IfModifierExpr | +| conditionals.rb:55:1:55:18 | ... unless ... | UnlessModifierExpr | +| conditionals.rb:58:5:58:25 | ... ? ... : ... | TernaryIfExpr | +| loops.rb:9:1:12:3 | for ... in ... | ForExpr | +| loops.rb:16:1:19:3 | for ... in ... | ForExpr | +| loops.rb:22:1:25:3 | for ... in ... | ForExpr | +| loops.rb:28:1:31:3 | for ... in ... | ForExpr | +| loops.rb:34:1:37:3 | while ... | WhileExpr | +| loops.rb:40:1:43:3 | while ... | WhileExpr | +| loops.rb:46:1:46:19 | ... while ... | WhileModifierExpr | +| loops.rb:49:1:52:3 | until ... | UntilExpr | +| loops.rb:55:1:58:3 | until ... | UntilExpr | +| loops.rb:61:1:61:19 | ... until ... | UntilModifierExpr | diff --git a/ql/test/library-tests/ast/control/ControlExpr.ql b/ql/test/library-tests/ast/control/ControlExpr.ql new file mode 100644 index 00000000000..ad802180efb --- /dev/null +++ b/ql/test/library-tests/ast/control/ControlExpr.ql @@ -0,0 +1,3 @@ +import ruby + +query predicate controlExprs(ControlExpr c, string pClass) { pClass = c.getAPrimaryQlClass() } diff --git a/ql/test/library-tests/ast/control/Loop.expected b/ql/test/library-tests/ast/control/Loop.expected new file mode 100644 index 00000000000..44f89900a2a --- /dev/null +++ b/ql/test/library-tests/ast/control/Loop.expected @@ -0,0 +1,38 @@ +loops +| loops.rb:9:1:12:3 | for ... in ... | ForExpr | loops.rb:9:15:12:3 | do ... | DoExpr | +| loops.rb:16:1:19:3 | for ... in ... | ForExpr | loops.rb:16:15:19:3 | do ... | DoExpr | +| loops.rb:22:1:25:3 | for ... in ... | ForExpr | loops.rb:22:35:25:3 | do ... | DoExpr | +| loops.rb:28:1:31:3 | for ... in ... | ForExpr | loops.rb:28:37:31:3 | do ... | DoExpr | +| loops.rb:34:1:37:3 | while ... | WhileExpr | loops.rb:34:12:37:3 | do ... | DoExpr | +| loops.rb:40:1:43:3 | while ... | WhileExpr | loops.rb:40:13:43:3 | do ... | DoExpr | +| loops.rb:46:1:46:19 | ... while ... | WhileModifierExpr | loops.rb:46:1:46:6 | ... += ... | AssignAddExpr | +| loops.rb:49:1:52:3 | until ... | UntilExpr | loops.rb:49:13:52:3 | do ... | DoExpr | +| loops.rb:55:1:58:3 | until ... | UntilExpr | loops.rb:55:13:58:3 | do ... | DoExpr | +| loops.rb:61:1:61:19 | ... until ... | UntilModifierExpr | loops.rb:61:1:61:6 | ... -= ... | AssignSubExpr | +forExprs +| loops.rb:9:1:12:3 | for ... in ... | loops.rb:9:5:9:5 | n | loops.rb:9:15:12:3 | do ... | 0 | loops.rb:10:5:10:12 | ... += ... | +| loops.rb:9:1:12:3 | for ... in ... | loops.rb:9:5:9:5 | n | loops.rb:9:15:12:3 | do ... | 1 | loops.rb:11:5:11:11 | ... = ... | +| loops.rb:16:1:19:3 | for ... in ... | loops.rb:16:5:16:5 | n | loops.rb:16:15:19:3 | do ... | 0 | loops.rb:17:5:17:12 | ... += ... | +| loops.rb:16:1:19:3 | for ... in ... | loops.rb:16:5:16:5 | n | loops.rb:16:15:19:3 | do ... | 1 | loops.rb:18:5:18:12 | ... -= ... | +| loops.rb:22:1:25:3 | for ... in ... | loops.rb:22:5:22:14 | (..., ...) | loops.rb:22:35:25:3 | do ... | 0 | loops.rb:23:3:23:14 | ... += ... | +| loops.rb:22:1:25:3 | for ... in ... | loops.rb:22:5:22:14 | (..., ...) | loops.rb:22:35:25:3 | do ... | 1 | loops.rb:24:3:24:14 | ... *= ... | +| loops.rb:28:1:31:3 | for ... in ... | loops.rb:28:5:28:16 | (..., ...) | loops.rb:28:37:31:3 | do ... | 0 | loops.rb:29:3:29:14 | ... += ... | +| loops.rb:28:1:31:3 | for ... in ... | loops.rb:28:5:28:16 | (..., ...) | loops.rb:28:37:31:3 | do ... | 1 | loops.rb:30:3:30:14 | ... /= ... | +forExprsTuplePatterns +| loops.rb:22:1:25:3 | for ... in ... | loops.rb:22:5:22:14 | (..., ...) | 0 | loops.rb:22:5:22:7 | key | +| loops.rb:22:1:25:3 | for ... in ... | loops.rb:22:5:22:14 | (..., ...) | 1 | loops.rb:22:10:22:14 | value | +| loops.rb:28:1:31:3 | for ... in ... | loops.rb:28:5:28:16 | (..., ...) | 0 | loops.rb:28:5:28:16 | (..., ...) | +whileExprs +| loops.rb:34:1:37:3 | while ... | loops.rb:34:7:34:11 | ... < ... | loops.rb:34:12:37:3 | do ... | 0 | loops.rb:35:3:35:8 | ... += ... | +| loops.rb:34:1:37:3 | while ... | loops.rb:34:7:34:11 | ... < ... | loops.rb:34:12:37:3 | do ... | 1 | loops.rb:36:3:36:8 | ... += ... | +| loops.rb:40:1:43:3 | while ... | loops.rb:40:7:40:11 | ... < ... | loops.rb:40:13:43:3 | do ... | 0 | loops.rb:41:3:41:8 | ... += ... | +| loops.rb:40:1:43:3 | while ... | loops.rb:40:7:40:11 | ... < ... | loops.rb:40:13:43:3 | do ... | 1 | loops.rb:42:3:42:8 | ... += ... | +whileModifierExprs +| loops.rb:46:1:46:19 | ... while ... | loops.rb:46:14:46:19 | ... >= ... | loops.rb:46:1:46:6 | ... += ... | +untilExprs +| loops.rb:49:1:52:3 | until ... | loops.rb:49:7:49:12 | ... == ... | loops.rb:49:13:52:3 | do ... | 0 | loops.rb:50:3:50:8 | ... += ... | +| loops.rb:49:1:52:3 | until ... | loops.rb:49:7:49:12 | ... == ... | loops.rb:49:13:52:3 | do ... | 1 | loops.rb:51:3:51:8 | ... -= ... | +| loops.rb:55:1:58:3 | until ... | loops.rb:55:7:55:11 | ... > ... | loops.rb:55:13:58:3 | do ... | 0 | loops.rb:56:3:56:8 | ... += ... | +| loops.rb:55:1:58:3 | until ... | loops.rb:55:7:55:11 | ... > ... | loops.rb:55:13:58:3 | do ... | 1 | loops.rb:57:3:57:8 | ... -= ... | +untilModifierExprs +| loops.rb:61:1:61:19 | ... until ... | loops.rb:61:14:61:19 | ... == ... | loops.rb:61:1:61:6 | ... -= ... | diff --git a/ql/test/library-tests/ast/control/Loop.ql b/ql/test/library-tests/ast/control/Loop.ql new file mode 100644 index 00000000000..9af8a94b357 --- /dev/null +++ b/ql/test/library-tests/ast/control/Loop.ql @@ -0,0 +1,38 @@ +import ruby + +query predicate loops(Loop l, string lClass, Expr body, string bodyClass) { + l.getBody() = body and lClass = l.getAPrimaryQlClass() and bodyClass = body.getAPrimaryQlClass() +} + +query predicate forExprs(ForExpr f, Pattern p, DoExpr body, int i, Expr bodyChild) { + p = f.getPattern() and + body = f.getBody() and + bodyChild = body.getExpr(i) +} + +query predicate forExprsTuplePatterns(ForExpr f, TuplePattern tp, int i, Pattern cp) { + tp = f.getPattern() and + cp = tp.getElement(i) +} + +query predicate whileExprs(WhileExpr e, Expr cond, DoExpr body, int i, Expr bodyChild) { + cond = e.getCondition() and + body = e.getBody() and + bodyChild = body.getExpr(i) +} + +query predicate whileModifierExprs(WhileModifierExpr e, Expr cond, Expr body) { + cond = e.getCondition() and + body = e.getBody() +} + +query predicate untilExprs(UntilExpr e, Expr cond, DoExpr body, int i, Expr bodyChild) { + cond = e.getCondition() and + body = e.getBody() and + bodyChild = body.getExpr(i) +} + +query predicate untilModifierExprs(UntilModifierExpr e, Expr cond, Expr body) { + cond = e.getCondition() and + body = e.getBody() +} diff --git a/ql/test/library-tests/ast/control/cases.rb b/ql/test/library-tests/ast/control/cases.rb new file mode 100644 index 00000000000..38a74da81d6 --- /dev/null +++ b/ql/test/library-tests/ast/control/cases.rb @@ -0,0 +1,22 @@ +# Define some variables used below +a = 0 +b = 0 +c = 0 +d = 0 + +# A case expr with a value and an else branch +case a +when b + 100 +when c, d + 200 +else + 300 +end + +# A case expr without a value or else branch +case +when a > b then 10 +when a == b then 20 +when a < b then 30 +end \ No newline at end of file diff --git a/ql/test/library-tests/ast/control/conditionals.rb b/ql/test/library-tests/ast/control/conditionals.rb new file mode 100644 index 00000000000..946a2d6659f --- /dev/null +++ b/ql/test/library-tests/ast/control/conditionals.rb @@ -0,0 +1,58 @@ +# Define some variables used below +a = 0 +b = 0 +c = 0 +d = 0 +e = 0 +f = 0 + +# If expr with no else +if a > b then + c +end + +# If expr with single else +if a == b + c +else + d +end + +# If expr with multiple nested elsif branches +if a == 0 then + c +elsif a == 1 then + d +elsif a == 2 then + e +else + f +end + +# If expr with elsif and then no else +if a == 0 + b +elsif a == 1 + c +end + +# Unless expr with no else +unless a > b then + c +end + +# Unless expr with else +unless a == b + c +else + d +end + +# If-modified expr +a = b if c > d + +# Unless-modified expr +a = b unless c < d + +# Ternary if expr +a = b > c ? d + 1 : e - 2 \ No newline at end of file diff --git a/ql/test/library-tests/ast/control/loops.rb b/ql/test/library-tests/ast/control/loops.rb new file mode 100644 index 00000000000..a400037caaa --- /dev/null +++ b/ql/test/library-tests/ast/control/loops.rb @@ -0,0 +1,61 @@ +# Define some variables used below. +foo = 0 +sum = 0 +x = 0 +y = 0 +z = 0 + +# For loop with a single variable as the iteration argument +for n in 1..10 + sum += n + foo = n +end + +# For loop with a single variable and a trailing comma as the iteration +# argument +for n in 1..10 + sum += n + foo -= n +end + +# For loop with a tuple pattern as the iteration argument +for key, value in {foo: 0, bar: 1} + sum += value + foo *= value +end + +# Same, but with parentheses around the pattern +for (key, value) in {foo: 0, bar: 1} + sum += value + foo /= value +end + +# While loop +while x < y + x += 1 + z += 1 +end + +# While loop with `do` keyword +while x < y do + x += 1 + z += 2 +end + +# While-modified expression +x += 1 while y >= x + +# Until loop +until x == y + x += 1 + z -= 1 +end + +# Until loop with `do` keyword +until x > y do + x += 1 + z -= 4 +end + +# Until-modified expression +x -= 1 until x == 0 \ No newline at end of file