From 967ccfef0cd5cc3692420ce0ceb3bd006275894b Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 18 Jun 2021 14:56:46 +0200 Subject: [PATCH 01/12] add support for `kew` --- javascript/change-notes/2021-06-18-promises.md | 4 ++++ javascript/ql/src/semmle/javascript/Promises.qll | 4 ++-- .../Promises/AdditionalPromises.expected | 1 + .../ql/test/library-tests/Promises/promises.js | 13 ++++++++++++- .../ql/test/library-tests/Promises/tests.expected | 7 +++++++ 5 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 javascript/change-notes/2021-06-18-promises.md diff --git a/javascript/change-notes/2021-06-18-promises.md b/javascript/change-notes/2021-06-18-promises.md new file mode 100644 index 000000000000..df9c1d806a11 --- /dev/null +++ b/javascript/change-notes/2021-06-18-promises.md @@ -0,0 +1,4 @@ +lgtm,codescanning +* The security queries now track flow through various `Promise` polyfills. + Affected packages are + [kew](https://npmjs.com/package/kew) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index 9190473201e8..7d842e0ae2ff 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -562,14 +562,14 @@ module Bluebird { } /** - * Provides classes for working with the `q` library (https://github.com/kriskowal/q). + * Provides classes for working with the `q` library (https://github.com/kriskowal/q) and the compatible `kew` library (https://github.com/Medium/kew). */ module Q { /** * A promise object created by the q `Promise` constructor. */ private class QPromiseDefinition extends PromiseDefinition, DataFlow::CallNode { - QPromiseDefinition() { this = DataFlow::moduleMember("q", "Promise").getACall() } + QPromiseDefinition() { this = DataFlow::moduleMember(["q", "kew"], "Promise").getACall() } override DataFlow::FunctionNode getExecutor() { result = getCallback(0) } } diff --git a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected index d6845266dccb..f6ee9faab001 100644 --- a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected +++ b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected @@ -79,3 +79,4 @@ | promises.js:71:5:71:27 | Promise ... source) | | promises.js:72:5:72:41 | new Pro ... ource)) | | promises.js:79:19:79:41 | Promise ... source) | +| promises.js:88:17:90:4 | Q.Promi ... );\\n }) | diff --git a/javascript/ql/test/library-tests/Promises/promises.js b/javascript/ql/test/library-tests/Promises/promises.js index 8a3d0cbc5cf1..dfbbbf6f2312 100644 --- a/javascript/ql/test/library-tests/Promises/promises.js +++ b/javascript/ql/test/library-tests/Promises/promises.js @@ -80,4 +80,15 @@ promise.then(function (val) { var sink = val; }); -})(); \ No newline at end of file +})(); + + +(function() { + var Q = require("kew"); + var promise = Q.Promise(function (resolve, reject) { + resolve(source); + }); + promise.then(function (val) { + var sink = val; + }); +})(); diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index 00320cc83fcf..14b0a0571679 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -82,6 +82,7 @@ test_PromiseDefinition_getExecutor | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:30:17:3 | (res, r ... e);\\n } | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:33:31:35:5 | functio ... ;\\n } | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:29:45:5 | functio ... ;\\n } | +| promises.js:88:17:90:4 | Q.Promi ... );\\n }) | promises.js:88:27:90:3 | functio ... e);\\n } | test_PromiseDefinition_getAFinallyHandler | flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:58:105:76 | x => {throw source} | | flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:58:109:70 | x => rejected | @@ -117,6 +118,7 @@ test_PromiseDefinition | promises.js:10:18:17:4 | new Pro ... );\\n }) | | promises.js:33:19:35:6 | new Pro ... \\n }) | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | +| promises.js:88:17:90:4 | Q.Promi ... );\\n }) | test_PromiseDefinition_getAResolveHandler | flow.js:24:2:24:49 | new Pro ... ource)) | flow.js:24:56:24:67 | x => sink(x) | | flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:56:26:66 | x => foo(x) | @@ -134,6 +136,7 @@ test_PromiseDefinition_getAResolveHandler | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:36:18:38:5 | functio ... ;\\n } | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:46:18:48:5 | functio ... ;\\n } | +| promises.js:88:17:90:4 | Q.Promi ... );\\n }) | promises.js:91:16:93:3 | functio ... al;\\n } | test_PromiseDefinition_getRejectParameter | flow.js:7:11:7:59 | new Pro ... ource)) | flow.js:7:33:7:38 | reject | | flow.js:10:11:10:58 | new Pro ... ource)) | flow.js:10:33:10:38 | reject | @@ -164,6 +167,7 @@ test_PromiseDefinition_getRejectParameter | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:36:10:38 | rej | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:33:50:33:55 | reject | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:48:43:53 | reject | +| promises.js:88:17:90:4 | Q.Promi ... );\\n }) | promises.js:88:46:88:51 | reject | test_PromiseDefinition_getResolveParameter | flow.js:7:11:7:59 | new Pro ... ource)) | flow.js:7:24:7:30 | resolve | | flow.js:10:11:10:58 | new Pro ... ource)) | flow.js:10:24:10:30 | resolve | @@ -194,6 +198,7 @@ test_PromiseDefinition_getResolveParameter | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:31:10:33 | res | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:33:41:33:47 | resolve | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:39:43:45 | resolve | +| promises.js:88:17:90:4 | Q.Promi ... );\\n }) | promises.js:88:37:88:43 | resolve | test_PromiseDefinition_getACatchHandler | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | | flow.js:48:2:48:36 | new Pro ... urce }) | flow.js:48:44:48:55 | x => sink(x) | @@ -400,3 +405,5 @@ typetrack | promises.js:71:34:71:36 | val | promises.js:71:5:71:27 | Promise ... source) | load $PromiseResolveField$ | | promises.js:72:48:72:50 | val | promises.js:72:5:72:41 | new Pro ... ource)) | load $PromiseResolveField$ | | promises.js:75:27:75:29 | val | promises.js:75:5:75:20 | resolver.promise | load $PromiseResolveField$ | +| promises.js:88:17:90:4 | Q.Promi ... );\\n }) | promises.js:89:15:89:20 | source | copy $PromiseResolveField$ | +| promises.js:88:17:90:4 | Q.Promi ... );\\n }) | promises.js:89:15:89:20 | source | store $PromiseResolveField$ | From f095e190a903ebc6e3ae4559087d6313269765bd Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 18 Jun 2021 16:06:39 +0200 Subject: [PATCH 02/12] add support for the `promise` polyfill --- .../change-notes/2021-06-18-promises.md | 3 ++- .../ql/src/semmle/javascript/Promises.qll | 25 ++++++++++++++----- .../test/library-tests/Promises/promises.js | 7 ++++++ .../library-tests/Promises/tests.expected | 6 +++++ 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/javascript/change-notes/2021-06-18-promises.md b/javascript/change-notes/2021-06-18-promises.md index df9c1d806a11..e170fd7ce24f 100644 --- a/javascript/change-notes/2021-06-18-promises.md +++ b/javascript/change-notes/2021-06-18-promises.md @@ -1,4 +1,5 @@ lgtm,codescanning * The security queries now track flow through various `Promise` polyfills. Affected packages are - [kew](https://npmjs.com/package/kew) + [kew](https://npmjs.com/package/kew), + [promise](https://npmjs.com/package/promise) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index 7d842e0ae2ff..328d04c47449 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -58,6 +58,22 @@ private predicate hasHandler(DataFlow::InvokeNode promise, string m, int i) { exists(promise.getAMethodCall(m).getCallback(i)) } +/** + * Gets a reference to the `Promise` object. + * Either from the standard library, a polyfill import, or a polyfill that defines the global `Promise` variable. + */ +private DataFlow::SourceNode getAPromiseObject() { + // Standard library, or polyfills like [es6-shim](https://npmjs.org/package/es6-shim). + result = DataFlow::globalVarRef("Promise") + or + // polyfills from the [`promise`](https://npmjs.org/package/promise) library. + result = + DataFlow::moduleImport([ + "promise", "promise/domains", "promise/setimmediate", "promise/lib/es6-extensions", + "promise/domains/es6-extensions", "promise/setimmediate/es6-extensions" + ]) +} + /** * A call that looks like a Promise. * @@ -75,7 +91,7 @@ class PromiseCandidate extends DataFlow::InvokeNode { * A promise object created by the standard ECMAScript 2015 `Promise` constructor. */ private class ES2015PromiseDefinition extends PromiseDefinition, DataFlow::NewNode { - ES2015PromiseDefinition() { this = DataFlow::globalVarRef("Promise").getAnInstantiation() } + ES2015PromiseDefinition() { this = getAPromiseObject().getAnInstantiation() } override DataFlow::FunctionNode getExecutor() { result = getCallback(0) } } @@ -109,9 +125,7 @@ abstract class PromiseAllCreation extends PromiseCreationCall { * A resolved promise created by the standard ECMAScript 2015 `Promise.resolve` function. */ class ResolvedES2015PromiseDefinition extends ResolvedPromiseDefinition { - ResolvedES2015PromiseDefinition() { - this = DataFlow::globalVarRef("Promise").getAMemberCall("resolve") - } + ResolvedES2015PromiseDefinition() { this = getAPromiseObject().getAMemberCall("resolve") } override DataFlow::Node getValue() { result = getArgument(0) } } @@ -121,8 +135,7 @@ class ResolvedES2015PromiseDefinition extends ResolvedPromiseDefinition { */ class AggregateES2015PromiseDefinition extends PromiseCreationCall { AggregateES2015PromiseDefinition() { - exists(string m | m = "all" or m = "race" or m = "any" | - this = DataFlow::globalVarRef("Promise").getAMemberCall(m) + this = getAPromiseObject().getAMemberCall(m) ) } diff --git a/javascript/ql/test/library-tests/Promises/promises.js b/javascript/ql/test/library-tests/Promises/promises.js index dfbbbf6f2312..49063fd08fa3 100644 --- a/javascript/ql/test/library-tests/Promises/promises.js +++ b/javascript/ql/test/library-tests/Promises/promises.js @@ -92,3 +92,10 @@ var sink = val; }); })(); + +(function() { + var PromiseA = require('promise'); + var PromiseB = require('promise/domains'); + PromiseA.resolve(source); + PromiseB.resolve(source); +})(); diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index 14b0a0571679..a6d2304537f4 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -36,6 +36,8 @@ test_ResolvedPromiseDefinition | promises.js:62:19:62:41 | Promise ... source) | promises.js:62:35:62:40 | source | | promises.js:71:5:71:27 | Promise ... source) | promises.js:71:21:71:26 | source | | promises.js:79:19:79:41 | Promise ... source) | promises.js:79:35:79:40 | source | +| promises.js:99:3:99:26 | Promise ... source) | promises.js:99:20:99:25 | source | +| promises.js:100:3:100:26 | Promise ... source) | promises.js:100:20:100:25 | source | test_PromiseDefinition_getARejectHandler | flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:69:26:80 | y => sink(y) | | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | @@ -407,3 +409,7 @@ typetrack | promises.js:75:27:75:29 | val | promises.js:75:5:75:20 | resolver.promise | load $PromiseResolveField$ | | promises.js:88:17:90:4 | Q.Promi ... );\\n }) | promises.js:89:15:89:20 | source | copy $PromiseResolveField$ | | promises.js:88:17:90:4 | Q.Promi ... );\\n }) | promises.js:89:15:89:20 | source | store $PromiseResolveField$ | +| promises.js:99:3:99:26 | Promise ... source) | promises.js:99:20:99:25 | source | copy $PromiseResolveField$ | +| promises.js:99:3:99:26 | Promise ... source) | promises.js:99:20:99:25 | source | store $PromiseResolveField$ | +| promises.js:100:3:100:26 | Promise ... source) | promises.js:100:20:100:25 | source | copy $PromiseResolveField$ | +| promises.js:100:3:100:26 | Promise ... source) | promises.js:100:20:100:25 | source | store $PromiseResolveField$ | From d7a47e8fbd164f3ffb789c90f1666281aa31ac6b Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 18 Jun 2021 16:13:28 +0200 Subject: [PATCH 03/12] add support for the `promise-polyfill` polyfill --- javascript/change-notes/2021-06-18-promises.md | 3 ++- javascript/ql/src/semmle/javascript/Promises.qll | 5 +++++ javascript/ql/test/library-tests/Promises/promises.js | 7 +++++++ javascript/ql/test/library-tests/Promises/tests.expected | 6 ++++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/javascript/change-notes/2021-06-18-promises.md b/javascript/change-notes/2021-06-18-promises.md index e170fd7ce24f..a3c94d971f14 100644 --- a/javascript/change-notes/2021-06-18-promises.md +++ b/javascript/change-notes/2021-06-18-promises.md @@ -2,4 +2,5 @@ lgtm,codescanning * The security queries now track flow through various `Promise` polyfills. Affected packages are [kew](https://npmjs.com/package/kew), - [promise](https://npmjs.com/package/promise) + [promise](https://npmjs.com/package/promise), + [promise-polyfill](https://npmjs.com/package/promise-polyfill) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index 328d04c47449..b7fc6ccd61a6 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -72,6 +72,11 @@ private DataFlow::SourceNode getAPromiseObject() { "promise", "promise/domains", "promise/setimmediate", "promise/lib/es6-extensions", "promise/domains/es6-extensions", "promise/setimmediate/es6-extensions" ]) + or + // polyfill from the [`promise-polyfill`](https://npmjs.org/package/promise-polyfill) library. + result = DataFlow::moduleMember(["promise-polyfill", "promise-polyfill/src/polyfill"], "default") + or + result = DataFlow::moduleImport(["promise-polyfill", "promise-polyfill/src/polyfill"]) } /** diff --git a/javascript/ql/test/library-tests/Promises/promises.js b/javascript/ql/test/library-tests/Promises/promises.js index 49063fd08fa3..811165775a18 100644 --- a/javascript/ql/test/library-tests/Promises/promises.js +++ b/javascript/ql/test/library-tests/Promises/promises.js @@ -99,3 +99,10 @@ PromiseA.resolve(source); PromiseB.resolve(source); })(); + +(function() { + var PromiseA = require('promise-polyfill').default; + import PromiseB from 'promise-polyfill'; + PromiseA.resolve(source); + PromiseB.resolve(source); +})(); diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index a6d2304537f4..ba203e3b4109 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -38,6 +38,8 @@ test_ResolvedPromiseDefinition | promises.js:79:19:79:41 | Promise ... source) | promises.js:79:35:79:40 | source | | promises.js:99:3:99:26 | Promise ... source) | promises.js:99:20:99:25 | source | | promises.js:100:3:100:26 | Promise ... source) | promises.js:100:20:100:25 | source | +| promises.js:106:3:106:26 | Promise ... source) | promises.js:106:20:106:25 | source | +| promises.js:107:3:107:26 | Promise ... source) | promises.js:107:20:107:25 | source | test_PromiseDefinition_getARejectHandler | flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:69:26:80 | y => sink(y) | | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | @@ -413,3 +415,7 @@ typetrack | promises.js:99:3:99:26 | Promise ... source) | promises.js:99:20:99:25 | source | store $PromiseResolveField$ | | promises.js:100:3:100:26 | Promise ... source) | promises.js:100:20:100:25 | source | copy $PromiseResolveField$ | | promises.js:100:3:100:26 | Promise ... source) | promises.js:100:20:100:25 | source | store $PromiseResolveField$ | +| promises.js:106:3:106:26 | Promise ... source) | promises.js:106:20:106:25 | source | copy $PromiseResolveField$ | +| promises.js:106:3:106:26 | Promise ... source) | promises.js:106:20:106:25 | source | store $PromiseResolveField$ | +| promises.js:107:3:107:26 | Promise ... source) | promises.js:107:20:107:25 | source | copy $PromiseResolveField$ | +| promises.js:107:3:107:26 | Promise ... source) | promises.js:107:20:107:25 | source | store $PromiseResolveField$ | From ebde9015d8d9bf4ea2a42d6a6bc8e93d18cdd43f Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 18 Jun 2021 16:18:24 +0200 Subject: [PATCH 04/12] add support for the `rsvp` and `es6-promise` polyfill --- javascript/change-notes/2021-06-18-promises.md | 4 +++- javascript/ql/src/semmle/javascript/Promises.qll | 2 ++ .../library-tests/Promises/AdditionalPromises.expected | 1 + javascript/ql/test/library-tests/Promises/promises.js | 7 +++++++ javascript/ql/test/library-tests/Promises/tests.expected | 7 +++++++ 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/javascript/change-notes/2021-06-18-promises.md b/javascript/change-notes/2021-06-18-promises.md index a3c94d971f14..f9e88266a05f 100644 --- a/javascript/change-notes/2021-06-18-promises.md +++ b/javascript/change-notes/2021-06-18-promises.md @@ -3,4 +3,6 @@ lgtm,codescanning Affected packages are [kew](https://npmjs.com/package/kew), [promise](https://npmjs.com/package/promise), - [promise-polyfill](https://npmjs.com/package/promise-polyfill) + [promise-polyfill](https://npmjs.com/package/promise-polyfill), + [rsvp](https://npmjs.com/package/rsvp), + [es6-promise](https://npmjs.com/package/es6-promise) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index b7fc6ccd61a6..ac2c8334f68b 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -77,6 +77,8 @@ private DataFlow::SourceNode getAPromiseObject() { result = DataFlow::moduleMember(["promise-polyfill", "promise-polyfill/src/polyfill"], "default") or result = DataFlow::moduleImport(["promise-polyfill", "promise-polyfill/src/polyfill"]) + or + result = DataFlow::moduleMember(["es6-promise", "rsvp"], "Promise") } /** diff --git a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected index f6ee9faab001..3b2f628ec3d1 100644 --- a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected +++ b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected @@ -80,3 +80,4 @@ | promises.js:72:5:72:41 | new Pro ... ource)) | | promises.js:79:19:79:41 | Promise ... source) | | promises.js:88:17:90:4 | Q.Promi ... );\\n }) | +| promises.js:112:17:112:62 | new RSV ... ct) {}) | diff --git a/javascript/ql/test/library-tests/Promises/promises.js b/javascript/ql/test/library-tests/Promises/promises.js index 811165775a18..9af2c123b0a1 100644 --- a/javascript/ql/test/library-tests/Promises/promises.js +++ b/javascript/ql/test/library-tests/Promises/promises.js @@ -106,3 +106,10 @@ PromiseA.resolve(source); PromiseB.resolve(source); })(); + +(function() { + var RSVP = require('rsvp'); + var promise = new RSVP.Promise(function(resolve, reject) {}); + var Promise = require('es6-promise').Promise; + Promise.resolve(source); +})(); diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index ba203e3b4109..5c9a3b95d408 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -40,6 +40,7 @@ test_ResolvedPromiseDefinition | promises.js:100:3:100:26 | Promise ... source) | promises.js:100:20:100:25 | source | | promises.js:106:3:106:26 | Promise ... source) | promises.js:106:20:106:25 | source | | promises.js:107:3:107:26 | Promise ... source) | promises.js:107:20:107:25 | source | +| promises.js:114:3:114:25 | Promise ... source) | promises.js:114:19:114:24 | source | test_PromiseDefinition_getARejectHandler | flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:69:26:80 | y => sink(y) | | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | @@ -87,6 +88,7 @@ test_PromiseDefinition_getExecutor | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:33:31:35:5 | functio ... ;\\n } | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:29:45:5 | functio ... ;\\n } | | promises.js:88:17:90:4 | Q.Promi ... );\\n }) | promises.js:88:27:90:3 | functio ... e);\\n } | +| promises.js:112:17:112:62 | new RSV ... ct) {}) | promises.js:112:34:112:61 | functio ... ect) {} | test_PromiseDefinition_getAFinallyHandler | flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:58:105:76 | x => {throw source} | | flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:58:109:70 | x => rejected | @@ -123,6 +125,7 @@ test_PromiseDefinition | promises.js:33:19:35:6 | new Pro ... \\n }) | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | | promises.js:88:17:90:4 | Q.Promi ... );\\n }) | +| promises.js:112:17:112:62 | new RSV ... ct) {}) | test_PromiseDefinition_getAResolveHandler | flow.js:24:2:24:49 | new Pro ... ource)) | flow.js:24:56:24:67 | x => sink(x) | | flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:56:26:66 | x => foo(x) | @@ -172,6 +175,7 @@ test_PromiseDefinition_getRejectParameter | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:33:50:33:55 | reject | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:48:43:53 | reject | | promises.js:88:17:90:4 | Q.Promi ... );\\n }) | promises.js:88:46:88:51 | reject | +| promises.js:112:17:112:62 | new RSV ... ct) {}) | promises.js:112:52:112:57 | reject | test_PromiseDefinition_getResolveParameter | flow.js:7:11:7:59 | new Pro ... ource)) | flow.js:7:24:7:30 | resolve | | flow.js:10:11:10:58 | new Pro ... ource)) | flow.js:10:24:10:30 | resolve | @@ -203,6 +207,7 @@ test_PromiseDefinition_getResolveParameter | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:33:41:33:47 | resolve | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:39:43:45 | resolve | | promises.js:88:17:90:4 | Q.Promi ... );\\n }) | promises.js:88:37:88:43 | resolve | +| promises.js:112:17:112:62 | new RSV ... ct) {}) | promises.js:112:43:112:49 | resolve | test_PromiseDefinition_getACatchHandler | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | | flow.js:48:2:48:36 | new Pro ... urce }) | flow.js:48:44:48:55 | x => sink(x) | @@ -419,3 +424,5 @@ typetrack | promises.js:106:3:106:26 | Promise ... source) | promises.js:106:20:106:25 | source | store $PromiseResolveField$ | | promises.js:107:3:107:26 | Promise ... source) | promises.js:107:20:107:25 | source | copy $PromiseResolveField$ | | promises.js:107:3:107:26 | Promise ... source) | promises.js:107:20:107:25 | source | store $PromiseResolveField$ | +| promises.js:114:3:114:25 | Promise ... source) | promises.js:114:19:114:24 | source | copy $PromiseResolveField$ | +| promises.js:114:3:114:25 | Promise ... source) | promises.js:114:19:114:24 | source | store $PromiseResolveField$ | From e467ea2ea6b9061885c8184a939c2a4148450917 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Sun, 20 Jun 2021 18:40:02 +0200 Subject: [PATCH 05/12] add support for the `native-promise-only` polyfill --- javascript/change-notes/2021-06-18-promises.md | 3 ++- javascript/ql/src/semmle/javascript/Promises.qll | 2 ++ javascript/ql/test/library-tests/Promises/promises.js | 5 +++++ javascript/ql/test/library-tests/Promises/tests.expected | 3 +++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/javascript/change-notes/2021-06-18-promises.md b/javascript/change-notes/2021-06-18-promises.md index f9e88266a05f..0fd98a4b78fa 100644 --- a/javascript/change-notes/2021-06-18-promises.md +++ b/javascript/change-notes/2021-06-18-promises.md @@ -5,4 +5,5 @@ lgtm,codescanning [promise](https://npmjs.com/package/promise), [promise-polyfill](https://npmjs.com/package/promise-polyfill), [rsvp](https://npmjs.com/package/rsvp), - [es6-promise](https://npmjs.com/package/es6-promise) + [es6-promise](https://npmjs.com/package/es6-promise), + [native-promise-only](https://npmjs.com/package/native-promise-only) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index ac2c8334f68b..b4760d12fe8e 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -79,6 +79,8 @@ private DataFlow::SourceNode getAPromiseObject() { result = DataFlow::moduleImport(["promise-polyfill", "promise-polyfill/src/polyfill"]) or result = DataFlow::moduleMember(["es6-promise", "rsvp"], "Promise") + or + result = DataFlow::moduleImport("native-promise-only") } /** diff --git a/javascript/ql/test/library-tests/Promises/promises.js b/javascript/ql/test/library-tests/Promises/promises.js index 9af2c123b0a1..bacdec1f3bc1 100644 --- a/javascript/ql/test/library-tests/Promises/promises.js +++ b/javascript/ql/test/library-tests/Promises/promises.js @@ -113,3 +113,8 @@ var Promise = require('es6-promise').Promise; Promise.resolve(source); })(); + +(function() { + var Promise = require('native-promise-only'); + Promise.resolve(source); +})(); diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index 5c9a3b95d408..c682a5236183 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -41,6 +41,7 @@ test_ResolvedPromiseDefinition | promises.js:106:3:106:26 | Promise ... source) | promises.js:106:20:106:25 | source | | promises.js:107:3:107:26 | Promise ... source) | promises.js:107:20:107:25 | source | | promises.js:114:3:114:25 | Promise ... source) | promises.js:114:19:114:24 | source | +| promises.js:119:3:119:25 | Promise ... source) | promises.js:119:19:119:24 | source | test_PromiseDefinition_getARejectHandler | flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:69:26:80 | y => sink(y) | | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | @@ -426,3 +427,5 @@ typetrack | promises.js:107:3:107:26 | Promise ... source) | promises.js:107:20:107:25 | source | store $PromiseResolveField$ | | promises.js:114:3:114:25 | Promise ... source) | promises.js:114:19:114:24 | source | copy $PromiseResolveField$ | | promises.js:114:3:114:25 | Promise ... source) | promises.js:114:19:114:24 | source | store $PromiseResolveField$ | +| promises.js:119:3:119:25 | Promise ... source) | promises.js:119:19:119:24 | source | copy $PromiseResolveField$ | +| promises.js:119:3:119:25 | Promise ... source) | promises.js:119:19:119:24 | source | store $PromiseResolveField$ | From bb1c971348f34267257a578627b4e8133adf0c17 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Sun, 20 Jun 2021 18:49:06 +0200 Subject: [PATCH 06/12] add support for the `when` polyfill, and expand the defition of `ES2015PromiseDefinition` --- javascript/change-notes/2021-06-18-promises.md | 3 ++- javascript/ql/src/semmle/javascript/Promises.qll | 9 ++++++--- .../library-tests/Promises/AdditionalPromises.expected | 1 + javascript/ql/test/library-tests/Promises/promises.js | 6 ++++++ javascript/ql/test/library-tests/Promises/tests.expected | 4 ++++ 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/javascript/change-notes/2021-06-18-promises.md b/javascript/change-notes/2021-06-18-promises.md index 0fd98a4b78fa..e529c6ca1ace 100644 --- a/javascript/change-notes/2021-06-18-promises.md +++ b/javascript/change-notes/2021-06-18-promises.md @@ -6,4 +6,5 @@ lgtm,codescanning [promise-polyfill](https://npmjs.com/package/promise-polyfill), [rsvp](https://npmjs.com/package/rsvp), [es6-promise](https://npmjs.com/package/es6-promise), - [native-promise-only](https://npmjs.com/package/native-promise-only) + [native-promise-only](https://npmjs.com/package/native-promise-only), + [when](https://npmjs.com/package/when) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index b4760d12fe8e..1f066ac48296 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -81,6 +81,8 @@ private DataFlow::SourceNode getAPromiseObject() { result = DataFlow::moduleMember(["es6-promise", "rsvp"], "Promise") or result = DataFlow::moduleImport("native-promise-only") + or + result = DataFlow::moduleImport("when") } /** @@ -97,10 +99,11 @@ class PromiseCandidate extends DataFlow::InvokeNode { } /** - * A promise object created by the standard ECMAScript 2015 `Promise` constructor. + * A promise object created by the standard ECMAScript 2015 `Promise` constructor, + * or a polyfill implementing a superset of the ECMAScript 2015 `Promise` API. */ -private class ES2015PromiseDefinition extends PromiseDefinition, DataFlow::NewNode { - ES2015PromiseDefinition() { this = getAPromiseObject().getAnInstantiation() } +private class ES2015PromiseDefinition extends PromiseDefinition, DataFlow::InvokeNode { + ES2015PromiseDefinition() { this = getAPromiseObject().getAnInvocation() } override DataFlow::FunctionNode getExecutor() { result = getCallback(0) } } diff --git a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected index 3b2f628ec3d1..a093621ca753 100644 --- a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected +++ b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected @@ -81,3 +81,4 @@ | promises.js:79:19:79:41 | Promise ... source) | | promises.js:88:17:90:4 | Q.Promi ... );\\n }) | | promises.js:112:17:112:62 | new RSV ... ct) {}) | +| promises.js:124:19:124:30 | when(source) | diff --git a/javascript/ql/test/library-tests/Promises/promises.js b/javascript/ql/test/library-tests/Promises/promises.js index bacdec1f3bc1..0d5524d7567a 100644 --- a/javascript/ql/test/library-tests/Promises/promises.js +++ b/javascript/ql/test/library-tests/Promises/promises.js @@ -118,3 +118,9 @@ var Promise = require('native-promise-only'); Promise.resolve(source); })(); + +(function() { + const when = require('when'); + const promise = when(source); + const promise2 = when.resolve(source); +})(); diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index c682a5236183..797e370a949d 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -42,6 +42,7 @@ test_ResolvedPromiseDefinition | promises.js:107:3:107:26 | Promise ... source) | promises.js:107:20:107:25 | source | | promises.js:114:3:114:25 | Promise ... source) | promises.js:114:19:114:24 | source | | promises.js:119:3:119:25 | Promise ... source) | promises.js:119:19:119:24 | source | +| promises.js:125:20:125:39 | when.resolve(source) | promises.js:125:33:125:38 | source | test_PromiseDefinition_getARejectHandler | flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:69:26:80 | y => sink(y) | | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | @@ -127,6 +128,7 @@ test_PromiseDefinition | promises.js:43:19:45:6 | Q.Promi ... \\n }) | | promises.js:88:17:90:4 | Q.Promi ... );\\n }) | | promises.js:112:17:112:62 | new RSV ... ct) {}) | +| promises.js:124:19:124:30 | when(source) | test_PromiseDefinition_getAResolveHandler | flow.js:24:2:24:49 | new Pro ... ource)) | flow.js:24:56:24:67 | x => sink(x) | | flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:56:26:66 | x => foo(x) | @@ -429,3 +431,5 @@ typetrack | promises.js:114:3:114:25 | Promise ... source) | promises.js:114:19:114:24 | source | store $PromiseResolveField$ | | promises.js:119:3:119:25 | Promise ... source) | promises.js:119:19:119:24 | source | copy $PromiseResolveField$ | | promises.js:119:3:119:25 | Promise ... source) | promises.js:119:19:119:24 | source | store $PromiseResolveField$ | +| promises.js:125:20:125:39 | when.resolve(source) | promises.js:125:33:125:38 | source | copy $PromiseResolveField$ | +| promises.js:125:20:125:39 | when.resolve(source) | promises.js:125:33:125:38 | source | store $PromiseResolveField$ | From b574292dab3e2f895359fad695295e1173744005 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Sun, 20 Jun 2021 19:16:14 +0200 Subject: [PATCH 07/12] add support for the `pinkie-promise` polyfill --- javascript/change-notes/2021-06-18-promises.md | 3 ++- javascript/ql/src/semmle/javascript/Promises.qll | 2 ++ .../test/library-tests/Promises/AdditionalPromises.expected | 1 + javascript/ql/test/library-tests/Promises/promises.js | 5 +++++ javascript/ql/test/library-tests/Promises/tests.expected | 3 +++ 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/javascript/change-notes/2021-06-18-promises.md b/javascript/change-notes/2021-06-18-promises.md index e529c6ca1ace..a76a81efb838 100644 --- a/javascript/change-notes/2021-06-18-promises.md +++ b/javascript/change-notes/2021-06-18-promises.md @@ -7,4 +7,5 @@ lgtm,codescanning [rsvp](https://npmjs.com/package/rsvp), [es6-promise](https://npmjs.com/package/es6-promise), [native-promise-only](https://npmjs.com/package/native-promise-only), - [when](https://npmjs.com/package/when) + [when](https://npmjs.com/package/when), + [pinkie-promise](https://npmjs.com/package/pinkie-promise) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index 1f066ac48296..420f8707e244 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -83,6 +83,8 @@ private DataFlow::SourceNode getAPromiseObject() { result = DataFlow::moduleImport("native-promise-only") or result = DataFlow::moduleImport("when") + or + result = DataFlow::moduleImport("pinkie-promise") } /** diff --git a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected index a093621ca753..56bb2cebd132 100644 --- a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected +++ b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected @@ -82,3 +82,4 @@ | promises.js:88:17:90:4 | Q.Promi ... );\\n }) | | promises.js:112:17:112:62 | new RSV ... ct) {}) | | promises.js:124:19:124:30 | when(source) | +| promises.js:130:14:130:69 | new Pro ... s'); }) | diff --git a/javascript/ql/test/library-tests/Promises/promises.js b/javascript/ql/test/library-tests/Promises/promises.js index 0d5524d7567a..72046d337118 100644 --- a/javascript/ql/test/library-tests/Promises/promises.js +++ b/javascript/ql/test/library-tests/Promises/promises.js @@ -124,3 +124,8 @@ const promise = when(source); const promise2 = when.resolve(source); })(); + +(function() { + var Promise = require('pinkie-promise'); + var prom = new Promise(function (resolve) { resolve('unicorns'); }); +})(); diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index 797e370a949d..1a77336cb17a 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -91,6 +91,7 @@ test_PromiseDefinition_getExecutor | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:29:45:5 | functio ... ;\\n } | | promises.js:88:17:90:4 | Q.Promi ... );\\n }) | promises.js:88:27:90:3 | functio ... e);\\n } | | promises.js:112:17:112:62 | new RSV ... ct) {}) | promises.js:112:34:112:61 | functio ... ect) {} | +| promises.js:130:14:130:69 | new Pro ... s'); }) | promises.js:130:26:130:68 | functio ... ns'); } | test_PromiseDefinition_getAFinallyHandler | flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:58:105:76 | x => {throw source} | | flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:58:109:70 | x => rejected | @@ -129,6 +130,7 @@ test_PromiseDefinition | promises.js:88:17:90:4 | Q.Promi ... );\\n }) | | promises.js:112:17:112:62 | new RSV ... ct) {}) | | promises.js:124:19:124:30 | when(source) | +| promises.js:130:14:130:69 | new Pro ... s'); }) | test_PromiseDefinition_getAResolveHandler | flow.js:24:2:24:49 | new Pro ... ource)) | flow.js:24:56:24:67 | x => sink(x) | | flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:56:26:66 | x => foo(x) | @@ -211,6 +213,7 @@ test_PromiseDefinition_getResolveParameter | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:39:43:45 | resolve | | promises.js:88:17:90:4 | Q.Promi ... );\\n }) | promises.js:88:37:88:43 | resolve | | promises.js:112:17:112:62 | new RSV ... ct) {}) | promises.js:112:43:112:49 | resolve | +| promises.js:130:14:130:69 | new Pro ... s'); }) | promises.js:130:36:130:42 | resolve | test_PromiseDefinition_getACatchHandler | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | | flow.js:48:2:48:36 | new Pro ... urce }) | flow.js:48:44:48:55 | x => sink(x) | From 5cb3c2c650e8917348969911e8ced6715b86e499 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Sun, 20 Jun 2021 19:20:50 +0200 Subject: [PATCH 08/12] add support for the `pinkie` polyfill --- javascript/change-notes/2021-06-18-promises.md | 3 ++- javascript/ql/src/semmle/javascript/Promises.qll | 2 ++ .../library-tests/Promises/AdditionalPromises.expected | 1 + javascript/ql/test/library-tests/Promises/promises.js | 7 +++++++ javascript/ql/test/library-tests/Promises/tests.expected | 6 ++++++ 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/javascript/change-notes/2021-06-18-promises.md b/javascript/change-notes/2021-06-18-promises.md index a76a81efb838..fc932db3e2d6 100644 --- a/javascript/change-notes/2021-06-18-promises.md +++ b/javascript/change-notes/2021-06-18-promises.md @@ -8,4 +8,5 @@ lgtm,codescanning [es6-promise](https://npmjs.com/package/es6-promise), [native-promise-only](https://npmjs.com/package/native-promise-only), [when](https://npmjs.com/package/when), - [pinkie-promise](https://npmjs.com/package/pinkie-promise) + [pinkie-promise](https://npmjs.com/package/pinkie-promise), + [pinkie](https://npmjs.com/package/pinkie) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index 420f8707e244..a9c181b553d7 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -85,6 +85,8 @@ private DataFlow::SourceNode getAPromiseObject() { result = DataFlow::moduleImport("when") or result = DataFlow::moduleImport("pinkie-promise") + or + result = DataFlow::moduleImport("pinkie") } /** diff --git a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected index 56bb2cebd132..e95c5e29ab8f 100644 --- a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected +++ b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected @@ -83,3 +83,4 @@ | promises.js:112:17:112:62 | new RSV ... ct) {}) | | promises.js:124:19:124:30 | when(source) | | promises.js:130:14:130:69 | new Pro ... s'); }) | +| promises.js:135:3:137:4 | new Pro ... );\\n }) | diff --git a/javascript/ql/test/library-tests/Promises/promises.js b/javascript/ql/test/library-tests/Promises/promises.js index 72046d337118..262aaae7028b 100644 --- a/javascript/ql/test/library-tests/Promises/promises.js +++ b/javascript/ql/test/library-tests/Promises/promises.js @@ -129,3 +129,10 @@ var Promise = require('pinkie-promise'); var prom = new Promise(function (resolve) { resolve('unicorns'); }); })(); + +(function() { + var Promise = require('pinkie'); + new Promise(function (resolve, reject) { + resolve(data); + }); +})(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index 1a77336cb17a..e779d8306376 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -92,6 +92,7 @@ test_PromiseDefinition_getExecutor | promises.js:88:17:90:4 | Q.Promi ... );\\n }) | promises.js:88:27:90:3 | functio ... e);\\n } | | promises.js:112:17:112:62 | new RSV ... ct) {}) | promises.js:112:34:112:61 | functio ... ect) {} | | promises.js:130:14:130:69 | new Pro ... s'); }) | promises.js:130:26:130:68 | functio ... ns'); } | +| promises.js:135:3:137:4 | new Pro ... );\\n }) | promises.js:135:15:137:3 | functio ... a);\\n } | test_PromiseDefinition_getAFinallyHandler | flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:58:105:76 | x => {throw source} | | flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:58:109:70 | x => rejected | @@ -131,6 +132,7 @@ test_PromiseDefinition | promises.js:112:17:112:62 | new RSV ... ct) {}) | | promises.js:124:19:124:30 | when(source) | | promises.js:130:14:130:69 | new Pro ... s'); }) | +| promises.js:135:3:137:4 | new Pro ... );\\n }) | test_PromiseDefinition_getAResolveHandler | flow.js:24:2:24:49 | new Pro ... ource)) | flow.js:24:56:24:67 | x => sink(x) | | flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:56:26:66 | x => foo(x) | @@ -181,6 +183,7 @@ test_PromiseDefinition_getRejectParameter | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:48:43:53 | reject | | promises.js:88:17:90:4 | Q.Promi ... );\\n }) | promises.js:88:46:88:51 | reject | | promises.js:112:17:112:62 | new RSV ... ct) {}) | promises.js:112:52:112:57 | reject | +| promises.js:135:3:137:4 | new Pro ... );\\n }) | promises.js:135:34:135:39 | reject | test_PromiseDefinition_getResolveParameter | flow.js:7:11:7:59 | new Pro ... ource)) | flow.js:7:24:7:30 | resolve | | flow.js:10:11:10:58 | new Pro ... ource)) | flow.js:10:24:10:30 | resolve | @@ -214,6 +217,7 @@ test_PromiseDefinition_getResolveParameter | promises.js:88:17:90:4 | Q.Promi ... );\\n }) | promises.js:88:37:88:43 | resolve | | promises.js:112:17:112:62 | new RSV ... ct) {}) | promises.js:112:43:112:49 | resolve | | promises.js:130:14:130:69 | new Pro ... s'); }) | promises.js:130:36:130:42 | resolve | +| promises.js:135:3:137:4 | new Pro ... );\\n }) | promises.js:135:25:135:31 | resolve | test_PromiseDefinition_getACatchHandler | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | | flow.js:48:2:48:36 | new Pro ... urce }) | flow.js:48:44:48:55 | x => sink(x) | @@ -436,3 +440,5 @@ typetrack | promises.js:119:3:119:25 | Promise ... source) | promises.js:119:19:119:24 | source | store $PromiseResolveField$ | | promises.js:125:20:125:39 | when.resolve(source) | promises.js:125:33:125:38 | source | copy $PromiseResolveField$ | | promises.js:125:20:125:39 | when.resolve(source) | promises.js:125:33:125:38 | source | store $PromiseResolveField$ | +| promises.js:135:3:137:4 | new Pro ... );\\n }) | promises.js:136:13:136:16 | data | copy $PromiseResolveField$ | +| promises.js:135:3:137:4 | new Pro ... );\\n }) | promises.js:136:13:136:16 | data | store $PromiseResolveField$ | From cb82cdf6e90b6179efacf736bd09181b58c501ac Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Sun, 20 Jun 2021 19:37:02 +0200 Subject: [PATCH 09/12] add support for the `synchronous-promise` library --- javascript/change-notes/2021-06-18-promises.md | 3 ++- javascript/ql/src/semmle/javascript/Promises.qll | 2 ++ javascript/ql/test/library-tests/Promises/promises.js | 8 +++++++- javascript/ql/test/library-tests/Promises/tests.expected | 3 +++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/javascript/change-notes/2021-06-18-promises.md b/javascript/change-notes/2021-06-18-promises.md index fc932db3e2d6..5ece14609dae 100644 --- a/javascript/change-notes/2021-06-18-promises.md +++ b/javascript/change-notes/2021-06-18-promises.md @@ -9,4 +9,5 @@ lgtm,codescanning [native-promise-only](https://npmjs.com/package/native-promise-only), [when](https://npmjs.com/package/when), [pinkie-promise](https://npmjs.com/package/pinkie-promise), - [pinkie](https://npmjs.com/package/pinkie) + [pinkie](https://npmjs.com/package/pinkie), + [synchronous-promise](https://npmjs.com/package/synchronous-promise) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index a9c181b553d7..2bbc8deab35c 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -87,6 +87,8 @@ private DataFlow::SourceNode getAPromiseObject() { result = DataFlow::moduleImport("pinkie-promise") or result = DataFlow::moduleImport("pinkie") + or + result = DataFlow::moduleMember("synchronous-promise", "SynchronousPromise") } /** diff --git a/javascript/ql/test/library-tests/Promises/promises.js b/javascript/ql/test/library-tests/Promises/promises.js index 262aaae7028b..4cdb6c51b89a 100644 --- a/javascript/ql/test/library-tests/Promises/promises.js +++ b/javascript/ql/test/library-tests/Promises/promises.js @@ -135,4 +135,10 @@ new Promise(function (resolve, reject) { resolve(data); }); -})(); \ No newline at end of file +})(); + +(function() { + import { SynchronousPromise } from 'synchronous-promise'; + // is technically not a promise, but behaves like one. + var promise = SynchronousPromise.resolve(source); +})(); diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index e779d8306376..4a28d9c02c68 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -43,6 +43,7 @@ test_ResolvedPromiseDefinition | promises.js:114:3:114:25 | Promise ... source) | promises.js:114:19:114:24 | source | | promises.js:119:3:119:25 | Promise ... source) | promises.js:119:19:119:24 | source | | promises.js:125:20:125:39 | when.resolve(source) | promises.js:125:33:125:38 | source | +| promises.js:143:17:143:50 | Synchro ... source) | promises.js:143:44:143:49 | source | test_PromiseDefinition_getARejectHandler | flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:69:26:80 | y => sink(y) | | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | @@ -442,3 +443,5 @@ typetrack | promises.js:125:20:125:39 | when.resolve(source) | promises.js:125:33:125:38 | source | store $PromiseResolveField$ | | promises.js:135:3:137:4 | new Pro ... );\\n }) | promises.js:136:13:136:16 | data | copy $PromiseResolveField$ | | promises.js:135:3:137:4 | new Pro ... );\\n }) | promises.js:136:13:136:16 | data | store $PromiseResolveField$ | +| promises.js:143:17:143:50 | Synchro ... source) | promises.js:143:44:143:49 | source | copy $PromiseResolveField$ | +| promises.js:143:17:143:50 | Synchro ... source) | promises.js:143:44:143:49 | source | store $PromiseResolveField$ | From 085efe5d204511d345f8b62eddb2d5440c55b3da Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Sun, 20 Jun 2021 19:42:33 +0200 Subject: [PATCH 10/12] add support for the `any-promise` polyfill --- javascript/change-notes/2021-06-18-promises.md | 3 ++- javascript/ql/src/semmle/javascript/Promises.qll | 2 ++ .../test/library-tests/Promises/AdditionalPromises.expected | 1 + javascript/ql/test/library-tests/Promises/promises.js | 5 +++++ javascript/ql/test/library-tests/Promises/tests.expected | 4 ++++ 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/javascript/change-notes/2021-06-18-promises.md b/javascript/change-notes/2021-06-18-promises.md index 5ece14609dae..7037e351c046 100644 --- a/javascript/change-notes/2021-06-18-promises.md +++ b/javascript/change-notes/2021-06-18-promises.md @@ -10,4 +10,5 @@ lgtm,codescanning [when](https://npmjs.com/package/when), [pinkie-promise](https://npmjs.com/package/pinkie-promise), [pinkie](https://npmjs.com/package/pinkie), - [synchronous-promise](https://npmjs.com/package/synchronous-promise) + [synchronous-promise](https://npmjs.com/package/synchronous-promise), + [any-promise](https://npmjs.com/package/any-promise) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index 2bbc8deab35c..2e7bb1c93bc6 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -89,6 +89,8 @@ private DataFlow::SourceNode getAPromiseObject() { result = DataFlow::moduleImport("pinkie") or result = DataFlow::moduleMember("synchronous-promise", "SynchronousPromise") + or + result = DataFlow::moduleImport("any-promise") } /** diff --git a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected index e95c5e29ab8f..e42037e1f077 100644 --- a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected +++ b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected @@ -84,3 +84,4 @@ | promises.js:124:19:124:30 | when(source) | | promises.js:130:14:130:69 | new Pro ... s'); }) | | promises.js:135:3:137:4 | new Pro ... );\\n }) | +| promises.js:148:10:148:49 | new Pro ... ect){}) | diff --git a/javascript/ql/test/library-tests/Promises/promises.js b/javascript/ql/test/library-tests/Promises/promises.js index 4cdb6c51b89a..3676c5537ae4 100644 --- a/javascript/ql/test/library-tests/Promises/promises.js +++ b/javascript/ql/test/library-tests/Promises/promises.js @@ -142,3 +142,8 @@ // is technically not a promise, but behaves like one. var promise = SynchronousPromise.resolve(source); })(); + +(function() { + var Promise = require('any-promise'); + return new Promise(function(resolve, reject){}) +})(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index 4a28d9c02c68..ac86ab5c46d4 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -94,6 +94,7 @@ test_PromiseDefinition_getExecutor | promises.js:112:17:112:62 | new RSV ... ct) {}) | promises.js:112:34:112:61 | functio ... ect) {} | | promises.js:130:14:130:69 | new Pro ... s'); }) | promises.js:130:26:130:68 | functio ... ns'); } | | promises.js:135:3:137:4 | new Pro ... );\\n }) | promises.js:135:15:137:3 | functio ... a);\\n } | +| promises.js:148:10:148:49 | new Pro ... ect){}) | promises.js:148:22:148:48 | functio ... ject){} | test_PromiseDefinition_getAFinallyHandler | flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:58:105:76 | x => {throw source} | | flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:58:109:70 | x => rejected | @@ -134,6 +135,7 @@ test_PromiseDefinition | promises.js:124:19:124:30 | when(source) | | promises.js:130:14:130:69 | new Pro ... s'); }) | | promises.js:135:3:137:4 | new Pro ... );\\n }) | +| promises.js:148:10:148:49 | new Pro ... ect){}) | test_PromiseDefinition_getAResolveHandler | flow.js:24:2:24:49 | new Pro ... ource)) | flow.js:24:56:24:67 | x => sink(x) | | flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:56:26:66 | x => foo(x) | @@ -185,6 +187,7 @@ test_PromiseDefinition_getRejectParameter | promises.js:88:17:90:4 | Q.Promi ... );\\n }) | promises.js:88:46:88:51 | reject | | promises.js:112:17:112:62 | new RSV ... ct) {}) | promises.js:112:52:112:57 | reject | | promises.js:135:3:137:4 | new Pro ... );\\n }) | promises.js:135:34:135:39 | reject | +| promises.js:148:10:148:49 | new Pro ... ect){}) | promises.js:148:40:148:45 | reject | test_PromiseDefinition_getResolveParameter | flow.js:7:11:7:59 | new Pro ... ource)) | flow.js:7:24:7:30 | resolve | | flow.js:10:11:10:58 | new Pro ... ource)) | flow.js:10:24:10:30 | resolve | @@ -219,6 +222,7 @@ test_PromiseDefinition_getResolveParameter | promises.js:112:17:112:62 | new RSV ... ct) {}) | promises.js:112:43:112:49 | resolve | | promises.js:130:14:130:69 | new Pro ... s'); }) | promises.js:130:36:130:42 | resolve | | promises.js:135:3:137:4 | new Pro ... );\\n }) | promises.js:135:25:135:31 | resolve | +| promises.js:148:10:148:49 | new Pro ... ect){}) | promises.js:148:31:148:37 | resolve | test_PromiseDefinition_getACatchHandler | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | | flow.js:48:2:48:36 | new Pro ... urce }) | flow.js:48:44:48:55 | x => sink(x) | From 95a7b1631507a0435c11aa2d8e921d3cd81244a2 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Sun, 20 Jun 2021 19:49:20 +0200 Subject: [PATCH 11/12] add support for the `lie` polyfill --- javascript/change-notes/2021-06-18-promises.md | 3 ++- javascript/ql/src/semmle/javascript/Promises.qll | 2 ++ javascript/ql/test/library-tests/Promises/promises.js | 5 +++++ javascript/ql/test/library-tests/Promises/tests.expected | 3 +++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/javascript/change-notes/2021-06-18-promises.md b/javascript/change-notes/2021-06-18-promises.md index 7037e351c046..67d71aab7cce 100644 --- a/javascript/change-notes/2021-06-18-promises.md +++ b/javascript/change-notes/2021-06-18-promises.md @@ -11,4 +11,5 @@ lgtm,codescanning [pinkie-promise](https://npmjs.com/package/pinkie-promise), [pinkie](https://npmjs.com/package/pinkie), [synchronous-promise](https://npmjs.com/package/synchronous-promise), - [any-promise](https://npmjs.com/package/any-promise) + [any-promise](https://npmjs.com/package/any-promise), + [lie](https://npmjs.com/package/lie) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index 2e7bb1c93bc6..72a2e42bac5f 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -91,6 +91,8 @@ private DataFlow::SourceNode getAPromiseObject() { result = DataFlow::moduleMember("synchronous-promise", "SynchronousPromise") or result = DataFlow::moduleImport("any-promise") + or + result = DataFlow::moduleImport("lie") } /** diff --git a/javascript/ql/test/library-tests/Promises/promises.js b/javascript/ql/test/library-tests/Promises/promises.js index 3676c5537ae4..2d91d132033b 100644 --- a/javascript/ql/test/library-tests/Promises/promises.js +++ b/javascript/ql/test/library-tests/Promises/promises.js @@ -146,4 +146,9 @@ (function() { var Promise = require('any-promise'); return new Promise(function(resolve, reject){}) +})(); + +(function() { + var Promise = require('lie'); + var promise = Promise.resolve(source); })(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index ac86ab5c46d4..1f07154ce48b 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -44,6 +44,7 @@ test_ResolvedPromiseDefinition | promises.js:119:3:119:25 | Promise ... source) | promises.js:119:19:119:24 | source | | promises.js:125:20:125:39 | when.resolve(source) | promises.js:125:33:125:38 | source | | promises.js:143:17:143:50 | Synchro ... source) | promises.js:143:44:143:49 | source | +| promises.js:153:17:153:39 | Promise ... source) | promises.js:153:33:153:38 | source | test_PromiseDefinition_getARejectHandler | flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:69:26:80 | y => sink(y) | | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | @@ -449,3 +450,5 @@ typetrack | promises.js:135:3:137:4 | new Pro ... );\\n }) | promises.js:136:13:136:16 | data | store $PromiseResolveField$ | | promises.js:143:17:143:50 | Synchro ... source) | promises.js:143:44:143:49 | source | copy $PromiseResolveField$ | | promises.js:143:17:143:50 | Synchro ... source) | promises.js:143:44:143:49 | source | store $PromiseResolveField$ | +| promises.js:153:17:153:39 | Promise ... source) | promises.js:153:33:153:38 | source | copy $PromiseResolveField$ | +| promises.js:153:17:153:39 | Promise ... source) | promises.js:153:33:153:38 | source | store $PromiseResolveField$ | From f53955fb5e0de37f91c0bdbeee8f448c6cc054e8 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Sun, 20 Jun 2021 19:52:34 +0200 Subject: [PATCH 12/12] add support for the `promise.allsettled` library --- javascript/change-notes/2021-06-18-promises.md | 3 ++- javascript/ql/src/semmle/javascript/Promises.qll | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/javascript/change-notes/2021-06-18-promises.md b/javascript/change-notes/2021-06-18-promises.md index 67d71aab7cce..b887666dbeb4 100644 --- a/javascript/change-notes/2021-06-18-promises.md +++ b/javascript/change-notes/2021-06-18-promises.md @@ -12,4 +12,5 @@ lgtm,codescanning [pinkie](https://npmjs.com/package/pinkie), [synchronous-promise](https://npmjs.com/package/synchronous-promise), [any-promise](https://npmjs.com/package/any-promise), - [lie](https://npmjs.com/package/lie) + [lie](https://npmjs.com/package/lie), + [promise.allsettled](https://npmjs.com/package/promise.allsettled) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index 72a2e42bac5f..262f35081d16 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -157,8 +157,11 @@ class ResolvedES2015PromiseDefinition extends ResolvedPromiseDefinition { */ class AggregateES2015PromiseDefinition extends PromiseCreationCall { AggregateES2015PromiseDefinition() { + exists(string m | m = "all" or m = "race" or m = "any" or m = "allSettled" | this = getAPromiseObject().getAMemberCall(m) ) + or + this = DataFlow::moduleImport("promise.allsettled").getACall() } override DataFlow::Node getValue() {