@@ -52,20 +52,110 @@ private DataFlow::Node fileInstance() {
5252 )
5353}
5454
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 ( ) ;
6379}
6480
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 }
69159}
70160
71161/**
@@ -111,31 +201,31 @@ module IO {
111201 * This class includes only reads that use the `IO` class directly, not those
112202 * that use a subclass of `IO` such as `File`.
113203 */
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+ }
130207
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" }
135225 }
136226
137227 /**
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`
139229 * or `File` classes. For example, the `IO.read` and `File#readline` calls in:
140230 *
141231 * ```rb
@@ -146,46 +236,32 @@ module IO {
146236 * File.new("foo.txt").readline
147237 * ```
148238 */
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 ( ) }
174241
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 ( ) }
181243
182- // This class represents calls that return data
183- override DataFlow :: Node getADataNode ( ) { result = this }
244+ override DataFlow :: Node getAPathArgument ( ) { result = this . getAPathArgumentImpl ( ) }
245+ }
184246
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 ( ) }
189265 }
190266}
191267
@@ -231,6 +307,10 @@ module File {
231307 */
232308 class FileModuleReader extends IO:: FileReader {
233309 FileModuleReader ( ) { this .getAPI ( ) = "File" }
310+
311+ override DataFlow:: Node getADataNode ( ) { result = this .getADataNodeImpl ( ) }
312+
313+ override DataFlow:: Node getAPathArgument ( ) { result = this .getAPathArgumentImpl ( ) }
234314 }
235315
236316 /**
0 commit comments