Files
codeql/javascript/ql/lib/semmle/javascript/frameworks/EventEmitter.qll
Asger F c687dc93b0 JS: Add overlay[global] to abstract classes with fields
Some abstract classes defines fields without binding them, leaving it up to the subclasses to bind them. When combined with overlay[local?], the charpred for such an abstract class can become local, while the subclasses are global. The means the charpred needs to be materialized, even though it doesn't bind the fields, leading to a cartesian product.
2026-01-07 11:05:41 +01:00

200 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.
*/
overlay[global]
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.
*/
overlay[global]
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
)
}
}