ESLint 的非常量二进制表达式捕获的有趣错误 (2022)
Interesting Bugs Caught by ESLint's no-constant-binary-expression (2022)

原始链接: https://eslint.org/blog/2022/07/interesting-bugs-caught-by-no-constant-binary-expression/

在编程中,尤其是在 JavaScript 中,“无用代码”通常表现为无论输入如何都会始终得出 True 或 False 的表达式。 开发人员错误地创建此类表达式的一种用法是使用常量二进制表达式。 出现这些问题的原因是某些运算符(如 typeof、+、- 和 !)中的优先顺序混乱,导致错误的比较和逻辑表达式始终计算为给定值。 为了解决此类错误,作者建议使用最近引入的名为“no-constant-binary expression”的 ESLint 核心规则。 针对真实代码库运行此规则发现了许多此类错误的实例。 运算符优先级的混乱不仅仅影响常量二进制,还存在其他几个常见问题,包括比较结构的相等与引用、将空对象视为等同于 null 或 false 值,以及将赋值与调用混淆等。 尽管对某些语言概念的解释存在常见的误解,但通过自动静态分析工具识别和解决它们可以显着减少因这些混淆而导致的错误的发生。

但由于嵌套分支的潜在存在,即使 100% 分支覆盖率也不一定意味着 100% 行或语句覆盖率。 此外,100% 的分支覆盖率需要大量的重构工作,与维护未经测试或部分测试的代码相比,这会显着增加维护成本。 因此,100% 的分支覆盖率通常保留用于特定领域,例如核电站控制器,而不是通用应用程序。 尽管如此,100% 的分支覆盖率仍然是软件开发中的一个有价值的目标,因为它可以增强对系统可靠性的信心,并降低与被动维护工作相关的成本。
相关文章

原文

In ESLint v8.14.0 I contributed a new core rule called no-constant-binary-expression which has surprised me with the wide variety of subtle and interesting bugs it has been able to detect.

In this post I’ll explain what the rule does and share some examples of real bugs it has detected in popular open source projects such as Material UI, Webpack, VS Code, and Firefox as well as a few interesting bugs that it found internally at Meta. I hope these examples will convince you to try enabling the rule in the projects you work on!

What does no-constant-binary-expression do?

The rule checks for comparisons (==, !==, etc) where the outcome cannot vary at runtime, and logical expressions (&&, ??, ||) which will either always or never short-circuit.

For example:

  • +x == null will always be false, because + will coerce x into a number, and a number is never nullish.
  • { ...foo } || DEFAULT will never return DEFAULT because objects are always truthy.

Both of these are examples of expressions that look like they can affect the way the program evaluates, but in reality, do not.

This rule originally started as just an attempt to detect unnecessary null checks. However, as I worked on it, I realized useless null checks were just a special case of a broader category: useless code. Eventually it clicked for me: developers don’t intend to write useless code, and code that does not match the developer’s intent is by definition a bug. Therefore, any useless code you can detect is a bug.

This realization was confirmed for me when I ran the first version of the rule against our code base at Meta, and it detected a wide variety of subtle and interesting bugs which had made it through code review.

Real world bugs found with no-constant-binary-expressions

In this section I’ll share a number of types of bugs that this rule can catch. Each includes at least one concrete example detected in a popular open source project. My choice to include real examples here is not to shame anyone or any project, but to drive home the fact that these are errors that any team can easily make.

Confusing operator precedence

The most common class of bug the rule finds is places where developers misunderstood the precedence of operators, particularly unary operators like !, + and typeof.

if (!whitelist.has(specifier.imported.name) == null) {
return;
}

From Material UI (also: VS Code 1, 2, Webpack, Mozilla)

Confusing ?? and || precedence

When trying to define default values, people get confused with expressions like a === b ?? c and assume it will be parsed as a === (b ?? c). When in actuality it will be parsed as (a === b) ?? c.

shouldShowWelcome() {
return this.viewModel?.welcomeExperience === WelcomeExperience.ForWorkspace ?? true;
}

From VS Code

Aside: Observing how frequently developers get confused by operator precedence inspired me to experiment with a VS Code extension to visually clarify how precedence gets interpreted.

Expecting objects to be compared by value

Developers coming from other languages where structures are compared by value, rather than by reference, can easily fall into the trap of thinking they can do things like test if an object is empty by comparing with a newly created empty object. Or course in JavaScript, objects are compared by reference, and no value can ever be equal to a newly constructed object literal.

In this example, hasData will always be set to true because data can never be referentially equal to a newly created object.

hasData = hasData || data !== {};

From Firefox (also: Firefox)

Expecting empty objects to be false or null

Another common categrory of JavaScript error is expecting empty objects to be nullish or falsy. This is likely an easy mistake to make for folks coming from a language like Python where empty lists and dictionaries are falsy.

const newConfigValue = { ...configProfiles } ?? {};

From VS Code (also: VS Code 1, 2)

Is it >= or =>?

I’ve only seen this particular typo once, but I wanted to include it because it’s a great example of the unexpected types of bugs this rule can catch.

Here, the developer meant to test if a value was greater than or equal to zero (>= 0), but accidentally reversed the order of the characters and created an arrow function that returned 0 && startWidth !

assert(startWidth => 0 && startWidth  1);

From Mozilla

Other errors caught by no-constant-binary-expression

The above five categories of errors are not exhaustive. When I originally ran the first version of this rule on our (very) large monorepo at Meta, it found over 500 issues. While many fell into the categories outlined above, there was also a long-tail of other interesting bugs. Some highlights include:

  • Thinking || allows for set operations: states.includes('VALID' || 'IN_PROGRESS')
  • Thinking primitive functions pass through nulls: Number(x) == null
  • Not knowing primitive constructors return boxed primitives: new Number(x) === 10

I never would have set out to lint for these specific issues individually, but by simply trying to identify anything “useless” we were able to find and correct them.

Conclusion

As you’ve now seen no-constant-binary-expression is capable of detecting a variety of different types of bugs. The rule accomplishes this not because it’s programmed to look for those specific issues, but because all those bugs have one thing in common: they manifest as useless code. Because developers generally don’t intend to write useless code, detecting useless code generally results in detecting bugs.

If you’ve found these examples compelling, please consider enabling no-constant-binary-expression in your ESLint config:


module.exports = {
rules: {

"no-constant-binary-expression": "error"
}
}

If you do, and it finds bugs, I’d love to hear about them!

Thanks to Brad Zacher for the original observation which inspired this work and the suggestion to propose it as a new core rule. And thanks to Milos Djermanovic for significant contributions during code review.

联系我们 contact @ memedata.com