diff --git a/ql/src/codeql_ruby/ast/Call.qll b/ql/src/codeql_ruby/ast/Call.qll index 522b19aa8e9..5db8689822a 100644 --- a/ql/src/codeql_ruby/ast/Call.qll +++ b/ql/src/codeql_ruby/ast/Call.qll @@ -7,6 +7,8 @@ private import internal.Call class Call extends Expr { override Call::Range range; + Call() { range.isNormal() } + override string getAPrimaryQlClass() { result = "Call" } /** @@ -93,6 +95,19 @@ class MethodCall extends Call { final Block getBlock() { result = range.getBlock() } } +/** + * A call to a setter method. + * ```rb + * self.foo = 10 + * a[0] = 10 + * ``` + */ +class SetterMethodCall extends LhsExpr { + final override SetterMethodCall::Range range; + + final override string getAPrimaryQlClass() { result = "SetterMethodCall" } +} + /** * An element reference; a call to the `[]` method. * ```rb diff --git a/ql/src/codeql_ruby/ast/internal/Call.qll b/ql/src/codeql_ruby/ast/internal/Call.qll index dc95bdaeea9..5b88373c169 100644 --- a/ql/src/codeql_ruby/ast/internal/Call.qll +++ b/ql/src/codeql_ruby/ast/internal/Call.qll @@ -1,6 +1,7 @@ private import codeql_ruby.AST private import codeql_ruby.ast.internal.AST private import codeql_ruby.ast.internal.Expr +private import codeql_ruby.ast.internal.Pattern private import codeql_ruby.ast.internal.TreeSitter private import codeql_ruby.ast.internal.Variable @@ -8,6 +9,12 @@ module Call { abstract class Range extends Expr::Range { abstract Expr getArgument(int n); + final predicate isSetter() { this instanceof LhsExpr } + + final predicate isNormal() { + not isSetter() or generated.getParent() instanceof AssignOperation + } + override predicate child(string label, AstNode::Range child) { label = "getArgument" and child = getArgument(_) } @@ -111,6 +118,30 @@ module ElementReference { } } +module SetterMethodCall { + class Range extends LhsExpr::Range { + private MethodCall::Range range; + + Range() { this = range } + + final Expr getReceiver() { result = range.getReceiver() } + + final Expr getArgument(int n) { result = range.getArgument(n) } + + final string getMethodName() { result = range.getMethodName() } + + final override string toString() { result = range.toString() } + + final override predicate child(string label, AstNode::Range child) { + super.child(label, child) + or + label = "getReceiver" and child = getReceiver() + or + label = "getArgument" and child = getArgument(_) + } + } +} + module YieldCall { class Range extends Call::Range, @yield { final override Generated::Yield generated; diff --git a/ql/test/library-tests/ast/calls/calls.expected b/ql/test/library-tests/ast/calls/calls.expected index 48d1a4389cd..1717de7dd8e 100644 --- a/ql/test/library-tests/ast/calls/calls.expected +++ b/ql/test/library-tests/ast/calls/calls.expected @@ -77,6 +77,9 @@ callsWithNoReceiverArgumentsOrBlock | calls.rb:305:5:305:9 | call to super | super | | calls.rb:310:1:310:3 | call to foo | foo | | calls.rb:311:1:311:3 | call to foo | foo | +| calls.rb:315:1:315:3 | call to foo | foo | +| calls.rb:316:22:316:24 | call to foo | foo | +| calls.rb:317:5:317:7 | call to foo | foo | callsWithArguments | calls.rb:14:1:14:11 | call to foo | foo | 0 | calls.rb:14:5:14:5 | 0 | | calls.rb:14:1:14:11 | call to foo | foo | 1 | calls.rb:14:8:14:8 | 1 | @@ -184,6 +187,7 @@ callsWithReceiver | calls.rb:305:5:305:15 | call to super | calls.rb:305:5:305:9 | call to super | | calls.rb:310:1:310:6 | call to call | calls.rb:310:1:310:3 | call to foo | | calls.rb:311:1:311:7 | call to call | calls.rb:311:1:311:3 | call to foo | +| calls.rb:318:1:318:10 | call to count | calls.rb:318:1:318:4 | self | callsWithBlock | calls.rb:17:1:17:17 | call to foo | calls.rb:17:5:17:17 | { ... } | | calls.rb:20:1:22:3 | call to foo | calls.rb:20:5:22:3 | do ... end | @@ -221,3 +225,11 @@ superCallsWithBlock | calls.rb:291:5:291:26 | call to super | calls.rb:291:11:291:26 | do ... end | | calls.rb:292:5:292:30 | call to super | calls.rb:292:16:292:30 | { ... } | | calls.rb:293:5:293:33 | call to super | calls.rb:293:16:293:33 | do ... end | +setterCalls +| calls.rb:314:1:314:8 | call to foo | +| calls.rb:315:1:315:6 | ...[...] | +| calls.rb:316:1:316:8 | call to foo | +| calls.rb:316:12:316:19 | call to bar | +| calls.rb:316:22:316:27 | ...[...] | +| calls.rb:317:5:317:10 | ...[...] | +| calls.rb:318:1:318:10 | call to count | diff --git a/ql/test/library-tests/ast/calls/calls.ql b/ql/test/library-tests/ast/calls/calls.ql index fa771eb3e32..32b1183317f 100644 --- a/ql/test/library-tests/ast/calls/calls.ql +++ b/ql/test/library-tests/ast/calls/calls.ql @@ -30,3 +30,5 @@ 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() } + +query predicate setterCalls(SetterMethodCall c) { any() } diff --git a/ql/test/library-tests/ast/calls/calls.rb b/ql/test/library-tests/ast/calls/calls.rb index 9a49cc82059..028f0659795 100644 --- a/ql/test/library-tests/ast/calls/calls.rb +++ b/ql/test/library-tests/ast/calls/calls.rb @@ -310,3 +310,9 @@ end foo.() foo.(1) +# setter calls +self.foo = 10 +foo[0] = 10 +self.foo, *self.bar, foo[4] = [1, 2, 3, 4] +a, *foo[5] = [1, 2, 3] +self.count += 1