Skip to content

Commit 3e2ca61

Browse files
committed
Ruby: support anonymous block parameters/arguments
1 parent b9258e7 commit 3e2ca61

File tree

9 files changed

+83
-5
lines changed

9 files changed

+83
-5
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,10 @@ class BlockArgument extends Expr, TBlockArgument {
187187
* foo(&bar)
188188
* ```
189189
*/
190-
final Expr getValue() { toGenerated(result) = g.getChild() }
190+
final Expr getValue() {
191+
toGenerated(result) = g.getChild() or
192+
synthChild(this, 0, result)
193+
}
191194

192195
final override string toString() { result = "&..." }
193196

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,19 @@ class BlockParameter extends NamedParameter, TBlockParameter {
148148

149149
BlockParameter() { this = TBlockParameter(g) }
150150

151+
/** Gets the name of this parameter, if any. */
151152
final override string getName() { result = g.getName().getValue() }
152153

153-
final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) }
154+
final override LocalVariable getVariable() {
155+
result = TLocalVariableReal(_, _, g.getName()) or
156+
result = TLocalVariableSynth(this, 0)
157+
}
154158

155-
final override string toString() { result = "&" + this.getName() }
159+
final override string toString() {
160+
result = "&" + this.getName()
161+
or
162+
not exists(this.getName()) and result = "&"
163+
}
156164

157165
final override string getAPrimaryQlClass() { result = "BlockParameter" }
158166
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ class LocalVariable extends Variable, TLocalVariable {
3535
override LocalVariableAccess getAnAccess() { result.getVariable() = this }
3636

3737
/** Gets the access where this local variable is first introduced. */
38-
VariableAccess getDefiningAccess() { result = this.(LocalVariableReal).getDefiningAccessImpl() }
38+
VariableAccess getDefiningAccess() {
39+
result = this.(LocalVariableReal).getDefiningAccessImpl() or
40+
synthChild(any(BlockParameter p | this = p.getVariable()), 0, result)
41+
}
3942

4043
/**
4144
* Holds if this variable is captured. For example in
@@ -117,6 +120,8 @@ class VariableAccess extends Expr instanceof VariableAccessImpl {
117120
this = any(SimpleParameterSynthImpl p).getDefininingAccess()
118121
or
119122
this = any(HashPattern p).getValue(_)
123+
or
124+
synthChild(any(BlockParameter p), 0, this)
120125
}
121126

122127
final override string toString() { result = VariableAccessImpl.super.toString() }

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,3 +996,37 @@ private module ImplicitHashValueSynthesis {
996996
}
997997
}
998998
}
999+
1000+
private module AnonymousBlockParameterSynth {
1001+
private BlockParameter anonymousBlockParameter() {
1002+
exists(Ruby::BlockParameter p | not exists(p.getName()) and toGenerated(result) = p)
1003+
}
1004+
1005+
private BlockArgument anonymousBlockArgument() {
1006+
exists(Ruby::BlockArgument p | not exists(p.getChild()) and toGenerated(result) = p)
1007+
}
1008+
1009+
private class AnonymousBlockParameterSynthesis extends Synthesis {
1010+
final override predicate child(AstNode parent, int i, Child child) {
1011+
i = 0 and
1012+
parent = anonymousBlockParameter() and
1013+
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(parent, 0)))
1014+
}
1015+
1016+
final override predicate localVariable(AstNode n, int i) {
1017+
n = anonymousBlockParameter() and i = 0
1018+
}
1019+
}
1020+
1021+
private class AnonymousBlockArgumentSynthesis extends Synthesis {
1022+
final override predicate child(AstNode parent, int i, Child child) {
1023+
i = 0 and
1024+
parent = anonymousBlockArgument() and
1025+
exists(BlockParameter param |
1026+
param = anonymousBlockParameter() and
1027+
scopeOf(toGenerated(parent)).getEnclosingMethod() = scopeOf(toGenerated(param)) and
1028+
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
1029+
)
1030+
}
1031+
}
1032+
}

ruby/ql/test/library-tests/ast/Ast.expected

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,9 @@ calls/calls.rb:
457457
# 267| getArgument: [BlockArgument] &...
458458
# 267| getValue: [MethodCall] call to bar
459459
# 267| getReceiver: [ConstantReadAccess] X
460+
# 268| getStmt: [MethodCall] call to foo
461+
# 268| getReceiver: [Self, SelfVariableAccess] self
462+
# 268| getArgument: [BlockArgument] &...
460463
# 270| getStmt: [MethodCall] call to foo
461464
# 270| getReceiver: [Self, SelfVariableAccess] self
462465
# 270| getArgument: [SplatExpr] * ...
@@ -2694,6 +2697,19 @@ params/params.rb:
26942697
# 77| getParameter: [SimpleParameter] val
26952698
# 77| getDefiningAccess: [LocalVariableAccess] val
26962699
# 77| getParameter: [HashSplatNilParameter] **nil
2700+
# 81| getStmt: [Method] anonymous_block_parameter
2701+
# 81| getParameter: [SimpleParameter] array
2702+
# 81| getDefiningAccess: [LocalVariableAccess] array
2703+
# 81| getParameter: [BlockParameter] &
2704+
# 81| getDefiningAccess: [LocalVariableAccess] __synth__0
2705+
# 82| getStmt: [MethodCall] call to proc
2706+
# 82| getReceiver: [Self, SelfVariableAccess] self
2707+
# 82| getArgument: [BlockArgument] &...
2708+
# 82| getValue: [LocalVariableAccess] __synth__0
2709+
# 83| getStmt: [MethodCall] call to each
2710+
# 83| getReceiver: [LocalVariableAccess] array
2711+
# 83| getArgument: [BlockArgument] &...
2712+
# 83| getValue: [LocalVariableAccess] __synth__0
26972713
erb/template.html.erb:
26982714
# 19| [Toplevel] template.html.erb
26992715
# 19| getStmt: [StringLiteral] "hello world"

ruby/ql/test/library-tests/ast/calls/calls.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ callsWithArguments
2626
| calls.rb:249:1:249:32 | call to [] | [] | 1 | calls.rb:249:15:249:30 | Pair |
2727
| calls.rb:266:1:266:9 | call to foo | foo | 0 | calls.rb:266:5:266:8 | &... |
2828
| calls.rb:267:1:267:12 | call to foo | foo | 0 | calls.rb:267:5:267:11 | &... |
29+
| calls.rb:268:1:268:6 | call to foo | foo | 0 | calls.rb:268:5:268:5 | &... |
2930
| calls.rb:270:1:270:9 | call to foo | foo | 0 | calls.rb:270:5:270:8 | * ... |
3031
| calls.rb:271:1:271:12 | call to foo | foo | 0 | calls.rb:271:5:271:11 | * ... |
3132
| calls.rb:274:1:274:10 | call to foo | foo | 0 | calls.rb:274:5:274:9 | ** ... |
@@ -267,6 +268,7 @@ callsWithReceiver
267268
| calls.rb:266:6:266:8 | call to bar | calls.rb:266:6:266:8 | self |
268269
| calls.rb:267:1:267:12 | call to foo | calls.rb:267:1:267:12 | self |
269270
| calls.rb:267:6:267:11 | call to bar | calls.rb:267:6:267:6 | X |
271+
| calls.rb:268:1:268:6 | call to foo | calls.rb:268:1:268:6 | self |
270272
| calls.rb:270:1:270:9 | call to foo | calls.rb:270:1:270:9 | self |
271273
| calls.rb:270:5:270:8 | * ... | calls.rb:270:6:270:8 | call to bar |
272274
| calls.rb:270:6:270:8 | call to bar | calls.rb:270:6:270:8 | self |

ruby/ql/test/library-tests/ast/calls/calls.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ module SomeModule
265265
# block argument
266266
foo(&bar)
267267
foo(&X::bar)
268-
268+
foo(&)
269269
# splat argument
270270
foo(*bar)
271271
foo(*X::bar)

ruby/ql/test/library-tests/ast/params/params.expected

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ idParams
3535
| params.rb:70:48:70:48 | c | c |
3636
| params.rb:73:27:73:32 | wibble | wibble |
3737
| params.rb:77:16:77:18 | val | val |
38+
| params.rb:81:31:81:35 | array | array |
3839
blockParams
3940
| params.rb:46:28:46:33 | &block | block |
4041
| params.rb:62:29:62:34 | &block | block |
@@ -87,6 +88,8 @@ paramsInMethods
8788
| params.rb:62:1:64:3 | use_block_with_optional | 0 | params.rb:62:29:62:34 | &block | BlockParameter |
8889
| params.rb:73:1:74:3 | method_with_nil_splat | 0 | params.rb:73:27:73:32 | wibble | SimpleParameter |
8990
| params.rb:73:1:74:3 | method_with_nil_splat | 1 | params.rb:73:35:73:39 | **nil | HashSplatNilParameter |
91+
| params.rb:81:1:84:3 | anonymous_block_parameter | 0 | params.rb:81:31:81:35 | array | SimpleParameter |
92+
| params.rb:81:1:84:3 | anonymous_block_parameter | 1 | params.rb:81:38:81:38 | & | BlockParameter |
9093
paramsInBlocks
9194
| params.rb:9:11:11:3 | do ... end | 0 | params.rb:9:15:9:17 | key | SimpleParameter |
9295
| params.rb:9:11:11:3 | do ... end | 1 | params.rb:9:20:9:24 | value | SimpleParameter |
@@ -157,3 +160,5 @@ params
157160
| params.rb:73:35:73:39 | **nil | 1 | HashSplatNilParameter |
158161
| params.rb:77:16:77:18 | val | 0 | SimpleParameter |
159162
| params.rb:77:21:77:25 | **nil | 1 | HashSplatNilParameter |
163+
| params.rb:81:31:81:35 | array | 0 | SimpleParameter |
164+
| params.rb:81:38:81:38 | & | 1 | BlockParameter |

ruby/ql/test/library-tests/ast/params/params.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,8 @@ def method_with_nil_splat(wibble, **nil)
7777
array.each do |val, **nil|
7878
end
7979

80+
# Anonymous block parameter
81+
def anonymous_block_parameter(array, &)
82+
proc(&)
83+
array.each(&)
84+
end

0 commit comments

Comments
 (0)