Skip to content

Commit 0ce0ada

Browse files
authored
Merge pull request #10002 from hmac/hmac/protected-methods
Ruby: Model protected methods
2 parents ffd5886 + 99b2df0 commit 0ce0ada

File tree

9 files changed

+492
-284
lines changed

9 files changed

+492
-284
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* `MethodBase` now has two new predicates related to visibility: `isPublic` and
5+
`isProtected`. These hold, respectively, if the method is public or protected.

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

Lines changed: 135 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -36,56 +36,119 @@ class MethodBase extends Callable, BodyStmt, Scope, TMethodBase {
3636
result = BodyStmt.super.getAChild(pred)
3737
}
3838

39+
/**
40+
* Holds if this method is public.
41+
* Methods are public by default.
42+
*/
43+
predicate isPublic() { this.getVisibility() = "public" }
44+
3945
/** Holds if this method is private. */
40-
predicate isPrivate() { none() }
46+
predicate isPrivate() { this.getVisibility() = "private" }
47+
48+
/** Holds if this method is protected. */
49+
predicate isProtected() { this.getVisibility() = "protected" }
50+
51+
/**
52+
* Gets a string describing the visibility of this method.
53+
* This is either 'public', 'private' or 'protected'.
54+
*/
55+
string getVisibility() {
56+
result = getVisibilityModifier(this).getVisibility()
57+
or
58+
not exists(getVisibilityModifier(this)) and result = "public"
59+
}
4160
}
4261

4362
/**
44-
* A method call which modifies another method in some way.
45-
* For example, `private :foo` makes the method `foo` private.
63+
* Gets the visibility modifier that explicitly sets the visibility of method
64+
* `m`.
65+
*
66+
* Examples:
67+
* ```rb
68+
* def f
69+
* end
70+
* private :f
71+
*
72+
* private def g
73+
* end
74+
* ```
4675
*/
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) }
76+
private VisibilityModifier getExplicitVisibilityModifier(Method m) {
77+
result.getMethodArgument() = m
78+
or
79+
exists(ModuleBase n, string name |
80+
methodIsDeclaredIn(m, n, name) and
81+
modifiesIn(result, n, name)
82+
)
83+
}
5084

51-
/** Holds if this call modifies a method with name `name` in namespace `n`. */
52-
pragma[noinline]
53-
predicate modifiesMethod(Namespace n, string name) {
54-
this = n.getAStmt() and
55-
[
56-
this.getMethodArgument().getConstantValue().getStringlikeValue(),
57-
this.getMethodArgument().(MethodBase).getName()
58-
] = name
59-
}
85+
/**
86+
* Gets the visibility modifier that defines the visibility of method `m`, if
87+
* any.
88+
*/
89+
private VisibilityModifier getVisibilityModifier(MethodBase mb) {
90+
mb =
91+
any(Method m |
92+
result = getExplicitVisibilityModifier(m)
93+
or
94+
not exists(getExplicitVisibilityModifier(m)) and
95+
exists(ModuleBase n, int methodPos | isDeclaredIn(m, n, methodPos) |
96+
// The relevant visibility modifier is the closest call that occurs before
97+
// the definition of `m` (typically this means higher up the file).
98+
result =
99+
max(int modifierPos, VisibilityModifier modifier |
100+
modifier.modifiesAmbientVisibility() and
101+
isDeclaredIn(modifier, n, modifierPos) and
102+
modifierPos < methodPos
103+
|
104+
modifier order by modifierPos
105+
)
106+
)
107+
)
108+
or
109+
mb =
110+
any(SingletonMethod m |
111+
result.getMethodArgument() = m
112+
or
113+
exists(ModuleBase n, string name |
114+
methodIsDeclaredIn(m, n, name) and
115+
modifiesIn(result, n, name)
116+
)
117+
)
60118
}
61119

62-
/** A call to `private` or `private_class_method`. */
63-
private class Private extends MethodModifier {
64-
private Namespace namespace;
65-
private int position;
120+
/**
121+
* A method call that sets the visibility of other methods.
122+
* For example, `private :foo` makes the method `foo` private.
123+
*/
124+
private class VisibilityModifier extends MethodCall {
125+
VisibilityModifier() {
126+
this.getMethodName() =
127+
["public", "private", "protected", "public_class_method", "private_class_method"]
128+
}
66129

67-
Private() { this.getMethodName() = "private" and namespace.getStmt(position) = this }
130+
/** Gets the name of the method that this call applies to. */
131+
Expr getMethodArgument() { result = this.getArgument(0) }
68132

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-
)
133+
/**
134+
* Holds if this modifier changes the "ambient" visibility - i.e. the default
135+
* visibility of any subsequent method definitions.
136+
*/
137+
predicate modifiesAmbientVisibility() {
138+
this.getMethodName() = ["public", "private", "protected"] and
139+
this.getNumberOfArguments() = 0
83140
}
84-
}
85141

86-
/** A call to `private_class_method`. */
87-
private class PrivateClassMethod extends MethodModifier {
88-
PrivateClassMethod() { this.getMethodName() = "private_class_method" }
142+
/** Gets the visibility set by this modifier. */
143+
string getVisibility() {
144+
this.getMethodName() = ["public", "public_class_method"] and result = "public"
145+
or
146+
this.getMethodName() = ["private", "private_class_method"] and
147+
result = "private"
148+
or
149+
this.getMethodName() = "protected" and
150+
result = "protected"
151+
}
89152
}
90153

91154
/** A normal method. */
@@ -139,29 +202,49 @@ class Method extends MethodBase, TMethod {
139202
* end
140203
* ```
141204
*/
142-
override predicate isPrivate() {
143-
exists(Namespace n, string name |
144-
any(Private p).modifiesMethod(n, name) and
145-
isDeclaredIn(this, n, name)
146-
)
147-
or
148-
// Top-level methods are private members of the Object class
149-
this.getEnclosingModule() instanceof Toplevel
150-
}
205+
override predicate isPrivate() { super.isPrivate() }
151206

152207
final override Parameter getParameter(int n) {
153208
toGenerated(result) = g.getParameters().getChild(n)
154209
}
155210

156211
final override string toString() { result = this.getName() }
212+
213+
override string getVisibility() {
214+
result = getVisibilityModifier(this).getVisibility()
215+
or
216+
this.getEnclosingModule() instanceof Toplevel and
217+
not exists(getVisibilityModifier(this)) and
218+
result = "private"
219+
or
220+
not this.getEnclosingModule() instanceof Toplevel and
221+
not exists(getVisibilityModifier(this)) and
222+
result = "public"
223+
}
224+
}
225+
226+
pragma[nomagic]
227+
private predicate modifiesIn(VisibilityModifier vm, ModuleBase n, string name) {
228+
n = vm.getEnclosingModule() and
229+
name = vm.getMethodArgument().getConstantValue().getStringlikeValue()
157230
}
158231

159232
/**
160-
* Holds if the method `m` has name `name` and is declared in namespace `n`.
233+
* Holds if statement `s` is declared in namespace `n` at position `pos`.
161234
*/
162-
pragma[noinline]
163-
private predicate isDeclaredIn(MethodBase m, Namespace n, string name) {
164-
n = m.getEnclosingModule() and name = m.getName()
235+
pragma[nomagic]
236+
private predicate isDeclaredIn(Stmt s, ModuleBase n, int pos) {
237+
n = s.getEnclosingModule() and
238+
n.getStmt(pos) = s
239+
}
240+
241+
/**
242+
* Holds if method `m` with name `name` is declared in namespace `n`.
243+
*/
244+
pragma[nomagic]
245+
private predicate methodIsDeclaredIn(MethodBase m, ModuleBase n, string name) {
246+
isDeclaredIn(m, n, _) and
247+
name = m.getName()
165248
}
166249

167250
/** A singleton method. */
@@ -216,12 +299,7 @@ class SingletonMethod extends MethodBase, TSingletonMethod {
216299
* end
217300
* ```
218301
*/
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-
}
302+
override predicate isPrivate() { super.isPrivate() }
225303
}
226304

227305
/**

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,10 @@ private.rb:
191191
# 1| E
192192
#-----| super -> Object
193193

194-
# 42| F
194+
# 62| F
195195

196-
# 62| PrivateOverride1
196+
# 82| PrivateOverride1
197197
#-----| super -> Object
198198

199-
# 76| PrivateOverride2
199+
# 96| PrivateOverride2
200200
#-----| super -> PrivateOverride1

0 commit comments

Comments
 (0)