Merge pull request #946 from esben-semmle/js/captured-nodes-query-and-type-inference-1

JS: Captured Nodes, type inference + a query
This commit is contained in:
Max Schaefer
2019-03-01 10:48:52 +00:00
committed by GitHub
42 changed files with 742 additions and 58 deletions

View File

@@ -31,7 +31,7 @@
| Incomplete URL substring sanitization | correctness, security, external/cwe/cwe-020 | Highlights URL sanitizers that are likely to be incomplete, indicating a violation of [CWE-020](https://cwe.mitre.org/data/definitions/20.html). Results shown on LGTM by default. |
| Incorrect suffix check (`js/incorrect-suffix-check`) | correctness, security, external/cwe/cwe-020 | Highlights error-prone suffix checks based on `indexOf`, indicating a potential violation of [CWE-20](https://cwe.mitre.org/data/definitions/20.html). Results are shown on LGTM by default. |
| Loop iteration skipped due to shifting (`js/loop-iteration-skipped-due-to-shifting`) | correctness | Highlights code that removes an element from an array while iterating over it, causing the loop to skip over some elements. Results are shown on LGTM by default. |
| Unbound event handler receiver (`js/unbound-event-handler-receiver`) | Fewer false positive results | Additional ways that class methods can be bound are recognized. |
| Unused property (`js/unused-property`) | maintainability | Highlights properties that are unused. Results are shown on LGTM by default. |
| Useless comparison test (`js/useless-comparison-test`) | correctness | Highlights code that is unreachable due to a numeric comparison that is always true or always false. Results are shown on LGTM by default. |
## Changes to existing queries
@@ -44,9 +44,10 @@
| Insecure randomness | More results | This rule now flags insecure uses of `crypto.pseudoRandomBytes`. |
| Reflected cross-site scripting | Fewer false-positive results. | This rule now recognizes custom sanitizers. |
| Stored cross-site scripting | Fewer false-positive results. | This rule now recognizes custom sanitizers. |
| Unbound event handler receiver (`js/unbound-event-handler-receiver`) | Fewer false positive results | Additional ways that class methods can be bound are recognized. |
| Uncontrolled data used in network request | More results | This rule now recognizes host values that are vulnerable to injection. |
| Unused parameter | Fewer false-positive results | This rule no longer flags parameters with leading underscore. |
| Unused variable, import, function or class | Fewer false-positive results | This rule now flags fewer variables that are implictly used by JSX elements, and no longer flags variables with leading underscore. |
| Unused variable, import, function or class | Fewer false-positive results | This rule now flags fewer variables that are implictly used by JSX elements, no longer flags variables with leading underscore, and no longer flags variables in dead code. |
| Uncontrolled data used in path expression | Fewer false-positive results | This rule now recognizes the Express `root` option, which prevents path traversal. |
| Unneeded defensive code | More true-positive results, fewer false-positive results. | This rule now recognizes additional defensive code patterns. |
| Useless conditional | Fewer results | Additional defensive coding patterns are now ignored. |

View File

@@ -5,6 +5,7 @@
+ semmlecode-javascript-queries/Declarations/DeadStoreOfProperty.ql: /Maintainability/Declarations
+ semmlecode-javascript-queries/Declarations/DuplicateVarDecl.ql: /Maintainability/Declarations
+ semmlecode-javascript-queries/Declarations/UnusedParameter.ql: /Maintainability/Declarations
+ semmlecode-javascript-queries/Declarations/UnusedProperty.ql: /Maintainability/Declarations
+ semmlecode-javascript-queries/Declarations/UnusedVariable.ql: /Maintainability/Declarations
+ semmlecode-javascript-queries/Expressions/UnneededDefensiveProgramming.ql: /Maintainability/Expressions
+ semmlecode-javascript-queries/LanguageFeatures/ArgumentsCallerCallee.ql: /Maintainability/Language Features

View File

@@ -1,5 +1,5 @@
/**
* This library contains the majority of the 'js/unused-parameter' query implementation.
* Provides classes and predicates for the 'js/unused-parameter' query.
*
* In order to suppress alerts that are similar to the 'js/unused-parameter' alerts,
* `isAnAccidentallyUnusedParameter` should be used since it holds iff that alert is active.

View File

@@ -0,0 +1,34 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Unused object properties make code harder to maintain and use. Clients that are unaware that a
property is unused may perform nontrivial computations to compute a value that is ultimately
unused.
</p>
</overview>
<recommendation>
<p>Remove the unused property.</p>
</recommendation>
<example>
<p>
In this code, the function <code>f</code> initializes a property <code>prop_a</code> with a
call to the function <code>expensiveComputation</code>, but later on this property is never read.
Removing <code>prop</code> would improve code quality and performance.
</p>
<sample src="examples/UnusedProperty.js" />
</example>
<references>
<li>Coding Horror: <a href="http://blog.codinghorror.com/code-smells/">Code Smells</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,78 @@
/**
* @name Unused property
* @description Unused properties may be a symptom of a bug and should be examined carefully.
* @kind problem
* @problem.severity recommendation
* @id js/unused-property
* @tags maintainability
* @precision high
*/
import javascript
import semmle.javascript.dataflow.LocalObjects
import UnusedVariable
import UnusedParameter
import Expressions.ExprHasNoEffect
predicate hasUnknownPropertyRead(LocalObject obj) {
// dynamic reads
exists(DataFlow::PropRead r | obj.getAPropertyRead() = r | not exists(r.getPropertyName()))
or
// reflective reads
obj.flowsToExpr(any(EnhancedForLoop l).getIterationDomain())
or
obj.flowsToExpr(any(InExpr l).getRightOperand())
or
obj.flowsToExpr(any(SpreadElement e).getOperand())
or
exists(obj.getAPropertyRead("hasOwnProperty"))
or
exists(obj.getAPropertyRead("propertyIsEnumerable"))
}
/**
* Holds if `obj` flows to an expression that must have a specific type.
*/
predicate flowsToTypeRestrictedExpression(LocalObject obj) {
exists (Expr restricted, TypeExpr type |
obj.flowsToExpr(restricted) and
not type.isAny() |
exists (TypeAssertion assertion |
type = assertion.getTypeAnnotation() and
restricted = assertion.getExpression()
)
or
exists (BindingPattern v |
type = v.getTypeAnnotation() and
restricted = v.getAVariable().getAnAssignedExpr()
)
// no need to reason about writes to typed fields, captured nodes do not reach them
)
}
from DataFlow::PropWrite write, LocalObject obj, string name
where
write = obj.getAPropertyWrite(name) and
not exists(obj.getAPropertyRead(name)) and
// `obj` is the only base object for the write: it is not spurious
not write.getBase().analyze().getAValue() != obj.analyze().getAValue() and
not hasUnknownPropertyRead(obj) and
// avoid reporting if the definition is unreachable
write.getAstNode().getFirstControlFlowNode().getBasicBlock() instanceof ReachableBasicBlock and
// avoid implicitly read properties
not (
name = "toString" or
name = "valueOf" or
name.matches("@@%") // @@iterator, for example
) and
// avoid flagging properties that a type system requires
not flowsToTypeRestrictedExpression(obj) and
// flagged by js/unused-local-variable
not exists(UnusedLocal l | l.getAnAssignedExpr().getUnderlyingValue().flow() = obj) and
// flagged by js/unused-parameter
not exists(Parameter p | isAnAccidentallyUnusedParameter(p) |
p.getDefault().getUnderlyingValue().flow() = obj
) and
// flagged by js/useless-expression
not inVoidContext(obj.asExpr())
select write, "Unused property " + name + "."

View File

@@ -10,23 +10,7 @@
*/
import javascript
/**
* A local variable that is neither used nor exported, and is not a parameter
* or a function name.
*/
class UnusedLocal extends LocalVariable {
UnusedLocal() {
not exists(getAnAccess()) and
not exists(Parameter p | this = p.getAVariable()) and
not exists(FunctionExpr fe | this = fe.getVariable()) and
not exists(ClassExpr ce | this = ce.getVariable()) and
not exists(ExportDeclaration ed | ed.exportsAs(this, _)) and
not exists(LocalVarTypeAccess type | type.getVariable() = this) and
// common convention: variables with leading underscore are intentionally unused
getName().charAt(0) != "_"
}
}
import UnusedVariable
/**
* Holds if `v` is mentioned in a JSDoc comment in the same file, and that file
@@ -206,6 +190,10 @@ predicate unusedImports(ImportVarDeclProvider provider, string msg) {
from ASTNode sel, string msg
where
unusedNonImports(sel, msg) or
unusedImports(sel, msg)
(
unusedNonImports(sel, msg) or
unusedImports(sel, msg)
) and
// avoid reporting if the definition is unreachable
sel.getFirstControlFlowNode().getBasicBlock() instanceof ReachableBasicBlock
select sel, msg

View File

@@ -0,0 +1,22 @@
/**
* Provides classes and predicates for the 'js/unused-local-variable' query.
*/
import javascript
/**
* A local variable that is neither used nor exported, and is not a parameter
* or a function name.
*/
class UnusedLocal extends LocalVariable {
UnusedLocal() {
not exists(getAnAccess()) and
not exists(Parameter p | this = p.getAVariable()) and
not exists(FunctionExpr fe | this = fe.getVariable()) and
not exists(ClassExpr ce | this = ce.getVariable()) and
not exists(ExportDeclaration ed | ed.exportsAs(this, _)) and
not exists(LocalVarTypeAccess type | type.getVariable() = this) and
// common convention: variables with leading underscore are intentionally unused
getName().charAt(0) != "_"
}
}

View File

@@ -0,0 +1,8 @@
function f() {
var o = {
prop_a: expensiveComputation(),
prop_b: anotherComputation()
};
return o.prop_b;
}

View File

@@ -16,40 +16,7 @@ import javascript
import DOMProperties
import semmle.javascript.frameworks.xUnit
import semmle.javascript.RestrictedLocations
/**
* Holds if `e` appears in a syntactic context where its value is discarded.
*/
predicate inVoidContext(Expr e) {
exists(ExprStmt parent |
// e is a toplevel expression in an expression statement
parent = e.getParent() and
// but it isn't an HTML attribute or a configuration object
not exists(TopLevel tl | tl = parent.getParent() |
tl instanceof CodeInAttribute
or
// if the toplevel in its entirety is of the form `({ ... })`,
// it is probably a configuration object (e.g., a require.js build configuration)
tl.getNumChildStmt() = 1 and e.stripParens() instanceof ObjectExpr
)
)
or
exists(SeqExpr seq, int i, int n |
e = seq.getOperand(i) and
n = seq.getNumOperands()
|
i < n - 1 or inVoidContext(seq)
)
or
exists(ForStmt stmt | e = stmt.getUpdate())
or
exists(ForStmt stmt | e = stmt.getInit() |
// Allow the pattern `for(i; i < 10; i++)`
not e instanceof VarAccess
)
or
exists(LogicalBinaryExpr logical | e = logical.getRightOperand() and inVoidContext(logical))
}
import ExprHasNoEffect
/**
* Holds if `e` is of the form `x;` or `e.p;` and has a JSDoc comment containing a tag.

View File

@@ -0,0 +1,39 @@
/**
* Provides classes and predicates for the 'js/useless-expression' query.
*/
import javascript
/**
* Holds if `e` appears in a syntactic context where its value is discarded.
*/
predicate inVoidContext(Expr e) {
exists(ExprStmt parent |
// e is a toplevel expression in an expression statement
parent = e.getParent() and
// but it isn't an HTML attribute or a configuration object
not exists(TopLevel tl | tl = parent.getParent() |
tl instanceof CodeInAttribute
or
// if the toplevel in its entirety is of the form `({ ... })`,
// it is probably a configuration object (e.g., a require.js build configuration)
tl.getNumChildStmt() = 1 and e.stripParens() instanceof ObjectExpr
)
)
or
exists(SeqExpr seq, int i, int n |
e = seq.getOperand(i) and
n = seq.getNumOperands()
|
i < n - 1 or inVoidContext(seq)
)
or
exists(ForStmt stmt | e = stmt.getUpdate())
or
exists(ForStmt stmt | e = stmt.getInit() |
// Allow the pattern `for(i; i < 10; i++)`
not e instanceof VarAccess
)
or
exists(LogicalBinaryExpr logical | e = logical.getRightOperand() and inVoidContext(logical))
}

View File

@@ -583,7 +583,18 @@ module DataFlow {
override string getPropertyName() { result = prop.getName() }
override Node getRhs() { result = parameterNode(prop.getParameter()) }
override Node getRhs() {
exists(Parameter param, Node paramNode |
param = prop.getParameter() and
parameterNode(paramNode, param)
|
result = paramNode
or
// special case: there is no SSA flow step for unused parameters
paramNode instanceof UnusedParameterNode and
result = param.getDefault().flow()
)
}
override ControlFlowNode getWriteNode() { result = prop.getParameter() }
}

View File

@@ -0,0 +1,80 @@
/**
* Provides classes for the local objects that the dataflow library can reason about soundly.
*/
import javascript
/**
* Holds if the dataflow library can not track flow through `escape` due to `cause`.
*/
private predicate isEscape(DataFlow::Node escape, string cause) {
escape = any(DataFlow::InvokeNode invk).getAnArgument() and cause = "argument"
or
escape = any(DataFlow::FunctionNode fun).getAReturn() and cause = "return"
or
escape = any(ThrowStmt t).getExpr().flow() and cause = "throw"
or
escape = any(DataFlow::GlobalVariable v).getAnAssignedExpr().flow() and cause = "global"
or
escape = any(DataFlow::PropWrite write).getRhs() and cause = "heap"
or
escape = any(ExportDeclaration e).getSourceNode(_) and cause = "export"
or
exists (WithStmt with, Assignment assign |
with.mayAffect(assign.getLhs()) and
assign.getRhs().flow() = escape and
cause = "heap"
)
}
private DataFlow::Node getAnEscape() {
isEscape(result, _)
}
/**
* Holds if `n` can flow to a `this`-variable.
*/
private predicate exposedAsReceiver(DataFlow::SourceNode n) {
// pragmatic limitation: guarantee for object literals only
not n instanceof DataFlow::ObjectLiteralNode
or
exists(AbstractValue v | n.getAPropertyWrite().getRhs().analyze().getALocalValue() = v |
v.isIndefinite(_) or
exists(ThisExpr dis | dis.getBinder() = v.(AbstractCallable).getFunction())
)
or
n.flowsToExpr(any(FunctionBindExpr bind).getObject())
or
// technically, the builtin prototypes could have a `this`-using function through which this node escapes, but we ignore that here
// (we also ignore `o['__' + 'proto__'] = ...`)
exists(n.getAPropertyWrite("__proto__"))
or
// could check the assigned value of all affected variables, but it is unlikely to matter in practice
exists(WithStmt with | n.flowsToExpr(with.getExpr()))
}
/**
* An object that is entirely local, in the sense that the dataflow
* library models all of its flow.
*
* All uses of this node are modeled by `this.flowsTo(_)` and related predicates.
*/
class LocalObject extends DataFlow::SourceNode {
LocalObject() {
// pragmatic limitation: object literals only
this instanceof DataFlow::ObjectLiteralNode and
not flowsTo(getAnEscape()) and
not exposedAsReceiver(this)
}
predicate hasOwnProperty(string name) {
// the property is defined in the initializer,
any(DataFlow::PropWrite write).writes(this, name, _) and
// and it is never deleted
not exists(DeleteExpr del, DataFlow::PropRef ref |
del.getOperand().flow() = ref and
flowsTo(ref.getBase()) and
(ref.getPropertyName() = name or not exists(ref.getPropertyName()))
)
}
}

View File

@@ -6,6 +6,7 @@
import javascript
import AbstractValuesImpl
import semmle.javascript.dataflow.LocalObjects
/**
* Flow analysis for `this` expressions inside functions.
@@ -230,3 +231,49 @@ private class TypeInferredCalleeWithAnalyzedReturnFlow extends CallWithNonLocalA
override AnalyzedFunction getACallee() { result = fun }
}
/**
* Holds if `call` uses `receiver` as its only receiver value.
*/
pragma[noinline]
private predicate hasDefiniteReceiver(
DataFlow::MethodCallNode call, LocalObject receiver
) {
call = receiver.getAMethodCall() and
exists (DataFlow::AnalyzedNode receiverNode, AbstractValue abstractCapturedReceiver |
receiverNode = call.getReceiver() and
not receiverNode.getALocalValue().isIndefinite(_) and
abstractCapturedReceiver = receiver.analyze().getALocalValue() and
forall(DataFlow::AbstractValue v |
receiverNode.getALocalValue() = v |
v = abstractCapturedReceiver
)
)
}
/**
* Enables inter-procedural type inference for the return value of a
* method call to a flow-insensitively type-inferred callee.
*/
private class TypeInferredMethodWithAnalyzedReturnFlow extends CallWithNonLocalAnalyzedReturnFlow {
DataFlow::FunctionNode fun;
TypeInferredMethodWithAnalyzedReturnFlow() {
exists(LocalObject obj, DataFlow::PropWrite write, string name |
this.(DataFlow::MethodCallNode).getMethodName() = name and
obj.hasOwnProperty(name) and
hasDefiniteReceiver(this, obj) and
// include all potential callees
// by construction, there are no unknown methods on `obj`
write = obj.getAPropertyWrite() and
fun.flowsTo(write.getRhs()) and
(
not exists(write.getPropertyName())
or
write.getPropertyName() = name
)
)
}
override AnalyzedFunction getACallee() { result = fun }
}

View File

@@ -0,0 +1,25 @@
| method-calls.js:2:11:7:2 | {\\n\\t\\tm1: ... }; }\\n\\t} |
| method-calls.js:11:23:11:24 | {} |
| method-calls.js:16:11:16:12 | {} |
| method-calls.js:25:11:25:17 | {m: f1} |
| method-calls.js:29:11:29:17 | {m: f2} |
| method-calls.js:34:12:34:18 | {m: f3} |
| method-calls.js:38:15:38:21 | {m: f4} |
| method-calls.js:46:16:46:28 | {m: () => 42} |
| method-calls.js:50:17:50:29 | {m: () => 42} |
| method-calls.js:54:16:54:28 | {m: () => 42} |
| method-calls.js:57:16:57:28 | {m: () => 42} |
| tst.js:3:18:3:19 | {} |
| tst.js:4:18:4:36 | { f: function(){} } |
| tst.js:5:18:5:34 | { [unknown]: 42 } |
| tst.js:38:18:38:26 | { p: 42 } |
| tst.js:42:18:42:33 | { p: 42, q: 42 } |
| tst.js:52:23:52:24 | {} |
| tst.js:56:20:56:35 | { p: 42, p: 42 } |
| tst.js:59:20:59:28 | { p: 42 } |
| tst.js:63:20:63:28 | { p: 42 } |
| tst.js:68:19:68:27 | { p: 42 } |
| tst.js:73:18:73:20 | { } |
| tst.js:76:18:76:26 | { p: 42 } |
| tst.js:77:18:77:28 | { p: true } |
| tst.js:82:20:82:21 | {} |

