diff --git a/ql/src/codeql_ruby/ast/Call.qll b/ql/src/codeql_ruby/ast/Call.qll index 20f30c0e299..4f9248a0ecb 100644 --- a/ql/src/codeql_ruby/ast/Call.qll +++ b/ql/src/codeql_ruby/ast/Call.qll @@ -108,6 +108,22 @@ class YieldCall extends Call, @yield { final override string getAPrimaryQlClass() { result = "YieldCall" } } +/** + * A call to `super`. + * ```rb + * class Foo < Bar + * def baz + * super + * end + * end + * ``` + */ +class SuperCall extends Call { + final override SuperCall::Range range; + + final override string getAPrimaryQlClass() { result = "SuperCall" } +} + /** * A block argument in a method call. * ```rb diff --git a/ql/src/codeql_ruby/ast/internal/Call.qll b/ql/src/codeql_ruby/ast/internal/Call.qll index d15f6dbe83c..b6d47ac9be8 100644 --- a/ql/src/codeql_ruby/ast/internal/Call.qll +++ b/ql/src/codeql_ruby/ast/internal/Call.qll @@ -66,6 +66,47 @@ module YieldCall { } } +module SuperCall { + abstract class Range extends Call::Range { } + + private class SuperTokenCallRange extends SuperCall::Range, @token_super { + final override Generated::Super generated; + + SuperTokenCallRange() { vcall(this) and not access(this, _) } + + final override Expr getReceiver() { none() } + + final override string getMethodName() { result = generated.getValue() } + + final override ScopeResolution getMethodScopeResolution() { none() } + + final override Expr getArgument(int n) { none() } + + final override Block getBlock() { none() } + } + + private class RegularSuperCallRange extends SuperCall::Range, @call { + final override Generated::Call generated; + + RegularSuperCallRange() { + generated = this and + generated.getMethod() instanceof Generated::Super + } + + final override Expr getReceiver() { result = generated.getReceiver() } + + final override string getMethodName() { + result = generated.getMethod().(Generated::Super).getValue() + } + + final override ScopeResolution getMethodScopeResolution() { none() } + + final override Expr getArgument(int n) { result = generated.getArguments().getChild(n) } + + final override Block getBlock() { result = generated.getBlock() } + } +} + module BlockArgument { class Range extends Expr::Range, @block_argument { final override Generated::BlockArgument generated; diff --git a/ql/src/codeql_ruby/ast/internal/Variable.qll b/ql/src/codeql_ruby/ast/internal/Variable.qll index 15f6086fb08..68f85b890c0 100644 --- a/ql/src/codeql_ruby/ast/internal/Variable.qll +++ b/ql/src/codeql_ruby/ast/internal/Variable.qll @@ -125,6 +125,11 @@ private module Cached { not scope.(CapturingScope).inherits(name, _) } + // Token types that can be accesses/vcalls + private class AccessTokenUnion = @token_identifier or @token_super; + + private class AccessToken extends Generated::Token, AccessTokenUnion { } + /** * Holds if `i` is an `identifier` node occurring in the context where it * should be considered a VCALL. VCALL is the term that MRI/Ripper uses @@ -139,7 +144,7 @@ private module Cached { * ``` */ cached - predicate vcall(Generated::Identifier i) { + predicate vcall(AccessToken i) { i = any(Generated::ArgumentList x).getChild(_) or i = any(Generated::Array x).getChild(_) @@ -266,7 +271,7 @@ private module Cached { } cached - predicate access(Generated::Identifier access, Variable variable) { + predicate access(AccessToken access, Variable variable) { exists(string name | name = access.getValue() | variable = enclosingScope(access).getVariable(name) and not strictlyBefore(access.getLocation(), variable.getLocation()) and diff --git a/ql/test/library-tests/ast/calls/calls.expected b/ql/test/library-tests/ast/calls/calls.expected index 70b0832b26c..68a0637c206 100644 --- a/ql/test/library-tests/ast/calls/calls.expected +++ b/ql/test/library-tests/ast/calls/calls.expected @@ -72,6 +72,8 @@ callsWithNoReceiverArgumentsOrBlock | calls.rb:204:6:204:8 | call to bar | bar | | calls.rb:207:7:207:9 | call to bar | bar | | calls.rb:210:11:210:13 | call to bar | bar | +| calls.rb:217:5:217:9 | call to super | super | +| calls.rb:218:5:218:11 | call to super | super | callsWithScopeResolutionName | calls.rb:5:1:5:10 | call to bar | calls.rb:5:1:5:8 | ...::bar | callsWithArguments @@ -86,6 +88,14 @@ callsWithArguments | calls.rb:204:1:204:9 | call to foo | foo | 0 | calls.rb:204:5:204:8 | *... | | calls.rb:207:1:207:10 | call to foo | foo | 0 | calls.rb:207:5:207:9 | **... | | calls.rb:210:1:210:14 | call to foo | foo | 0 | calls.rb:210:5:210:13 | Pair | +| calls.rb:219:5:219:16 | call to super | super | 0 | calls.rb:219:11:219:16 | blah | +| calls.rb:220:5:220:17 | call to super | super | 0 | calls.rb:220:11:220:11 | 1 | +| calls.rb:220:5:220:17 | call to super | super | 1 | calls.rb:220:14:220:14 | 2 | +| calls.rb:220:5:220:17 | call to super | super | 2 | calls.rb:220:17:220:17 | 3 | +| calls.rb:223:5:223:30 | call to super | super | 0 | calls.rb:223:11:223:11 | 4 | +| calls.rb:223:5:223:30 | call to super | super | 1 | calls.rb:223:14:223:14 | 5 | +| calls.rb:224:5:224:33 | call to super | super | 0 | calls.rb:224:11:224:11 | 6 | +| calls.rb:224:5:224:33 | call to super | super | 1 | calls.rb:224:14:224:14 | 7 | callsWithReceiver | calls.rb:8:1:8:7 | call to bar | calls.rb:8:1:8:3 | 123 | | calls.rb:22:1:24:3 | call to bar | calls.rb:22:1:22:3 | 123 | @@ -96,6 +106,33 @@ callsWithBlock | calls.rb:22:1:24:3 | call to bar | calls.rb:22:16:24:3 | do ... end | | calls.rb:80:1:80:13 | call to foo | calls.rb:80:7:80:13 | { ... } | | calls.rb:83:1:83:16 | call to foo | calls.rb:83:7:83:16 | do ... end | +| calls.rb:221:5:221:23 | call to super | calls.rb:221:11:221:23 | { ... } | +| calls.rb:222:5:222:26 | call to super | calls.rb:222:11:222:26 | do ... end | +| calls.rb:223:5:223:30 | call to super | calls.rb:223:16:223:30 | { ... } | +| calls.rb:224:5:224:33 | call to super | calls.rb:224:16:224:33 | do ... end | yieldCalls | calls.rb:28:3:28:7 | call to yield | | calls.rb:33:3:33:16 | call to yield | +superCalls +| calls.rb:217:5:217:9 | call to super | +| calls.rb:218:5:218:11 | call to super | +| calls.rb:219:5:219:16 | call to super | +| calls.rb:220:5:220:17 | call to super | +| calls.rb:221:5:221:23 | call to super | +| calls.rb:222:5:222:26 | call to super | +| calls.rb:223:5:223:30 | call to super | +| calls.rb:224:5:224:33 | call to super | +superCallsWithArguments +| calls.rb:219:5:219:16 | call to super | 0 | calls.rb:219:11:219:16 | blah | +| calls.rb:220:5:220:17 | call to super | 0 | calls.rb:220:11:220:11 | 1 | +| calls.rb:220:5:220:17 | call to super | 1 | calls.rb:220:14:220:14 | 2 | +| calls.rb:220:5:220:17 | call to super | 2 | calls.rb:220:17:220:17 | 3 | +| calls.rb:223:5:223:30 | call to super | 0 | calls.rb:223:11:223:11 | 4 | +| calls.rb:223:5:223:30 | call to super | 1 | calls.rb:223:14:223:14 | 5 | +| calls.rb:224:5:224:33 | call to super | 0 | calls.rb:224:11:224:11 | 6 | +| calls.rb:224:5:224:33 | call to super | 1 | calls.rb:224:14:224:14 | 7 | +superCallsWithBlock +| calls.rb:221:5:221:23 | call to super | calls.rb:221:11:221:23 | { ... } | +| calls.rb:222:5:222:26 | call to super | calls.rb:222:11:222:26 | do ... end | +| calls.rb:223:5:223:30 | call to super | calls.rb:223:16:223:30 | { ... } | +| calls.rb:224:5:224:33 | call to super | calls.rb:224:16:224:33 | do ... end | diff --git a/ql/test/library-tests/ast/calls/calls.ql b/ql/test/library-tests/ast/calls/calls.ql index 7e29d5f7dd6..bc6c244356e 100644 --- a/ql/test/library-tests/ast/calls/calls.ql +++ b/ql/test/library-tests/ast/calls/calls.ql @@ -22,3 +22,9 @@ query predicate callsWithReceiver(Call c, Expr rcv) { rcv = c.getReceiver() } query predicate callsWithBlock(Call c, Block b) { b = c.getBlock() } query predicate yieldCalls(YieldCall c) { any() } + +query predicate superCalls(SuperCall c) { any() } + +query predicate superCallsWithArguments(SuperCall c, int n, Expr argN) { argN = c.getArgument(n) } + +query predicate superCallsWithBlock(SuperCall c, Block b) { b = c.getBlock() } diff --git a/ql/test/library-tests/ast/calls/calls.rb b/ql/test/library-tests/ast/calls/calls.rb index 7133ee98ff4..e7c1e426435 100644 --- a/ql/test/library-tests/ast/calls/calls.rb +++ b/ql/test/library-tests/ast/calls/calls.rb @@ -207,4 +207,20 @@ foo(*bar) foo(**bar) # the value in a keyword argument -foo(blah: bar) \ No newline at end of file +foo(blah: bar) + +# ------------------------------------------------------------------------------ +# calls to `super` + +class MyClass + def my_method + super + super() + super 'blah' + super 1, 2, 3 + super { |x| x + 1 } + super do |x| x * 2 end + super 4, 5 { |x| x + 100 } + super 6, 7 do |x| x + 200 end + end +end \ No newline at end of file diff --git a/ql/test/library-tests/controlflow/graph/Cfg.expected b/ql/test/library-tests/controlflow/graph/Cfg.expected index d3a1e0e8a41..630d18bcf13 100644 --- a/ql/test/library-tests/controlflow/graph/Cfg.expected +++ b/ql/test/library-tests/controlflow/graph/Cfg.expected @@ -1631,12 +1631,12 @@ cfg.rb: #-----| -> exit print (normal) # 144| puts -#-----| -> super +#-----| -> call to super # 144| call to print #-----| -> call to puts -# 144| super +# 144| call to super #-----| -> print # 144| print