@@ -24,14 +24,17 @@ module ActiveSupport {
24
24
*/
25
25
module String {
26
26
/**
27
- * A call to `String#constantize`, which tries to find a declared constant with the given name.
28
- * Passing user input to this method may result in instantiation of arbitrary Ruby classes.
27
+ * A call to `String#constantize` or `String#safe_constantize`, which
28
+ * tries to find a declared constant with the given name.
29
+ * Passing user input to this method may result in instantiation of
30
+ * arbitrary Ruby classes.
29
31
*/
30
32
class Constantize extends CodeExecution:: Range , DataFlow:: CallNode {
31
33
// We treat this an `UnknownMethodCall` in order to match every call to `constantize` that isn't overridden.
32
34
// We can't (yet) rely on API Graphs or dataflow to tell us that the receiver is a String.
33
35
Constantize ( ) {
34
- this .asExpr ( ) .getExpr ( ) .( UnknownMethodCall ) .getMethodName ( ) = "constantize"
36
+ this .asExpr ( ) .getExpr ( ) .( UnknownMethodCall ) .getMethodName ( ) =
37
+ [ "constantize" , "safe_constantize" ]
35
38
}
36
39
37
40
override DataFlow:: Node getCode ( ) { result = this .getReceiver ( ) }
@@ -49,9 +52,11 @@ module ActiveSupport {
49
52
override MethodCall getACall ( ) {
50
53
result .getMethodName ( ) =
51
54
[
52
- "camelize" , "camelcase" , "classify" , "dasherize" , "deconstantize" , "demodulize" ,
53
- "foreign_key" , "humanize" , "indent" , "parameterize" , "pluralize" , "singularize" ,
54
- "squish" , "strip_heredoc" , "tableize" , "titlecase" , "titleize" , "underscore" ,
55
+ "at" , "camelize" , "camelcase" , "classify" , "dasherize" , "deconstantize" , "demodulize" ,
56
+ "first" , "foreign_key" , "from" , "html_safe" , "humanize" , "indent" , "indent!" ,
57
+ "inquiry" , "last" , "mb_chars" , "parameterize" , "pluralize" , "remove" , "remove!" ,
58
+ "singularize" , "squish" , "squish!" , "strip_heredoc" , "tableize" , "titlecase" ,
59
+ "titleize" , "to" , "truncate" , "truncate_bytes" , "truncate_words" , "underscore" ,
55
60
"upcase_first"
56
61
]
57
62
}
@@ -62,6 +67,112 @@ module ActiveSupport {
62
67
}
63
68
}
64
69
70
+ /**
71
+ * Extensions to the `Object` class.
72
+ */
73
+ module Object {
74
+ /** Flow summary for methods which can return the receiver. */
75
+ private class IdentitySummary extends SimpleSummarizedCallable {
76
+ IdentitySummary ( ) { this = [ "presence" , "deep_dup" ] }
77
+
78
+ override predicate propagatesFlowExt ( string input , string output , boolean preservesValue ) {
79
+ input = "Argument[self]" and
80
+ output = "ReturnValue" and
81
+ preservesValue = true
82
+ }
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Extensions to the `Hash` class.
88
+ */
89
+ module Hash {
90
+ private class WithIndifferentAccessSummary extends SimpleSummarizedCallable {
91
+ WithIndifferentAccessSummary ( ) { this = "with_indifferent_access" }
92
+
93
+ override predicate propagatesFlowExt ( string input , string output , boolean preservesValue ) {
94
+ input = "Argument[self].Element[any]" and
95
+ output = "ReturnValue.Element[any]" and
96
+ preservesValue = true
97
+ }
98
+ }
99
+
100
+ private class TransformSummary extends SimpleSummarizedCallable {
101
+ TransformSummary ( ) {
102
+ this =
103
+ [
104
+ "stringify_keys" , "to_options" , "symbolize_keys" , "deep_stringify_keys" ,
105
+ "deep_symbolize_keys" , "with_indifferent_access"
106
+ ]
107
+ }
108
+
109
+ override predicate propagatesFlowExt ( string input , string output , boolean preservesValue ) {
110
+ input = "Argument[self].Element[any]" and
111
+ output = "ReturnValue.Element[?]" and
112
+ preservesValue = true
113
+ }
114
+ }
115
+
116
+ private string getExtractComponent ( MethodCall mc , int i ) {
117
+ mc .getMethodName ( ) = "extract!" and
118
+ result = DataFlow:: Content:: getKnownElementIndex ( mc .getArgument ( i ) ) .serialize ( )
119
+ }
120
+
121
+ /**
122
+ * A flow summary for `Hash#extract!`. This method removes the key/value pairs
123
+ * matching the given keys from the receiver and returns them (as a Hash).
124
+ *
125
+ * Example:
126
+ *
127
+ * ```rb
128
+ * hash = { a: 1, b: 2, c: 3, d: 4 }
129
+ * hash.extract!(:a, :b) # => {:a=>1, :b=>2}
130
+ * hash # => {:c=>3, :d=>4}
131
+ * ```
132
+ *
133
+ * There is value flow from elements corresponding to keys in the
134
+ * arguments (`:a` and `:b` in the example) to elements in
135
+ * the return value.
136
+ * There is also value flow from any element corresponding to a key _not_
137
+ * mentioned in the arguments to an element in `self`, including elements
138
+ * at unknown keys.
139
+ */
140
+ private class ExtractSummary extends SummarizedCallable {
141
+ MethodCall mc ;
142
+
143
+ ExtractSummary ( ) {
144
+ mc .getMethodName ( ) = "extract!" and
145
+ this =
146
+ "extract!(" +
147
+ concat ( int i , string s | s = getExtractComponent ( mc , i ) | s , "," order by i ) + ")"
148
+ }
149
+
150
+ final override MethodCall getACall ( ) { result = mc }
151
+
152
+ override predicate propagatesFlowExt ( string input , string output , boolean preservesValue ) {
153
+ (
154
+ exists ( string s | s = getExtractComponent ( mc , _) |
155
+ input = "Argument[self].Element[" + s + "!]" and
156
+ output = "ReturnValue.Element[" + s + "!]"
157
+ )
158
+ or
159
+ // Argument[self].WithoutElement[:a!, :b!].WithElement[any] means
160
+ // "an element of self whose key is not :a or :b, including elements
161
+ // with unknown keys"
162
+ input =
163
+ "Argument[self]" +
164
+ concat ( int i , string s |
165
+ s = getExtractComponent ( mc , i )
166
+ |
167
+ ".WithoutElement[" + s + "!]" order by i
168
+ ) + ".WithElement[any]" and
169
+ output = "Argument[self]"
170
+ ) and
171
+ preservesValue = true
172
+ }
173
+ }
174
+ }
175
+
65
176
/**
66
177
* Extensions to the `Enumerable` module.
67
178
*/
0 commit comments