@@ -21,6 +21,25 @@ private class ApplicationRecordAccess extends ConstantReadAccess {
21
21
ApplicationRecordAccess ( ) { this .getName ( ) = "ApplicationRecord" }
22
22
}
23
23
24
+ /// See https://api.rubyonrails.org/classes/ActiveRecord/Persistence.html
25
+ private string activeRecordPersistenceInstanceMethodName ( ) {
26
+ result =
27
+ [
28
+ "becomes" , "becomes!" , "decrement" , "decrement!" , "delete" , "delete!" , "destroy" , "destroy!" ,
29
+ "destroyed?" , "increment" , "increment!" , "new_record?" , "persisted?" ,
30
+ "previously_new_record?" , "reload" , "save" , "save!" , "toggle" , "toggle!" , "touch" , "update" ,
31
+ "update!" , "update_attribute" , "update_column" , "update_columns"
32
+ ]
33
+ }
34
+
35
+ // Methods with these names are defined for all active record model instances,
36
+ // so they are unlikely to refer to a database field.
37
+ private predicate isBuiltInMethodForActiveRecordModelInstance ( string methodName ) {
38
+ methodName = activeRecordPersistenceInstanceMethodName ( ) or
39
+ methodName = basicObjectInstanceMethodName ( ) or
40
+ methodName = objectInstanceMethodName ( )
41
+ }
42
+
24
43
/**
25
44
* A `ClassDeclaration` for a class that extends `ActiveRecord::Base`. For example,
26
45
*
@@ -52,11 +71,14 @@ class ActiveRecordModelClass extends ClassDeclaration {
52
71
// There is a value that can be returned by this method which may include field data
53
72
exists ( DataFlow:: Node returned , ActiveRecordInstanceMethodCall cNode , MethodCall c |
54
73
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
74
+ // The referenced method is not built-in, and...
75
+ not isBuiltInMethodForActiveRecordModelInstance ( c .getMethodName ( ) ) and (
76
+ // TODO: this would be more accurate if we also checked methods defined in
77
+ // super classes and mixins
78
+
79
+ // ...There is no matching method definition in the class, or...
58
80
not exists ( cNode .getInstance ( ) .getClass ( ) .getMethod ( c .getMethodName ( ) ) ) or
59
- // The called method can access a field
81
+ // ...the called method can access a field
60
82
c .getATarget ( ) = cNode .getInstance ( ) .getClass ( ) .methodMayAccessField ( )
61
83
)
62
84
)
@@ -202,18 +224,17 @@ private string constantQualifiedName(ConstantWriteAccess w) {
202
224
abstract class ActiveRecordModelInstantiation extends OrmInstantiation:: Range , DataFlow:: LocalSourceNode {
203
225
abstract ActiveRecordModelClass getClass ( ) ;
204
226
205
- override predicate methodCallMayAccessField ( MethodCall 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
227
+ bindingset [ methodName]
228
+ override predicate methodCallMayAccessField ( string methodName ) {
229
+ // The method is not a built-in, and...
230
+ not isBuiltInMethodForActiveRecordModelInstance ( methodName ) and (
231
+ // ...There is no matching method definition in the class, or...
232
+ not exists ( this .getClass ( ) .getMethod ( methodName ) ) or
233
+ // ...the called method can access a field.
211
234
exists ( Method m |
212
235
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 ( )
236
+ // We rely on matching by name here as the call graph might not have
237
+ m .getName ( ) = methodName
217
238
)
218
239
)
219
240
}
@@ -298,20 +319,4 @@ private class ActiveRecordInstanceMethodCall extends DataFlow::CallNode {
298
319
private ActiveRecordInstance instance ;
299
320
ActiveRecordInstanceMethodCall ( ) { this .getReceiver ( ) = instance }
300
321
ActiveRecordInstance getInstance ( ) { result = instance }
301
- }
302
-
303
- private string activeRecordPersistenceInstanceMethodName ( ) {
304
- result =
305
- [
306
- "becomes" , "becomes!" , "decrement" , "decrement!" , "delete" , "delete!" , "destroy" , "destroy!" ,
307
- "destroyed?" , "increment" , "increment!" , "new_record?" , "persisted?" ,
308
- "previously_new_record?" , "reload" , "save" , "save!" , "toggle" , "toggle!" , "touch" , "update" ,
309
- "update!" , "update_attribute" , "update_column" , "update_columns"
310
- ]
311
- }
312
-
313
- private predicate isCallToBuiltInMethod ( MethodCall c ) {
314
- c .getMethodName ( ) = activeRecordPersistenceInstanceMethodName ( ) or
315
- c instanceof BasicObjectInstanceMethodCall or
316
- c instanceof ObjectInstanceMethodCall
317
- }
322
+ }
0 commit comments