@@ -21,6 +21,25 @@ private class ApplicationRecordAccess extends ConstantReadAccess {
2121 ApplicationRecordAccess ( ) { this .getName ( ) = "ApplicationRecord" }
2222}
2323
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+
2443/**
2544 * A `ClassDeclaration` for a class that extends `ActiveRecord::Base`. For example,
2645 *
@@ -52,11 +71,14 @@ class ActiveRecordModelClass extends ClassDeclaration {
5271 // There is a value that can be returned by this method which may include field data
5372 exists ( DataFlow:: Node returned , ActiveRecordInstanceMethodCall cNode , MethodCall c |
5473 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...
5880 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
6082 c .getATarget ( ) = cNode .getInstance ( ) .getClass ( ) .methodMayAccessField ( )
6183 )
6284 )
@@ -202,18 +224,17 @@ private string constantQualifiedName(ConstantWriteAccess w) {
202224abstract class ActiveRecordModelInstantiation extends OrmInstantiation:: Range , DataFlow:: LocalSourceNode {
203225 abstract ActiveRecordModelClass getClass ( ) ;
204226
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.
211234 exists ( Method m |
212235 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
217238 )
218239 )
219240 }
@@ -298,20 +319,4 @@ private class ActiveRecordInstanceMethodCall extends DataFlow::CallNode {
298319 private ActiveRecordInstance instance ;
299320 ActiveRecordInstanceMethodCall ( ) { this .getReceiver ( ) = instance }
300321 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