Skip to content

Commit 028ef6f

Browse files
committed
Ruby: Handle synthesized scopes
1 parent 48e6bdb commit 028ef6f

File tree

8 files changed

+114
-71
lines changed

8 files changed

+114
-71
lines changed

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

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,27 @@ private import ast.internal.Scope
1818
private import ast.internal.Synthesis
1919
private import ast.internal.TreeSitter
2020

21+
cached
22+
private module Cached {
23+
cached
24+
ModuleBase getEnclosingModule(Scope s) {
25+
result = s
26+
or
27+
not s instanceof ModuleBase and result = getEnclosingModule(s.getOuterScope())
28+
}
29+
30+
cached
31+
MethodBase getEnclosingMethod(Scope s) {
32+
result = s
33+
or
34+
not s instanceof MethodBase and
35+
not s instanceof ModuleBase and
36+
result = getEnclosingMethod(s.getOuterScope())
37+
}
38+
}
39+
40+
private import Cached
41+
2142
/**
2243
* A node in the abstract syntax tree. This class is the base class for all Ruby
2344
* program elements.
@@ -39,20 +60,10 @@ class AstNode extends TAstNode {
3960
final string getPrimaryQlClasses() { result = concat(this.getAPrimaryQlClass(), ",") }
4061

4162
/** Gets the enclosing module, if any. */
42-
ModuleBase getEnclosingModule() {
43-
exists(Scope::Range s |
44-
s = scopeOf(toGeneratedInclSynth(this)) and
45-
toGeneratedInclSynth(result) = s.getEnclosingModule()
46-
)
47-
}
63+
ModuleBase getEnclosingModule() { result = getEnclosingModule(scopeOfInclSynth(this)) }
4864

4965
/** Gets the enclosing method, if any. */
50-
MethodBase getEnclosingMethod() {
51-
exists(Scope::Range s |
52-
s = scopeOf(toGeneratedInclSynth(this)) and
53-
toGeneratedInclSynth(result) = s.getEnclosingMethod()
54-
)
55-
}
66+
MethodBase getEnclosingMethod() { result = getEnclosingMethod(scopeOfInclSynth(this)) }
5667

5768
/** Gets a textual representation of this node. */
5869
cached

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

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,6 @@ class Block extends Callable, StmtSequence, Scope, TBlock {
183183
or
184184
result = StmtSequence.super.getAChild(pred)
185185
}
186-
187-
override string getAPrimaryQlClass() { result = "Block" }
188186
}
189187

