Skip to content

Commit cf0cd00

Browse files
authored
Merge pull request github#3627 from asger-semmle/js/unneeded-defensive-return
Approved by erik-krogh
2 parents c39dce4 + f9b7962 commit cf0cd00

File tree

6 files changed

+62
-5
lines changed

6 files changed

+62
-5
lines changed

change-notes/1.25/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
| Uncontrolled command line (`js/command-line-injection`) | More results | This query now recognizes additional command execution calls. |
6464
| Uncontrolled data used in path expression (`js/path-injection`) | More results | This query now recognizes additional file system calls. |
6565
| Unknown directive (`js/unknown-directive`) | Fewer results | This query no longer flags directives generated by the Babel compiler. |
66+
| Unneeded defensive code (`js/unneeded-defensive-code`) | Fewer false-positive results | This query now recognizes checks meant to handle the `document.all` object. |
6667
| Unused property (`js/unused-property`) | Fewer results | This query no longer flags properties of objects that are operands of `yield` expressions. |
6768
| Zip Slip (`js/zipslip`) | More results | This query now recognizes additional vulnerabilities. |
6869

javascript/ql/src/Expressions/UnneededDefensiveProgramming.ql

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,43 @@
1313
import javascript
1414
import semmle.javascript.DefensiveProgramming
1515

16+
/**
17+
* Holds if `e` looks like a check for `document.all`, which is an unusual browser object which coerces
18+
* to `false` and has typeof `undefined`.
19+
*/
20+
predicate isFalsyObjectCheck(LogicalBinaryExpr e) {
21+
exists(Variable v |
22+
e.getAnOperand().(DefensiveExpressionTest::TypeofUndefinedTest).getOperand() = v.getAnAccess() and
23+
e.getAnOperand().(DefensiveExpressionTest::UndefinedComparison).getOperand() = v.getAnAccess()
24+
)
25+
}
26+
27+
/**
28+
* Holds if `e` is part of a check for `document.all`.
29+
*/
30+
predicate isPartOfFalsyObjectCheck(Expr e) {
31+
exists(LogicalBinaryExpr binary |
32+
isFalsyObjectCheck(binary) and
33+
e = binary.getAnOperand()
34+
)
35+
or
36+
isFalsyObjectCheck(e)
37+
}
38+
1639
from DefensiveExpressionTest e, boolean cv
1740
where
41+
not isPartOfFalsyObjectCheck(e.asExpr()) and
1842
e.getTheTestResult() = cv and
1943
// whitelist
2044
not (
2145
// module environment detection
2246
exists(VarAccess access, string name | name = "exports" or name = "module" |
23-
e.asExpr().(Internal::TypeofUndefinedTest).getOperand() = access and
47+
e.asExpr().(DefensiveExpressionTest::TypeofUndefinedTest).getOperand() = access and
2448
access.getName() = name and
2549
not exists(access.getVariable().getADeclaration())
2650
)
2751
or
2852
// too benign in practice
29-
e instanceof Internal::DefensiveInit
53+
e instanceof DefensiveExpressionTest::DefensiveInit
3054
)
3155
select e, "This guard always evaluates to " + cv + "."

javascript/ql/src/semmle/javascript/DefensiveProgramming.qll

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ abstract class DefensiveExpressionTest extends DataFlow::ValueNode {
1414
}
1515

1616
/**
17-
* INTERNAL: Do not use directly; use `DefensiveExpressionTest` instead.
17+
* Provides classes for specific kinds of defensive programming patterns.
1818
*/
19-
module Internal {
19+
module DefensiveExpressionTest {
2020
/**
2121
* A defensive truthiness check that may be worth keeping, even if it
2222
* is strictly speaking useless.
@@ -187,6 +187,13 @@ module Internal {
187187
override Expr getOperand() { result = operand }
188188
}
189189

190+
/**
191+
* Comparison against `undefined`, such as `x === undefined`.
192+
*/
193+
class UndefinedComparison extends NullUndefinedComparison {
194+
UndefinedComparison() { op2type = TTUndefined() }
195+
}
196+
190197
/**
191198
* An expression that throws an exception if one of its subexpressions evaluates to `null` or `undefined`.
192199
*
@@ -380,7 +387,7 @@ module Internal {
380387
/**
381388
* A test for `undefined` using a `typeof` expression.
382389
*
383-
* Example: `typeof x === undefined'.
390+
* Example: `typeof x === "undefined"'.
384391
*/
385392
class TypeofUndefinedTest extends UndefinedNullTest {
386393
TypeofTest test;

javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/UnneededDefensiveProgramming.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
| module-environment-detection.js:23:8:23:36 | typeof ... efined' | This guard always evaluates to true. |
2+
| regression.js:9:9:9:12 | date | This guard always evaluates to true. |
3+
| regression.js:13:25:13:40 | obj != undefined | This guard always evaluates to true. |
24
| tst2.js:4:12:4:35 | typeof ... efined" | This guard always evaluates to true. |
35
| tst.js:18:5:18:5 | u | This guard always evaluates to false. |
46
| tst.js:19:5:19:5 | n | This guard always evaluates to false. |
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
function getDate() {
2+
var date;
3+
if (something()) {
4+
date = new Date();
5+
} else {
6+
return null;
7+
}
8+
console.log(date);
9+
return date && date.getTime(); // NOT OK
10+
}
11+
12+
function isNotNullOrString(obj) {
13+
return obj != null && obj != undefined && // NOT OK
14+
typeof obj != 'string';
15+
}

javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/tst2.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,11 @@
88
}
99
});
1010
});
11+
12+
const isFalsyObject = (v) => typeof v === 'undefined' && v !== undefined; // OK
13+
14+
function f(v) {
15+
if (typeof v === 'undefined' && v !== undefined) { // OK
16+
doSomething(v);
17+
}
18+
}

0 commit comments

Comments
 (0)