Merge pull request #1032 from xiemaisi/master-for-merge

Merge master into rc/1.20
This commit is contained in:
Max Schaefer
2019-03-04 21:23:51 +00:00
committed by GitHub
452 changed files with 32902 additions and 30435 deletions

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

@@ -95,9 +95,9 @@ predicate isDerivedFromLength(DataFlow::Node length, DataFlow::Node operand) {
or
isDerivedFromLength(length.getAPredecessor(), operand)
or
exists(SubExpr sub |
isDerivedFromLength(sub.getAnOperand().flow(), operand) and
length = sub.flow()
exists(BinaryExpr expr | expr instanceof SubExpr or expr instanceof AddExpr |
isDerivedFromLength(expr.getAnOperand().flow(), operand) and
length = expr.flow()
)
}

View File

@@ -0,0 +1,67 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Extracting files from a malicious zip archive without validating that the destination file path
is within the destination directory can cause files outside the destination directory to be
overwritten, due to the possible presence of directory traversal elements (<code>..</code>) in
archive paths.</p>
<p>Zip archives contain archive entries representing each file in the archive. These entries
include a file path for the entry, but these file paths are not restricted and may contain
unexpected special elements such as the directory traversal element (<code>..</code>). If these
file paths are used to determine an output file to write the contents of the archive item to, then
the file may be written to an unexpected location. This can result in sensitive information being
revealed or deleted, or an attacker being able to influence behavior by modifying unexpected
files.</p>
<p>For example, if a zip file contains a file entry <code>..\sneaky-file</code>, and the zip file
is extracted to the directory <code>c:\output</code>, then naively combining the paths would result
in an output file path of <code>c:\output\..\sneaky-file</code>, which would cause the file to be
written to <code>c:\sneaky-file</code>.</p>
</overview>
<recommendation>
<p>Ensure that output paths constructed from zip archive entries are validated
to prevent writing files to unexpected locations.</p>
<p>The recommended way of writing an output file from a zip archive entry is to check that
<code>".."</code> does not occur in the path.
</p>
</recommendation>
<example>
<p>
In this example an archive is extracted without validating file paths.
If <code>archive.zip</code> contained relative paths (for
instance, if it were created by something like <code>zip archive.zip
../file.txt</code>) then executing this code could write to locations
outside the destination directory.
</p>
<sample src="ZipSlipBad.js" />
<p>To fix this vulnerability, we need to check that the path does not
contain any <code>".."</code> elements in it.
</p>
<sample src="ZipSlipGood.js" />
</example>
<references>
<li>
Snyk:
<a href="https://snyk.io/research/zip-slip-vulnerability">Zip Slip Vulnerability</a>.
</li>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Path_traversal">Path Traversal</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,22 @@
/**
* @name Arbitrary file write during zip extraction ("Zip Slip")
* @description Extracting files from a malicious zip archive without validating that the
* destination file path is within the destination directory can cause files outside
* the destination directory to be overwritten.
* @kind path-problem
* @id js/zipslip
* @problem.severity error
* @precision medium
* @tags security
* external/cwe/cwe-022
*/
import javascript
import semmle.javascript.security.dataflow.ZipSlip::ZipSlip
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"Unsanitized zip archive $@, which may contain '..', is used in a file system operation.",
source.getNode(), "item path"

View File

@@ -0,0 +1,10 @@
const fs = require('fs');
const unzip = require('unzip');
fs.createReadStream('archive.zip')
.pipe(unzip.Parse())
.on('entry', entry => {
const fileName = entry.path;
// BAD: This could write any file on the filesystem.
entry.pipe(fs.createWriteStream(fileName));
});

View File

@@ -0,0 +1,15 @@
const fs = require('fs');
const unzip = require('unzip');
fs.createReadStream('archive.zip')
.pipe(unzip.Parse())
.on('entry', entry => {
const fileName = entry.path;
// GOOD: ensures the path is safe to write to.
if (fileName.indexOf('..') == -1) {
entry.pipe(fs.createWriteStream(fileName));
}
else {
console.log('skipping bad path', fileName);
}
});

View File

@@ -112,14 +112,18 @@ module StringOps {
}
/**
* A call of form `_.startsWith(A, B)` or `ramda.startsWith(A, B)`.
* A call of form `_.startsWith(A, B)` or `ramda.startsWith(A, B)` or `goog.string.startsWith(A, B)`.
*/
private class StartsWith_Library extends Range, DataFlow::CallNode {
StartsWith_Library() {
getNumArgument() = 2 and
exists(DataFlow::SourceNode callee | this = callee.getACall() |
callee = LodashUnderscore::member("startsWith") or
callee = DataFlow::moduleMember("ramda", "startsWith")
callee = DataFlow::moduleMember("ramda", "startsWith") or
exists(string name |
callee = Closure::moduleImport("goog.string." + name) and
(name = "startsWith" or name = "caseInsensitiveStartsWith")
)
)
}
@@ -250,6 +254,9 @@ module StringOps {
exists(string name |
this = LodashUnderscore::member(name).getACall() and
(name = "includes" or name = "include" or name = "contains")
or
this = Closure::moduleImport("goog.string." + name).getACall() and
(name = "contains" or name = "caseInsensitiveContains")
)
}
@@ -416,7 +423,11 @@ module StringOps {
getNumArgument() = 2 and
exists(DataFlow::SourceNode callee | this = callee.getACall() |
callee = LodashUnderscore::member("endsWith") or
callee = DataFlow::moduleMember("ramda", "endsWith")
callee = DataFlow::moduleMember("ramda", "endsWith") or
exists(string name |
callee = Closure::moduleImport("goog.string." + name) and
(name = "endsWith" or name = "caseInsensitiveEndsWith")
)
)
}

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() }
}
@@ -1078,6 +1089,16 @@ module DataFlow {
)
}
/**
* Holds if there is a step from `pred` to `succ` through a field accessed through `this` in a class.
*/
predicate localFieldStep(DataFlow::Node pred, DataFlow::Node succ) {
exists (ClassNode cls, string prop |
pred = cls.getAReceiverNode().getAPropertyWrite(prop).getRhs() and
succ = cls.getAReceiverNode().getAPropertyRead(prop)
)
}
/**
* Gets the data flow node representing the source of definition `def`, taking
* flow through IIFE calls into account.

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

@@ -650,6 +650,15 @@ class ClassNode extends DataFlow::SourceNode {
.(AbstractCallable)
.getFunction()
}
/**
* Gets the receiver of an instance member or constructor of this class.
*/
DataFlow::SourceNode getAReceiverNode() {
result = getConstructor().getReceiver()
or
result = getAnInstanceMember().getReceiver()
}
}
module ClassNode {

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

@@ -604,14 +604,15 @@ module Express {
}
/**
* An argument passed to the `send` method of an HTTP response object.
* An argument passed to the `send` or `end` method of an HTTP response object.
*/
private class ResponseSendArgument extends HTTP::ResponseSendArgument {
RouteHandler rh;
ResponseSendArgument() {
exists(MethodCallExpr mce |
mce.calls(rh.getAResponseExpr(), "send") and
exists(MethodCallExpr mce, string name |
mce.calls(rh.getAResponseExpr(), name) and
(name = "send" or name = "end") and
this = mce.getArgument(0)
)
}

View File

@@ -0,0 +1,111 @@
/**
* Provides a taint tracking configuration for reasoning about unsafe zip extraction.
*/
import javascript
module ZipSlip {
/**
* A data flow source for unsafe zip extraction.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for unsafe zip extraction.
*/
abstract class Sink extends DataFlow::Node { }
/**
* A sanitizer guard for unsafe zip extraction.
*/
abstract class SanitizerGuard extends TaintTracking::SanitizerGuardNode, DataFlow::ValueNode { }
/** A taint tracking configuration for unsafe zip extraction. */
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "ZipSlip" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode nd) {
nd instanceof SanitizerGuard
}
}
/**
* Gets a node that can be a parsed zip archive.
*/
private DataFlow::SourceNode parsedArchive() {
result = DataFlow::moduleImport("unzip").getAMemberCall("Parse")
or
// `streamProducer.pipe(unzip.Parse())` is a typical (but not
// universal) pattern when using nodejs streams, whose return
// value is the parsed stream.
exists(DataFlow::MethodCallNode pipe |
pipe = result and
pipe.getMethodName() = "pipe" and
parsedArchive().flowsTo(pipe.getArgument(0))
)
}
/** A zip archive entry path access, as a source for unsafe zip extraction. */
class UnzipEntrySource extends Source {
// For example, in
// ```javascript
// const unzip = require('unzip');
//
// fs.createReadStream('archive.zip')
// .pipe(unzip.Parse())
// .on('entry', entry => {
// const path = entry.path;
// });
// ```
// there is an `UnzipEntrySource` node corresponding to
// the expression `entry.path`.
UnzipEntrySource() {
exists(DataFlow::CallNode cn |
cn = parsedArchive().getAMemberCall("on") and
cn.getArgument(0).mayHaveStringValue("entry") and
this = cn.getCallback(1)
.getParameter(0)
.getAPropertyRead("path"))
}
}
/** A call to `fs.createWriteStream`, as a sink for unsafe zip extraction. */
class CreateWriteStreamSink extends Sink {
CreateWriteStreamSink() {
// This is not covered by `FileSystemWriteSink`, because it is
// required that a write actually takes place to the stream.
// However, we want to consider even the bare `createWriteStream`
// to be a zipslip vulnerability since it may truncate an
// existing file.
this = DataFlow::moduleImport("fs").getAMemberCall("createWriteStream").getArgument(0)
}
}
/** A file path of a file write, as a sink for unsafe zip extraction. */
class FileSystemWriteSink extends Sink {
FileSystemWriteSink() { exists(FileSystemWriteAccess fsw | fsw.getAPathArgument() = this) }
}
/**
* Gets a string which is sufficient to exclude to make
* a filepath definitely not refer to parent directories.
*/
private string getAParentDirName() { result = ".." or result = "../" }
/** A check that a path string does not include '..' */
class NoParentDirSanitizerGuard extends SanitizerGuard {
StringOps::Includes incl;
NoParentDirSanitizerGuard() { this = incl }
override predicate sanitizes(boolean outcome, Expr e) {
incl.getPolarity().booleanNot() = outcome and
incl.getBaseString().asExpr() = e and
incl.getSubstring().mayHaveStringValue(getAParentDirName())
}
}
}

View File

@@ -0,0 +1,3 @@
| tst2.js:3:14:3:14 | x | tst2.js:7:5:7:10 | this.x |
| tst2.js:3:14:3:14 | x | tst2.js:8:25:8:30 | this.x |
| tst2.js:3:14:3:14 | x | tst2.js:12:12:12:17 | this.x |

View File

@@ -0,0 +1,5 @@
import javascript
from DataFlow::Node pred, DataFlow::Node succ
where DataFlow::localFieldStep(pred, succ)
select pred, succ

View File

@@ -1,4 +1,6 @@
| namespace.js:5:32:5:44 | function() {} | Baz.method | method |
| tst2.js:6:9:9:3 | () {\\n ... .x;\\n } | C.method | method |
| tst2.js:11:13:13:3 | () {\\n ... .x;\\n } | C.getter | getter |
| tst.js:4:17:4:21 | () {} | A.instanceMethod | method |
| tst.js:7:6:7:10 | () {} | A.bar | method |
| tst.js:9:10:9:14 | () {} | A.baz | getter |

View File

@@ -1,4 +1,5 @@
| namespace.js:5:32:5:44 | function() {} | Baz.method |
| tst2.js:6:9:9:3 | () {\\n ... .x;\\n } | C.method |
| tst.js:4:17:4:21 | () {} | A.instanceMethod |
| tst.js:7:6:7:10 | () {} | A.bar |
| tst.js:17:19:17:31 | function() {} | B.foo |

View File

@@ -0,0 +1,18 @@
| namespace.js:3:15:3:31 | function Baz() {} | namespace.js:3:15:3:14 | this |
| namespace.js:3:15:3:31 | function Baz() {} | namespace.js:5:32:5:31 | this |
| tst2.js:1:1:14:1 | class C ... ;\\n }\\n} | tst2.js:2:14:2:13 | this |
| tst2.js:1:1:14:1 | class C ... ;\\n }\\n} | tst2.js:6:9:6:8 | this |
| tst2.js:1:1:14:1 | class C ... ;\\n }\\n} | tst2.js:11:13:11:12 | this |
| tst.js:3:1:10:1 | class A ... () {}\\n} | tst.js:3:9:3:8 | this |
| tst.js:3:1:10:1 | class A ... () {}\\n} | tst.js:4:17:4:16 | this |
| tst.js:3:1:10:1 | class A ... () {}\\n} | tst.js:7:6:7:5 | this |
| tst.js:3:1:10:1 | class A ... () {}\\n} | tst.js:9:10:9:9 | this |
| tst.js:13:1:13:21 | class A ... ds A {} | tst.js:13:20:13:19 | this |
| tst.js:15:1:15:15 | function B() {} | tst.js:15:1:15:0 | this |
| tst.js:15:1:15:15 | function B() {} | tst.js:17:19:17:18 | this |
| tst.js:19:1:19:15 | function C() {} | tst.js:19:1:19:0 | this |
| tst.js:19:1:19:15 | function C() {} | tst.js:21:19:21:18 | this |
| tst.js:23:1:23:15 | function D() {} | tst.js:23:1:23:0 | this |
| tst.js:23:1:23:15 | function D() {} | tst.js:25:13:25:12 | this |
| tst.js:23:1:23:15 | function D() {} | tst.js:26:13:26:12 | this |
| tst.js:23:1:23:15 | function D() {} | tst.js:27:4:27:3 | this |

