Skip to content

Commit 062f7fe

Browse files
authored
Merge pull request #7340 from github/hmac/private-methods
Ruby: handle private module methods
2 parents 15caaa7 + a327112 commit 062f7fe

File tree

8 files changed

+207
-99
lines changed

8 files changed

+207
-99
lines changed

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

Lines changed: 86 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -35,36 +35,59 @@ class MethodBase extends Callable, BodyStmt, Scope, TMethodBase {
3535
or
3636
result = BodyStmt.super.getAChild(pred)
3737
}
38-
}
3938

40-
/** A call to `private`. */
41-
private class Private extends MethodCall {
42-
Private() { this.getMethodName() = "private" }
39+
/** Holds if this method is private. */
40+
predicate isPrivate() { none() }
41+
}
4342

44-
/** Gets the method that this `private` call applies to, if any */
45-
Expr getMethod() { result = this.getArgument(0) }
43+
/**
44+
* A method call which modifies another method in some way.
45+
* For example, `private :foo` makes the method `foo` private.
46+
*/
47+
private class MethodModifier extends MethodCall {
48+
/** Gets the name of the method that this call applies to. */
49+
Expr getMethodArgument() { result = this.getArgument(0) }
4650

47-
/**
48-
* Holds if this `private` call happens inside `c`, and refers to a
49-
* method named `name`.
50-
*/
51+
/** Holds if this call modifies a method with name `name` in namespace `n`. */
5152
pragma[noinline]
52-
predicate isRef(ClassDeclaration c, string name) {
53-
this = c.getAStmt() and
54-
name = this.getMethod().(SymbolLiteral).getValueText()
53+
predicate modifiesMethod(Namespace n, string name) {
54+
this = n.getAStmt() and
55+
[
56+
this.getMethodArgument().(StringlikeLiteral).getValueText(),
57+
this.getMethodArgument().(MethodBase).getName()
58+
] = name
5559
}
60+
}
5661

57-
/**
58-
* Holds if this `private` call happens at position `i` inside `c`,
59-
* and the call has no arguments.
60-
*/
61-
pragma[noinline]
62-
predicate hasNoArg(ClassDeclaration c, int i) {
63-
this = c.getStmt(i) and
64-
not exists(this.getMethod())
62+
/** A call to `private` or `private_class_method`. */
63+
private class Private extends MethodModifier {
64+
private Namespace namespace;
65+
private int position;
66+
67+
Private() { this.getMethodName() = "private" and namespace.getStmt(position) = this }
68+
69+
override predicate modifiesMethod(Namespace n, string name) {
70+
n = namespace and
71+
(
72+
// def foo
73+
// ...
74+
// private :foo
75+
super.modifiesMethod(n, name)
76+
or
77+
// private
78+
// ...
79+
// def foo
80+
not exists(this.getMethodArgument()) and
81+
exists(MethodBase m, int i | n.getStmt(i) = m and m.getName() = name and i > position)
82+
)
6583
}
6684
}
6785

86+
/** A call to `private_class_method`. */
87+
private class PrivateClassMethod extends MethodModifier {
88+
PrivateClassMethod() { this.getMethodName() = "private_class_method" }
89+
}
90+
6891
/** A normal method. */
6992
class Method extends MethodBase, TMethod {
7093
private Ruby::Method g;
@@ -90,12 +113,6 @@ class Method extends MethodBase, TMethod {
90113
*/
91114
final predicate isSetter() { g.getName() instanceof Ruby::Setter }
92115

93-
pragma[noinline]
94-
private predicate isDeclaredIn(ClassDeclaration c, string name) {
95-
this = c.getAStmt() and
96-
name = this.getName()
97-
}
98-
99116
/**
100117
* Holds if this method is private. All methods with the name prefix
101118
* `private` are private below:
@@ -122,18 +139,10 @@ class Method extends MethodBase, TMethod {
122139
* end
123140
* ```
124141
*/
125-
predicate isPrivate() {
126-
this = any(Private p).getMethod()
127-
or
128-
exists(ClassDeclaration c, Private p, string name |
129-
this.isDeclaredIn(c, name) and
130-
p.isRef(c, name)
131-
)
132-
or
133-
exists(ClassDeclaration c, Private p, int i, int j |
134-
p.hasNoArg(c, i) and
135-
this = c.getStmt(j) and
136-
j > i
142+
override predicate isPrivate() {
143+
exists(Namespace n, string name |
144+
any(Private p).modifiesMethod(n, name) and
145+
isDeclaredIn(this, n, name)
137146
)
138147
or
139148
// Top-level methods are private members of the Object class
@@ -147,6 +156,14 @@ class Method extends MethodBase, TMethod {
147156
final override string toString() { result = this.getName() }
148157
}
149158

159+
/**
160+
* Holds if the method `m` has name `name` and is declared in namespace `n`.
161+
*/
162+
pragma[noinline]
163+
private predicate isDeclaredIn(MethodBase m, Namespace n, string name) {
164+
n = m.getEnclosingModule() and name = m.getName()
165+
}
166+
150167
/** A singleton method. */
151168
class SingletonMethod extends MethodBase, TSingletonMethod {
152169
private Ruby::SingletonMethod g;
@@ -175,6 +192,36 @@ class SingletonMethod extends MethodBase, TSingletonMethod {
175192
or
176193
pred = "getObject" and result = this.getObject()
177194
}
195+
196+
/**
197+
* Holds if this method is private. All methods with the name prefix
198+
* `private` are private below:
199+
*
200+
* ```rb
201+
* class C
202+
* private_class_method def self.private1
203+
* end
204+
*
205+
* def self.public
206+
* end
207+
*
208+
* def self.private2
209+
* end
210+
* private_class_method :private2
211+
*
212+
* private # this has no effect on singleton methods
213+
*
214+
* def self.public2
215+
* end
216+
* end
217+
* ```
218+
*/
219+
override predicate isPrivate() {
220+
exists(Namespace n, string name |
221+
any(PrivateClassMethod p).modifiesMethod(n, name) and
222+
isDeclaredIn(this, n, name)
223+
)
224+
}
178225
}
179226

180227
/**

ruby/ql/test/library-tests/modules/ancestors.expected

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,10 @@ calls.rb:
4949

5050
# 15| M
5151

52-
private.rb:
53-
# 1| C
52+
# 29| C
5453
#-----| super -> Object
5554
#-----| include -> M
5655

57-
calls.rb:
5856
# 51| D
5957
#-----| super -> C
6058

@@ -110,6 +108,13 @@ modules.rb:
110108

111109
# 115| XX
112110

111+
private.rb:
112+
# 1| E
113+
#-----| super -> Object
114+
115+
# 42| F
116+
117+
modules.rb:
113118
# 5| Foo::Bar
114119

115120
# 19| Foo::ClassInFoo

ruby/ql/test/library-tests/modules/callgraph.expected

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,16 @@ getTarget
7878
| private.rb:2:3:3:5 | call to private | calls.rb:94:5:94:20 | private |
7979
| private.rb:10:3:10:19 | call to private | calls.rb:94:5:94:20 | private |
8080
| private.rb:12:3:12:9 | call to private | calls.rb:94:5:94:20 | private |
81-
| private.rb:24:1:24:5 | call to new | calls.rb:99:5:99:16 | new |
82-
| private.rb:25:1:25:5 | call to new | calls.rb:99:5:99:16 | new |
83-
| private.rb:26:1:26:5 | call to new | calls.rb:99:5:99:16 | new |
84-
| private.rb:27:1:27:5 | call to new | calls.rb:99:5:99:16 | new |
85-
| private.rb:28:1:28:5 | call to new | calls.rb:99:5:99:16 | new |
86-
| private.rb:28:1:28:12 | call to public | private.rb:5:3:6:5 | public |
87-
| private.rb:30:1:30:15 | call to private_on_main | private.rb:21:1:22:3 | private_on_main |
81+
| private.rb:34:1:34:5 | call to new | calls.rb:99:5:99:16 | new |
82+
| private.rb:35:1:35:5 | call to new | calls.rb:99:5:99:16 | new |
83+
| private.rb:36:1:36:5 | call to new | calls.rb:99:5:99:16 | new |
84+
| private.rb:37:1:37:5 | call to new | calls.rb:99:5:99:16 | new |
85+
| private.rb:38:1:38:5 | call to new | calls.rb:99:5:99:16 | new |
86+
| private.rb:38:1:38:12 | call to public | private.rb:5:3:6:5 | public |
87+
| private.rb:40:1:40:15 | call to private_on_main | private.rb:31:1:32:3 | private_on_main |
88+
| private.rb:43:3:44:5 | call to private | calls.rb:94:5:94:20 | private |
89+
| private.rb:51:3:51:19 | call to private | calls.rb:94:5:94:20 | private |
90+
| private.rb:53:3:53:9 | call to private | calls.rb:94:5:94:20 | private |
8891
unresolvedCall
8992
| calls.rb:19:5:19:14 | call to instance_m |
9093
| calls.rb:20:5:20:19 | call to instance_m |
@@ -110,10 +113,12 @@ unresolvedCall
110113
| hello.rb:20:16:20:26 | ... + ... |
111114
| hello.rb:20:16:20:34 | ... + ... |
112115
| hello.rb:20:16:20:40 | ... + ... |
113-
| private.rb:24:1:24:14 | call to private1 |
114-
| private.rb:25:1:25:14 | call to private2 |
115-
| private.rb:26:1:26:14 | call to private3 |
116-
| private.rb:27:1:27:14 | call to private4 |
116+
| private.rb:23:3:24:5 | call to private_class_method |
117+
| private.rb:28:3:28:32 | call to private_class_method |
118+
| private.rb:34:1:34:14 | call to private1 |
119+
| private.rb:35:1:35:14 | call to private2 |
120+
| private.rb:36:1:36:14 | call to private3 |
121+
| private.rb:37:1:37:14 | call to private4 |
117122
privateMethod
118123
| calls.rb:1:1:3:3 | foo |
119124
| calls.rb:62:1:65:3 | optional_arg |
@@ -126,4 +131,10 @@ privateMethod
126131
| private.rb:8:3:9:5 | private2 |
127132
| private.rb:14:3:15:5 | private3 |
128133
| private.rb:17:3:18:5 | private4 |
129-
| private.rb:21:1:22:3 | private_on_main |
134+
| private.rb:23:24:24:5 | private5 |
135+
| private.rb:26:3:27:5 | private6 |
136+
| private.rb:31:1:32:3 | private_on_main |
137+
| private.rb:43:11:44:5 | private1 |
138+
| private.rb:49:3:50:5 | private2 |
139+
| private.rb:55:3:56:5 | private3 |
140+
| private.rb:58:3:59:5 | private4 |

ruby/ql/test/library-tests/modules/callgraph.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ query Callable getTarget(Call call) { result = call.getATarget() }
44

55
query predicate unresolvedCall(Call call) { not exists(call.getATarget()) }
66

7-
query predicate privateMethod(Method m) { m.isPrivate() }
7+
query predicate privateMethod(MethodBase m) { m.isPrivate() }

ruby/ql/test/library-tests/modules/methods.expected

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
getMethod
22
| calls.rb:15:1:24:3 | M | instance_m | calls.rb:16:5:16:23 | instance_m |
3+
| calls.rb:29:1:44:3 | C | baz | calls.rb:37:5:43:7 | baz |
34
| calls.rb:51:1:55:3 | D | baz | calls.rb:52:5:54:7 | baz |
45
| calls.rb:77:1:80:3 | Integer | abs | calls.rb:79:5:79:16 | abs |
56
| calls.rb:77:1:80:3 | Integer | bit_length | calls.rb:78:5:78:23 | bit_length |
@@ -17,7 +18,7 @@ getMethod
1718
| calls.rb:97:1:100:3 | Object | new | calls.rb:99:5:99:16 | new |
1819
| calls.rb:97:1:100:3 | Object | optional_arg | calls.rb:62:1:65:3 | optional_arg |
1920
| calls.rb:97:1:100:3 | Object | private_on_main | calls.rb:164:1:165:3 | private_on_main |
20-
| calls.rb:97:1:100:3 | Object | private_on_main | private.rb:21:1:22:3 | private_on_main |
21+
| calls.rb:97:1:100:3 | Object | private_on_main | private.rb:31:1:32:3 | private_on_main |
2122
| calls.rb:102:1:104:3 | Hash | [] | calls.rb:103:5:103:15 | [] |
2223
| calls.rb:106:1:117:3 | Array | [] | calls.rb:107:3:107:13 | [] |
2324
| calls.rb:106:1:117:3 | Array | foreach | calls.rb:110:3:116:5 | foreach |
@@ -35,13 +36,27 @@ getMethod
3536
| modules.rb:5:3:14:5 | Foo::Bar | method_in_foo_bar | modules.rb:9:5:10:7 | method_in_foo_bar |
3637
| modules.rb:37:1:46:3 | Bar | method_a | modules.rb:38:3:39:5 | method_a |
3738
| modules.rb:37:1:46:3 | Bar | method_b | modules.rb:41:3:42:5 | method_b |
38-
| private.rb:1:1:19:3 | C | baz | calls.rb:37:5:43:7 | baz |
39-
| private.rb:1:1:19:3 | C | private2 | private.rb:8:3:9:5 | private2 |
40-
| private.rb:1:1:19:3 | C | private3 | private.rb:14:3:15:5 | private3 |
41-
| private.rb:1:1:19:3 | C | private4 | private.rb:17:3:18:5 | private4 |
42-
| private.rb:1:1:19:3 | C | public | private.rb:5:3:6:5 | public |
39+
| private.rb:1:1:29:3 | E | private2 | private.rb:8:3:9:5 | private2 |
40+
| private.rb:1:1:29:3 | E | private3 | private.rb:14:3:15:5 | private3 |
41+
| private.rb:1:1:29:3 | E | private4 | private.rb:17:3:18:5 | private4 |
42+
| private.rb:1:1:29:3 | E | public | private.rb:5:3:6:5 | public |
43+
| private.rb:42:1:60:3 | F | private2 | private.rb:49:3:50:5 | private2 |
44+
| private.rb:42:1:60:3 | F | private3 | private.rb:55:3:56:5 | private3 |
45+
| private.rb:42:1:60:3 | F | private4 | private.rb:58:3:59:5 | private4 |
46+
| private.rb:42:1:60:3 | F | public | private.rb:46:3:47:5 | public |
4347
lookupMethod
4448
| calls.rb:15:1:24:3 | M | instance_m | calls.rb:16:5:16:23 | instance_m |
49+
| calls.rb:29:1:44:3 | C | baz | calls.rb:37:5:43:7 | baz |
50+
| calls.rb:29:1:44:3 | C | call_block | calls.rb:67:1:69:3 | call_block |
51+
| calls.rb:29:1:44:3 | C | foo | calls.rb:1:1:3:3 | foo |
52+
| calls.rb:29:1:44:3 | C | foo | calls.rb:71:1:75:3 | foo |
53+
| calls.rb:29:1:44:3 | C | funny | calls.rb:119:1:121:3 | funny |
54+
| calls.rb:29:1:44:3 | C | indirect | calls.rb:137:1:139:3 | indirect |
55+
| calls.rb:29:1:44:3 | C | instance_m | calls.rb:16:5:16:23 | instance_m |
56+
| calls.rb:29:1:44:3 | C | new | calls.rb:99:5:99:16 | new |
57+
| calls.rb:29:1:44:3 | C | optional_arg | calls.rb:62:1:65:3 | optional_arg |
58+
| calls.rb:29:1:44:3 | C | private_on_main | calls.rb:164:1:165:3 | private_on_main |
59+
| calls.rb:29:1:44:3 | C | puts | calls.rb:87:5:87:17 | puts |
4560
| calls.rb:51:1:55:3 | D | baz | calls.rb:52:5:54:7 | baz |
4661
| calls.rb:51:1:55:3 | D | call_block | calls.rb:67:1:69:3 | call_block |
4762
| calls.rb:51:1:55:3 | D | foo | calls.rb:1:1:3:3 | foo |
@@ -51,11 +66,7 @@ lookupMethod
5166
| calls.rb:51:1:55:3 | D | instance_m | calls.rb:16:5:16:23 | instance_m |
5267
| calls.rb:51:1:55:3 | D | new | calls.rb:99:5:99:16 | new |
5368
| calls.rb:51:1:55:3 | D | optional_arg | calls.rb:62:1:65:3 | optional_arg |
54-
| calls.rb:51:1:55:3 | D | private2 | private.rb:8:3:9:5 | private2 |
55-
| calls.rb:51:1:55:3 | D | private3 | private.rb:14:3:15:5 | private3 |
56-
| calls.rb:51:1:55:3 | D | private4 | private.rb:17:3:18:5 | private4 |
5769
| calls.rb:51:1:55:3 | D | private_on_main | calls.rb:164:1:165:3 | private_on_main |
58-
| calls.rb:51:1:55:3 | D | public | private.rb:5:3:6:5 | public |
5970
| calls.rb:51:1:55:3 | D | puts | calls.rb:87:5:87:17 | puts |
6071
| calls.rb:77:1:80:3 | Integer | abs | calls.rb:79:5:79:16 | abs |
6172
| calls.rb:77:1:80:3 | Integer | bit_length | calls.rb:78:5:78:23 | bit_length |
@@ -93,7 +104,7 @@ lookupMethod
93104
| calls.rb:97:1:100:3 | Object | new | calls.rb:99:5:99:16 | new |
94105
| calls.rb:97:1:100:3 | Object | optional_arg | calls.rb:62:1:65:3 | optional_arg |
95106
| calls.rb:97:1:100:3 | Object | private_on_main | calls.rb:164:1:165:3 | private_on_main |
96-
| calls.rb:97:1:100:3 | Object | private_on_main | private.rb:21:1:22:3 | private_on_main |
107+
| calls.rb:97:1:100:3 | Object | private_on_main | private.rb:31:1:32:3 | private_on_main |
97108
| calls.rb:97:1:100:3 | Object | puts | calls.rb:87:5:87:17 | puts |
98109
| calls.rb:102:1:104:3 | Hash | [] | calls.rb:103:5:103:15 | [] |
99110
| calls.rb:102:1:104:3 | Hash | call_block | calls.rb:67:1:69:3 | call_block |
@@ -205,19 +216,14 @@ lookupMethod
205216
| modules.rb:112:1:113:3 | YY | puts | calls.rb:87:5:87:17 | puts |
206217
| modules.rb:116:7:117:9 | XX::YY | new | calls.rb:99:5:99:16 | new |
207218
| modules.rb:116:7:117:9 | XX::YY | puts | calls.rb:87:5:87:17 | puts |
208-
| private.rb:1:1:19:3 | C | baz | calls.rb:37:5:43:7 | baz |
209-
| private.rb:1:1:19:3 | C | call_block | calls.rb:67:1:69:3 | call_block |
210-
| private.rb:1:1:19:3 | C | foo | calls.rb:1:1:3:3 | foo |
211-
| private.rb:1:1:19:3 | C | foo | calls.rb:71:1:75:3 | foo |
212-
| private.rb:1:1:19:3 | C | funny | calls.rb:119:1:121:3 | funny |
213-
| private.rb:1:1:19:3 | C | indirect | calls.rb:137:1:139:3 | indirect |
214-
| private.rb:1:1:19:3 | C | instance_m | calls.rb:16:5:16:23 | instance_m |
215-
| private.rb:1:1:19:3 | C | new | calls.rb:99:5:99:16 | new |
216-
| private.rb:1:1:19:3 | C | optional_arg | calls.rb:62:1:65:3 | optional_arg |
217-
| private.rb:1:1:19:3 | C | private2 | private.rb:8:3:9:5 | private2 |
218-
| private.rb:1:1:19:3 | C | private3 | private.rb:14:3:15:5 | private3 |
219-
| private.rb:1:1:19:3 | C | private4 | private.rb:17:3:18:5 | private4 |
220-
| private.rb:1:1:19:3 | C | private_on_main | calls.rb:164:1:165:3 | private_on_main |
221-
| private.rb:1:1:19:3 | C | private_on_main | private.rb:21:1:22:3 | private_on_main |
222-
| private.rb:1:1:19:3 | C | public | private.rb:5:3:6:5 | public |
223-
| private.rb:1:1:19:3 | C | puts | calls.rb:87:5:87:17 | puts |
219+
| private.rb:1:1:29:3 | E | new | calls.rb:99:5:99:16 | new |
220+
| private.rb:1:1:29:3 | E | private2 | private.rb:8:3:9:5 | private2 |
221+
| private.rb:1:1:29:3 | E | private3 | private.rb:14:3:15:5 | private3 |
222+
| private.rb:1:1:29:3 | E | private4 | private.rb:17:3:18:5 | private4 |
223+
| private.rb:1:1:29:3 | E | private_on_main | private.rb:31:1:32:3 | private_on_main |
224+
| private.rb:1:1:29:3 | E | public | private.rb:5:3:6:5 | public |
225+
| private.rb:1:1:29:3 | E | puts | calls.rb:87:5:87:17 | puts |
226+
| private.rb:42:1:60:3 | F | private2 | private.rb:49:3:50:5 | private2 |
227+
| private.rb:42:1:60:3 | F | private3 | private.rb:55:3:56:5 | private3 |
228+
| private.rb:42:1:60:3 | F | private4 | private.rb:58:3:59:5 | private4 |
229+
| private.rb:42:1:60:3 | F | public | private.rb:46:3:47:5 | public |

0 commit comments

Comments
 (0)