Skip to content

Commit e40b93b

Browse files
committed
JS: Add type-tracking step through simple Promise.all() calls
1 parent 4093afb commit e40b93b

File tree

1 file changed

+49
-3
lines changed
  • javascript/ql/lib/semmle/javascript/internal/flow_summaries

1 file changed

+49
-3
lines changed

javascript/ql/lib/semmle/javascript/internal/flow_summaries/Promises.qll

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
private import javascript
66
private import semmle.javascript.dataflow.FlowSummary
7+
private import semmle.javascript.dataflow.TypeTracking
78
private import FlowSummaryUtil
89

910
DataFlow::SourceNode promiseConstructorRef() {
@@ -211,12 +212,57 @@ private class PromiseReject extends SummarizedCallable {
211212
}
212213
}
213214

215+
/**
216+
* A call to `Promise.all()`.
217+
*/
218+
class PromiseAllCall extends DataFlow::CallNode {
219+
PromiseAllCall() { this = promiseConstructorRef().getAMemberCall("all") }
220+
221+
/** Gets the source of the input array */
222+
DataFlow::ArrayCreationNode getInputArray() { result = this.getArgument(0).getALocalSource() }
223+
224+
/** Gets the `n`th element of the input array */
225+
DataFlow::Node getNthInput(int n) { result = this.getInputArray().getElement(n) }
226+
227+
/** Gets a reference to the output array. */
228+
DataFlow::SourceNode getOutputArray() {
229+
exists(AwaitExpr await |
230+
this.flowsToExpr(await.getOperand()) and
231+
result = await.flow()
232+
)
233+
or
234+
result = this.getAMethodCall("then").getCallback(0).getParameter(0)
235+
}
236+
237+
/** Gets the `n`th output */
238+
DataFlow::SourceNode getNthOutput(int n) {
239+
exists(string prop |
240+
result = this.getOutputArray().getAPropertyRead(prop) and
241+
n = prop.toInt()
242+
)
243+
}
244+
}
245+
246+
/**
247+
* Helps type-tracking simple uses of `Promise.all()` such as `const [a, b] = await Promise.all([x, y])`.
248+
*
249+
* Due to limited access path depth, type tracking can't track things that are in a promise and an array
250+
* at once. This generates a step directly from the input array to the output array.
251+
*/
252+
private class PromiseAllStep extends SharedTypeTrackingStep {
253+
override predicate loadStep(DataFlow::Node node1, DataFlow::Node node2, string prop) {
254+
exists(PromiseAllCall call, int n |
255+
node1 = call.getNthInput(n) and
256+
node2 = call.getNthOutput(n) and
257+
prop = Promises::valueProp()
258+
)
259+
}
260+
}
261+
214262
private class PromiseAll extends SummarizedCallable {
215263
PromiseAll() { this = "Promise.all()" }
216264

217-
override DataFlow::InvokeNode getACallSimple() {
218-
result = promiseConstructorRef().getAMemberCall("all")
219-
}
265+
override DataFlow::InvokeNode getACallSimple() { result instanceof PromiseAllCall }
220266

221267
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
222268
preservesValue = true and

0 commit comments

Comments
 (0)