mirror of
https://github.com/github/codeql.git
synced 2025-12-16 08:43:11 +01:00
762 lines
26 KiB
Plaintext
762 lines
26 KiB
Plaintext
/**
|
|
* Provides classes for modeling promises and their data-flow.
|
|
*/
|
|
|
|
import javascript
|
|
private import dataflow.internal.StepSummary
|
|
private import semmle.javascript.dataflow.internal.FlowSteps
|
|
|
|
/**
|
|
* A call to the `Promise` constructor, such as `new Promise((resolve, reject) => { ... })`.
|
|
*
|
|
* This includes calls to the built-in `Promise` constructor as well as promise implementations from known libraries, such as `bluebird`.
|
|
*/
|
|
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 = this.getExecutor().getParameter(0) }
|
|
|
|
/** Gets the `reject` parameter of the executor function. */
|
|
DataFlow::ParameterNode getRejectParameter() { result = this.getExecutor().getParameter(1) }
|
|
|
|
/** Gets the `i`th callback handler installed by method `m`. */
|
|
private DataFlow::FunctionNode getAHandler(string m, int i) {
|
|
result = this.getAMethodCall(m).getCallback(i)
|
|
}
|
|
|
|
/**
|
|
* Gets a function that handles promise resolution, including both
|
|
* `then` handlers and `finally` handlers.
|
|
*/
|
|
DataFlow::FunctionNode getAResolveHandler() {
|
|
result = this.getAHandler("then", 0) or
|
|
result = this.getAFinallyHandler()
|
|
}
|
|
|
|
/**
|
|
* Gets a function that handles promise rejection, including
|
|
* `then` handlers, `catch` handlers and `finally` handlers.
|
|
*/
|
|
DataFlow::FunctionNode getARejectHandler() {
|
|
result = this.getAHandler("then", 1) or
|
|
result = this.getACatchHandler() or
|
|
result = this.getAFinallyHandler()
|
|
}
|
|
|
|
/**
|
|
* Gets a `catch` handler of this promise.
|
|
*/
|
|
DataFlow::FunctionNode getACatchHandler() { result = this.getAHandler("catch", 0) }
|
|
|
|
/**
|
|
* Gets a `finally` handler of this promise.
|
|
*/
|
|
DataFlow::FunctionNode getAFinallyHandler() { result = this.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))
|
|
}
|
|
|
|
/**
|
|
* Gets a reference to the `Promise` object.
|
|
* Either from the standard library, a polyfill import, or a polyfill that defines the global `Promise` variable.
|
|
*/
|
|
private DataFlow::SourceNode getAPromiseObject() {
|
|
// Standard library, or polyfills like [es6-shim](https://npmjs.org/package/es6-shim).
|
|
result = DataFlow::globalVarRef("Promise")
|
|
or
|
|
// polyfills from the [`promise`](https://npmjs.org/package/promise) library.
|
|
result =
|
|
DataFlow::moduleImport([
|
|
"promise", "promise/domains", "promise/setimmediate", "promise/lib/es6-extensions",
|
|
"promise/domains/es6-extensions", "promise/setimmediate/es6-extensions"
|
|
])
|
|
or
|
|
// polyfill from the [`promise-polyfill`](https://npmjs.org/package/promise-polyfill) library.
|
|
result = DataFlow::moduleMember(["promise-polyfill", "promise-polyfill/src/polyfill"], "default")
|
|
or
|
|
result = DataFlow::moduleImport(["promise-polyfill", "promise-polyfill/src/polyfill"])
|
|
or
|
|
result = DataFlow::moduleMember(["es6-promise", "rsvp"], "Promise")
|
|
or
|
|
result = DataFlow::moduleImport("native-promise-only")
|
|
or
|
|
result = DataFlow::moduleImport("when")
|
|
or
|
|
result = DataFlow::moduleImport("pinkie-promise")
|
|
or
|
|
result = DataFlow::moduleImport("pinkie")
|
|
or
|
|
result = DataFlow::moduleMember("synchronous-promise", "SynchronousPromise")
|
|
or
|
|
result = DataFlow::moduleImport("any-promise")
|
|
or
|
|
result = DataFlow::moduleImport("lie")
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
* or a polyfill implementing a superset of the ECMAScript 2015 `Promise` API.
|
|
*/
|
|
private class ES2015PromiseDefinition extends PromiseDefinition, DataFlow::InvokeNode {
|
|
ES2015PromiseDefinition() { this = getAPromiseObject().getAnInvocation() }
|
|
|
|
override DataFlow::FunctionNode getExecutor() { result = this.getCallback(0) }
|
|
}
|
|
|
|
/**
|
|
* A promise that is created and resolved with one or more value.
|
|
*/
|
|
abstract class PromiseCreationCall extends DataFlow::CallNode {
|
|
/**
|
|
* Gets a 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 promise that is created using a `Promise.all(array)` call.
|
|
*/
|
|
abstract class PromiseAllCreation extends PromiseCreationCall {
|
|
/**
|
|
* Gets a node for the array of values given to the `Promise.all(array)` call.
|
|
*/
|
|
abstract DataFlow::Node getArrayNode();
|
|
}
|
|
|
|
/**
|
|
* A resolved promise created by the standard ECMAScript 2015 `Promise.resolve` function.
|
|
*/
|
|
class ResolvedES2015PromiseDefinition extends ResolvedPromiseDefinition {
|
|
ResolvedES2015PromiseDefinition() { this = getAPromiseObject().getAMemberCall("resolve") }
|
|
|
|
override DataFlow::Node getValue() { result = this.getArgument(0) }
|
|
}
|
|
|
|
/**
|
|
* An aggregated promise produced either by `Promise.all`, `Promise.race`, or `Promise.any`.
|
|
*/
|
|
class AggregateES2015PromiseDefinition extends PromiseCreationCall {
|
|
AggregateES2015PromiseDefinition() {
|
|
this = getAPromiseObject().getAMemberCall(["all", "race", "any", "allSettled"])
|
|
or
|
|
this = DataFlow::moduleImport("promise.allsettled").getACall()
|
|
}
|
|
|
|
override DataFlow::Node getValue() {
|
|
result = this.getArgument(0).getALocalSource().(DataFlow::ArrayCreationNode).getAnElement()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An aggregated promise created using `Promise.all()`.
|
|
*/
|
|
class ES2015PromiseAllDefinition extends AggregateES2015PromiseDefinition, PromiseAllCreation {
|
|
ES2015PromiseAllDefinition() { this.getCalleeName() = "all" }
|
|
|
|
override DataFlow::Node getArrayNode() { result = this.getArgument(0) }
|
|
}
|
|
|
|
/**
|
|
* Common predicates shared between type-tracking and data-flow for promises.
|
|
*/
|
|
module Promises {
|
|
/**
|
|
* Gets the pseudo-field used to describe resolved values in a promise.
|
|
*/
|
|
overlay[local?]
|
|
string valueProp() { result = "$PromiseResolveField$" }
|
|
|
|
/**
|
|
* Gets the pseudo-field used to describe rejected values in a promise.
|
|
*/
|
|
overlay[local?]
|
|
string errorProp() { result = "$PromiseRejectField$" }
|
|
|
|
/** A property set containing the pseudo-properites of a promise object. */
|
|
class PromiseProps extends DataFlow::PropertySet {
|
|
PromiseProps() { this = "PromiseProps" }
|
|
|
|
override string getAProperty() { result = [valueProp(), errorProp()] }
|
|
}
|
|
|
|
predicate promiseConstructorRef = getAPromiseObject/0;
|
|
}
|
|
|
|
/**
|
|
* A module for supporting promises in type-tracking predicates.
|
|
* The `PromiseTypeTracking::promiseStep` predicate is used for type tracking in and out of promises,
|
|
* and is included in the standard type-tracking steps (`SourceNode::track`).
|
|
* The `TypeTracker::startInPromise()` predicate can be used to initiate a type-tracker
|
|
* where the tracked value is a promise.
|
|
*
|
|
* The below is an example of a type-tracking predicate where the initial value is a promise:
|
|
* ```
|
|
* DataFlow::SourceNode myType(DataFlow::TypeTracker t) {
|
|
* t.startInPromise() and
|
|
* result = <the promise value> and
|
|
* or
|
|
* exists(DataFlow::TypeTracker t2 | result = myType(t2).track(t2, t))
|
|
* }
|
|
* ```
|
|
*
|
|
* The type-tracking predicate above will only end (`t = DataFlow::TypeTracker::end()`) after the tracked value has been
|
|
* extracted from the promise.
|
|
*
|
|
* The `PromiseTypeTracking::promiseStep` predicate can be used instead of `SourceNode::track`
|
|
* to get type-tracking only for promise steps.
|
|
*
|
|
* Replace `t.startInPromise()` in the above example with `t.start()` to create a type-tracking predicate
|
|
* where the value is not initially inside a promise.
|
|
*/
|
|
module PromiseTypeTracking {
|
|
/**
|
|
* Gets the result from a single step through a promise, from `pred` to `result` summarized by `summary`.
|
|
* This can be loading a resolved value from a promise, storing a value in a promise, or copying a resolved value from one promise to another.
|
|
*
|
|
* These type-tracking steps are already included in the default type-tracking steps (through `PreCallGraphStep`).
|
|
*/
|
|
overlay[caller?]
|
|
pragma[inline]
|
|
DataFlow::Node promiseStep(DataFlow::Node pred, StepSummary summary) {
|
|
exists(string field | field = Promises::valueProp() |
|
|
summary = LoadStep(field) and
|
|
PromiseFlow::loadStep(pred, result, field)
|
|
or
|
|
summary = StoreStep(field) and
|
|
PromiseFlow::storeStep(pred, result, field)
|
|
or
|
|
summary = CopyStep(field) and
|
|
PromiseFlow::loadStoreStep(pred, result, field)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets the result from a single step through a promise, from `pred` with tracker `t2` to `result` with tracker `t`.
|
|
* This can be loading a resolved value from a promise, storing a value in a promise, or copying a resolved value from one promise to another.
|
|
*/
|
|
overlay[caller?]
|
|
pragma[inline]
|
|
DataFlow::SourceNode promiseStep(
|
|
DataFlow::SourceNode pred, DataFlow::TypeTracker t, DataFlow::TypeTracker t2
|
|
) {
|
|
exists(DataFlow::Node mid, StepSummary summary | pred.flowsTo(mid) and t = t2.append(summary) |
|
|
result = PromiseTypeTracking::promiseStep(mid, summary)
|
|
)
|
|
}
|
|
}
|
|
|
|
private import semmle.javascript.dataflow.internal.PreCallGraphStep
|
|
|
|
/**
|
|
* A step related to promises.
|
|
*
|
|
* These steps are for `await p`, `new Promise()`, `Promise.resolve()`,
|
|
* `Promise.then()`, `Promise.catch()`, and `Promise.finally()`.
|
|
*/
|
|
private class PromiseStep extends LegacyPreCallGraphStep {
|
|
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
|
|
PromiseFlow::loadStep(obj, element, prop)
|
|
}
|
|
|
|
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
|
|
PromiseFlow::storeStep(element, obj, prop)
|
|
}
|
|
|
|
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
|
PromiseFlow::loadStoreStep(pred, succ, prop)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A step from `p -> await p` for the case where `p` is not a promise.
|
|
*
|
|
* In this case, `await p` just returns `p` itself. We block flow of the promise-related
|
|
* pseudo properties through this edge.
|
|
*/
|
|
private class RawAwaitStep extends DataFlow::SharedTypeTrackingStep {
|
|
override predicate withoutPropStep(
|
|
DataFlow::Node pred, DataFlow::Node succ, DataFlow::PropertySet props
|
|
) {
|
|
exists(AwaitExpr await |
|
|
pred = await.getOperand().flow() and
|
|
succ = await.flow() and
|
|
props instanceof Promises::PromiseProps
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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).
|
|
*/
|
|
module PromiseFlow {
|
|
private predicate valueProp = Promises::valueProp/0;
|
|
|
|
private predicate errorProp = Promises::errorProp/0;
|
|
|
|
/**
|
|
* Holds if there is a step for loading a `value` from a `promise`.
|
|
* `prop` is either `valueProp()` if the value is a resolved value, or `errorProp()` if the promise has been rejected.
|
|
*/
|
|
predicate loadStep(DataFlow::Node promise, DataFlow::Node value, string prop) {
|
|
// await promise.
|
|
exists(AwaitExpr await | await.getOperand() = promise.asExpr() |
|
|
prop = valueProp() and
|
|
value.asExpr() = await
|
|
or
|
|
prop = errorProp() and
|
|
value = await.getExceptionTarget()
|
|
)
|
|
or
|
|
// promise.then()
|
|
exists(DataFlow::MethodCallNode call |
|
|
call.getMethodName() = "then" and promise = call.getReceiver()
|
|
|
|
|
prop = valueProp() and
|
|
value = call.getCallback(0).getParameter(0)
|
|
or
|
|
prop = errorProp() and
|
|
value = call.getCallback(1).getParameter(0)
|
|
)
|
|
or
|
|
// promise.catch()
|
|
exists(DataFlow::MethodCallNode call | call.getMethodName() = "catch" |
|
|
prop = errorProp() and
|
|
promise = call.getReceiver() and
|
|
value = call.getCallback(0).getParameter(0)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if there is a step for storing a `value` into a promise `obj`.
|
|
* `prop` is either `valueProp()` if the value is a resolved value, or `errorProp()` if the promise has been rejected.
|
|
*/
|
|
predicate storeStep(DataFlow::Node value, DataFlow::SourceNode obj, string prop) {
|
|
// promise definition, e.g. `new Promise()`
|
|
exists(PromiseDefinition promise | obj = promise |
|
|
prop = valueProp() and
|
|
value = promise.getResolveParameter().getACall().getArgument(0)
|
|
or
|
|
prop = errorProp() and
|
|
value =
|
|
[
|
|
promise.getRejectParameter().getACall().getArgument(0),
|
|
promise.getExecutor().getExceptionalReturn()
|
|
]
|
|
)
|
|
or
|
|
// promise creation call, e.g. `Promise.resolve`.
|
|
exists(PromiseCreationCall promise | obj = promise |
|
|
not promise instanceof PromiseAllCreation and
|
|
prop = valueProp() and
|
|
value = promise.getValue()
|
|
or
|
|
prop = valueProp() and
|
|
value = promise.(PromiseAllCreation).getArrayNode()
|
|
)
|
|
or
|
|
// promise.then()
|
|
exists(DataFlow::MethodCallNode call | call.getMethodName() = "then" and obj = call |
|
|
prop = valueProp() and
|
|
value = call.getCallback([0 .. 1]).getAReturn()
|
|
or
|
|
prop = errorProp() and
|
|
value = call.getCallback([0 .. 1]).getExceptionalReturn()
|
|
)
|
|
or
|
|
// promise.catch()
|
|
exists(DataFlow::MethodCallNode call | call.getMethodName() = "catch" and obj = call |
|
|
prop = errorProp() and
|
|
value = call.getCallback(0).getExceptionalReturn()
|
|
or
|
|
prop = valueProp() and
|
|
value = call.getCallback(0).getAReturn()
|
|
)
|
|
or
|
|
// promise.finally()
|
|
exists(DataFlow::MethodCallNode call | call.getMethodName() = "finally" |
|
|
prop = errorProp() and
|
|
value = call.getCallback(0).getExceptionalReturn() and
|
|
obj = call
|
|
)
|
|
or
|
|
exists(DataFlow::FunctionNode f | f.getFunction().isAsync() |
|
|
// ordinary return
|
|
prop = valueProp() and
|
|
value = f.getAReturn() and
|
|
obj = f.getReturnNode()
|
|
or
|
|
// exceptional return
|
|
prop = errorProp() and
|
|
localExceptionStepWithAsyncFlag(value, obj, true)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if there is a step copying a resolved/rejected promise value from promise `pred` to promise `succ`.
|
|
* `prop` is either `valueProp()` if the value is a resolved value, or `errorProp()` if the promise has been rejected.
|
|
*/
|
|
predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
|
// promise definition, e.g. `new Promise()`
|
|
exists(PromiseDefinition promise | succ = promise |
|
|
// Copy the value of a resolved promise to the value of this promise.
|
|
prop = valueProp() and
|
|
pred = promise.getResolveParameter().getACall().getArgument(0)
|
|
)
|
|
or
|
|
// promise creation call, e.g. `Promise.resolve`.
|
|
exists(PromiseCreationCall promise | succ = promise |
|
|
// Copy the value of a resolved promise to the value of this promise.
|
|
not promise instanceof PromiseAllCreation and
|
|
pred = promise.getValue() and
|
|
prop = valueProp()
|
|
or
|
|
pred = promise.(PromiseAllCreation).getArrayNode() and
|
|
prop = valueProp()
|
|
)
|
|
or
|
|
// promise.then()
|
|
exists(DataFlow::MethodCallNode call | call.getMethodName() = "then" and succ = call |
|
|
not exists(call.getArgument(1)) and
|
|
prop = errorProp() and
|
|
pred = call.getReceiver()
|
|
or
|
|
// read the value of a resolved/rejected promise that is returned
|
|
(prop = errorProp() or prop = valueProp()) and
|
|
pred = call.getCallback([0 .. 1]).getAReturn()
|
|
)
|
|
or
|
|
// promise.catch()
|
|
exists(DataFlow::MethodCallNode call | call.getMethodName() = "catch" and succ = call |
|
|
prop = valueProp() and
|
|
pred = call.getReceiver()
|
|
or
|
|
// read the value of a resolved/rejected promise that is returned
|
|
(prop = errorProp() or prop = valueProp()) and
|
|
pred = call.getCallback(0).getAReturn()
|
|
)
|
|
or
|
|
// promise.finally()
|
|
exists(DataFlow::MethodCallNode call | call.getMethodName() = "finally" and succ = call |
|
|
(prop = valueProp() or prop = errorProp()) and
|
|
pred = call.getReceiver()
|
|
or
|
|
// read the value of a rejected promise that is returned
|
|
prop = errorProp() and
|
|
pred = call.getCallback(0).getAReturn()
|
|
)
|
|
or
|
|
// return from `async` function
|
|
exists(DataFlow::FunctionNode f | f.getFunction().isAsync() |
|
|
// ordinary return
|
|
prop = valueProp() and
|
|
pred = f.getAReturn() and
|
|
succ = f.getReturnNode()
|
|
)
|
|
}
|
|
}
|
|
|
|
private class PromiseTaintStep extends TaintTracking::LegacyTaintStep {
|
|
override predicate promiseStep(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() and
|
|
not succ instanceof PromiseAllCreation
|
|
or
|
|
// from `arr` to `Promise.all(arr)`
|
|
pred = succ.(PromiseAllCreation).getArrayNode()
|
|
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
|
|
)
|
|
or
|
|
exists(DataFlow::CallNode mapSeries |
|
|
mapSeries = DataFlow::moduleMember("bluebird", "mapSeries").getACall()
|
|
|
|
|
// from `xs` to `x` in `require("bluebird").mapSeries(xs, (x) => {...})`.
|
|
pred = mapSeries.getArgument(0) and
|
|
succ = mapSeries.getABoundCallbackParameter(1, 0)
|
|
or
|
|
// from `y` to `require("bluebird").mapSeries(x, x => y)`.
|
|
pred = mapSeries.getCallback(1).getAReturn() and
|
|
succ = mapSeries
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Defines flow steps for return on async functions.
|
|
*/
|
|
private module AsyncReturnSteps {
|
|
/**
|
|
* A data-flow step for ordinary return from an async function in a taint configuration.
|
|
*/
|
|
private class AsyncTaintReturn extends TaintTracking::LegacyTaintStep {
|
|
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
|
exists(Function f |
|
|
f.isAsync() and
|
|
returnExpr(f, pred, _) and
|
|
succ.(DataFlow::FunctionReturnNode).getFunction() = f
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Provides classes for working with the `bluebird` library (http://bluebirdjs.com).
|
|
*/
|
|
module Bluebird {
|
|
private DataFlow::SourceNode bluebird() {
|
|
result = DataFlow::globalVarRef("Promise") or // same as ES2015PromiseDefinition!
|
|
result = DataFlow::moduleImport("bluebird")
|
|
}
|
|
|
|
/**
|
|
* A promise object created by the bluebird `Promise` constructor.
|
|
*/
|
|
private class BluebirdPromiseDefinition extends PromiseDefinition, DataFlow::NewNode {
|
|
BluebirdPromiseDefinition() { this = bluebird().getAnInstantiation() }
|
|
|
|
override DataFlow::FunctionNode getExecutor() { result = this.getCallback(0) }
|
|
}
|
|
|
|
/**
|
|
* A resolved promise created by the bluebird `Promise.resolve` function.
|
|
*/
|
|
class ResolvedBluebidPromiseDefinition extends ResolvedPromiseDefinition {
|
|
ResolvedBluebidPromiseDefinition() { this = bluebird().getAMemberCall("resolve") }
|
|
|
|
override DataFlow::Node getValue() { result = this.getArgument(0) }
|
|
}
|
|
|
|
/**
|
|
* An aggregated promise produced either by `Promise.all`, `Promise.race` or `Promise.map`.
|
|
*/
|
|
class AggregateBluebirdPromiseDefinition extends PromiseCreationCall {
|
|
AggregateBluebirdPromiseDefinition() {
|
|
exists(string m | m = "all" or m = "race" or m = "map" | this = bluebird().getAMemberCall(m))
|
|
}
|
|
|
|
override DataFlow::Node getValue() {
|
|
result = this.getArgument(0).getALocalSource().(DataFlow::ArrayCreationNode).getAnElement()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A promise created using `Promise.all`:
|
|
*/
|
|
class BluebirdPromiseAllDefinition extends AggregateBluebirdPromiseDefinition, PromiseAllCreation {
|
|
BluebirdPromiseAllDefinition() { this.getCalleeName() = "all" }
|
|
|
|
override DataFlow::Node getArrayNode() { result = this.getArgument(0) }
|
|
}
|
|
|
|
/**
|
|
* An async function created using a call to `bluebird.coroutine`.
|
|
*/
|
|
class BluebirdCoroutineDefinition extends DataFlow::CallNode {
|
|
BluebirdCoroutineDefinition() { this = bluebird().getAMemberCall("coroutine") }
|
|
}
|
|
|
|
private class BluebirdCoroutineDefinitionAsPartialInvoke extends DataFlow::PartialInvokeNode::Range,
|
|
BluebirdCoroutineDefinition
|
|
{
|
|
override DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) {
|
|
boundArgs = 0 and
|
|
callback = this.getArgument(0) and
|
|
result = this
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Provides classes for working with the `q` library (https://github.com/kriskowal/q) and the compatible `kew` library (https://github.com/Medium/kew).
|
|
*/
|
|
module Q {
|
|
/**
|
|
* A promise object created by the q `Promise` constructor.
|
|
*/
|
|
private class QPromiseDefinition extends PromiseDefinition, DataFlow::CallNode {
|
|
QPromiseDefinition() { this = DataFlow::moduleMember(["q", "kew"], "Promise").getACall() }
|
|
|
|
override DataFlow::FunctionNode getExecutor() { result = this.getCallback(0) }
|
|
}
|
|
}
|
|
|
|
private module ClosurePromise {
|
|
/**
|
|
* A promise created by a call `new goog.Promise(executor)`.
|
|
*/
|
|
private class ClosurePromiseDefinition extends PromiseDefinition, DataFlow::NewNode {
|
|
ClosurePromiseDefinition() { this = Closure::moduleImport("goog.Promise").getACall() }
|
|
|
|
override DataFlow::FunctionNode getExecutor() { result = this.getCallback(0) }
|
|
}
|
|
|
|
/**
|
|
* A promise created by a call `goog.Promise.resolve(value)`.
|
|
*/
|
|
private class ResolvedClosurePromiseDefinition extends ResolvedPromiseDefinition {
|
|
pragma[noinline]
|
|
ResolvedClosurePromiseDefinition() {
|
|
this = Closure::moduleImport("goog.Promise.resolve").getACall()
|
|
}
|
|
|
|
override DataFlow::Node getValue() { result = this.getArgument(0) }
|
|
}
|
|
|
|
/**
|
|
* Taint steps through closure promise methods.
|
|
*/
|
|
private class ClosurePromiseTaintStep extends TaintTracking::LegacyTaintStep {
|
|
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
|
// static methods in goog.Promise
|
|
exists(DataFlow::CallNode call, string name |
|
|
call = Closure::moduleImport("goog.Promise." + name).getACall() and
|
|
succ = call and
|
|
pred = call.getAnArgument()
|
|
|
|
|
name = ["all", "allSettled", "firstFulfilled", "race"]
|
|
)
|
|
or
|
|
// promise created through goog.promise.withResolver()
|
|
exists(DataFlow::CallNode resolver |
|
|
resolver = Closure::moduleImport("goog.Promise.withResolver").getACall() and
|
|
succ = resolver.getAPropertyRead("promise") and
|
|
pred = resolver.getAMethodCall("resolve").getArgument(0)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
private module DynamicImportSteps {
|
|
/**
|
|
* A step from an export value to its uses via dynamic imports.
|
|
*
|
|
* For example:
|
|
* ```js
|
|
* // foo.js
|
|
* export default Foo
|
|
*
|
|
* // bar.js
|
|
* let Foo = await import('./foo');
|
|
* ```
|
|
*/
|
|
class DynamicImportStep extends LegacyPreCallGraphStep {
|
|
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
|
exists(DynamicImportExpr imprt |
|
|
pred = imprt.getImportedModule().getAnExportedValue("default") and
|
|
succ = imprt.flow() and
|
|
prop = Promises::valueProp()
|
|
)
|
|
}
|
|
|
|
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
|
// Special-case code like `(await import(x)).Foo` to boost type tracking depth.
|
|
exists(
|
|
DynamicImportExpr imprt, string name, DataFlow::Node mid, DataFlow::SourceNode awaited
|
|
|
|
|
pred = imprt.getImportedModule().getAnExportedValue(name) and
|
|
mid.getALocalSource() = imprt.flow() and
|
|
PromiseFlow::loadStep(mid, awaited, Promises::valueProp()) and
|
|
succ = awaited.getAPropertyRead(name)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Provides classes modeling libraries implementing `promisify` functions.
|
|
* That is, functions that convert callback style functions to functions that return a promise.
|
|
*/
|
|
module Promisify {
|
|
/**
|
|
* A call to a `promisifyAll` function.
|
|
* E.g. `require("bluebird").promisifyAll(...)`.
|
|
*/
|
|
class PromisifyAllCall extends DataFlow::CallNode {
|
|
PromisifyAllCall() {
|
|
this =
|
|
[
|
|
DataFlow::moduleMember(["bluebird", "@google-cloud/promisify", "es6-promisify"],
|
|
"promisifyAll"),
|
|
DataFlow::moduleMember("thenify-all", "withCallback"),
|
|
DataFlow::moduleImport([
|
|
"util-promisifyall", "pify", "thenify-all", "@gar/promisify", "util.promisify-all"
|
|
])
|
|
].getACall()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A call to a `promisify` function.
|
|
* E.g. `require("util").promisify(...)`.
|
|
*/
|
|
class PromisifyCall extends DataFlow::CallNode {
|
|
PromisifyCall() {
|
|
this = DataFlow::moduleImport(["util", "bluebird"]).getAMemberCall("promisify")
|
|
or
|
|
this = DataFlow::moduleImport(["pify", "util.promisify", "util-promisify"]).getACall()
|
|
or
|
|
this = DataFlow::moduleImport(["thenify", "@gar/promisify", "es6-promisify"]).getACall()
|
|
or
|
|
this = DataFlow::moduleMember("thenify", "withCallback").getACall()
|
|
or
|
|
this = DataFlow::moduleMember("@google-cloud/promisify", "promisify").getACall()
|
|
}
|
|
}
|
|
}
|