Skip to content

Commit ea3560f

Browse files
committed
JS: Ignore document.all checks explicitly
1 parent 696d19c commit ea3560f

File tree

3 files changed

+44
-5
lines changed

3 files changed

+44
-5
lines changed

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/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)