Skip to content

Commit 43a4968

Browse files
committed
reorganize ActiveRecord field access heuristics
1 parent 8f81eaa commit 43a4968

File tree

1 file changed

+37
-24
lines changed

1 file changed

+37
-24
lines changed

ql/lib/codeql/ruby/frameworks/ActiveRecord.qll

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,19 @@ class ActiveRecordModelClass extends ClassDeclaration {
4545
}
4646

4747
/**
48-
* Returns true if `call` may refer to a method that returns a database value
49-
* if invoked against an instance of this class.
50-
*/
51-
predicate methodCallMayAccessField(MethodCall call) {
52-
not (
53-
// Methods whose names can be hardcoded
54-
isCallToBuiltInMethod(call)
55-
or
56-
// Methods defined in the ActiveRecord model class that do not return database fields
57-
exists(Method m | m = this.getMethod(call.getMethodName()) |
58-
forall(DataFlow::Node returned, ActiveRecordInstanceMethodCall c |
59-
exprNodeReturnedFrom(returned, m) and c.flowsTo(returned)
60-
|
61-
not this.methodCallMayAccessField(returned.asExpr().getExpr())
62-
)
48+
* Gets methods defined in this class that may access a field from the database.
49+
*/
50+
Method methodMayAccessField() {
51+
result = this.getAMethod() and
52+
// There is a value that can be returned by this method which may include field data
53+
exists(DataFlow::Node returned, ActiveRecordInstanceMethodCall cNode, MethodCall c |
54+
exprNodeReturnedFrom(returned, result) and cNode.flowsTo(returned) and c = cNode.asExpr().getExpr() |
55+
// The referenced method is not built-in, and
56+
not isCallToBuiltInMethod(c) and (
57+
// There is no matching method definition in the class, or
58+
not exists(cNode.getInstance().getClass().getMethod(c.getMethodName())) or
59+
// The called method can access a field
60+
c.getATarget() = cNode.getInstance().getClass().methodMayAccessField()
6361
)
6462
)
6563
}
@@ -201,11 +199,23 @@ private string constantQualifiedName(ConstantWriteAccess w) {
201199
/**
202200
* A node that may evaluate to one or more `ActiveRecordModelClass` instances.
203201
*/
204-
abstract class ActiveRecordModelInstantiation extends OrmInstantiation::Range {
202+
abstract class ActiveRecordModelInstantiation extends OrmInstantiation::Range, DataFlow::LocalSourceNode {
205203
abstract ActiveRecordModelClass getClass();
206204

207205
override predicate methodCallMayAccessField(MethodCall call) {
208-
this.getClass().methodCallMayAccessField(call)
206+
// The method is not a built-in
207+
not isCallToBuiltInMethod(call) and (
208+
// There is no matching method definition in the class, or
209+
not exists(this.getClass().getMethod(call.getMethodName())) or
210+
// The called method can access a field
211+
exists(Method m |
212+
m = this.getClass().methodMayAccessField() |
213+
// TODO: this may be too broad - we haven't limited the call target
214+
// It's likely that the call graph isn't sufficient here, as resolution
215+
// e.g. from ActionView views won't catch everything
216+
m.getName() = call.getMethodName()
217+
)
218+
)
209219
}
210220
}
211221

@@ -260,8 +270,7 @@ private class ActiveRecordModelFinderCall extends ActiveRecordModelInstantiation
260270
}
261271

262272
// A `self` reference that may resolve to an active record model object
263-
private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInstantiation,
264-
DataFlow::LocalSourceNode {
273+
private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInstantiation {
265274
private ActiveRecordModelClass cls;
266275

267276
ActiveRecordModelClassSelfReference() {
@@ -276,15 +285,19 @@ private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInsta
276285
}
277286

278287
// A (locally tracked) active record model object
279-
private DataFlow::Node activeRecordModelInstance() {
280-
result instanceof ActiveRecordModelInstantiation
281-
or
282-
exists(ActiveRecordModelInstantiation inst | inst.(DataFlow::LocalSourceNode).flowsTo(result))
288+
private class ActiveRecordInstance extends DataFlow::Node {
289+
private ActiveRecordModelInstantiation instantiation;
290+
291+
ActiveRecordInstance() { this = instantiation or instantiation.flowsTo(this) }
292+
293+
ActiveRecordModelClass getClass() { result = instantiation.getClass() }
283294
}
284295

285296
// A call whose receiver may be an active record model object
286297
private class ActiveRecordInstanceMethodCall extends DataFlow::CallNode {
287-
ActiveRecordInstanceMethodCall() { this.getReceiver() = activeRecordModelInstance() }
298+
private ActiveRecordInstance instance;
299+
ActiveRecordInstanceMethodCall() { this.getReceiver() = instance }
300+
ActiveRecordInstance getInstance() { result = instance }
288301
}
289302

290303
private string activeRecordPersistenceInstanceMethodName() {

0 commit comments

Comments
 (0)