From 20ff4c4299a4e322f729de22036f337bbf98eb60 Mon Sep 17 00:00:00 2001 From: Harry Maclean Date: Fri, 17 Jun 2022 10:19:53 +1200 Subject: [PATCH] Ruby: Model ActiveRecord::Relation#touch_all --- .../codeql/ruby/frameworks/ActiveRecord.qll | 24 ++++++++++++++++++- .../concepts/PersistentWriteAccess.expected | 1 + .../app/controllers/users_controller.rb | 4 ++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll index 0846e30d047..1097a75919a 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll @@ -359,7 +359,7 @@ private module Persistence { } /** - * Holds if `call` has a keyword argument of with value `value`. + * Holds if `call` has a keyword argument with value `value`. */ private predicate keywordArgumentWithValue(DataFlow::CallNode call, DataFlow::ExprNode value) { exists(ExprNodes::PairCfgNode pair | pair = call.getArgument(_).asExpr() | @@ -412,6 +412,28 @@ private module Persistence { } } + /** + * A call to `ActiveRecord::Relation#touch_all`, which updates the `updated_at` + * attribute on all records in the relation, setting it to the current time or + * the time specified. If passed additional attribute names, they will also be + * updated with the time. + * Examples: + * ```rb + * Person.all.touch_all + * Person.where(name: "David").touch_all + * Person.all.touch_all(:created_at) + * Person.all.touch_all(time: Time.new(2020, 5, 16, 0, 0, 0)) + * ``` + */ + private class TouchAllCall extends DataFlow::CallNode, PersistentWriteAccess::Range { + TouchAllCall() { + exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and + this.getMethodName() = "touch_all" + } + + override DataFlow::Node getValue() { result = this.getKeywordArgument("time") } + } + /** A call to e.g. `User.insert_all([{name: "foo"}, {name: "bar"}])` */ private class InsertAllLikeCall extends DataFlow::CallNode, PersistentWriteAccess::Range { private ExprNodes::ArrayLiteralCfgNode arr; diff --git a/ruby/ql/test/library-tests/concepts/PersistentWriteAccess.expected b/ruby/ql/test/library-tests/concepts/PersistentWriteAccess.expected index f4ecda5888d..f71e5f89116 100644 --- a/ruby/ql/test/library-tests/concepts/PersistentWriteAccess.expected +++ b/ruby/ql/test/library-tests/concepts/PersistentWriteAccess.expected @@ -14,6 +14,7 @@ | app/controllers/users_controller.rb:20:7:20:57 | call to update_attributes | app/controllers/users_controller.rb:20:49:20:55 | call to get_uid | | app/controllers/users_controller.rb:23:7:23:42 | call to update_attribute | app/controllers/users_controller.rb:23:37:23:41 | "U13" | | app/controllers/users_controller.rb:26:19:26:23 | ... = ... | app/controllers/users_controller.rb:26:19:26:23 | "U14" | +| app/controllers/users_controller.rb:31:7:31:32 | call to touch_all | app/controllers/users_controller.rb:31:28:31:31 | call to time | | app/models/user.rb:4:5:4:28 | call to update | app/models/user.rb:4:23:4:27 | "U15" | | app/models/user.rb:5:5:5:23 | call to update | app/models/user.rb:5:18:5:22 | "U16" | | app/models/user.rb:6:5:6:56 | call to update_attributes | app/models/user.rb:6:35:6:39 | "U17" | diff --git a/ruby/ql/test/library-tests/concepts/app/controllers/users_controller.rb b/ruby/ql/test/library-tests/concepts/app/controllers/users_controller.rb index b6f23d5ff43..83ee6088527 100644 --- a/ruby/ql/test/library-tests/concepts/app/controllers/users_controller.rb +++ b/ruby/ql/test/library-tests/concepts/app/controllers/users_controller.rb @@ -25,6 +25,10 @@ module Users # AssignAttributeCall user.name = "U14" user.save + + # TouchAllCall + User.touch_all + User.touch_all(time: time) end def get_uid