From c2fb14640e66aaf0b97b9a39d424071e5fa92806 Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Thu, 27 Sep 2018 15:01:46 +0200
Subject: [PATCH 01/20] JS: move isDefensiveInit to DefensiveProgramming.qll
---
.../ql/src/Statements/UselessConditional.ql | 26 ++----------------
.../javascript/DefensiveProgramming.qll | 27 +++++++++++++++++++
2 files changed, 29 insertions(+), 24 deletions(-)
create mode 100644 javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
diff --git a/javascript/ql/src/Statements/UselessConditional.ql b/javascript/ql/src/Statements/UselessConditional.ql
index 6e71d6827134..5de56e292eec 100644
--- a/javascript/ql/src/Statements/UselessConditional.ql
+++ b/javascript/ql/src/Statements/UselessConditional.ql
@@ -15,29 +15,7 @@
import javascript
import semmle.javascript.RestrictedLocations
import semmle.javascript.dataflow.Refinements
-
-/**
- * Holds if `va` is a defensive truthiness check that may be worth keeping, even if it
- * is strictly speaking useless.
- *
- * We currently recognize three patterns:
- *
- * - the first `x` in `x || (x = e)`
- * - the second `x` in `x = (x || e)`
- * - the second `x` in `var x = x || e`
- */
-predicate isDefensiveInit(VarAccess va) {
- exists (LogOrExpr o, VarRef va2 |
- va = o.getLeftOperand().getUnderlyingReference() and va2.getVariable() = va.getVariable() |
- exists (AssignExpr assgn | va2 = assgn.getTarget() |
- assgn = o.getRightOperand().stripParens() or
- o = assgn.getRhs().getUnderlyingValue()
- ) or
- exists (VariableDeclarator vd | va2 = vd.getBindingPattern() |
- o = vd.getInit().getUnderlyingValue()
- )
- )
-}
+import semmle.javascript.DefensiveProgramming
/**
* Holds if variable `v` looks like a symbolic constant, that is, it is assigned
@@ -109,7 +87,7 @@ predicate isConstantBooleanReturnValue(Expr e) {
predicate whitelist(Expr e) {
isConstant(e) or
isConstant(e.(LogNotExpr).getOperand()) or
- isDefensiveInit(e) or
+ e.flow() instanceof DefensiveInit or
isInitialParameterUse(e) or
isConstantBooleanReturnValue(e)
}
diff --git a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
new file mode 100644
index 000000000000..2bd4f80bede1
--- /dev/null
+++ b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
@@ -0,0 +1,27 @@
+import javascript
+
+/**
+ * A defensive truthiness check that may be worth keeping, even if it
+ * is strictly speaking useless.
+ *
+ * We currently recognize three patterns:
+ *
+ * - the first `x` in `x || (x = e)`
+ * - the second `x` in `x = (x || e)`
+ * - the second `x` in `var x = x || e`
+ */
+class DefensiveInit extends DataFlow::ValueNode {
+ DefensiveInit() {
+ exists(VarAccess va, LogOrExpr o, VarRef va2 |
+ va = astNode and
+ va = o.getLeftOperand().stripParens() and va2.getVariable() = va.getVariable() |
+ exists(AssignExpr assgn | va2 = assgn.getTarget() |
+ assgn = o.getRightOperand().stripParens() or
+ o = assgn.getRhs().stripParens()
+ )
+ or
+ exists(VariableDeclarator vd | va2 = vd.getBindingPattern() | o = vd.getInit().stripParens())
+ )
+ }
+
+}
\ No newline at end of file
From a5eeba3c3a4996e9f76b3b008f754685c92948a6 Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Wed, 10 Oct 2018 14:45:06 +0200
Subject: [PATCH 02/20] JS: prepare DefensiveProgramming.qll for additions
---
.../ql/src/Statements/UselessConditional.ql | 2 +-
.../javascript/DefensiveProgramming.qll | 60 +++++++++++++------
2 files changed, 42 insertions(+), 20 deletions(-)
diff --git a/javascript/ql/src/Statements/UselessConditional.ql b/javascript/ql/src/Statements/UselessConditional.ql
index 5de56e292eec..06e6e8601b91 100644
--- a/javascript/ql/src/Statements/UselessConditional.ql
+++ b/javascript/ql/src/Statements/UselessConditional.ql
@@ -87,7 +87,7 @@ predicate isConstantBooleanReturnValue(Expr e) {
predicate whitelist(Expr e) {
isConstant(e) or
isConstant(e.(LogNotExpr).getOperand()) or
- e.flow() instanceof DefensiveInit or
+ e.flow() instanceof Internal::DefensiveInit or
isInitialParameterUse(e) or
isConstantBooleanReturnValue(e)
}
diff --git a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
index 2bd4f80bede1..a14988661081 100644
--- a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
+++ b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
@@ -1,27 +1,49 @@
+/**
+ * Provides classes for working with defensive programming patterns.
+ */
+
import javascript
+private import semmle.javascript.dataflow.InferredTypes
/**
- * A defensive truthiness check that may be worth keeping, even if it
- * is strictly speaking useless.
- *
- * We currently recognize three patterns:
- *
- * - the first `x` in `x || (x = e)`
- * - the second `x` in `x = (x || e)`
- * - the second `x` in `var x = x || e`
+ * The test in a defensive programming pattern.
*/
-class DefensiveInit extends DataFlow::ValueNode {
- DefensiveInit() {
- exists(VarAccess va, LogOrExpr o, VarRef va2 |
- va = astNode and
- va = o.getLeftOperand().stripParens() and va2.getVariable() = va.getVariable() |
- exists(AssignExpr assgn | va2 = assgn.getTarget() |
- assgn = o.getRightOperand().stripParens() or
- o = assgn.getRhs().stripParens()
+abstract class DefensiveExpression extends DataFlow::ValueNode {
+ /** Gets the unique Boolean value that this test evaluates to, if any. */
+ abstract boolean getTheTestResult();
+}
+
+/**
+ * INTERNAL: Do not use directly; use `DefensiveExpression` instead.
+ */
+module Internal {
+ /**
+ * A defensive truthiness check that may be worth keeping, even if it
+ * is strictly speaking useless.
+ *
+ * We currently recognize three patterns:
+ *
+ * - the first `x` in `x || (x = e)`
+ * - the second `x` in `x = (x || e)`
+ * - the second `x` in `var x = x || e`
+ */
+ class DefensiveInit extends DefensiveExpression {
+ DefensiveInit() {
+ exists(VarAccess va, LogOrExpr o, VarRef va2 |
+ va = astNode and
+ va = o.getLeftOperand().stripParens() and va2.getVariable() = va.getVariable() |
+ exists(AssignExpr assgn | va2 = assgn.getTarget() |
+ assgn = o.getRightOperand().stripParens() or
+ o = assgn.getRhs().stripParens()
+ )
+ or
+ exists(VariableDeclarator vd | va2 = vd.getBindingPattern() | o = vd.getInit().stripParens())
)
- or
- exists(VariableDeclarator vd | va2 = vd.getBindingPattern() | o = vd.getInit().stripParens())
- )
+ }
+
+ override boolean getTheTestResult() {
+ result = analyze().getTheBooleanValue()
+ }
}
}
\ No newline at end of file
From 8086e88587d5b9c333b4f988862c881796f16e85 Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Wed, 10 Oct 2018 14:53:26 +0200
Subject: [PATCH 03/20] JS: add utilities to DefensiveProgramming.qll
---
javascript/ql/src/semmle/javascript/DefensiveProgramming.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
index a14988661081..9c4a975dbcc5 100644
--- a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
+++ b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
@@ -6,7 +6,7 @@ import javascript
private import semmle.javascript.dataflow.InferredTypes
/**
- * The test in a defensive programming pattern.
+ * A test in a defensive programming pattern.
*/
abstract class DefensiveExpression extends DataFlow::ValueNode {
/** Gets the unique Boolean value that this test evaluates to, if any. */
From 2b6ef24bc2ce74436ef5fc9eedd80e8642550109 Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Wed, 10 Oct 2018 14:53:26 +0200
Subject: [PATCH 04/20] JS: add utilities to DefensiveProgramming.qll
---
.../javascript/DefensiveProgramming.qll | 82 +++++++++++++++++++
1 file changed, 82 insertions(+)
diff --git a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
index 9c4a975dbcc5..7c6baa87256e 100644
--- a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
+++ b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
@@ -46,4 +46,86 @@ module Internal {
}
}
+ /**
+ * Gets the inner expression of `e`, with any surrounding parentheses and boolean nots removed.
+ * `polarity` is true iff the inner expression is nested in an even number of negations.
+ */
+ private Expr stripNotsAndParens(Expr e, boolean polarity) {
+ exists (Expr inner |
+ inner = e.stripParens() |
+ if inner instanceof LogNotExpr then
+ (result = stripNotsAndParens(inner.(LogNotExpr).getOperand(), polarity.booleanNot()))
+ else
+ (result = inner and polarity = true)
+ )
+ }
+
+ /**
+ * An equality test for `null` and `undefined`.
+ */
+ private abstract class UndefinedNullTest extends EqualityTest {
+ /** Gets the unique Boolean value that this test evaluates to, if any. */
+ abstract boolean getTheTestResult();
+
+ /**
+ * Gets the expression that is tested for being `null` or `undefined`.
+ */
+ abstract Expr getOperand();
+ }
+
+ /**
+ * A dis- or conjunction that tests if an expression is `null` or `undefined` in either branch.
+ */
+ private class CompositeUndefinedNullTestPart extends DefensiveExpression {
+
+ UndefinedNullTest test;
+
+ boolean polarity;
+
+ CompositeUndefinedNullTestPart(){
+ exists (BinaryExpr composite, Variable v, Expr op, Expr opOther, UndefinedNullTest testOther |
+ composite instanceof LogAndExpr or
+ composite instanceof LogOrExpr |
+ composite.hasOperands(op, opOther) and
+ this = op.flow() and
+ test = stripNotsAndParens(op, polarity) and
+ testOther = stripNotsAndParens(opOther, _) and
+ test.getOperand().(VarRef).getVariable() = v and
+ testOther.getOperand().(VarRef).getVariable() = v
+ )
+ }
+
+ override boolean getTheTestResult() {
+ polarity = true and result = test.getTheTestResult()
+ or
+ polarity = false and result = test.getTheTestResult().booleanNot()
+ }
+
+ }
+
+ /**
+ * A test for `undefined` or `null` in an if-statement.
+ */
+ private class SanityCheckingUndefinedNullGuard extends DefensiveExpression {
+
+ UndefinedNullTest test;
+
+ boolean polarity;
+
+ SanityCheckingUndefinedNullGuard() {
+ exists (IfStmt c |
+ this = c.getCondition().flow() and
+ test = stripNotsAndParens(c.getCondition(), polarity) and
+ test.getOperand() instanceof VarRef
+ )
+ }
+
+ override boolean getTheTestResult() {
+ polarity = true and result = test.getTheTestResult()
+ or
+ polarity = false and result = test.getTheTestResult().booleanNot()
+ }
+
+ }
+
}
\ No newline at end of file
From a2ecf408789bbe9e37ae30452c9f766fd479b4ef Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Wed, 10 Oct 2018 14:58:10 +0200
Subject: [PATCH 05/20] JS: recognize defensive expressions for null/undefined
---
.../javascript/DefensiveProgramming.qll | 56 +++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
index 7c6baa87256e..515f03d45b0e 100644
--- a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
+++ b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
@@ -128,4 +128,60 @@ module Internal {
}
+ /**
+ * Holds if `t` is `null` or `undefined`.
+ */
+ private predicate isNullOrUndefined(InferredType t) {
+ t = TTNull() or
+ t = TTUndefined()
+ }
+
+ /**
+ * Holds if `t` is not `null` or `undefined`.
+ */
+ private predicate isNotNullOrUndefined(InferredType t) {
+ not isNullOrUndefined(t)
+ }
+
+ /**
+ * A value comparison for `null` and `undefined`.
+ */
+ private class NullUndefinedComparison extends UndefinedNullTest {
+
+ Expr operand;
+
+ InferredType op2type;
+
+ NullUndefinedComparison() {
+ exists (Expr op2 |
+ hasOperands(operand, op2) |
+ op2type = TTNull() and SyntacticConstants::isNull(op2)
+ or
+ op2type = TTUndefined() and SyntacticConstants::isUndefined(op2)
+ )
+ }
+
+ override boolean getTheTestResult() {
+ result = getPolarity() and
+ (
+ if this instanceof StrictEqualityTest then
+ operand.analyze().getTheType() = op2type
+ else
+ not isNotNullOrUndefined(operand.analyze().getAType())
+ )
+ or
+ result = getPolarity().booleanNot() and
+ (
+ if this instanceof StrictEqualityTest then
+ not operand.analyze().getAType() = op2type
+ else
+ not isNullOrUndefined(operand.analyze().getAType())
+ )
+ }
+
+ override Expr getOperand() {
+ result = operand
+ }
+ }
+
}
\ No newline at end of file
From 6e77489a3b24dc2ae80aa4d7f3daa9646ff1115c Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Wed, 10 Oct 2018 15:00:21 +0200
Subject: [PATCH 06/20] JS: add utilities for expression guards to
DefensiveProgramming.qll
---
.../javascript/DefensiveProgramming.qll | 66 +++++++++++++++++++
1 file changed, 66 insertions(+)
diff --git a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
index 515f03d45b0e..2d07a01f2177 100644
--- a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
+++ b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
@@ -184,4 +184,70 @@ module Internal {
}
}
+ /**
+ * An expression that throws an exception if one of its subexpressions evaluates to `null` or `undefined`.
+ */
+ private class UndefinedNullCrashUse extends Expr {
+
+ Expr target;
+
+ UndefinedNullCrashUse() {
+ this.(InvokeExpr).getCallee().stripParens() = target
+ or
+ this.(PropAccess).getBase().stripParens() = target
+ or
+ this.(MethodCallExpr).getReceiver().stripParens() = target
+ }
+
+ /**
+ * Gets the subexpression that will cause an exception to be thrown if it is `null` or `undefined`.
+ */
+ Expr getVulnerableSubexpression() {
+ result = target
+ }
+
+ }
+
+ /**
+ * An expression that throws an exception if one of its subexpressions is not a `function`.
+ */
+ private class NonFunctionCallCrashUse extends Expr {
+
+ Expr target;
+
+ NonFunctionCallCrashUse() {
+ this.(InvokeExpr).getCallee().stripParens() = target
+ }
+
+ /**
+ * Gets the subexpression that will cause an exception to be thrown if it is not a `function`.
+ */
+ Expr getVulnerableSubexpression() {
+ result = target
+ }
+
+ }
+
+ /**
+ * Gets the first expression that is guarded by `guard`.
+ */
+ private Expr getAGuardedExpr(Expr guard) {
+ exists(BinaryExpr op |
+ op.getLeftOperand() = guard and
+ (op instanceof LogAndExpr or op instanceof LogOrExpr) and
+ op.getRightOperand() = result
+ )
+ or
+ exists(IfStmt c |
+ c.getCondition() = guard |
+ result = c.getAControlledStmt().getChildExpr(0) or
+ result = c.getAControlledStmt().(BlockStmt).getStmt(0).getChildExpr(0)
+ )
+ or
+ exists (ConditionalExpr c |
+ c.getCondition() = guard |
+ result = c.getABranch()
+ )
+ }
+
}
\ No newline at end of file
From c403416fef86ccca0322993d57ac86c20437139d Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Wed, 10 Oct 2018 15:02:42 +0200
Subject: [PATCH 07/20] JS: recognize defensive expressions that prevents
exceptions
---
.../javascript/DefensiveProgramming.qll | 67 +++++++++++++++++++
1 file changed, 67 insertions(+)
diff --git a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
index 2d07a01f2177..e0f659fa186f 100644
--- a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
+++ b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
@@ -250,4 +250,71 @@ module Internal {
)
}
+ /**
+ * Holds if `t` is `string`, `number` or `boolean`.
+ */
+ private predicate isStringOrNumOrBool(InferredType t) {
+ t = TTString() or
+ t = TTNumber() or
+ t = TTBoolean()
+ }
+
+ /**
+ * A defensive expression that tests for `undefined` and `null` using a truthiness test.
+ */
+ private class UndefinedNullTruthinessGuard extends DefensiveExpression {
+
+ VarRef guardVar;
+
+ boolean polarity;
+
+ UndefinedNullTruthinessGuard() {
+ exists (VarRef useVar |
+ guardVar = stripNotsAndParens(this.asExpr(), polarity) and
+ guardVar.getVariable() = useVar.getVariable() |
+ getAGuardedExpr(this.asExpr()).stripParens().(UndefinedNullCrashUse).getVulnerableSubexpression() = useVar and
+ // exclude types whose truthiness depend on the value
+ not isStringOrNumOrBool(guardVar.analyze().getAType())
+ )
+ }
+
+ override boolean getTheTestResult() {
+ exists (boolean testResult |
+ testResult = guardVar.analyze().getTheBooleanValue() |
+ if polarity = true then
+ result = testResult
+ else
+ result = testResult.booleanNot()
+ )
+ }
+
+ }
+
+ /**
+ * A defensive expression that tests for `undefined` and `null`.
+ */
+ private class UndefinedNullTypeGuard extends DefensiveExpression {
+
+ UndefinedNullTest test;
+
+ boolean polarity;
+
+ UndefinedNullTypeGuard() {
+ exists (Expr guard, VarRef guardVar, VarRef useVar |
+ this = guard.flow() and
+ test = stripNotsAndParens(guard, polarity) and
+ test.getOperand() = guardVar and
+ guardVar.getVariable() = useVar.getVariable() |
+ getAGuardedExpr(guard).stripParens().(UndefinedNullCrashUse).getVulnerableSubexpression() = useVar
+ )
+ }
+
+ override boolean getTheTestResult() {
+ polarity = true and result = test.getTheTestResult()
+ or
+ polarity = false and result = test.getTheTestResult().booleanNot()
+ }
+
+ }
+
}
\ No newline at end of file
From 7b215ecb2b621de8499914dd2d5a0787df40fe2a Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Wed, 10 Oct 2018 15:10:36 +0200
Subject: [PATCH 08/20] JS: recognize defensive programming patterns using
`typeof`
---
.../javascript/DefensiveProgramming.qll | 92 +++++++++++++++++++
1 file changed, 92 insertions(+)
diff --git a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
index e0f659fa186f..ffa4246e1285 100644
--- a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
+++ b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
@@ -317,4 +317,96 @@ module Internal {
}
+ /**
+ * A test for the value of a `typeof` expression.
+ */
+ private class TypeofTest extends EqualityTest {
+ Expr operand;
+
+ TypeofTag tag;
+
+ TypeofTest() {
+ exists (Expr op1, Expr op2 |
+ hasOperands(op1, op2) |
+ operand = op1.(TypeofExpr).getOperand() and
+ op2.mayHaveStringValue(tag)
+ )
+ }
+
+ boolean getTheTestResult() {
+ exists (boolean testResult |
+ testResult = true and operand.analyze().getTheType().getTypeofTag() = tag or
+ testResult = false and not operand.analyze().getAType().getTypeofTag() = tag |
+ if getPolarity() = true then
+ result = testResult
+ else
+ result = testResult.booleanNot()
+ )
+ }
+
+ /**
+ * Gets the operand used in the `typeof` expression.
+ */
+ Expr getOperand() {
+ result = operand
+ }
+
+ /**
+ * Gets the `typeof` tag that is tested.
+ */
+ TypeofTag getTag() {
+ result = tag
+ }
+ }
+
+ /**
+ * A defensive expression that tests if an expression has type `function`.
+ */
+ private class FunctionTypeGuard extends DefensiveExpression {
+
+ TypeofTest test;
+
+ boolean polarity;
+
+ FunctionTypeGuard() {
+ exists (Expr guard, VarRef guardVar, VarRef useVar |
+ this = guard.flow() and
+ test = stripNotsAndParens(guard, polarity) and
+ test.getOperand() = guardVar and
+ guardVar.getVariable() = useVar.getVariable() |
+ getAGuardedExpr(guard).stripParens().(NonFunctionCallCrashUse).getVulnerableSubexpression() = useVar
+ ) and
+ test.getTag() = "function"
+ }
+
+ override boolean getTheTestResult() {
+ polarity = true and result = test.getTheTestResult()
+ or
+ polarity = false and result = test.getTheTestResult().booleanNot()
+ }
+
+ }
+
+ /**
+ * A test for `undefined` using a `typeof` expression.
+ */
+ private class TypeofUndefinedTest extends UndefinedNullTest {
+
+ TypeofTest test;
+
+ TypeofUndefinedTest() {
+ this = test and
+ test.getTag() = "undefined"
+ }
+
+ override boolean getTheTestResult() {
+ result = test.getTheTestResult()
+ }
+
+ override Expr getOperand() {
+ result = test.getOperand()
+ }
+
+ }
+
}
\ No newline at end of file
From b073fcfca25ea55c638c90c30dc955fc38509bd8 Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Wed, 10 Oct 2018 14:34:58 +0200
Subject: [PATCH 09/20] JS: add query: js/useless-defensive-code
---
.../suites/javascript/maintainability-more | 1 +
.../UselessDefensiveProgramming.qhelp | 91 +++++++++
.../UselessDefensiveProgramming.ql | 19 ++
.../UselessDefensiveProgramming1_bad.js | 10 +
.../UselessDefensiveProgramming1_good.js | 9 +
.../UselessDefensiveProgramming2_bad.js | 5 +
.../UselessDefensiveProgramming2_good.js | 5 +
.../UselessDefensiveProgramming.expected | 78 ++++++++
.../UselessDefensiveProgramming.qlref | 1 +
.../global-module-definition.js | 11 ++
.../module-environment-detection.js | 24 +++
.../UselessDefensiveProgramming/tst.js | 174 ++++++++++++++++++
.../UselessDefensiveProgramming/tst2.js | 10 +
13 files changed, 438 insertions(+)
create mode 100644 javascript/ql/src/Expressions/UselessDefensiveProgramming.qhelp
create mode 100644 javascript/ql/src/Expressions/UselessDefensiveProgramming.ql
create mode 100644 javascript/ql/src/Expressions/examples/UselessDefensiveProgramming1_bad.js
create mode 100644 javascript/ql/src/Expressions/examples/UselessDefensiveProgramming1_good.js
create mode 100644 javascript/ql/src/Expressions/examples/UselessDefensiveProgramming2_bad.js
create mode 100644 javascript/ql/src/Expressions/examples/UselessDefensiveProgramming2_good.js
create mode 100644 javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessDefensiveProgramming.expected
create mode 100644 javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessDefensiveProgramming.qlref
create mode 100644 javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/global-module-definition.js
create mode 100644 javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/module-environment-detection.js
create mode 100644 javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/tst.js
create mode 100644 javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/tst2.js
diff --git a/javascript/config/suites/javascript/maintainability-more b/javascript/config/suites/javascript/maintainability-more
index 6cf17736f532..822a055b2e7b 100644
--- a/javascript/config/suites/javascript/maintainability-more
+++ b/javascript/config/suites/javascript/maintainability-more
@@ -6,6 +6,7 @@
+ semmlecode-javascript-queries/Declarations/DuplicateVarDecl.ql: /Maintainability/Declarations
+ semmlecode-javascript-queries/Declarations/UnusedParameter.ql: /Maintainability/Declarations
+ semmlecode-javascript-queries/Declarations/UnusedVariable.ql: /Maintainability/Declarations
++ semmlecode-javascript-queries/Expressions/UselessDefensiveProgramming.ql: /Maintainability/Expressions
+ semmlecode-javascript-queries/LanguageFeatures/ArgumentsCallerCallee.ql: /Maintainability/Language Features
+ semmlecode-javascript-queries/LanguageFeatures/ConditionalComments.ql: /Maintainability/Language Features
+ semmlecode-javascript-queries/LanguageFeatures/Eval.ql: /Maintainability/Language Features
diff --git a/javascript/ql/src/Expressions/UselessDefensiveProgramming.qhelp b/javascript/ql/src/Expressions/UselessDefensiveProgramming.qhelp
new file mode 100644
index 000000000000..870bc26da177
--- /dev/null
+++ b/javascript/ql/src/Expressions/UselessDefensiveProgramming.qhelp
@@ -0,0 +1,91 @@
+
+
+
+
+
+ Defensive code can prevent unforeseen circumstances from
+ causing fatal program behaviors.
+
+ A common defensive code pattern is to guard
+ against dereferencing the values null or
+ undefined.
+
+ However, if the situation that some defensive code guards
+ against never can occur, then the defensive code serves no purpose and
+ it can safely be removed.
+
+
+
+
+
+
+
+
+ Examine the surrounding code to determine if the defensive
+ code is worth keeping despite providing no practical use. If it is no
+ longer needed, remove it.
+
+
+
+
+
+
+
+
+ The following example shows a cleanupLater
+ function that asynchronously will perform a cleanup task after some
+ delay. When the cleanup task completes, the function invokes the
+ provided callback parameter cb.
+
+ To prevent a crash by invoking cb when it
+ has the value undefined, defensive code guards
+ the invocation by checking if cb is truthy.
+
+
+
+
+
+
+
+ However, the cleanupLater function is always
+ invoked with a callback argument, so the defensive code condition
+ always holds, and it is therefore not
+ required. The function can therefore be simplified to:
+
+
+
+
+
+
+
+ Guarding against the same situation multiple times is
+ another example of defensive code that provides no practical use. The
+ example below shows a function that assigns a value to a property of
+ an object, where defensive code ensures that the assigned value is not
+ undefined or null.
+
+
+
+
+
+
+
+ However, due to coercion rules, v ==
+ undefined holds for both the situation where v
+ isundefined and the situation where v
+ isnull, so the v == null
+ guard serves no purpose, and it can be removed:
+
+
+
+
+
+
+
+
+ Wikipedia: Defensive programming.
+
+
+
diff --git a/javascript/ql/src/Expressions/UselessDefensiveProgramming.ql b/javascript/ql/src/Expressions/UselessDefensiveProgramming.ql
new file mode 100644
index 000000000000..9dd76ffec130
--- /dev/null
+++ b/javascript/ql/src/Expressions/UselessDefensiveProgramming.ql
@@ -0,0 +1,19 @@
+/**
+ * @name Useless defensive code
+ * @description If the situation some defensive code guards against never
+ * happens, then the defensive code is not needed.
+ * @kind problem
+ * @problem.severity recommendation
+ * @id js/useless-defensive-code
+ * @tags correctness
+ * external/cwe/cwe-570
+ * external/cwe/cwe-571
+ * @precision very-high
+ */
+
+import javascript
+import semmle.javascript.DefensiveProgramming
+
+from DefensiveExpression e, boolean cv
+where e.getTheTestResult() = cv
+select e, "This guard always evaluates to " + cv + "."
diff --git a/javascript/ql/src/Expressions/examples/UselessDefensiveProgramming1_bad.js b/javascript/ql/src/Expressions/examples/UselessDefensiveProgramming1_bad.js
new file mode 100644
index 000000000000..d4816efdabb3
--- /dev/null
+++ b/javascript/ql/src/Expressions/examples/UselessDefensiveProgramming1_bad.js
@@ -0,0 +1,10 @@
+function cleanupLater(delay, cb) {
+ setTimeout(function() {
+ cleanup();
+ if (cb) { // BAD: useless check, `cb` is always truthy
+ cb();
+ }
+ }, delay)
+}
+
+cleanupLater(1000, function(){console.log("Cleanup done")});
diff --git a/javascript/ql/src/Expressions/examples/UselessDefensiveProgramming1_good.js b/javascript/ql/src/Expressions/examples/UselessDefensiveProgramming1_good.js
new file mode 100644
index 000000000000..3a67bd563f88
--- /dev/null
+++ b/javascript/ql/src/Expressions/examples/UselessDefensiveProgramming1_good.js
@@ -0,0 +1,9 @@
+function cleanupLater(delay, cb) {
+ setTimeout(function() {
+ cleanupNow();
+ // GOOD: no need to guard the invocation
+ cb();
+ }, delay)
+}
+
+cleanupLater(function(){console.log("Cleanup done")});
diff --git a/javascript/ql/src/Expressions/examples/UselessDefensiveProgramming2_bad.js b/javascript/ql/src/Expressions/examples/UselessDefensiveProgramming2_bad.js
new file mode 100644
index 000000000000..087a19497c9d
--- /dev/null
+++ b/javascript/ql/src/Expressions/examples/UselessDefensiveProgramming2_bad.js
@@ -0,0 +1,5 @@
+function setSafeStringProp(o, prop, v) {
+ // BAD: `v == null` is useless
+ var safe = v == undefined || v == null? '': v;
+ o[prop] = safe;
+}
diff --git a/javascript/ql/src/Expressions/examples/UselessDefensiveProgramming2_good.js b/javascript/ql/src/Expressions/examples/UselessDefensiveProgramming2_good.js
new file mode 100644
index 000000000000..80734c2391ff
--- /dev/null
+++ b/javascript/ql/src/Expressions/examples/UselessDefensiveProgramming2_good.js
@@ -0,0 +1,5 @@
+function setSafeStringProp(o, prop, v) {
+ // GOOD: `v == undefined` handles both `undefined` and `null`
+ var safe = v == undefined? '': v;
+ o[prop] = safe;
+}
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessDefensiveProgramming.expected b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessDefensiveProgramming.expected
new file mode 100644
index 000000000000..65a25659f1a5
--- /dev/null
+++ b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessDefensiveProgramming.expected
@@ -0,0 +1,78 @@
+| global-module-definition.js:10:8:10:11 | Mod2 | This guard always evaluates to false. |
+| module-environment-detection.js:3:13:3:41 | typeof ... efined' | This guard always evaluates to true. |
+| module-environment-detection.js:15:9:15:37 | typeof ... efined' | This guard always evaluates to true. |
+| module-environment-detection.js:23:8:23:36 | typeof ... efined' | This guard always evaluates to true. |
+| tst2.js:4:12:4:35 | typeof ... efined" | This guard always evaluates to true. |
+| tst.js:13:10:13:11 | u_ | This guard always evaluates to false. |
+| tst.js:14:10:14:11 | n_ | This guard always evaluates to false. |
+| tst.js:15:10:15:11 | o_ | This guard always evaluates to true. |
+| tst.js:18:5:18:5 | u | This guard always evaluates to false. |
+| tst.js:19:5:19:5 | n | This guard always evaluates to false. |
+| tst.js:20:5:20:5 | o | This guard always evaluates to true. |
+| tst.js:23:5:23:5 | u | This guard always evaluates to false. |
+| tst.js:24:5:24:5 | n | This guard always evaluates to false. |
+| tst.js:25:5:25:5 | o | This guard always evaluates to true. |
+| tst.js:28:5:28:6 | !u | This guard always evaluates to true. |
+| tst.js:29:5:29:6 | !n | This guard always evaluates to true. |
+| tst.js:30:5:30:6 | !o | This guard always evaluates to false. |
+| tst.js:33:5:33:7 | !!u | This guard always evaluates to false. |
+| tst.js:34:5:34:7 | !!n | This guard always evaluates to false. |
+| tst.js:35:5:35:7 | !!o | This guard always evaluates to true. |
+| tst.js:38:5:38:18 | u != undefined | This guard always evaluates to false. |
+| tst.js:39:5:39:18 | n != undefined | This guard always evaluates to false. |
+| tst.js:40:5:40:18 | o != undefined | This guard always evaluates to true. |
+| tst.js:43:5:43:18 | u == undefined | This guard always evaluates to true. |
+| tst.js:44:5:44:18 | n == undefined | This guard always evaluates to true. |
+| tst.js:45:5:45:18 | o == undefined | This guard always evaluates to false. |
+| tst.js:48:5:48:19 | u === undefined | This guard always evaluates to true. |
+| tst.js:49:5:49:19 | n === undefined | This guard always evaluates to false. |
+| tst.js:50:5:50:19 | o === undefined | This guard always evaluates to false. |
+| tst.js:53:9:53:9 | u | This guard always evaluates to false. |
+| tst.js:56:9:56:9 | n | This guard always evaluates to false. |
+| tst.js:59:9:59:9 | o | This guard always evaluates to true. |
+| tst.js:66:5:66:5 | u | This guard always evaluates to false. |
+| tst.js:67:5:67:5 | n | This guard always evaluates to false. |
+| tst.js:68:5:68:5 | o | This guard always evaluates to true. |
+| tst.js:71:9:71:23 | u !== undefined | This guard always evaluates to false. |
+| tst.js:74:9:74:23 | n !== undefined | This guard always evaluates to true. |
+| tst.js:77:9:77:23 | o !== undefined | This guard always evaluates to true. |
+| tst.js:84:9:84:22 | u == undefined | This guard always evaluates to true. |
+| tst.js:85:9:85:22 | n == undefined | This guard always evaluates to true. |
+| tst.js:86:9:86:22 | o == undefined | This guard always evaluates to false. |
+| tst.js:89:9:89:22 | u != undefined | This guard always evaluates to false. |
+| tst.js:90:9:90:22 | n != undefined | This guard always evaluates to false. |
+| tst.js:91:9:91:22 | o != undefined | This guard always evaluates to true. |
+| tst.js:94:9:94:32 | typeof ... efined" | This guard always evaluates to true. |
+| tst.js:95:9:95:32 | typeof ... efined" | This guard always evaluates to false. |
+| tst.js:96:9:96:32 | typeof ... efined" | This guard always evaluates to false. |
+| tst.js:100:5:100:27 | typeof ... nction" | This guard always evaluates to true. |
+| tst.js:101:5:101:27 | typeof ... nction" | This guard always evaluates to false. |
+| tst.js:114:5:114:15 | empty_array | This guard always evaluates to true. |
+| tst.js:115:5:115:22 | pseudo_empty_array | This guard always evaluates to true. |
+| tst.js:116:5:116:19 | non_empty_array | This guard always evaluates to true. |
+| tst.js:124:6:124:20 | u !== undefined | This guard always evaluates to false. |
+| tst.js:124:25:124:34 | u !== null | This guard always evaluates to true. |
+| tst.js:125:5:125:19 | u !== undefined | This guard always evaluates to false. |
+| tst.js:125:24:125:33 | u !== null | This guard always evaluates to true. |
+| tst.js:127:5:127:18 | u != undefined | This guard always evaluates to false. |
+| tst.js:127:23:127:31 | u != null | This guard always evaluates to false. |
+| tst.js:127:23:127:31 | u != null | This guard always evaluates to true. |
+| tst.js:128:5:128:18 | u == undefined | This guard always evaluates to true. |
+| tst.js:128:23:128:31 | u == null | This guard always evaluates to false. |
+| tst.js:128:23:128:31 | u == null | This guard always evaluates to true. |
+| tst.js:129:5:129:19 | u !== undefined | This guard always evaluates to false. |
+| tst.js:129:24:129:33 | u !== null | This guard always evaluates to true. |
+| tst.js:130:5:130:22 | !(u === undefined) | This guard always evaluates to false. |
+| tst.js:130:27:130:39 | !(u === null) | This guard always evaluates to true. |
+| tst.js:131:5:131:19 | u === undefined | This guard always evaluates to true. |
+| tst.js:131:24:131:33 | u === null | This guard always evaluates to false. |
+| tst.js:132:7:132:21 | u === undefined | This guard always evaluates to true. |
+| tst.js:132:26:132:35 | u === null | This guard always evaluates to false. |
+| tst.js:133:5:133:22 | !(u === undefined) | This guard always evaluates to false. |
+| tst.js:133:27:133:36 | u !== null | This guard always evaluates to true. |
+| tst.js:135:5:135:18 | u == undefined | This guard always evaluates to true. |
+| tst.js:135:23:135:31 | u == null | This guard always evaluates to true. |
+| tst.js:138:24:138:33 | x === null | This guard always evaluates to false. |
+| tst.js:140:13:140:22 | x === null | This guard always evaluates to false. |
+| tst.js:156:23:156:31 | x != null | This guard always evaluates to true. |
+| tst.js:158:13:158:21 | x != null | This guard always evaluates to true. |
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessDefensiveProgramming.qlref b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessDefensiveProgramming.qlref
new file mode 100644
index 000000000000..9a5222a05d0e
--- /dev/null
+++ b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessDefensiveProgramming.qlref
@@ -0,0 +1 @@
+Expressions/UselessDefensiveProgramming.ql
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/global-module-definition.js b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/global-module-definition.js
new file mode 100644
index 000000000000..8c61f38d1132
--- /dev/null
+++ b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/global-module-definition.js
@@ -0,0 +1,11 @@
+var Mod1;
+(function (Mod1) {
+ Mod1.p = 42;
+})(Mod1 || (Mod1 = {}));
+
+(function(){
+ var Mod2;
+ (function (Mod2) {
+ Mod2.p = 42;
+ })(Mod2 || (Mod2 = {})); // NOT OK
+});
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/module-environment-detection.js b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/module-environment-detection.js
new file mode 100644
index 000000000000..913684d6f6aa
--- /dev/null
+++ b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/module-environment-detection.js
@@ -0,0 +1,24 @@
+var _ = (function() {
+ if (typeof exports !== 'undefined') {
+ if (typeof module !== 'undefined' && module.exports) {
+ exports = module.exports = _;
+ }
+ exports._ = _;
+ }
+ return {
+ define: function(name, factory) {
+ }
+ };
+})(this);
+
+if (typeof exports !== 'undefined') {
+ if (typeof module !== 'undefined' && module.exports) {
+ exports = module.exports = emmet;
+ }
+ exports.emmet = emmet;
+}
+
+(function(){
+ var module;
+ if(typeof module === 'undefined'); // NOT OK
+});
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/tst.js b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/tst.js
new file mode 100644
index 000000000000..4993c1e32c00
--- /dev/null
+++ b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/tst.js
@@ -0,0 +1,174 @@
+(function(){
+ var v;
+ var u = undefined;
+ var n = null;
+ var o = {};
+ var x = functionWithUnknownReturnValue();
+
+ var u_ = u;
+ var n_ = n;
+ var o_ = o;
+ var x_ = x;
+
+ u_ = u_ || e;
+ n_ = n_ || e;
+ o_ = o_ || e;
+ x_ = x_ || e;
+
+ u && u.p;
+ n && n.p;
+ o && o.p;
+ x && x.p;
+
+ u && u();
+ n && n();
+ o && o();
+ x && x();
+
+ !u || u.p;
+ !n || n.p;
+ !o || o.p;
+ !x || x.p;
+
+ !!u && u.p;
+ !!n && n.p;
+ !!o && o.p;
+ !!x && x.p;
+
+ u != undefined && u.p;
+ n != undefined && n.p;
+ o != undefined && o.p;
+ x != undefined && x.p;
+
+ u == undefined || u.p;
+ n == undefined || n.p;
+ o == undefined || o.p;
+ x == undefined || x.p;
+
+ u === undefined || u.p;
+ n === undefined || n.p;
+ o === undefined || o.p; // T, D
+ x === undefined || x.p;
+
+ if (u) {
+ u.p;
+ }
+ if (n) {
+ n.p;
+ }
+ if (o) {
+ o.p;
+ }
+ if (x) {
+ x.p;
+ }
+
+ u? u():_;
+ n? n(): _;
+ o? o(): _;
+ x? x(): _;
+
+ if (u !== undefined) {
+ u.p;
+ }
+ if (n !== undefined) {
+ n.p;
+ }
+ if (o !== undefined) {
+ o.p;
+ }
+ if (x !== undefined) {
+ x.p;
+ }
+
+ if (u == undefined){}
+ if (n == undefined){}
+ if (o == undefined){}
+ if (x == undefined){}
+
+ if (u != undefined){}
+ if (n != undefined){}
+ if (o != undefined){}
+ if (x != undefined){}
+
+ if (typeof u === "undefined"){}
+ if (typeof n === "undefined"){}
+ if (typeof o === "undefined"){}
+ if (typeof x === "undefined"){}
+
+ function f() { }
+ typeof f === "function" && f();
+ typeof u === "function" && u();
+ typeof x === "function" && x();
+
+ var empty_array = [];
+ var pseudo_empty_array = [''];
+ var non_empty_array = ['foo'];
+ var empty_string = "";
+ var non_empty_string = "foo";
+ var zero = 0;
+ var neg = -1;
+ var _true = true;
+ var _false = false;
+
+ empty_array && empty_array.pop();
+ pseudo_empty_array && pseudo_empty_array.pop();
+ non_empty_array && non_empty_array.pop();
+ empty_string && empty_string.charAt(0);
+ non_empty_string && non_empty_string.charAt(0);
+ zero && zero();
+ neg && neg();
+ _true && _true();
+ _false && _false();D
+
+ (u !== undefined && u !== null) && u.p;
+ u !== undefined && u !== null && u.p;
+
+ u != undefined && u != null;
+ u == undefined || u == null;
+ u !== undefined && u !== null;
+ !(u === undefined) && !(u === null);
+ u === undefined || u === null;
+ !(u === undefined || u === null);
+ !(u === undefined) && u !== null;
+ u !== undefined && n !== null;
+ u == undefined && u == null;
+ x == undefined && x == null;
+
+ x === undefined && x === null;
+ if (x === undefined) {
+ if (x === null) {
+ }
+ }
+
+ x !== undefined && x !== null;
+ if (x !== undefined) {
+ if (x !== null) {
+ }
+ }
+
+ x == undefined && x == null;
+ if (x == undefined) {
+ if (x == null) {
+ }
+ }
+
+ x != undefined && x != null;
+ if (x != undefined) {
+ if (x != null) {
+ }
+ }
+
+ if (typeof x !== undefined);
+ if (typeof window !== undefined);
+ if (typeof x !== x);
+ if (typeof x !== u);
+
+ if (typeof window !== "undefined");
+ if (typeof module !== "undefined");
+ if (typeof global !== "undefined");
+
+ if (typeof window !== "undefined" && window.document);
+ if (typeof module !== "undefined" && module.exports);
+ if (typeof global !== "undefined" && global.process);
+});
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/tst2.js b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/tst2.js
new file mode 100644
index 000000000000..4c6ad02a6bfb
--- /dev/null
+++ b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/tst2.js
@@ -0,0 +1,10 @@
+(function(){
+ var v;
+ (function(){
+ if(typeof v === "undefined"){ // NOT OK
+ v = 42;
+ }
+ for(var v in x){
+ }
+ });
+});
From e29c57a58ee1387b30ccf04af13122594e9f89a6 Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Wed, 10 Oct 2018 14:37:36 +0200
Subject: [PATCH 10/20] JS: add whitelist to js/useless-defensive-code
---
.../Expressions/UselessDefensiveProgramming.ql | 15 ++++++++++++++-
.../semmle/javascript/DefensiveProgramming.qll | 2 +-
.../UselessDefensiveProgramming.expected | 6 ------
3 files changed, 15 insertions(+), 8 deletions(-)
diff --git a/javascript/ql/src/Expressions/UselessDefensiveProgramming.ql b/javascript/ql/src/Expressions/UselessDefensiveProgramming.ql
index 9dd76ffec130..3ca0a5fdafbc 100644
--- a/javascript/ql/src/Expressions/UselessDefensiveProgramming.ql
+++ b/javascript/ql/src/Expressions/UselessDefensiveProgramming.ql
@@ -15,5 +15,18 @@ import javascript
import semmle.javascript.DefensiveProgramming
from DefensiveExpression e, boolean cv
-where e.getTheTestResult() = cv
+where e.getTheTestResult() = cv and
+ // whitelist
+ not (
+ // module environment detection
+ exists (VarAccess access, string name |
+ name = "exports" or name = "module" |
+ e.asExpr().(Internal::TypeofUndefinedTest).getOperand() = access and
+ access.getName() = name and
+ not exists (access.getVariable().getADeclaration())
+ )
+ or
+ // too benign in practice
+ e instanceof Internal::DefensiveInit
+ )
select e, "This guard always evaluates to " + cv + "."
diff --git a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
index ffa4246e1285..3de4f2121ef6 100644
--- a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
+++ b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
@@ -390,7 +390,7 @@ module Internal {
/**
* A test for `undefined` using a `typeof` expression.
*/
- private class TypeofUndefinedTest extends UndefinedNullTest {
+ class TypeofUndefinedTest extends UndefinedNullTest {
TypeofTest test;
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessDefensiveProgramming.expected b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessDefensiveProgramming.expected
index 65a25659f1a5..f726f0a0b5df 100644
--- a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessDefensiveProgramming.expected
+++ b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessDefensiveProgramming.expected
@@ -1,11 +1,5 @@
-| global-module-definition.js:10:8:10:11 | Mod2 | This guard always evaluates to false. |
-| module-environment-detection.js:3:13:3:41 | typeof ... efined' | This guard always evaluates to true. |
-| module-environment-detection.js:15:9:15:37 | typeof ... efined' | This guard always evaluates to true. |
| module-environment-detection.js:23:8:23:36 | typeof ... efined' | This guard always evaluates to true. |
| tst2.js:4:12:4:35 | typeof ... efined" | This guard always evaluates to true. |
-| tst.js:13:10:13:11 | u_ | This guard always evaluates to false. |
-| tst.js:14:10:14:11 | n_ | This guard always evaluates to false. |
-| tst.js:15:10:15:11 | o_ | This guard always evaluates to true. |
| tst.js:18:5:18:5 | u | This guard always evaluates to false. |
| tst.js:19:5:19:5 | n | This guard always evaluates to false. |
| tst.js:20:5:20:5 | o | This guard always evaluates to true. |
From 358e6188d98ed43449a29a0c54d3da9c31546b5c Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Wed, 10 Oct 2018 12:52:52 +0200
Subject: [PATCH 11/20] JS: downgrade other alerts to js/useless-defensive-code
---
javascript/ql/src/Expressions/HeterogeneousComparison.ql | 2 ++
javascript/ql/src/Statements/UselessConditional.ql | 2 +-
.../HeterogeneousComparison.expected | 3 +++
.../UselessDefensiveProgramming/HeterogeneousComparison.qlref | 1 +
.../UselessDefensiveProgramming/UselessConditional.expected | 0
.../UselessDefensiveProgramming/UselessConditional.qlref | 1 +
6 files changed, 8 insertions(+), 1 deletion(-)
create mode 100644 javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/HeterogeneousComparison.expected
create mode 100644 javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/HeterogeneousComparison.qlref
create mode 100644 javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessConditional.expected
create mode 100644 javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessConditional.qlref
diff --git a/javascript/ql/src/Expressions/HeterogeneousComparison.ql b/javascript/ql/src/Expressions/HeterogeneousComparison.ql
index fd524466ddf4..6e20e3acaa4c 100644
--- a/javascript/ql/src/Expressions/HeterogeneousComparison.ql
+++ b/javascript/ql/src/Expressions/HeterogeneousComparison.ql
@@ -15,6 +15,7 @@
import javascript
private import semmle.javascript.dataflow.InferredTypes
+private import semmle.javascript.DefensiveProgramming
/**
* Holds if `left` and `right` are the left and right operands, respectively, of `nd`, which is
@@ -198,6 +199,7 @@ from ASTNode cmp,
int leftTypeCount, int rightTypeCount ,
string leftTypeDescription, string rightTypeDescription
where isHeterogeneousComparison(cmp, left, right, leftTypes, rightTypes) and
+ not exists (cmp.(Expr).flow().(DefensiveExpression).getTheTestResult()) and
not whitelist(left.asExpr()) and
not whitelist(right.asExpr()) and
leftExprDescription = capitalize(getDescription(left.asExpr(), "this expression")) and
diff --git a/javascript/ql/src/Statements/UselessConditional.ql b/javascript/ql/src/Statements/UselessConditional.ql
index 06e6e8601b91..d4dbebbb6f86 100644
--- a/javascript/ql/src/Statements/UselessConditional.ql
+++ b/javascript/ql/src/Statements/UselessConditional.ql
@@ -87,7 +87,7 @@ predicate isConstantBooleanReturnValue(Expr e) {
predicate whitelist(Expr e) {
isConstant(e) or
isConstant(e.(LogNotExpr).getOperand()) or
- e.flow() instanceof Internal::DefensiveInit or
+ exists (e.flow().(DefensiveExpression).getTheTestResult()) or
isInitialParameterUse(e) or
isConstantBooleanReturnValue(e)
}
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/HeterogeneousComparison.expected b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/HeterogeneousComparison.expected
new file mode 100644
index 000000000000..534048b0d15f
--- /dev/null
+++ b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/HeterogeneousComparison.expected
@@ -0,0 +1,3 @@
+| tst.js:162:9:162:16 | typeof x | This expression is of type string, but it is compared to $@ of type undefined. | tst.js:162:22:162:30 | undefined | 'undefined' |
+| tst.js:163:9:163:21 | typeof window | This expression is of type string, but it is compared to $@ of type undefined. | tst.js:163:27:163:35 | undefined | 'undefined' |
+| tst.js:165:9:165:16 | typeof x | This expression is of type string, but it is compared to $@ of type undefined. | tst.js:165:22:165:22 | u | variable 'u' |
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/HeterogeneousComparison.qlref b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/HeterogeneousComparison.qlref
new file mode 100644
index 000000000000..13b0e2a181cb
--- /dev/null
+++ b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/HeterogeneousComparison.qlref
@@ -0,0 +1 @@
+Expressions/HeterogeneousComparison.ql
\ No newline at end of file
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessConditional.expected b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessConditional.expected
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessConditional.qlref b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessConditional.qlref
new file mode 100644
index 000000000000..d29916245d6e
--- /dev/null
+++ b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessConditional.qlref
@@ -0,0 +1 @@
+Statements/UselessConditional.ql
\ No newline at end of file
From f440c9221ac686b4e3c34de865fe946872a9f54e Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Tue, 16 Oct 2018 10:40:58 +0200
Subject: [PATCH 12/20] JS: replace some `Expr.stripParens` with
`Expr.getUnderlyingValue`
---
.../semmle/javascript/DefensiveProgramming.qll | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
index 3de4f2121ef6..def68da6824f 100644
--- a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
+++ b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
@@ -52,7 +52,7 @@ module Internal {
*/
private Expr stripNotsAndParens(Expr e, boolean polarity) {
exists (Expr inner |
- inner = e.stripParens() |
+ inner = e.getUnderlyingValue() |
if inner instanceof LogNotExpr then
(result = stripNotsAndParens(inner.(LogNotExpr).getOperand(), polarity.booleanNot()))
else
@@ -192,11 +192,11 @@ module Internal {
Expr target;
UndefinedNullCrashUse() {
- this.(InvokeExpr).getCallee().stripParens() = target
+ this.(InvokeExpr).getCallee().getUnderlyingValue() = target
or
- this.(PropAccess).getBase().stripParens() = target
+ this.(PropAccess).getBase().getUnderlyingValue() = target
or
- this.(MethodCallExpr).getReceiver().stripParens() = target
+ this.(MethodCallExpr).getReceiver().getUnderlyingValue() = target
}
/**
@@ -216,7 +216,7 @@ module Internal {
Expr target;
NonFunctionCallCrashUse() {
- this.(InvokeExpr).getCallee().stripParens() = target
+ this.(InvokeExpr).getCallee().getUnderlyingValue() = target
}
/**
@@ -272,7 +272,7 @@ module Internal {
exists (VarRef useVar |
guardVar = stripNotsAndParens(this.asExpr(), polarity) and
guardVar.getVariable() = useVar.getVariable() |
- getAGuardedExpr(this.asExpr()).stripParens().(UndefinedNullCrashUse).getVulnerableSubexpression() = useVar and
+ getAGuardedExpr(this.asExpr()).getUnderlyingValue().(UndefinedNullCrashUse).getVulnerableSubexpression() = useVar and
// exclude types whose truthiness depend on the value
not isStringOrNumOrBool(guardVar.analyze().getAType())
)
@@ -305,7 +305,7 @@ module Internal {
test = stripNotsAndParens(guard, polarity) and
test.getOperand() = guardVar and
guardVar.getVariable() = useVar.getVariable() |
- getAGuardedExpr(guard).stripParens().(UndefinedNullCrashUse).getVulnerableSubexpression() = useVar
+ getAGuardedExpr(guard).getUnderlyingValue().(UndefinedNullCrashUse).getVulnerableSubexpression() = useVar
)
}
@@ -374,7 +374,7 @@ module Internal {
test = stripNotsAndParens(guard, polarity) and
test.getOperand() = guardVar and
guardVar.getVariable() = useVar.getVariable() |
- getAGuardedExpr(guard).stripParens().(NonFunctionCallCrashUse).getVulnerableSubexpression() = useVar
+ getAGuardedExpr(guard).getUnderlyingValue().(NonFunctionCallCrashUse).getVulnerableSubexpression() = useVar
) and
test.getTag() = "function"
}
From 7d4cf495451d7138a2bf94f88709a0c9ae044d7c Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Wed, 31 Oct 2018 09:33:49 +0100
Subject: [PATCH 13/20] JS: fixup double reporting of alerts
---
.../ql/src/Statements/UselessConditional.ql | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/javascript/ql/src/Statements/UselessConditional.ql b/javascript/ql/src/Statements/UselessConditional.ql
index d4dbebbb6f86..e682a56b0064 100644
--- a/javascript/ql/src/Statements/UselessConditional.ql
+++ b/javascript/ql/src/Statements/UselessConditional.ql
@@ -73,6 +73,19 @@ predicate isConstantBooleanReturnValue(Expr e) {
isConstantBooleanReturnValue(e.(LogNotExpr).getOperand())
}
+/**
+ * Holds if `e` is a defensive expression with a fixed outcome.
+ */
+predicate isConstantDefensive(Expr e) {
+ exists(Expr defensive |
+ defensive = e or
+ // traverse negations
+ defensive.(LogNotExpr).getOperand+() = e
+ |
+ exists(defensive.flow().(DefensiveExpression).getTheTestResult())
+ )
+}
+
/**
* Holds if `e` is an expression that should not be flagged as a useless condition.
*
@@ -87,7 +100,7 @@ predicate isConstantBooleanReturnValue(Expr e) {
predicate whitelist(Expr e) {
isConstant(e) or
isConstant(e.(LogNotExpr).getOperand()) or
- exists (e.flow().(DefensiveExpression).getTheTestResult()) or
+ isConstantDefensive(e) or // flagged by js/useless-defensive-code
isInitialParameterUse(e) or
isConstantBooleanReturnValue(e)
}
From a636319c97b64264fff32eeede7929d9de5ed4e7 Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Thu, 1 Nov 2018 08:47:35 +0100
Subject: [PATCH 14/20] JS: change notes for js/useless-defensive-code
---
change-notes/1.19/analysis-javascript.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/change-notes/1.19/analysis-javascript.md b/change-notes/1.19/analysis-javascript.md
index 5630452ded37..e79bae13009f 100644
--- a/change-notes/1.19/analysis-javascript.md
+++ b/change-notes/1.19/analysis-javascript.md
@@ -24,6 +24,7 @@
| 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. |
| 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. |
| Useless assignment to property | maintainability | Highlights property assignments whose value is always overwritten. Results are shown on LGTM by default. |
+| Useless defensive code | correctness, external/cwe/cwe-570, external/cwe/cwe-571 | Highlights locations where defensive code serves no purpose. Results are shown on LGTM by default. |
| 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. |
## Changes to existing queries
From 8b71b25a2a05c26a1681cb660c9725a47a0e1a12 Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Thu, 1 Nov 2018 09:48:16 +0100
Subject: [PATCH 15/20] JS: annotate test file with expected results
---
.../UselessDefensiveProgramming/tst.js | 130 +++++++++---------
1 file changed, 65 insertions(+), 65 deletions(-)
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/tst.js b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/tst.js
index 4993c1e32c00..1e03c4e3ba1a 100644
--- a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/tst.js
+++ b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/tst.js
@@ -10,95 +10,95 @@
var o_ = o;
var x_ = x;
- u_ = u_ || e;
- n_ = n_ || e;
- o_ = o_ || e;
+ u_ = u_ || e; // NOT OK
+ n_ = n_ || e; // NOT OK
+ o_ = o_ || e; // NOT OK
x_ = x_ || e;
- u && u.p;
- n && n.p;
- o && o.p;
+ u && u.p; // NOT OK
+ n && n.p; // NOT OK
+ o && o.p; // NOT OK
x && x.p;
- u && u();
- n && n();
- o && o();
+ u && u(); // NOT OK
+ n && n(); // NOT OK
+ o && o(); // NOT OK
x && x();
- !u || u.p;
- !n || n.p;
- !o || o.p;
+ !u || u.p; // NOT OK
+ !n || n.p; // NOT OK
+ !o || o.p; // NOT OK
!x || x.p;
- !!u && u.p;
- !!n && n.p;
- !!o && o.p;
+ !!u && u.p; // NOT OK
+ !!n && n.p; // NOT OK
+ !!o && o.p; // NOT OK
!!x && x.p;
- u != undefined && u.p;
- n != undefined && n.p;
- o != undefined && o.p;
+ u != undefined && u.p; // NOT OK
+ n != undefined && n.p; // NOT OK
+ o != undefined && o.p; // NOT OK
x != undefined && x.p;
- u == undefined || u.p;
- n == undefined || n.p;
- o == undefined || o.p;
+ u == undefined || u.p; // NOT OK
+ n == undefined || n.p; // NOT OK
+ o == undefined || o.p; // NOT OK
x == undefined || x.p;
- u === undefined || u.p;
- n === undefined || n.p;
- o === undefined || o.p; // T, D
+ u === undefined || u.p; // NOT OK
+ n === undefined || n.p; // NOT OK
+ o === undefined || o.p; // NOT OK
x === undefined || x.p;
- if (u) {
+ if (u) { // NOT OK
u.p;
}
- if (n) {
+ if (n) { // NOT OK
n.p;
}
- if (o) {
+ if (o) { // NOT OK
o.p;
}
if (x) {
x.p;
}
- u? u():_;
- n? n(): _;
- o? o(): _;
+ u? u():_; // NOT OK
+ n? n(): _; // NOT OK
+ o? o(): _; // NOT OK
x? x(): _;
- if (u !== undefined) {
+ if (u !== undefined) { // NOT OK
u.p;
}
- if (n !== undefined) {
+ if (n !== undefined) { // NOT OK
n.p;
}
- if (o !== undefined) {
+ if (o !== undefined) { // NOT OK
o.p;
}
if (x !== undefined) {
x.p;
}
- if (u == undefined){}
- if (n == undefined){}
- if (o == undefined){}
+ if (u == undefined){} // NOT OK
+ if (n == undefined){} // NOT OK
+ if (o == undefined){} // NOT OK
if (x == undefined){}
- if (u != undefined){}
- if (n != undefined){}
- if (o != undefined){}
+ if (u != undefined){} // NOT OK
+ if (n != undefined){} // NOT OK
+ if (o != undefined){} // NOT OK
if (x != undefined){}
- if (typeof u === "undefined"){}
- if (typeof n === "undefined"){}
- if (typeof o === "undefined"){}
+ if (typeof u === "undefined"){} // NOT OK
+ if (typeof n === "undefined"){} // NOT OK
+ if (typeof o === "undefined"){} // NOT OK
if (typeof x === "undefined"){}
function f() { }
- typeof f === "function" && f();
- typeof u === "function" && u();
+ typeof f === "function" && f(); // NOT OK
+ typeof u === "function" && u(); // NOT OK
typeof x === "function" && x();
var empty_array = [];
@@ -111,33 +111,33 @@
var _true = true;
var _false = false;
- empty_array && empty_array.pop();
- pseudo_empty_array && pseudo_empty_array.pop();
- non_empty_array && non_empty_array.pop();
+ empty_array && empty_array.pop(); // NOT OK
+ pseudo_empty_array && pseudo_empty_array.pop(); // NOT OK
+ non_empty_array && non_empty_array.pop(); // NOT OK
empty_string && empty_string.charAt(0);
non_empty_string && non_empty_string.charAt(0);
zero && zero();
neg && neg();
_true && _true();
- _false && _false();D
-
- (u !== undefined && u !== null) && u.p;
- u !== undefined && u !== null && u.p;
-
- u != undefined && u != null;
- u == undefined || u == null;
- u !== undefined && u !== null;
- !(u === undefined) && !(u === null);
- u === undefined || u === null;
- !(u === undefined || u === null);
- !(u === undefined) && u !== null;
+ _false && _false();
+
+ (u !== undefined && u !== null) && u.p; // NOT OK
+ u !== undefined && u !== null && u.p; // NOT OK
+
+ u != undefined && u != null; // NOT OK
+ u == undefined || u == null; // NOT OK
+ u !== undefined && u !== null; // NOT OK
+ !(u === undefined) && !(u === null); // NOT OK
+ u === undefined || u === null; // NOT OK
+ !(u === undefined || u === null); // NOT OK
+ !(u === undefined) && u !== null; // NOT OK
u !== undefined && n !== null;
- u == undefined && u == null;
+ u == undefined && u == null; // NOT OK
x == undefined && x == null;
- x === undefined && x === null;
+ x === undefined && x === null; // NOT OK
if (x === undefined) {
- if (x === null) {
+ if (x === null) { // NOT OK
}
}
@@ -153,16 +153,16 @@
}
}
- x != undefined && x != null;
+ x != undefined && x != null; // NOT OK
if (x != undefined) {
- if (x != null) {
+ if (x != null) { // NOT OK
}
}
if (typeof x !== undefined);
if (typeof window !== undefined);
if (typeof x !== x);
- if (typeof x !== u);
+ if (typeof x !== u); // NOT OK
if (typeof window !== "undefined");
if (typeof module !== "undefined");
From 8ea9fd4ccac8415a6ec6d3e4f4a5ad56001008bb Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Mon, 5 Nov 2018 11:27:09 +0100
Subject: [PATCH 16/20] JS: address review comments
---
.../Expressions/HeterogeneousComparison.ql | 2 +-
.../UselessDefensiveProgramming.ql | 2 +-
.../ql/src/Statements/UselessConditional.ql | 2 +-
.../javascript/DefensiveProgramming.qll | 27 ++++++++++---------
4 files changed, 17 insertions(+), 16 deletions(-)
diff --git a/javascript/ql/src/Expressions/HeterogeneousComparison.ql b/javascript/ql/src/Expressions/HeterogeneousComparison.ql
index 6e20e3acaa4c..ae1be5a7a989 100644
--- a/javascript/ql/src/Expressions/HeterogeneousComparison.ql
+++ b/javascript/ql/src/Expressions/HeterogeneousComparison.ql
@@ -199,7 +199,7 @@ from ASTNode cmp,
int leftTypeCount, int rightTypeCount ,
string leftTypeDescription, string rightTypeDescription
where isHeterogeneousComparison(cmp, left, right, leftTypes, rightTypes) and
- not exists (cmp.(Expr).flow().(DefensiveExpression).getTheTestResult()) and
+ not exists (cmp.(Expr).flow().(DefensiveExpressionTest).getTheTestResult()) and
not whitelist(left.asExpr()) and
not whitelist(right.asExpr()) and
leftExprDescription = capitalize(getDescription(left.asExpr(), "this expression")) and
diff --git a/javascript/ql/src/Expressions/UselessDefensiveProgramming.ql b/javascript/ql/src/Expressions/UselessDefensiveProgramming.ql
index 3ca0a5fdafbc..ce6dea98ecfb 100644
--- a/javascript/ql/src/Expressions/UselessDefensiveProgramming.ql
+++ b/javascript/ql/src/Expressions/UselessDefensiveProgramming.ql
@@ -14,7 +14,7 @@
import javascript
import semmle.javascript.DefensiveProgramming
-from DefensiveExpression e, boolean cv
+from DefensiveExpressionTest e, boolean cv
where e.getTheTestResult() = cv and
// whitelist
not (
diff --git a/javascript/ql/src/Statements/UselessConditional.ql b/javascript/ql/src/Statements/UselessConditional.ql
index e682a56b0064..3051ee3448e7 100644
--- a/javascript/ql/src/Statements/UselessConditional.ql
+++ b/javascript/ql/src/Statements/UselessConditional.ql
@@ -82,7 +82,7 @@ predicate isConstantDefensive(Expr e) {
// traverse negations
defensive.(LogNotExpr).getOperand+() = e
|
- exists(defensive.flow().(DefensiveExpression).getTheTestResult())
+ exists(defensive.flow().(DefensiveExpressionTest).getTheTestResult())
)
}
diff --git a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
index def68da6824f..08bfe6702c51 100644
--- a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
+++ b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
@@ -8,13 +8,13 @@ private import semmle.javascript.dataflow.InferredTypes
/**
* A test in a defensive programming pattern.
*/
-abstract class DefensiveExpression extends DataFlow::ValueNode {
+abstract class DefensiveExpressionTest extends DataFlow::ValueNode {
/** Gets the unique Boolean value that this test evaluates to, if any. */
abstract boolean getTheTestResult();
}
/**
- * INTERNAL: Do not use directly; use `DefensiveExpression` instead.
+ * INTERNAL: Do not use directly; use `DefensiveExpressionTest` instead.
*/
module Internal {
/**
@@ -27,7 +27,7 @@ module Internal {
* - the second `x` in `x = (x || e)`
* - the second `x` in `var x = x || e`
*/
- class DefensiveInit extends DefensiveExpression {
+ class DefensiveInit extends DefensiveExpressionTest {
DefensiveInit() {
exists(VarAccess va, LogOrExpr o, VarRef va2 |
va = astNode and
@@ -76,16 +76,14 @@ module Internal {
/**
* A dis- or conjunction that tests if an expression is `null` or `undefined` in either branch.
*/
- private class CompositeUndefinedNullTestPart extends DefensiveExpression {
+ private class CompositeUndefinedNullTestPart extends DefensiveExpressionTest {
UndefinedNullTest test;
boolean polarity;
CompositeUndefinedNullTestPart(){
- exists (BinaryExpr composite, Variable v, Expr op, Expr opOther, UndefinedNullTest testOther |
- composite instanceof LogAndExpr or
- composite instanceof LogOrExpr |
+ exists (LogicalBinaryExpr composite, Variable v, Expr op, Expr opOther, UndefinedNullTest testOther |
composite.hasOperands(op, opOther) and
this = op.flow() and
test = stripNotsAndParens(op, polarity) and
@@ -106,7 +104,7 @@ module Internal {
/**
* A test for `undefined` or `null` in an if-statement.
*/
- private class SanityCheckingUndefinedNullGuard extends DefensiveExpression {
+ private class SanityCheckingUndefinedNullGuard extends DefensiveExpressionTest {
UndefinedNullTest test;
@@ -165,16 +163,20 @@ module Internal {
result = getPolarity() and
(
if this instanceof StrictEqualityTest then
+ // case: `operand === null` or `operand === undefined`
operand.analyze().getTheType() = op2type
else
+ // case: `operand == null` or `operand == undefined`
not isNotNullOrUndefined(operand.analyze().getAType())
)
or
result = getPolarity().booleanNot() and
(
if this instanceof StrictEqualityTest then
+ // case: `operand !== null` or `operand !== undefined`
not operand.analyze().getAType() = op2type
else
+ // case: `operand != null` or `operand != undefined`
not isNullOrUndefined(operand.analyze().getAType())
)
}
@@ -232,9 +234,8 @@ module Internal {
* Gets the first expression that is guarded by `guard`.
*/
private Expr getAGuardedExpr(Expr guard) {
- exists(BinaryExpr op |
+ exists(LogicalBinaryExpr op |
op.getLeftOperand() = guard and
- (op instanceof LogAndExpr or op instanceof LogOrExpr) and
op.getRightOperand() = result
)
or
@@ -262,7 +263,7 @@ module Internal {
/**
* A defensive expression that tests for `undefined` and `null` using a truthiness test.
*/
- private class UndefinedNullTruthinessGuard extends DefensiveExpression {
+ private class UndefinedNullTruthinessGuard extends DefensiveExpressionTest {
VarRef guardVar;
@@ -293,7 +294,7 @@ module Internal {
/**
* A defensive expression that tests for `undefined` and `null`.
*/
- private class UndefinedNullTypeGuard extends DefensiveExpression {
+ private class UndefinedNullTypeGuard extends DefensiveExpressionTest {
UndefinedNullTest test;
@@ -362,7 +363,7 @@ module Internal {
/**
* A defensive expression that tests if an expression has type `function`.
*/
- private class FunctionTypeGuard extends DefensiveExpression {
+ private class FunctionTypeGuard extends DefensiveExpressionTest {
TypeofTest test;
From 15123da0b7f728f7ecc327f7d4d809159347ea32 Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Mon, 5 Nov 2018 11:33:14 +0100
Subject: [PATCH 17/20] JS: minor fixup: only traverse `LogNotExpr`s
---
javascript/ql/src/Statements/UselessConditional.ql | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/javascript/ql/src/Statements/UselessConditional.ql b/javascript/ql/src/Statements/UselessConditional.ql
index 3051ee3448e7..27b703f652ba 100644
--- a/javascript/ql/src/Statements/UselessConditional.ql
+++ b/javascript/ql/src/Statements/UselessConditional.ql
@@ -73,16 +73,18 @@ predicate isConstantBooleanReturnValue(Expr e) {
isConstantBooleanReturnValue(e.(LogNotExpr).getOperand())
}
+private Expr maybeStripLogNot(Expr e) {
+ result = maybeStripLogNot(e.(LogNotExpr).getOperand()) or
+ result = e
+}
+
/**
* Holds if `e` is a defensive expression with a fixed outcome.
*/
predicate isConstantDefensive(Expr e) {
- exists(Expr defensive |
- defensive = e or
- // traverse negations
- defensive.(LogNotExpr).getOperand+() = e
- |
- exists(defensive.flow().(DefensiveExpressionTest).getTheTestResult())
+ exists(DefensiveExpressionTest defensive |
+ maybeStripLogNot(defensive.asExpr()) = e and
+ exists(defensive.getTheTestResult())
)
}
From 3aae1d17dbcb3d7b8b42806279d331bf39f19894 Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Mon, 5 Nov 2018 11:34:34 +0100
Subject: [PATCH 18/20] JS: avoid two uses of `getChildExpr(0)`
---
.../ql/src/semmle/javascript/DefensiveProgramming.qll | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
index 08bfe6702c51..6b224fe586b2 100644
--- a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
+++ b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
@@ -239,10 +239,11 @@ module Internal {
op.getRightOperand() = result
)
or
- exists(IfStmt c |
- c.getCondition() = guard |
- result = c.getAControlledStmt().getChildExpr(0) or
- result = c.getAControlledStmt().(BlockStmt).getStmt(0).getChildExpr(0)
+ exists(IfStmt c, ExprStmt guardedStmt |
+ c.getCondition() = guard and
+ result = guardedStmt.getExpr() |
+ guardedStmt = c.getAControlledStmt() or
+ guardedStmt = c.getAControlledStmt().(BlockStmt).getStmt(0)
)
or
exists (ConditionalExpr c |
From 1db2e6ca55777501ae048696782b28f321a411e9 Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Mon, 5 Nov 2018 11:49:54 +0100
Subject: [PATCH 19/20] JS: add source code examples to docstrings
---
.../javascript/DefensiveProgramming.qll | 22 +++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
index 6b224fe586b2..6529566495b6 100644
--- a/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
+++ b/javascript/ql/src/semmle/javascript/DefensiveProgramming.qll
@@ -62,6 +62,8 @@ module Internal {
/**
* An equality test for `null` and `undefined`.
+ *
+ * Examples: `e === undefined` or `typeof e !== undefined`.
*/
private abstract class UndefinedNullTest extends EqualityTest {
/** Gets the unique Boolean value that this test evaluates to, if any. */
@@ -75,6 +77,8 @@ module Internal {
/**
* A dis- or conjunction that tests if an expression is `null` or `undefined` in either branch.
+ *
+ * Example: a branch in `x === null || x === undefined`.
*/
private class CompositeUndefinedNullTestPart extends DefensiveExpressionTest {
@@ -103,6 +107,8 @@ module Internal {
/**
* A test for `undefined` or `null` in an if-statement.
+ *
+ * Example: `if (x === null) ...`.
*/
private class SanityCheckingUndefinedNullGuard extends DefensiveExpressionTest {
@@ -143,6 +149,8 @@ module Internal {
/**
* A value comparison for `null` and `undefined`.
+ *
+ * Examples: `x === null` or `x != undefined`.
*/
private class NullUndefinedComparison extends UndefinedNullTest {
@@ -188,6 +196,8 @@ module Internal {
/**
* An expression that throws an exception if one of its subexpressions evaluates to `null` or `undefined`.
+ *
+ * Examples: `sub.p` or `sub()`.
*/
private class UndefinedNullCrashUse extends Expr {
@@ -212,6 +222,8 @@ module Internal {
/**
* An expression that throws an exception if one of its subexpressions is not a `function`.
+ *
+ * Example: `sub()`.
*/
private class NonFunctionCallCrashUse extends Expr {
@@ -263,6 +275,8 @@ module Internal {
/**
* A defensive expression that tests for `undefined` and `null` using a truthiness test.
+ *
+ * Examples: The condition in `if(x) { x.p; }` or `!x || x.m()`.
*/
private class UndefinedNullTruthinessGuard extends DefensiveExpressionTest {
@@ -294,6 +308,8 @@ module Internal {
/**
* A defensive expression that tests for `undefined` and `null`.
+ *
+ * Example: the condition in `if(x !== null) { x.p; }`.
*/
private class UndefinedNullTypeGuard extends DefensiveExpressionTest {
@@ -321,6 +337,8 @@ module Internal {
/**
* A test for the value of a `typeof` expression.
+ *
+ * Example: `typeof x === 'undefined'`.
*/
private class TypeofTest extends EqualityTest {
Expr operand;
@@ -363,6 +381,8 @@ module Internal {
/**
* A defensive expression that tests if an expression has type `function`.
+ *
+ * Example: the condition in `if(typeof x === 'function') x()`.
*/
private class FunctionTypeGuard extends DefensiveExpressionTest {
@@ -391,6 +411,8 @@ module Internal {
/**
* A test for `undefined` using a `typeof` expression.
+ *
+ * Example: `typeof x === undefined'.
*/
class TypeofUndefinedTest extends UndefinedNullTest {
From 5666deac14bee439f529cc238767af74f3851427 Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Thu, 8 Nov 2018 12:08:32 +0100
Subject: [PATCH 20/20] JS: rename js/useless-defensive-code to
js/unneeded-defensive-code
---
change-notes/1.19/analysis-javascript.md | 2 +-
...ming.qhelp => UnneededDefensiveProgramming.qhelp} | 12 ++++++------
...rogramming.ql => UnneededDefensiveProgramming.ql} | 7 +++----
...1_bad.js => UnneededDefensiveProgramming1_bad.js} | 0
...good.js => UnneededDefensiveProgramming1_good.js} | 0
...2_bad.js => UnneededDefensiveProgramming2_bad.js} | 0
...good.js => UnneededDefensiveProgramming2_good.js} | 0
.../HeterogeneousComparison.expected | 0
.../HeterogeneousComparison.qlref | 0
.../UnneededDefensiveProgramming.expected} | 0
.../UnneededDefensiveProgramming.qlref | 1 +
.../UselessConditional.expected | 0
.../UselessConditional.qlref | 0
.../global-module-definition.js | 0
.../module-environment-detection.js | 0
.../tst.js | 0
.../tst2.js | 0
.../UselessDefensiveProgramming.qlref | 1 -
18 files changed, 11 insertions(+), 12 deletions(-)
rename javascript/ql/src/Expressions/{UselessDefensiveProgramming.qhelp => UnneededDefensiveProgramming.qhelp} (86%)
rename javascript/ql/src/Expressions/{UselessDefensiveProgramming.ql => UnneededDefensiveProgramming.ql} (80%)
rename javascript/ql/src/Expressions/examples/{UselessDefensiveProgramming1_bad.js => UnneededDefensiveProgramming1_bad.js} (100%)
rename javascript/ql/src/Expressions/examples/{UselessDefensiveProgramming1_good.js => UnneededDefensiveProgramming1_good.js} (100%)
rename javascript/ql/src/Expressions/examples/{UselessDefensiveProgramming2_bad.js => UnneededDefensiveProgramming2_bad.js} (100%)
rename javascript/ql/src/Expressions/examples/{UselessDefensiveProgramming2_good.js => UnneededDefensiveProgramming2_good.js} (100%)
rename javascript/ql/test/query-tests/Expressions/{UselessDefensiveProgramming => UnneededDefensiveProgramming}/HeterogeneousComparison.expected (100%)
rename javascript/ql/test/query-tests/Expressions/{UselessDefensiveProgramming => UnneededDefensiveProgramming}/HeterogeneousComparison.qlref (100%)
rename javascript/ql/test/query-tests/Expressions/{UselessDefensiveProgramming/UselessDefensiveProgramming.expected => UnneededDefensiveProgramming/UnneededDefensiveProgramming.expected} (100%)
create mode 100644 javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/UnneededDefensiveProgramming.qlref
rename javascript/ql/test/query-tests/Expressions/{UselessDefensiveProgramming => UnneededDefensiveProgramming}/UselessConditional.expected (100%)
rename javascript/ql/test/query-tests/Expressions/{UselessDefensiveProgramming => UnneededDefensiveProgramming}/UselessConditional.qlref (100%)
rename javascript/ql/test/query-tests/Expressions/{UselessDefensiveProgramming => UnneededDefensiveProgramming}/global-module-definition.js (100%)
rename javascript/ql/test/query-tests/Expressions/{UselessDefensiveProgramming => UnneededDefensiveProgramming}/module-environment-detection.js (100%)
rename javascript/ql/test/query-tests/Expressions/{UselessDefensiveProgramming => UnneededDefensiveProgramming}/tst.js (100%)
rename javascript/ql/test/query-tests/Expressions/{UselessDefensiveProgramming => UnneededDefensiveProgramming}/tst2.js (100%)
delete mode 100644 javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessDefensiveProgramming.qlref
diff --git a/change-notes/1.19/analysis-javascript.md b/change-notes/1.19/analysis-javascript.md
index e79bae13009f..4071deb9cb9d 100644
--- a/change-notes/1.19/analysis-javascript.md
+++ b/change-notes/1.19/analysis-javascript.md
@@ -23,8 +23,8 @@
| 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. |
| 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. |
| 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. |
+| 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. |
| Useless assignment to property | maintainability | Highlights property assignments whose value is always overwritten. Results are shown on LGTM by default. |
-| Useless defensive code | correctness, external/cwe/cwe-570, external/cwe/cwe-571 | Highlights locations where defensive code serves no purpose. Results are shown on LGTM by default. |
| 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. |
## Changes to existing queries
diff --git a/javascript/ql/src/Expressions/UselessDefensiveProgramming.qhelp b/javascript/ql/src/Expressions/UnneededDefensiveProgramming.qhelp
similarity index 86%
rename from javascript/ql/src/Expressions/UselessDefensiveProgramming.qhelp
rename to javascript/ql/src/Expressions/UnneededDefensiveProgramming.qhelp
index 870bc26da177..fd7191dd7fa8 100644
--- a/javascript/ql/src/Expressions/UselessDefensiveProgramming.qhelp
+++ b/javascript/ql/src/Expressions/UnneededDefensiveProgramming.qhelp
@@ -14,7 +14,7 @@
However, if the situation that some defensive code guards
against never can occur, then the defensive code serves no purpose and
- it can safely be removed.
+ can safely be removed.
@@ -45,7 +45,7 @@
-
+
@@ -56,7 +56,7 @@
-
+
@@ -68,7 +68,7 @@
-
+
@@ -76,11 +76,11 @@
undefined holds for both the situation where v
isundefined and the situation where v
isnull, so the v == null
- guard serves no purpose, and it can be removed:
+ guard serves no purpose, and can be removed:
-
+
diff --git a/javascript/ql/src/Expressions/UselessDefensiveProgramming.ql b/javascript/ql/src/Expressions/UnneededDefensiveProgramming.ql
similarity index 80%
rename from javascript/ql/src/Expressions/UselessDefensiveProgramming.ql
rename to javascript/ql/src/Expressions/UnneededDefensiveProgramming.ql
index ce6dea98ecfb..bb36d94aef33 100644
--- a/javascript/ql/src/Expressions/UselessDefensiveProgramming.ql
+++ b/javascript/ql/src/Expressions/UnneededDefensiveProgramming.ql
@@ -1,10 +1,9 @@
/**
- * @name Useless defensive code
- * @description If the situation some defensive code guards against never
- * happens, then the defensive code is not needed.
+ * @name Unneeded defensive code
+ * @description Defensive code that guards against a situation that never happens is not needed.
* @kind problem
* @problem.severity recommendation
- * @id js/useless-defensive-code
+ * @id js/unneeded-defensive-code
* @tags correctness
* external/cwe/cwe-570
* external/cwe/cwe-571
diff --git a/javascript/ql/src/Expressions/examples/UselessDefensiveProgramming1_bad.js b/javascript/ql/src/Expressions/examples/UnneededDefensiveProgramming1_bad.js
similarity index 100%
rename from javascript/ql/src/Expressions/examples/UselessDefensiveProgramming1_bad.js
rename to javascript/ql/src/Expressions/examples/UnneededDefensiveProgramming1_bad.js
diff --git a/javascript/ql/src/Expressions/examples/UselessDefensiveProgramming1_good.js b/javascript/ql/src/Expressions/examples/UnneededDefensiveProgramming1_good.js
similarity index 100%
rename from javascript/ql/src/Expressions/examples/UselessDefensiveProgramming1_good.js
rename to javascript/ql/src/Expressions/examples/UnneededDefensiveProgramming1_good.js
diff --git a/javascript/ql/src/Expressions/examples/UselessDefensiveProgramming2_bad.js b/javascript/ql/src/Expressions/examples/UnneededDefensiveProgramming2_bad.js
similarity index 100%
rename from javascript/ql/src/Expressions/examples/UselessDefensiveProgramming2_bad.js
rename to javascript/ql/src/Expressions/examples/UnneededDefensiveProgramming2_bad.js
diff --git a/javascript/ql/src/Expressions/examples/UselessDefensiveProgramming2_good.js b/javascript/ql/src/Expressions/examples/UnneededDefensiveProgramming2_good.js
similarity index 100%
rename from javascript/ql/src/Expressions/examples/UselessDefensiveProgramming2_good.js
rename to javascript/ql/src/Expressions/examples/UnneededDefensiveProgramming2_good.js
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/HeterogeneousComparison.expected b/javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/HeterogeneousComparison.expected
similarity index 100%
rename from javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/HeterogeneousComparison.expected
rename to javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/HeterogeneousComparison.expected
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/HeterogeneousComparison.qlref b/javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/HeterogeneousComparison.qlref
similarity index 100%
rename from javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/HeterogeneousComparison.qlref
rename to javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/HeterogeneousComparison.qlref
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessDefensiveProgramming.expected b/javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/UnneededDefensiveProgramming.expected
similarity index 100%
rename from javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessDefensiveProgramming.expected
rename to javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/UnneededDefensiveProgramming.expected
diff --git a/javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/UnneededDefensiveProgramming.qlref b/javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/UnneededDefensiveProgramming.qlref
new file mode 100644
index 000000000000..13c05f1e74bd
--- /dev/null
+++ b/javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/UnneededDefensiveProgramming.qlref
@@ -0,0 +1 @@
+Expressions/UnneededDefensiveProgramming.ql
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessConditional.expected b/javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/UselessConditional.expected
similarity index 100%
rename from javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessConditional.expected
rename to javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/UselessConditional.expected
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessConditional.qlref b/javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/UselessConditional.qlref
similarity index 100%
rename from javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessConditional.qlref
rename to javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/UselessConditional.qlref
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/global-module-definition.js b/javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/global-module-definition.js
similarity index 100%
rename from javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/global-module-definition.js
rename to javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/global-module-definition.js
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/module-environment-detection.js b/javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/module-environment-detection.js
similarity index 100%
rename from javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/module-environment-detection.js
rename to javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/module-environment-detection.js
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/tst.js b/javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/tst.js
similarity index 100%
rename from javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/tst.js
rename to javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/tst.js
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/tst2.js b/javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/tst2.js
similarity index 100%
rename from javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/tst2.js
rename to javascript/ql/test/query-tests/Expressions/UnneededDefensiveProgramming/tst2.js
diff --git a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessDefensiveProgramming.qlref b/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessDefensiveProgramming.qlref
deleted file mode 100644
index 9a5222a05d0e..000000000000
--- a/javascript/ql/test/query-tests/Expressions/UselessDefensiveProgramming/UselessDefensiveProgramming.qlref
+++ /dev/null
@@ -1 +0,0 @@
-Expressions/UselessDefensiveProgramming.ql