Skip to content

Commit dfcf767

Browse files
author
Max Schaefer
authored
Merge pull request #440 from asger-semmle/range-analysis
JS: Range analysis for dead code detection
2 parents dbeb2df + 3ed40d5 commit dfcf767

File tree

21 files changed

+984
-3
lines changed

21 files changed

+984
-3
lines changed

change-notes/1.20/analysis-javascript.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44

55
## New queries
66

7-
| **Query** | **Tags** | **Purpose** |
8-
|-----------|----------|-------------|
9-
| | | |
7+
| **Query** | **Tags** | **Purpose** |
8+
|-----------------------------------------------|------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
9+
| Useless comparison test | correctness | Highlights code that is unreachable due to a numeric comparison that is always true or always false. |
10+
1011

1112
## Changes to existing queries
1213

javascript/config/suites/javascript/correctness-more

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@
1515
+ semmlecode-javascript-queries/Statements/ReturnOutsideFunction.ql: /Correctness/Statements
1616
+ semmlecode-javascript-queries/Statements/SuspiciousUnusedLoopIterationVariable.ql: /Correctness/Statements
1717
+ semmlecode-javascript-queries/Statements/UselessConditional.ql: /Correctness/Statements
18+
+ semmlecode-javascript-queries/Statements/UselessComparisonTest.ql: /Correctness/Statements
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
If a condition always evaluates to true or always evaluates to false, this often indicates
8+
incomplete code or a latent bug, and it should be examined carefully.
9+
</p>
10+
11+
</overview>
12+
<recommendation>
13+
14+
<p>
15+
Examine the surrounding code to determine why the condition is redundant. If it is no
16+
longer needed, remove it.
17+
</p>
18+
19+
<p>
20+
If the check is needed to guard against <code>NaN</code> values, insert a comment explaning the possibility of <code>NaN</code>.
21+
</p>
22+
23+
</recommendation>
24+
<example>
25+
26+
<p>
27+
The following example finds the index of an element in a given slice of the array:
28+
</p>
29+
30+
<sample src="examples/UselessComparisonTest.js" />
31+
32+
<p>
33+
The condition <code>i &lt; end</code> at the end is always false, however. The code can be clarified if the
34+
redundant condition is removed:
35+
</p>
36+
37+
<sample src="examples/UselessComparisonTestGood.js" />
38+
39+
</example>
40+
<references>
41+
42+
43+
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Glossary/Truthy">Truthy</a>,
44+
<a href="https://developer.mozilla.org/en-US/docs/Glossary/Falsy">Falsy</a>.</li>
45+
46+
</references>
47+
</qhelp>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* @name Useless comparison test
3+
* @description A comparison that always evaluates to true or always evaluates to false may
4+
* indicate faulty logic and dead code.
5+
* @kind problem
6+
* @problem.severity warning
7+
* @id js/useless-comparison-test
8+
* @tags correctness
9+
* @precision high
10+
*/
11+
12+
import javascript
13+
14+
/**
15+
* Holds if there are any contradictory guard nodes in `container`.
16+
*
17+
* We use this to restrict reachability analysis to a small set of containers.
18+
*/
19+
predicate hasContradictoryGuardNodes(StmtContainer container) {
20+
exists (ConditionGuardNode guard |
21+
RangeAnalysis::isContradictoryGuardNode(guard) and
22+
container = guard.getContainer())
23+
}
24+
25+
/**
26+
* Holds if `block` is reachable and is in a container with contradictory guard nodes.
27+
*/
28+
predicate isReachable(BasicBlock block) {
29+
exists (StmtContainer container |
30+
hasContradictoryGuardNodes(container) and
31+
block = container.getEntryBB())
32+
or
33+
isReachable(block.getAPredecessor()) and
34+
not RangeAnalysis::isContradictoryGuardNode(block.getANode())
35+
}
36+
37+
/**
38+
* Holds if `block` is unreachable, but could be reached if `guard` was not contradictory.
39+
*/
40+
predicate isBlockedByContradictoryGuardNodes(BasicBlock block, ConditionGuardNode guard) {
41+
RangeAnalysis::isContradictoryGuardNode(guard) and
42+
isReachable(block.getAPredecessor()) and // the guard itself is reachable
43+
block = guard.getBasicBlock()
44+
or
45+
isBlockedByContradictoryGuardNodes(block.getAPredecessor(), guard) and
46+
not isReachable(block)
47+
}
48+
49+
/**
50+
* Holds if the given guard node is contradictory and causes an expression or statement to be unreachable.
51+
*/
52+
predicate isGuardNodeWithDeadCode(ConditionGuardNode guard) {
53+
exists (BasicBlock block |
54+
isBlockedByContradictoryGuardNodes(block, guard) and
55+
block.getANode() instanceof ExprOrStmt)
56+
}
57+
58+
from ConditionGuardNode guard
59+
where isGuardNodeWithDeadCode(guard)
60+
select guard.getTest(), "The condition '" + guard.getTest() + "' is always " + guard.getOutcome().booleanNot() + "."
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
function findValue(values, x, start, end) {
2+
let i;
3+
for (i = start; i < end; ++i) {
4+
if (values[i] === x) {
5+
return i;
6+
}
7+
}
8+
if (i < end) {
9+
return i;
10+
}
11+
return -1;
12+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
function findValue(values, x, start, end) {
2+
for (let i = start; i < end; ++i) {
3+
if (values[i] === x) {
4+
return i;
5+
}
6+
}
7+
return -1;
8+
}

javascript/ql/src/javascript.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import semmle.javascript.NPM
3636
import semmle.javascript.Paths
3737
import semmle.javascript.Promises
3838
import semmle.javascript.CanonicalNames
39+
import semmle.javascript.RangeAnalysis
3940
import semmle.javascript.Regexp
4041
import semmle.javascript.SSA
4142
import semmle.javascript.StandardLibrary

0 commit comments

Comments
 (0)