Merge master into next.

This commit is contained in:
Aditya Sharad
2018-11-14 10:06:27 +00:00
97 changed files with 5908 additions and 212 deletions

View File

@@ -0,0 +1,49 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
A value is assigned to a variable or property, but either that location is never read
later on, or its value is always overwritten before being read. This means
that the original assignment has no effect, and could indicate a logic error or
incomplete code.
</p>
</overview>
<recommendation>
<p>
Ensure that you check the control and data flow in the method carefully.
If a value is really not needed, consider omitting the assignment. Be careful,
though: if the right-hand side has a side-effect (like performing a method call),
it is important to keep this to preserve the overall behavior.
</p>
</recommendation>
<example>
<p>
In the following example, the return value of the call to <code>send</code> on line 2
is assigned to the local variable <code>result</code>, but then never used.
</p>
<sample src="examples/DeadStoreOfLocal.js" />
<p>
Assuming that <code>send</code> returns a status code indicating whether the operation
succeeded or not, the value of <code>result</code> should be checked, perhaps like this:
</p>
<sample src="examples/DeadStoreOfLocalGood.js" />
</example>
<references>
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Dead_store">Dead store</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,22 @@
/**
* Provides classes and predicates for reasoning about dead stores.
*/
import javascript
/**
* Holds if `e` is an expression that may be used as a default initial value,
* such as `0` or `-1`, or an empty object or array literal.
*/
predicate isDefaultInit(Expr e) {
// primitive default values: zero, false, empty string, and (integer) -1
e.(NumberLiteral).getValue().toFloat() = 0.0 or
e.(NegExpr).getOperand().(NumberLiteral).getValue() = "1" or
e.(ConstantString).getStringValue() = "" or
e.(BooleanLiteral).getValue() = "false" or
// initialising to an empty array or object literal, even if unnecessary,
// can convey useful type information to the reader
e.(ArrayExpr).getSize() = 0 or
e.(ObjectExpr).getNumProperty() = 0 or
SyntacticConstants::isNullOrUndefined(e)
}

View File

@@ -1,49 +1,9 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
A value is assigned to a local variable, but either that variable is never read
later on, or its value is always overwritten before being read. This means
that the original assignment has no effect, and could indicate a logic error or
incomplete code.
</p>
</overview>
<recommendation>
<include src="DeadStore.qhelp" />
<p>
Ensure that you check the control and data flow in the method carefully.
If a value is really not needed, consider omitting the assignment. Be careful,
though: if the right-hand side has a side-effect (like performing a method call),
it is important to keep this to preserve the overall behavior.
</p>
</recommendation>
<example>
<p>
In the following example, the return value of the call to <code>send</code> on line 2
is assigned to the local variable <code>result</code>, but then never used.
</p>
<sample src="examples/DeadStoreOfLocal.js" />
<p>
Assuming that <code>send</code> returns a status code indicating whether the operation
succeeded or not, the value of <code>result</code> should be checked, perhaps like this:
</p>
<sample src="examples/DeadStoreOfLocalGood.js" />
</example>
<references>
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Dead_store">Dead store</a>.</li>
</references>
</qhelp>

View File

@@ -11,6 +11,7 @@
*/
import javascript
import DeadStore
/**
* Holds if `vd` is a definition of variable `v` that is dead, that is,
@@ -25,22 +26,6 @@ predicate deadStoreOfLocal(VarDef vd, PurelyLocalVariable v) {
not exists (SsaExplicitDefinition ssa | ssa.defines(vd, v))
}
/**
* Holds if `e` is an expression that may be used as a default initial value,
* such as `0` or `-1`, or an empty object or array literal.
*/
predicate isDefaultInit(Expr e) {
// primitive default values: zero, false, empty string, and (integer) -1
e.(NumberLiteral).getValue().toFloat() = 0.0 or
e.(NegExpr).getOperand().(NumberLiteral).getValue() = "1" or
e.(ConstantString).getStringValue() = "" or
e.(BooleanLiteral).getValue() = "false" or
// initialising to an empty array or object literal, even if unnecessary,
// can convey useful type information to the reader
e.(ArrayExpr).getSize() = 0 or
e.(ObjectExpr).getNumProperty() = 0
}
from VarDef dead, PurelyLocalVariable v // captured variables may be read by closures, so don't flag them
where deadStoreOfLocal(dead, v) and
// the variable should be accessed somewhere; otherwise it will be flagged by UnusedVariable

View File

@@ -0,0 +1,9 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<include src="DeadStore.qhelp" />
</qhelp>

View File

