diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index d74aafc265c..ee4b42addcc 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -1,9 +1,380 @@ /** - * Provides classes for modelling promise libraries. + * Provides classes for modelling promises and their data-flow. */ import javascript +/** + * A definition of a `Promise` object. + */ +abstract class PromiseDefinition extends DataFlow::SourceNode { + /** Gets the executor function of this promise object. */ + abstract DataFlow::FunctionNode getExecutor(); + + /** Gets the `resolve` parameter of the executor function. */ + DataFlow::ParameterNode getResolveParameter() { result = getExecutor().getParameter(0) } + + /** Gets the `reject` parameter of the executor function. */ + DataFlow::ParameterNode getRejectParameter() { result = getExecutor().getParameter(1) } + + /** Gets the `i`th callback handler installed by method `m`. */ + private DataFlow::FunctionNode getAHandler(string m, int i) { + result = getAMethodCall(m).getCallback(i) + } + + /** + * Gets a function that handles promise resolution, including both + * `then` handlers and `finally` handlers. + */ + DataFlow::FunctionNode getAResolveHandler() { + result = getAHandler("then", 0) or + result = getAFinallyHandler() + } + + /** + * Gets a function that handles promise rejection, including + * `then` handlers, `catch` handlers and `finally` handlers. + */ + DataFlow::FunctionNode getARejectHandler() { + result = getAHandler("then", 1) or + result = getACatchHandler() or + result = getAFinallyHandler() + } + + /** + * Gets a `catch` handler of this promise. + */ + DataFlow::FunctionNode getACatchHandler() { result = getAHandler("catch", 0) } + + /** + * Gets a `finally` handler of this promise. + */ + DataFlow::FunctionNode getAFinallyHandler() { result = getAHandler("finally", 0) } +} + +/** Holds if the `i`th callback handler is installed by method `m`. */ +private predicate hasHandler(DataFlow::InvokeNode promise, string m, int i) { + exists(promise.getAMethodCall(m).getCallback(i)) +} + +/** + * A call that looks like a Promise. + * + * For example, this could be the call `promise(f).then(function(v){...})` + */ +class PromiseCandidate extends DataFlow::InvokeNode { + PromiseCandidate() { + hasHandler(this, "then", [0 .. 1]) or + hasHandler(this, "catch", 0) or + hasHandler(this, "finally", 0) + } +} + +/** + * A promise object created by the standard ECMAScript 2015 `Promise` constructor. + */ +private class ES2015PromiseDefinition extends PromiseDefinition, DataFlow::NewNode { + ES2015PromiseDefinition() { this = DataFlow::globalVarRef("Promise").getAnInstantiation() } + + override DataFlow::FunctionNode getExecutor() { result = getCallback(0) } +} + +/** + * A promise that is created and resolved with one or more value. + */ +abstract class PromiseCreationCall extends DataFlow::CallNode { + /** + * Gets the value this promise is resolved with. + */ + abstract DataFlow::Node getValue(); +} + +/** + * A promise that is created using a `.resolve()` call. + */ +abstract class ResolvedPromiseDefinition 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") + } + + override DataFlow::Node getValue() { result = getArgument(0) } +} + +/** + * An aggregated promise produced either by `Promise.all` or `Promise.race`. + */ +class AggregateES2015PromiseDefinition extends PromiseCreationCall { + AggregateES2015PromiseDefinition() { + exists(string m | m = "all" or m = "race" | + this = DataFlow::globalVarRef("Promise").getAMemberCall(m) + ) + } + + override DataFlow::Node getValue() { + result = getArgument(0).getALocalSource().(DataFlow::ArrayCreationNode).getAnElement() + } +} + +/** + * This module defines how data-flow propagates into and out a Promise. + */ +private module PromiseFlow { + /** + * A promise from which data-flow can flow into or out of. + * + * This promise can both be a promise created by e.g. `new Promise(..)` or `Promise.resolve(..)`, + * or the result from calling a method on a promise e.g. `promise.then(..)`. + * + * The 4 methods in this class describe that ordinary and exceptional flow can flow into and out of this promise. + */ + private abstract class PromiseNode extends DataFlow::SourceNode { + + /** + * Get a DataFlow::Node for a value that this promise is resolved with. + * The value is sent either to a chained promise, or to an `await` expression. + * + * The value is e.g. an argument to `resolve(..)`, or a return value from a `.then(..)` handler. + */ + DataFlow::Node getASentResolveValue() { none() } + + /** + * Get the DataFlow::Node that receives the value that this promise has been resolved with. + * + * E.g. the `x` in `promise.then((x) => ..)`. + */ + DataFlow::Node getReceivedResolveValue() { none() } + + /** + * Get a DataFlow::Node for a value that this promise is rejected with. + * The value is sent either to a chained promise, or thrown by an `await` expression. + * + * The value is e.g. an argument to `reject(..)`, or an exception thrown by the promise executor. + */ + DataFlow::Node getASentRejectValue() { none() } + + /** + * Get the DataFlow::Node that receives the value that this promise has been rejected with. + * + * E.g. the `x` in `promise.catch((x) => ..)`. + */ + DataFlow::Node getReceivedRejectValue() { none() } + } + + /** + * A PromiseNode for a PromiseDefinition. + * E.g. `new Promise(..)`. + */ + private class PromiseDefinitionNode extends PromiseNode { + PromiseDefinition promise; + + PromiseDefinitionNode() { this = promise } + + override DataFlow::Node getASentResolveValue() { + result = promise.getResolveParameter().getACall().getArgument(0) + } + + override DataFlow::Node getASentRejectValue() { + result = promise.getRejectParameter().getACall().getArgument(0) + or + result = promise.getExecutor().getExceptionalReturn() + } + } + + /** + * A PromiseNode for a call that creates a promise. + * E.g. `Promise.resolve(..)` or `Promise.all(..)`. + */ + private class PromiseCreationNode extends PromiseNode { + PromiseCreationCall promise; + + PromiseCreationNode() { this = promise } + + override DataFlow::Node getASentResolveValue() { + exists(DataFlow::Node value | value = promise.getValue() | + not value instanceof PromiseNode and + result = value + or + result = value.(PromiseNode).getASentResolveValue() + ) + } + + override DataFlow::Node getASentRejectValue() { + result = promise.getValue().(PromiseNode).getASentRejectValue() + } + } + + /** + * A node referring to a PromiseNode through type-tracking. + */ + private class TrackedPromiseNode extends PromiseNode { + PromiseNode base; + TrackedPromiseNode() { + this = trackPromise(DataFlow::TypeTracker::end(), base) and + not this instanceof PromiseDefinitionNode and + not this instanceof PromiseCreationNode + } + + override DataFlow::Node getASentResolveValue() { result = base.getASentResolveValue() } + override DataFlow::Node getReceivedResolveValue() { result = base.getReceivedResolveValue() } + override DataFlow::Node getASentRejectValue() { result = base.getASentRejectValue() } + override DataFlow::Node getReceivedRejectValue() { result = base.getReceivedRejectValue() } + } + + private DataFlow::SourceNode trackPromise(DataFlow::TypeTracker t, PromiseNode promise) { + t.start() and result = promise + or + exists(DataFlow::TypeTracker t2 | result = trackPromise(t2, promise).track(t2, t)) + } + + /** + * A PromiseNode that is a method call on an existing PromiseNode. + * E.g. `promise.then(..)`. + */ + private abstract class ChainedPromiseNode extends PromiseNode, DataFlow::MethodCallNode { + PromiseNode base; + + ChainedPromiseNode() { this = base.getAMethodCall(_) } + + PromiseNode getBase() { result = base } + } + + /** + * A PromiseNode for the `.then(..)` method on an existing promise. + */ + private class PromiseThenNode extends ChainedPromiseNode { + PromiseThenNode() { this = base.getAMethodCall("then") } + + override DataFlow::Node getASentResolveValue() { + exists(DataFlow::Node ret | ret = this.getCallback(0).getAReturn() | + if ret instanceof PromiseNode + then result = ret.(PromiseNode).getReceivedResolveValue() + else result = ret + ) + } + + override DataFlow::Node getASentRejectValue() { + not exists(this.getCallback(1)) and result = base.getASentRejectValue() + or + result = this.getCallback([0..1]).getExceptionalReturn() + } + + override DataFlow::Node getReceivedResolveValue() { result = this.getCallback(0).getParameter(0) } + + override DataFlow::Node getReceivedRejectValue() { result = this.getCallback(1).getParameter(0) } + } + + /** + * A PromiseNode for the `.finally(..)` method on an existing promise. + */ + private class PromiseFinallyNode extends ChainedPromiseNode { + PromiseFinallyNode() { this = base.getAMethodCall("finally") } + + override DataFlow::Node getASentResolveValue() { result = base.getASentResolveValue() } + + override DataFlow::Node getASentRejectValue() { + result = base.getASentRejectValue() + or + result = this.getCallback(0).getExceptionalReturn() + } + } + + /** + * A PromiseNode for the `.catch(..)` method on an existing promise. + */ + private class PromiseCatchNode extends ChainedPromiseNode { + PromiseCatchNode() { this = base.getAMethodCall("catch") } + + override DataFlow::Node getASentResolveValue() { + exists(DataFlow::Node ret | ret = this.getCallback(0).getAReturn() | + if ret instanceof PromiseNode + then result = ret.(PromiseNode).getReceivedResolveValue() + else result = ret + ) + or + result = base.getASentResolveValue() + } + + override DataFlow::Node getASentRejectValue() { result = this.getCallback(0).getExceptionalReturn() } + + override DataFlow::Node getReceivedResolveValue() { none() } + + override DataFlow::Node getReceivedRejectValue() { result = this.getCallback(0).getParameter(0) } + } + + + private ChainedPromiseNode getAChainedPromise(PromiseNode p) { result.getBase() = p} + + /** + * A data flow edge from a promise resolve/reject to the corresponding handler (or `await` expression). + */ + private class PromiseFlowStep extends DataFlow::AdditionalFlowStep { + PromiseNode promise; + + PromiseFlowStep() { this = promise } + + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + pred = promise.getASentResolveValue() and + succ = getAChainedPromise(promise).getReceivedResolveValue() + or + pred = promise.getASentRejectValue() and + succ = getAChainedPromise(promise).getReceivedRejectValue() + or + pred = promise.getASentResolveValue() and + exists(DataFlow::SourceNode awaitNode | + awaitNode.asExpr().(AwaitExpr).getOperand() = promise.asExpr() and + succ = awaitNode + ) + or + pred = promise.getASentRejectValue() and + exists(DataFlow::SourceNode awaitNode | + awaitNode.asExpr().(AwaitExpr).getOperand() = promise.asExpr() and + succ = awaitNode.asExpr().getExceptionTarget() + ) + } + } +} + +/** + * Holds if taint propagates from `pred` to `succ` through promises. + */ +predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) { + // from `x` to `new Promise((res, rej) => res(x))` + pred = succ.(PromiseDefinition).getResolveParameter().getACall().getArgument(0) + or + // from `x` to `Promise.resolve(x)` + pred = succ.(PromiseCreationCall).getValue() + or + exists(DataFlow::MethodCallNode thn, DataFlow::FunctionNode cb | + thn.getMethodName() = "then" and cb = thn.getCallback(0) + | + // from `p` to `x` in `p.then(x => ...)` + pred = thn.getReceiver() and + succ = cb.getParameter(0) + or + // from `v` to `p.then(x => return v)` + pred = cb.getAReturn() and + succ = thn + ) +} + +/** + * An additional taint step that involves promises. + */ +private class PromiseTaintStep extends TaintTracking::AdditionalTaintStep { + DataFlow::Node source; + + PromiseTaintStep() { promiseTaintStep(source, this) } + + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + pred = source and succ = this + } +} + /** * Provides classes for working with the `bluebird` library (http://bluebirdjs.com). */ diff --git a/javascript/ql/src/semmle/javascript/StandardLibrary.qll b/javascript/ql/src/semmle/javascript/StandardLibrary.qll index 03b15b012e0..0de4cce8e05 100644 --- a/javascript/ql/src/semmle/javascript/StandardLibrary.qll +++ b/javascript/ql/src/semmle/javascript/StandardLibrary.qll @@ -76,191 +76,6 @@ private class AnalyzedThisInArrayIterationFunction extends AnalyzedNode, DataFlo } } -/** - * A definition of a `Promise` object. - */ -abstract class PromiseDefinition extends DataFlow::SourceNode { - /** Gets the executor function of this promise object. */ - abstract DataFlow::FunctionNode getExecutor(); - - /** Gets the `resolve` parameter of the executor function. */ - DataFlow::ParameterNode getResolveParameter() { result = getExecutor().getParameter(0) } - - /** Gets the `reject` parameter of the executor function. */ - DataFlow::ParameterNode getRejectParameter() { result = getExecutor().getParameter(1) } - - /** Gets the `i`th callback handler installed by method `m`. */ - private DataFlow::FunctionNode getAHandler(string m, int i) { - result = getAMethodCall(m).getCallback(i) - } - - /** - * Gets a function that handles promise resolution, including both - * `then` handlers and `finally` handlers. - */ - DataFlow::FunctionNode getAResolveHandler() { - result = getAHandler("then", 0) or - result = getAFinallyHandler() - } - - /** - * Gets a function that handles promise rejection, including - * `then` handlers, `catch` handlers and `finally` handlers. - */ - DataFlow::FunctionNode getARejectHandler() { - result = getAHandler("then", 1) or - result = getACatchHandler() or - result = getAFinallyHandler() - } - - /** - * Gets a `catch` handler of this promise. - */ - DataFlow::FunctionNode getACatchHandler() { result = getAHandler("catch", 0) } - - /** - * Gets a `finally` handler of this promise. - */ - DataFlow::FunctionNode getAFinallyHandler() { result = getAHandler("finally", 0) } -} - -/** Holds if the `i`th callback handler is installed by method `m`. */ -private predicate hasHandler(DataFlow::InvokeNode promise, string m, int i) { - exists(promise.getAMethodCall(m).getCallback(i)) -} - -/** - * A call that looks like a Promise. - * - * For example, this could be the call `promise(f).then(function(v){...})` - */ -class PromiseCandidate extends DataFlow::InvokeNode { - PromiseCandidate() { - hasHandler(this, "then", [0 .. 1]) or - hasHandler(this, "catch", 0) or - hasHandler(this, "finally", 0) - } -} - -/** - * A promise object created by the standard ECMAScript 2015 `Promise` constructor. - */ -private class ES2015PromiseDefinition extends PromiseDefinition, DataFlow::NewNode { - ES2015PromiseDefinition() { this = DataFlow::globalVarRef("Promise").getAnInstantiation() } - - override DataFlow::FunctionNode getExecutor() { result = getCallback(0) } -} - -/** - * A promise that is created and resolved with one or more value. - */ -abstract class PromiseCreationCall extends DataFlow::CallNode { - /** - * Gets the value this promise is resolved with. - */ - abstract DataFlow::Node getValue(); -} - -/** - * A promise that is created using a `.resolve()` call. - */ -abstract class ResolvedPromiseDefinition 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") - } - - override DataFlow::Node getValue() { result = getArgument(0) } -} - -/** - * An aggregated promise produced either by `Promise.all` or `Promise.race`. - */ -class AggregateES2015PromiseDefinition extends PromiseCreationCall { - AggregateES2015PromiseDefinition() { - exists(string m | m = "all" or m = "race" | - this = DataFlow::globalVarRef("Promise").getAMemberCall(m) - ) - } - - override DataFlow::Node getValue() { - result = getArgument(0).getALocalSource().(DataFlow::ArrayCreationNode).getAnElement() - } -} - -/** - * A data flow edge from a promise reaction to the corresponding handler. - */ -private class PromiseFlowStep extends DataFlow::AdditionalFlowStep { - PromiseDefinition p; - - PromiseFlowStep() { this = p } - - override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - pred = p.getResolveParameter().getACall().getArgument(0) and - succ = p.getAResolveHandler().getParameter(0) - or - pred = p.getRejectParameter().getACall().getArgument(0) and - succ = p.getARejectHandler().getParameter(0) - } -} - -/** - * A data flow edge from the exceptional return of the promise executor to the promise catch handler. - * This only adds an edge from the exceptional return of the promise executor to a `.catch()` handler. - */ -private class PromiseExceptionalStep extends DataFlow::AdditionalFlowStep { - PromiseDefinition promise; - PromiseExceptionalStep() { - promise = this - } - - override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - pred = promise.getExecutor().getExceptionalReturn() and - succ = promise.getACatchHandler().getParameter(0) - } -} - -/** - * Holds if taint propagates from `pred` to `succ` through promises. - */ -predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) { - // from `x` to `new Promise((res, rej) => res(x))` - pred = succ.(PromiseDefinition).getResolveParameter().getACall().getArgument(0) - or - // from `x` to `Promise.resolve(x)` - pred = succ.(PromiseCreationCall).getValue() - or - exists(DataFlow::MethodCallNode thn, DataFlow::FunctionNode cb | - thn.getMethodName() = "then" and cb = thn.getCallback(0) - | - // from `p` to `x` in `p.then(x => ...)` - pred = thn.getReceiver() and - succ = cb.getParameter(0) - or - // from `v` to `p.then(x => return v)` - pred = cb.getAReturn() and - succ = thn - ) -} - -/** - * An additional taint step that involves promises. - */ -private class PromiseTaintStep extends TaintTracking::AdditionalTaintStep { - DataFlow::Node source; - - PromiseTaintStep() { promiseTaintStep(source, this) } - - override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - pred = source and succ = this - } -} - /** * A flow step propagating the exception thrown from a callback to a method whose name coincides * a built-in Array iteration method, such as `forEach` or `map`. @@ -298,9 +113,7 @@ class StringReplaceCall extends DataFlow::MethodCallNode { } /** Gets the regular expression passed as the first argument to `replace`, if any. */ - DataFlow::RegExpLiteralNode getRegExp() { - result.flowsTo(getArgument(0)) - } + DataFlow::RegExpLiteralNode getRegExp() { result.flowsTo(getArgument(0)) } /** Gets a string that is being replaced by this call. */ string getAReplacedString() { @@ -312,17 +125,13 @@ class StringReplaceCall extends DataFlow::MethodCallNode { * Gets the second argument of this call to `replace`, which is either a string * or a callback. */ - DataFlow::Node getRawReplacement() { - result = getArgument(1) - } + DataFlow::Node getRawReplacement() { result = getArgument(1) } /** * Holds if this is a global replacement, that is, the first argument is a regular expression * with the `g` flag. */ - predicate isGlobal() { - getRegExp().isGlobal() - } + predicate isGlobal() { getRegExp().isGlobal() } /** * Holds if this call to `replace` replaces `old` with `new`. diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected b/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected index ab45472d898..24e5f967f9f 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected @@ -23,11 +23,11 @@ | partial.js:5:15:5:24 | "tainted1" | partial.js:21:15:21:15 | x | | partial.js:5:15:5:24 | "tainted1" | partial.js:27:15:27:15 | x | | promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val | +| promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v | | promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v | -| promises.js:11:22:11:31 | "resolved" | promises.js:27:16:27:16 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:21:20:21:20 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:24:20:24:20 | v | -| promises.js:12:22:12:31 | "rejected" | promises.js:27:16:27:16 | v | +| promises.js:32:24:32:37 | "also tainted" | promises.js:38:32:38:32 | v | | properties2.js:7:14:7:21 | "source" | properties2.js:8:12:8:24 | foo(source).p | | properties2.js:7:14:7:21 | "source" | properties2.js:33:13:33:20 | getP(o3) | | properties.js:2:16:2:24 | "tainted" | properties.js:5:14:5:23 | a.someProp | diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected b/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected index 67db1a56cd9..5c252b4aeea 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected @@ -30,10 +30,8 @@ | promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val | | promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v | | promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v | -| promises.js:11:22:11:31 | "resolved" | promises.js:27:16:27:16 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:21:20:21:20 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:24:20:24:20 | v | -| promises.js:12:22:12:31 | "rejected" | promises.js:27:16:27:16 | v | | promises.js:32:24:32:37 | "also tainted" | promises.js:38:32:38:32 | v | | properties2.js:7:14:7:21 | "source" | properties2.js:8:12:8:24 | foo(source).p | | properties2.js:7:14:7:21 | "source" | properties2.js:33:13:33:20 | getP(o3) | diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/promises.js b/javascript/ql/test/library-tests/InterProceduralFlow/promises.js index 2f67ea30194..b0055c78781 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/promises.js +++ b/javascript/ql/test/library-tests/InterProceduralFlow/promises.js @@ -23,7 +23,7 @@ promise2.catch((v) => { var rej_sink = v; }); - promise2.finally((v) => { + promise2.finally((v) => { // no promise implementation sends an argument to the finally handler. So there is no data-flow here. var sink = v; }); diff --git a/javascript/ql/test/library-tests/Promises/Flowsteps.qll b/javascript/ql/test/library-tests/Promises/Flowsteps.qll new file mode 100644 index 00000000000..c18f87946ad --- /dev/null +++ b/javascript/ql/test/library-tests/Promises/Flowsteps.qll @@ -0,0 +1,5 @@ +import javascript + +query predicate flowSteps(DataFlow::Node pred, DataFlow::Node succ) { + any(DataFlow::AdditionalFlowStep step).step(pred, succ) +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/flowsteps.js b/javascript/ql/test/library-tests/Promises/flowsteps.js new file mode 100644 index 00000000000..284008553c4 --- /dev/null +++ b/javascript/ql/test/library-tests/Promises/flowsteps.js @@ -0,0 +1,56 @@ +(async function () { + function throws(resolve, reject) { + throw new Error() + } + new Promise(throws) + .catch((e) => console.log(e)); + + new Promise(throws) + .then((val) => console.log(val), (error) => console.log(error)); + + try { + await new Promise(throws); + } catch (e2) { + console.log(e2); + } + + new Promise((resolve, reject) => reject(3)) + .catch((e3) => console.log(e3)); + + try { + await new Promise((resolve, reject) => reject(4)); + } catch(e4) { + console.log(e4); + } + + new Promise(throws) + .then(() => {}) + .catch((e5) => console.log(e5)); + + + new Promise(throws) + .then(() => {}) + .catch((e6) => console.log(e6)) + .catch((e7) => console.log(e7)); + + var foo = await new Promise((resolve, reject) => resolve(8)) + + var bar = await new Promise((resolve, reject) => resolve(9)).then((x) => x + 2); + + var p = Promise.resolve(3); + var baz = await p.then((val) => val * 2); + + var p2 = new Promise((resolve, reject) => { + if (Math.random() > 0.5) { + resolve(13); + } else { + reject(14); + } + }); + var quz = await p2.then(val => val * 4).catch(e => e * 3); + + function returnsPromise() { + return Promise.resolve(3); + } + var a = await returnsPromise(); +})(); \ 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 144b9e19036..a7351c85a13 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -1,12 +1,27 @@ test_ResolvedPromiseDefinition +| flowsteps.js:40:11:40:28 | Promise.resolve(3) | flowsteps.js:40:27:40:27 | 3 | +| flowsteps.js:53:12:53:29 | Promise.resolve(3) | flowsteps.js:53:28:53:28 | 3 | | promises.js:53:19:53:41 | Promise ... source) | promises.js:53:35:53:40 | source | | 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 | test_PromiseDefinition_getARejectHandler +| flowsteps.js:5:3:5:21 | new Promise(throws) | flowsteps.js:6:12:6:32 | (e) => ... .log(e) | +| flowsteps.js:8:3:8:21 | new Promise(throws) | flowsteps.js:9:38:9:66 | (error) ... (error) | +| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | flowsteps.js:18:12:18:34 | (e3) => ... log(e3) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:20:6:22:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:23:18:25:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | test_PromiseDefinition_getExecutor +| flowsteps.js:5:3:5:21 | new Promise(throws) | flowsteps.js:2:3:4:3 | functio ... r()\\n } | +| flowsteps.js:8:3:8:21 | new Promise(throws) | flowsteps.js:2:3:4:3 | functio ... r()\\n } | +| flowsteps.js:12:11:12:29 | new Promise(throws) | flowsteps.js:2:3:4:3 | functio ... r()\\n } | +| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | flowsteps.js:17:15:17:44 | (resolv ... ject(3) | +| flowsteps.js:21:11:21:53 | new Pro ... ect(4)) | flowsteps.js:21:23:21:52 | (resolv ... ject(4) | +| flowsteps.js:26:3:26:21 | new Promise(throws) | flowsteps.js:2:3:4:3 | functio ... r()\\n } | +| flowsteps.js:31:3:31:21 | new Promise(throws) | flowsteps.js:2:3:4:3 | functio ... r()\\n } | +| flowsteps.js:36:19:36:62 | new Pro ... lve(8)) | flowsteps.js:36:31:36:61 | (resolv ... olve(8) | +| flowsteps.js:38:19:38:62 | new Pro ... lve(9)) | flowsteps.js:38:31:38:61 | (resolv ... olve(9) | +| flowsteps.js:43:12:49:4 | new Pro ... }\\n }) | flowsteps.js:43:24:49:3 | (resolv ... }\\n } | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:29:5:3 | functio ... e);\\n } | | 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 } | @@ -14,25 +29,103 @@ test_PromiseDefinition_getExecutor test_PromiseDefinition_getAFinallyHandler | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | test_PromiseDefinition +| flowsteps.js:5:3:5:21 | new Promise(throws) | +| flowsteps.js:8:3:8:21 | new Promise(throws) | +| flowsteps.js:12:11:12:29 | new Promise(throws) | +| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | +| flowsteps.js:21:11:21:53 | new Pro ... ect(4)) | +| flowsteps.js:26:3:26:21 | new Promise(throws) | +| flowsteps.js:31:3:31:21 | new Promise(throws) | +| flowsteps.js:36:19:36:62 | new Pro ... lve(8)) | +| flowsteps.js:38:19:38:62 | new Pro ... lve(9)) | +| flowsteps.js:43:12:49:4 | new Pro ... }\\n }) | | promises.js:3:17:5:4 | new Pro ... );\\n }) | | 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 }) | test_PromiseDefinition_getAResolveHandler +| flowsteps.js:8:3:8:21 | new Promise(throws) | flowsteps.js:9:11:9:35 | (val) = ... og(val) | +| flowsteps.js:26:3:26:21 | new Promise(throws) | flowsteps.js:27:11:27:18 | () => {} | +| flowsteps.js:31:3:31:21 | new Promise(throws) | flowsteps.js:32:11:32:18 | () => {} | +| flowsteps.js:38:19:38:62 | new Pro ... lve(9)) | flowsteps.js:38:69:38:80 | (x) => x + 2 | +| flowsteps.js:43:12:49:4 | new Pro ... }\\n }) | flowsteps.js:50:27:50:40 | val => val * 4 | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:6:16:8:3 | functio ... al;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:18:17:20:3 | (v) => ... v;\\n } | | 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 } | test_PromiseDefinition_getRejectParameter +| flowsteps.js:5:3:5:21 | new Promise(throws) | flowsteps.js:2:28:2:33 | reject | +| flowsteps.js:8:3:8:21 | new Promise(throws) | flowsteps.js:2:28:2:33 | reject | +| flowsteps.js:12:11:12:29 | new Promise(throws) | flowsteps.js:2:28:2:33 | reject | +| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | flowsteps.js:17:25:17:30 | reject | +| flowsteps.js:21:11:21:53 | new Pro ... ect(4)) | flowsteps.js:21:33:21:38 | reject | +| flowsteps.js:26:3:26:21 | new Promise(throws) | flowsteps.js:2:28:2:33 | reject | +| flowsteps.js:31:3:31:21 | new Promise(throws) | flowsteps.js:2:28:2:33 | reject | +| flowsteps.js:36:19:36:62 | new Pro ... lve(8)) | flowsteps.js:36:41:36:46 | reject | +| flowsteps.js:38:19:38:62 | new Pro ... lve(9)) | flowsteps.js:38:41:38:46 | reject | +| flowsteps.js:43:12:49:4 | new Pro ... }\\n }) | flowsteps.js:43:34:43:39 | reject | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:48:3:53 | reject | | 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 | test_PromiseDefinition_getResolveParameter +| flowsteps.js:5:3:5:21 | new Promise(throws) | flowsteps.js:2:19:2:25 | resolve | +| flowsteps.js:8:3:8:21 | new Promise(throws) | flowsteps.js:2:19:2:25 | resolve | +| flowsteps.js:12:11:12:29 | new Promise(throws) | flowsteps.js:2:19:2:25 | resolve | +| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | flowsteps.js:17:16:17:22 | resolve | +| flowsteps.js:21:11:21:53 | new Pro ... ect(4)) | flowsteps.js:21:24:21:30 | resolve | +| flowsteps.js:26:3:26:21 | new Promise(throws) | flowsteps.js:2:19:2:25 | resolve | +| flowsteps.js:31:3:31:21 | new Promise(throws) | flowsteps.js:2:19:2:25 | resolve | +| flowsteps.js:36:19:36:62 | new Pro ... lve(8)) | flowsteps.js:36:32:36:38 | resolve | +| flowsteps.js:38:19:38:62 | new Pro ... lve(9)) | flowsteps.js:38:32:38:38 | resolve | +| flowsteps.js:43:12:49:4 | new Pro ... }\\n }) | flowsteps.js:43:25:43:31 | resolve | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:39:3:45 | resolve | | 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 | test_PromiseDefinition_getACatchHandler +| flowsteps.js:5:3:5:21 | new Promise(throws) | flowsteps.js:6:12:6:32 | (e) => ... .log(e) | +| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | flowsteps.js:18:12:18:34 | (e3) => ... log(e3) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:23:18:25:3 | (v) => ... v;\\n } | +flowSteps +| flowsteps.js:2:3:4:3 | exceptional return of function throws | flowsteps.js:6:13:6:13 | e | +| flowsteps.js:2:3:4:3 | exceptional return of function throws | flowsteps.js:9:39:9:43 | error | +| flowsteps.js:2:3:4:3 | exceptional return of function throws | flowsteps.js:13:12:13:13 | e2 | +| flowsteps.js:2:3:4:3 | exceptional return of function throws | flowsteps.js:28:13:28:14 | e5 | +| flowsteps.js:2:3:4:3 | exceptional return of function throws | flowsteps.js:33:13:33:14 | e6 | +| flowsteps.js:17:15:17:44 | exceptional return of anonymous function | flowsteps.js:18:13:18:14 | e3 | +| flowsteps.js:17:43:17:43 | 3 | flowsteps.js:18:13:18:14 | e3 | +| flowsteps.js:21:23:21:52 | exceptional return of anonymous function | flowsteps.js:22:11:22:12 | e4 | +| flowsteps.js:21:51:21:51 | 4 | flowsteps.js:22:11:22:12 | e4 | +| flowsteps.js:27:11:27:18 | exceptional return of anonymous function | flowsteps.js:28:13:28:14 | e5 | +| flowsteps.js:32:11:32:18 | exceptional return of anonymous function | flowsteps.js:33:13:33:14 | e6 | +| flowsteps.js:33:12:33:34 | exceptional return of anonymous function | flowsteps.js:34:13:34:14 | e7 | +| flowsteps.js:36:31:36:61 | exceptional return of anonymous function | flowsteps.js:1:2:56:1 | exceptional return of anonymous function | +| flowsteps.js:36:60:36:60 | 8 | flowsteps.js:36:13:36:62 | await n ... lve(8)) | +| flowsteps.js:38:31:38:61 | exceptional return of anonymous function | flowsteps.js:1:2:56:1 | exceptional return of anonymous function | +| flowsteps.js:38:60:38:60 | 9 | flowsteps.js:38:70:38:70 | x | +| flowsteps.js:38:69:38:80 | exceptional return of anonymous function | flowsteps.js:1:2:56:1 | exceptional return of anonymous function | +| flowsteps.js:38:76:38:80 | x + 2 | flowsteps.js:38:13:38:81 | await n ... x + 2) | +| flowsteps.js:40:27:40:27 | 3 | flowsteps.js:41:27:41:29 | val | +| flowsteps.js:41:26:41:41 | exceptional return of anonymous function | flowsteps.js:1:2:56:1 | exceptional return of anonymous function | +| flowsteps.js:41:35:41:41 | val * 2 | flowsteps.js:41:13:41:42 | await p ... al * 2) | +| flowsteps.js:43:24:49:3 | exceptional return of anonymous function | flowsteps.js:50:49:50:49 | e | +| flowsteps.js:45:12:45:13 | 13 | flowsteps.js:50:27:50:29 | val | +| flowsteps.js:47:11:47:12 | 14 | flowsteps.js:50:49:50:49 | e | +| flowsteps.js:50:27:50:40 | exceptional return of anonymous function | flowsteps.js:50:49:50:49 | e | +| flowsteps.js:50:34:50:40 | val * 4 | flowsteps.js:50:13:50:59 | await p ... e * 3) | +| flowsteps.js:50:49:50:58 | exceptional return of anonymous function | flowsteps.js:1:2:56:1 | exceptional return of anonymous function | +| flowsteps.js:50:54:50:58 | e * 3 | flowsteps.js:50:13:50:59 | await p ... e * 3) | +| flowsteps.js:53:28:53:28 | 3 | flowsteps.js:55:11:55:32 | await r ... omise() | +| promises.js:4:13:4:18 | source | promises.js:6:26:6:28 | val | +| promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:20:7:20:7 | v | +| promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:23:19:23:19 | v | +| promises.js:14:11:14:20 | res_source | promises.js:18:18:18:18 | v | +| promises.js:16:11:16:20 | rej_source | promises.js:20:7:20:7 | v | +| promises.js:16:11:16:20 | rej_source | promises.js:23:19:23:19 | v | +| promises.js:34:17:34:22 | source | promises.js:36:28:36:30 | val | +| promises.js:44:17:44:22 | source | promises.js:46:28:46:30 | val | +| promises.js:53:35:53:40 | source | promises.js:54:28:54:30 | val | +| promises.js:62:35:62:40 | source | promises.js:63:28:63:30 | val | +| promises.js:71:21:71:26 | source | promises.js:71:34:71:36 | val | diff --git a/javascript/ql/test/library-tests/Promises/tests.ql b/javascript/ql/test/library-tests/Promises/tests.ql index 6b29dc01fa3..b2ff77cd0a1 100644 --- a/javascript/ql/test/library-tests/Promises/tests.ql +++ b/javascript/ql/test/library-tests/Promises/tests.ql @@ -7,3 +7,4 @@ import PromiseDefinition_getAResolveHandler import PromiseDefinition_getRejectParameter import PromiseDefinition_getResolveParameter import PromiseDefinition_getACatchHandler +import Flowsteps \ No newline at end of file