Skip to content

Commit a499009

Browse files
author
Max Schaefer
authored
Merge pull request #395 from esben-semmle/js/useless-defensive-code
JS: add query: js/useless-defensive-code
2 parents 4fdfbb7 + 5666dea commit a499009

21 files changed

+905
-24
lines changed

change-notes/1.19/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
| Replacement of a substring with itself (`js/identity-replacement`) | correctness, security, external/cwe/cwe-116 | Highlights string replacements that replace a string with itself, which usually indicates a mistake. Results shown on LGTM by default. |
2424
| Stored cross-site scripting (`js/stored-xss`) | security, external/cwe/cwe-079, external/cwe/cwe-116 | Highlights uncontrolled stored values flowing into HTML content, indicating a violation of [CWE-079](https://cwe.mitre.org/data/definitions/79.html). Results shown on LGTM by default. |
2525
| Unclear precedence of nested operators (`js/unclear-operator-precedence`) | maintainability, correctness, external/cwe/cwe-783 | Highlights nested binary operators whose relative precedence is easy to misunderstand. Results shown on LGTM by default. |
26+
| Unneeded defensive code | correctness, external/cwe/cwe-570, external/cwe/cwe-571 | Highlights locations where defensive code is not needed. Results are shown on LGTM by default. |
2627
| Useless assignment to property | maintainability | Highlights property assignments whose value is always overwritten. Results are shown on LGTM by default. |
2728
| User-controlled data in file | security, external/cwe/cwe-912 | Highlights locations where user-controlled data is written to a file. Results are not shown on LGTM by default. |
2829

javascript/config/suites/javascript/maintainability-more

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
+ semmlecode-javascript-queries/Declarations/DuplicateVarDecl.ql: /Maintainability/Declarations
77
+ semmlecode-javascript-queries/Declarations/UnusedParameter.ql: /Maintainability/Declarations
88
+ semmlecode-javascript-queries/Declarations/UnusedVariable.ql: /Maintainability/Declarations
9+
+ semmlecode-javascript-queries/Expressions/UselessDefensiveProgramming.ql: /Maintainability/Expressions
910
+ semmlecode-javascript-queries/LanguageFeatures/ArgumentsCallerCallee.ql: /Maintainability/Language Features
1011
+ semmlecode-javascript-queries/LanguageFeatures/ConditionalComments.ql: /Maintainability/Language Features
1112
+ semmlecode-javascript-queries/LanguageFeatures/Eval.ql: /Maintainability/Language Features

javascript/ql/src/Expressions/HeterogeneousComparison.ql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import javascript
1717
private import semmle.javascript.dataflow.InferredTypes
18+
private import semmle.javascript.DefensiveProgramming
1819

1920
/**
2021
* Holds if `left` and `right` are the left and right operands, respectively, of `nd`, which is
@@ -198,6 +199,7 @@ from ASTNode cmp,
198199
int leftTypeCount, int rightTypeCount ,
199200
string leftTypeDescription, string rightTypeDescription
200201
where isHeterogeneousComparison(cmp, left, right, leftTypes, rightTypes) and
202+
not exists (cmp.(Expr).flow().(DefensiveExpressionTest).getTheTestResult()) and
201203
not whitelist(left.asExpr()) and
202204
not whitelist(right.asExpr()) and
203205
leftExprDescription = capitalize(getDescription(left.asExpr(), "this expression")) and
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
8+
Defensive code can prevent unforeseen circumstances from
9+
causing fatal program behaviors.
10+
11+
A common defensive code pattern is to guard
12+
against dereferencing the values <code>null</code> or
13+
<code>undefined</code>.
14+
15+
However, if the situation that some defensive code guards
16+
against never can occur, then the defensive code serves no purpose and
17+
can safely be removed.
18+
19+
</p>
20+
21+
</overview>
22+
<recommendation>
23+
24+
<p>
25+
26+
Examine the surrounding code to determine if the defensive
27+
code is worth keeping despite providing no practical use. If it is no
28+
longer needed, remove it.
29+
30+
</p>
31+
32+
</recommendation>
33+
<example>
34+
35+
<p>
36+
37+
The following example shows a <code>cleanupLater</code>
38+
function that asynchronously will perform a cleanup task after some
39+
delay. When the cleanup task completes, the function invokes the
40+
provided callback parameter <code>cb</code>.
41+
42+
To prevent a crash by invoking <code>cb</code> when it
43+
has the value <code>undefined</code>, defensive code guards
44+
the invocation by checking if <code>cb</code> is truthy.
45+
46+
</p>
47+
48+
<sample src="examples/UnneededDefensiveProgramming1_bad.js" />
49+
50+
<p>
51+
52+
However, the <code>cleanupLater</code> function is always
53+
invoked with a callback argument, so the defensive code condition
54+
always holds, and it is therefore not
55+
required. The function can therefore be simplified to:
56+
57+
</p>
58+
59+
<sample src="examples/UnneededDefensiveProgramming1_good.js" />
60+
61+
<p>
62+
63+
Guarding against the same situation multiple times is
64+
another example of defensive code that provides no practical use. The
65+
example below shows a function that assigns a value to a property of
66+
an object, where defensive code ensures that the assigned value is not
67+
<code>undefined</code> or <code>null</code>.
68+
69+
</p>
70+
71+
<sample src="examples/UnneededDefensiveProgramming2_bad.js" />
72+
73+
<p>
74+
75+
However, due to coercion rules, <code>v ==
76+
undefined</code> holds for both the situation where <code>v</code>
77+
is<code>undefined</code> and the situation where <code>v</code>
78+
is<code>null</code>, so the <code>v == null</code>
79+
guard serves no purpose, and can be removed:
80+
81+
</p>
82+
83+
<sample src="examples/UnneededDefensiveProgramming2_good.js" />
84+
85+
</example>
86+
<references>
87+
88+
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Defensive_programming">Defensive programming</a>.</li>
89+
90+
</references>
91+
</qhelp>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* @name Unneeded defensive code
3+
* @description Defensive code that guards against a situation that never happens is not needed.
4+
* @kind problem
5+
* @problem.severity recommendation
6+
* @id js/unneeded-defensive-code
7+
* @tags correctness
8+
* external/cwe/cwe-570
9+
* external/cwe/cwe-571
10+
* @precision very-high
11+
*/
12+
13+
import javascript
14+
import semmle.javascript.DefensiveProgramming
15+
16+
from DefensiveExpressionTest e, boolean cv
17+
where e.getTheTestResult() = cv and
18+
// whitelist
19+
not (
20+
// module environment detection
21+
exists (VarAccess access, string name |
22+
name = "exports" or name = "module" |
23+
e.asExpr().(Internal::TypeofUndefinedTest).getOperand() = access and
24+
access.getName() = name and
25+
not exists (access.getVariable().getADeclaration())
26+
)
27+
or
28+
// too benign in practice
29+
e instanceof Internal::DefensiveInit
30+
)
31+
select e, "This guard always evaluates to " + cv + "."
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
function cleanupLater(delay, cb) {
2+
setTimeout(function() {
3+
cleanup();
4+
if (cb) { // BAD: useless check, `cb` is always truthy
5+
cb();
6+
}
7+
}, delay)
8+
}
9+
10+
cleanupLater(1000, function(){console.log("Cleanup done")});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
function cleanupLater(delay, cb) {
2+
setTimeout(function() {
3+
cleanupNow();
4+
// GOOD: no need to guard the invocation
5+
cb();
6+
}, delay)
7+
}
8+
9+
cleanupLater(function(){console.log("Cleanup done")});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
function setSafeStringProp(o, prop, v) {
2+
// BAD: `v == null` is useless
3+
var safe = v == undefined || v == null? '': v;
4+
o[prop] = safe;
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
function setSafeStringProp(o, prop, v) {
2+
// GOOD: `v == undefined` handles both `undefined` and `null`
3+
var safe = v == undefined? '': v;
4+
o[prop] = safe;
5+
}

javascript/ql/src/Statements/UselessConditional.ql

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,7 @@
1515
import javascript
1616
import semmle.javascript.RestrictedLocations
1717
import semmle.javascript.dataflow.Refinements
18-
19-
/**
20-
* Holds if `va` is a defensive truthiness check that may be worth keeping, even if it
21-
* is strictly speaking useless.
22-
*
23-
* We currently recognize three patterns:
24-
*
25-
* - the first `x` in `x || (x = e)`
26-
* - the second `x` in `x = (x || e)`
27-
* - the second `x` in `var x = x || e`
28-
*/
29-
predicate isDefensiveInit(VarAccess va) {
30-
exists (LogOrExpr o, VarRef va2 |
31-
va = o.getLeftOperand().getUnderlyingReference() and va2.getVariable() = va.getVariable() |
32-
exists (AssignExpr assgn | va2 = assgn.getTarget() |
33-
assgn = o.getRightOperand().stripParens() or
34-
o = assgn.getRhs().getUnderlyingValue()
35-
) or
36-
exists (VariableDeclarator vd | va2 = vd.getBindingPattern() |
37-
o = vd.getInit().getUnderlyingValue()
38-
)
39-
)
40-
}
18+
import semmle.javascript.DefensiveProgramming
4119

4220
/**
4321
* Holds if variable `v` looks like a symbolic constant, that is, it is assigned
@@ -95,6 +73,21 @@ predicate isConstantBooleanReturnValue(Expr e) {
9573
isConstantBooleanReturnValue(e.(LogNotExpr).getOperand())
9674
}
9775

76+
private Expr maybeStripLogNot(Expr e) {
77+
result = maybeStripLogNot(e.(LogNotExpr).getOperand()) or
78+
result = e
79+
}
80+
81+
/**
82+
* Holds if `e` is a defensive expression with a fixed outcome.
83+
*/
84+
predicate isConstantDefensive(Expr e) {
85+
exists(DefensiveExpressionTest defensive |
86+
maybeStripLogNot(defensive.asExpr()) = e and
87+
exists(defensive.getTheTestResult())
88+
)
89+
}
90+
9891
/**
9992
* Holds if `e` is an expression that should not be flagged as a useless condition.
10093
*
@@ -109,7 +102,7 @@ predicate isConstantBooleanReturnValue(Expr e) {
109102
predicate whitelist(Expr e) {
110103
isConstant(e) or
111104
isConstant(e.(LogNotExpr).getOperand()) or
112-
isDefensiveInit(e) or
105+
isConstantDefensive(e) or // flagged by js/useless-defensive-code
113106
isInitialParameterUse(e) or
114107
isConstantBooleanReturnValue(e)
115108
}

0 commit comments

Comments
 (0)