Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 3 additions & 24 deletions javascript/ql/src/Declarations/DeadStoreOfLocal.ql
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,6 @@ predicate deadStoreOfLocal(VarDef vd, PurelyLocalVariable v) {
not exists (SsaExplicitDefinition ssa | ssa.defines(vd, v))
}

/**
* Holds if `e` is an expression evaluating to `null` or `undefined`.
*
* This includes not only direct references to `null` and `undefined`, but
* also `void` expressions and assignments of the form `x = rhs`, where `rhs`
* is itself an expression evaluating to `null` or `undefined`.
*/
predicate isNullOrUndef(Expr e) {
exists (Expr inner |
inner = e.stripParens() |
// `null` or `undefined`
inner instanceof NullLiteral or
inner.(VarAccess).getName() = "undefined" or
inner instanceof VoidExpr or
// recursive case to catch multi-assignments of the form `x = y = null`
isNullOrUndef(inner.(AssignExpr).getRhs())
)
}

/**
* 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.
Expand All @@ -57,9 +38,7 @@ predicate isDefaultInit(Expr e) {
// 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
// recursive case
isDefaultInit(e.(AssignExpr).getRhs())
e.(ObjectExpr).getNumProperty() = 0
}

from VarDef dead, PurelyLocalVariable v // captured variables may be read by closures, so don't flag them
Expand All @@ -77,9 +56,9 @@ where deadStoreOfLocal(dead, v) and
not fd = outer.getBody().(BlockStmt).getAStmt()
) and
// don't flag overwrites with `null` or `undefined`
not isNullOrUndef(dead.getSource()) and
not SyntacticConstants::isNullOrUndefined(dead.getSource()) and
// don't flag default inits that are later overwritten
not (isDefaultInit(dead.getSource()) and dead.isOverwritten(v)) and
not (isDefaultInit(dead.getSource().(Expr).getUnderlyingValue()) and dead.isOverwritten(v)) and
// don't flag assignments in externs
not dead.(ASTNode).inExternsFile() and
// don't flag exported variables
Expand Down
26 changes: 22 additions & 4 deletions javascript/ql/src/semmle/javascript/Constants.qll
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,32 @@ module SyntacticConstants {
class WrappedConstant extends SyntacticConstant {

WrappedConstant() {
stripParens() instanceof SyntacticConstant or
this.(SeqExpr).getLastOperand() instanceof SyntacticConstant or
this.(TypeAssertion).getExpression() instanceof SyntacticConstant or
this.(Assignment).getRhs() instanceof SyntacticConstant
getUnderlyingValue() instanceof SyntacticConstant
}

}

/**
* Holds if `c` evaluates to `undefined`.
*/
predicate isUndefined(SyntacticConstant c) {
c.getUnderlyingValue() instanceof UndefinedConstant
}

/**
* Holds if `c` evaluates to `null`.
*/
predicate isNull(SyntacticConstant c) {
c.getUnderlyingValue() instanceof NullConstant
}

/**
* Holds if `c` evaluates to `null` or `undefined`.
*/
predicate isNullOrUndefined(SyntacticConstant c) {
isUndefined(c) or isNull(c)
}

}

/**
Expand Down
67 changes: 65 additions & 2 deletions javascript/ql/src/semmle/javascript/Expr.qll
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,45 @@ class ExprOrType extends @exprortype, Documentable {
)
}

/** Gets this expression or type, with any surrounding parentheses removed. */
/**
* Gets this expression or type, with any surrounding parentheses removed.
*
* Also see `getUnderlyingValue` and `getUnderlyingReference`.
*/
ExprOrType stripParens() { result = this }

/**
* Gets the innermost reference that this expression evaluates to, if any.
*
* Examples:
*
* - a variable or property access: the access itself.
* - a parenthesized expression `(e)`: the underlying reference of `e`.
* - a TypeScript type assertion `e as T`: the underlying reference of `e`.
*
* Also see `getUnderlyingValue` and `stripParens`.
*/
Expr getUnderlyingReference() {
none()
}

/**
* Gets the innermost expression that this expression evaluates to.
Comment thread
This conversation was marked as resolved.
*
* Examples:
*
* - a parenthesised expression `(e)`: the underlying value of `e`.
* - a sequence expression `e1, e2`: the underlying value of `e2`.
* - an assignment expression `v = e`: the underlying value of `e`.
* - a TypeScript type assertion `e as T`: the underlying value of `e`.
* - any other expression: the expression itself.
*
* Also see `getUnderlyingReference` and `stripParens`.
*/
Expr getUnderlyingValue() {
result = this
}

}

/** An expression. */
Expand Down Expand Up @@ -210,6 +247,15 @@ class ParExpr extends @parexpr, Expr {
override predicate isImpure() {
getExpression().isImpure()
}

override Expr getUnderlyingValue() {
result = getExpression().getUnderlyingValue()
}

override Expr getUnderlyingReference() {
result = getExpression().getUnderlyingReference()
}

}

/** A `null` literal. */
Expand Down Expand Up @@ -617,6 +663,11 @@ class SeqExpr extends @seqexpr, Expr {
override string getStringValue() {
result = getLastOperand().getStringValue()
}

override Expr getUnderlyingValue() {
result = getLastOperand().getUnderlyingValue()
}

}

/** A conditional expression. */
Expand Down Expand Up @@ -862,6 +913,11 @@ class PropAccess extends @propaccess, Expr {
override ControlFlowNode getFirstControlFlowNode() {
result = getBase().getFirstControlFlowNode()
}

override Expr getUnderlyingReference() {
result = this
}

}

