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

Update: Fix && vs || short-circuiting false negatives (fixes #13634) #13769

Merged
merged 2 commits into from Oct 22, 2020

Conversation

btmills
Copy link
Member

@btmills btmills commented Oct 18, 2020

Prerequisites checklist

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

[ ] Documentation update
[x] Bug fix (template)
[ ] New rule (template)
[ ] Changes an existing rule (template)
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:


Bug report copied from #13634.

Tell us about your environment

  • ESLint Version: 7.7.0
  • Node Version: 14.9.0
  • npm Version: 6.14.7

What parser (default, @babel/eslint-parser, @typescript-eslint/parser, etc.) are you using?

default

Please show your full configuration:

Configuration
module.exports = {
    parserOptions: {
        ecmaVersion: 2020
    }
};

What did you do? Please include the actual source code causing the issue, as well as the command that you used to run ESLint.

/* eslint constructor-super: "error",
   no-throw-literal: "error",
   prefer-promise-reject-errors: "error"
 */
let anything;

// If anything is a constructor, it'll be truthy, we'll evaluate the
// right side of the &&, and we'll extend 42.
class Subclass extends (anything && 42) {
    constructor() {
        super();
    }
}

// If anything is an Error instance, it'll be truthy, we'll evaluate
// the right side of the &&, and we'll throw 42.
throw anything && 42;

// If anything is an Error instance, it'll be truthy, we'll evaluate
// the right side of the &&, and we'll reject with 42.
Promise.reject(anything && 5);

Demo

What did you expect to happen?

We should report errors in each of the above cases. All three are looking for a specific type of truthy value from the expression. Because the expression uses &&, any possibly-valid value would evaluate to the right side of the expression, which would not be a valid value in these examples.

This was originally discovered in #13618 (comment). Because it relates to existing behavior that existed prior to that pull request, we decided to split it into its own issue that can be fixed after the pull request is merged. This bug is a false negative in the 3 rules above, and fixing it would cause them to report more errors. Does anyone object to fixing this in a semver-minor change as allowed by our semver policy?

The fix would update both astUtils.couldBeError() and constructor-super's isPossibleConstructor() to only check the right side of && LogicalExpressions.

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

No errors are reported.


What changes did you make? (Give an overview)

When constructor-super, no-throw-literal, and prefer-promise-reject-errors are looking for plausible constructor or error values, they fail to consider the short-circuiting behavior differences between the && and || operators. This results in a false negative where these rules allow foo && 42 when that expression cannot be a constructor or error. If the left side is falsy, the expression short-circuits with the falsy value. If the left side is truthy, the result of the expression is the right side value. All three rules already report the right side value in isolation but currently incorrectly allow it when on the right side of an && expression.

When @mdjermanovic added support for logical assignment operators in #13618, we decided to ship with the corrected behavior for &&= by only checking the right side of the expression, accepting that treatment of &&= would be inconsistent with existing treatment of &&. This PR then fixes the && treatment in what we believe can be a semver-minor bug fix.

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

Does everyone else agree with @mdjermanovic and me that this is can be a semver-minor bug fix?

A future improvement could detect statically falsy values on the left side of && expressions and report those as well. Such a change could also update the || treatment to ignore plausibly-(constructor|error) values on the right side if the left side is statically truthy but not plausibly a constructor or error, so 42 || foo should fail.

When `constructor-super`, `no-throw-literal`, and
`prefer-promise-reject-errors` are looking for plausible constructor or
error values, they fail to consider the short-circuiting behavior
differences between the `&&` and `||` operators. This results in a false
negative where these rules allow `foo && 42` when that expression cannot
be a constructor or error. If the left side is falsy, the expression
short-circuits with the falsy value. If the left side is truthy, the
result of the expression is the right side value. All three rules
already report the right side value in isolation but currently
incorrectly allow it when on the right side of an `&&` expression.

When @mdjermanovic added support for logical assignment operators, we
decided to ship with the corrected behavior for `&&=` by only checking
the right side of the expression, accepting that treatment of `&&=`
would be inconsistent with existing treatment of `&&`. This PR then
fixes the `&&` treatment in what we believe can be a semver-minor bug
fix.

A future improvement could detect statically falsy values on the left
side of `&&` expressions and report those as well. Such a change could
also update the `||` treatment to ignore plausibly-(constructor|error)
values on the right side if the left side is statically truthy but not
plausibly a constructor or error, so `42 || foo` should fail.
@btmills btmills added bug ESLint is working incorrectly rule Relates to ESLint's core rules core Relates to ESLint's core APIs and features evaluating The team will evaluate this issue to decide whether it meets the criteria for inclusion labels Oct 18, 2020
Comment on lines +1104 to +1105
// A future improvement could detect the left side as statically falsy, making this false.
"false && foo": true,
Copy link
Member

Choose a reason for hiding this comment

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

👍

We are already doing that in no-constant-condition for some basic cases here, and it's going to be improved by #13245.

Copy link
Member

@mdjermanovic mdjermanovic left a comment

Choose a reason for hiding this comment

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

LGTM, thanks!

lib/rules/constructor-super.js Outdated Show resolved Hide resolved
lib/rules/constructor-super.js Outdated Show resolved Hide resolved
lib/rules/utils/ast-utils.js Outdated Show resolved Hide resolved
lib/rules/utils/ast-utils.js Outdated Show resolved Hide resolved
Co-authored-by: Nicholas C. Zakas <nicholas@nczconsulting.com>
@mdjermanovic
Copy link
Member

Does everyone else agree with @mdjermanovic and me that this is can be a semver-minor bug fix?

There were no concerns expressed, so marking as accepted and merging for a minor version.

@mdjermanovic mdjermanovic added accepted There is consensus among the team that this change meets the criteria for inclusion and removed evaluating The team will evaluate this issue to decide whether it meets the criteria for inclusion labels Oct 22, 2020
@mdjermanovic mdjermanovic merged commit 0510621 into master Oct 22, 2020
@mdjermanovic mdjermanovic deleted the issue13684 branch October 22, 2020 09:18
@eslint-github-bot eslint-github-bot bot locked and limited conversation to collaborators Apr 21, 2021
@eslint-github-bot eslint-github-bot bot added the archived due to age This issue has been archived; please open a new issue for any further discussion label Apr 21, 2021
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 rule Relates to ESLint's core rules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants