Skip to content

Commit 12a3251

Browse files
committed
Ruby: extend FileSystemReadAccess and restructure some Files.qll classes
1 parent 08b6a17 commit 12a3251

File tree

3 files changed

+60
-50
lines changed

3 files changed

+60
-50
lines changed

ruby/ql/lib/codeql/ruby/frameworks/Files.qll

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -52,22 +52,22 @@ private DataFlow::Node fileInstance() {
5252
)
5353
}
5454

55-
private string ioFileReaderClassMethodName() {
55+
private string ioReaderClassMethodName() {
5656
result = ["binread", "foreach", "read", "readlines", "try_convert"]
5757
}
5858

59-
private string ioFileReaderInstanceMethodName() {
59+
private string ioReaderInstanceMethodName() {
6060
result =
6161
[
6262
"getbyte", "getc", "gets", "pread", "read", "read_nonblock", "readbyte", "readchar",
6363
"readline", "readlines", "readpartial", "sysread"
6464
]
6565
}
6666

67-
private string ioFileReaderMethodName(boolean classMethodCall) {
68-
classMethodCall = true and result = ioFileReaderClassMethodName()
67+
private string ioReaderMethodName(string receiverKind) {
68+
receiverKind = "class" and result = ioReaderClassMethodName()
6969
or
70-
classMethodCall = false and result = ioFileReaderInstanceMethodName()
70+
receiverKind = "instance" and result = ioReaderInstanceMethodName()
7171
}
7272

7373
/**
@@ -110,72 +110,81 @@ module IO {
110110
*
111111
* This class includes reads both from shell commands and reads from the
112112
* filesystem. For working with filesystem accesses specifically, see
113-
* `IOFileReader` or the `FileSystemReadAccess` concept.
113+
* `FileReader` or the `FileSystemReadAccess` concept.
114114
*/
115115
class IOReader extends DataFlow::CallNode {
116-
private boolean classMethodCall;
117-
private string api;
116+
private string receiverKind;
118117

119118
IOReader() {
120-
// Class methods
121-
api = ["File", "IO"] and
122-
classMethodCall = true and
123-
this = API::getTopLevelMember(api).getAMethodCall(ioFileReaderMethodName(classMethodCall))
119+
// `IO` class method calls
120+
receiverKind = "class" and
121+
this = API::getTopLevelMember("IO").getAMethodCall(ioReaderMethodName(receiverKind))
124122
or
125-
// IO instance methods
126-
classMethodCall = false and
127-
api = "IO" and
123+
// `IO` instance method calls
124+
receiverKind = "instance" and
128125
exists(IOInstanceStrict ii |
129126
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)
127+
this.getMethodName() = ioReaderMethodName(receiverKind)
139128
)
140129
// TODO: enumeration style methods such as `each`, `foreach`, etc.
141130
}
142131

143132
/**
144-
* Returns the most specific core class used for this read, `IO` or `File`
133+
* Gets a string representation of the receiver kind, either "class" or "instance".
145134
*/
146-
string getAPI() { result = api }
147-
148-
predicate isClassMethodCall() { classMethodCall = true }
135+
string getReceiverKind() { result = receiverKind }
149136
}
150137

151138
/**
152139
* A `DataFlow::CallNode` that reads data from the filesystem using the `IO`
153-
* class. For example, the `IO.read call in:
140+
* or `File` classes. For example, the `IO.read call in:
154141
*
155142
* ```rb
156143
* IO.read("foo.txt")
157144
* ```
158145
*
159146
* reads the file `foo.txt` and returns its contents as a string.
160147
*/
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.
148+
class FileReader extends DataFlow::CallNode, FileSystemReadAccess::Range {
149+
private string receiverKind;
150+
private string api;
151+
152+
FileReader() {
153+
// A viable `IOReader` that could feasibly read from the filesystem
154+
api = "IO" and
155+
receiverKind = this.(IOReader).getReceiverKind() and
168156
not pathArgSpawnsSubprocess(this.getArgument(0).asExpr().getExpr())
157+
or
158+
api = "File" and
159+
(
160+
// `File` class method calls
161+
receiverKind = "class" and
162+
this = API::getTopLevelMember(api).getAMethodCall(ioReaderMethodName(receiverKind))
163+
or
164+
// `File` instance method calls
165+
receiverKind = "instance" and
166+
exists(File::FileInstance fi |
167+
this.getReceiver() = fi and
168+
this.getMethodName() = ioReaderMethodName(receiverKind)
169+
)
170+
)
171+
// TODO: enumeration style methods such as `each`, `foreach`, etc.
169172
}
170173

171-
// TODO: can we infer a path argument for instance method calls?
174+
// TODO: Currently this only handles class method calls.
175+
// Can we infer a path argument for instance method calls?
172176
// e.g. by tracing back to the instantiation of that instance
173177
override DataFlow::Node getAPathArgument() {
174-
result = this.getArgument(0) and this.isClassMethodCall()
178+
result = this.getArgument(0) and receiverKind = "class"
175179
}
176180

177181
// This class represents calls that return data
178182
override DataFlow::Node getADataNode() { result = this }
183+
184+
/**
185+
* Returns the most specific core class used for this read, `IO` or `File`
186+
*/
187+
string getAPI() { result = api }
179188
}
180189
}
181190

@@ -210,7 +219,7 @@ module File {
210219
* puts f.read()
211220
* ```
212221
*/
213-
class FileModuleReader extends IO::IOFileReader {
222+
class FileModuleReader extends IO::FileReader {
214223
FileModuleReader() { this.getAPI() = "File" }
215224
}
216225

ruby/ql/test/library-tests/frameworks/files/Files.expected

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,24 @@ ioInstances
3636
| Files.rb:24:19:24:40 | call to open |
3737
| Files.rb:35:1:35:56 | ... = ... |
3838
| Files.rb:35:13:35:56 | call to open |
39-
fileReaders
39+
fileModuleReaders
4040
| Files.rb:7:13:7:32 | call to readlines |
4141
ioReaders
42-
| Files.rb:7:13:7:32 | call to readlines | File |
43-
| Files.rb:20:13:20:25 | call to read | IO |
44-
| Files.rb:29:12:29:29 | call to read | IO |
45-
| Files.rb:32:8:32:23 | call to read | IO |
46-
ioFileReaders
47-
| Files.rb:7:13:7:32 | call to readlines | File |
48-
| Files.rb:29:12:29:29 | call to read | IO |
42+
| Files.rb:20:13:20:25 | call to read |
43+
| Files.rb:29:12:29:29 | call to read |
44+
| Files.rb:32:8:32:23 | call to read |
45+
fileReaders
46+
| Files.rb:7:13:7:32 | call to readlines |
47+
| Files.rb:20:13:20:25 | call to read |
48+
| Files.rb:29:12:29:29 | call to read |
4949
fileModuleFilenameSources
5050
| Files.rb:10:6:10:18 | call to path |
5151
| Files.rb:11:6:11:21 | call to to_path |
5252
fileUtilsFilenameSources
5353
| Files.rb:14:8:14:43 | call to makedirs |
5454
fileSystemReadAccesses
5555
| Files.rb:7:13:7:32 | call to readlines |
56+
| Files.rb:20:13:20:25 | call to read |
5657
| Files.rb:29:12:29:29 | call to read |
5758
fileNameSources
5859
| Files.rb:10:6:10:18 | call to path |

ruby/ql/test/library-tests/frameworks/files/Files.ql

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ query predicate fileInstances(File::FileInstance i) { any() }
66

77
query predicate ioInstances(IO::IOInstance i) { any() }
88

9-
query predicate fileReaders(File::FileModuleReader r) { any() }
9+
query predicate fileModuleReaders(File::FileModuleReader r) { any() }
1010

11-
query predicate ioReaders(IO::IOReader r, string api) { api = r.getAPI() }
11+
query predicate ioReaders(IO::IOReader r) { any() }
1212

13-
query predicate ioFileReaders(IO::IOFileReader r, string api) { api = r.getAPI() }
13+
query predicate fileReaders(IO::FileReader r) { any() }
1414

1515
query predicate fileModuleFilenameSources(File::FileModuleFilenameSource s) { any() }
1616

0 commit comments

Comments
 (0)