View File

@@ -0,0 +1,4 @@
import javascript
import semmle.javascript.dataflow.LocalObjects
select any(LocalObject n)

View File

@@ -0,0 +1,22 @@
| method-calls.js:2:11:7:2 | {\\n\\t\\tm1: ... }; }\\n\\t} | m1 |
| method-calls.js:2:11:7:2 | {\\n\\t\\tm1: ... }; }\\n\\t} | m2 |
| method-calls.js:2:11:7:2 | {\\n\\t\\tm1: ... }; }\\n\\t} | m3 |
| method-calls.js:2:11:7:2 | {\\n\\t\\tm1: ... }; }\\n\\t} | m4 |
| method-calls.js:25:11:25:17 | {m: f1} | m |
| method-calls.js:29:11:29:17 | {m: f2} | m |
| method-calls.js:34:12:34:18 | {m: f3} | m |
| method-calls.js:38:15:38:21 | {m: f4} | m |
| method-calls.js:46:16:46:28 | {m: () => 42} | m |
| method-calls.js:50:17:50:29 | {m: () => 42} | m |
| method-calls.js:54:16:54:28 | {m: () => 42} | m |
| method-calls.js:57:16:57:28 | {m: () => 42} | m |
| tst.js:4:18:4:36 | { f: function(){} } | f |
| tst.js:38:18:38:26 | { p: 42 } | p |
| tst.js:42:18:42:33 | { p: 42, q: 42 } | p |
| tst.js:42:18:42:33 | { p: 42, q: 42 } | q |
| tst.js:56:20:56:35 | { p: 42, p: 42 } | p |
| tst.js:59:20:59:28 | { p: 42 } | p |
| tst.js:63:20:63:28 | { p: 42 } | p |
| tst.js:68:19:68:27 | { p: 42 } | p |
| tst.js:76:18:76:26 | { p: 42 } | p |
| tst.js:77:18:77:28 | { p: true } | p |