/** A dot expression. */
Expand Down Expand Up @@ -1284,10 +1340,17 @@ class Assignment extends @assignment, Expr {
override ControlFlowNode getFirstControlFlowNode() {
result = getLhs().getFirstControlFlowNode()
}

}

/** A simple assignment expression. */
class AssignExpr extends @assignexpr, Assignment {}
class AssignExpr extends @assignexpr, Assignment {

override Expr getUnderlyingValue() {
Comment thread
This conversation was marked as resolved.
result = getRhs().getUnderlyingValue()
}

}

/** A compound assign expression. */
abstract class CompoundAssignExpr extends Assignment {}
Expand Down
9 changes: 9 additions & 0 deletions javascript/ql/src/semmle/javascript/TypeScript.qll
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,15 @@ class TypeAssertion extends Expr, @typeassertion {
override ControlFlowNode getFirstControlFlowNode() {
result = getExpression().getFirstControlFlowNode()
}

override Expr getUnderlyingValue() {
result = getExpression().getUnderlyingValue()
}

override Expr getUnderlyingReference() {
Comment thread
This conversation was marked as resolved.
result = getExpression().getUnderlyingReference()
}

}

/**
Expand Down
5 changes: 5 additions & 0 deletions javascript/ql/src/semmle/javascript/Variables.qll
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,11 @@ class VarRef extends @varref, Identifier, BindingPattern, LexicalRef {
override VarRef getABindingVarRef() { result = this }

override predicate isImpure() { none() }

override Expr getUnderlyingReference() {
result = this
}

}

/** An identifier that refers to a variable in a non-declaring position. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
| tst.js:47:5:47:5 | 1 |
| tst.js:48:1:48:7 | x.p = 1 |
| tst.js:48:7:48:7 | 1 |
| tst.js:49:1:49:6 | x += 1 |
| tst.js:49:6:49:6 | 1 |
| tst.ts:1:13:1:21 | <number>1 |
| tst.ts:1:21:1:21 | 1 |
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
| tst.js:3:5:3:13 | arguments | Redefinition of arguments. |
| tst.js:7:7:7:15 | arguments | Redefinition of arguments. |
| tst.js:11:11:11:19 | arguments | Redefinition of arguments. |
| tst.js:12:11:12:19 | arguments | Redefinition of arguments. |
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ function f() {
function g(x, y) {
var arguments = [y, x]; // NOT OK
}

(function (){
for ([arguments] of o);
for ([arguments = 4] of o);
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
| tst.js:7:1:7:6 | y = 23 | Assignment to variable y, which is $@ constant. | tst.js:1:1:1:21 | const x ... y = 42; | declared |
| tst.js:10:5:10:10 | y = -1 | Assignment to variable y, which is $@ constant. | tst.js:1:1:1:21 | const x ... y = 42; | declared |
| tst.js:13:1:13:3 | ++x | Assignment to variable x, which is $@ constant. | tst.js:1:1:1:21 | const x ... y = 42; | declared |
| tst.js:25:10:25:14 | [ c ] | Assignment to variable c, which is $@ constant. | tst.js:24:5:24:19 | const c = null; | declared |
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@ var z = 56;
z = 72;

// OK
const s = "hi";
const s = "hi";

(function (){
const c = null;
for ([ c ] of o);
});
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,7 @@ function v() {
z2 = 42;
return x + y + z1 + z2;
});

(function() {
for (var a = (x, -1) in v = a, o);
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
| test.js:54:10:54:10 | z | Variable z is used like a local variable, but is missing a declaration. |
| test.js:60:6:60:6 | y | Variable y is used like a local variable, but is missing a declaration. |
| test.js:66:2:66:2 | z | Variable z is used like a local variable, but is missing a declaration. |
| test.js:72:9:72:20 | unresolvable | Variable unresolvable is used like a local variable, but is missing a declaration. |
| tst3.js:7:10:7:10 | x | Variable x is used like a local variable, but is missing a declaration. |
| tst3.js:7:16:7:19 | rest | Variable rest is used like a local variable, but is missing a declaration. |
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,10 @@ function r() {
z = {};
for (var p in z)
;
}
}

(function() {
for ([ unresolvable ] of o) {
unresolvable;
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,11 @@ function l() {

1n + 1; // NOT OK, but not currently flagged

(function(){
let sum = 0;
for ({value} of async(o)) {
sum += value;
}
});

// semmle-extractor-options: --experimental
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,15 @@
if (x !== undefined)
x.p;
})();

(function() {
var v0
for({ v0 } of o) {
v0.p;
}

var v1;
for({ v1 = x } of o) {
v1.p;
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,9 @@ function countOccurrencesDead(xs, p) {
++count;
return count;
}

(function(a) {
for([a] of o) {
a;
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
| UselessConditional.js:65:5:65:5 | x | Variable 'x' always evaluates to true here. |
| UselessConditional.js:76:13:76:13 | x | Variable 'x' always evaluates to true here. |
| UselessConditional.js:82:13:82:13 | x | Variable 'x' always evaluates to true here. |
| UselessConditional.js:89:10:89:16 | x, true | This expression always evaluates to true. |
| UselessConditionalGood.js:58:12:58:13 | x2 | Variable 'x2' always evaluates to false here. |
| UselessConditionalGood.js:69:12:69:13 | xy | Variable 'xy' always evaluates to false here. |
| UselessConditionalGood.js:85:12:85:13 | xy | Variable 'xy' always evaluates to false here. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,9 @@ async function awaitFlow(){
}
f3(true);
});

(function() {
if ((x, true));
});

// semmle-extractor-options: --experimental