@@ -52,20 +52,110 @@ private DataFlow::Node fileInstance() {
52
52
)
53
53
}
54
54
55
- private string ioReaderClassMethodName ( ) { result = [ "binread" , "foreach" , "read" , "readlines" ] }
56
-
57
- private string ioReaderInstanceMethodName ( ) {
58
- result =
59
- [
60
- "getbyte" , "getc" , "gets" , "pread" , "read" , "read_nonblock" , "readbyte" , "readchar" ,
61
- "readline" , "readlines" , "readpartial" , "sysread"
62
- ]
55
+ abstract private class IOOrFileMethodCall extends DataFlow:: CallNode {
56
+ // TODO: Currently this only handles class method calls.
57
+ // Can we infer a path argument for instance method calls?
58
+ // e.g. by tracing back to the instantiation of that instance
59
+ DataFlow:: Node getAPathArgumentImpl ( ) {
60
+ result = this .getArgument ( 0 ) and this .getReceiverKind ( ) = "class"
61
+ }
62
+
63
+ /**
64
+ * Holds if this call appears to read/write from/to a spawned subprocess,
65
+ * rather than to/from a file.
66
+ */
67
+ predicate spawnsSubprocess ( ) {
68
+ pathArgSpawnsSubprocess ( this .getAPathArgumentImpl ( ) .asExpr ( ) .getExpr ( ) )
69
+ }
70
+
71
+ /** Gets the API used to perform this call, either "IO" or "File" */
72
+ abstract string getAPI ( ) ;
73
+
74
+ /** Gets a node representing the data read or written by this call */
75
+ abstract DataFlow:: Node getADataNodeImpl ( ) ;
76
+
77
+ /** Gets a string representation of the receiver kind, either "class" or "instance". */
78
+ abstract string getReceiverKind ( ) ;
63
79
}
64
80
65
- private string ioReaderMethodName ( string receiverKind ) {
66
- receiverKind = "class" and result = ioReaderClassMethodName ( )
67
- or
68
- receiverKind = "instance" and result = ioReaderInstanceMethodName ( )
81
+ /**
82
+ * A method call that performs a read using either the `IO` or `File` classes.
83
+ */
84
+ private class IOOrFileReadMethodCall extends IOOrFileMethodCall {
85
+ private string api ;
86
+ private string receiverKind ;
87
+
88
+ IOOrFileReadMethodCall ( ) {
89
+ exists ( string methodName | methodName = this .getMethodName ( ) |
90
+ // e.g. `{IO,File}.readlines("foo.txt")`
91
+ receiverKind = "class" and
92
+ methodName = [ "binread" , "foreach" , "read" , "readlines" ] and
93
+ api = [ "IO" , "File" ] and
94
+ this = API:: getTopLevelMember ( api ) .getAMethodCall ( methodName )
95
+ or
96
+ // e.g. `{IO,File}.new("foo.txt", "r").getc`
97
+ receiverKind = "interface" and
98
+ (
99
+ methodName =
100
+ [
101
+ "getbyte" , "getc" , "gets" , "pread" , "read" , "read_nonblock" , "readbyte" , "readchar" ,
102
+ "readline" , "readlines" , "readpartial" , "sysread"
103
+ ] and
104
+ (
105
+ this .getReceiver ( ) = ioInstance ( ) and api = "IO"
106
+ or
107
+ this .getReceiver ( ) = fileInstance ( ) and api = "File"
108
+ )
109
+ )
110
+ )
111
+ }
112
+
113
+ override string getAPI ( ) { result = api }
114
+
115
+ override DataFlow:: Node getADataNodeImpl ( ) { result = this }
116
+
117
+ override string getReceiverKind ( ) { result = receiverKind }
118
+ }
119
+
120
+ /**
121
+ * A method call that performs a write using either the `IO` or `File` classes.
122
+ */
123
+ private class IOOrFileWriteMethodCall extends IOOrFileMethodCall {
124
+ private string api ;
125
+ private string receiverKind ;
126
+ private DataFlow:: Node dataNode ;
127
+
128
+ IOOrFileWriteMethodCall ( ) {
129
+ exists ( string methodName | methodName = this .getMethodName ( ) |
130
+ // e.g. `{IO,File}.write("foo.txt", "hello\n")`
131
+ receiverKind = "class" and
132
+ api = [ "IO" , "File" ] and
133
+ this = API:: getTopLevelMember ( api ) .getAMethodCall ( methodName ) and
134
+ methodName = [ "binwrite" , "write" ] and
135
+ dataNode = this .getArgument ( 1 )
136
+ or
137
+ // e.g. `{IO,File}.new("foo.txt", "a+).puts("hello")`
138
+ receiverKind = "interface" and
139
+ (
140
+ this .getReceiver ( ) = ioInstance ( ) and api = "IO"
141
+ or
142
+ this .getReceiver ( ) = fileInstance ( ) and api = "File"
143
+ ) and
144
+ (
145
+ methodName = [ "<<" , "print" , "putc" , "puts" , "syswrite" , "pwrite" , "write_nonblock" ] and
146
+ dataNode = this .getArgument ( 0 )
147
+ or
148
+ // Any argument to these methods may be written as data
149
+ methodName = [ "printf" , "write" ] and dataNode = this .getArgument ( _)
150
+ )
151
+ )
152
+ }
153
+
154
+ override string getAPI ( ) { result = api }
155
+
156
+ override DataFlow:: Node getADataNodeImpl ( ) { result = dataNode }
157
+
158
+ override string getReceiverKind ( ) { result = receiverKind }
69
159
}
70
160
71
161
/**
@@ -111,31 +201,31 @@ module IO {
111
201
* This class includes only reads that use the `IO` class directly, not those
112
202
* that use a subclass of `IO` such as `File`.
113
203
*/
114
- class IOReader extends DataFlow:: CallNode {
115
- private string receiverKind ;
116
-
117
- IOReader ( ) {
118
- // `IO` class method calls
119
- receiverKind = "class" and
120
- this = API:: getTopLevelMember ( "IO" ) .getAMethodCall ( ioReaderMethodName ( receiverKind ) )
121
- or
122
- // `IO` instance method calls
123
- receiverKind = "instance" and
124
- exists ( IOInstanceStrict ii |
125
- this .getReceiver ( ) = ii and
126
- this .getMethodName ( ) = ioReaderMethodName ( receiverKind )
127
- )
128
- // TODO: enumeration style methods such as `each`, `foreach`, etc.
129
- }
204
+ class IOReader extends IOOrFileReadMethodCall {
205
+ IOReader ( ) { this .getAPI ( ) = "IO" }
206
+ }
130
207
131
- /**
132
- * Gets a string representation of the receiver kind, either "class" or "instance".
133
- */
134
- string getReceiverKind ( ) { result = receiverKind }
208
+ /**
209
+ * A `DataFlow::CallNode` that writes data using the `IO` class. For example,
210
+ * the `write` and `puts` calls in:
211
+ *
212
+ * ```rb
213
+ * # writes the string `hello world` to the file `foo.txt`
214
+ * IO.write("foo.txt", "hello world")
215
+ *
216
+ * # appends the string `hello again\n` to the file `foo.txt`
217
+ * IO.new(IO.sysopen("foo.txt", "a")).puts("hello again")
218
+ * ```
219
+ *
220
+ * This class includes only writes that use the `IO` class directly, not those
221
+ * that use a subclass of `IO` such as `File`.
222
+ */
223
+ class IOWriter extends IOOrFileWriteMethodCall {
224
+ IOWriter ( ) { this .getAPI ( ) = "IO" }
135
225
}
136
226
137
227
/**
138
- * A `DataFlow::CallNode` that reads data from the filesystem using the `IO`
228
+ * A `DataFlow::CallNode` that reads data to the filesystem using the `IO`
139
229
* or `File` classes. For example, the `IO.read` and `File#readline` calls in:
140
230
*
141
231
* ```rb
@@ -146,46 +236,32 @@ module IO {
146
236
* File.new("foo.txt").readline
147
237
* ```
148
238
*/
149
- class FileReader extends DataFlow:: CallNode , FileSystemReadAccess:: Range {
150
- private string receiverKind ;
151
- private string api ;
152
-
153
- FileReader ( ) {
154
- // A viable `IOReader` that could feasibly read from the filesystem
155
- api = "IO" and
156
- receiverKind = this .( IOReader ) .getReceiverKind ( ) and
157
- not pathArgSpawnsSubprocess ( this .getArgument ( 0 ) .asExpr ( ) .getExpr ( ) )
158
- or
159
- api = "File" and
160
- (
161
- // `File` class method calls
162
- receiverKind = "class" and
163
- this = API:: getTopLevelMember ( api ) .getAMethodCall ( ioReaderMethodName ( receiverKind ) )
164
- or
165
- // `File` instance method calls
166
- receiverKind = "instance" and
167
- exists ( File:: FileInstance fi |
168
- this .getReceiver ( ) = fi and
169
- this .getMethodName ( ) = ioReaderMethodName ( receiverKind )
170
- )
171
- )
172
- // TODO: enumeration style methods such as `each`, `foreach`, etc.
173
- }
239
+ class FileReader extends IOOrFileReadMethodCall , FileSystemReadAccess:: Range {
240
+ FileReader ( ) { not this .spawnsSubprocess ( ) }
174
241
175
- // TODO: Currently this only handles class method calls.
176
- // Can we infer a path argument for instance method calls?
177
- // e.g. by tracing back to the instantiation of that instance
178
- override DataFlow:: Node getAPathArgument ( ) {
179
- result = this .getArgument ( 0 ) and receiverKind = "class"
180
- }
242
+ override DataFlow:: Node getADataNode ( ) { result = this .getADataNodeImpl ( ) }
181
243
182
- // This class represents calls that return data
183
- override DataFlow :: Node getADataNode ( ) { result = this }
244
+ override DataFlow :: Node getAPathArgument ( ) { result = this . getAPathArgumentImpl ( ) }
245
+ }
184
246
185
- /**
186
- * Returns the most specific core class used for this read, `IO` or `File`
187
- */
188
- string getAPI ( ) { result = api }
247
+ /**
248
+ * A `DataFlow::CallNode` that reads data from the filesystem using the `IO`
249
+ * or `File` classes. For example, the `write` and `puts` calls in:
250
+ *
251
+ * ```rb
252
+ * # writes the string `hello world` to the file `foo.txt`
253
+ * IO.write("foo.txt", "hello world")
254
+ *
255
+ * # appends the string `hello again\n` to the file `foo.txt`
256
+ * File.new("foo.txt", "a").puts("hello again")
257
+ * ```
258
+ */
259
+ class FileWriter extends IOOrFileWriteMethodCall , FileSystemWriteAccess:: Range {
260
+ FileWriter ( ) { not this .spawnsSubprocess ( ) }
261
+
262
+ override DataFlow:: Node getADataNode ( ) { result = this .getADataNodeImpl ( ) }
263
+
264
+ override DataFlow:: Node getAPathArgument ( ) { result = this .getAPathArgumentImpl ( ) }
189
265
}
190
266
}
191
267
@@ -231,6 +307,10 @@ module File {
231
307
*/
232
308
class FileModuleReader extends IO:: FileReader {
233
309
FileModuleReader ( ) { this .getAPI ( ) = "File" }
310
+
311
+ override DataFlow:: Node getADataNode ( ) { result = this .getADataNodeImpl ( ) }
312
+
313
+ override DataFlow:: Node getAPathArgument ( ) { result = this .getAPathArgumentImpl ( ) }
234
314
}
235
315
236
316
/**
0 commit comments