View File

@@ -0,0 +1,4 @@
import javascript
from DataFlow::ClassNode cls
select cls, cls.getAReceiverNode()

View File

@@ -0,0 +1,14 @@
class C {
constructor(x) {
this.x = x;
}
method() {
this.x;
let closure = () => this.x;
}
get getter() {
return this.x;
}
}

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

@@ -1,3 +1,5 @@
| tst.js:5:7:5:19 | A.endsWith(B) | tst.js:5:7:5:7 | A | tst.js:5:18:5:18 | B | true |
| tst.js:6:7:6:22 | _.endsWith(A, B) | tst.js:6:18:6:18 | A | tst.js:6:21:6:21 | B | true |
| tst.js:7:7:7:22 | R.endsWith(A, B) | tst.js:7:18:7:18 | A | tst.js:7:21:7:21 | B | true |
| tst.js:6:7:6:19 | A.endsWith(B) | tst.js:6:7:6:7 | A | tst.js:6:18:6:18 | B | true |
| tst.js:7:7:7:22 | _.endsWith(A, B) | tst.js:7:18:7:18 | A | tst.js:7:21:7:21 | B | true |
| tst.js:8:7:8:22 | R.endsWith(A, B) | tst.js:8:18:8:18 | A | tst.js:8:21:8:21 | B | true |
| tst.js:9:7:9:28 | strings ... h(A, B) | tst.js:9:24:9:24 | A | tst.js:9:27:9:27 | B | true |
| tst.js:10:7:10:43 | strings ... h(A, B) | tst.js:10:39:10:39 | A | tst.js:10:42:10:42 | B | true |

View File

@@ -1,8 +1,11 @@
import * as _ from 'underscore';
import * as R from 'ramda';
let strings = goog.require('goog.string');
function test() {
if (A.endsWith(B)) {}
if (_.endsWith(A, B)) {}
if (R.endsWith(A, B)) {}
if (strings.endsWith(A, B)) {}
if (strings.caseInsensitiveEndsWith(A, B)) {}
}

View File

@@ -1,7 +1,9 @@
| tst.js:4:7:4:19 | A.includes(B) | tst.js:4:7:4:7 | A | tst.js:4:18:4:18 | B | true |
| tst.js:5:7:5:22 | _.includes(A, B) | tst.js:5:18:5:18 | A | tst.js:5:21:5:21 | B | true |
| tst.js:6:7:6:25 | A.indexOf(B) !== -1 | tst.js:6:7:6:7 | A | tst.js:6:17:6:17 | B | true |
| tst.js:7:7:7:23 | A.indexOf(B) >= 0 | tst.js:7:7:7:7 | A | tst.js:7:17:7:17 | B | true |
| tst.js:8:7:8:19 | ~A.indexOf(B) | tst.js:8:8:8:8 | A | tst.js:8:18:8:18 | B | true |
| tst.js:11:7:11:25 | A.indexOf(B) === -1 | tst.js:11:7:11:7 | A | tst.js:11:17:11:17 | B | false |
| tst.js:12:7:12:22 | A.indexOf(B) < 0 | tst.js:12:7:12:7 | A | tst.js:12:17:12:17 | B | false |
| tst.js:5:7:5:19 | A.includes(B) | tst.js:5:7:5:7 | A | tst.js:5:18:5:18 | B | true |
| tst.js:6:7:6:22 | _.includes(A, B) | tst.js:6:18:6:18 | A | tst.js:6:21:6:21 | B | true |
| tst.js:7:7:7:25 | A.indexOf(B) !== -1 | tst.js:7:7:7:7 | A | tst.js:7:17:7:17 | B | true |
| tst.js:8:7:8:23 | A.indexOf(B) >= 0 | tst.js:8:7:8:7 | A | tst.js:8:17:8:17 | B | true |
| tst.js:9:7:9:19 | ~A.indexOf(B) | tst.js:9:8:9:8 | A | tst.js:9:18:9:18 | B | true |
| tst.js:12:7:12:25 | A.indexOf(B) === -1 | tst.js:12:7:12:7 | A | tst.js:12:17:12:17 | B | false |
| tst.js:13:7:13:22 | A.indexOf(B) < 0 | tst.js:13:7:13:7 | A | tst.js:13:17:13:17 | B | false |
| tst.js:20:7:20:28 | strings ... s(A, B) | tst.js:20:24:20:24 | A | tst.js:20:27:20:27 | B | true |
| tst.js:21:7:21:43 | strings ... s(A, B) | tst.js:21:39:21:39 | A | tst.js:21:42:21:42 | B | true |

View File

@@ -1,4 +1,5 @@
import * as _ from 'lodash';
let strings = goog.require('goog.string');
function test() {
if (A.includes(B)) {}
@@ -15,4 +16,7 @@ function test() {
if (A.indexOf(B) === 0) {}
if (A.indexOf(B) !== 0) {}
if (A.indexOf(B) > 0) {}
if (strings.contains(A, B)) {}
if (strings.caseInsensitiveContains(A, B)) {}
}

View File

@@ -1,14 +1,16 @@
| tst.js:5:9:5:23 | A.startsWith(B) | tst.js:5:9:5:9 | A | tst.js:5:22:5:22 | B | true |
| tst.js:6:9:6:26 | _.startsWith(A, B) | tst.js:6:22:6:22 | A | tst.js:6:25:6:25 | B | true |
| tst.js:7:9:7:26 | R.startsWith(A, B) | tst.js:7:22:7:22 | A | tst.js:7:25:7:25 | B | true |
| tst.js:8:9:8:26 | A.indexOf(B) === 0 | tst.js:8:9:8:9 | A | tst.js:8:19:8:19 | B | true |
| tst.js:9:9:9:26 | A.indexOf(B) !== 0 | tst.js:9:9:9:9 | A | tst.js:9:19:9:19 | B | false |
| tst.js:10:9:10:26 | 0 !== A.indexOf(B) | tst.js:10:15:10:15 | A | tst.js:10:25:10:25 | B | false |
| tst.js:11:9:11:25 | 0 != A.indexOf(B) | tst.js:11:14:11:14 | A | tst.js:11:24:11:24 | B | false |
| tst.js:12:9:12:20 | A.indexOf(B) | tst.js:12:9:12:9 | A | tst.js:12:19:12:19 | B | false |
| tst.js:13:10:13:21 | A.indexOf(B) | tst.js:13:10:13:10 | A | tst.js:13:20:13:20 | B | false |
| tst.js:14:11:14:22 | A.indexOf(B) | tst.js:14:11:14:11 | A | tst.js:14:21:14:21 | B | false |
| tst.js:15:9:15:38 | A.subst ... ) === B | tst.js:15:9:15:9 | A | tst.js:15:38:15:38 | B | true |
| tst.js:16:9:16:38 | A.subst ... ) !== B | tst.js:16:9:16:9 | A | tst.js:16:38:16:38 | B | false |
| tst.js:17:9:17:35 | A.subst ... ) === B | tst.js:17:9:17:9 | A | tst.js:17:35:17:35 | B | true |
| tst.js:18:9:18:36 | A.subst ... "web/" | tst.js:18:9:18:9 | A | tst.js:18:31:18:36 | "web/" | true |
| tst.js:6:9:6:23 | A.startsWith(B) | tst.js:6:9:6:9 | A | tst.js:6:22:6:22 | B | true |
| tst.js:7:9:7:26 | _.startsWith(A, B) | tst.js:7:22:7:22 | A | tst.js:7:25:7:25 | B | true |
| tst.js:8:9:8:26 | R.startsWith(A, B) | tst.js:8:22:8:22 | A | tst.js:8:25:8:25 | B | true |
| tst.js:9:9:9:26 | A.indexOf(B) === 0 | tst.js:9:9:9:9 | A | tst.js:9:19:9:19 | B | true |
| tst.js:10:9:10:26 | A.indexOf(B) !== 0 | tst.js:10:9:10:9 | A | tst.js:10:19:10:19 | B | false |
| tst.js:11:9:11:26 | 0 !== A.indexOf(B) | tst.js:11:15:11:15 | A | tst.js:11:25:11:25 | B | false |
| tst.js:12:9:12:25 | 0 != A.indexOf(B) | tst.js:12:14:12:14 | A | tst.js:12:24:12:24 | B | false |
| tst.js:13:9:13:20 | A.indexOf(B) | tst.js:13:9:13:9 | A | tst.js:13:19:13:19 | B | false |
| tst.js:14:10:14:21 | A.indexOf(B) | tst.js:14:10:14:10 | A | tst.js:14:20:14:20 | B | false |
| tst.js:15:11:15:22 | A.indexOf(B) | tst.js:15:11:15:11 | A | tst.js:15:21:15:21 | B | false |
| tst.js:16:9:16:38 | A.subst ... ) === B | tst.js:16:9:16:9 | A | tst.js:16:38:16:38 | B | true |
| tst.js:17:9:17:38 | A.subst ... ) !== B | tst.js:17:9:17:9 | A | tst.js:17:38:17:38 | B | false |
| tst.js:18:9:18:35 | A.subst ... ) === B | tst.js:18:9:18:9 | A | tst.js:18:35:18:35 | B | true |
| tst.js:19:9:19:36 | A.subst ... "web/" | tst.js:19:9:19:9 | A | tst.js:19:31:19:36 | "web/" | true |
| tst.js:32:9:32:32 | strings ... h(A, B) | tst.js:32:28:32:28 | A | tst.js:32:31:32:31 | B | true |
| tst.js:33:9:33:47 | strings ... h(A, B) | tst.js:33:43:33:43 | A | tst.js:33:46:33:46 | B | true |

View File

@@ -1,5 +1,6 @@
import * as _ from 'lodash';
import * as R from 'ramda';
let strings = goog.require('goog.string');
function f(A, B) {
if (A.startsWith(B)) {}
@@ -27,4 +28,7 @@ function f(A, B) {
if (A.indexOf(B, 2)) {}
if (~A.indexOf(B)) {} // checks for existence, not startsWith
if (A.substring(B.length) === 0) {}
if (strings.startsWith(A, B)) {}
if (strings.caseInsensitiveStartsWith(A, B)) {}
}

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

View File

@@ -8,3 +8,4 @@
| tst.js:55:32:55:71 | x.index ... gth - 1 | This suffix check is missing a length comparison to correctly handle indexOf returning -1. |
| tst.js:67:32:67:71 | x.index ... gth - 1 | This suffix check is missing a length comparison to correctly handle indexOf returning -1. |
| tst.js:76:25:76:57 | index = ... gth - 1 | This suffix check is missing a length comparison to correctly handle indexOf returning -1. |
| tst.js:80:10:80:57 | x.index ... th + 1) | This suffix check is missing a length comparison to correctly handle indexOf returning -1. |

View File

@@ -75,3 +75,7 @@ function withIndexOfCheckBad(x, y) {
let index = x.indexOf(y);
return index !== 0 && index === x.length - y.length - 1; // NOT OK
}
function plus(x, y) {
return x.indexOf("." + y) === x.length - (y.length + 1); // NOT OK
}

View File

@@ -0,0 +1,17 @@
nodes
| ZipSlipBad2.js:5:9:5:46 | fileName |
| ZipSlipBad2.js:5:20:5:46 | 'output ... ry.path |
| ZipSlipBad2.js:5:37:5:46 | entry.path |
| ZipSlipBad2.js:6:22:6:29 | fileName |
| ZipSlipBad.js:7:11:7:31 | fileName |
| ZipSlipBad.js:7:22:7:31 | entry.path |
| ZipSlipBad.js:8:37:8:44 | fileName |
edges
| ZipSlipBad2.js:5:9:5:46 | fileName | ZipSlipBad2.js:6:22:6:29 | fileName |
| ZipSlipBad2.js:5:20:5:46 | 'output ... ry.path | ZipSlipBad2.js:5:9:5:46 | fileName |
| ZipSlipBad2.js:5:37:5:46 | entry.path | ZipSlipBad2.js:5:20:5:46 | 'output ... ry.path |
| ZipSlipBad.js:7:11:7:31 | fileName | ZipSlipBad.js:8:37:8:44 | fileName |
| ZipSlipBad.js:7:22:7:31 | entry.path | ZipSlipBad.js:7:11:7:31 | fileName |
#select
| ZipSlipBad2.js:6:22:6:29 | fileName | ZipSlipBad2.js:5:37:5:46 | entry.path | ZipSlipBad2.js:6:22:6:29 | fileName | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlipBad2.js:5:37:5:46 | entry.path | item path |
| ZipSlipBad.js:8:37:8:44 | fileName | ZipSlipBad.js:7:22:7:31 | entry.path | ZipSlipBad.js:8:37:8:44 | fileName | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlipBad.js:7:22:7:31 | entry.path | item path |

View File

@@ -0,0 +1 @@
Security/CWE-022/ZipSlip.ql

View File

@@ -0,0 +1,9 @@
const fs = require('fs');
const unzip = require('unzip');
fs.createReadStream('archive.zip')
.pipe(unzip.Parse())
.on('entry', entry => {
const fileName = entry.path;
entry.pipe(fs.createWriteStream(fileName));
});

View File

@@ -0,0 +1,8 @@
var fs = require('fs');
var unzip = require('unzip');
fs.readFile('path/to/archive.zip', function (err, zipContents) {
unzip.Parse(zipContents).on('entry', function (entry) {
var fileName = 'output/path/' + entry.path;
fs.writeFileSync(fileName, entry.contents);
});
});

View File

@@ -0,0 +1,14 @@
const fs = require('fs');
const unzip = require('unzip');
fs.createReadStream('archive.zip')
.pipe(unzip.Parse())
.on('entry', entry => {
const fileName = entry.path;
if (fileName.indexOf('..') == -1) {
entry.pipe(fs.createWriteStream(fileName));
}
else {
console.log('skipping bad path', fileName);
}
});

View File

@@ -0,0 +1,11 @@
/**
* @externs
*/
var fs = {};
/**
* @param {string} filename
* @param {*} data
* @return {void}
*/
fs.writeFileSync = function(filename, data) {};