Skip to content

Commit 4332de4

Browse files
committed
Eliminate false positives by detecting non-stream objects returned from pipe() calls based on accessed properties
1 parent 5710f0c commit 4332de4

File tree

3 files changed

+36
-9
lines changed

3 files changed

+36
-9
lines changed

javascript/ql/src/Quality/UnhandledStreamPipe.ql

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,21 @@ string getNonchainableStreamMethodName() {
4848
result = ["read", "write", "end", "pipe", "unshift", "push", "isPaused", "wrap", "emit"]
4949
}
5050

51+
/**
52+
* Gets the property names commonly found on Node.js streams.
53+
*/
54+
string getStreamPropertyName() {
55+
result =
56+
[
57+
"readable", "writable", "destroyed", "closed", "readableHighWaterMark", "readableLength",
58+
"readableObjectMode", "readableEncoding", "readableFlowing", "readableEnded", "flowing",
59+
"writableHighWaterMark", "writableLength", "writableObjectMode", "writableFinished",
60+
"writableCorked", "writableEnded", "defaultEncoding", "allowHalfOpen", "objectMode",
61+
"errored", "pending", "autoDestroy", "encoding", "path", "fd", "bytesRead", "bytesWritten",
62+
"_readableState", "_writableState"
63+
]
64+
}
65+
5166
/**
5267
* Gets all method names commonly found on Node.js streams.
5368
*/
@@ -109,6 +124,25 @@ predicate isPipeFollowedByNonStreamMethod(PipeCall pipeCall) {
109124
)
110125
}
111126

127+
/**
128+
* Holds if the pipe call result is used to access a property that is not typical of streams.
129+
*/
130+
predicate isPipeFollowedByNonStreamProperty(PipeCall pipeCall) {
131+
exists(DataFlow::PropRef propRef |
132+
propRef = pipeResultRef(pipeCall).getAPropertyRead() and
133+
not propRef.getPropertyName() = getStreamPropertyName()
134+
)
135+
}
136+
137+
/**
138+
* Holds if the pipe call result is used in a non-stream-like way,
139+
* either by calling non-stream methods or accessing non-stream properties.
140+
*/
141+
predicate isPipeFollowedByNonStreamAccess(PipeCall pipeCall) {
142+
isPipeFollowedByNonStreamMethod(pipeCall) or
143+
isPipeFollowedByNonStreamProperty(pipeCall)
144+
}
145+
112146
/**
113147
* Gets a reference to a stream that may be the source of the given pipe call.
114148
* Uses type back-tracking to trace stream references in the data flow.
@@ -145,6 +179,6 @@ predicate hasErrorHandlerRegistered(PipeCall pipeCall) {
145179
from PipeCall pipeCall
146180
where
147181
not hasErrorHandlerRegistered(pipeCall) and
148-
not isPipeFollowedByNonStreamMethod(pipeCall)
182+
not isPipeFollowedByNonStreamAccess(pipeCall)
149183
select pipeCall,
150184
"Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped."

javascript/ql/test/query-tests/Quality/UnhandledStreamPipe/test.expected

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,11 @@
1111
| test.js:116:5:116:21 | stream.pipe(dest) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
1212
| test.js:125:5:125:26 | getStre ... e(dest) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
1313
| test.js:143:5:143:62 | stream. ... itable) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
14-
| test.js:171:17:171:40 | notStre ... itable) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
1514
| test.js:175:17:175:40 | notStre ... itable) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
1615
| test.js:185:5:185:32 | copyStr ... nation) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
1716
| test.js:190:17:190:40 | notStre ... itable) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
1817
| test.js:195:17:195:40 | notStre ... itable) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
1918
| test.js:199:5:199:22 | notStream.pipe({}) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
2019
| test.js:203:5:203:26 | notStre ... ()=>{}) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
21-
| test.js:207:5:207:31 | getStre ... mber()) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
22-
| test.js:207:5:207:42 | getStre ... e(dest) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
23-
| test.js:207:5:207:53 | getStre ... e(dest) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
2420
| test.js:207:5:207:64 | getStre ... e(dest) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
25-
| test.js:212:5:212:23 | getStream().pipe(p) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
26-
| test.js:212:5:212:34 | getStre ... e(dest) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
27-
| test.js:212:5:212:45 | getStre ... e(dest) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
2821
| test.js:212:5:212:56 | getStre ... e(dest) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |

javascript/ql/test/query-tests/Quality/UnhandledStreamPipe/test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ function test() {
168168
}
169169
{ // Member access on a non-stream after pipe
170170
const notStream = getNotAStream();
171-
const val = notStream.pipe(writable).someMember; // $SPURIOUS:Alert
171+
const val = notStream.pipe(writable).someMember;
172172
}
173173
{ // Member access on a stream after pipe
174174
const notStream = getNotAStream();

0 commit comments

Comments
 (0)