@@ -0,0 +1,142 @@
/**
* @name Useless assignment to property
* @description An assignment to a property whose value is always overwritten has no effect.
* @kind problem
* @problem.severity warning
* @id js/useless-assignment-to-property
* @tags maintainability
* @precision high
*/
import javascript
import Expressions.DOMProperties
import DeadStore
/**
* Holds if `write` writes to property `name` of `base`, and `base` is the only base object of `write`.
*/
predicate unambiguousPropWrite(DataFlow::SourceNode base, string name, DataFlow::PropWrite write) {
write = base.getAPropertyWrite(name) and
not exists (DataFlow::SourceNode otherBase |
otherBase != base and
write = otherBase.getAPropertyWrite(name)
)
}
/**
* Holds if `assign1` and `assign2` both assign property `name` of the same object, and `assign2` post-dominates `assign1`.
*/
predicate postDominatedPropWrite(string name, DataFlow::PropWrite assign1, DataFlow::PropWrite assign2) {
exists (ControlFlowNode write1, ControlFlowNode write2, DataFlow::SourceNode base, ReachableBasicBlock block1, ReachableBasicBlock block2 |
write1 = assign1.getWriteNode() and
write2 = assign2.getWriteNode() and
block1 = write1.getBasicBlock() and
block2 = write2.getBasicBlock() and
unambiguousPropWrite(base, name, assign1) and
unambiguousPropWrite(base, name, assign2) and
block2.postDominates(block1) and
(block1 = block2 implies
exists (int i1, int i2 |
write1 = block1.getNode(i1) and
write2 = block2.getNode(i2) and
i1 < i2
)
)
)
}
/**
* Holds if `e` may access a property named `name`.
*/
bindingset[name]
predicate maybeAccessesProperty(Expr e, string name) {
(e.(PropAccess).getPropertyName() = name and e instanceof RValue) or
// conservatively reject all side-effects
e.isImpure()
}
/**
* Holds if `assign1` and `assign2` both assign property `name`, but `assign1` is dead because of `assign2`.
*/
predicate isDeadAssignment(string name, DataFlow::PropWrite assign1, DataFlow::PropWrite assign2) {
postDominatedPropWrite(name, assign1, assign2) and
noPropAccessBetween(name, assign1, assign2) and
not isDOMProperty(name)
}
/**
* Holds if `assign` assigns a property `name` that may be accessed somewhere else in the same block,
* `after` indicates if the access happens before or after the node for `assign`.
*/
bindingset[name]
predicate maybeAccessesAssignedPropInBlock(string name, DataFlow::PropWrite assign, boolean after) {
exists (ControlFlowNode write, ReachableBasicBlock block, int i, int j, Expr e |
write = assign.getWriteNode() and
block = assign.getBasicBlock() and
write = block.getNode(i) and
e = block.getNode(j) and
maybeAccessesProperty(e, name) |
after = true and i < j
or
after = false and j < i
)
}
/**
* Holds if `assign1` and `assign2` both assign property `name`, and the assigned property is not accessed between the two assignments.
*/
bindingset[name]
predicate noPropAccessBetween(string name, DataFlow::PropWrite assign1, DataFlow::PropWrite assign2) {
exists (ControlFlowNode write1, ControlFlowNode write2, ReachableBasicBlock block1, ReachableBasicBlock block2 |
write1 = assign1.getWriteNode() and
write2 = assign2.getWriteNode() and
write1.getBasicBlock() = block1 and
write2.getBasicBlock() = block2 and
if block1 = block2 then
// same block: check for access between
not exists (int i1, Expr mid, int i2 |
assign1.getWriteNode() = block1.getNode(i1) and
assign2.getWriteNode() = block2.getNode(i2) and
mid = block1.getNode([i1+1..i2-1]) and
maybeAccessesProperty(mid, name)
)
else
// other block:
not (
// check for an access after the first write node
maybeAccessesAssignedPropInBlock(name, assign1, true) or
// check for an access between the two write blocks
exists (ReachableBasicBlock mid |
block1.getASuccessor+() = mid and
mid.getASuccessor+() = block2 |
maybeAccessesProperty(mid.getANode(), name)
) or
// check for an access before the second write node
maybeAccessesAssignedPropInBlock(name, assign2, false)
)
)
}
from string name, DataFlow::PropWrite assign1, DataFlow::PropWrite assign2
where isDeadAssignment(name, assign1, assign2) and
// whitelist
not (
// Google Closure Compiler pattern: `o.p = o['p'] = v`
exists (PropAccess p1, PropAccess p2 |
p1 = assign1.getAstNode() and
p2 = assign2.getAstNode() |
p1 instanceof DotExpr and p2 instanceof IndexExpr
or
p2 instanceof DotExpr and p1 instanceof IndexExpr
)
or
// don't flag overwrites for default values
isDefaultInit(assign1.getRhs().asExpr().getUnderlyingValue())
or
// don't flag assignments in externs
assign1.getAstNode().inExternsFile()
or
// exclude result from js/overwritten-property
assign2.getBase() instanceof DataFlow::ObjectLiteralNode
)
select assign1.getWriteNode(), "This write to property '" + name + "' is useless, since $@ always overrides it.", assign2.getWriteNode(), "another property write"

View File

@@ -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().(DefensiveExpressionTest).getTheTestResult()) and
not whitelist(left.asExpr()) and
not whitelist(right.asExpr()) and
leftExprDescription = capitalize(getDescription(left.asExpr(), "this expression")) and

View File

@@ -0,0 +1,91 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Defensive code can prevent unforeseen circumstances from
causing fatal program behaviors.
A common defensive code pattern is to guard
against dereferencing the values <code>null</code> or
<code>undefined</code>.
However, if the situation that some defensive code guards
against never can occur, then the defensive code serves no purpose and
can safely be removed.
</p>
</overview>
<recommendation>
<p>
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.
</p>
</recommendation>
<example>
<p>
The following example shows a <code>cleanupLater</code>
function that asynchronously will perform a cleanup task after some
delay. When the cleanup task completes, the function invokes the
provided callback parameter <code>cb</code>.
To prevent a crash by invoking <code>cb</code> when it
has the value <code>undefined</code>, defensive code guards
the invocation by checking if <code>cb</code> is truthy.
</p>
<sample src="examples/UnneededDefensiveProgramming1_bad.js" />
<p>
However, the <code>cleanupLater</code> 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:
</p>
<sample src="examples/UnneededDefensiveProgramming1_good.js" />
<p>
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
<code>undefined</code> or <code>null</code>.
</p>
<sample src="examples/UnneededDefensiveProgramming2_bad.js" />
<p>
However, due to coercion rules, <code>v ==
undefined</code> holds for both the situation where <code>v</code>
is<code>undefined</code> and the situation where <code>v</code>
is<code>null</code>, so the <code>v == null</code>
guard serves no purpose, and can be removed:
</p>
<sample src="examples/UnneededDefensiveProgramming2_good.js" />
</example>
<references>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Defensive_programming">Defensive programming</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,31 @@
/**
* @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/unneeded-defensive-code
* @tags correctness
* external/cwe/cwe-570
* external/cwe/cwe-571
* @precision very-high
*/
import javascript
import semmle.javascript.DefensiveProgramming
from DefensiveExpressionTest e, boolean 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 + "."

View File

@@ -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")});

View File

@@ -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")});

View File

@@ -0,0 +1,5 @@
function setSafeStringProp(o, prop, v) {
// BAD: `v == null` is useless
var safe = v == undefined || v == null? '': v;
o[prop] = safe;
}

View File

@@ -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;
}

View File

@@ -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
@@ -95,6 +73,21 @@ 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(DefensiveExpressionTest defensive |
maybeStripLogNot(defensive.asExpr()) = e and
exists(defensive.getTheTestResult())
)
}
/**
* Holds if `e` is an expression that should not be flagged as a useless condition.
*
@@ -109,7 +102,7 @@ predicate isConstantBooleanReturnValue(Expr e) {
predicate whitelist(Expr e) {
isConstant(e) or
isConstant(e.(LogNotExpr).getOperand()) or
isDefensiveInit(e) or
isConstantDefensive(e) or // flagged by js/useless-defensive-code
isInitialParameterUse(e) or
isConstantBooleanReturnValue(e)
}

View File

@@ -0,0 +1,436 @@
/**
* Provides classes for working with defensive programming patterns.
*/
import javascript
private import semmle.javascript.dataflow.InferredTypes
/**
* A test in a defensive programming pattern.
*/
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 `DefensiveExpressionTest` 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 DefensiveExpressionTest {
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())
)
}
override boolean getTheTestResult() {
result = analyze().getTheBooleanValue()
}
}
/**
* 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.getUnderlyingValue() |
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`.
*
* 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. */
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.
*
* Example: a branch in `x === null || x === undefined`.
*/
private class CompositeUndefinedNullTestPart extends DefensiveExpressionTest {
UndefinedNullTest test;
boolean polarity;
CompositeUndefinedNullTestPart(){
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
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.
*
* Example: `if (x === null) ...`.
*/
private class SanityCheckingUndefinedNullGuard extends DefensiveExpressionTest {
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()
}
}
/**
* 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`.
*
* Examples: `x === null` or `x != 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
// 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())
)
}
override Expr getOperand() {
result = operand
}
}
/**
* 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 {
Expr target;
UndefinedNullCrashUse() {
this.(InvokeExpr).getCallee().getUnderlyingValue() = target
or
this.(PropAccess).getBase().getUnderlyingValue() = target
or
this.(MethodCallExpr).getReceiver().getUnderlyingValue() = 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`.
*
* Example: `sub()`.
*/
private class NonFunctionCallCrashUse extends Expr {
Expr target;
NonFunctionCallCrashUse() {
this.(InvokeExpr).getCallee().getUnderlyingValue() = 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(LogicalBinaryExpr op |
op.getLeftOperand() = guard and
op.getRightOperand() = result
)
or
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 |
c.getCondition() = guard |
result = c.getABranch()
)
}
/**
* 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.
*
* Examples: The condition in `if(x) { x.p; }` or `!x || x.m()`.
*/
private class UndefinedNullTruthinessGuard extends DefensiveExpressionTest {
VarRef guardVar;
boolean polarity;
UndefinedNullTruthinessGuard() {
exists (VarRef useVar |
guardVar = stripNotsAndParens(this.asExpr(), polarity) and
guardVar.getVariable() = useVar.getVariable() |
getAGuardedExpr(this.asExpr()).getUnderlyingValue().(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`.
*
* Example: the condition in `if(x !== null) { x.p; }`.
*/
private class UndefinedNullTypeGuard extends DefensiveExpressionTest {
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).getUnderlyingValue().(UndefinedNullCrashUse).getVulnerableSubexpression() = useVar
)
}
override boolean getTheTestResult() {
polarity = true and result = test.getTheTestResult()
or
polarity = false and result = test.getTheTestResult().booleanNot()
}
}
/**
* A test for the value of a `typeof` expression.
*
* Example: `typeof x === 'undefined'`.
*/
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`.
*
* Example: the condition in `if(typeof x === 'function') x()`.
*/
private class FunctionTypeGuard extends DefensiveExpressionTest {
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).getUnderlyingValue().(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.
*
* Example: `typeof x === undefined'.
*/
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()
}
}
}

View File

@@ -459,6 +459,11 @@ module DataFlow {
prop = getPropertyName() and
rhs = getRhs()
}
/**
* Gets the node where the property write happens in the control flow graph.
*/
abstract ControlFlowNode getWriteNode();
}
/**
@@ -484,6 +489,10 @@ module DataFlow {
override Node getRhs() {
result = valueNode(astNode.(LValue).getRhs())
}
override ControlFlowNode getWriteNode() {
result = astNode.(LValue).getDefNode()
}
}
/**
@@ -508,6 +517,10 @@ module DataFlow {
override Node getRhs() {
result = valueNode(prop.(ValueProperty).getInit())
}
override ControlFlowNode getWriteNode() {
result = prop
}
}
/**
@@ -537,6 +550,10 @@ module DataFlow {
propdesc.hasPropertyWrite("value", result)
)
}
override ControlFlowNode getWriteNode() {
result = odp.getAstNode()
}
}
/**
@@ -563,6 +580,10 @@ module DataFlow {
not prop instanceof AccessorMethodDefinition and
result = valueNode(prop.getInit())
}
override ControlFlowNode getWriteNode() {
result = prop
}
}
/**
@@ -587,6 +608,10 @@ module DataFlow {
override Node getRhs() {
result = valueNode(prop.getValue())
}
override ControlFlowNode getWriteNode() {
result = prop
}
}
/**
@@ -869,6 +894,11 @@ module DataFlow {
override Node getBase() {
result = valueNode(arr)
}
override ControlFlowNode getWriteNode() {
result = arr
}
}
/**

View File

@@ -15,6 +15,7 @@
import javascript
import semmle.javascript.dataflow.CallGraph
private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
private import semmle.javascript.dataflow.InferredTypes
/**
@@ -809,6 +810,74 @@ module TaintTracking {
}
/**
* A function that returns the result of a sanitizer check.
*/
private class SanitizingFunction extends Function {
Parameter sanitizedParameter;
SanitizerGuardNode sanitizer;
boolean sanitizerOutcome;
SanitizingFunction() {
exists(Expr e |
exists(Expr returnExpr |
returnExpr = sanitizer.asExpr()
or
// ad hoc support for conjunctions:
returnExpr.(LogAndExpr).getAnOperand() = sanitizer.asExpr() and sanitizerOutcome = true
or
// ad hoc support for disjunctions:
returnExpr.(LogOrExpr).getAnOperand() = sanitizer.asExpr() and sanitizerOutcome = false
|
exists(SsaExplicitDefinition ssa |
ssa.getDef().getSource() = returnExpr and
ssa.getVariable().getAUse() = getAReturnedExpr()
)
or
returnExpr = getAReturnedExpr()
) and
DataFlow::parameterNode(sanitizedParameter).flowsToExpr(e) and
sanitizer.sanitizes(sanitizerOutcome, e)
) and
getNumParameter() = 1 and
sanitizedParameter = getParameter(0)
}
/**
* Holds if this function sanitizes argument `e` of call `call`, provided the call evaluates to `outcome`.
*/
predicate isSanitizingCall(DataFlow::CallNode call, Expr e, boolean outcome) {
exists(DataFlow::Node arg |
arg.asExpr() = e and
arg = call.getArgument(0) and
call.getNumArgument() = 1 and
FlowSteps::argumentPassing(call, arg, this, sanitizedParameter) and
outcome = sanitizerOutcome
)
}
/**
* Holds if this function applies to the flow in `cfg`.
*/
predicate appliesTo(Configuration cfg) {
cfg.isBarrierGuard(sanitizer)
}
}
/**
* A call that sanitizes an argument.
*/
private class AdditionalSanitizingCall extends AdditionalSanitizerGuardNode, DataFlow::CallNode {
SanitizingFunction f;
AdditionalSanitizingCall() { f.isSanitizingCall(this, _, _) }
override predicate sanitizes(boolean outcome, Expr e) { f.isSanitizingCall(this, e, outcome) }
override predicate appliesTo(Configuration cfg) { f.appliesTo(cfg) }
}
/**
* An equality test on `e.origin` or `e.source` where `e` is a `postMessage` event object,

View File

@@ -246,3 +246,14 @@ private class SuperAgentUrlRequest extends CustomClientRequest {
}
}
/**
* A model of a URL request made using the `XMLHttpRequest` browser class.
*/
private class XMLHttpRequest extends CustomClientRequest {
XMLHttpRequest() { this = DataFlow::globalVarRef("XMLHttpRequest").getAnInstantiation() }
override DataFlow::Node getUrl() { result = getAMethodCall("open").getArgument(1) }
override DataFlow::Node getADataNode() { result = getAMethodCall("send").getArgument(0) }
}

View File

@@ -340,3 +340,24 @@ private class JQueryChainedElement extends DOM::Element {
)
}
}
/**
* A model of a URL request made using the `jQuery.ajax` or `jQuery.getJSON`.
*/
private class JQueryClientRequest extends CustomClientRequest {
JQueryClientRequest() {
exists(string name |
name = "ajax" or
name = "getJSON"
|
this = jquery().getAMemberCall(name)
)
}
override DataFlow::Node getUrl() {
result = getArgument(0) or
result = getOptionArgument([0 .. 1], "url")
}
override DataFlow::Node getADataNode() { result = getOptionArgument([0 .. 1], "data") }
}

View File

@@ -38,3 +38,25 @@
| tst.js:226:9:226:26 | -1 >= o.indexOf(v) | ExampleConfiguration | false | tst.js:226:25:226:25 | v |
| tst.js:236:9:236:24 | isWhitelisted(v) | ExampleConfiguration | true | tst.js:236:23:236:23 | v |
| tst.js:240:9:240:28 | config.allowValue(v) | ExampleConfiguration | true | tst.js:240:27:240:27 | v |
| tst.js:252:16:252:36 | whiteli ... ains(x) | ExampleConfiguration | true | tst.js:252:35:252:35 | x |
| tst.js:254:9:254:12 | f(v) | ExampleConfiguration | true | tst.js:254:11:254:11 | v |
| tst.js:261:25:261:45 | whiteli ... ains(y) | ExampleConfiguration | true | tst.js:261:44:261:44 | y |
| tst.js:264:9:264:12 | g(v) | ExampleConfiguration | true | tst.js:264:11:264:11 | v |
| tst.js:271:25:271:45 | whiteli ... ains(z) | ExampleConfiguration | true | tst.js:271:44:271:44 | z |
| tst.js:281:16:281:25 | x2 != null | ExampleConfiguration | false | tst.js:281:16:281:17 | x2 |
| tst.js:281:30:281:51 | whiteli ... ins(x2) | ExampleConfiguration | true | tst.js:281:49:281:50 | x2 |
| tst.js:283:9:283:13 | f2(v) | ExampleConfiguration | true | tst.js:283:12:283:12 | v |
| tst.js:290:16:290:25 | x3 == null | ExampleConfiguration | true | tst.js:290:16:290:17 | x3 |
| tst.js:290:30:290:51 | whiteli ... ins(x3) | ExampleConfiguration | true | tst.js:290:49:290:50 | x3 |
| tst.js:299:17:299:38 | whiteli ... ins(x4) | ExampleConfiguration | true | tst.js:299:36:299:37 | x4 |
| tst.js:308:18:308:39 | whiteli ... ins(x5) | ExampleConfiguration | true | tst.js:308:37:308:38 | x5 |
| tst.js:317:26:317:47 | whiteli ... ins(x6) | ExampleConfiguration | true | tst.js:317:45:317:46 | x6 |
| tst.js:327:25:327:34 | x7 != null | ExampleConfiguration | false | tst.js:327:25:327:26 | x7 |
| tst.js:327:39:327:60 | whiteli ... ins(x7) | ExampleConfiguration | true | tst.js:327:58:327:59 | x7 |
| tst.js:330:9:330:13 | f7(v) | ExampleConfiguration | true | tst.js:330:12:330:12 | v |
| tst.js:337:25:337:46 | whiteli ... ins(x8) | ExampleConfiguration | true | tst.js:337:44:337:45 | x8 |
| tst.js:338:16:338:25 | x8 != null | ExampleConfiguration | false | tst.js:338:16:338:17 | x8 |
| tst.js:347:29:347:50 | whiteli ... ins(x9) | ExampleConfiguration | true | tst.js:347:48:347:49 | x9 |
| tst.js:356:16:356:27 | x10 !== null | ExampleConfiguration | false | tst.js:356:16:356:18 | x10 |
| tst.js:356:32:356:48 | x10 !== undefined | ExampleConfiguration | false | tst.js:356:32:356:34 | x10 |
| tst.js:358:9:358:14 | f10(v) | ExampleConfiguration | false | tst.js:358:13:358:13 | v |

View File

@@ -36,3 +36,23 @@
| tst.js:227:14:227:14 | v | tst.js:199:13:199:20 | SOURCE() |
| tst.js:239:14:239:14 | v | tst.js:235:13:235:20 | SOURCE() |
| tst.js:243:14:243:14 | v | tst.js:235:13:235:20 | SOURCE() |
| tst.js:249:10:249:10 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:257:14:257:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:267:14:267:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:275:14:275:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:277:14:277:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:286:14:286:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:293:14:293:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:295:14:295:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:302:14:302:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:304:14:304:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:311:14:311:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:313:14:313:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:321:14:321:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:323:14:323:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:333:14:333:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:341:14:341:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:343:14:343:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:350:14:350:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:352:14:352:14 | v | tst.js:248:13:248:20 | SOURCE() |
| tst.js:359:14:359:14 | v | tst.js:248:13:248:20 | SOURCE() |

View File

@@ -31,3 +31,9 @@
| tst.js:229:14:229:14 | v | ExampleConfiguration |
| tst.js:237:14:237:14 | v | ExampleConfiguration |
| tst.js:241:14:241:14 | v | ExampleConfiguration |
| tst.js:255:14:255:14 | v | ExampleConfiguration |
| tst.js:265:14:265:14 | v | ExampleConfiguration |
| tst.js:284:14:284:14 | v | ExampleConfiguration |
| tst.js:331:14:331:14 | v | ExampleConfiguration |
| tst.js:356:16:356:27 | x10 | ExampleConfiguration |
| tst.js:361:14:361:14 | v | ExampleConfiguration |

View File

@@ -243,3 +243,122 @@ function adhocWhitelisting() {
SINK(v);
}
function IndirectSanitizer () {
var v = SOURCE();
SINK(v);
function f(x) {
return whitelist.contains(x);
}
if (f(v)) {
SINK(v);
} else {
SINK(v);
}
function g(y) {
var sanitized = whitelist.contains(y);
return sanitized;
}
if (g(v)) {
SINK(v);
} else {
SINK(v);
}
function h(z) {
var sanitized = whitelist.contains(z);
return somethingElse();
}
if (h(v)) {
SINK(v);
} else {
SINK(v);
}
function f2(x2) {
return x2 != null && whitelist.contains(x2);
}
if (f2(v)) {
SINK(v);
} else {
SINK(v);
}
function f3(x3) {
return x3 == null || whitelist.contains(x3);
}
if (f3(v)) {
SINK(v); // SANITIZATION OF THIS IS NOT YET SUPPORTED
} else {
SINK(v);
}
function f4(x4) {
return !whitelist.contains(x4);
}
if (f4(v)) {
SINK(v);
} else {
SINK(v); // SANITIZATION OF THIS IS NOT YET SUPPORTED
}
function f5(x5) {
return !!whitelist.contains(x5);
}
if (f5(v)) {
SINK(v); // SANITIZATION OF THIS IS NOT YET SUPPORTED
} else {
SINK(v);
}
function f6(x6) {
var sanitized = !whitelist.contains(x6);
return !sanitized;
}
if (f6(v)) {
SINK(v);
} else {
SINK(v); // SANITIZATION OF THIS IS NOT YET SUPPORTED
}
function f7(x7) {
var sanitized = x7 != null && whitelist.contains(x7);
return sanitized;
}
if (f7(v)) {
SINK(v);
} else {
SINK(v);
}
function f8(x8) {
var sanitized = whitelist.contains(x8);
return x8 != null && sanitized;
}
if (f8(v)) {
SINK(v); // SANITIZATION OF THIS IS NOT YET SUPPORTED
} else {
SINK(v);
}
function f9(x9) {
return unknown() && whitelist.contains(x9) && unknown();
}
if (f9(v)) {
SINK(v); // SANITIZATION OF THIS IS NOT YET SUPPORTED
} else {
SINK(v);
}
function f10(x10) {
return x10 !== null || x10 !== undefined;
}
if (f10(v)) {
SINK(v);
} else {
SINK(v);
}
}

View File

@@ -27,3 +27,8 @@
| tst.js:67:5:67:24 | superagent.post(url) |
| tst.js:68:5:68:23 | superagent.get(url) |
| tst.js:69:5:69:23 | superagent.get(url) |
| tst.js:74:5:74:29 | $.ajax( ... data}) |
| tst.js:75:5:75:35 | $.ajax( ... data}) |
| tst.js:77:5:77:32 | $.getJS ... data}) |
| tst.js:78:5:78:38 | $.getJS ... data}) |
| tst.js:80:15:80:34 | new XMLHttpRequest() |

View File

@@ -15,3 +15,6 @@
| tst.js:68:5:68:23 | superagent.get(url) | tst.js:68:34:68:43 | headerData |
| tst.js:68:5:68:23 | superagent.get(url) | tst.js:68:52:68:60 | queryData |
| tst.js:69:5:69:23 | superagent.get(url) | tst.js:69:48:69:56 | queryData |
| tst.js:74:5:74:29 | $.ajax( ... data}) | tst.js:74:24:74:27 | data |
| tst.js:77:5:77:32 | $.getJS ... data}) | tst.js:77:27:77:30 | data |
| tst.js:80:15:80:34 | new XMLHttpRequest() | tst.js:82:14:82:17 | data |

View File

@@ -31,3 +31,10 @@
| tst.js:67:5:67:24 | superagent.post(url) | tst.js:67:21:67:23 | url |
| tst.js:68:5:68:23 | superagent.get(url) | tst.js:68:20:68:22 | url |
| tst.js:69:5:69:23 | superagent.get(url) | tst.js:69:20:69:22 | url |
| tst.js:74:5:74:29 | $.ajax( ... data}) | tst.js:74:12:74:14 | url |
| tst.js:75:5:75:35 | $.ajax( ... data}) | tst.js:75:12:75:34 | {url: u ... : data} |
| tst.js:75:5:75:35 | $.ajax( ... data}) | tst.js:75:18:75:20 | url |
| tst.js:77:5:77:32 | $.getJS ... data}) | tst.js:77:15:77:17 | url |
| tst.js:78:5:78:38 | $.getJS ... data}) | tst.js:78:15:78:37 | {url: u ... : data} |
| tst.js:78:5:78:38 | $.getJS ... data}) | tst.js:78:21:78:23 | url |
| tst.js:80:15:80:34 | new XMLHttpRequest() | tst.js:81:17:81:19 | url |

View File

@@ -69,3 +69,15 @@ import {ClientRequest, net} from 'electron';
superagent.get(url).unknown(nonData).query(queryData);
});
(function() {
$.ajax(url, {data: data});
$.ajax({url: url, tdata: data});
$.getJSON(url, {data: data});
$.getJSON({url: url, tdata: data});
var xhr = new XMLHttpRequest();
xhr.open(_, url);
xhr.send(data);
});

View File

@@ -0,0 +1,13 @@
| real-world-examples.js:5:4:5:11 | o.p = 42 | This write to property 'p' is useless, since $@ always overrides it. | real-world-examples.js:10:2:10:9 | o.p = 42 | another property write |
| real-world-examples.js:15:9:15:18 | o.p1 += 42 | This write to property 'p1' is useless, since $@ always overrides it. | real-world-examples.js:15:2:15:18 | o.p1 = o.p1 += 42 | another property write |
| real-world-examples.js:16:11:16:20 | o.p2 *= 42 | This write to property 'p2' is useless, since $@ always overrides it. | real-world-examples.js:16:2:16:21 | o.p2 -= (o.p2 *= 42) | another property write |
| real-world-examples.js:29:5:29:12 | o.p = 42 | This write to property 'p' is useless, since $@ always overrides it. | real-world-examples.js:32:3:32:10 | o.p = 42 | another property write |
| real-world-examples.js:38:15:38:24 | o.p = f3() | This write to property 'p' is useless, since $@ always overrides it. | real-world-examples.js:38:2:38:31 | o.p = f ... : f4() | another property write |
| tst.js:3:5:3:16 | o.pure1 = 42 | This write to property 'pure1' is useless, since $@ always overrides it. | tst.js:4:5:4:16 | o.pure1 = 42 | another property write |
| tst.js:6:5:6:16 | o.pure2 = 42 | This write to property 'pure2' is useless, since $@ always overrides it. | tst.js:7:5:7:16 | o.pure2 = 43 | another property write |
| tst.js:13:5:13:16 | o.pure4 = 42 | This write to property 'pure4' is useless, since $@ always overrides it. | tst.js:15:5:15:16 | o.pure4 = 42 | another property write |
| tst.js:20:5:20:17 | o.pure6 = f() | This write to property 'pure6' is useless, since $@ always overrides it. | tst.js:21:5:21:16 | o.pure6 = 42 | another property write |
| tst.js:23:5:23:16 | o.pure7 = 42 | This write to property 'pure7' is useless, since $@ always overrides it. | tst.js:25:5:25:16 | o.pure7 = 42 | another property write |
| tst.js:76:5:76:34 | o.pure1 ... te = 42 | This write to property 'pure16_simpleAliasWrite' is useless, since $@ always overrides it. | tst.js:77:5:77:36 | o16.pur ... te = 42 | another property write |
| tst.js:95:5:95:17 | o.pure18 = 42 | This write to property 'pure18' is useless, since $@ always overrides it. | tst.js:96:5:96:17 | o.pure18 = 42 | another property write |
| tst.js:96:5:96:17 | o.pure18 = 42 | This write to property 'pure18' is useless, since $@ always overrides it. | tst.js:97:5:97:17 | o.pure18 = 42 | another property write |

View File

@@ -0,0 +1 @@
Declarations/DeadStoreOfProperty.ql

View File

@@ -0,0 +1,12 @@
(function(){
var o = {};
o.prop1 = o['prop1'] = x;
o['prop2'] = o.prop2 = x;
o.prop3 = x
o['prop3'] = x;
o['prop4'] = x;
o.prop4 = x
});

View File

@@ -0,0 +1,43 @@
// Adapted from the Google Closure externs; original copyright header included below.
/*
* Copyright 2008 The Closure Compiler Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @interface
*/
function EventTarget() {}
/**
* @constructor
* @implements {EventTarget}
* @see http://www.w3.org/TR/1998/REC-DOM-Level-1-19981001/level-one-core.html#ID-1950641247
*/
function Node() {}
/**
* @constructor
* @extends {Node}
* @see http://www.w3.org/TR/1998/REC-DOM-Level-1-19981001/level-one-core.html#ID-745549614
*/
function Element() {}
/**
* @type {number}
* @see http://www.w3.org/TR/cssom-view/#dom-element-clienttop
*/
Element.prototype.clientTop;
//semmle-extractor-options: --externs

View File

@@ -0,0 +1,13 @@
(function(){
var first = f();
function flush() {
var flushed = first;
var next = g();
first = next;
next.prev = 42;
flushed.prev = 42;
}
});

View File

@@ -0,0 +1,39 @@
(function(){
var o = f1();
while (f2()) {
if (f4()) {
o.p = 42; // NOT OK
break;
}
f5();
}
o.p = 42;
});
(function(){
var o = f1();
o.p1 = o.p1 += 42; // NOT OK
o.p2 -= (o.p2 *= 42); // NOT OK
});
(function(){
var o = f1();
o.m(function () {
if (f2()) {
} else {
try {
f3();
} catch (e) {
f4();
o.p = 42; // NOT OK
}
}
o.p = 42;
});
});
(function(){
var o = f1();
o.p = f2() ? o.p = f3() : f4(); // NOT OK
});

View File

@@ -0,0 +1,98 @@
(function(){
var o = {};
o.pure1 = 42; // NOT OK
o.pure1 = 42;
o.pure2 = 42; // NOT OK
o.pure2 = 43;
o.impure3 = 42;
f();
o.impure3 = 42;
o.pure4 = 42; // NOT OK
43;
o.pure4 = 42;
o.impure5 = 42;
o.impure5 = f();
o.pure6 = f(); // NOT OK
o.pure6 = 42;
o.pure7 = 42; // NOT OK
if(x){}
o.pure7 = 42;
o.pure8_cond = 42;
if(x){
o.pure8_cond = 42;
}
o.impure9 = 42;
f();
if(x){
}
o.impure9 = 42;
o.impure10 = 42;
if(x){
f();
}
o.impure10 = 42;
o.impure11 = 42;
if(x){
}
f();
o.impure11 = 42;
o.pure12_read = 42;
o.pure12_read;
o.pure12_read = 42;
var o2;
o.pure13_otherRead = 42;
o2.pure13_otherRead;
o.pure13_otherRead = 42;
function id14(e) {
return e;
}
var o14 = id14(o);
o.pure14_aliasRead = 42;
o14.pure14_aliasRead;
o.pure14_aliasRead = 42;
function id15(e) {
return e;
}
var o15 = id15(o);
o.pure15_aliasWrite = 42;
o15.pure15_aliasWrite = 42;
var o16 = x? o: null;
o.pure16_simpleAliasWrite = 42; // NOT OK
o16.pure16_simpleAliasWrite = 42;
var o17 = {
duplicate17: 42,
duplicate17: 42
}
// DOM
o.clientTop = 42;
o.clientTop = 42;
o.defaulted1 = null;
o.defaulted1 = 42;
o.defaulted2 = -1;
o.defaulted2 = 42;
var o = {};
o.pure18 = 42; // NOT OK
o.pure18 = 42; // NOT OK
o.pure18 = 42;
});

View File

@@ -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' |

View File

@@ -0,0 +1 @@
Expressions/HeterogeneousComparison.ql

View File

@@ -0,0 +1,72 @@
| 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: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. |

View File

@@ -0,0 +1 @@
Expressions/UnneededDefensiveProgramming.ql

View File

@@ -0,0 +1 @@
Statements/UselessConditional.ql

View File

@@ -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
});

View File

@@ -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
});

View File

@@ -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; // NOT OK
n_ = n_ || e; // NOT OK
o_ = o_ || e; // NOT OK
x_ = x_ || e;
u && u.p; // NOT OK
n && n.p; // NOT OK
o && o.p; // NOT OK
x && x.p;
u && u(); // NOT OK
n && n(); // NOT OK
o && o(); // NOT OK
x && x();
!u || u.p; // NOT OK
!n || n.p; // NOT OK
!o || o.p; // NOT OK
!x || x.p;
!!u && u.p; // NOT OK
!!n && n.p; // NOT OK
!!o && o.p; // NOT OK
!!x && x.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; // NOT OK
n == undefined || n.p; // NOT OK
o == undefined || o.p; // NOT OK
x == undefined || x.p;
u === undefined || u.p; // NOT OK
n === undefined || n.p; // NOT OK
o === undefined || o.p; // NOT OK
x === undefined || x.p;
if (u) { // NOT OK
u.p;
}
if (n) { // NOT OK
n.p;
}
if (o) { // NOT OK
o.p;
}
if (x) {
x.p;
}
u? u():_; // NOT OK
n? n(): _; // NOT OK
o? o(): _; // NOT OK
x? x(): _;
if (u !== undefined) { // NOT OK
u.p;
}
if (n !== undefined) { // NOT OK
n.p;
}
if (o !== undefined) { // NOT OK
o.p;
}
if (x !== undefined) {
x.p;
}
if (u == undefined){} // NOT OK
if (n == undefined){} // NOT OK
if (o == undefined){} // NOT OK
if (x == undefined){}
if (u != undefined){} // NOT OK
if (n != undefined){} // NOT OK
if (o != undefined){} // NOT OK
if (x != 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(); // NOT OK
typeof u === "function" && u(); // NOT OK
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(); // 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();
(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; // NOT OK
x == undefined && x == null;
x === undefined && x === null; // NOT OK
if (x === undefined) {
if (x === null) { // NOT OK
}
}
x !== undefined && x !== null;
if (x !== undefined) {
if (x !== null) {
}
}
x == undefined && x == null;
if (x == undefined) {
if (x == null) {
}
}
x != undefined && x != null; // NOT OK
if (x != undefined) {
if (x != null) { // NOT OK
}
}
if (typeof x !== undefined);
if (typeof window !== undefined);
if (typeof x !== x);
if (typeof x !== u); // NOT OK
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);
});

View File

@@ -0,0 +1,10 @@
(function(){
var v;
(function(){
if(typeof v === "undefined"){ // NOT OK
v = 42;
}
for(var v in x){
}
});
});

View File

@@ -2,12 +2,12 @@
| express.js:12:26:12:44 | req.param("target") | Untrusted URL redirection due to $@. | express.js:12:26:12:44 | req.param("target") | user-provided value |
| express.js:33:18:33:23 | target | Untrusted URL redirection due to $@. | express.js:27:16:27:34 | req.param("target") | user-provided value |
| express.js:35:16:35:21 | target | Untrusted URL redirection due to $@. | express.js:27:16:27:34 | req.param("target") | user-provided value |
| express.js:44:16:44:108 | (req.pa ... ntacts" | Untrusted URL redirection due to $@. | express.js:44:69:44:87 | req.param('action') | user-provided value |
| express.js:53:26:53:28 | url | Untrusted URL redirection due to $@. | express.js:48:16:48:28 | req.params[0] | user-provided value |
| express.js:78:16:78:43 | `${req. ... )}/foo` | Untrusted URL redirection due to $@. | express.js:78:19:78:37 | req.param("target") | user-provided value |
| express.js:94:18:94:23 | target | Untrusted URL redirection due to $@. | express.js:87:16:87:34 | req.param("target") | user-provided value |
| express.js:101:16:101:21 | target | Untrusted URL redirection due to $@. | express.js:87:16:87:34 | req.param("target") | user-provided value |
| express.js:122:16:122:72 | [req.qu ... oin('') | Untrusted URL redirection due to $@. | express.js:122:17:122:30 | req.query.page | user-provided value |
| express.js:40:16:40:108 | (req.pa ... ntacts" | Untrusted URL redirection due to $@. | express.js:40:69:40:87 | req.param('action') | user-provided value |
| express.js:49:26:49:28 | url | Untrusted URL redirection due to $@. | express.js:44:16:44:28 | req.params[0] | user-provided value |
| express.js:74:16:74:43 | `${req. ... )}/foo` | Untrusted URL redirection due to $@. | express.js:74:19:74:37 | req.param("target") | user-provided value |
| express.js:90:18:90:23 | target | Untrusted URL redirection due to $@. | express.js:83:16:83:34 | req.param("target") | user-provided value |
| express.js:97:16:97:21 | target | Untrusted URL redirection due to $@. | express.js:83:16:83:34 | req.param("target") | user-provided value |
| express.js:118:16:118:72 | [req.qu ... oin('') | Untrusted URL redirection due to $@. | express.js:118:17:118:30 | req.query.page | user-provided value |
| node.js:7:34:7:39 | target | Untrusted URL redirection due to $@. | node.js:6:26:6:32 | req.url | user-provided value |
| node.js:15:34:15:45 | '/' + target | Untrusted URL redirection due to $@. | node.js:11:26:11:32 | req.url | user-provided value |
| node.js:32:34:32:55 | target ... =" + me | Untrusted URL redirection due to $@. | node.js:29:26:29:32 | req.url | user-provided value |

View File

@@ -35,10 +35,6 @@ app.get('/some/path', function(req, res) {
res.redirect(target);
});
function isLocalURL(target) {
return new RegExp("^/(?![/\\])|^~/").exec(target);
}
app.get('/foo', function(req, res) {
// BAD: may be a global redirection
res.redirect((req.param('action') && req.param('action') != "") ? req.param('action') : "/google_contacts")