Ruby: minor overhaul of ActiveRecord model

Old version had scalability issues when adding taking more interprocedural flow and inheritance into account.
This commit is contained in:
Asger F 2023-06-19 12:07:21 +02:00
Родитель bb3b973b32
Коммит 8bc4193ce0
3 изменённых файлов: 247 добавлений и 127 удалений

Просмотреть файл

@ -30,10 +30,8 @@ private predicate isBuiltInMethodForActiveRecordModelInstance(string methodName)
methodName = objectInstanceMethodName()
}
private API::Node activeRecordClassApiNode() {
private API::Node activeRecordBaseClass() {
result =
// class Foo < ActiveRecord::Base
// class Bar < Foo
[
API::getTopLevelMember("ActiveRecord").getMember("Base"),
// In Rails applications `ApplicationRecord` typically extends `ActiveRecord::Base`, but we
@ -42,6 +40,46 @@ private API::Node activeRecordClassApiNode() {
]
}
/**
* Gets an object with methods from the ActiveRecord query interface.
*/
private API::Node activeRecordQueryBuilder() {
result = activeRecordBaseClass()
or
result = activeRecordBaseClass().getInstance()
or
// Assume any method call might return an ActiveRecord::Relation
// These are dynamically generated
result = activeRecordQueryBuilderMethodAccess(_).getReturn()
}
/** Gets a call targeting the ActiveRecord query interface. */
private API::MethodAccessNode activeRecordQueryBuilderMethodAccess(string name) {
result = activeRecordQueryBuilder().getMethod(name) and
// Due to the heuristic tracking of query builder objects, add a restriction for methods with a known call target
not isUnlikelyExternalCall(result)
}
/** Gets a call targeting the ActiveRecord query interface. */
private DataFlow::CallNode activeRecordQueryBuilderCall(string name) {
result = activeRecordQueryBuilderMethodAccess(name).asCall()
}
/**
* Holds if `call` is unlikely to call into an external library, since it has a possible
* call target in its enclosing module.
*/
private predicate isUnlikelyExternalCall(API::MethodAccessNode node) {
exists(DataFlow::ModuleNode mod, DataFlow::CallNode call | call = node.asCall() |
call.getATarget() = [mod.getAnOwnSingletonMethod(), mod.getAnOwnInstanceMethod()] and
call.getEnclosingMethod() = [mod.getAnOwnSingletonMethod(), mod.getAnOwnInstanceMethod()]
)
}
private API::Node activeRecordConnectionInstance() {
result = activeRecordBaseClass().getReturn("connection")
}
/**
* A `ClassDeclaration` for a class that inherits from `ActiveRecord::Base`. For example,
*
@ -55,20 +93,19 @@ private API::Node activeRecordClassApiNode() {
* ```
*/
class ActiveRecordModelClass extends ClassDeclaration {
private DataFlow::ClassNode cls;
ActiveRecordModelClass() {
this.getSuperclassExpr() =
activeRecordClassApiNode().getASubclass().getAValueReachableFromSource().asExpr().getExpr()
cls = activeRecordBaseClass().getADescendentModule() and this = cls.getADeclaration()
}
// Gets the class declaration for this class and all of its super classes
private ModuleBase getAllClassDeclarations() {
result = this.getModule().getSuperClass*().getADeclaration()
}
private ModuleBase getAllClassDeclarations() { result = cls.getAnAncestor().getADeclaration() }
/**
* Gets methods defined in this class that may access a field from the database.
*/
Method getAPotentialFieldAccessMethod() {
deprecated Method getAPotentialFieldAccessMethod() {
// It's a method on this class or one of its super classes
result = this.getAllClassDeclarations().getAMethod() and
// There is a value that can be returned by this method which may include field data
@ -90,58 +127,84 @@ class ActiveRecordModelClass extends ClassDeclaration {
)
)
}
/** Gets the class as a `DataFlow::ClasNode`. */
DataFlow::ClassNode getClassNode() { result = cls }
}
/** A class method call whose receiver is an `ActiveRecordModelClass`. */
class ActiveRecordModelClassMethodCall extends MethodCall {
private ActiveRecordModelClass recvCls;
/**
* Gets a potential reference to an ActiveRecord class object.
*/
deprecated private API::Node getAnActiveRecordModelClassRef() {
result = any(ActiveRecordModelClass cls).getClassNode().trackModule()
or
// For methods with an unknown call target, assume this might be a database field, thus returning another ActiveRecord object.
// In this case we do not know which class it belongs to, which is why this predicate can't associate the reference with a specific class.
result = getAnUnknownActiveRecordModelClassCall().getReturn()
}
/**
* Gets a call performed on an ActiveRecord class object, without a known call target in the codebase.
*/
deprecated private API::MethodAccessNode getAnUnknownActiveRecordModelClassCall() {
result = getAnActiveRecordModelClassRef().getMethod(_) and
result.asCall().asExpr().getExpr() instanceof UnknownMethodCall
}
/**
* DEPRECATED. Use `ActiveRecordModelClass.getClassNode().trackModule().getMethod()` instead.
*
* A class method call whose receiver is an `ActiveRecordModelClass`.
*/
deprecated class ActiveRecordModelClassMethodCall extends MethodCall {
ActiveRecordModelClassMethodCall() {
// e.g. Foo.where(...)
recvCls.getModule() = this.getReceiver().(ConstantReadAccess).getModule()
or
// e.g. Foo.joins(:bars).where(...)
recvCls = this.getReceiver().(ActiveRecordModelClassMethodCall).getReceiverClass()
or
// e.g. self.where(...) within an ActiveRecordModelClass
this.getReceiver() instanceof SelfVariableAccess and
this.getEnclosingModule() = recvCls
this = getAnUnknownActiveRecordModelClassCall().asCall().asExpr().getExpr()
}
/** The `ActiveRecordModelClass` of the receiver of this method. */
ActiveRecordModelClass getReceiverClass() { result = recvCls }
/** Gets the `ActiveRecordModelClass` of the receiver of this method, if it can be determined. */
ActiveRecordModelClass getReceiverClass() {
this = result.getClassNode().trackModule().getMethod(_).asCall().asExpr().getExpr()
}
}
private Expr sqlFragmentArgument(MethodCall call) {
exists(string methodName |
methodName = call.getMethodName() and
(
methodName =
[
"delete_all", "delete_by", "destroy_all", "destroy_by", "exists?", "find_by", "find_by!",
"find_or_create_by", "find_or_create_by!", "find_or_initialize_by", "find_by_sql", "from",
"group", "having", "joins", "lock", "not", "order", "reorder", "pluck", "where",
"rewhere", "select", "reselect", "update_all"
] and
result = call.getArgument(0)
or
methodName = "calculate" and result = call.getArgument(1)
or
methodName in ["average", "count", "maximum", "minimum", "sum", "count_by_sql"] and
result = call.getArgument(0)
or
// This format was supported until Rails 2.3.8
methodName = ["all", "find", "first", "last"] and
result = call.getKeywordArgument("conditions")
or
methodName = "reload" and
result = call.getKeywordArgument("lock")
or
// Calls to `annotate` can be used to add block comments to SQL queries. These are potentially vulnerable to
// SQLi if user supplied input is passed in as an argument.
methodName = "annotate" and
result = call.getArgument(_)
)
private predicate sqlFragmentArgumentInner(DataFlow::CallNode call, DataFlow::Node sink) {
call =
activeRecordQueryBuilderCall([
"delete_all", "delete_by", "destroy_all", "destroy_by", "exists?", "find_by", "find_by!",
"find_or_create_by", "find_or_create_by!", "find_or_initialize_by", "find_by_sql", "from",
"group", "having", "joins", "lock", "not", "order", "reorder", "pluck", "where", "rewhere",
"select", "reselect", "update_all"
]) and
sink = call.getArgument(0)
or
call = activeRecordQueryBuilderCall("calculate") and
sink = call.getArgument(1)
or
call =
activeRecordQueryBuilderCall(["average", "count", "maximum", "minimum", "sum", "count_by_sql"]) and
sink = call.getArgument(0)
or
// This format was supported until Rails 2.3.8
call = activeRecordQueryBuilderCall(["all", "find", "first", "last"]) and
sink = call.getKeywordArgument("conditions")
or
call = activeRecordQueryBuilderCall("reload") and
sink = call.getKeywordArgument("lock")
or
// Calls to `annotate` can be used to add block comments to SQL queries. These are potentially vulnerable to
// SQLi if user supplied input is passed in as an argument.
call = activeRecordQueryBuilderCall("annotate") and
sink = call.getArgument(_)
or
call = activeRecordConnectionInstance().getAMethodCall("execute") and
sink = call.getArgument(0)
}
private predicate sqlFragmentArgument(DataFlow::CallNode call, DataFlow::Node sink) {
exists(DataFlow::Node arg |
sqlFragmentArgumentInner(call, arg) and
sink = [arg, arg.(DataFlow::ArrayLiteralNode).getElement(0)] and
unsafeSqlExpr(sink.asExpr().getExpr())
)
}
@ -162,6 +225,8 @@ private predicate unsafeSqlExpr(Expr sqlFragmentExpr) {
}
/**
* DEPRECATED. Use the `SqlExecution` concept or `ActiveRecordSqlExecutionRange`.
*
* A method call that may result in executing unintended user-controlled SQL
* queries if the `getSqlFragmentSinkArgument()` expression is tainted by
* unsanitized user-controlled input. For example, supposing that `User` is an
@ -175,55 +240,32 @@ private predicate unsafeSqlExpr(Expr sqlFragmentExpr) {
* as `"') OR 1=1 --"` could result in the application looking up all users
* rather than just one with a matching name.
*/
class PotentiallyUnsafeSqlExecutingMethodCall extends ActiveRecordModelClassMethodCall {
// The SQL fragment argument itself
private Expr sqlFragmentExpr;
deprecated class PotentiallyUnsafeSqlExecutingMethodCall extends ActiveRecordModelClassMethodCall {
private DataFlow::CallNode call;
PotentiallyUnsafeSqlExecutingMethodCall() {
exists(Expr arg |
arg = sqlFragmentArgument(this) and
unsafeSqlExpr(sqlFragmentExpr) and
(
sqlFragmentExpr = arg
or
sqlFragmentExpr = arg.(ArrayLiteral).getElement(0)
) and
// Check that method has not been overridden
not exists(SingletonMethod m |
m.getName() = this.getMethodName() and
m.getOuterScope() = this.getReceiverClass()
)
)
call.asExpr().getExpr() = this and sqlFragmentArgument(call, _)
}
/**
* Gets the SQL fragment argument of this method call.
*/
Expr getSqlFragmentSinkArgument() { result = sqlFragmentExpr }
Expr getSqlFragmentSinkArgument() {
exists(DataFlow::Node sink |
sqlFragmentArgument(call, sink) and result = sink.asExpr().getExpr()
)
}
}
/**
* An `SqlExecution::Range` for an argument to a
* `PotentiallyUnsafeSqlExecutingMethodCall` that may be vulnerable to being
* controlled by user input.
* A SQL execution arising from a call to the ActiveRecord library.
*/
class ActiveRecordSqlExecutionRange extends SqlExecution::Range {
ActiveRecordSqlExecutionRange() {
exists(PotentiallyUnsafeSqlExecutingMethodCall mc |
this.asExpr().getNode() = mc.getSqlFragmentSinkArgument()
)
or
this = activeRecordConnectionInstance().getAMethodCall("execute").getArgument(0) and
unsafeSqlExpr(this.asExpr().getExpr())
}
ActiveRecordSqlExecutionRange() { sqlFragmentArgument(_, this) }
override DataFlow::Node getSql() { result = this }
}
private API::Node activeRecordConnectionInstance() {
result = activeRecordClassApiNode().getReturn("connection")
}
// TODO: model `ActiveRecord` sanitizers
// https://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html
/**
@ -241,15 +283,8 @@ abstract class ActiveRecordModelInstantiation extends OrmInstantiation::Range,
override predicate methodCallMayAccessField(string methodName) {
// The method is not a built-in, and...
not isBuiltInMethodForActiveRecordModelInstance(methodName) and
(
// ...There is no matching method definition in the class, or...
not exists(this.getClass().getMethod(methodName))
or
// ...the called method can access a field.
exists(Method m | m = this.getClass().getAPotentialFieldAccessMethod() |
m.getName() = methodName
)
)
// ...There is no matching method definition in the class
not exists(this.getClass().getMethod(methodName))
}
}
@ -317,21 +352,10 @@ private class ActiveRecordModelFinderCall extends ActiveRecordModelInstantiation
}
// A `self` reference that may resolve to an active record model object
private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInstantiation,
DataFlow::SelfParameterNode
{
private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInstantiation {
private ActiveRecordModelClass cls;
ActiveRecordModelClassSelfReference() {
exists(MethodBase m |
m = this.getCallable() and
m.getEnclosingModule() = cls and
m = cls.getAMethod()
) and
// In a singleton method, `self` refers to the class itself rather than an
// instance of that class
not this.getSelfVariable().getDeclaringScope() instanceof SingletonMethod
}
ActiveRecordModelClassSelfReference() { this = cls.getClassNode().getAnOwnInstanceSelf() }
final override ActiveRecordModelClass getClass() { result = cls }
}
@ -342,7 +366,7 @@ private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInsta
class ActiveRecordInstance extends DataFlow::Node {
private ActiveRecordModelInstantiation instantiation;
ActiveRecordInstance() { this = instantiation or instantiation.flowsTo(this) }
ActiveRecordInstance() { this = instantiation.track().getAValueReachableFromSource() }
/** Gets the `ActiveRecordModelClass` that this is an instance of. */
ActiveRecordModelClass getClass() { result = instantiation.getClass() }
@ -380,12 +404,12 @@ private module Persistence {
/** A call to e.g. `User.create(name: "foo")` */
private class CreateLikeCall extends DataFlow::CallNode, PersistentWriteAccess::Range {
CreateLikeCall() {
exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and
this.getMethodName() =
[
"create", "create!", "create_or_find_by", "create_or_find_by!", "find_or_create_by",
"find_or_create_by!", "insert", "insert!"
]
this =
activeRecordBaseClass()
.getAMethodCall([
"create", "create!", "create_or_find_by", "create_or_find_by!", "find_or_create_by",
"find_or_create_by!", "insert", "insert!"
])
}
override DataFlow::Node getValue() {
@ -402,8 +426,7 @@ private module Persistence {
/** A call to e.g. `User.update(1, name: "foo")` */
private class UpdateLikeClassMethodCall extends DataFlow::CallNode, PersistentWriteAccess::Range {
UpdateLikeClassMethodCall() {
exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and
this.getMethodName() = ["update", "update!", "upsert"]
this = activeRecordBaseClass().getAMethodCall(["update", "update!", "upsert"])
}
override DataFlow::Node getValue() {
@ -448,10 +471,7 @@ private module Persistence {
* ```
*/
private class TouchAllCall extends DataFlow::CallNode, PersistentWriteAccess::Range {
TouchAllCall() {
exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and
this.getMethodName() = "touch_all"
}
TouchAllCall() { this = activeRecordQueryBuilderCall("touch_all") }
override DataFlow::Node getValue() { result = this.getKeywordArgument("time") }
}
@ -461,8 +481,7 @@ private module Persistence {
private ExprNodes::ArrayLiteralCfgNode arr;
InsertAllLikeCall() {
exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and
this.getMethodName() = ["insert_all", "insert_all!", "upsert_all"] and
this = activeRecordBaseClass().getAMethodCall(["insert_all", "insert_all!", "upsert_all"]) and
arr = this.getArgument(0).asExpr()
}

Просмотреть файл

@ -10,6 +10,7 @@ activeRecordInstances
| ActiveRecord.rb:9:5:9:68 | call to find |
| ActiveRecord.rb:13:5:13:40 | call to find_by |
| ActiveRecord.rb:13:5:13:46 | call to users |
| ActiveRecord.rb:35:5:35:51 | call to authenticate |
| ActiveRecord.rb:36:5:36:30 | call to find_by_name |
| ActiveRecord.rb:55:5:57:7 | if ... |
| ActiveRecord.rb:55:43:56:40 | then ... |
@ -107,12 +108,14 @@ activeRecordSqlExecutionRanges
| ActiveRecord.rb:19:16:19:24 | condition |
| ActiveRecord.rb:28:30:28:44 | ...[...] |
| ActiveRecord.rb:29:20:29:42 | "id = '#{...}'" |
| ActiveRecord.rb:30:21:30:45 | call to [] |
| ActiveRecord.rb:30:22:30:44 | "id = '#{...}'" |
| ActiveRecord.rb:31:16:31:21 | <<-SQL |
| ActiveRecord.rb:34:20:34:47 | "user.id = '#{...}'" |
| ActiveRecord.rb:46:20:46:32 | ... + ... |
| ActiveRecord.rb:52:16:52:28 | "name #{...}" |
| ActiveRecord.rb:56:20:56:39 | "username = #{...}" |
| ActiveRecord.rb:68:21:68:44 | ...[...] |
| ActiveRecord.rb:106:27:106:76 | "this is an unsafe annotation:..." |
activeRecordModelClassMethodCalls
| ActiveRecord.rb:2:3:2:17 | call to has_many |
@ -127,7 +130,6 @@ activeRecordModelClassMethodCalls
| ActiveRecord.rb:31:5:31:35 | call to where |
| ActiveRecord.rb:34:5:34:14 | call to where |
| ActiveRecord.rb:34:5:34:48 | call to not |
| ActiveRecord.rb:35:5:35:51 | call to authenticate |
| ActiveRecord.rb:36:5:36:30 | call to find_by_name |
| ActiveRecord.rb:37:5:37:36 | call to not_a_find_by_method |
| ActiveRecord.rb:46:5:46:33 | call to delete_by |
@ -135,7 +137,6 @@ activeRecordModelClassMethodCalls
| ActiveRecord.rb:56:7:56:40 | call to find_by |
| ActiveRecord.rb:60:5:60:33 | call to find_by |
| ActiveRecord.rb:62:5:62:34 | call to find |
| ActiveRecord.rb:68:5:68:45 | call to delete_by |
| ActiveRecord.rb:72:5:72:24 | call to create |
| ActiveRecord.rb:76:5:76:66 | call to create |
| ActiveRecord.rb:80:5:80:68 | call to create |
@ -152,6 +153,96 @@ activeRecordModelClassMethodCalls
| associations.rb:12:3:12:32 | call to has_and_belongs_to_many |
| associations.rb:16:3:16:18 | call to belongs_to |
| associations.rb:19:11:19:20 | call to new |
| associations.rb:21:9:21:21 | call to posts |
| associations.rb:21:9:21:28 | call to create |
| associations.rb:23:12:23:25 | call to comments |
| associations.rb:23:12:23:32 | call to create |
| associations.rb:25:11:25:22 | call to author |
| associations.rb:27:9:27:21 | call to posts |
| associations.rb:27:9:27:28 | call to create |
| associations.rb:29:1:29:13 | call to posts |
| associations.rb:29:1:29:22 | ... << ... |
| associations.rb:31:1:31:12 | call to author= |
| associations.rb:35:1:35:14 | call to comments |
| associations.rb:35:1:35:21 | call to create |
| associations.rb:35:1:35:28 | call to create |
| associations.rb:37:1:37:13 | call to posts |
| associations.rb:37:1:37:20 | call to reload |
| associations.rb:37:1:37:27 | call to create |
| associations.rb:39:1:39:15 | call to build_tag |
| associations.rb:40:1:40:15 | call to build_tag |
| associations.rb:42:1:42:13 | call to posts |
| associations.rb:42:1:42:25 | call to push |
| associations.rb:43:1:43:13 | call to posts |
| associations.rb:43:1:43:27 | call to concat |
| associations.rb:44:1:44:13 | call to posts |
| associations.rb:44:1:44:19 | call to build |
| associations.rb:45:1:45:13 | call to posts |
| associations.rb:45:1:45:20 | call to create |
| associations.rb:46:1:46:13 | call to posts |
| associations.rb:46:1:46:21 | call to create! |
| associations.rb:47:1:47:13 | call to posts |
| associations.rb:47:1:47:20 | call to delete |
| associations.rb:48:1:48:13 | call to posts |
| associations.rb:48:1:48:24 | call to delete_all |
| associations.rb:49:1:49:13 | call to posts |
| associations.rb:49:1:49:21 | call to destroy |
| associations.rb:50:1:50:13 | call to posts |
| associations.rb:50:1:50:25 | call to destroy_all |
| associations.rb:51:1:51:13 | call to posts |
| associations.rb:51:1:51:22 | call to distinct |
| associations.rb:51:1:51:36 | call to find |
| associations.rb:52:1:52:13 | call to posts |
| associations.rb:52:1:52:19 | call to reset |
| associations.rb:52:1:52:33 | call to find |
| associations.rb:53:1:53:13 | call to posts |
| associations.rb:53:1:53:20 | call to reload |
| associations.rb:53:1:53:34 | call to find |
activeRecordModelClassMethodCallsReplacement
| ActiveRecord.rb:1:1:3:3 | UserGroup | ActiveRecord.rb:2:3:2:17 | call to has_many |
| ActiveRecord.rb:1:1:3:3 | UserGroup | ActiveRecord.rb:13:5:13:40 | call to find_by |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:6:3:6:24 | call to belongs_to |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:9:5:9:68 | call to find |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:19:5:19:25 | call to destroy_by |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:28:5:28:45 | call to calculate |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:29:5:29:43 | call to delete_by |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:30:5:30:46 | call to destroy_by |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:31:5:31:35 | call to where |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:34:5:34:14 | call to where |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:35:5:35:51 | call to authenticate |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:36:5:36:30 | call to find_by_name |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:37:5:37:36 | call to not_a_find_by_method |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:46:5:46:33 | call to delete_by |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:52:5:52:29 | call to order |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:56:7:56:40 | call to find_by |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:60:5:60:33 | call to find_by |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:62:5:62:34 | call to find |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:68:5:68:45 | call to delete_by |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:72:5:72:24 | call to create |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:76:5:76:66 | call to create |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:80:5:80:68 | call to create |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:84:5:84:16 | call to create |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:88:5:88:27 | call to update |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:92:5:92:69 | call to update |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:96:5:96:71 | call to update |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:102:13:102:54 | call to annotate |
| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:106:13:106:77 | call to annotate |
| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:19:5:19:25 | call to destroy_by |
| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:68:5:68:45 | call to delete_by |
| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:72:5:72:24 | call to create |
| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:76:5:76:66 | call to create |
| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:80:5:80:68 | call to create |
| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:84:5:84:16 | call to create |
| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:88:5:88:27 | call to update |
| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:92:5:92:69 | call to update |
| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:96:5:96:71 | call to update |
| associations.rb:1:1:3:3 | Author | associations.rb:2:3:2:17 | call to has_many |
| associations.rb:1:1:3:3 | Author | associations.rb:19:11:19:20 | call to new |
| associations.rb:5:1:9:3 | Post | associations.rb:6:3:6:20 | call to belongs_to |
| associations.rb:5:1:9:3 | Post | associations.rb:7:3:7:20 | call to has_many |
| associations.rb:5:1:9:3 | Post | associations.rb:8:3:8:31 | call to has_and_belongs_to_many |
| associations.rb:11:1:13:3 | Tag | associations.rb:12:3:12:32 | call to has_and_belongs_to_many |
| associations.rb:15:1:17:3 | Comment | associations.rb:16:3:16:18 | call to belongs_to |
potentiallyUnsafeSqlExecutingMethodCall
| ActiveRecord.rb:9:5:9:68 | call to find |
| ActiveRecord.rb:19:5:19:25 | call to destroy_by |

Просмотреть файл

@ -9,9 +9,19 @@ query predicate activeRecordInstances(ActiveRecordInstance i) { any() }
query predicate activeRecordSqlExecutionRanges(ActiveRecordSqlExecutionRange range) { any() }
query predicate activeRecordModelClassMethodCalls(ActiveRecordModelClassMethodCall call) { any() }
deprecated query predicate activeRecordModelClassMethodCalls(ActiveRecordModelClassMethodCall call) {
any()
}
query predicate potentiallyUnsafeSqlExecutingMethodCall(PotentiallyUnsafeSqlExecutingMethodCall call) {
query predicate activeRecordModelClassMethodCallsReplacement(
ActiveRecordModelClass cls, DataFlow::CallNode call
) {
call = cls.getClassNode().trackModule().getAMethodCall(_)
}
deprecated query predicate potentiallyUnsafeSqlExecutingMethodCall(
PotentiallyUnsafeSqlExecutingMethodCall call
) {
any()
}