Skip to content

Commit 5a6958a

Browse files
committed
add promise aggregators
1 parent 351cb46 commit 5a6958a

File tree

4 files changed

+40
-7
lines changed

4 files changed

+40
-7
lines changed

javascript/ql/src/Statements/UseOfReturnlessFunction.ql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ predicate benignContext(Expr e) {
6767
any(InvokeExpr invoke).getCallee() = e
6868
or
6969
// arguments to Promise.resolve (and promise library variants) are benign.
70-
e = any(ResolvedPromiseDefinition promise).getValue().asExpr()
70+
e = any(PromiseCreationCall promise).getValue().asExpr()
7171
}
7272

7373
predicate oneshotClosure(InvokeExpr call) {
@@ -191,7 +191,7 @@ module Deferred {
191191
/**
192192
* A resolved promise created by a `new Deferred().resolve()` call.
193193
*/
194-
class ResolvedDeferredPromiseDefinition extends ResolvedPromiseDefinition {
194+
class ResolvedDeferredPromiseDefinition extends PromiseCreationCall {
195195
ResolvedDeferredPromiseDefinition() {
196196
this = any(DeferredPromiseDefinition def).ref().getAMethodCall("resolve")
197197
}

javascript/ql/src/semmle/javascript/Promises.qll

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,27 @@ module Bluebird {
2525
/**
2626
* A resolved promise created by the bluebird `Promise.resolve` function.
2727
*/
28-
class ResolvedBluebidPromiseDefinition extends ResolvedPromiseDefinition {
28+
class ResolvedBluebidPromiseDefinition extends PromiseCreationCall {
2929
ResolvedBluebidPromiseDefinition() { this = bluebird().getAMemberCall("resolve") }
3030

3131
override DataFlow::Node getValue() { result = getArgument(0) }
3232
}
33+
34+
/**
35+
* An aggregated promise produced either by `Primise.all`, `Promise.race` or `Promise.map`.
36+
*/
37+
class AggregateBluebirdPromiseDefinition extends PromiseCreationCall {
38+
AggregateBluebirdPromiseDefinition() {
39+
exists(string m | m = "all" or m = "race" or m = "map" |
40+
this = bluebird().getAMemberCall(m)
41+
)
42+
}
43+
44+
override DataFlow::Node getValue() {
45+
result = getArgument(0).getALocalSource().(DataFlow::ArrayCreationNode).getAnElement()
46+
}
47+
}
48+
3349
}
3450

3551
/**
@@ -59,7 +75,7 @@ private module ClosurePromise {
5975
/**
6076
* A promise created by a call `goog.Promise.resolve(value)`.
6177
*/
62-
private class ResolvedClosurePromiseDefinition extends ResolvedPromiseDefinition {
78+
private class ResolvedClosurePromiseDefinition extends PromiseCreationCall {
6379
ResolvedClosurePromiseDefinition() {
6480
this = Closure::moduleImport("goog.Promise.resolve").getACall()
6581
}

javascript/ql/src/semmle/javascript/StandardLibrary.qll

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ private class ES2015PromiseDefinition extends PromiseDefinition, DataFlow::NewNo
154154
/**
155155
* A promise that is resolved with the given value.
156156
*/
157-
abstract class ResolvedPromiseDefinition extends DataFlow::CallNode {
157+
abstract class PromiseCreationCall extends DataFlow::CallNode {
158158
/**
159159
* Gets the value this promise is resolved with.
160160
*/
@@ -164,14 +164,29 @@ abstract class ResolvedPromiseDefinition extends DataFlow::CallNode {
164164
/**
165165
* A resolved promise created by the standard ECMAScript 2015 `Promise.resolve` function.
166166
*/
167-
class ResolvedES2015PromiseDefinition extends ResolvedPromiseDefinition {
167+
class ResolvedES2015PromiseDefinition extends PromiseCreationCall {
168168
ResolvedES2015PromiseDefinition() {
169169
this = DataFlow::globalVarRef("Promise").getAMemberCall("resolve")
170170
}
171171

172172
override DataFlow::Node getValue() { result = getArgument(0) }
173173
}
174174

175+
/**
176+
* An aggregated promise produced either by `Primise.all` or `Promise.race`.
177+
*/
178+
class AggregateES2015PromiseDefinition extends PromiseCreationCall {
179+
AggregateES2015PromiseDefinition() {
180+
exists(string m | m = "all" or m = "race" |
181+
this = DataFlow::globalVarRef("Promise").getAMemberCall(m)
182+
)
183+
}
184+
185+
override DataFlow::Node getValue() {
186+
result = getArgument(0).getALocalSource().(DataFlow::ArrayCreationNode).getAnElement()
187+
}
188+
}
189+
175190
/**
176191
* A data flow edge from a promise reaction to the corresponding handler.
177192
*/
@@ -197,7 +212,7 @@ predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
197212
pred = succ.(PromiseDefinition).getResolveParameter().getACall().getArgument(0)
198213
or
199214
// from `x` to `Promise.resolve(x)`
200-
pred = succ.(ResolvedPromiseDefinition).getValue()
215+
pred = succ.(PromiseCreationCall).getValue()
201216
or
202217
exists(DataFlow::MethodCallNode thn, DataFlow::FunctionNode cb |
203218
thn.getMethodName() = "then" and cb = thn.getCallback(0)

javascript/ql/test/query-tests/Statements/UseOfReturnlessFunction/tst.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,6 @@
8888
}
8989

9090
new Deferred().resolve(onlySideEffects()); // OK
91+
92+
Promise.all([onlySideEffects(), onlySideEffects()])
9193
})();

0 commit comments

Comments
 (0)