View File

@@ -0,0 +1,6 @@
import javascript
import semmle.javascript.dataflow.LocalObjects
from LocalObject src, string name
where src.hasOwnProperty(name)
select src, name

View File

@@ -0,0 +1,12 @@
| method-calls.js:8:2:8:8 | o1.m1() | object |
| method-calls.js:9:2:9:8 | o1.m2() | object |
| method-calls.js:12:2:12:7 | o.m3() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| method-calls.js:21:2:21:7 | o2.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| method-calls.js:25:11:25:21 | {m: f1}.m() | object |
| method-calls.js:30:11:30:16 | o2.m() | object |
| method-calls.js:34:11:34:23 | ({m: f3}).m() | object |
| method-calls.js:41:11:41:16 | o4.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| method-calls.js:47:12:47:16 | o.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| method-calls.js:51:12:51:16 | o.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| method-calls.js:55:12:55:16 | o.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| method-calls.js:60:12:60:16 | o.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |

View File

@@ -0,0 +1,4 @@
import javascript
from DataFlow::MethodCallNode call
select call, call.analyze().ppTypes()

View File

@@ -0,0 +1,4 @@
| method-calls.js:25:11:25:21 | {m: f1}.m() | method-calls.js:26:2:26:3 | v1 | method-calls.js:24:23:24:24 | object literal |
| method-calls.js:30:11:30:16 | o2.m() | method-calls.js:31:2:31:3 | v2 | method-calls.js:28:23:28:24 | object literal |
| method-calls.js:34:11:34:23 | ({m: f3}).m() | method-calls.js:35:2:35:3 | v3 | method-calls.js:33:23:33:24 | object literal |
| method-calls.js:41:11:41:16 | o4.m() | method-calls.js:42:2:42:3 | v4 | file://:0:0:0:0 | indefinite value (call) |

View File

@@ -0,0 +1,5 @@
import javascript
from DataFlow::MethodCallNode call, DataFlow::Node use
where call.flowsTo(use) and use != call and not exists(use.getASuccessor())
select call, use, use.analyze().getAValue()

View File

@@ -0,0 +1,63 @@
(function() {
var o1 = {
m1: function(){ return {}; },
m2: function(){ return {}; },
m3: function(){ return {}; },
m4: function(){ return {}; }
};
o1.m1(); // analyzed precisely
o1.m2(); // analyzed precisely
unknown(o1.m2);
var o = unknown? o1: {};
o.m3(); // not analyzed precisely: `o1` is not the only receiver.
var m4 = o.m4;
m4(); // (not a method call)
var o2 = {};
o2.m = function() { return {}; };
// not analyzed precisely: `m` may be in the prototype since `m`
// is not in the initializer, and we do not attempt to reason flow
// sensitively beyond that at the moment
o2.m();
});
(function(){
function f1(){return {};}
var v1 = {m: f1}.m(); // analyzed precisely
v1 === true;
function f2(){return {};}
var o2 = {m: f2};
var v2 = o2.m(); // analyzed precisely
v2 === true;
function f3(){return {};}
var v3 = ({m: f3}).m(); // analyzed precisely
v3 === true;
function f4(){return {};}
var { o4 } = {m: f4};
// not analyzed precisely: o4 is from a destructuring assignment
// (and it is even `undefined` in this case)
var v4 = o4.m();
v4 === true;
});
(function(){
(function(o = {m: () => 42}){
var v1 = o.m(); // not analyzed precisely: `o` may be `unknown`
})(unknown);
function f(o = {m: () => 42}){
var v2 = o.m(); // not analyzed precisely: `o` may be `unknown`
};
f(unknown);
(function(o = {m: () => 42}){
var v3 = o.m(); // not analyzed precisely: `o.m` may be `unknown`
})({m: unknown});
(function(o = {m: () => 42}){
// not analyzed precisely: we only support unique receivers at
// the moment
var v4 = o.m();
})({m: () => true});
});

View File

@@ -0,0 +1,93 @@
(function capturedSource(){
let captured1 = {};
let captured2 = { f: function(){} };
let captured3 = { [unknown]: 42 };
unknown({});
function known(){}
known({});
function known_escaping(e){unknown(e)}
known_escaping({});
(function(){return {}});
(function(){throw {}});
global = {};
this.p = {};
let local_in_with;
with (unknown) {
local_in_with = {};
}
with({}){}
({ m: function(){ this; } });
({ m: unknown });
let indirectlyUnknown = unknown? unknown: function(){};
({ m: indirectlyUnknown });
});
(function capturedProperty(){
let captured1 = { p: 42 };
captured1.p;
captured1.p;
let captured2 = { p: 42, q: 42 };
captured2.p;
captured2.p;
captured2.q = 42;
captured2 = 42;
let nonObject = function(){}
nonObject.p = 42;
nonObject.p;
let nonInitializer = {};
nonInitializer.p = 42;
nonInitializer.p;
let overridden1 = { p: 42, p: 42 };
overridden1.p;
let overridden2 = { p: 42 };
overridden2.p = 42;
overridden2.p;
let overridden3 = { p: 42 };
overridden3[x] = 42;
overridden3.p;
function f(o) {
let captured3 = { p: 42 };
o = o || captured3;
o.p;
}
let captured4 = { };
captured4.p;
let captured5 = { p: 42 },
captured6 = { p: true };
(unknown? captured5: captured6).p; // could support this with a bit of extra work
(function(semiCaptured7){
if(unknown)
semiCaptured7 = {};
semiCaptured7.p = 42;
});
});
(function (){
let bound = {};
bound::unknown();
});
// semmle-extractor-options: --experimental

View File

@@ -0,0 +1,6 @@
class C {
constructor(
private readonly F: { timeout: number } = { timeout: 1500 }
) {
}
}

View File

@@ -2,6 +2,10 @@
| classes.ts:4:3:4:24 | instanc ... foo(); |
| classes.ts:8:3:8:39 | constru ... eld) {} |
| classes.ts:8:15:8:35 | public ... erField |
| classes.ts:12:5:12:68 | constru ... + 42; } |
| classes.ts:12:17:12:37 | public ... erField |
| classes.ts:16:5:16:46 | constru ... {}) {} |
| classes.ts:16:17:16:37 | public ... erField |
| tst.js:3:5:3:8 | x: 4 |
| tst.js:4:5:6:5 | func: f ... ;\\n } |
| tst.js:7:5:9:5 | f() {\\n ... ;\\n } |

View File

@@ -1,5 +1,7 @@
| classes.ts:4:3:4:24 | instanc ... foo(); | classes.ts:3:21:3:20 | this |
| classes.ts:8:15:8:35 | public ... erField | classes.ts:8:3:8:2 | this |
| classes.ts:12:17:12:37 | public ... erField | classes.ts:12:5:12:4 | this |
| classes.ts:16:17:16:37 | public ... erField | classes.ts:16:5:16:4 | this |
| tst.js:3:5:3:8 | x: 4 | tst.js:2:11:10:1 | {\\n x ... }\\n} |
| tst.js:4:5:6:5 | func: f ... ;\\n } | tst.js:2:11:10:1 | {\\n x ... }\\n} |
| tst.js:7:5:9:5 | f() {\\n ... ;\\n } | tst.js:2:11:10:1 | {\\n x ... }\\n} |

View File

@@ -2,6 +2,10 @@
| classes.ts:4:3:4:24 | instanc ... foo(); | instanceField |
| classes.ts:8:3:8:39 | constru ... eld) {} | constructor |
| classes.ts:8:15:8:35 | public ... erField | parameterField |
| classes.ts:12:5:12:68 | constru ... + 42; } | constructor |
| classes.ts:12:17:12:37 | public ... erField | parameterField |
| classes.ts:16:5:16:46 | constru ... {}) {} | constructor |
| classes.ts:16:17:16:37 | public ... erField | parameterField |
| tst.js:3:5:3:8 | x: 4 | x |
| tst.js:4:5:6:5 | func: f ... ;\\n } | func |
| tst.js:7:5:9:5 | f() {\\n ... ;\\n } | f |

View File

@@ -2,6 +2,11 @@
| classes.ts:4:3:4:24 | instanc ... foo(); | classes.ts:4:19:4:23 | foo() |
| classes.ts:8:3:8:39 | constru ... eld) {} | classes.ts:8:3:8:39 | constru ... eld) {} |
| classes.ts:8:15:8:35 | public ... erField | classes.ts:8:22:8:35 | parameterField |
| classes.ts:12:5:12:68 | constru ... + 42; } | classes.ts:12:5:12:68 | constru ... + 42; } |
| classes.ts:12:17:12:37 | public ... erField | classes.ts:12:24:12:37 | parameterField |
| classes.ts:16:5:16:46 | constru ... {}) {} | classes.ts:16:5:16:46 | constru ... {}) {} |
| classes.ts:16:17:16:37 | public ... erField | classes.ts:16:24:16:37 | parameterField |
| classes.ts:16:17:16:37 | public ... erField | classes.ts:16:41:16:42 | {} |
| tst.js:3:5:3:8 | x: 4 | tst.js:3:8:3:8 | 4 |
| tst.js:4:5:6:5 | func: f ... ;\\n } | tst.js:4:11:6:5 | functio ... ;\\n } |
| tst.js:7:5:9:5 | f() {\\n ... ;\\n } | tst.js:7:6:9:5 | () {\\n ... ;\\n } |

View File

@@ -7,3 +7,11 @@ class InstanceField {
class ParameterField {
constructor(public parameterField) {}
}
class ParameterFieldInit {
constructor(public parameterField = {}) { parameterField + 42; }
}
class ParameterFieldInitUnused {
constructor(public parameterField = {}) {}
}

View File

@@ -1,5 +1,7 @@
| classes.ts:3:21:3:20 | this | classes.ts:4:3:4:24 | instanc ... foo(); |
| classes.ts:8:3:8:2 | this | classes.ts:8:15:8:35 | public ... erField |
| classes.ts:12:5:12:4 | this | classes.ts:12:17:12:37 | public ... erField |
| classes.ts:16:5:16:4 | this | classes.ts:16:17:16:37 | public ... erField |
| tst.js:1:1:1:0 | this | tst.js:23:15:23:29 | this.someMethod |
| tst.js:1:1:1:0 | this | tst.js:24:36:24:45 | this.state |
| tst.js:2:11:10:1 | {\\n x ... }\\n} | tst.js:3:5:3:8 | x: 4 |

View File

@@ -1,5 +1,7 @@
| classes.ts:3:21:3:20 | this | instanceField | classes.ts:4:3:4:24 | instanc ... foo(); |
| classes.ts:8:3:8:2 | this | parameterField | classes.ts:8:15:8:35 | public ... erField |
| classes.ts:12:5:12:4 | this | parameterField | classes.ts:12:17:12:37 | public ... erField |
| classes.ts:16:5:16:4 | this | parameterField | classes.ts:16:17:16:37 | public ... erField |
| tst.js:1:1:1:0 | this | someMethod | tst.js:23:15:23:29 | this.someMethod |
| tst.js:1:1:1:0 | this | state | tst.js:24:36:24:45 | this.state |
| tst.js:2:11:10:1 | {\\n x ... }\\n} | f | tst.js:7:5:9:5 | f() {\\n ... ;\\n } |

View File

@@ -1,5 +1,9 @@
| classes.ts:3:21:3:20 | this | instanceField | classes.ts:4:19:4:23 | foo() |
| classes.ts:8:3:8:2 | this | parameterField | classes.ts:8:22:8:35 | parameterField |
| classes.ts:12:5:12:4 | this | parameterField | classes.ts:12:24:12:37 | parameterField |
| classes.ts:12:5:12:4 | this | parameterField | classes.ts:12:41:12:42 | {} |
| classes.ts:16:5:16:4 | this | parameterField | classes.ts:16:24:16:37 | parameterField |
| classes.ts:16:5:16:4 | this | parameterField | classes.ts:16:41:16:42 | {} |
| tst.js:2:11:10:1 | {\\n x ... }\\n} | f | tst.js:7:6:9:5 | () {\\n ... ;\\n } |
| tst.js:2:11:10:1 | {\\n x ... }\\n} | func | tst.js:4:11:6:5 | functio ... ;\\n } |
| tst.js:12:1:19:1 | class C ... ;\\n }\\n} | func | tst.js:13:14:15:3 | (x) {\\n ... x);\\n } |

View File

@@ -1,5 +1,7 @@
| classes.ts:3:21:3:20 | this | classes.ts:4:3:4:24 | instanc ... foo(); |
| classes.ts:8:3:8:2 | this | classes.ts:8:15:8:35 | public ... erField |
| classes.ts:12:5:12:4 | this | classes.ts:12:17:12:37 | public ... erField |
| classes.ts:16:5:16:4 | this | classes.ts:16:17:16:37 | public ... erField |
| tst.js:2:11:10:1 | {\\n x ... }\\n} | tst.js:3:5:3:8 | x: 4 |
| tst.js:2:11:10:1 | {\\n x ... }\\n} | tst.js:4:5:6:5 | func: f ... ;\\n } |
| tst.js:2:11:10:1 | {\\n x ... }\\n} | tst.js:7:5:9:5 | f() {\\n ... ;\\n } |

View File

@@ -1,5 +1,7 @@
| classes.ts:3:21:3:20 | this | instanceField | classes.ts:4:3:4:24 | instanc ... foo(); |
| classes.ts:8:3:8:2 | this | parameterField | classes.ts:8:15:8:35 | public ... erField |
| classes.ts:12:5:12:4 | this | parameterField | classes.ts:12:17:12:37 | public ... erField |
| classes.ts:16:5:16:4 | this | parameterField | classes.ts:16:17:16:37 | public ... erField |
| tst.js:2:11:10:1 | {\\n x ... }\\n} | f | tst.js:7:5:9:5 | f() {\\n ... ;\\n } |
| tst.js:2:11:10:1 | {\\n x ... }\\n} | func | tst.js:4:5:6:5 | func: f ... ;\\n } |
| tst.js:2:11:10:1 | {\\n x ... }\\n} | x | tst.js:3:5:3:8 | x: 4 |

View File

@@ -1,5 +1,8 @@
| classes.ts:3:21:3:20 | this | instanceField | classes.ts:4:19:4:23 | foo() |
| classes.ts:8:3:8:2 | this | parameterField | classes.ts:8:22:8:35 | parameterField |
| classes.ts:12:5:12:4 | this | parameterField | classes.ts:12:24:12:37 | parameterField |
| classes.ts:16:5:16:4 | this | parameterField | classes.ts:16:24:16:37 | parameterField |
| classes.ts:16:5:16:4 | this | parameterField | classes.ts:16:41:16:42 | {} |
| tst.js:2:11:10:1 | {\\n x ... }\\n} | f | tst.js:7:6:9:5 | () {\\n ... ;\\n } |
| tst.js:2:11:10:1 | {\\n x ... }\\n} | func | tst.js:4:11:6:5 | functio ... ;\\n } |
| tst.js:2:11:10:1 | {\\n x ... }\\n} | x | tst.js:3:8:3:8 | 4 |

View File

@@ -7,6 +7,7 @@
| tst.js:11:5:11:8 | f1() | file://:0:0:0:0 | undefined |
| tst.js:14:5:14:8 | f2() | file://:0:0:0:0 | undefined |
| tst.js:15:5:15:8 | f2() | file://:0:0:0:0 | undefined |
| tst.js:18:5:18:11 | o1.f3() | file://:0:0:0:0 | undefined |
| tst.js:23:5:23:8 | f4() | tst.js:21:16:21:30 | function f5 |
| tst.js:23:5:23:10 | f4()() | file://:0:0:0:0 | undefined |
| tst.js:30:5:30:8 | f6() | tst.js:26:16:28:9 | function f7 |

View File

@@ -11,7 +11,7 @@
| tst.js:11:5:11:8 | f1() | file://:0:0:0:0 | undefined |
| tst.js:14:5:14:8 | f2() | file://:0:0:0:0 | undefined |
| tst.js:15:5:15:8 | f2() | file://:0:0:0:0 | undefined |
| tst.js:18:5:18:11 | o1.f3() | file://:0:0:0:0 | indefinite value (call) |
| tst.js:18:5:18:11 | o1.f3() | file://:0:0:0:0 | undefined |
| tst.js:23:5:23:8 | f4() | tst.js:21:16:21:30 | function f5 |
| tst.js:23:5:23:10 | f4()() | file://:0:0:0:0 | undefined |
| tst.js:30:5:30:8 | f6() | tst.js:26:16:28:9 | function f7 |

View File

@@ -0,0 +1,9 @@
| tst.js:4:9:4:19 | unused1: 42 | Unused property unused1. |
| tst.js:19:5:19:15 | unused9: 42 | Unused property unused9. |
| tst.js:26:13:26:24 | unused11: 42 | Unused property unused11. |
| tst.js:31:13:31:35 | used12_ ... lly: 42 | Unused property used12_butNotReally. |
| tst.js:32:13:32:24 | unused12: 42 | Unused property unused12. |
| tst.js:52:3:52:14 | unused14: 42 | Unused property unused14. |
| tst.js:54:2:54:20 | captured14.unused14 | Unused property unused14. |
| tst.js:55:2:55:20 | captured14.unused14 | Unused property unused14. |
| tst.ts:24:21:24:25 | p: 42 | Unused property p. |

View File

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

View File

@@ -0,0 +1,83 @@
(function(){
var captured1 = {
used1: 42,
unused1: 42
};
captured1.used1;
var unused2 = {
unused2a: 42,
unused2b: 42
};
for (x.p in { used3: 42 });
for (x.p of { used4: 42 });
42 in { used5: 42 };
f(...{used6: 42});
[...{used7: 42}];
({...{used8: 42}});
({ unused9: 42 }) + "";
({ used10: 42 }).hasOwnProperty;
({ used10: 42 }).propertyIsEnumerable;
(function(){
var captured11 = {
used11: 42,
unused11: 42
};
captured11.used11;
var captured12 = {
used12_butNotReally: 42,
unused12: 42
};
throw x;
captured12.used12_butNotReally;
var captured13 = {
used13: 42,
unused13: 42
};
captured13.used13;
});
(function(options){
if(unknown)
options = {};
options.output = 42;
});
var captured14 = {
unused14: 42
};
captured14.unused14 = 42;
captured14.unused14 = 42;
var captured15 = {
semiUnused15: 42
};
captured15.semiUnused15 = 42;
captured15.semiUnused15;
});
(function(unusedParam = {unusedProp: 42}){
});
(function(){
var unusedObj = {
unusedProp: 42
};
});
(function(){
var unusedSpecials = {
toString: function(){},
valueOf: function(){},
'@@iterator': function(){}
};
unusedSpecials.foo;
});
(function(){
({ unusedProp: 42 }, 42);
});

View File

@@ -0,0 +1,28 @@
(function(){
var o1: { p: int, q: int } = { p: 42, q: 42 };
o1.q;
var o2 = <{ p: int, q: int }>{ p: 42, q: 42 };
o2.q;
var o3: { p: int, q: int } = f();
o3 = o3 || { p: 42, q: 42 };
o3.q;
});
class C {
private o: { p: int, q: int };
constructor() {
this.o = { p: 42, q: 42 };
this.o.q;
}
}
(function(){
var o1: any = { p: 42, q: 42 };
o1.q;
var o2: any = { p: 42, q: 42 };
var o3: { p: int, q: int } = o2;
})

View File

@@ -0,0 +1,4 @@
(function(){
throw 42;
var x = 42;
});