@@ -24,14 +24,17 @@ module ActiveSupport {
2424 */
2525 module String {
2626 /**
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.
2931 */
3032 class Constantize extends CodeExecution:: Range , DataFlow:: CallNode {
3133 // We treat this an `UnknownMethodCall` in order to match every call to `constantize` that isn't overridden.
3234 // We can't (yet) rely on API Graphs or dataflow to tell us that the receiver is a String.
3335 Constantize ( ) {
34- this .asExpr ( ) .getExpr ( ) .( UnknownMethodCall ) .getMethodName ( ) = "constantize"
36+ this .asExpr ( ) .getExpr ( ) .( UnknownMethodCall ) .getMethodName ( ) =
37+ [ "constantize" , "safe_constantize" ]
3538 }
3639
3740 override DataFlow:: Node getCode ( ) { result = this .getReceiver ( ) }
@@ -49,9 +52,11 @@ module ActiveSupport {
4952 override MethodCall getACall ( ) {
5053 result .getMethodName ( ) =
5154 [
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" ,
5560 "upcase_first"
5661 ]
5762 }
@@ -62,6 +67,112 @@ module ActiveSupport {
6267 }
6368 }
6469
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+
65176 /**
66177 * Extensions to the `Enumerable` module.
67178 */
0 commit comments