@@ -57,14 +57,26 @@ module ActiveStorage {
5757 // "activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Method[compose].Argument[0].Element[any]",
5858 // ActiveStorage::Blob.find_signed(!) : Blob
5959 "activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Method[find_signed,find_signed!].ReturnValue" ,
60- // ActiveStorage::Attachment#blob : Blob
61- "activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Attachment].Instance.Method[blob].ReturnValue" ,
62- // ActiveStorage::Attachment delegates method calls to its associated Blob
63- "activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Attachment].Instance" ,
6460 ]
6561 }
6662 }
6763
64+ private class BlobInstance extends DataFlow:: Node {
65+ BlobInstance ( ) {
66+ this = ModelOutput:: getATypeNode ( "activestorage" , "Blob" ) .getAValueReachableFromSource ( )
67+ or
68+ // ActiveStorage::Attachment#blob : Blob
69+ exists ( DataFlow:: CallNode call |
70+ call = this and
71+ call .getReceiver ( ) instanceof AttachmentInstance and
72+ call .getMethodName ( ) = "blob"
73+ )
74+ or
75+ // ActiveStorage::Attachment delegates method calls to its associated Blob
76+ this instanceof AttachmentInstance
77+ }
78+ }
79+
6880 /**
6981 * Method calls on `ActiveStorage::Blob` that send HTTP requests.
7082 */
@@ -78,11 +90,16 @@ module ActiveStorage {
7890 .getASubclass * ( )
7991 .getAMethodCall ( [ "create_after_unfurling!" , "create_and_upload!" ] ) ,
8092 // Instance methods
81- ModelOutput:: getATypeNode ( "activestorage" , "Blob" )
82- .getAMethodCall ( [
83- "upload" , "upload_without_unfurling" , "download" , "download_chunk" , "delete" ,
84- "purge"
85- ] )
93+ any ( BlobInstance i , DataFlow:: CallNode c |
94+ i .( DataFlow:: LocalSourceNode ) .flowsTo ( c .getReceiver ( ) ) and
95+ c .getMethodName ( ) =
96+ [
97+ "upload" , "upload_without_unfurling" , "download" , "download_chunk" , "delete" ,
98+ "purge"
99+ ]
100+ |
101+ c
102+ )
86103 ] .asExpr ( ) .getExpr ( )
87104 }
88105
@@ -110,7 +127,7 @@ module ActiveStorage {
110127 }
111128
112129 /**
113- * An ActiveStorage attachment, instantiated via an association with an
130+ * An ActiveStorage attachment, instantiated directly or via an association with an
114131 * ActiveRecord model.
115132 *
116133 * ```rb
@@ -120,18 +137,30 @@ module ActiveStorage {
120137 *
121138 * user = User.find(id)
122139 * user.avatar
140+ * ActiveStorage::Attachment.new
123141 * ```
124142 */
125- private class AttachmentInstance extends DataFlow:: CallNode {
143+ class AttachmentInstance extends DataFlow:: Node {
126144 AttachmentInstance ( ) {
127- exists ( Association assoc , string model | model = assoc .getTargetModelName ( ) |
128- this .getReceiver ( ) .( ActiveRecordInstance ) .getClass ( ) = assoc .getSourceClass ( ) and
145+ this =
146+ API:: getTopLevelMember ( "ActiveStorage" )
147+ .getMember ( "Attachment" )
148+ .getInstance ( )
149+ .getAValueReachableFromSource ( )
150+ or
151+ exists ( Association assoc , string model , DataFlow:: CallNode call |
152+ model = assoc .getTargetModelName ( )
153+ |
154+ call = this and
155+ call .getReceiver ( ) .( ActiveRecordInstance ) .getClass ( ) = assoc .getSourceClass ( ) and
129156 (
130- assoc .isSingular ( ) and this .getMethodName ( ) = model
157+ assoc .isSingular ( ) and call .getMethodName ( ) = model
131158 or
132- assoc .isCollection ( ) and this .getMethodName ( ) = model
159+ assoc .isCollection ( ) and call .getMethodName ( ) = model
133160 )
134161 )
162+ or
163+ any ( AttachmentInstance i ) .( DataFlow:: LocalSourceNode ) .flowsTo ( this )
135164 }
136165 }
137166
@@ -141,35 +170,41 @@ module ActiveStorage {
141170 */
142171 private class ImageProcessingCall extends DataFlow:: CallNode , SystemCommandExecution:: Range {
143172 ImageProcessingCall ( ) {
144- this =
145- ModelOutput :: getATypeNode ( "activestorage ", "Blob" )
146- . getAMethodCall ( [ "variant" , "preview" , "representation" ] ) or
173+ this . getReceiver ( ) instanceof BlobInstance and
174+ this . getMethodName ( ) = [ "variant ", "preview" , "representation" ]
175+ or
147176 this =
148177 API:: getTopLevelMember ( "ActiveStorage" )
149178 .getMember ( "Attachment" )
150179 .getInstance ( )
151- .getAMethodCall ( [ "variant" , "preview" , "representation" ] ) or
180+ .getAMethodCall ( [ "variant" , "preview" , "representation" ] )
181+ or
152182 this =
153183 API:: getTopLevelMember ( "ActiveStorage" )
154184 .getMember ( "Variation" )
155- .getAMethodCall ( [ "new" , "wrap" , "encode" ] ) or
185+ .getAMethodCall ( [ "new" , "wrap" , "encode" ] )
186+ or
156187 this =
157188 API:: getTopLevelMember ( "ActiveStorage" )
158189 .getMember ( "Variation" )
159190 .getInstance ( )
160- .getAMethodCall ( "transformations=" ) or
191+ .getAMethodCall ( "transformations=" )
192+ or
161193 this =
162194 API:: getTopLevelMember ( "ActiveStorage" )
163195 .getMember ( "Transformers" )
164196 .getMember ( "ImageProcessingTransformer" )
165- .getAMethodCall ( "new" ) or
197+ .getAMethodCall ( "new" )
198+ or
166199 this =
167200 API:: getTopLevelMember ( "ActiveStorage" )
168201 .getMember ( [ "Preview" , "VariantWithRecord" ] )
169- .getAMethodCall ( "new" ) or
202+ .getAMethodCall ( "new" )
203+ or
170204 // `ActiveStorage.paths` is a global hash whose values are passed to
171205 // a `system` call.
172- this = API:: getTopLevelMember ( "ActiveStorage" ) .getAMethodCall ( "paths=" ) or
206+ this = API:: getTopLevelMember ( "ActiveStorage" ) .getAMethodCall ( "paths=" )
207+ or
173208 // `ActiveStorage.video_preview_arguments` is passed to a `system` call.
174209 this = API:: getTopLevelMember ( "ActiveStorage" ) .getAMethodCall ( "video_preview_arguments=" )
175210 }
@@ -187,57 +222,4 @@ module ActiveStorage {
187222
188223 override DataFlow:: Node getCode ( ) { result = this .getArgument ( 0 ) }
189224 }
190-
191- /**
192- * Adds ActiveStorage instances to the API graph.
193- * Source code may not mention `ActiveStorage` or `ActiveStorage::Attachment`,
194- * so we add synthetic nodes for them.
195- */
196- private module ApiNodes {
197- class ActiveStorage extends API:: EntryPoint {
198- ActiveStorage ( ) { this = "ActiveStorage" }
199-
200- override predicate edge ( API:: Node pred , API:: Label:: ApiLabel lbl ) {
201- pred = API:: root ( ) and lbl = API:: Label:: member ( "ActiveStorage" )
202- }
203- }
204-
205- class Attachment extends API:: EntryPoint {
206- Attachment ( ) { this = "ActiveStorage::Attachment" }
207-
208- override predicate edge ( API:: Node pred , API:: Label:: ApiLabel lbl ) {
209- pred = API:: getTopLevelMember ( "ActiveStorage" ) and
210- lbl = API:: Label:: member ( "Attachment" )
211- }
212- }
213-
214- class AttachmentNew extends API:: EntryPoint {
215- AttachmentNew ( ) { this = "ActiveStorage::Attachment.new" }
216-
217- override predicate edge ( API:: Node pred , API:: Label:: ApiLabel lbl ) {
218- pred = API:: getTopLevelMember ( "ActiveStorage" ) .getMember ( "Attachment" ) and
219- lbl = API:: Label:: method ( "new" )
220- }
221- }
222-
223- /**
224- * An API entry point for instances of `ActiveStorage::Attachment`.
225- * These arise from calls to methods generated by `has_one_attached` and
226- * `has_many_attached` associations.
227- */
228- class AttachmentInstanceNode extends API:: EntryPoint {
229- AttachmentInstanceNode ( ) { this = "ActiveStorage::Attachment.new.ReturnValue" }
230-
231- override predicate edge ( API:: Node pred , API:: Label:: ApiLabel lbl ) {
232- pred = API:: getTopLevelMember ( "ActiveStorage" ) .getMember ( "Attachment" ) .getMethod ( "new" ) and
233- lbl = API:: Label:: return ( )
234- }
235-
236- override DataFlow:: LocalSourceNode getAUse ( ) { result = any ( AttachmentInstance i ) }
237-
238- override DataFlow:: CallNode getACall ( ) {
239- any ( AttachmentInstance i ) .flowsTo ( result .getReceiver ( ) )
240- }
241- }
242- }
243225}
0 commit comments