Add AST library for control expressions (conditionals and loops)

This commit is contained in:
Nick Rolfe
2021-01-05 16:08:33 +00:00
parent c35283cefb
commit 7c503120ae
23 changed files with 1070 additions and 39 deletions

View File

@@ -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() }
}