@@ -34,7 +34,7 @@ private predicate pathArgSpawnsSubprocess(Expr arg) {
3434private DataFlow:: Node fileInstanceInstantiation ( ) {
3535 result = API:: getTopLevelMember ( "File" ) .getAnInstantiation ( )
3636 or
37- result = API:: getTopLevelMember ( "File" ) .getAMethodCall ( "open" )
37+ result = API:: getTopLevelMember ( "File" ) .getAMethodCall ( [ "open" , "try_convert" ] )
3838 or
3939 // Calls to `Kernel.open` can yield `File` instances
4040 result .( KernelMethodCall ) .getMethodName ( ) = "open" and
@@ -52,22 +52,20 @@ private DataFlow::Node fileInstance() {
5252 )
5353}
5454
55- private string ioFileReaderClassMethodName ( ) {
56- result = [ "binread" , "foreach" , "read" , "readlines" , "try_convert" ]
57- }
55+ private string ioReaderClassMethodName ( ) { result = [ "binread" , "foreach" , "read" , "readlines" ] }
5856
59- private string ioFileReaderInstanceMethodName ( ) {
57+ private string ioReaderInstanceMethodName ( ) {
6058 result =
6159 [
6260 "getbyte" , "getc" , "gets" , "pread" , "read" , "read_nonblock" , "readbyte" , "readchar" ,
6361 "readline" , "readlines" , "readpartial" , "sysread"
6462 ]
6563}
6664
67- private string ioFileReaderMethodName ( boolean classMethodCall ) {
68- classMethodCall = true and result = ioFileReaderClassMethodName ( )
65+ private string ioReaderMethodName ( string receiverKind ) {
66+ receiverKind = "class" and result = ioReaderClassMethodName ( )
6967 or
70- classMethodCall = false and result = ioFileReaderInstanceMethodName ( )
68+ receiverKind = "instance" and result = ioReaderInstanceMethodName ( )
7169}
7270
7371/**
@@ -100,82 +98,94 @@ module IO {
10098
10199 /**
102100 * 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:
104102 *
105103 * ```rb
104+ * # invokes the `date` shell command as a subprocess, returning its output as a string
106105 * IO.read("|date")
107- * ```
108106 *
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+ * ```
110110 *
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`.
114113 */
115114 class IOReader extends DataFlow:: CallNode {
116- private boolean classMethodCall ;
117- private string api ;
115+ private string receiverKind ;
118116
119117 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 ) )
124121 or
125- // IO instance methods
126- classMethodCall = false and
127- api = "IO" and
122+ // `IO` instance method calls
123+ receiverKind = "instance" and
128124 exists ( IOInstanceStrict ii |
129125 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 )
139127 )
140128 // TODO: enumeration style methods such as `each`, `foreach`, etc.
141129 }
142130
143131 /**
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".
145133 */
146- string getAPI ( ) { result = api }
147-
148- predicate isClassMethodCall ( ) { classMethodCall = true }
134+ string getReceiverKind ( ) { result = receiverKind }
149135 }
150136
151137 /**
152138 * 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:
154140 *
155141 * ```rb
142+ * # reads the file `foo.txt` and returns its contents as a string.
156143 * IO.read("foo.txt")
157- * ```
158144 *
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+ * ```
160148 */
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
168157 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.
169173 }
170174
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?
172177 // e.g. by tracing back to the instantiation of that instance
173178 override DataFlow:: Node getAPathArgument ( ) {
174- result = this .getArgument ( 0 ) and this . isClassMethodCall ( )
179+ result = this .getArgument ( 0 ) and receiverKind = "class"
175180 }
176181
177182 // This class represents calls that return data
178183 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 }
179189 }
180190}
181191
@@ -210,7 +220,7 @@ module File {
210220 * puts f.read()
211221 * ```
212222 */
213- class FileModuleReader extends IO:: IOFileReader {
223+ class FileModuleReader extends IO:: FileReader {
214224 FileModuleReader ( ) { this .getAPI ( ) = "File" }
215225 }
216226
0 commit comments