Files
codeql/javascript/ql/lib/semmle/javascript/frameworks/EventEmitter.qll
2021-10-26 12:55:25 +02:00

198 lines
6.7 KiB
Plaintext

import javascript
module EventEmitter {
/** Gets the name of a method on `EventEmitter` that returns `this`. */
string chainableMethod() {
result = "off" or
result = "removeAllListeners" or
result = "removeListener" or
result = "setMaxListeners" or
result = on()
}
/** Gets the name of a method on `EventEmitter` that registers an event handler. */
string on() { result = ["addListener", "on", "once", "prependListener", "prependOnceListener"] }
/**
* Gets a node that refers to an EventEmitter object.
*/
DataFlow::SourceNode trackEventEmitter(EventEmitter emitter) {
result = trackEventEmitter(DataFlow::TypeTracker::end(), emitter)
}
private DataFlow::SourceNode trackEventEmitter(DataFlow::TypeTracker t, EventEmitter emitter) {
t.start() and result = emitter
or
exists(DataFlow::TypeTracker t2, DataFlow::SourceNode pred |
pred = trackEventEmitter(t2, emitter)
|
result = pred.track(t2, t)
or
// invocation of a chainable method
exists(DataFlow::MethodCallNode mcn |
mcn = pred.getAMethodCall(chainableMethod()) and
// exclude getter versions
exists(mcn.getAnArgument()) and
result = mcn and
t = t2.continue()
)
)
}
/**
* An object that implements the EventEmitter API.
* Extending this class does nothing, its mostly to indicate intent.
*
* The classes EventRegistration::Range and EventDispatch::Range must be extended to get a working EventEmitter model.
* An EventRegistration models a method call that registers some event handler on an EventEmitter.
* And EventDispatch models that some event is dispatched on an EventEmitter.
*
* Both the EventRegistration and EventDispatch have a field `emitter`,
* which is the EventEmitter that events are registered on / dispatched to respectively.
*
* Here is a simple JavaScript example with the NodeJS EventEmitter:
* var e = new EventEmitter(); // <- EventEmitter
* e.on("name", (data) => {...}); // <- EventRegistration
* e.emit("name", "foo"); // <- EventDispatch
*/
abstract class Range extends DataFlow::Node { }
}
/**
* An EventEmitter instance that implements the EventEmitter API.
* Extend EventEmitter::Range to mark something as being an EventEmitter.
*/
class EventEmitter extends DataFlow::Node instanceof EventEmitter::Range { }
/**
* A registration of an event handler on an EventEmitter.
*/
class EventRegistration extends DataFlow::Node instanceof EventRegistration::Range {
/** Gets the EventEmitter that the event handler is registered on. */
final EventEmitter getEmitter() { result = super.getEmitter() }
/** Gets the name of the channel if possible. */
string getChannel() { result = super.getChannel() }
/** Gets the `i`th parameter in the event handler. */
DataFlow::Node getReceivedItem(int i) { result = super.getReceivedItem(i) }
/**
* Gets a value that is returned by the event handler.
* The default implementation is that no value can be returned.
*/
DataFlow::Node getAReturnedValue() { result = super.getAReturnedValue() }
/**
* Get a dispatch that this event handler can return a value to.
* The default implementation is that there exists no such dispatch.
*/
EventDispatch getAReturnDispatch() { result = super.getAReturnDispatch() }
}
module EventRegistration {
/**
* A registration of an event handler on an EventEmitter.
*/
abstract class Range extends DataFlow::Node {
EventEmitter::Range emitter;
final EventEmitter getEmitter() { result = emitter }
abstract string getChannel();
abstract DataFlow::Node getReceivedItem(int i);
DataFlow::Node getAReturnedValue() { none() }
EventDispatch::Range getAReturnDispatch() { none() }
}
/**
* A default implementation of an EventRegistration.
* The default implementation assumes that `this` is a DataFlow::InvokeNode where the
* first argument is a string describing which channel is registered, and the second
* argument is the event handler callback.
*/
abstract class DefaultEventRegistration extends Range, DataFlow::InvokeNode {
override string getChannel() { this.getArgument(0).mayHaveStringValue(result) }
override DataFlow::Node getReceivedItem(int i) {
result = this.getABoundCallbackParameter(1, i)
}
}
}
/**
* A dispatch of an event on an EventEmitter.
*/
class EventDispatch extends DataFlow::Node instanceof EventDispatch::Range {
/** Gets the emitter that the event dispatch happens on. */
EventEmitter getEmitter() { result = super.getEmitter() }
/** Gets the name of the channel if possible. */
string getChannel() { result = super.getChannel() }
/** Gets the `i`th argument that is send to the event handler. */
DataFlow::Node getSentItem(int i) { result = super.getSentItem(i) }
/**
* Get an EventRegistration that this event dispatch can send an event to.
* The default implementation is that the emitters of the dispatch and registration have to be equal.
* Channels are by default ignored.
*/
EventRegistration getAReceiver() { result = super.getAReceiver() }
}
module EventDispatch {
/**
* A dispatch of an event on an EventEmitter.
*/
abstract class Range extends DataFlow::Node {
EventEmitter::Range emitter;
final EventEmitter getEmitter() { result = emitter }
abstract string getChannel();
abstract DataFlow::Node getSentItem(int i);
abstract EventRegistration::Range getAReceiver();
}
/**
* A default implementation of an EventDispatch.
* The default implementation assumes that the dispatch is a DataFlow::InvokeNode,
* where the first argument is a string describing the channel, and the `i`+1 argument
* is the `i`th item sent to the event handler.
*/
abstract class DefaultEventDispatch extends Range, DataFlow::InvokeNode {
override string getChannel() { this.getArgument(0).mayHaveStringValue(result) }
override DataFlow::Node getSentItem(int i) { result = this.getArgument(i + 1) }
override EventRegistration::Range getAReceiver() { this.getEmitter() = result.getEmitter() }
}
}
/**
* A flow-step that models data-flow between event handlers and event dispatchers.
*/
private class EventEmitterFlowStep extends DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(EventRegistration reg, EventDispatch dispatch |
reg = dispatch.getAReceiver() and
not dispatch.getChannel() != reg.getChannel()
|
exists(int i | i >= 0 |
pred = dispatch.getSentItem(i) and
succ = reg.getReceivedItem(i)
)
or
dispatch = reg.getAReturnDispatch() and
pred = reg.getAReturnedValue() and
succ = dispatch
)
}
}