mirror of
https://github.com/github/codeql.git
synced 2026-05-03 04:39:29 +02:00
Merge pull request #260 from xiemaisi/js/confusing-precedence
Approved by esben-semmle, mc-semmle
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Nested expressions that rely on less well-known operator precedence rules can be
|
||||
hard to read and understand. They could even indicate a bug where the author of the
|
||||
code misunderstood the precedence rules.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Use parentheses or additional whitespace to clarify grouping.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
Consider the following snippet of code:
|
||||
</p>
|
||||
|
||||
<sample src="examples/UnclearOperatorPrecedence.js" />
|
||||
|
||||
<p>
|
||||
It might look like this tests whether <code>x</code> and <code>y</code> have any bits in
|
||||
common, but in fact <code>==</code> binds more tightly than <code>&</code>, so the test
|
||||
is equivalent to <code>x & (y == 0)</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If this is the intended interpretation, parentheses should be used to clarify this. You could
|
||||
also consider adding extra whitespace around <code>&</code> or removing whitespace
|
||||
around <code>==</code> to make it visually apparent that it binds less tightly:
|
||||
<code>x & y==0</code>.
|
||||
</p>
|
||||
<p>
|
||||
Probably the best approach in this case, though, would be to use the <code>&&</code>
|
||||
operator instead to clarify the intended interpretation: <code>x && y == 0</code>.
|
||||
</p>
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>Mozilla Developer Network, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence">Operator precedence</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
29
javascript/ql/src/Expressions/UnclearOperatorPrecedence.ql
Normal file
29
javascript/ql/src/Expressions/UnclearOperatorPrecedence.ql
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @name Unclear precedence of nested operators
|
||||
* @description Nested expressions involving binary bitwise operators and comparisons are easy
|
||||
* to misunderstand without additional disambiguating parentheses or whitespace.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id js/unclear-operator-precedence
|
||||
* @tags maintainability
|
||||
* correctness
|
||||
* statistical
|
||||
* non-attributable
|
||||
* external/cwe/cwe-783
|
||||
* @precision very-high
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
from BitwiseBinaryExpr bit, Comparison rel, Expr other
|
||||
where bit.hasOperands(rel, other) and
|
||||
// only flag if whitespace doesn't clarify the nesting (note that if `bit` has less
|
||||
// whitespace than `rel`, it will be reported by `js/whitespace-contradicts-precedence`)
|
||||
bit.getWhitespaceAroundOperator() = rel.getWhitespaceAroundOperator() and
|
||||
// don't flag if the other operand is itself a comparison,
|
||||
// since the nesting tends to be visually more obvious in such cases
|
||||
not other instanceof Comparison and
|
||||
// don't flag occurrences in minified code
|
||||
not rel.getTopLevel().isMinified()
|
||||
select rel, "The '" + rel.getOperator() + "' operator binds more tightly than " +
|
||||
"'" + bit.getOperator() + "', which may not be obvious in this case."
|
||||
@@ -54,29 +54,6 @@ class HarmlessNestedExpr extends BinaryExpr {
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if the right operand of `expr` starts on line `line`, at column `col`. */
|
||||
predicate startOfBinaryRhs(BinaryExpr expr, int line, int col) {
|
||||
exists(Location rloc | rloc = expr.getRightOperand().getLocation() |
|
||||
rloc.getStartLine() = line and rloc.getStartColumn() = col
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the left operand of `expr` ends on line `line`, at column `col`. */
|
||||
predicate endOfBinaryLhs(BinaryExpr expr, int line, int col) {
|
||||
exists(Location lloc | lloc = expr.getLeftOperand().getLocation() |
|
||||
lloc.getEndLine() = line and lloc.getEndColumn() = col
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the number of whitespace characters around the operator of `expr`. */
|
||||
int operatorWS(BinaryExpr expr) {
|
||||
exists(int line, int lcol, int rcol |
|
||||
endOfBinaryLhs(expr, line, lcol) and
|
||||
startOfBinaryRhs(expr, line, rcol) and
|
||||
result = rcol - lcol + 1 - expr.getOperator().length()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `inner` is an operand of `outer`, and the relative precedence
|
||||
* may not be immediately clear, but is important for the semantics of
|
||||
@@ -88,10 +65,8 @@ predicate interestingNesting(BinaryExpr inner, BinaryExpr outer) {
|
||||
not inner instanceof HarmlessNestedExpr
|
||||
}
|
||||
|
||||
from BinaryExpr inner, BinaryExpr outer, int wsouter, int wsinner
|
||||
from BinaryExpr inner, BinaryExpr outer
|
||||
where interestingNesting(inner, outer) and
|
||||
wsinner = operatorWS(inner) and wsouter = operatorWS(outer) and
|
||||
wsinner % 2 = 0 and wsouter % 2 = 0 and
|
||||
wsinner > wsouter and
|
||||
inner.getWhitespaceAroundOperator() > outer.getWhitespaceAroundOperator() and
|
||||
not outer.getTopLevel().isMinified()
|
||||
select outer, "Whitespace around nested operators contradicts precedence."
|
||||
@@ -0,0 +1,3 @@
|
||||
if (x & y == 0) {
|
||||
// ...
|
||||
}
|
||||
@@ -1008,6 +1008,25 @@ class BinaryExpr extends @binaryexpr, Expr {
|
||||
override ControlFlowNode getFirstControlFlowNode() {
|
||||
result = getLeftOperand().getFirstControlFlowNode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of whitespace characters around the operator of this expression.
|
||||
*
|
||||
* This predicate is only defined if both operands are on the same line, and if the
|
||||
* amount of whitespace before and after the operator are the same.
|
||||
*/
|
||||
int getWhitespaceAroundOperator() {
|
||||
exists (Token lastLeft, Token operator, Token firstRight, int l, int c1, int c2, int c3, int c4 |
|
||||
lastLeft = getLeftOperand().getLastToken() and
|
||||
operator = lastLeft.getNextToken() and
|
||||
firstRight = operator.getNextToken() and
|
||||
lastLeft.getLocation().hasLocationInfo(_, _, _, l, c1) and
|
||||
operator.getLocation().hasLocationInfo(_, l, c2, l, c3) and
|
||||
firstRight.getLocation().hasLocationInfo(_, l, c4, _, _) and
|
||||
result = c2-c1-1 and
|
||||
result = c4-c3-1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user