Skip to content

Commit ea46873

Browse files
authored
Merge pull request github#3065 from erik-krogh/PathSinks
Approved by esbena
2 parents 1472bf0 + 9403026 commit ea46873

File tree

6 files changed

+251
-0
lines changed

6 files changed

+251
-0
lines changed

change-notes/1.24/analysis-javascript.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323

2424
* Support for the following frameworks and libraries has been improved:
2525
- [Electron](https://electronjs.org/)
26+
- [fstream](https://www.npmjs.com/package/fstream)
2627
- [Handlebars](https://www.npmjs.com/package/handlebars)
28+
- [jsonfile](https://www.npmjs.com/package/jsonfile)
2729
- [Koa](https://www.npmjs.com/package/koa)
2830
- [Node.js](https://nodejs.org/)
2931
- [Socket.IO](https://socket.io/)
@@ -35,10 +37,17 @@
3537
- [jQuery](https://jquery.com/)
3638
- [lazy-cache](https://www.npmjs.com/package/lazy-cache)
3739
- [mongodb](https://www.npmjs.com/package/mongodb)
40+
- [ncp](https://www.npmjs.com/package/ncp)
41+
- [node-dir](https://www.npmjs.com/package/node-dir)
42+
- [path-exists](https://www.npmjs.com/package/path-exists)
3843
- [react](https://www.npmjs.com/package/react)
44+
- [recursive-readdir](https://www.npmjs.com/package/recursive-readdir)
3945
- [request](https://www.npmjs.com/package/request)
46+
- [rimraf](https://www.npmjs.com/package/rimraf)
4047
- [send](https://www.npmjs.com/package/send)
4148
- [typeahead.js](https://www.npmjs.com/package/typeahead.js)
49+
- [vinyl-fs](https://www.npmjs.com/package/vinyl-fs)
50+
- [write-file-atomic](https://www.npmjs.com/package/write-file-atomic)
4251
- [ws](https://github.com/websockets/ws)
4352

4453
## New queries

javascript/ql/src/semmle/javascript/frameworks/Files.qll

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@
44

55
import javascript
66

7+
/**
8+
* A call that can produce a file name.
9+
*/
10+
abstract private class FileNameProducer extends DataFlow::Node {
11+
/**
12+
* Gets a file name produced by this producer.
13+
*/
14+
abstract DataFlow::Node getAFileName();
15+
}
16+
17+
/**
18+
* A node that contains a file name, and is produced by a `ProducesFileNames`.
19+
*/
20+
private class ProducedFileName extends FileNameSource {
21+
ProducedFileName() { this = any(FileNameProducer producer).getAFileName() }
22+
}
23+
724
/**
825
* A file name from the `walk-sync` library.
926
*/
@@ -106,3 +123,143 @@ private class FastGlobFileNameSource extends FileNameSource {
106123
)
107124
}
108125
}
126+
127+
/**
128+
* Classes and predicates for modelling the `fstream` library (https://www.npmjs.com/package/fstream).
129+
*/
130+
private module FStream {
131+
/**
132+
* Gets a reference to a method in the `fstream` library.
133+
*/
134+
private DataFlow::SourceNode getAnFStreamProperty() {
135+
exists(DataFlow::SourceNode mod, string readOrWrite, string subMod |
136+
mod = DataFlow::moduleImport("fstream") and
137+
(readOrWrite = "Reader" or readOrWrite = "Writer") and
138+
(subMod = "File" or subMod = "Dir" or subMod = "Link" or subMod = "Proxy")
139+
|
140+
result = mod.getAPropertyRead(readOrWrite) or
141+
result = mod.getAPropertyRead(readOrWrite).getAPropertyRead(subMod) or
142+
result = mod.getAPropertyRead(subMod).getAPropertyRead(readOrWrite)
143+
)
144+
}
145+
146+
/**
147+
* An invocation of a method defined in the `fstream` library.
148+
*/
149+
private class FStream extends FileSystemAccess, DataFlow::InvokeNode {
150+
FStream() { this = getAnFStreamProperty().getAnInvocation() }
151+
152+
override DataFlow::Node getAPathArgument() {
153+
result = getOptionArgument(0, "path")
154+
or
155+
not exists(getOptionArgument(0, "path")) and
156+
result = getArgument(0)
157+
}
158+
}
159+
}
160+
161+
/**
162+
* A call to the library `write-file-atomic`.
163+
*/
164+
private class WriteFileAtomic extends FileSystemWriteAccess, DataFlow::CallNode {
165+
WriteFileAtomic() {
166+
this = DataFlow::moduleImport("write-file-atomic").getACall()
167+
or
168+
this = DataFlow::moduleMember("write-file-atomic", "sync").getACall()
169+
}
170+
171+
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
172+
173+
override DataFlow::Node getADataNode() { result = getArgument(1) }
174+
}
175+
176+
/**
177+
* A call to the library `recursive-readdir`.
178+
*/
179+
private class RecursiveReadDir extends FileSystemAccess, FileNameProducer, DataFlow::CallNode {
180+
RecursiveReadDir() { this = DataFlow::moduleImport("recursive-readdir").getACall() }
181+
182+
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
183+
184+
override DataFlow::Node getAFileName() { result = getCallback([1 .. 2]).getParameter(1) }
185+
}
186+
187+
/**
188+
* Classes and predicates for modelling the `jsonfile` library (https://www.npmjs.com/package/jsonfile).
189+
*/
190+
private module JSONFile {
191+
/**
192+
* A reader for JSON files.
193+
*/
194+
class JSONFileReader extends FileSystemReadAccess, DataFlow::CallNode {
195+
JSONFileReader() {
196+
this =
197+
DataFlow::moduleMember("jsonfile", any(string s | s = "readFile" or s = "readFileSync"))
198+
.getACall()
199+
}
200+
201+
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
202+
203+
override DataFlow::Node getADataNode() {
204+
this.getCalleeName() = "readFile" and result = getCallback([1 .. 2]).getParameter(1)
205+
or
206+
this.getCalleeName() = "readFileSync" and result = this
207+
}
208+
}
209+
210+
/**
211+
* A writer for JSON files.
212+
*/
213+
class JSONFileWriter extends FileSystemWriteAccess, DataFlow::CallNode {
214+
JSONFileWriter() {
215+
this =
216+
DataFlow::moduleMember("jsonfile", any(string s | s = "writeFile" or s = "writeFileSync"))
217+
.getACall()
218+
}
219+
220+
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
221+
222+
override DataFlow::Node getADataNode() { result = getArgument(1) }
223+
}
224+
}
225+
226+
/**
227+
* A file system access made by a NodeJS library.
228+
* This class models multiple NodeJS libraries that access files.
229+
*/
230+
private class LibraryAccess extends FileSystemAccess, DataFlow::InvokeNode {
231+
int pathArgument; // The index of the path argument.
232+
233+
LibraryAccess() {
234+
pathArgument = 0 and
235+
(
236+
this = DataFlow::moduleImport("path-exists").getACall()
237+
or
238+
this = DataFlow::moduleImport("rimraf").getACall()
239+
or
240+
this =
241+
DataFlow::moduleMember("node-dir",
242+
any(string s |
243+
s = "readFiles" or
244+
s = "readFilesStream" or
245+
s = "files" or
246+
s = "promiseFiles" or
247+
s = "subdirs" or
248+
s = "paths"
249+
)).getACall()
250+
)
251+
or
252+
pathArgument = 0 and
253+
this =
254+
DataFlow::moduleMember("vinyl-fs", any(string s | s = "src" or s = "dest" or s = "symlink"))
255+
.getACall()
256+
or
257+
pathArgument = [0 .. 1] and
258+
(
259+
this = DataFlow::moduleImport("ncp").getACall() or
260+
this = DataFlow::moduleMember("ncp", "ncp").getACall()
261+
)
262+
}
263+
264+
override DataFlow::Node getAPathArgument() { result = getArgument(pathArgument) }
265+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
getPathArgument
2+
| file-access.js:3:1:4:34 | fstream ... file"}) | file-access.js:4:19:4:32 | "path/to/file" |
3+
| file-access.js:8:1:9:24 | fstream ... o/dir") | file-access.js:9:11:9:23 | "path/to/dir" |
4+
| file-access.js:10:9:10:43 | fstream ... r/dir") | file-access.js:10:24:10:42 | "path/to/other/dir" |
5+
| file-access.js:15:1:15:60 | writeFi ... rr) {}) | file-access.js:15:17:15:28 | 'atmoic.txt' |
6+
| file-access.js:18:1:18:59 | writeFi ... tions]) | file-access.js:18:21:18:34 | "syncFile.txt" |
7+
| file-access.js:22:1:22:48 | recursi ... es) {}) | file-access.js:22:11:22:21 | "some/path" |
8+
| file-access.js:25:1:25:59 | jsonfil ... bj) {}) | file-access.js:25:19:25:34 | '/tmp/data.json' |
9+
| file-access.js:26:1:26:39 | jsonfil ... .json') | file-access.js:26:23:26:38 | '/tmp/data.json' |
10+
| file-access.js:28:1:28:60 | jsonfil ... rr) {}) | file-access.js:28:20:28:35 | '/tmp/data.json' |
11+
| file-access.js:29:1:29:45 | jsonfil ... ', obj) | file-access.js:29:24:29:39 | '/tmp/data.json' |
12+
| file-access.js:34:4:34:23 | pathExists('foo.js') | file-access.js:34:15:34:22 | 'foo.js' |
13+
| file-access.js:39:1:39:28 | rimraf( ... => {}) | file-access.js:39:8:39:10 | "/" |
14+
| file-access.js:42:1:42:59 | dir.rea ... on(){}) | file-access.js:42:15:42:31 | "/some/directory" |
15+
| file-access.js:46:1:46:25 | vfs.src ... path"]) | file-access.js:46:9:46:24 | ["some", "path"] |
16+
| file-access.js:47:1:47:36 | vfs.des ... true }) | file-access.js:47:10:47:13 | './' |
17+
| file-access.js:51:1:51:36 | ncp("fr ... rr) {}) | file-access.js:51:5:51:10 | "from" |
18+
| file-access.js:51:1:51:36 | ncp("fr ... rr) {}) | file-access.js:51:13:51:16 | "to" |
19+
getReadNode
20+
| file-access.js:25:1:25:59 | jsonfil ... bj) {}) | file-access.js:25:52:25:54 | obj |
21+
| file-access.js:26:1:26:39 | jsonfil ... .json') | file-access.js:26:1:26:39 | jsonfil ... .json') |
22+
getWriteNode
23+
| file-access.js:15:1:15:60 | writeFi ... rr) {}) | file-access.js:15:31:15:36 | 'Data' |
24+
| file-access.js:18:1:18:59 | writeFi ... tions]) | file-access.js:18:37:18:47 | "More data" |
25+
| file-access.js:28:1:28:60 | jsonfil ... rr) {}) | file-access.js:28:38:28:40 | obj |
26+
| file-access.js:29:1:29:45 | jsonfil ... ', obj) | file-access.js:29:42:29:44 | obj |
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import javascript
2+
3+
query DataFlow::Node getPathArgument(FileSystemAccess access) { result = access.getAPathArgument() }
4+
5+
query DataFlow::Node getReadNode(FileSystemReadAccess access) { result = access.getADataNode() }
6+
7+
query DataFlow::Node getWriteNode(FileSystemWriteAccess access) { result = access.getADataNode() }

javascript/ql/test/library-tests/frameworks/Concepts/FileNameSource.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
| file-access.js:22:39:22:43 | files |
12
| tst-file-names.js:7:1:7:10 | walkSync() |
23
| tst-file-names.js:9:35:9:44 | stats.name |
34
| tst-file-names.js:11:1:11:12 | glob.sync(_) |
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
var fstream = require("fstream");
2+
3+
fstream
4+
.Writer({ path: "path/to/file"})
5+
.write("hello\n")
6+
.end()
7+
8+
fstream
9+
.Reader("path/to/dir")
10+
.pipe(fstream.Writer("path/to/other/dir"))
11+
12+
13+
var writeFileAtomic= require("write-file-atomic");
14+
15+
writeFileAtomic('atmoic.txt', 'Data', {}, function (err) {});
16+
17+
var writeFileAtomicSync = require('write-file-atomic').sync
18+
writeFileAtomicSync("syncFile.txt", "More data", [options])
19+
20+
var recursive = require("recursive-readdir");
21+
22+
recursive("some/path", function (err, files) {});
23+
24+
const jsonfile = require('jsonfile');
25+
jsonfile.readFile('/tmp/data.json', function (err, obj) {});
26+
jsonfile.readFileSync('/tmp/data.json');
27+
28+
jsonfile.writeFile('/tmp/data.json', obj, function (err) {});
29+
jsonfile.writeFileSync('/tmp/data.json', obj);
30+
31+
32+
const pathExists = require('path-exists');
33+
34+
if(pathExists('foo.js')) {
35+
// do something.
36+
}
37+
38+
var rimraf = require("rimraf");
39+
rimraf("/", {}, (err) => {});
40+
41+
var dir = require("node-dir");
42+
dir.readFiles("/some/directory",function() {},function(){});
43+
44+
var vfs = require("vinyl-fs");
45+
46+
vfs.src(["some", "path"]);
47+
vfs.dest('./', { sourcemaps: true });
48+
49+
50+
var ncp = require('ncp').ncp;
51+
ncp("from", "to", function (err) {});

0 commit comments

Comments
 (0)