Skip to content
Merged
51 changes: 51 additions & 0 deletions javascript/ql/src/Statements/UseOfReturnlessFunction.ql
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,57 @@ predicate voidArrayCallback(DataFlow::CallNode call, Function func) {
)
}


/**
* Provides classes for working with various Deferred implementations.
* It is a heuristic. The heuristic assume that a class is a promise defintion
* if the class is called "Deferred" and the method `resolve` is called on an instance.
*
* Removes some false positives in the js/use-of-returnless-function query.
*/
module Deferred {
/**
* An instance of a `Deferred` class.
* For example the result from `new Deferred()` or `new $.Deferred()`.
*/
class DeferredInstance extends DataFlow::NewNode {
// Describes both `new Deferred()`, `new $.Deferred` and other variants.
DeferredInstance() { this.getCalleeName() = "Deferred" }

private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
t.start() and
result = this
or
exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t))
}

DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) }
}

/**
* A promise object created by a Deferred constructor
*/
private class DeferredPromiseDefinition extends PromiseDefinition, DeferredInstance {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though we are intending to use this as a FP filter, we will effectively also be extending the dataflow reasoning that deals with promises. This may lead to surprising dataflow in this query caused by a bad heuristic one day. I am willing to accept that risk though.

DeferredPromiseDefinition() {
// hardening of the "Deferred" heuristic: a method call to `resolve`.
exists(ref().getAMethodCall("resolve"))
}

override DataFlow::FunctionNode getExecutor() { result = getCallback(0) }
}

/**
* A resolved promise created by a `new Deferred().resolve()` call.
*/
class ResolvedDeferredPromiseDefinition extends ResolvedPromiseDefinition {
ResolvedDeferredPromiseDefinition() {
this = any(DeferredPromiseDefinition def).ref().getAMethodCall("resolve")
}

override DataFlow::Node getValue() { result = getArgument(0) }
}
}

from DataFlow::CallNode call, Function func, string name, string msg
where
(
Expand Down
2 changes: 1 addition & 1 deletion javascript/ql/test/library-tests/TaintTracking/promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ function closure() {
let resolver = Promise.withResolver();
resolver.resolve(source());
sink(resolver.promise); // NOT OK
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,10 @@

var baz = [1,2,3].filter(n => {n === 3}) // OK
console.log(baz);

class Deferred {

}

new Deferred().resolve(onlySideEffects()); // OK
})();