@@ -316,3 +316,151 @@ private class ActiveRecordInstanceMethodCall extends DataFlow::CallNode {
316
316
317
317
ActiveRecordInstance getInstance ( ) { result = instance }
318
318
}
319
+
320
+ /**
321
+ * Provides modeling relating to the `ActiveRecord::Persistence` module.
322
+ */
323
+ private module Persistence {
324
+ /**
325
+ * Holds if there is a hash literal argument to `call` at `argIndex`
326
+ * containing a KV pair with value `value`.
327
+ */
328
+ private predicate hashArgumentWithValue (
329
+ DataFlow:: CallNode call , int argIndex , DataFlow:: ExprNode value
330
+ ) {
331
+ exists ( ExprNodes:: HashLiteralCfgNode hash , ExprNodes:: PairCfgNode pair |
332
+ hash = call .getArgument ( argIndex ) .asExpr ( ) and
333
+ pair = hash .getAKeyValuePair ( )
334
+ |
335
+ value .asExpr ( ) = pair .getValue ( )
336
+ )
337
+ }
338
+
339
+ /**
340
+ * Holds if `call` has a keyword argument of with value `value`.
341
+ */
342
+ private predicate keywordArgumentWithValue ( DataFlow:: CallNode call , DataFlow:: ExprNode value ) {
343
+ exists ( ExprNodes:: PairCfgNode pair | pair = call .getArgument ( _) .asExpr ( ) |
344
+ value .asExpr ( ) = pair .getValue ( )
345
+ )
346
+ }
347
+
348
+ /** A call to e.g. `User.create(name: "foo")` */
349
+ private class CreateLikeCall extends DataFlow:: CallNode , PersistentWriteAccess:: Range {
350
+ private ActiveRecordModelClass modelCls ;
351
+
352
+ CreateLikeCall ( ) {
353
+ modelCls = this .asExpr ( ) .getExpr ( ) .( ActiveRecordModelClassMethodCall ) .getReceiverClass ( ) and
354
+ this .getMethodName ( ) =
355
+ [
356
+ "create" , "create!" , "create_or_find_by" , "create_or_find_by!" , "find_or_create_by" ,
357
+ "find_or_create_by!" , "insert" , "insert!"
358
+ ]
359
+ }
360
+
361
+ override DataFlow:: Node getValue ( ) {
362
+ // attrs as hash elements in arg0
363
+ hashArgumentWithValue ( this , 0 , result ) or
364
+ keywordArgumentWithValue ( this , result )
365
+ }
366
+ }
367
+
368
+ /** A call to e.g. `User.update(1, name: "foo")` */
369
+ private class UpdateLikeClassMethodCall extends DataFlow:: CallNode , PersistentWriteAccess:: Range {
370
+ private ActiveRecordModelClass modelCls ;
371
+
372
+ UpdateLikeClassMethodCall ( ) {
373
+ modelCls = this .asExpr ( ) .getExpr ( ) .( ActiveRecordModelClassMethodCall ) .getReceiverClass ( ) and
374
+ this .getMethodName ( ) = [ "update" , "update!" , "upsert" ]
375
+ }
376
+
377
+ override DataFlow:: Node getValue ( ) {
378
+ keywordArgumentWithValue ( this , result )
379
+ or
380
+ // Case where 2 array args are passed - the first an array of IDs, and the
381
+ // second an array of hashes - each hash corresponding to an ID in the
382
+ // first array.
383
+ exists ( ExprNodes:: ArrayLiteralCfgNode hashesArray |
384
+ this .getArgument ( 0 ) .asExpr ( ) instanceof ExprNodes:: ArrayLiteralCfgNode and
385
+ hashesArray = this .getArgument ( 1 ) .asExpr ( )
386
+ |
387
+ exists ( ExprNodes:: HashLiteralCfgNode hash , ExprNodes:: PairCfgNode pair |
388
+ hash = hashesArray .getArgument ( _) and
389
+ pair = hash .getAKeyValuePair ( )
390
+ |
391
+ result .asExpr ( ) = pair .getValue ( )
392
+ )
393
+ )
394
+ }
395
+ }
396
+
397
+ /** A call to e.g. `User.insert_all([{name: "foo"}, {name: "bar"}])` */
398
+ private class InsertAllLikeCall extends DataFlow:: CallNode , PersistentWriteAccess:: Range {
399
+ private ExprNodes:: ArrayLiteralCfgNode arr ;
400
+ private ActiveRecordModelClass modelCls ;
401
+
402
+ InsertAllLikeCall ( ) {
403
+ modelCls = this .asExpr ( ) .getExpr ( ) .( ActiveRecordModelClassMethodCall ) .getReceiverClass ( ) and
404
+ this .getMethodName ( ) = [ "insert_all" , "insert_all!" , "upsert_all" ] and
405
+ arr = this .getArgument ( 0 ) .asExpr ( )
406
+ }
407
+
408
+ override DataFlow:: Node getValue ( ) {
409
+ // attrs as hash elements of members of array arg0
410
+ exists ( ExprNodes:: HashLiteralCfgNode hash , ExprNodes:: PairCfgNode pair |
411
+ hash = arr .getArgument ( _) and
412
+ pair = hash .getAKeyValuePair ( )
413
+ |
414
+ result .asExpr ( ) = pair .getValue ( )
415
+ )
416
+ }
417
+ }
418
+
419
+ /** A call to e.g. `user.update(name: "foo")` */
420
+ private class UpdateLikeInstanceMethodCall extends PersistentWriteAccess:: Range ,
421
+ ActiveRecordInstanceMethodCall {
422
+ UpdateLikeInstanceMethodCall ( ) {
423
+ this .getMethodName ( ) = [ "update" , "update!" , "update_attributes" , "update_attributes!" ]
424
+ }
425
+
426
+ override DataFlow:: Node getValue ( ) {
427
+ // attrs as hash elements in arg0
428
+ hashArgumentWithValue ( this , 0 , result )
429
+ or
430
+ // keyword arg
431
+ keywordArgumentWithValue ( this , result )
432
+ }
433
+ }
434
+
435
+ /** A call to e.g. `user.update_attribute(name, "foo")` */
436
+ private class UpdateAttributeCall extends PersistentWriteAccess:: Range ,
437
+ ActiveRecordInstanceMethodCall {
438
+ UpdateAttributeCall ( ) { this .getMethodName ( ) = "update_attribute" }
439
+
440
+ override DataFlow:: Node getValue ( ) {
441
+ // e.g. `foo.update_attribute(key, value)`
442
+ result = this .getArgument ( 1 )
443
+ }
444
+ }
445
+
446
+ /**
447
+ * An assignment like `user.name = "foo"`. Though this does not write to the
448
+ * database without a subsequent call to persist the object, it is considered
449
+ * as an `PersistentWriteAccess` to avoid missing cases where the path to a
450
+ * subsequent write is not clear.
451
+ */
452
+ private class AssignAttribute extends PersistentWriteAccess:: Range {
453
+ private ExprNodes:: AssignExprCfgNode assignNode ;
454
+
455
+ AssignAttribute ( ) {
456
+ exists ( DataFlow:: CallNode setter |
457
+ assignNode = this .asExpr ( ) and
458
+ setter .getArgument ( 0 ) = this and
459
+ setter instanceof ActiveRecordInstanceMethodCall and
460
+ setter .asExpr ( ) .getExpr ( ) instanceof SetterMethodCall
461
+ )
462
+ }
463
+
464
+ override DataFlow:: Node getValue ( ) { assignNode .getRhs ( ) = result .asExpr ( ) }
465
+ }
466
+ }
0 commit comments