Skip to content

Commit 72db219

Browse files
authored
Merge pull request github#1910 from xiemaisi/js/unused-index-variable
Approved by esben-semmle, shati-semmle
2 parents b85823b + 500cde6 commit 72db219

File tree

17 files changed

+170
-0
lines changed

17 files changed

+170
-0
lines changed

change-notes/1.23/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
| **Query** | **Tags** | **Purpose** |
1515
|---------------------------------------------------------------------------|-------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
16+
| Unused index variable (`js/unused-index-variable`) | correctness | Highlights loops that iterate over an array, but do not use the index variable to access array elements, indicating a possible typo or logic error. |
1617

1718

1819
## Changes to existing queries

javascript/config/suites/javascript/correctness-core

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
+ semmlecode-javascript-queries/LanguageFeatures/SyntaxError.ql: /Correctness/Language Features
2929
+ semmlecode-javascript-queries/LanguageFeatures/TemplateSyntaxInStringLiteral.ql: /Correctness/Language Features
3030
+ semmlecode-javascript-queries/LanguageFeatures/ThisBeforeSuper.ql: /Correctness/Language Features
31+
+ semmlecode-javascript-queries/LanguageFeatures/UnusedIndexVariable.ql: /Correctness/Language Features
3132
+ semmlecode-javascript-queries/RegExp/BackrefBeforeGroup.ql: /Correctness/Regular Expressions
3233
+ semmlecode-javascript-queries/RegExp/BackrefIntoNegativeLookahead.ql: /Correctness/Regular Expressions
3334
+ semmlecode-javascript-queries/RegExp/DuplicateCharacterInCharacterClass.ql: /Correctness/Regular Expressions

javascript/ql/src/Declarations/UnusedVariable.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44

55
import javascript
6+
import LanguageFeatures.UnusedIndexVariable
67

78
/**
89
* A local variable that is neither used nor exported, and is not a parameter
@@ -16,6 +17,8 @@ class UnusedLocal extends LocalVariable {
1617
not exists(ClassExpr ce | this = ce.getVariable()) and
1718
not exists(ExportDeclaration ed | ed.exportsAs(this, _)) and
1819
not exists(LocalVarTypeAccess type | type.getVariable() = this) and
20+
// avoid double reporting
21+
not unusedIndexVariable(_, this, _) and
1922
// common convention: variables with leading underscore are intentionally unused
2023
getName().charAt(0) != "_"
2124
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
4+
<overview>
5+
<p>
6+
If the loop variable of a <code>for</code> loop ranges over the indices of an array, that variable
7+
would normally be used as an array index in the body of the loop. If, instead, the loop body only
8+
refers to array elements at constant indices, this may indicate a logic error or leftover testing
9+
code.
10+
</p>
11+
</overview>
12+
13+
<recommendation>
14+
<p>
15+
Examine the loop carefully to ensure it is behaving as expected. You may want to consider using
16+
a <code>for</code>-<code>of</code> loop to iterate over all elements of an array without the need
17+
for error-prone index manipulations.
18+
</p>
19+
</recommendation>
20+
21+
<example>
22+
<p>
23+
The following example shows a function that is intended to sum up the elements of an array
24+
<code>xs</code>. The loop variable <code>i</code> is counted up from zero to
25+
<code>xs.length-1</code>, but instead of adding <code>xs[i]</code> to the running sum
26+
<code>res</code>, the code adds <code>xs[0]</code>, the first element of <code>xs</code>,
27+
to it, which is likely a mistake:
28+
</p>
29+
<sample src="examples/UnusedIndexVariable.js"/>
30+
<p>
31+
The problem can be fixed by adding <code>xs[i]</code> instead:
32+
</p>
33+
<sample src="examples/UnusedIndexVariableGood.js"/>
34+
<p>
35+
Alternatively, the function can be written more succinctly using a <code>for</code>-<code>of</code>
36+
loop:
37+
</p>
38+
<sample src="examples/UnusedIndexVariableGood2.js"/>
39+
</example>
40+
41+
<references>
42+
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for">for</a></li>
43+
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of">for...of</a></li>
44+
</references>
45+
</qhelp>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* @name Unused index variable
3+
* @description Iterating over an array but not using the index variable to access array elements
4+
* may indicate a typo or logic error.
5+
* @kind problem
6+
* @problem.severity warning
7+
* @id js/unused-index-variable
8+
* @precision high
9+
* @tags correctness
10+
*/
11+
12+
import javascript
13+
import UnusedIndexVariable
14+
15+
from RelationalComparison rel, Variable idx, Variable v
16+
where unusedIndexVariable(rel, idx, v)
17+
select rel, "Index variable " + idx + " is never used to access elements of " + v + "."
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Provides a predicate for identifying unused index variables in loops.
3+
*/
4+
5+
import javascript
6+
7+
/**
8+
* Holds if `arr` is of the form `base[idx]` and is nested inside loop `fs`.
9+
*/
10+
private predicate arrayIndexInLoop(IndexExpr arr, Variable base, Expr idx, ForStmt fs) {
11+
arr.getEnclosingStmt().getParentStmt*() = fs.getBody() and
12+
arr.getBase() = base.getAnAccess() and
13+
arr.getIndex() = idx
14+
}
15+
16+
/**
17+
* Gets `e` or a sub-expression `s` resulting from `e` by peeling off unary and binary
18+
* operators, increments, decrements, type assertions, parentheses, sequence expressions,
19+
* and assignments.
20+
*/
21+
private Expr unwrap(Expr e) {
22+
result = e or
23+
result = unwrap(e.(UpdateExpr).getOperand()) or
24+
result = unwrap(e.(UnaryExpr).getOperand()) or
25+
result = unwrap(e.(BinaryExpr).getAnOperand()) or
26+
result = unwrap(e.getUnderlyingValue())
27+
}
28+
29+
/**
30+
* Holds if `rel` is a for-loop condition of the form `idx <= v.length`, but all array
31+
* indices `v[c]` inside the loop body (of which there must be at least one) use a constant
32+
* index `c` instead of an index based on `idx`.
33+
*/
34+
predicate unusedIndexVariable(RelationalComparison rel, Variable idx, Variable v) {
35+
exists(ForStmt fs | fs.getTest() = rel |
36+
unwrap(rel.getLesserOperand()) = idx.getAnAccess() and
37+
rel.getGreaterOperand().(PropAccess).accesses(v.getAnAccess(), "length") and
38+
forex(IndexExpr arr, Expr e | arrayIndexInLoop(arr, v, e, fs) | exists(e.getIntValue()))
39+
)
40+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
function sum(xs) {
2+
var res = 0;
3+
for(var i=0; i<xs.length; ++i)
4+
res += xs[0]; // BAD: should be xs[i]
5+
return res;
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
function sum(xs) {
2+
var res = 0;
3+
for(var i=0; i<xs.length; ++i)
4+
res += xs[i];
5+
return res;
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
function sum(xs) {
2+
var res = 0;
3+
for(var x of xs)
4+
res += x;
5+
return res;
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
function sum(xs, i) {
2+
var res = 0;
3+
for(;i++<xs.length;) // NOT OK, but flagged by js/unused-index-variable
4+
res += xs[0];
5+
return res;
6+
}

0 commit comments

Comments
 (0)