Skip to content

Commit 30f2815

Browse files
committed
Fixed issue where a custom pipe method which returns non stream would be flagged by the query
1 parent ef1bde5 commit 30f2815

File tree

3 files changed

+48
-7
lines changed

3 files changed

+48
-7
lines changed

javascript/ql/src/Quality/UnhandledStreamPipe.ql

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,20 @@ string getChainableStreamMethodName() {
4141
]
4242
}
4343

44+
/**
45+
* Gets the method names that are not chainable on Node.js streams.
46+
*/
47+
string getNonchainableStreamMethodName() {
48+
result = ["read", "write", "end", "pipe", "unshift", "push", "isPaused", "wrap", "emit"]
49+
}
50+
51+
/**
52+
* Gets all method names commonly found on Node.js streams.
53+
*/
54+
string getStreamMethodName() {
55+
result = [getChainableStreamMethodName(), getNonchainableStreamMethodName()]
56+
}
57+
4458
/**
4559
* A call to register an event handler on a Node.js stream.
4660
* This includes methods like `on`, `once`, and `addListener`.
@@ -67,6 +81,34 @@ predicate streamFlowStep(DataFlow::Node streamNode, DataFlow::Node relatedNode)
6781
)
6882
}
6983

84+
/**
85+
* Tracks the result of a pipe call as it flows through the program.
86+
*/
87+
private DataFlow::SourceNode pipeResultTracker(DataFlow::TypeTracker t, PipeCall pipe) {
88+
t.start() and result = pipe
89+
or
90+
exists(DataFlow::TypeTracker t2 | result = pipeResultTracker(t2, pipe).track(t2, t))
91+
}
92+
93+
/**
94+
* Gets a reference to the result of a pipe call.
95+
*/
96+
private DataFlow::SourceNode pipeResultRef(PipeCall pipe) {
97+
result = pipeResultTracker(DataFlow::TypeTracker::end(), pipe)
98+
}
99+
100+
/**
101+
* Holds if the pipe call result is used to call a non-stream method.
102+
* Since pipe() returns the destination stream, this finds cases where
103+
* the destination stream is used with methods not typical of streams.
104+
*/
105+
predicate isPipeFollowedByNonStreamMethod(PipeCall pipeCall) {
106+
exists(DataFlow::MethodCallNode call |
107+
call = pipeResultRef(pipeCall).getAMethodCall() and
108+
not call.getMethodName() = getStreamMethodName()
109+
)
110+
}
111+
70112
/**
71113
* Gets a reference to a stream that may be the source of the given pipe call.
72114
* Uses type back-tracking to trace stream references in the data flow.
@@ -101,6 +143,8 @@ predicate hasErrorHandlerRegistered(PipeCall pipeCall) {
101143
}
102144

103145
from PipeCall pipeCall
104-
where not hasErrorHandlerRegistered(pipeCall)
146+
where
147+
not hasErrorHandlerRegistered(pipeCall) and
148+
not isPipeFollowedByNonStreamMethod(pipeCall)
105149
select pipeCall,
106150
"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 & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,5 @@
99
| 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. |
1010
| 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. |
1111
| 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. |
12-
| test.js:147:5:147:28 | notStre ... itable) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
13-
| test.js:151:20:151:43 | notStre ... itable) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
14-
| test.js:157:47:157:74 | someVar ... ething) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
1512
| test.js:163:5:163:20 | notStream.pipe() | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
1613
| test.js:167:5:167:36 | notStre ... , arg3) | 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: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,17 +144,17 @@ function test() {
144144
}
145145
{ // Non-stream with pipe method that returns subscribable object (Streams do not have subscribe method)
146146
const notStream = getNotAStream();
147-
notStream.pipe(writable).subscribe(); // $SPURIOUS:Alert
147+
notStream.pipe(writable).subscribe();
148148
}
149149
{ // Non-stream with pipe method that returns subscribable object (Streams do not have subscribe method)
150150
const notStream = getNotAStream();
151-
const result = notStream.pipe(writable); // $SPURIOUS:Alert
151+
const result = notStream.pipe(writable);
152152
const dealWithResult = (result) => { result.subscribe(); };
153153
dealWithResult(result);
154154
}
155155
{ // Non-stream with pipe method that returns subscribable object (Streams do not have subscribe method)
156156
const notStream = getNotAStream();
157-
const pipeIt = (someVariable) => { return someVariable.pipe(something); }; // $SPURIOUS:Alert
157+
const pipeIt = (someVariable) => { return someVariable.pipe(something); };
158158
let x = pipeIt(notStream);
159159
x.subscribe();
160160
}

0 commit comments

Comments
 (0)