Skip to content

Commit a743067

Browse files
alexrfordhvitved
authored andcommitted
Support synthesis of blocks (without a new variable scope)
1 parent 04df56d commit a743067

File tree

5 files changed

+75
-15
lines changed

5 files changed

+75
-15
lines changed

ruby/ql/lib/codeql/ruby/ast/Method.qll

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,22 @@ class BraceBlock extends Block, TBraceBlock {
226226

227227
final override string getAPrimaryQlClass() { result = "BraceBlock" }
228228
}
229+
230+
/**
231+
* A synthesized block, such as the block synthesized from the body of
232+
* a `for` loop.
233+
*/
234+
class SynthBlock extends Block, TBlockSynth {
235+
SynthBlock() { this = TBlockSynth(_, _) }
236+
237+
final override Parameter getParameter(int n) { synthChild(this, n, result) }
238+
239+
final override Stmt getStmt(int i) {
240+
i >= 0 and
241+
synthChild(this, i + this.getNumberOfParameters(), result)
242+
}
243+
244+
final override string toString() { result = "{ ... }" }
245+
246+
final override string getAPrimaryQlClass() { result = "SynthBlock" }
247+
}

ruby/ql/lib/codeql/ruby/ast/Scope.qll

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,12 @@ private import internal.AST
33
private import internal.Scope
44
private import internal.TreeSitter
55

6-
class Scope extends AstNode, TScopeType {
7-
private Scope::Range range;
6+
class Scope extends AstNode, TScopeType instanceof ScopeImpl {
7+
Scope getOuterScope() { result = super.getOuterScopeImpl() }
88

9-
Scope() { range = toGenerated(this) }
9+
Variable getAVariable() { result = super.getAVariableImpl() }
1010

11-
/** Gets the scope in which this scope is nested, if any. */
12-
Scope getOuterScope() { toGenerated(result) = range.getOuterScope() }
13-
14-
/** Gets a variable that is declared in this scope. */
15-
final Variable getAVariable() { result.getDeclaringScope() = this }
16-
17-
/** Gets the variable declared in this scope with the given name, if any. */
18-
final Variable getVariable(string name) {
19-
result = this.getAVariable() and
20-
result.getName() = name
21-
}
11+
Variable getVariable(string name) { result = super.getVariableImpl(name) }
2212
}
2313

2414
class SelfScope extends Scope, TSelfScopeType { }

ruby/ql/lib/codeql/ruby/ast/internal/AST.qll

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ private module Cached {
9393
} or
9494
TBlockArgument(Ruby::BlockArgument g) or
9595
TBlockParameter(Ruby::BlockParameter g) or
96+
TBlockSynth(AST::AstNode parent, int i) { mkSynthChild(BlockKind(), parent, i) } or
9697
TBraceBlock(Ruby::Block g) { not g.getParent() instanceof Ruby::Lambda } or
9798
TBreakStmt(Ruby::Break g) or
9899
TCaseEqExpr(Ruby::Binary g) { g instanceof @ruby_binary_equalequalequal } or
@@ -491,6 +492,8 @@ private module Cached {
491492
or
492493
result = TBitwiseXorExprSynth(parent, i)
493494
or
495+
result = TBlockSynth(parent, i)
496+
or
494497
result = TClassVariableAccessSynth(parent, i, _)
495498
or
496499
result = TConstantReadAccessSynth(parent, i, _)
@@ -644,7 +647,7 @@ class TCallable = TMethodBase or TLambda or TBlock;
644647

645648
class TMethodBase = TMethod or TSingletonMethod;
646649

647-
class TBlock = TDoBlock or TBraceBlock;
650+
class TBlock = TDoBlock or TBraceBlock or TBlockSynth;
648651

649652
class TModuleBase = TToplevel or TNamespace or TSingletonClass;
650653

ruby/ql/lib/codeql/ruby/ast/internal/Scope.qll

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
private import TreeSitter
2+
private import codeql.ruby.AST
23
private import codeql.ruby.ast.Scope
34
private import codeql.ruby.ast.internal.AST
45
private import codeql.ruby.ast.internal.Parameter
6+
private import codeql.ruby.ast.internal.Variable
57

68
class TScopeType = TMethodBase or TModuleLike or TBlockLike;
79

@@ -15,6 +17,10 @@ private class TBlockLike = TDoBlock or TLambda or TBlock or TEndBlock;
1517

1618
private class TModuleLike = TToplevel or TModuleDeclaration or TClassDeclaration or TSingletonClass;
1719

20+
private class TScopeReal = TMethodBase or TModuleLike or TDoBlock or TLambda or TBraceBlock;
21+
22+
private class TScopeSynth = TBlockSynth;
23+
1824
module Scope {
1925
class TypeRange = Callable::TypeRange or ModuleBase::TypeRange or @ruby_end_block;
2026

@@ -128,3 +134,44 @@ Scope::Range scopeOf(Ruby::AstNode n) {
128134
not p instanceof Scope::Range and result = scopeOf(p)
129135
)
130136
}
137+
138+
abstract class ScopeImpl extends AstNode, TScopeType {
139+
abstract Scope getOuterScopeImpl();
140+
141+
abstract Variable getAVariableImpl();
142+
143+
final Variable getVariableImpl(string name) {
144+
result = this.getAVariableImpl() and
145+
result.getName() = name
146+
}
147+
}
148+
149+
private class ScopeRealImpl extends ScopeImpl, TScopeReal {
150+
private Scope::Range range;
151+
152+
ScopeRealImpl() { range = toGenerated(this) }
153+
154+
override Scope getOuterScopeImpl() { toGenerated(result) = range.getOuterScope() }
155+
156+
override Variable getAVariableImpl() { result.getDeclaringScope() = this }
157+
}
158+
159+
// We desugar for loops by implementing them as calls to `each` with a block
160+
// argument. Though this is how the desugaring is described in the MRI parser,
161+
// in practice there is not a real nested scope created, so variables that
162+
// may appear to be local to the loop body (e.g. the iteration variable) are
163+
// scoped to the outer scope rather than the loop body.
164+
private class ScopeSynthImpl extends ScopeImpl, TScopeSynth {
165+
ScopeSynthImpl() { this = TBlockSynth(_, _) }
166+
167+
override Scope getOuterScopeImpl() { scopeOf(toGeneratedInclSynth(this)) = toGenerated(result) }
168+
169+
override Variable getAVariableImpl() {
170+
// Synthesized variables introduced as parameters to this scope
171+
// As this variable is also synthetic, it is genuinely local to this scope.
172+
exists(SimpleParameter p | p = TSimpleParameterSynth(this, _) |
173+
p.getVariable() = result and
174+
exists(TLocalVariableAccessSynth(p, _, result))
175+
)
176+
}
177+
}

ruby/ql/lib/codeql/ruby/ast/internal/Synthesis.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ newtype SynthKind =
1515
BitwiseAndExprKind() or
1616
BitwiseOrExprKind() or
1717
BitwiseXorExprKind() or
18+
BlockKind() or
1819
ClassVariableAccessKind(ClassVariable v) or
1920
DivExprKind() or
2021
ExponentExprKind() or

0 commit comments

Comments
 (0)