Skip to content

Commit 9d072a1

Browse files
authored
Merge pull request #7098 from github/ruby/desugar-for-1
Ruby: Desugar `for` loops as calls to `each`
2 parents 21aff99 + a4538de commit 9d072a1

37 files changed

+1096
-681
lines changed

csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowGraph.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ module ControlFlow {
2525
* Only nodes that can be reached from the callable entry point are included in
2626
* the CFG.
2727
*/
28-
class Node extends TNode {
28+
class Node extends TCfgNode {
2929
/** Gets a textual representation of this control flow node. */
3030
string toString() { none() }
3131

csharp/ql/lib/semmle/code/csharp/controlflow/internal/ControlFlowGraphImplShared.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -788,7 +788,7 @@ private module Cached {
788788
* The control flow graph is pruned for unreachable nodes.
789789
*/
790790
cached
791-
newtype TNode =
791+
newtype TCfgNode =
792792
TEntryNode(CfgScope scope) { succEntrySplits(scope, _, _, _) } or
793793
TAnnotatedExitNode(CfgScope scope, boolean normal) {
794794
exists(Reachability::SameSplitsBlock b, SuccessorType t | b.isReachable(_) |
@@ -807,7 +807,7 @@ private module Cached {
807807

808808
/** Gets a successor node of a given flow type, if any. */
809809
cached
810-
TNode getASuccessor(TNode pred, SuccessorType t) {
810+
TCfgNode getASuccessor(TCfgNode pred, SuccessorType t) {
811811
// Callable entry node -> callable body
812812
exists(ControlFlowElement succElement, Splits succSplits, CfgScope scope |
813813
result = TElementNode(succElement, succSplits) and

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/Control.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ class ForExpr extends Loop, TForExpr {
583583
final override string getAPrimaryQlClass() { result = "ForExpr" }
584584

585585
/** Gets the body of this `for` loop. */
586-
final override Stmt getBody() { toGenerated(result) = g.getBody() }
586+
final override StmtSequence getBody() { toGenerated(result) = g.getBody() }
587587

588588
/** Gets the pattern representing the iteration argument. */
589589
final Pattern getPattern() { toGenerated(result) = g.getPattern() }

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

Lines changed: 18 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
private import codeql.ruby.AST
22
private import codeql.ruby.CFG
33
private import internal.AST
4+
private import internal.Expr
45
private import internal.TreeSitter
56

67
/**
@@ -91,90 +92,19 @@ class StmtSequence extends Expr, TStmtSequence {
9192
}
9293
}
9394

94-
private class StmtSequenceSynth extends StmtSequence, TStmtSequenceSynth {
95-
final override Stmt getStmt(int n) { synthChild(this, n, result) }
96-
97-
final override string toString() { result = "..." }
98-
}
99-
100-
private class Then extends StmtSequence, TThen {
101-
private Ruby::Then g;
102-
103-
Then() { this = TThen(g) }
104-
105-
override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
106-
107-
final override string toString() { result = "then ..." }
108-
}
109-
110-
private class Else extends StmtSequence, TElse {
111-
private Ruby::Else g;
112-
113-
Else() { this = TElse(g) }
114-
115-
override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
116-
117-
final override string toString() { result = "else ..." }
118-
}
119-
120-
private class Do extends StmtSequence, TDo {
121-
private Ruby::Do g;
122-
123-
Do() { this = TDo(g) }
124-
125-
override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
126-
127-
final override string toString() { result = "do ..." }
128-
}
129-
130-
private class Ensure extends StmtSequence, TEnsure {
131-
private Ruby::Ensure g;
132-
133-
Ensure() { this = TEnsure(g) }
134-
135-
override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
136-
137-
final override string toString() { result = "ensure ..." }
138-
}
139-
14095
/**
14196
* A sequence of statements representing the body of a method, class, module,
14297
* or do-block. That is, any body that may also include rescue/ensure/else
14398
* statements.
14499
*/
145100
class BodyStmt extends StmtSequence, TBodyStmt {
146-
// Not defined by dispatch, as it should not be exposed
147-
private Ruby::AstNode getChild(int i) {
148-
result = any(Ruby::Method g | this = TMethod(g)).getChild(i)
149-
or
150-
result = any(Ruby::SingletonMethod g | this = TSingletonMethod(g)).getChild(i)
151-
or
152-
exists(Ruby::Lambda g | this = TLambda(g) |
153-
result = g.getBody().(Ruby::DoBlock).getChild(i) or
154-
result = g.getBody().(Ruby::Block).getChild(i)
155-
)
156-
or
157-
result = any(Ruby::DoBlock g | this = TDoBlock(g)).getChild(i)
158-
or
159-
result = any(Ruby::Program g | this = TToplevel(g)).getChild(i) and
160-
not result instanceof Ruby::BeginBlock
161-
or
162-
result = any(Ruby::Class g | this = TClassDeclaration(g)).getChild(i)
163-
or
164-
result = any(Ruby::SingletonClass g | this = TSingletonClass(g)).getChild(i)
165-
or
166-
result = any(Ruby::Module g | this = TModuleDeclaration(g)).getChild(i)
167-
or
168-
result = any(Ruby::Begin g | this = TBeginExpr(g)).getChild(i)
169-
}
170-
171101
final override Stmt getStmt(int n) {
172-
result =
173-
rank[n + 1](AstNode node, int i |
174-
toGenerated(node) = this.getChild(i) and
175-
not node instanceof Else and
176-
not node instanceof RescueClause and
177-
not node instanceof Ensure
102+
toGenerated(result) =
103+
rank[n + 1](Ruby::AstNode node, int i |
104+
node = getBodyStmtChild(this, i) and
105+
not node instanceof Ruby::Else and
106+
not node instanceof Ruby::Rescue and
107+
not node instanceof Ruby::Ensure
178108
|
179109
node order by i
180110
)
@@ -183,17 +113,25 @@ class BodyStmt extends StmtSequence, TBodyStmt {
183113
/** Gets the `n`th rescue clause in this block. */
184114
final RescueClause getRescue(int n) {
185115
result =
186-
rank[n + 1](RescueClause node, int i | toGenerated(node) = this.getChild(i) | node order by i)
116+
rank[n + 1](RescueClause node, int i |
117+
toGenerated(node) = getBodyStmtChild(this, i)
118+
|
119+
node order by i
120+
)
187121
}
188122

189123
/** Gets a rescue clause in this block. */
190124
final RescueClause getARescue() { result = this.getRescue(_) }
191125

192126
/** Gets the `else` clause in this block, if any. */
193-
final StmtSequence getElse() { result = unique(Else s | toGenerated(s) = getChild(_)) }
127+
final StmtSequence getElse() {
128+
result = unique(Else s | toGenerated(s) = getBodyStmtChild(this, _))
129+
}
194130

195131
/** Gets the `ensure` clause in this block, if any. */
196-
final StmtSequence getEnsure() { result = unique(Ensure s | toGenerated(s) = getChild(_)) }
132+
final StmtSequence getEnsure() {
133+
result = unique(Ensure s | toGenerated(s) = getBodyStmtChild(this, _))
134+
}
197135

198136
final predicate hasEnsure() { exists(this.getEnsure()) }
199137

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

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ private import codeql.ruby.AST
22
private import codeql.ruby.controlflow.ControlFlowGraph
33
private import internal.AST
44
private import internal.TreeSitter
5+
private import internal.Method
56

67
/** A callable. */
78
class Callable extends StmtSequence, Expr, Scope, TCallable {
@@ -212,16 +213,6 @@ class DoBlock extends Block, BodyStmt, TDoBlock {
212213
* ```
213214
*/
214215
class BraceBlock extends Block, TBraceBlock {
215-
private Ruby::Block g;
216-
217-
BraceBlock() { this = TBraceBlock(g) }
218-
219-
final override Parameter getParameter(int n) {
220-
toGenerated(result) = g.getParameters().getChild(n)
221-
}
222-
223-
final override Stmt getStmt(int i) { toGenerated(result) = g.getChild(i) }
224-
225216
final override string toString() { result = "{ ... }" }
226217

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

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,11 @@ class NamedParameter extends Parameter, TNamedParameter {
5757
final VariableAccess getAnAccess() { result = this.getVariable().getAnAccess() }
5858

5959
/** Gets the access that defines the underlying local variable. */
60-
final VariableAccess getDefiningAccess() { result = this.getVariable().getDefiningAccess() }
60+
final VariableAccess getDefiningAccess() {
61+
result = this.getVariable().getDefiningAccess()
62+
or
63+
result = this.(SimpleParameterSynthImpl).getDefininingAccess()
64+
}
6165

6266
override AstNode getAChild(string pred) {
6367
result = super.getAChild(pred)
@@ -68,14 +72,12 @@ class NamedParameter extends Parameter, TNamedParameter {
6872
}
6973

7074
/** A simple (normal) parameter. */
71-
class SimpleParameter extends NamedParameter, PatternParameter, VariablePattern, TSimpleParameter {
72-
private Ruby::Identifier g;
73-
74-
SimpleParameter() { this = TSimpleParameter(g) }
75+
class SimpleParameter extends NamedParameter, PatternParameter, VariablePattern, TSimpleParameter instanceof SimpleParameterImpl {
76+
final override string getName() { result = SimpleParameterImpl.super.getNameImpl() }
7577

76-
final override string getName() { result = g.getValue() }
77-
78-
final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g) }
78+
final override LocalVariable getVariable() {
79+
result = SimpleParameterImpl.super.getVariableImpl()
80+
}
7981

8082
final override LocalVariable getAVariable() { result = this.getVariable() }
8183

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ private import internal.AST
44
private import internal.Pattern
55
private import internal.TreeSitter
66
private import internal.Variable
7+
private import internal.Parameter
78

89
/** A pattern. */
910
class Pattern extends AstNode {
@@ -15,6 +16,8 @@ class Pattern extends AstNode {
1516
implicitParameterAssignmentNode(toGenerated(this), _)
1617
or
1718
this = getSynthChild(any(AssignExpr ae), 0)
19+
or
20+
this instanceof SimpleParameterImpl
1821
}
1922

2023
/** Gets a variable used in (or introduced by) this pattern. */

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

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,20 @@ 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+
/**
7+
* A variable scope. This is either a top-level (file), a module, a class,
8+
* or a callable.
9+
*/
10+
class Scope extends AstNode, TScopeType instanceof ScopeImpl {
11+
/** Gets the outer scope, if any. */
12+
Scope getOuterScope() { result = super.getOuterScopeImpl() }
813

9-
Scope() { range = toGenerated(this) }
14+
/** Gets a variable declared in this scope. */
15+
Variable getAVariable() { result = super.getAVariableImpl() }
1016

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-
}
17+
/** Gets the variable named `name` declared in this scope. */
18+
Variable getVariable(string name) { result = super.getVariableImpl(name) }
2219
}
2320

21+
/** A scope in which a `self` variable exists. */
2422
class SelfScope extends Scope, TSelfScopeType { }

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ private import codeql.Locations
55
private import internal.AST
66
private import internal.TreeSitter
77
private import internal.Variable
8+
private import internal.Parameter
89

910
/** A variable declared in a scope. */
1011
class Variable instanceof VariableImpl {
@@ -50,7 +51,7 @@ class LocalVariable extends Variable, TLocalVariable {
5051
*
5152
* `x` is a captured variable, whereas `y` is not.
5253
*/
53-
predicate isCaptured() { this.getAnAccess().isCapturedAccess() }
54+
final predicate isCaptured() { this.getAnAccess().isCapturedAccess() }
5455
}
5556

5657
/** A global variable. */
@@ -110,7 +111,11 @@ class VariableAccess extends Expr instanceof VariableAccessImpl {
110111
* the access to `elements` in the parameter list is an implicit assignment,
111112
* as is the first access to `e`.
112113
*/
113-
predicate isImplicitWrite() { implicitWriteAccess(toGenerated(this)) }
114+
predicate isImplicitWrite() {
115+
implicitWriteAccess(toGenerated(this))
116+
or
117+
this = any(SimpleParameterSynthImpl p).getDefininingAccess()
118+
}
114119

115120
final override string toString() { result = VariableAccessImpl.super.toString() }
116121
}
@@ -147,7 +152,7 @@ class LocalVariableAccess extends VariableAccess instanceof LocalVariableAccessI
147152
* the access to `x` in the first `puts x` is a captured access, while
148153
* the access to `x` in the second `puts x` is not.
149154
*/
150-
predicate isCapturedAccess() { isCapturedAccess(this) }
155+
final predicate isCapturedAccess() { isCapturedAccess(this) }
151156
}
152157

153158
/** An access to a local variable where the value is updated. */
@@ -195,10 +200,4 @@ class SelfVariableAccess extends LocalVariableAccess instanceof SelfVariableAcce
195200
}
196201

197202
/** An access to the `self` variable where the value is read. */
198-
class SelfVariableReadAccess extends SelfVariableAccess, VariableReadAccess {
199-
// We override the definition in `LocalVariableAccess` because it gives the
200-
// wrong result for synthesised `self` variables.
201-
override predicate isCapturedAccess() {
202-
this.getVariable().getDeclaringScope() != this.getCfgScope()
203-
}
204-
}
203+
class SelfVariableReadAccess extends SelfVariableAccess, VariableReadAccess { }

0 commit comments

Comments
 (0)