Skip to content

Commit f095e19

Browse files
committed
add support for the promise polyfill
1 parent 967ccfe commit f095e19

4 files changed

Lines changed: 34 additions & 7 deletions

File tree

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
lgtm,codescanning
22
* The security queries now track flow through various `Promise` polyfills.
33
Affected packages are
4-
[kew](https://npmjs.com/package/kew)
4+
[kew](https://npmjs.com/package/kew),
5+
[promise](https://npmjs.com/package/promise)

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

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,22 @@ private predicate hasHandler(DataFlow::InvokeNode promise, string m, int i) {
5858
exists(promise.getAMethodCall(m).getCallback(i))
5959
}
6060

61+
/**
62+
* Gets a reference to the `Promise` object.
63+
* Either from the standard library, a polyfill import, or a polyfill that defines the global `Promise` variable.
64+
*/
65+
private DataFlow::SourceNode getAPromiseObject() {
66+
// Standard library, or polyfills like [es6-shim](https://npmjs.org/package/es6-shim).
67+
result = DataFlow::globalVarRef("Promise")
68+
or
69+
// polyfills from the [`promise`](https://npmjs.org/package/promise) library.
70+
result =
71+
DataFlow::moduleImport([
72+
"promise", "promise/domains", "promise/setimmediate", "promise/lib/es6-extensions",
73+
"promise/domains/es6-extensions", "promise/setimmediate/es6-extensions"
74+
])
75+
}
76+
6177
/**
6278
* A call that looks like a Promise.
6379
*
@@ -75,7 +91,7 @@ class PromiseCandidate extends DataFlow::InvokeNode {
7591
* A promise object created by the standard ECMAScript 2015 `Promise` constructor.
7692
*/
7793
private class ES2015PromiseDefinition extends PromiseDefinition, DataFlow::NewNode {
78-
ES2015PromiseDefinition() { this = DataFlow::globalVarRef("Promise").getAnInstantiation() }
94+
ES2015PromiseDefinition() { this = getAPromiseObject().getAnInstantiation() }
7995

8096
override DataFlow::FunctionNode getExecutor() { result = getCallback(0) }
8197
}
@@ -109,9 +125,7 @@ abstract class PromiseAllCreation extends PromiseCreationCall {
109125
* A resolved promise created by the standard ECMAScript 2015 `Promise.resolve` function.
110126
*/
111127
class ResolvedES2015PromiseDefinition extends ResolvedPromiseDefinition {
112-
ResolvedES2015PromiseDefinition() {
113-
this = DataFlow::globalVarRef("Promise").getAMemberCall("resolve")
114-
}
128+
ResolvedES2015PromiseDefinition() { this = getAPromiseObject().getAMemberCall("resolve") }
115129

116130
override DataFlow::Node getValue() { result = getArgument(0) }
117131
}
@@ -121,8 +135,7 @@ class ResolvedES2015PromiseDefinition extends ResolvedPromiseDefinition {
121135
*/
122136
class AggregateES2015PromiseDefinition extends PromiseCreationCall {
123137
AggregateES2015PromiseDefinition() {
124-
exists(string m | m = "all" or m = "race" or m = "any" |
125-
this = DataFlow::globalVarRef("Promise").getAMemberCall(m)
138+
this = getAPromiseObject().getAMemberCall(m)
126139
)
127140
}
128141

javascript/ql/test/library-tests/Promises/promises.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,10 @@
9292
var sink = val;
9393
});
9494
})();
95+
96+
(function() {
97+
var PromiseA = require('promise');
98+
var PromiseB = require('promise/domains');
99+
PromiseA.resolve(source);
100+
PromiseB.resolve(source);
101+
})();

javascript/ql/test/library-tests/Promises/tests.expected

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ test_ResolvedPromiseDefinition
3636
| promises.js:62:19:62:41 | Promise ... source) | promises.js:62:35:62:40 | source |
3737
| promises.js:71:5:71:27 | Promise ... source) | promises.js:71:21:71:26 | source |
3838
| promises.js:79:19:79:41 | Promise ... source) | promises.js:79:35:79:40 | source |
39+
| promises.js:99:3:99:26 | Promise ... source) | promises.js:99:20:99:25 | source |
40+
| promises.js:100:3:100:26 | Promise ... source) | promises.js:100:20:100:25 | source |
3941
test_PromiseDefinition_getARejectHandler
4042
| flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:69:26:80 | y => sink(y) |
4143
| flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) |
@@ -407,3 +409,7 @@ typetrack
407409
| promises.js:75:27:75:29 | val | promises.js:75:5:75:20 | resolver.promise | load $PromiseResolveField$ |
408410
| promises.js:88:17:90:4 | Q.Promi ... );\\n }) | promises.js:89:15:89:20 | source | copy $PromiseResolveField$ |
409411
| promises.js:88:17:90:4 | Q.Promi ... );\\n }) | promises.js:89:15:89:20 | source | store $PromiseResolveField$ |
412+
| promises.js:99:3:99:26 | Promise ... source) | promises.js:99:20:99:25 | source | copy $PromiseResolveField$ |
413+
| promises.js:99:3:99:26 | Promise ... source) | promises.js:99:20:99:25 | source | store $PromiseResolveField$ |
414+
| promises.js:100:3:100:26 | Promise ... source) | promises.js:100:20:100:25 | source | copy $PromiseResolveField$ |
415+
| promises.js:100:3:100:26 | Promise ... source) | promises.js:100:20:100:25 | source | store $PromiseResolveField$ |

0 commit comments

Comments
 (0)