190188
/** A block enclosed within `do` and `end`. */
@@ -215,16 +213,6 @@ class DoBlock extends Block, BodyStmt, TDoBlock {
215213
* ```
216214
*/
217215
class BraceBlock extends Block, TBraceBlock {
218-
private Ruby::Block g;
219-
220-
BraceBlock() { this = TBraceBlock(g) }
221-
222-
final override Parameter getParameter(int n) {
223-
toGenerated(result) = g.getParameters().getChild(n)
224-
}
225-
226-
final override Stmt getStmt(int i) { toGenerated(result) = g.getChild(i) }
227-
228216
final override string toString() { result = "{ ... }" }
229217

230218
final override string getAPrimaryQlClass() { result = "BraceBlock" }

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ 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
97-
TBraceBlock(Ruby::Block g) { not g.getParent() instanceof Ruby::Lambda } or
96+
TBraceBlockSynth(AST::AstNode parent, int i) { mkSynthChild(BraceBlockKind(), parent, i) } or
97+
TBraceBlockReal(Ruby::Block g) { not g.getParent() instanceof Ruby::Lambda } or
9898
TBreakStmt(Ruby::Break g) or
9999
TCaseEqExpr(Ruby::Binary g) { g instanceof @ruby_binary_equalequalequal } or
100100
TCaseExpr(Ruby::Case g) or
@@ -362,7 +362,7 @@ private module Cached {
362362
n = TBitwiseXorExprReal(result) or
363363
n = TBlockArgument(result) or
364364
n = TBlockParameter(result) or
365-
n = TBraceBlock(result) or
365+
n = TBraceBlockReal(result) or
366366
n = TBreakStmt(result) or
367367
n = TCaseEqExpr(result) or
368368
n = TCaseExpr(result) or
@@ -490,7 +490,7 @@ private module Cached {
490490
or
491491
result = TBitwiseXorExprSynth(parent, i)
492492
or
493-
result = TBlockSynth(parent, i)
493+
result = TBraceBlockSynth(parent, i)
494494
or
495495
result = TClassVariableAccessSynth(parent, i, _)
496496
or
@@ -645,7 +645,9 @@ class TCallable = TMethodBase or TLambda or TBlock;
645645

646646
class TMethodBase = TMethod or TSingletonMethod;
647647

648-
class TBlock = TDoBlock or TBraceBlock or TBlockSynth;
648+
class TBraceBlock = TBraceBlockReal or TBraceBlockSynth;
649+
650+
class TBlock = TDoBlock or TBraceBlock;
649651

650652
class TModuleBase = TToplevel or TNamespace or TSingletonClass;
651653

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,27 @@ private import codeql.ruby.AST
22
private import AST
33
private import TreeSitter
44

5+
class BraceBlockReal extends BraceBlock, TBraceBlockReal {
6+
private Ruby::Block g;
7+
8+
BraceBlockReal() { this = TBraceBlockReal(g) }
9+
10+
final override Parameter getParameter(int n) {
11+
toGenerated(result) = g.getParameters().getChild(n)
12+
}
13+
14+
final override Stmt getStmt(int i) { toGenerated(result) = g.getChild(i) }
15+
}
16+
517
/**
618
* A synthesized block, such as the block synthesized from the body of
719
* a `for` loop.
820
*/
9-
class SynthBlock extends Block, TBlockSynth {
10-
SynthBlock() { this = TBlockSynth(_, _) }
11-
21+
class BraceBlockSynth extends BraceBlock, TBraceBlockSynth {
1222
final override Parameter getParameter(int n) { synthChild(this, n, result) }
1323

1424
final override Stmt getStmt(int i) {
1525
i >= 0 and
1626
synthChild(this, i + this.getNumberOfParameters(), result)
1727
}
18-
19-
final override string toString() { result = "{ ... }" }
2028
}

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

Lines changed: 64 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ private class TModuleLike = TToplevel or TModuleDeclaration or TClassDeclaration
1919

2020
private class TScopeReal = TMethodBase or TModuleLike or TDoBlock or TLambda or TBraceBlock;
2121

22-
private class TScopeSynth = TBlockSynth;
23-
2422
module Scope {
2523
class TypeRange = Callable::TypeRange or ModuleBase::TypeRange or @ruby_end_block;
2624

@@ -108,35 +106,74 @@ Ruby::HeredocBody getHereDocBody(Ruby::HeredocBeginning g) {
108106
)
109107
}
110108

109+
private Ruby::AstNode specialParentOf(Ruby::AstNode n) {
110+
n =
111+
[
112+
result.(Ruby::Module).getName(), result.(Ruby::Class).getName(),
113+
result.(Ruby::Class).getSuperclass(), result.(Ruby::SingletonClass).getValue(),
114+
result.(Ruby::Method).getName(), result.(Ruby::SingletonMethod).getName(),
115+
result.(Ruby::SingletonMethod).getObject()
116+
]
117+
}
118+
111119
private Ruby::AstNode parentOf(Ruby::AstNode n) {
112120
n = getHereDocBody(result)
113121
or
114-
exists(Ruby::AstNode parent | parent = n.getParent() |
115-
if
116-
n =
117-
[
118-
parent.(Ruby::Module).getName(), parent.(Ruby::Class).getName(),
119-
parent.(Ruby::Class).getSuperclass(), parent.(Ruby::SingletonClass).getValue(),
120-
parent.(Ruby::Method).getName(), parent.(Ruby::SingletonMethod).getName(),
121-
parent.(Ruby::SingletonMethod).getObject()
122-
]
123-
then result = parent.getParent()
124-
else result = parent
125-
)
122+
result = specialParentOf(n).getParent()
123+
or
124+
not exists(specialParentOf(n)) and
125+
result = n.getParent()
126126
}
127127

128-
/** Gets the enclosing scope of a node */
129-
cached
130-
Scope::Range scopeOf(Ruby::AstNode n) {
131-
exists(Ruby::AstNode p | p = parentOf(n) |
132-
p = result
128+
private AstNode specialParentOfInclSynth(AstNode n) {
129+
n =
130+
[
131+
result.(Namespace).getScopeExpr(), result.(ClassDeclaration).getSuperclassExpr(),
132+
result.(SingletonMethod).getObject()
133+
]
134+
}
135+
136+
private AstNode parentOfInclSynth(AstNode n) {
137+
(
138+
result = specialParentOfInclSynth(n).getParent()
133139
or
134-
not p instanceof Scope::Range and result = scopeOf(p)
135-
)
140+
not exists(specialParentOfInclSynth(n)) and
141+
result = n.getParent()
142+
) and
143+
(synthChild(_, _, n) implies synthChild(result, _, n))
144+
}
145+
146+
cached
147+
private module Cached {
148+
/** Gets the enclosing scope of a node */
149+
cached
150+
Scope::Range scopeOf(Ruby::AstNode n) {
151+
exists(Ruby::AstNode p | p = parentOf(n) |
152+
p = result
153+
or
154+
not p instanceof Scope::Range and result = scopeOf(p)
155+
)
156+
}
157+
158+
/**
159+
* Gets the enclosing scope of a node. Unlike `scopeOf`, this predicate
160+
* operates on the external AST API, and therefore takes synthesized nodes
161+
* and synthesized scopes into account.
162+
*/
163+
cached
164+
Scope scopeOfInclSynth(AstNode n) {
165+
exists(AstNode p | p = parentOfInclSynth(n) |
166+
p = result
167+
or
168+
not p instanceof Scope and result = scopeOfInclSynth(p)
169+
)
170+
}
136171
}
137172

173+
import Cached
174+
138175
abstract class ScopeImpl extends AstNode, TScopeType {
139-
abstract Scope getOuterScopeImpl();
176+
final Scope getOuterScopeImpl() { result = scopeOfInclSynth(this) }
140177

141178
abstract Variable getAVariableImpl();
142179

@@ -151,8 +188,6 @@ private class ScopeRealImpl extends ScopeImpl, TScopeReal {
151188

152189
ScopeRealImpl() { range = toGenerated(this) }
153190

154-
override Scope getOuterScopeImpl() { toGenerated(result) = range.getOuterScope() }
155-
156191
override Variable getAVariableImpl() { result.getDeclaringScope() = this }
157192
}
158193

@@ -161,17 +196,15 @@ private class ScopeRealImpl extends ScopeImpl, TScopeReal {
161196
// in practice there is not a real nested scope created, so variables that
162197
// may appear to be local to the loop body (e.g. the iteration variable) are
163198
// 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) }
199+
private class ScopeSynthImpl extends ScopeImpl, TBraceBlockSynth {
200+
ScopeSynthImpl() { this = TBraceBlockSynth(_, _) }
168201

169202
override Variable getAVariableImpl() {
170203
// Synthesized variables introduced as parameters to this scope
171204
// 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))
205+
exists(SimpleParameterSynthImpl p |
206+
p = TSimpleParameterSynth(this, _) and
207+
p.getVariableImpl() = result
175208
)
176209
}
177210
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ newtype SynthKind =
1515
BitwiseAndExprKind() or
1616
BitwiseOrExprKind() or
1717
BitwiseXorExprKind() or
18-
BlockKind() or
18+
BraceBlockKind() or
1919
ClassVariableAccessKind(ClassVariable v) or
2020
DivExprKind() or
2121
ExponentExprKind() or
@@ -834,6 +834,7 @@ private module ArrayLiteralDesugar {
834834
* scoped to the synthesized block.
835835
*/
836836
private module ForLoopDesugar {
837+
pragma[nomagic]
837838
private predicate forLoopSynthesis(AstNode parent, int i, Child child) {
838839
exists(ForExpr for |
839840
// each call
@@ -849,9 +850,9 @@ private module ForLoopDesugar {
849850
or
850851
parent = eachCall and
851852
i = -2 and
852-
child = SynthChild(BlockKind())
853+
child = SynthChild(BraceBlockKind())
853854
or
854-
exists(Block block | block = TBlockSynth(eachCall, -2) |
855+
exists(Block block | block = TBraceBlockSynth(eachCall, -2) |
855856
// block params
856857
parent = block and
857858
i = 0 and

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ private module Cached {
327327

328328
cached
329329
predicate isCapturedAccess(LocalVariableAccess access) {
330-
toGenerated(access.getVariable().getDeclaringScope()) != scopeOf(toGenerated(access))
330+
access.getVariable().getDeclaringScope() != access.getCfgScope()
331331
}
332332

333333
cached
@@ -524,7 +524,7 @@ private class LocalVariableAccessReal extends LocalVariableAccessImpl, TLocalVar
524524
final override string toString() { result = g.getValue() }
525525
}
526526

527-
private class LocalVariableAccessSynth extends LocalVariableAccessImpl, TLocalVariableAccessSynth {
527+
class LocalVariableAccessSynth extends LocalVariableAccessImpl, TLocalVariableAccessSynth {
528528
private LocalVariable v;
529529

530530
LocalVariableAccessSynth() { this = TLocalVariableAccessSynth(_, _, v) }

ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImpl.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1050,7 +1050,7 @@ private module Cached {
10501050
cached
10511051
CfgScope getCfgScopeImpl(AstNode n) {
10521052
forceCachingInSameStage() and
1053-
result = parent*(ASTInternal::fromGenerated(scopeOf(ASTInternal::toGeneratedInclSynth(n))))
1053+
result = parent*(scopeOfInclSynth(n))
10541054
}
10551055

10561056
cached

0 commit comments

Comments
 (0)