Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: special EventEmitter keys leak information about other rules #9328

Merged
merged 4 commits into from Sep 27, 2017

Conversation

not-an-aardvark
Copy link
Member

What is the purpose of this pull request? (put an "X" next to item)

[x] Bug fix

Tell us about your environment

  • ESLint Version: master
  • Node Version: 8.5.0
  • npm Version: 5.3.0

What parser (default, Babel-ESLint, etc.) are you using?

N/A

Please show your full configuration:

N/A

What did you do? Please include the actual source code causing the issue.

"use strict";

const eslint = require("eslint");
const linter = new eslint.Linter();

linter.defineRule("foo-rule", context => {
    return {
        newListener() {
            assert(false);
        }
    }
});

linter.verify("foo", { rules: { "foo-rule": "error", "no-undef": "error" } });

What did you expect to happen?

I expected the newListener method to never get called, because there are no nodes with type newListener.

What actually happened? Please include the actual, raw output from ESLint.

The newListener method got called because a listener was added from no-undef, and the string "newListener" has special behavior when added to an event emitter.

What changes did you make? (Give an overview)

Linter uses Node's EventEmitter API to register listeners for rules. However, the EventEmitter API has a few problems for this use case:

  • EventEmitter has three "special" events (newListener, removeListener, and error) which are called when something happens with another listener. This is undesirable because Linter allows rules to register listeners for arbitrary string events, and we don't want rule listeners to be able to detect each other.
  • EventEmitter calls listeners with a this value of the event emitter itself. This is undesirable because this would allow rules to modify or tamper with listeners registered by other rules.

This commit fixes the problem by updating Linter to use a custom event-emitting object with a similar API, rather than EventEmitter itself.

Is there anything you'd like reviewers to focus on?

Nothing in particular

@not-an-aardvark not-an-aardvark added accepted There is consensus among the team that this change meets the criteria for inclusion bug ESLint is working incorrectly core Relates to ESLint's core APIs and features labels Sep 19, 2017
Copy link
Member

@platinumazure platinumazure left a comment

Choose a reason for hiding this comment

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

Left a few notes about sinon usage.


linter.defineRule("checker", () => ({ Program: spy }));
linter.verify("foo", { rules: { checker: "error" } });
assert(spy.calledOnce);
Copy link
Member

Choose a reason for hiding this comment

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

I think this would be more readable by adding assert.notOk(spy.firstCall.thisValue) so all of the assertions are in one place. But I won't insist on it.


linter.defineRule("checker", () => ({ newListener: spy }));
linter.verify("foo", { rules: { checker: "error", "no-undef": "error" } });
assert(!spy.calledOnce);
Copy link
Member

Choose a reason for hiding this comment

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

This should be assert(!spy.called) or assert(spy.notCalled) or similar. Using calledOnce means a call twice would erroneously pass.

Copy link
Member

@platinumazure platinumazure left a comment

Choose a reason for hiding this comment

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

LGTM, just left a few suggestions for improving the inline documentation. Sorry for the delay.

lib/linter.js Outdated
* 2. It calls listener functions without any `this` value. (`EventEmitter` calls listeners with a
* `this` value of the emitter instance, which would give listeners access to other listeners.)
* 3. Events can be emitted with at most 3 arguments.
* @returns {{on: Function, emit: Function, eventNames: Function}} An emitter
Copy link
Member

Choose a reason for hiding this comment

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

I'd love to see this document in a JSDoc typedef, but this is nowhere near a blocker.

lib/linter.js Outdated
* another module throws an error or registers a listener.
* 2. It calls listener functions without any `this` value. (`EventEmitter` calls listeners with a
* `this` value of the emitter instance, which would give listeners access to other listeners.)
* 3. Events can be emitted with at most 3 arguments.
Copy link
Member

Choose a reason for hiding this comment

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

I'd love to see an example of 3-argument usage documented somewhere, maybe here, if possible. Not a blocker.

lib/linter.js Outdated
@@ -50,6 +49,40 @@ const MAX_AUTOFIX_PASSES = 10;
//------------------------------------------------------------------------------

/**
* Creates an object which can listen for and emit events.
Copy link
Member

Choose a reason for hiding this comment

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

Can this be moved out into it's own file? Since the behavior doesn't have direct connection to Linter, I'd rather see a separate file, even if it's small.

`Linter` uses Node's `EventEmitter` API to register listeners for rules. However, the `EventEmitter` API has a few problems for this use case:

* `EventEmitter` has three "special" events (`newListener`, `removeListener`, and `error`) which are called when something happens with another listener. This is undesirable because `Linter` allows rules to register listeners for arbitrary string events, and we don't want rule listeners to be able to detect each other.
* `EventEmitter` calls listeners with a `this` value of the event emitter itself. This is undesirable because this would allow rules to modify or tamper with listeners registered by other rules.

This commit fixes the problem by updating `Linter` to use a custom event-emitting object with a similar API, rather than `EventEmitter` itself.
@not-an-aardvark not-an-aardvark force-pushed the no-emitter-leaks branch 2 times, most recently from 862dee5 to 9d82dd1 Compare September 27, 2017 04:50
@eslint eslint deleted a comment from eslintbot Sep 27, 2017
@eslint eslint deleted a comment from eslintbot Sep 27, 2017
@eslint eslint deleted a comment from eslintbot Sep 27, 2017
@eslint eslint deleted a comment from eslintbot Sep 27, 2017
@eslint eslint deleted a comment from eslintbot Sep 27, 2017
@eslintbot
Copy link

LGTM

@eslint eslint deleted a comment from eslintbot Sep 27, 2017
@not-an-aardvark not-an-aardvark merged commit 1c6bc67 into master Sep 27, 2017
@not-an-aardvark not-an-aardvark deleted the no-emitter-leaks branch September 27, 2017 20:34
@eslint-deprecated eslint-deprecated bot locked and limited conversation to collaborators Mar 27, 2018
@eslint-deprecated eslint-deprecated bot added the archived due to age This issue has been archived; please open a new issue for any further discussion label Mar 27, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
accepted There is consensus among the team that this change meets the criteria for inclusion archived due to age This issue has been archived; please open a new issue for any further discussion bug ESLint is working incorrectly core Relates to ESLint's core APIs and features
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants