diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index d74aafc265ce..cac97e54e09c 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -1,9 +1,385 @@ /** - * 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 of a Promise. + * The data-flow is based on pseudo-properties rather than tainting the Promise object (which is what `PromiseTaintStep` does). + */ +private module PromiseFlow { + /** + * Gets the pseudo-field used to describe resolved values in a promise. + */ + string resolveField() { + result = "$PromiseResolveField$" + } + + /** + * Gets the pseudo-field used to describe rejected values in a promise. + */ + string rejectField() { + result = "$PromiseRejectField$" + } + + /** + * A flow step describing a promise definition. + * + * The resolved/rejected value is written to a pseudo-field on the promise. + */ + class PromiseDefitionStep extends DataFlow::AdditionalFlowStep { + PromiseDefinition promise; + PromiseDefitionStep() { + this = promise + } + + override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = promise.getResolveParameter().getACall().getArgument(0) and + succ = this + or + prop = rejectField() and + ( + pred = promise.getRejectParameter().getACall().getArgument(0) or + pred = promise.getExecutor().getExceptionalReturn() + ) and + succ = this + } + + override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + // Copy the value of a resolved promise to the value of this promise. + prop = resolveField() and + pred = promise.getResolveParameter().getACall().getArgument(0) and + succ = this + } + } + + /** + * A flow step describing the a Promise.resolve (and similar) call. + */ + class CreationStep extends DataFlow::AdditionalFlowStep { + PromiseCreationCall promise; + CreationStep() { + this = promise + } + + override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = promise.getValue() and + succ = this + } + + override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + // Copy the value of a resolved promise to the value of this promise. + prop = resolveField() and + pred = promise.getValue() and + succ = this + } + } + + + /** + * A load step loading the pseudo-field describing that the promise is rejected. + * The rejected value is thrown as a exception. + */ + class AwaitStep extends DataFlow::AdditionalFlowStep { + DataFlow::Node operand; + AwaitExpr await; + AwaitStep() { + this.getEnclosingExpr() = await and + operand.getEnclosingExpr() = await.getOperand() + } + + override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + succ = this and + pred = operand + or + prop = rejectField() and + succ = await.getExceptionTarget() and + pred = operand + } + } + + /** + * A flow step describing the data-flow related to the `.then` method of a promise. + */ + class ThenStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode { + ThenStep() { + this.getMethodName() = "then" + } + + override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = getReceiver() and + succ = getCallback(0).getParameter(0) + or + prop = rejectField() and + pred = getReceiver() and + succ = getCallback(1).getParameter(0) + } + + override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + not exists(this.getArgument(1)) and + prop = rejectField() and + pred = getReceiver() and + succ = this + or + // read the value of a resolved/rejected promise that is returned + (prop = rejectField() or prop = resolveField()) and + pred = getCallback([0..1]).getAReturn() and + succ = this + } + + override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = getCallback([0..1]).getAReturn() and + succ = this + or + prop = rejectField() and + pred = getCallback([0..1]).getExceptionalReturn() and + succ = this + } + } + + /** + * A flow step describing the data-flow related to the `.catch` method of a promise. + */ + class CatchStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode { + CatchStep() { + this.getMethodName() = "catch" + } + + override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = rejectField() and + pred = getReceiver() and + succ = getCallback(0).getParameter(0) + } + + override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = getReceiver().getALocalSource() and + succ = this + or + // read the value of a resolved/rejected promise that is returned + (prop = rejectField() or prop = resolveField()) and + pred = getCallback(0).getAReturn() and + succ = this + } + + override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = rejectField() and + pred = getCallback(0).getExceptionalReturn() and + succ = this + or + prop = resolveField() and + pred = getCallback(0).getAReturn() and + succ = this + } + } + + /** + * A flow step describing the data-flow related to the `.finally` method of a promise. + */ + class FinallyStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode { + FinallyStep() { + this.getMethodName() = "finally" + } + + override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + (prop = resolveField() or prop = rejectField()) and + pred = getReceiver() and + succ = this + or + // read the value of a rejected promise that is returned + prop = rejectField() and + pred = getCallback(0).getAReturn() and + succ = this + } + + override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = rejectField() and + pred = getCallback(0).getExceptionalReturn() and + succ = this + } + } +} + +/** + * 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 | + thn.getMethodName() = "then" + | + // from `p` to `x` in `p.then(x => ...)` + pred = thn.getReceiver() and + succ = thn.getCallback(0).getParameter(0) + or + // from `v` to `p.then(x => return v)` + pred = thn.getCallback([0..1]).getAReturn() and + succ = thn + ) + or + exists(DataFlow::MethodCallNode catch | catch.getMethodName() = "catch" | + // from `p` to `p.catch(..)` + pred = catch.getReceiver() and + succ = catch + or + // from `v` to `p.catch(x => return v)` + pred = catch.getCallback(0).getAReturn() and + succ = catch + ) + or + // from `p` to `p.finally(..)` + exists(DataFlow::MethodCallNode finally | finally.getMethodName() = "finally" | + pred = finally.getReceiver() and + succ = finally + ) + or + // from `x` to `await x` + exists(AwaitExpr await | + pred.getEnclosingExpr() = await.getOperand() and + succ.getEnclosingExpr() = await + ) +} + +/** + * 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 03b15b012e09..0de4cce8e050 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/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index 577e73829211..f281a8aa23eb 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -223,6 +223,29 @@ abstract class Configuration extends string { predicate hasFlowPath(SourcePathNode source, SinkPathNode sink) { flowsTo(source, _, sink, _, this) } + + /** + * EXPERIMENTAL. This API may change in the future. + * + * Holds if `pred` should be stored in the object `succ` under the property `prop`. + */ + predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + + /** + * EXPERIMENTAL. This API may change in the future. + * + * Holds if the property `prop` of the object `pred` should be loaded into `succ`. + */ + predicate isAdditionalLoadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + + /** + * EXPERIMENTAL. This API may change in the future. + * + * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. + */ + predicate isAdditionalLoadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + none() + } } /** @@ -307,11 +330,8 @@ abstract class BarrierGuardNode extends DataFlow::Node { // 1) `nd` is a use of a refinement node that blocks its input variable exists(SsaRefinementNode ref, boolean outcome | nd = DataFlow::ssaDefinitionNode(ref) and - forex(SsaVariable input | input = ref.getAnInput() | - getEnclosingExpr() = ref.getGuard().getTest() and - outcome = ref.getGuard().(ConditionGuardNode).getOutcome() and - barrierGuardBlocksExpr(this, outcome, input.getAUse(), label) - ) + outcome = ref.getGuard().(ConditionGuardNode).getOutcome() and + ssaRefinementBlocks(outcome, ref, label) ) or // 2) `nd` is an instance of an access path `p`, and dominated by a barrier for `p` @@ -324,6 +344,18 @@ abstract class BarrierGuardNode extends DataFlow::Node { ) } + /** + * Holds if there exists an input variable of `ref` that blocks the label `label`. + * + * This predicate is outlined to give the optimizer a hint about the join ordering. + */ + private predicate ssaRefinementBlocks(boolean outcome, SsaRefinementNode ref, string label) { + getEnclosingExpr() = ref.getGuard().getTest() and + forex(SsaVariable input | input = ref.getAnInput() | + barrierGuardBlocksExpr(this, outcome, input.getAUse(), label) + ) + } + /** * Holds if this node blocks expression `e` provided it evaluates to `outcome`. * @@ -338,11 +370,13 @@ abstract class BarrierGuardNode extends DataFlow::Node { } /** - * Holds if data flow node `nd` acts as a barrier for data flow. - * - * `label` is bound to the blocked label, or the empty string if all labels should be blocked. - */ -private predicate barrierGuardBlocksExpr(BarrierGuardNode guard, boolean outcome, Expr test, string label) { + * Holds if data flow node `nd` acts as a barrier for data flow. + * + * `label` is bound to the blocked label, or the empty string if all labels should be blocked. + */ +private predicate barrierGuardBlocksExpr( + BarrierGuardNode guard, boolean outcome, Expr test, string label +) { guard.blocks(outcome, test) and label = "" or guard.blocks(outcome, test, label) @@ -353,23 +387,29 @@ private predicate barrierGuardBlocksExpr(BarrierGuardNode guard, boolean outcome } /** - * Holds if data flow node `nd` acts as a barrier for data flow due to aliasing through - * an access path. - * - * `label` is bound to the blocked label, or the empty string if all labels should be blocked. - */ + * Holds if data flow node `nd` acts as a barrier for data flow due to aliasing through + * an access path. + * + * `label` is bound to the blocked label, or the empty string if all labels should be blocked. + */ pragma[noinline] -private predicate barrierGuardBlocksAccessPath(BarrierGuardNode guard, boolean outcome, AccessPath ap, string label) { +private predicate barrierGuardBlocksAccessPath( + BarrierGuardNode guard, boolean outcome, AccessPath ap, string label +) { barrierGuardBlocksExpr(guard, outcome, ap.getAnInstance(), label) } /** - * Holds if `guard` should block flow along the edge `pred -> succ`. - * - * `label` is bound to the blocked label, or the empty string if all labels should be blocked. - */ -private predicate barrierGuardBlocksEdge(BarrierGuardNode guard, DataFlow::Node pred, DataFlow::Node succ, string label) { - exists(SsaVariable input, SsaPhiNode phi, BasicBlock bb, ConditionGuardNode cond, boolean outcome | + * Holds if `guard` should block flow along the edge `pred -> succ`. + * + * `label` is bound to the blocked label, or the empty string if all labels should be blocked. + */ +private predicate barrierGuardBlocksEdge( + BarrierGuardNode guard, DataFlow::Node pred, DataFlow::Node succ, string label +) { + exists( + SsaVariable input, SsaPhiNode phi, BasicBlock bb, ConditionGuardNode cond, boolean outcome + | pred = DataFlow::ssaDefinitionNode(input) and succ = DataFlow::ssaDefinitionNode(phi) and input = phi.getInputFromBlock(bb) and @@ -399,7 +439,9 @@ private predicate isBarrierEdge(Configuration cfg, DataFlow::Node pred, DataFlow * Holds if there is a labeled barrier edge `pred -> succ` in `cfg` either through an explicit barrier edge * or one implied by a barrier guard. */ -private predicate isLabeledBarrierEdge(Configuration cfg, DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel label) { +private predicate isLabeledBarrierEdge( + Configuration cfg, DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel label +) { cfg.isBarrierEdge(pred, succ, label) or exists(DataFlow::BarrierGuardNode guard | @@ -449,6 +491,30 @@ abstract class AdditionalFlowStep extends DataFlow::Node { ) { none() } + + /** + * EXPERIMENTAL. This API may change in the future. + * + * Holds if `pred` should be stored in the object `succ` under the property `prop`. + */ + cached + predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + + /** + * EXPERIMENTAL. This API may change in the future. + * + * Holds if the property `prop` of the object `pred` should be loaded into `succ`. + */ + cached + predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + + /** + * EXPERIMENTAL. This API may change in the future. + * + * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. + */ + cached + predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } } /** @@ -551,6 +617,9 @@ private predicate exploratoryFlowStep( basicFlowStep(pred, succ, _, cfg) or basicStoreStep(pred, succ, _) or basicLoadStep(pred, succ, _) or + isAdditionalStoreStep(pred, succ, _, cfg) or + isAdditionalLoadStep(pred, succ, _, cfg) or + isAdditionalLoadStoreStep(pred, succ, _, cfg) or // the following two disjuncts taken together over-approximate flow through // higher-order calls callback(pred, succ) or @@ -712,6 +781,9 @@ private predicate storeStep( basicStoreStep(pred, succ, prop) and summary = PathSummary::level() or + isAdditionalStoreStep(pred, succ, prop, cfg) and + summary = PathSummary::level() + or exists(Function f, DataFlow::Node mid | // `f` stores its parameter `pred` in property `prop` of a value that flows back to the caller, // and `succ` is an invocation of `f` @@ -719,6 +791,10 @@ private predicate storeStep( ( returnedPropWrite(f, _, prop, mid) or + exists(DataFlow::SourceNode base | base.flowsToExpr(f.getAReturnedExpr()) | + isAdditionalStoreStep(mid, base, prop, cfg) + ) + or succ instanceof DataFlow::NewNode and receiverPropWrite(f, prop, mid) ) @@ -729,12 +805,16 @@ private predicate storeStep( * Holds if `f` may `read` property `prop` of parameter `parm`. */ private predicate parameterPropRead( - Function f, DataFlow::Node invk, DataFlow::Node arg, string prop, DataFlow::PropRead read, + Function f, DataFlow::Node invk, DataFlow::Node arg, string prop, DataFlow::Node read, DataFlow::Configuration cfg ) { exists(DataFlow::SourceNode parm | callInputStep(f, invk, arg, parm, cfg) and - read = parm.getAPropertyRead(prop) + ( + read = parm.getAPropertyRead(prop) + or + exists(DataFlow::Node use | parm.flowsTo(use) | isAdditionalLoadStep(use, read, prop, cfg)) + ) ) } @@ -756,6 +836,39 @@ private predicate reachesReturn( ) } +/** + * Holds if the property `prop` of the object `pred` should be loaded into `succ`. + */ +private predicate isAdditionalLoadStep( + DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg +) { + any(AdditionalFlowStep s).loadStep(pred, succ, prop) + or + cfg.isAdditionalLoadStep(pred, succ, prop) +} + +/** + * Holds if `pred` should be stored in the object `succ` under the property `prop`. + */ +private predicate isAdditionalStoreStep( + DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg +) { + any(AdditionalFlowStep s).storeStep(pred, succ, prop) + or + cfg.isAdditionalStoreStep(pred, succ, prop) +} + +/** + * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. + */ +private predicate isAdditionalLoadStoreStep( + DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg +) { + any(AdditionalFlowStep s).loadStoreStep(pred, succ, prop) + or + cfg.isAdditionalLoadStoreStep(pred, succ, prop) +} + /** * Holds if property `prop` of `pred` may flow into `succ` along a path summarized by * `summary`. @@ -767,7 +880,10 @@ private predicate loadStep( basicLoadStep(pred, succ, prop) and summary = PathSummary::level() or - exists(Function f, DataFlow::PropRead read | + isAdditionalLoadStep(pred, succ, prop, cfg) and + summary = PathSummary::level() + or + exists(Function f, DataFlow::Node read | parameterPropRead(f, succ, pred, prop, read, cfg) and reachesReturn(f, read, cfg, summary) ) @@ -788,7 +904,12 @@ private predicate reachableFromStoreBase( or exists(DataFlow::Node mid, PathSummary oldSummary, PathSummary newSummary | reachableFromStoreBase(prop, rhs, mid, cfg, oldSummary) and - flowStep(mid, cfg, nd, newSummary) and + ( + flowStep(mid, cfg, nd, newSummary) + or + isAdditionalLoadStoreStep(mid, nd, prop, cfg) and + newSummary = PathSummary::level() + ) and summary = oldSummary.appendValuePreserving(newSummary) ) } @@ -996,19 +1117,19 @@ private predicate onPath(DataFlow::Node nd, DataFlow::Configuration cfg, PathSum * Holds if there is a configuration that has at least one source and at least one sink. */ pragma[noinline] -private predicate isLive() { exists(DataFlow::Configuration cfg | isSource(_, cfg, _) and isSink(_, cfg, _)) } +private predicate isLive() { + exists(DataFlow::Configuration cfg | isSource(_, cfg, _) and isSink(_, cfg, _)) +} /** * A data flow node on an inter-procedural path from a source. */ private newtype TPathNode = - MkSourceNode(DataFlow::Node nd, DataFlow::Configuration cfg) { isSourceNode(nd, cfg, _) } - or + MkSourceNode(DataFlow::Node nd, DataFlow::Configuration cfg) { isSourceNode(nd, cfg, _) } or MkMidNode(DataFlow::Node nd, DataFlow::Configuration cfg, PathSummary summary) { isLive() and onPath(nd, cfg, summary) - } - or + } or MkSinkNode(DataFlow::Node nd, DataFlow::Configuration cfg) { isSinkNode(nd, cfg, _) } /** @@ -1069,9 +1190,7 @@ class PathNode extends TPathNode { } /** Holds if this path node wraps data-flow node `nd` and configuration `c`. */ - predicate wraps(DataFlow::Node n, DataFlow::Configuration c) { - nd = n and cfg = c - } + predicate wraps(DataFlow::Node n, DataFlow::Configuration c) { nd = n and cfg = c } /** Gets the underlying configuration of this path node. */ DataFlow::Configuration getConfiguration() { result = cfg } @@ -1080,9 +1199,7 @@ class PathNode extends TPathNode { DataFlow::Node getNode() { result = nd } /** Gets a successor node of this path node. */ - final PathNode getASuccessor() { - result = getASuccessor(this) - } + final PathNode getASuccessor() { result = getASuccessor(this) } /** Gets a textual representation of this path node. */ string toString() { result = nd.toString() } @@ -1126,7 +1243,10 @@ private MidPathNode finalMidNode(SinkPathNode snk) { * This helper predicate exists to clarify the intended join order in `getASuccessor` below. */ pragma[noinline] -private predicate midNodeStep(PathNode nd, DataFlow::Node predNd, Configuration cfg, PathSummary summary, DataFlow::Node succNd, PathSummary newSummary) { +private predicate midNodeStep( + PathNode nd, DataFlow::Node predNd, Configuration cfg, PathSummary summary, DataFlow::Node succNd, + PathSummary newSummary +) { nd = MkMidNode(predNd, cfg, summary) and flowStep(predNd, id(cfg), succNd, newSummary) } @@ -1139,7 +1259,10 @@ private PathNode getASuccessor(PathNode nd) { result = initialMidNode(nd) or // mid node to mid node - exists(Configuration cfg, DataFlow::Node predNd, PathSummary summary, DataFlow::Node succNd, PathSummary newSummary | + exists( + Configuration cfg, DataFlow::Node predNd, PathSummary summary, DataFlow::Node succNd, + PathSummary newSummary + | midNodeStep(nd, predNd, cfg, summary, succNd, newSummary) and result = MkMidNode(succNd, id(cfg), summary.append(newSummary)) ) @@ -1210,9 +1333,7 @@ class SinkPathNode extends PathNode, MkSinkNode { */ module PathGraph { /** Holds if `nd` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode nd) { - not nd.(MidPathNode).isHidden() - } + query predicate nodes(PathNode nd) { not nd.(MidPathNode).isHidden() } /** * Gets a node to which data from `nd` may flow in one step, skipping over hidden nodes. @@ -1220,7 +1341,8 @@ module PathGraph { private PathNode succ0(PathNode nd) { result = getASuccessorIfHidden*(nd.getASuccessor()) and // skip hidden nodes - nodes(nd) and nodes(result) + nodes(nd) and + nodes(result) } /** @@ -1260,27 +1382,21 @@ module PathGraph { } } - - /** - * Gets an operand of the given `&&` operator. - * - * We use this to construct the transitive closure over a relation - * that does not include all of `BinaryExpr.getAnOperand`. - */ -private Expr getALogicalAndOperand(LogAndExpr e) { - result = e.getAnOperand() -} + * Gets an operand of the given `&&` operator. + * + * We use this to construct the transitive closure over a relation + * that does not include all of `BinaryExpr.getAnOperand`. + */ +private Expr getALogicalAndOperand(LogAndExpr e) { result = e.getAnOperand() } /** - * Gets an operand of the given `||` operator. - * - * We use this to construct the transitive closure over a relation - * that does not include all of `BinaryExpr.getAnOperand`. - */ -private Expr getALogicalOrOperand(LogOrExpr e) { - result = e.getAnOperand() -} + * Gets an operand of the given `||` operator. + * + * We use this to construct the transitive closure over a relation + * that does not include all of `BinaryExpr.getAnOperand`. + */ +private Expr getALogicalOrOperand(LogOrExpr e) { result = e.getAnOperand() } /** * A `BarrierGuardNode` that controls which data flow @@ -1295,8 +1411,8 @@ abstract class AdditionalBarrierGuardNode extends BarrierGuardNode { } /** - * A function that returns the result of a barrier guard. - */ + * A function that returns the result of a barrier guard. + */ private class BarrierGuardFunction extends Function { DataFlow::ParameterNode sanitizedParameter; BarrierGuardNode guard; @@ -1329,8 +1445,8 @@ private class BarrierGuardFunction extends Function { } /** - * Holds if this function sanitizes argument `e` of call `call`, provided the call evaluates to `outcome`. - */ + * Holds if this function sanitizes argument `e` of call `call`, provided the call evaluates to `outcome`. + */ predicate isBarrierCall(DataFlow::CallNode call, Expr e, boolean outcome, string lbl) { exists(DataFlow::Node arg | arg.asExpr() = e and @@ -1343,22 +1459,20 @@ private class BarrierGuardFunction extends Function { } /** - * Holds if this function applies to the flow in `cfg`. - */ + * Holds if this function applies to the flow in `cfg`. + */ predicate appliesTo(Configuration cfg) { cfg.isBarrierGuard(guard) } } /** - * A call that sanitizes an argument. - */ + * A call that sanitizes an argument. + */ private class AdditionalBarrierGuardCall extends AdditionalBarrierGuardNode, DataFlow::CallNode { BarrierGuardFunction f; AdditionalBarrierGuardCall() { f.isBarrierCall(this, _, _, _) } - override predicate blocks(boolean outcome, Expr e) { - f.isBarrierCall(this, e, outcome, "") - } + override predicate blocks(boolean outcome, Expr e) { f.isBarrierCall(this, e, outcome, "") } predicate internalBlocksLabel(boolean outcome, Expr e, DataFlow::FlowLabel label) { f.isBarrierCall(this, e, outcome, label) diff --git a/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.expected b/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.expected new file mode 100644 index 000000000000..b53738c569a4 --- /dev/null +++ b/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.expected @@ -0,0 +1 @@ +| tst.js:4:15:4:22 | "source" | tst.js:9:7:9:24 | readTaint(tainted) | diff --git a/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql b/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql new file mode 100644 index 000000000000..e4ffc89294d1 --- /dev/null +++ b/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql @@ -0,0 +1,26 @@ +import javascript + +class Configuration extends TaintTracking::Configuration { + Configuration() { this = "PromiseFlowTestingConfig" } + + override predicate isSource(DataFlow::Node source) { + source.getEnclosingExpr().getStringValue() = "source" + } + + override predicate isSink(DataFlow::Node sink) { + any(DataFlow::InvokeNode call | call.getCalleeName() = "sink").getAnArgument() = sink + } + + // When the source code states that "foo" is being read, "bar" is additionally being read. + override predicate isAdditionalLoadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + exists(DataFlow::PropRead read | read = succ | + read.getBase() = pred and + read.getPropertyName() = "foo" + ) and + prop = "bar" + } +} + +from DataFlow::Node pred, DataFlow::Node succ, Configuration cfg +where cfg.hasFlow(pred, succ) +select pred, succ diff --git a/javascript/ql/test/library-tests/CustomLoadStoreSteps/tst.js b/javascript/ql/test/library-tests/CustomLoadStoreSteps/tst.js new file mode 100644 index 000000000000..c3fff1b41872 --- /dev/null +++ b/javascript/ql/test/library-tests/CustomLoadStoreSteps/tst.js @@ -0,0 +1,10 @@ +// When the source code states that "foo" is being read, "bar" is additionally being read. + +(function () { + var source = "source"; + var tainted = { bar: source }; + function readTaint(x) { + return x.foo; + } + sink(readTaint(tainted)); +})(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected b/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected index ab45472d898f..24e5f967f9fb 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/GermanFlow.expected b/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected index fef4bafb3fdc..9c3980797ea7 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected @@ -24,11 +24,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 67db1a56cd95..5c252b4aeea4 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/TrackedNodes.expected b/javascript/ql/test/library-tests/InterProceduralFlow/TrackedNodes.expected index e69de29bb2d1..7c8c2ca4b45d 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/TrackedNodes.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/TrackedNodes.expected @@ -0,0 +1,32 @@ +| missing | promises.js:1:2:1:2 | source | promises.js:6:26:6:28 | val | +| missing | promises.js:1:2:1:2 | source | promises.js:7:16:7:18 | val | +| missing | promises.js:1:2:1:2 | source | promises.js:37:11:37:11 | v | +| missing | promises.js:1:2:1:2 | source | promises.js:38:32:38:32 | v | +| missing | promises.js:2:16:2:24 | "tainted" | promises.js:6:26:6:28 | val | +| missing | promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val | +| missing | promises.js:2:16:2:24 | "tainted" | promises.js:37:11:37:11 | v | +| missing | promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v | +| missing | promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:20:7:20:7 | v | +| missing | promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:21:20:21:20 | v | +| missing | promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:23:19:23:19 | v | +| missing | promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:24:20:24:20 | v | +| missing | promises.js:11:22:11:31 | "resolved" | promises.js:18:18:18:18 | v | +| missing | promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v | +| missing | promises.js:12:22:12:31 | "rejected" | promises.js:20:7:20:7 | v | +| missing | promises.js:12:22:12:31 | "rejected" | promises.js:21:20:21:20 | v | +| missing | promises.js:12:22:12:31 | "rejected" | promises.js:23:19:23:19 | v | +| missing | promises.js:12:22:12:31 | "rejected" | promises.js:24:20:24:20 | v | +| missing | promises.js:13:9:13:21 | exceptional return of Math.random() | promises.js:20:7:20:7 | v | +| missing | promises.js:13:9:13:21 | exceptional return of Math.random() | promises.js:21:20:21:20 | v | +| missing | promises.js:13:9:13:21 | exceptional return of Math.random() | promises.js:23:19:23:19 | v | +| missing | promises.js:13:9:13:21 | exceptional return of Math.random() | promises.js:24:20:24:20 | v | +| missing | promises.js:14:7:14:21 | exceptional return of res(res_source) | promises.js:20:7:20:7 | v | +| missing | promises.js:14:7:14:21 | exceptional return of res(res_source) | promises.js:21:20:21:20 | v | +| missing | promises.js:14:7:14:21 | exceptional return of res(res_source) | promises.js:23:19:23:19 | v | +| missing | promises.js:14:7:14:21 | exceptional return of res(res_source) | promises.js:24:20:24:20 | v | +| missing | promises.js:16:7:16:21 | exceptional return of rej(rej_source) | promises.js:20:7:20:7 | v | +| missing | promises.js:16:7:16:21 | exceptional return of rej(rej_source) | promises.js:21:20:21:20 | v | +| missing | promises.js:16:7:16:21 | exceptional return of rej(rej_source) | promises.js:23:19:23:19 | v | +| missing | promises.js:16:7:16:21 | exceptional return of rej(rej_source) | promises.js:24:20:24:20 | v | +| missing | promises.js:32:24:32:37 | "also tainted" | promises.js:37:11:37:11 | v | +| missing | promises.js:32:24:32:37 | "also tainted" | promises.js:38:32:38:32 | v | diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/promises.js b/javascript/ql/test/library-tests/InterProceduralFlow/promises.js index 2f67ea30194e..b0055c787816 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/AdditionalPromises.expected b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected index a3fa5795b303..1c1f14deabec 100644 --- a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected +++ b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected @@ -1,4 +1,74 @@ | additional-promises.js:2:13:2:57 | new Pin ... ct) {}) | +| flow.js:7:11:7:59 | new Pro ... ource)) | +| flow.js:10:11:10:58 | new Pro ... ource)) | +| flow.js:13:11:13:58 | new Pro ... ource)) | +| flow.js:20:2:20:24 | Promise ... source) | +| flow.js:22:2:22:24 | Promise ... source) | +| flow.js:24:2:24:49 | new Pro ... ource)) | +| flow.js:26:2:26:49 | new Pro ... ource)) | +| flow.js:28:2:28:23 | Promise ... ("foo") | +| flow.js:28:2:28:41 | Promise ... source) | +| flow.js:30:2:30:24 | Promise ... source) | +| flow.js:30:2:30:41 | Promise ... "foo") | +| flow.js:32:2:32:49 | new Pro ... ource)) | +| flow.js:34:2:34:24 | Promise ... source) | +| flow.js:34:2:34:41 | Promise ... => { }) | +| flow.js:36:11:36:33 | Promise ... source) | +| flow.js:37:11:37:29 | p5.catch(() => { }) | +| flow.js:40:2:40:49 | new Pro ... ource)) | +| flow.js:40:2:40:65 | new Pro ... => { }) | +| flow.js:42:2:42:49 | new Pro ... ource)) | +| flow.js:42:2:42:76 | new Pro ... => { }) | +| flow.js:44:2:44:24 | Promise ... source) | +| flow.js:44:2:44:41 | Promise ... => { }) | +| flow.js:44:2:44:58 | Promise ... => { }) | +| flow.js:44:2:44:75 | Promise ... => { }) | +| flow.js:46:2:46:24 | Promise ... source) | +| flow.js:46:2:46:43 | Promise ... => { }) | +| flow.js:48:2:48:36 | new Pro ... urce }) | +| flow.js:53:2:53:22 | createP ... source) | +| flow.js:55:11:55:58 | new Pro ... ource)) | +| flow.js:56:11:56:27 | p8.then(() => {}) | +| flow.js:57:12:57:31 | p9.finally(() => {}) | +| flow.js:60:12:60:59 | new Pro ... ource)) | +| flow.js:61:12:61:29 | p11.then(() => {}) | +| flow.js:65:9:65:56 | new Pro ... ource)) | +| flow.js:74:10:74:57 | new Pro ... ource)) | +| flow.js:76:2:76:17 | chainedPromise() | +| flow.js:76:2:76:32 | chained ... => {}) | +| flow.js:86:23:86:70 | new Pro ... ource)) | +| flow.js:89:3:89:27 | ("foo", ... => {}) | +| flow.js:91:21:91:68 | new Pro ... ource)) | +| flow.js:100:28:100:75 | new Pro ... ource)) | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | +| flow.js:103:2:103:76 | new Pro ... ource}) | +| flow.js:105:2:105:48 | new Pro ... "BLA")) | +| flow.js:105:2:105:77 | new Pro ... ource}) | +| flow.js:107:17:107:64 | new Pro ... ource)) | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | +| flow.js:109:2:109:71 | new Pro ... jected) | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | +| flow.js:111:2:111:69 | new Pro ... jected) | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | +| flow.js:113:2:113:69 | new Pro ... jected) | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | +| flow.js:117:2:117:69 | new Pro ... solved) | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | +| flow.js:119:2:119:69 | new Pro ... solved) | +| flow.js:121:2:121:21 | Promise.resolve(123) | +| flow.js:121:2:121:41 | Promise ... solved) | +| flow.js:123:2:123:21 | Promise.resolve(123) | +| flow.js:123:2:123:41 | Promise ... solved) | +| flow.js:125:2:125:21 | Promise.resolve(123) | +| flow.js:125:2:125:41 | Promise ... jected) | +| flow.js:127:2:127:21 | Promise.resolve(123) | +| flow.js:127:2:127:41 | Promise ... jected) | +| flow.js:129:2:129:52 | new Pro ... olved)) | +| flow.js:131:2:131:26 | Promise ... solved) | +| interflow.js:6:3:6:25 | loadScr ... urce()) | +| interflow.js:6:3:7:26 | loadScr ... () { }) | +| interflow.js:6:3:8:26 | loadScr ... () { }) | +| interflow.js:11:12:15:6 | 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 }) | diff --git a/javascript/ql/test/library-tests/Promises/flow.js b/javascript/ql/test/library-tests/Promises/flow.js new file mode 100644 index 000000000000..d92ef541b854 --- /dev/null +++ b/javascript/ql/test/library-tests/Promises/flow.js @@ -0,0 +1,132 @@ +(async function () { + var source = "source"; + + var p1 = Promise.resolve(source); + sink(await p1); // NOT OK + + var p2 = new Promise((resolve, reject) => resolve(source)); + sink(await p2); // NOT OK + + var p3 = new Promise((resolve, reject) => reject(source)); + sink(await p3); // OK! + + var p4 = new Promise((resolve, reject) => reject(source)); + try { + var foo = await p4; + } catch (e) { + sink(e); // NOT OK! + } + + Promise.resolve(source).then(x => sink(x)); // NOT OK! + + Promise.resolve(source).then(x => foo(x), y => sink(y)); // OK! + + new Promise((resolve, reject) => reject(source)).then(x => sink(x)); // OK! + + new Promise((resolve, reject) => reject(source)).then(x => foo(x), y => sink(y)); // NOT OK! + + Promise.resolve("foo").then(x => source).then(z => sink(z)); // NOT OK! + + Promise.resolve(source).then(x => "foo").then(z => sink(z)); // OK! + + new Promise((resolve, reject) => reject(source)).catch(x => sink(x)); // NOT OK! + + Promise.resolve(source).catch(() => { }).then(a => sink(a)); // NOT OK! + + var p5 = Promise.resolve(source); + var p6 = p5.catch(() => { }); + var p7 = p6.then(a => sink(a)); // NOT OK! + + new Promise((resolve, reject) => reject(source)).then(() => { }).catch(x => sink(x)); // NOT OK! + + new Promise((resolve, reject) => reject(source)).then(() => { }, () => { }).catch(x => sink(x)); // OK! + + Promise.resolve(source).catch(() => { }).catch(() => { }).catch(() => { }).then(a => sink(a)); // NOT OK! + + Promise.resolve(source).finally(() => { }).then(a => sink(a)); // NOT OK! + + new Promise(() => { throw source }).catch(x => sink(x)); // NOT OK! + + function createPromise(src) { + return Promise.resolve(src); + } + createPromise(source).then(v => sink(v)); // NOT OK! + + var p8 = new Promise((resolve, reject) => reject(source)); + var p9 = p8.then(() => {}); + var p10 = p9.finally(() => {}); + p10.catch((x) => sink(x)); // NOT OK! + + var p11 = new Promise((resolve, reject) => reject(source)); + var p12 = p11.then(() => {}); + p12.catch(x => sink(x)); // NOT OK! + + async function throws() { + await new Promise((resolve, reject) => reject(source)); + } + try { + throws(); + } catch(e) { + sink(e); // NOT OK! + } + + function chainedPromise() { + return new Promise((resolve, reject) => reject(source)).then(() => {}); + } + chainedPromise().then(() => {}).catch(e => sink(e)); // NOT OK! + + function leaksResolvedPromise(p) { + p.then(x => sink(x)); // NOT OK! + } + leaksResolvedPromise(Promise.resolve(source)); + + function leaksRejectedPromise(p) { + p.catch(e => sink(e)); // NOT OK! + } + leaksRejectedPromise(new Promise((resolve, reject) => reject(source))); + + function leaksRejectedAgain(p) { + ("foo", p).then(() => {}).catch(e => sink(e)); // NOT OK! + } + leaksRejectedAgain(new Promise((resolve, reject) => reject(source)).then(() => {})); + + async function returnsRejected(p) { + try { + await p; + } catch(e) { + return e; + } + } + var foo = returnsRejected(new Promise((resolve, reject) => reject(source))); + sink(foo); // NOT OK! + + new Promise((resolve, reject) => reject("BLA")).catch(x => {return source}).then(x => sink(x)); // NOT OK + + new Promise((resolve, reject) => reject("BLA")).finally(x => {throw source}).catch(x => sink(x)); // NOT OK + + var rejected = new Promise((resolve, reject) => reject(source)); + + new Promise((resolve, reject) => reject("BLA")).finally(x => rejected).catch(x => sink(x)); // NOT OK + + new Promise((resolve, reject) => reject("BLA")).catch(x => rejected).then(x => sink(x)) // OK + + new Promise((resolve, reject) => reject("BLA")).catch(x => rejected).catch(x => sink(x)) // NOT OK + + var resolved = Promise.resolve(source); + + new Promise((resolve, reject) => reject("BLA")).catch(x => resolved).catch(x => sink(x)) // OK + + new Promise((resolve, reject) => reject("BLA")).catch(x => resolved).then(x => sink(x)) // NOT OK + + Promise.resolve(123).then(x => resolved).catch(x => sink(x)) // OK + + Promise.resolve(123).then(x => resolved).then(x => sink(x)) // NOT OK + + Promise.resolve(123).then(x => rejected).catch(x => sink(x)) // NOT OK + + Promise.resolve(123).then(x => rejected).then(x => sink(x)) // OK + + new Promise((resolve, reject) => resolve(resolved)).then(x => sink(x)); // NOT OK + + Promise.resolve(resolved).then(x => sink(x)); // NOT OK +})(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/flow.qll b/javascript/ql/test/library-tests/Promises/flow.qll new file mode 100644 index 000000000000..395d5cb88a8d --- /dev/null +++ b/javascript/ql/test/library-tests/Promises/flow.qll @@ -0,0 +1,34 @@ +import javascript + +class Configuration extends DataFlow::Configuration { + Configuration() { this = "PromiseDataFlowFlowTestingConfig" } + + override predicate isSource(DataFlow::Node source) { + source.getEnclosingExpr().getStringValue() = "source" + } + + override predicate isSink(DataFlow::Node sink) { + any(DataFlow::InvokeNode call | call.getCalleeName() = "sink").getAnArgument() = sink + } +} + +class TaintConfig extends TaintTracking::Configuration { + TaintConfig() { this = "PromiseTaintFlowTestingConfig" } + + override predicate isSource(DataFlow::Node source) { + source.getEnclosingExpr().getStringValue() = "source" + } + + override predicate isSink(DataFlow::Node sink) { + any(DataFlow::InvokeNode call | call.getCalleeName() = "sink").getAnArgument() = sink + } +} + +query predicate flow(DataFlow::Node source, DataFlow::Node sink) { + any(Configuration c).hasFlow(source, sink) +} + +query predicate exclusiveTaintFlow(DataFlow::Node source, DataFlow::Node sink) { + not any(Configuration c).hasFlow(source, sink) and + any(TaintConfig c).hasFlow(source, sink) +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/interflow.js b/javascript/ql/test/library-tests/Promises/interflow.js new file mode 100644 index 000000000000..836b18950afa --- /dev/null +++ b/javascript/ql/test/library-tests/Promises/interflow.js @@ -0,0 +1,20 @@ +(function () { + function getSource() { + var source = "source"; // step 1 + return source; // step 2 + } + loadScript(getSource()) // step 3 + .then(function () { }) + .then(function () { }) + .catch(handleError); + function loadScript(src) { // step 4 (is summarized) + return new Promise(function (resolve, reject) { + setTimeout(function (error) { + reject(new Error('Blah: ' + src)); // step 5 + }, 1000); + }); + } + function handleError(error) { // step 6 + sink(error); // step 7 + } +})(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/options b/javascript/ql/test/library-tests/Promises/options new file mode 100644 index 000000000000..ae107b46f9ea --- /dev/null +++ b/javascript/ql/test/library-tests/Promises/options @@ -0,0 +1 @@ +semmle-extractor-options: --experimental diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index 144b9e190361..f4ee1263be55 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -1,38 +1,222 @@ test_ResolvedPromiseDefinition +| flow.js:4:11:4:33 | Promise ... source) | flow.js:4:27:4:32 | source | +| flow.js:20:2:20:24 | Promise ... source) | flow.js:20:18:20:23 | source | +| flow.js:22:2:22:24 | Promise ... source) | flow.js:22:18:22:23 | source | +| flow.js:28:2:28:23 | Promise ... ("foo") | flow.js:28:18:28:22 | "foo" | +| flow.js:30:2:30:24 | Promise ... source) | flow.js:30:18:30:23 | source | +| flow.js:34:2:34:24 | Promise ... source) | flow.js:34:18:34:23 | source | +| flow.js:36:11:36:33 | Promise ... source) | flow.js:36:27:36:32 | source | +| flow.js:44:2:44:24 | Promise ... source) | flow.js:44:18:44:23 | source | +| flow.js:46:2:46:24 | Promise ... source) | flow.js:46:18:46:23 | source | +| flow.js:51:10:51:29 | Promise.resolve(src) | flow.js:51:26:51:28 | src | +| flow.js:81:23:81:45 | Promise ... source) | flow.js:81:39:81:44 | source | +| flow.js:115:17:115:39 | Promise ... source) | flow.js:115:33:115:38 | source | +| flow.js:121:2:121:21 | Promise.resolve(123) | flow.js:121:18:121:20 | 123 | +| flow.js:123:2:123:21 | Promise.resolve(123) | flow.js:123:18:123:20 | 123 | +| flow.js:125:2:125:21 | Promise.resolve(123) | flow.js:125:18:125:20 | 123 | +| flow.js:127:2:127:21 | Promise.resolve(123) | flow.js:127:18:127:20 | 123 | +| flow.js:131:2:131:26 | Promise ... solved) | flow.js:131:18:131:25 | resolved | | 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 +| 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) | +| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:67:42:75 | () => { } | +| flow.js:48:2:48:36 | new Pro ... urce }) | flow.js:48:44:48:55 | x => sink(x) | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:56:103:75 | x => {return source} | +| 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 | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | flow.js:111:56:111:68 | x => rejected | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | flow.js:113:56:113:68 | x => rejected | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | flow.js:117:56:117:68 | x => resolved | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | flow.js:119:56:119:68 | x => resolved | | 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 +| flow.js:7:11:7:59 | new Pro ... ource)) | flow.js:7:23:7:58 | (resolv ... source) | +| flow.js:10:11:10:58 | new Pro ... ource)) | flow.js:10:23:10:57 | (resolv ... source) | +| flow.js:13:11:13:58 | new Pro ... ource)) | flow.js:13:23:13:57 | (resolv ... source) | +| flow.js:24:2:24:49 | new Pro ... ource)) | flow.js:24:14:24:48 | (resolv ... source) | +| flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:14:26:48 | (resolv ... source) | +| flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:14:32:48 | (resolv ... source) | +| flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:14:40:48 | (resolv ... source) | +| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:14:42:48 | (resolv ... source) | +| flow.js:48:2:48:36 | new Pro ... urce }) | flow.js:48:14:48:35 | () => { ... ource } | +| flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:55:23:55:57 | (resolv ... source) | +| flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:60:24:60:58 | (resolv ... source) | +| flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:21:65:55 | (resolv ... source) | +| flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:22:74:56 | (resolv ... source) | +| flow.js:86:23:86:70 | new Pro ... ource)) | flow.js:86:35:86:69 | (resolv ... source) | +| flow.js:91:21:91:68 | new Pro ... ource)) | flow.js:91:33:91:67 | (resolv ... source) | +| flow.js:100:28:100:75 | new Pro ... ource)) | flow.js:100:40:100:74 | (resolv ... source) | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:14:103:47 | (resolv ... ("BLA") | +| flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:14:105:47 | (resolv ... ("BLA") | +| flow.js:107:17:107:64 | new Pro ... ource)) | flow.js:107:29:107:63 | (resolv ... source) | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:14:109:47 | (resolv ... ("BLA") | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | flow.js:111:14:111:47 | (resolv ... ("BLA") | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | flow.js:113:14:113:47 | (resolv ... ("BLA") | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | flow.js:117:14:117:47 | (resolv ... ("BLA") | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | flow.js:119:14:119:47 | (resolv ... ("BLA") | +| flow.js:129:2:129:52 | new Pro ... olved)) | flow.js:129:14:129:51 | (resolv ... solved) | +| interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:24:15:5 | functio ... ;\\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 } | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:29:45:5 | functio ... ;\\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 | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | test_PromiseDefinition +| flow.js:7:11:7:59 | new Pro ... ource)) | +| flow.js:10:11:10:58 | new Pro ... ource)) | +| flow.js:13:11:13:58 | new Pro ... ource)) | +| flow.js:24:2:24:49 | new Pro ... ource)) | +| flow.js:26:2:26:49 | new Pro ... ource)) | +| flow.js:32:2:32:49 | new Pro ... ource)) | +| flow.js:40:2:40:49 | new Pro ... ource)) | +| flow.js:42:2:42:49 | new Pro ... ource)) | +| flow.js:48:2:48:36 | new Pro ... urce }) | +| flow.js:55:11:55:58 | new Pro ... ource)) | +| flow.js:60:12:60:59 | new Pro ... ource)) | +| flow.js:65:9:65:56 | new Pro ... ource)) | +| flow.js:74:10:74:57 | new Pro ... ource)) | +| flow.js:86:23:86:70 | new Pro ... ource)) | +| flow.js:91:21:91:68 | new Pro ... ource)) | +| flow.js:100:28:100:75 | new Pro ... ource)) | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | +| flow.js:105:2:105:48 | new Pro ... "BLA")) | +| flow.js:107:17:107:64 | new Pro ... ource)) | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | +| flow.js:129:2:129:52 | new Pro ... olved)) | +| interflow.js:11:12:15:6 | 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 +| 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) | +| flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:56:40:64 | () => { } | +| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:56:42:64 | () => { } | +| flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:56:19:56:26 | () => {} | +| flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:61:21:61:28 | () => {} | +| flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:64:74:71 | () => {} | +| flow.js:91:21:91:68 | new Pro ... ource)) | flow.js:91:75:91:82 | () => {} | +| 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 | +| flow.js:129:2:129:52 | new Pro ... olved)) | flow.js:129:59:129:70 | x => sink(x) | | 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 +| 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 | +| flow.js:13:11:13:58 | new Pro ... ource)) | flow.js:13:33:13:38 | reject | +| flow.js:24:2:24:49 | new Pro ... ource)) | flow.js:24:24:24:29 | reject | +| flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:24:26:29 | reject | +| flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:24:32:29 | reject | +| flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:24:40:29 | reject | +| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:24:42:29 | reject | +| flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:55:33:55:38 | reject | +| flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:60:34:60:39 | reject | +| flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:31:65:36 | reject | +| flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:32:74:37 | reject | +| flow.js:86:23:86:70 | new Pro ... ource)) | flow.js:86:45:86:50 | reject | +| flow.js:91:21:91:68 | new Pro ... ource)) | flow.js:91:43:91:48 | reject | +| flow.js:100:28:100:75 | new Pro ... ource)) | flow.js:100:50:100:55 | reject | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:24:103:29 | reject | +| flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:24:105:29 | reject | +| flow.js:107:17:107:64 | new Pro ... ource)) | flow.js:107:39:107:44 | reject | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:24:109:29 | reject | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | flow.js:111:24:111:29 | reject | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | flow.js:113:24:113:29 | reject | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | flow.js:117:24:117:29 | reject | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | flow.js:119:24:119:29 | reject | +| flow.js:129:2:129:52 | new Pro ... olved)) | flow.js:129:24:129:29 | reject | +| interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:43:11:48 | 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 +| 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 | +| flow.js:13:11:13:58 | new Pro ... ource)) | flow.js:13:24:13:30 | resolve | +| flow.js:24:2:24:49 | new Pro ... ource)) | flow.js:24:15:24:21 | resolve | +| flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:15:26:21 | resolve | +| flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:15:32:21 | resolve | +| flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:15:40:21 | resolve | +| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:15:42:21 | resolve | +| flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:55:24:55:30 | resolve | +| flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:60:25:60:31 | resolve | +| flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:22:65:28 | resolve | +| flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:23:74:29 | resolve | +| flow.js:86:23:86:70 | new Pro ... ource)) | flow.js:86:36:86:42 | resolve | +| flow.js:91:21:91:68 | new Pro ... ource)) | flow.js:91:34:91:40 | resolve | +| flow.js:100:28:100:75 | new Pro ... ource)) | flow.js:100:41:100:47 | resolve | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:15:103:21 | resolve | +| flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:15:105:21 | resolve | +| flow.js:107:17:107:64 | new Pro ... ource)) | flow.js:107:30:107:36 | resolve | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:15:109:21 | resolve | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | flow.js:111:15:111:21 | resolve | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | flow.js:113:15:113:21 | resolve | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | flow.js:117:15:117:21 | resolve | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | flow.js:119:15:119:21 | resolve | +| flow.js:129:2:129:52 | new Pro ... olved)) | flow.js:129:15:129:21 | resolve | +| interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:34:11:40 | 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 +| 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) | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:56:103:75 | x => {return source} | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | flow.js:111:56:111:68 | x => rejected | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | flow.js:113:56:113:68 | x => rejected | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | flow.js:117:56:117:68 | x => resolved | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | flow.js:119:56:119:68 | x => resolved | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:23:18:25:3 | (v) => ... v;\\n } | +flow +| flow.js:2:15:2:22 | "source" | flow.js:5:7:5:14 | await p1 | +| flow.js:2:15:2:22 | "source" | flow.js:8:7:8:14 | await p2 | +| flow.js:2:15:2:22 | "source" | flow.js:17:8:17:8 | e | +| flow.js:2:15:2:22 | "source" | flow.js:20:41:20:41 | x | +| flow.js:2:15:2:22 | "source" | flow.js:26:79:26:79 | y | +| flow.js:2:15:2:22 | "source" | flow.js:28:58:28:58 | z | +| flow.js:2:15:2:22 | "source" | flow.js:32:67:32:67 | x | +| flow.js:2:15:2:22 | "source" | flow.js:34:58:34:58 | a | +| flow.js:2:15:2:22 | "source" | flow.js:38:29:38:29 | a | +| flow.js:2:15:2:22 | "source" | flow.js:40:83:40:83 | x | +| flow.js:2:15:2:22 | "source" | flow.js:44:92:44:92 | a | +| flow.js:2:15:2:22 | "source" | flow.js:46:60:46:60 | a | +| flow.js:2:15:2:22 | "source" | flow.js:48:54:48:54 | x | +| flow.js:2:15:2:22 | "source" | flow.js:53:39:53:39 | v | +| flow.js:2:15:2:22 | "source" | flow.js:58:24:58:24 | x | +| flow.js:2:15:2:22 | "source" | flow.js:62:22:62:22 | x | +| flow.js:2:15:2:22 | "source" | flow.js:70:8:70:8 | e | +| flow.js:2:15:2:22 | "source" | flow.js:76:50:76:50 | e | +| flow.js:2:15:2:22 | "source" | flow.js:79:20:79:20 | x | +| flow.js:2:15:2:22 | "source" | flow.js:84:21:84:21 | e | +| flow.js:2:15:2:22 | "source" | flow.js:89:45:89:45 | e | +| flow.js:2:15:2:22 | "source" | flow.js:101:7:101:9 | foo | +| flow.js:2:15:2:22 | "source" | flow.js:103:93:103:93 | x | +| flow.js:2:15:2:22 | "source" | flow.js:105:95:105:95 | x | +| flow.js:2:15:2:22 | "source" | flow.js:109:89:109:89 | x | +| flow.js:2:15:2:22 | "source" | flow.js:113:87:113:87 | x | +| flow.js:2:15:2:22 | "source" | flow.js:119:86:119:86 | x | +| flow.js:2:15:2:22 | "source" | flow.js:123:58:123:58 | x | +| flow.js:2:15:2:22 | "source" | flow.js:125:59:125:59 | x | +| flow.js:2:15:2:22 | "source" | flow.js:129:69:129:69 | x | +| flow.js:2:15:2:22 | "source" | flow.js:131:43:131:43 | x | +exclusiveTaintFlow +| interflow.js:3:18:3:25 | "source" | interflow.js:18:10:18:14 | error | diff --git a/javascript/ql/test/library-tests/Promises/tests.ql b/javascript/ql/test/library-tests/Promises/tests.ql index 6b29dc01fa3d..2490f3789700 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 flow \ No newline at end of file