@@ -34,7 +34,7 @@ private predicate pathArgSpawnsSubprocess(Expr arg) {
34
34
private DataFlow:: Node fileInstanceInstantiation ( ) {
35
35
result = API:: getTopLevelMember ( "File" ) .getAnInstantiation ( )
36
36
or
37
- result = API:: getTopLevelMember ( "File" ) .getAMethodCall ( "open" )
37
+ result = API:: getTopLevelMember ( "File" ) .getAMethodCall ( [ "open" , "try_convert" ] )
38
38
or
39
39
// Calls to `Kernel.open` can yield `File` instances
40
40
result .( KernelMethodCall ) .getMethodName ( ) = "open" and
@@ -52,22 +52,20 @@ private DataFlow::Node fileInstance() {
52
52
)
53
53
}
54
54
55
- private string ioFileReaderClassMethodName ( ) {
56
- result = [ "binread" , "foreach" , "read" , "readlines" , "try_convert" ]
57
- }
55
+ private string ioReaderClassMethodName ( ) { result = [ "binread" , "foreach" , "read" , "readlines" ] }
58
56
59
- private string ioFileReaderInstanceMethodName ( ) {
57
+ private string ioReaderInstanceMethodName ( ) {
60
58
result =
61
59
[
62
60
"getbyte" , "getc" , "gets" , "pread" , "read" , "read_nonblock" , "readbyte" , "readchar" ,
63
61
"readline" , "readlines" , "readpartial" , "sysread"
64
62
]
65
63
}
66
64
67
- private string ioFileReaderMethodName ( boolean classMethodCall ) {
68
- classMethodCall = true and result = ioFileReaderClassMethodName ( )
65
+ private string ioReaderMethodName ( string receiverKind ) {
66
+ receiverKind = "class" and result = ioReaderClassMethodName ( )
69
67
or
70
- classMethodCall = false and result = ioFileReaderInstanceMethodName ( )
68
+ receiverKind = "instance" and result = ioReaderInstanceMethodName ( )
71
69
}
72
70
73
71
/**
@@ -100,82 +98,94 @@ module IO {
100
98
101
99
/**
102
100
* A `DataFlow::CallNode` that reads data using the `IO` class. For example,
103
- * the `IO. read call in:
101
+ * the `read` and `readline` calls in:
104
102
*
105
103
* ```rb
104
+ * # invokes the `date` shell command as a subprocess, returning its output as a string
106
105
* IO.read("|date")
107
- * ```
108
106
*
109
- * returns the output of the `date` shell command, invoked as a subprocess.
107
+ * # reads from the file `foo.txt`, returning its first line as a string
108
+ * IO.new(IO.sysopen("foo.txt")).readline
109
+ * ```
110
110
*
111
- * This class includes reads both from shell commands and reads from the
112
- * filesystem. For working with filesystem accesses specifically, see
113
- * `IOFileReader` or the `FileSystemReadAccess` concept.
111
+ * This class includes only reads that use the `IO` class directly, not those
112
+ * that use a subclass of `IO` such as `File`.
114
113
*/
115
114
class IOReader extends DataFlow:: CallNode {
116
- private boolean classMethodCall ;
117
- private string api ;
115
+ private string receiverKind ;
118
116
119
117
IOReader ( ) {
120
- // Class methods
121
- api = [ "File" , "IO" ] and
122
- classMethodCall = true and
123
- this = API:: getTopLevelMember ( api ) .getAMethodCall ( ioFileReaderMethodName ( classMethodCall ) )
118
+ // `IO` class method calls
119
+ receiverKind = "class" and
120
+ this = API:: getTopLevelMember ( "IO" ) .getAMethodCall ( ioReaderMethodName ( receiverKind ) )
124
121
or
125
- // IO instance methods
126
- classMethodCall = false and
127
- api = "IO" and
122
+ // `IO` instance method calls
123
+ receiverKind = "instance" and
128
124
exists ( IOInstanceStrict ii |
129
125
this .getReceiver ( ) = ii and
130
- this .getMethodName ( ) = ioFileReaderMethodName ( classMethodCall )
131
- )
132
- or
133
- // File instance methods
134
- classMethodCall = false and
135
- api = "File" and
136
- exists ( File:: FileInstance fi |
137
- this .getReceiver ( ) = fi and
138
- this .getMethodName ( ) = ioFileReaderMethodName ( classMethodCall )
126
+ this .getMethodName ( ) = ioReaderMethodName ( receiverKind )
139
127
)
140
128
// TODO: enumeration style methods such as `each`, `foreach`, etc.
141
129
}
142
130
143
131
/**
144
- * Returns the most specific core class used for this read, `IO` or `File`
132
+ * Gets a string representation of the receiver kind, either "class" or "instance".
145
133
*/
146
- string getAPI ( ) { result = api }
147
-
148
- predicate isClassMethodCall ( ) { classMethodCall = true }
134
+ string getReceiverKind ( ) { result = receiverKind }
149
135
}
150
136
151
137
/**
152
138
* A `DataFlow::CallNode` that reads data from the filesystem using the `IO`
153
- * class . For example, the `IO.read call in:
139
+ * or `File` classes . For example, the `IO.read` and `File#readline` calls in:
154
140
*
155
141
* ```rb
142
+ * # reads the file `foo.txt` and returns its contents as a string.
156
143
* IO.read("foo.txt")
157
- * ```
158
144
*
159
- * reads the file `foo.txt` and returns its contents as a string.
145
+ * # reads from the file `foo.txt`, returning its first line as a string
146
+ * File.new("foo.txt").readline
147
+ * ```
160
148
*/
161
- class IOFileReader extends IOReader , FileSystemReadAccess:: Range {
162
- IOFileReader ( ) {
163
- this .getAPI ( ) = "File"
164
- or
165
- this .isClassMethodCall ( ) and
166
- // Assume that calls that don't invoke shell commands will instead
167
- // read from a file.
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
168
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.
169
173
}
170
174
171
- // TODO: can we infer a path argument for instance method calls?
175
+ // TODO: Currently this only handles class method calls.
176
+ // Can we infer a path argument for instance method calls?
172
177
// e.g. by tracing back to the instantiation of that instance
173
178
override DataFlow:: Node getAPathArgument ( ) {
174
- result = this .getArgument ( 0 ) and this . isClassMethodCall ( )
179
+ result = this .getArgument ( 0 ) and receiverKind = "class"
175
180
}
176
181
177
182
// This class represents calls that return data
178
183
override DataFlow:: Node getADataNode ( ) { result = this }
184
+
185
+ /**
186
+ * Returns the most specific core class used for this read, `IO` or `File`
187
+ */
188
+ string getAPI ( ) { result = api }
179
189
}
180
190
}
181
191
@@ -210,7 +220,7 @@ module File {
210
220
* puts f.read()
211
221
* ```
212
222
*/
213
- class FileModuleReader extends IO:: IOFileReader {
223
+ class FileModuleReader extends IO:: FileReader {
214
224
FileModuleReader ( ) { this .getAPI ( ) = "File" }
215
225
}
216
226
0 commit comments