mirror of
https://github.com/github/codeql.git
synced 2026-04-30 11:15:13 +02:00
Merge pull request #2106 from asger-semmle/call-graph-3
JS: Call graph changes
This commit is contained in:
@@ -61,5 +61,7 @@ where
|
||||
forex(DataFlow::InvokeNode cs2, Function otherCallee |
|
||||
cs2.getInvokeExpr() = cs.getInvokeExpr() and otherCallee = cs2.getACallee() |
|
||||
illegalInvocation(cs, otherCallee, _, _)
|
||||
)
|
||||
) and
|
||||
// require that all callees are known
|
||||
not cs.isIncomplete()
|
||||
select cs, "Illegal invocation of $@ " + how + ".", callee, calleeDesc
|
||||
|
||||
@@ -47,97 +47,6 @@ class RelevantFunction extends Function {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `name` is a the name of an external module.
|
||||
*/
|
||||
predicate isExternalLibrary(string name) {
|
||||
// Mentioned in package.json
|
||||
any(Dependency dep).info(name, _) or
|
||||
// Node.js built-in
|
||||
name = "assert" or
|
||||
name = "async_hooks" or
|
||||
name = "child_process" or
|
||||
name = "cluster" or
|
||||
name = "crypto" or
|
||||
name = "dns" or
|
||||
name = "domain" or
|
||||
name = "events" or
|
||||
name = "fs" or
|
||||
name = "http" or
|
||||
name = "http2" or
|
||||
name = "https" or
|
||||
name = "inspector" or
|
||||
name = "net" or
|
||||
name = "os" or
|
||||
name = "path" or
|
||||
name = "perf_hooks" or
|
||||
name = "process" or
|
||||
name = "punycode" or
|
||||
name = "querystring" or
|
||||
name = "readline" or
|
||||
name = "repl" or
|
||||
name = "stream" or
|
||||
name = "string_decoder" or
|
||||
name = "timer" or
|
||||
name = "tls" or
|
||||
name = "trace_events" or
|
||||
name = "tty" or
|
||||
name = "dgram" or
|
||||
name = "url" or
|
||||
name = "util" or
|
||||
name = "v8" or
|
||||
name = "vm" or
|
||||
name = "worker_threads" or
|
||||
name = "zlib"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the global variable `name` is defined externally.
|
||||
*/
|
||||
predicate isExternalGlobal(string name) {
|
||||
exists(ExternalGlobalDecl decl | decl.getName() = name)
|
||||
or
|
||||
exists(Dependency dep |
|
||||
// If name is never assigned anywhere, and it coincides with a dependency,
|
||||
// it's most likely coming from there.
|
||||
dep.info(name, _) and
|
||||
not exists(Assignment assign | assign.getLhs().(GlobalVarAccess).getName() = name)
|
||||
)
|
||||
or
|
||||
name = "_"
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that was derived from an import of `moduleName`.
|
||||
*
|
||||
* This is a rough approximation as it follows all property reads, invocations,
|
||||
* and callbacks, so some of these might refer to internal objects.
|
||||
*
|
||||
* Additionally, we don't recognize when a project imports another file in the
|
||||
* same project using its module name (for example import "vscode" from inside the vscode project).
|
||||
*/
|
||||
SourceNode externalNode() {
|
||||
exists(string moduleName |
|
||||
result = moduleImport(moduleName) and
|
||||
isExternalLibrary(moduleName)
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
result = globalVarRef(name) and
|
||||
isExternalGlobal(name)
|
||||
)
|
||||
or
|
||||
result = DOM::domValueRef()
|
||||
or
|
||||
result = jquery()
|
||||
or
|
||||
result = externalNode().getAPropertyRead()
|
||||
or
|
||||
result = externalNode().getAnInvocation()
|
||||
or
|
||||
result = externalNode().(InvokeNode).getCallback(_).getParameter(_)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node that can be resolved to a function, usually a callback.
|
||||
*
|
||||
@@ -167,7 +76,7 @@ SourceNode nodeLeadingToInvocation() {
|
||||
result.flowsTo(arg)
|
||||
)
|
||||
or
|
||||
exists(AdditionalPartialInvokeNode invoke, Node arg |
|
||||
exists(PartialInvokeNode invoke, Node arg |
|
||||
invoke.isPartialArgument(arg, _, _) and
|
||||
result.flowsTo(arg)
|
||||
)
|
||||
@@ -192,49 +101,15 @@ class ResolvableCall extends RelevantInvoke {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call site that is believed to call an external function.
|
||||
*/
|
||||
class ExternalCall extends RelevantInvoke {
|
||||
ExternalCall() {
|
||||
not this instanceof ResolvableCall and // avoid double counting
|
||||
(
|
||||
// Call to modelled external library
|
||||
this = externalNode()
|
||||
or
|
||||
// 'require' call or similar
|
||||
this = moduleImport(_)
|
||||
or
|
||||
// Resolved to externs file
|
||||
exists(this.(InvokeNode).getACallee(1))
|
||||
or
|
||||
// Modelled as taint step but isn't from an NPM module, for example, `substring` or `push`.
|
||||
exists(TaintTracking::AdditionalTaintStep step |
|
||||
step.step(_, this)
|
||||
or
|
||||
step.step(this.getAnArgument(), _)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call site that could not be resolved.
|
||||
*/
|
||||
class UnresolvableCall extends RelevantInvoke {
|
||||
UnresolvableCall() {
|
||||
not this instanceof ResolvableCall and
|
||||
not this instanceof ExternalCall
|
||||
not this instanceof ResolvableCall
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call that is believed to call a function within the same project.
|
||||
*/
|
||||
class NonExternalCall extends RelevantInvoke {
|
||||
NonExternalCall() { not this instanceof ExternalCall }
|
||||
}
|
||||
|
||||
/**
|
||||
* A function with at least one call site.
|
||||
*/
|
||||
|
||||
@@ -248,4 +248,25 @@ module Closure {
|
||||
DataFlow::SourceNode moduleImport(string moduleName) {
|
||||
getClosureNamespaceFromSourceNode(result) = moduleName
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `goog.bind`, as a partial function invocation.
|
||||
*/
|
||||
private class BindCall extends DataFlow::PartialInvokeNode::Range, DataFlow::CallNode {
|
||||
BindCall() { this = moduleImport("goog.bind").getACall() }
|
||||
|
||||
override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
|
||||
index >= 0 and
|
||||
callback = getArgument(0) and
|
||||
argument = getArgument(index + 2)
|
||||
}
|
||||
|
||||
override DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) {
|
||||
boundArgs = getNumArgument() - 2 and
|
||||
callback = getArgument(0) and
|
||||
result = this
|
||||
}
|
||||
|
||||
override DataFlow::Node getBoundReceiver() { result = getArgument(1) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,18 +432,6 @@ abstract class AdditionalSink extends DataFlow::Node {
|
||||
predicate isSinkFor(Configuration cfg, FlowLabel lbl) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An invocation that is modeled as a partial function application.
|
||||
*
|
||||
* This contributes additional argument-passing flow edges that should be added to all data flow configurations.
|
||||
*/
|
||||
abstract class AdditionalPartialInvokeNode extends DataFlow::InvokeNode {
|
||||
/**
|
||||
* Holds if `argument` is passed as argument `index` to the function in `callback`.
|
||||
*/
|
||||
abstract predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional flow step to model flow from import specifiers into the SSA variable
|
||||
* corresponding to the imported variable.
|
||||
@@ -457,45 +445,6 @@ private class FlowStepThroughImport extends AdditionalFlowStep, DataFlow::ValueN
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A partial call through the built-in `Function.prototype.bind`.
|
||||
*/
|
||||
private class BindPartialCall extends AdditionalPartialInvokeNode, DataFlow::MethodCallNode {
|
||||
BindPartialCall() { getMethodName() = "bind" }
|
||||
|
||||
override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
|
||||
callback = getReceiver() and
|
||||
argument = getArgument(index + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A partial call through `_.partial`.
|
||||
*/
|
||||
private class LodashPartialCall extends AdditionalPartialInvokeNode {
|
||||
LodashPartialCall() { this = LodashUnderscore::member("partial").getACall() }
|
||||
|
||||
override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
|
||||
callback = getArgument(0) and
|
||||
argument = getArgument(index + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A partial call through `ramda.partial`.
|
||||
*/
|
||||
private class RamdaPartialCall extends AdditionalPartialInvokeNode {
|
||||
RamdaPartialCall() { this = DataFlow::moduleMember("ramda", "partial").getACall() }
|
||||
|
||||
override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
|
||||
callback = getArgument(0) and
|
||||
exists(DataFlow::ArrayCreationNode array |
|
||||
array.flowsTo(getArgument(1)) and
|
||||
argument = array.getElement(index)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a flow step from `pred` to `succ` described by `summary`
|
||||
* under configuration `cfg`.
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
private import internal.CallGraphs
|
||||
|
||||
module DataFlow {
|
||||
cached
|
||||
@@ -117,14 +118,23 @@ module DataFlow {
|
||||
int getIntValue() { result = asExpr().getIntValue() }
|
||||
|
||||
/** Gets a function value that may reach this node. */
|
||||
FunctionNode getAFunctionValue() {
|
||||
result.getAstNode() = analyze().getAValue().(AbstractCallable).getFunction()
|
||||
final FunctionNode getAFunctionValue() {
|
||||
CallGraph::getAFunctionReference(result, 0).flowsTo(this)
|
||||
}
|
||||
|
||||
/** Gets a function value that may reach this node with the given `imprecision` level. */
|
||||
final FunctionNode getAFunctionValue(int imprecision) {
|
||||
CallGraph::getAFunctionReference(result, imprecision).flowsTo(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a function value that may reach this node,
|
||||
* possibly derived from a partial function invocation.
|
||||
*/
|
||||
final FunctionNode getABoundFunctionValue(int boundArgs) {
|
||||
result = getAFunctionValue() and boundArgs = 0
|
||||
or
|
||||
exists(string name |
|
||||
GlobalAccessPath::isAssignedInUniqueFile(name) and
|
||||
GlobalAccessPath::fromRhs(result) = name and
|
||||
GlobalAccessPath::fromReference(this) = name
|
||||
)
|
||||
CallGraph::getABoundFunctionReference(result, boundArgs).flowsTo(this)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
private import javascript
|
||||
private import semmle.javascript.dependencies.Dependencies
|
||||
private import internal.CallGraphs
|
||||
|
||||
/** A data flow node corresponding to an expression. */
|
||||
class ExprNode extends DataFlow::ValueNode {
|
||||
@@ -89,9 +90,43 @@ class InvokeNode extends DataFlow::SourceNode {
|
||||
|
||||
Function getEnclosingFunction() { result = getBasicBlock().getContainer() }
|
||||
|
||||
/** Gets a function passed as the `i`th argument of this invocation. */
|
||||
/**
|
||||
* Gets a function passed as the `i`th argument of this invocation.
|
||||
*
|
||||
* This predicate only performs local data flow tracking.
|
||||
* Consider using `getABoundCallbackParameter` to handle interprocedural flow of callback functions.
|
||||
*/
|
||||
FunctionNode getCallback(int i) { result.flowsTo(getArgument(i)) }
|
||||
|
||||
/**
|
||||
* Gets a parameter of a callback passed into this call.
|
||||
*
|
||||
* `callback` indicates which argument the callback passed into, and `param`
|
||||
* is the index of the parameter in the callback function.
|
||||
*
|
||||
* For example, for the call below, `getABoundCallbackParameter(1, 0)` refers
|
||||
* to the parameter `e` (the first parameter of the second callback argument):
|
||||
* ```js
|
||||
* addEventHandler("click", e => { ... })
|
||||
* ```
|
||||
*
|
||||
* This predicate takes interprocedural data flow into account, as well as
|
||||
* partial function applications such as `.bind`.
|
||||
*
|
||||
* For example, for the call below `getABoundCallbackParameter(1, 0)` returns the parameter `e`,
|
||||
* (the first parameter of the second callback argument), since the first parameter of `foo`
|
||||
* has been bound by the `bind` call:
|
||||
* ```js
|
||||
* function foo(x, e) { }
|
||||
* addEventHandler("click", foo.bind(this, "value of x"))
|
||||
* ```
|
||||
*/
|
||||
ParameterNode getABoundCallbackParameter(int callback, int param) {
|
||||
exists(int boundArgs |
|
||||
result = getArgument(callback).getABoundFunctionValue(boundArgs).getParameter(param + boundArgs)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the `i`th argument of this invocation is an object literal whose property
|
||||
* `name` is set to `result`.
|
||||
@@ -104,7 +139,7 @@ class InvokeNode extends DataFlow::SourceNode {
|
||||
}
|
||||
|
||||
/** Gets an abstract value representing possible callees of this call site. */
|
||||
AbstractValue getACalleeValue() { result = InvokeNode::getACalleeValue(this) }
|
||||
final AbstractValue getACalleeValue() { result = getCalleeNode().analyze().getAValue() }
|
||||
|
||||
/**
|
||||
* Gets a potential callee of this call site.
|
||||
@@ -112,7 +147,7 @@ class InvokeNode extends DataFlow::SourceNode {
|
||||
* To alter the call graph as seen by the interprocedural data flow libraries, override
|
||||
* the `getACallee(int imprecision)` predicate instead.
|
||||
*/
|
||||
Function getACallee() { result = InvokeNode::getACallee(this) }
|
||||
final Function getACallee() { result = getACallee(0) }
|
||||
|
||||
/**
|
||||
* Gets a callee of this call site where `imprecision` is a heuristic measure of how
|
||||
@@ -120,12 +155,15 @@ class InvokeNode extends DataFlow::SourceNode {
|
||||
* imprecise analysis of global variables and is not, in fact, a viable callee at all.
|
||||
*
|
||||
* Callees with imprecision zero, in particular, have either been derived without
|
||||
* considering global variables, or are calls to a global variable within the same file.
|
||||
* considering global variables, or are calls to a global variable within the same file,
|
||||
* or a global variable that has unique definition within the project.
|
||||
*
|
||||
* This predicate can be overridden to alter the call graph used by the interprocedural
|
||||
* data flow libraries.
|
||||
*/
|
||||
Function getACallee(int imprecision) { result = InvokeNode::getACallee(this, imprecision) }
|
||||
Function getACallee(int imprecision) {
|
||||
result = CallGraph::getACallee(this, imprecision).getFunction()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the approximation of possible callees for this call site is
|
||||
@@ -173,60 +211,6 @@ class InvokeNode extends DataFlow::SourceNode {
|
||||
}
|
||||
}
|
||||
|
||||
/** Auxiliary module used to cache a few related predicates together. */
|
||||
cached
|
||||
private module InvokeNode {
|
||||
/** Gets an abstract value representing possible callees of `invk`. */
|
||||
cached
|
||||
AbstractValue getACalleeValue(InvokeNode invk) {
|
||||
result = invk.getCalleeNode().analyze().getAValue()
|
||||
}
|
||||
|
||||
/** Gets a potential callee of `invk` based on dataflow analysis results. */
|
||||
private Function getACalleeFromDataflow(InvokeNode invk) {
|
||||
result = getACalleeValue(invk).(AbstractCallable).getFunction()
|
||||
}
|
||||
|
||||
/** Gets a potential callee of `invk`. */
|
||||
cached
|
||||
Function getACallee(InvokeNode invk) {
|
||||
result = getACalleeFromDataflow(invk)
|
||||
or
|
||||
not exists(getACalleeFromDataflow(invk)) and
|
||||
result = invk.(DataFlow::Impl::ExplicitInvokeNode).asExpr().(InvokeExpr).getResolvedCallee()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a callee of `invk` where `imprecision` is a heuristic measure of how
|
||||
* likely it is that `callee` is only suggested as a potential callee due to
|
||||
* imprecise analysis of global variables and is not, in fact, a viable callee at all.
|
||||
*
|
||||
* Callees with imprecision zero, in particular, have either been derived without
|
||||
* considering global variables, or are calls to a global variable within the same file.
|
||||
*/
|
||||
cached
|
||||
Function getACallee(InvokeNode invk, int imprecision) {
|
||||
result = getACallee(invk) and
|
||||
(
|
||||
// if global flow was used to derive the callee, we may be imprecise
|
||||
if invk.isIndefinite("global")
|
||||
then
|
||||
// callees within the same file are probably genuine
|
||||
result.getFile() = invk.getFile() and imprecision = 0
|
||||
or
|
||||
// calls to global functions declared in an externs file are fairly
|
||||
// safe as well
|
||||
result.inExternsFile() and imprecision = 1
|
||||
or
|
||||
// otherwise we make worst-case assumptions
|
||||
imprecision = 2
|
||||
else
|
||||
// no global flow, so no imprecision
|
||||
imprecision = 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A data flow node corresponding to a function call without `new`. */
|
||||
class CallNode extends InvokeNode {
|
||||
override DataFlow::Impl::CallNodeDef impl;
|
||||
@@ -403,6 +387,9 @@ class ArrayLiteralNode extends DataFlow::ValueNode, DataFlow::SourceNode {
|
||||
|
||||
/** Gets an element of this array literal. */
|
||||
DataFlow::ValueNode getAnElement() { result = DataFlow::valueNode(astNode.getAnElement()) }
|
||||
|
||||
/** Gets the initial size of this array. */
|
||||
int getSize() { result = astNode.getSize() }
|
||||
}
|
||||
|
||||
/** A data flow node corresponding to a `new Array()` or `Array()` invocation. */
|
||||
@@ -420,6 +407,14 @@ class ArrayConstructorInvokeNode extends DataFlow::InvokeNode {
|
||||
getNumArgument() > 1 and
|
||||
result = getAnArgument()
|
||||
}
|
||||
|
||||
/** Gets the initial size of the created array, if it can be determined. */
|
||||
int getSize() {
|
||||
if getNumArgument() = 1 then
|
||||
result = getArgument(0).getIntValue()
|
||||
else
|
||||
result = count(getAnElement())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -440,6 +435,12 @@ class ArrayCreationNode extends DataFlow::ValueNode, DataFlow::SourceNode {
|
||||
|
||||
/** Gets an initial element of this array, if one if provided. */
|
||||
DataFlow::ValueNode getAnElement() { result = getElement(_) }
|
||||
|
||||
/** Gets the initial size of the created array, if it can be determined. */
|
||||
int getSize() {
|
||||
result = this.(ArrayLiteralNode).getSize() or
|
||||
result = this.(ArrayConstructorInvokeNode).getSize()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -660,6 +661,8 @@ class ClassNode extends DataFlow::SourceNode {
|
||||
|
||||
/**
|
||||
* Gets a direct super class of this class.
|
||||
*
|
||||
* This predicate can be overridden to customize the class hierarchy.
|
||||
*/
|
||||
ClassNode getADirectSuperClass() { result.getAClassReference().flowsTo(getASuperClassNode()) }
|
||||
|
||||
@@ -689,10 +692,17 @@ class ClassNode extends DataFlow::SourceNode {
|
||||
|
||||
/**
|
||||
* Gets a dataflow node that refers to this class object.
|
||||
*
|
||||
* This predicate can be overridden to customize the tracking of class objects.
|
||||
*/
|
||||
private DataFlow::SourceNode getAClassReference(DataFlow::TypeTracker t) {
|
||||
DataFlow::SourceNode getAClassReference(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result.(AnalyzedNode).getAValue() = getAbstractClassValue()
|
||||
result.(AnalyzedNode).getAValue() = getAbstractClassValue() and
|
||||
(
|
||||
not CallGraph::isIndefiniteGlobal(result)
|
||||
or
|
||||
result.getAstNode().getFile() = this.getAstNode().getFile()
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = getAClassReference(t2).track(t2, t))
|
||||
}
|
||||
@@ -701,14 +711,16 @@ class ClassNode extends DataFlow::SourceNode {
|
||||
* Gets a dataflow node that refers to this class object.
|
||||
*/
|
||||
cached
|
||||
DataFlow::SourceNode getAClassReference() {
|
||||
final DataFlow::SourceNode getAClassReference() {
|
||||
result = getAClassReference(DataFlow::TypeTracker::end())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a dataflow node that refers to an instance of this class.
|
||||
*
|
||||
* This predicate can be overridden to customize the tracking of class instances.
|
||||
*/
|
||||
private DataFlow::SourceNode getAnInstanceReference(DataFlow::TypeTracker t) {
|
||||
DataFlow::SourceNode getAnInstanceReference(DataFlow::TypeTracker t) {
|
||||
result = getAClassReference(t.continue()).getAnInstantiation()
|
||||
or
|
||||
t.start() and
|
||||
@@ -743,10 +755,17 @@ class ClassNode extends DataFlow::SourceNode {
|
||||
* Gets a dataflow node that refers to an instance of this class.
|
||||
*/
|
||||
cached
|
||||
DataFlow::SourceNode getAnInstanceReference() {
|
||||
final DataFlow::SourceNode getAnInstanceReference() {
|
||||
result = getAnInstanceReference(DataFlow::TypeTracker::end())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an access to a static member of this class.
|
||||
*/
|
||||
DataFlow::PropRead getAStaticMemberAccess(string name) {
|
||||
result = getAClassReference().getAPropertyRead(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this class is exposed in the global scope through the given qualified name.
|
||||
*/
|
||||
@@ -978,3 +997,140 @@ module ClassNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node that performs a partial function application.
|
||||
*
|
||||
* Examples:
|
||||
* ```js
|
||||
* fn.bind(this)
|
||||
* x.method.bind(x)
|
||||
* _.partial(fn, x, y, z)
|
||||
* ```
|
||||
*/
|
||||
class PartialInvokeNode extends DataFlow::Node {
|
||||
PartialInvokeNode::Range range;
|
||||
|
||||
PartialInvokeNode() { this = range }
|
||||
|
||||
/**
|
||||
* Holds if `argument` is passed as argument `index` to the function in `callback`.
|
||||
*/
|
||||
predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
|
||||
range.isPartialArgument(callback, argument, index)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node referring to a bound version of `callback` with `boundArgs` arguments bound.
|
||||
*/
|
||||
DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) {
|
||||
result = range.getBoundFunction(callback, boundArgs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node holding the receiver to be passed to the bound function, if specified.
|
||||
*/
|
||||
DataFlow::Node getBoundReceiver() { result = range.getBoundReceiver() }
|
||||
}
|
||||
|
||||
module PartialInvokeNode {
|
||||
/**
|
||||
* A data flow node that performs a partial function application.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/**
|
||||
* Holds if `argument` is passed as argument `index` to the function in `callback`.
|
||||
*/
|
||||
predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) { none() }
|
||||
|
||||
/**
|
||||
* Gets a node referring to a bound version of `callback` with `boundArgs` arguments bound.
|
||||
*/
|
||||
DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) { none() }
|
||||
|
||||
/**
|
||||
* Gets the node holding the receiver to be passed to the bound function, if specified.
|
||||
*/
|
||||
DataFlow::Node getBoundReceiver() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A partial call through the built-in `Function.prototype.bind`.
|
||||
*/
|
||||
private class BindPartialCall extends PartialInvokeNode::Range, DataFlow::MethodCallNode {
|
||||
BindPartialCall() {
|
||||
getMethodName() = "bind" and
|
||||
|
||||
// Avoid overlap with angular.bind and goog.bind
|
||||
not this = AngularJS::angular().getAMethodCall() and
|
||||
not getReceiver().accessesGlobal("goog")
|
||||
}
|
||||
|
||||
override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
|
||||
index >= 0 and
|
||||
callback = getReceiver() and
|
||||
argument = getArgument(index + 1)
|
||||
}
|
||||
|
||||
override DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) {
|
||||
callback = getReceiver() and
|
||||
boundArgs = getNumArgument() - 1 and
|
||||
result = this
|
||||
}
|
||||
|
||||
override DataFlow::Node getBoundReceiver() {
|
||||
result = getArgument(0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A partial call through `_.partial`.
|
||||
*/
|
||||
private class LodashPartialCall extends PartialInvokeNode::Range, DataFlow::CallNode {
|
||||
LodashPartialCall() { this = LodashUnderscore::member("partial").getACall() }
|
||||
|
||||
override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
|
||||
index >= 0 and
|
||||
callback = getArgument(0) and
|
||||
argument = getArgument(index + 1)
|
||||
}
|
||||
|
||||
override DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) {
|
||||
callback = getArgument(0) and
|
||||
boundArgs = getNumArgument() - 1 and
|
||||
result = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A partial call through `ramda.partial`.
|
||||
*/
|
||||
private class RamdaPartialCall extends PartialInvokeNode::Range, DataFlow::CallNode {
|
||||
RamdaPartialCall() { this = DataFlow::moduleMember("ramda", "partial").getACall() }
|
||||
|
||||
private DataFlow::ArrayCreationNode getArgumentsArray() {
|
||||
result.flowsTo(getArgument(1))
|
||||
}
|
||||
|
||||
override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
|
||||
callback = getArgument(0) and
|
||||
argument = getArgumentsArray().getElement(index)
|
||||
}
|
||||
|
||||
override DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) {
|
||||
callback = getArgument(0) and
|
||||
boundArgs = getArgumentsArray().getSize() and
|
||||
result = this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED. Subclasses should extend `PartialInvokeNode::Range` instead,
|
||||
* and predicates should use `PartialInvokeNode` instead.
|
||||
*
|
||||
* An invocation that is modeled as a partial function application.
|
||||
*
|
||||
* This contributes additional argument-passing flow edges that should be added to all data flow configurations.
|
||||
*/
|
||||
deprecated class AdditionalPartialInvokeNode = PartialInvokeNode::Range;
|
||||
|
||||
@@ -55,6 +55,7 @@ module StepSummary {
|
||||
/**
|
||||
* INTERNAL: Use `SourceNode.track()` or `SourceNode.backtrack()` instead.
|
||||
*/
|
||||
cached
|
||||
predicate step(DataFlow::SourceNode pred, DataFlow::SourceNode succ, StepSummary summary) {
|
||||
exists(DataFlow::Node mid | pred.flowsTo(mid) | smallstep(mid, succ, summary))
|
||||
}
|
||||
@@ -170,6 +171,7 @@ class TypeTracker extends TTypeTracker {
|
||||
TypeTracker() { this = MkTypeTracker(hasCall, prop) }
|
||||
|
||||
/** Gets the summary resulting from appending `step` to this type-tracking summary. */
|
||||
cached
|
||||
TypeTracker append(StepSummary step) {
|
||||
step = LevelStep() and result = this
|
||||
or
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* Internal predicates for computing the call graph.
|
||||
*/
|
||||
private import javascript
|
||||
|
||||
cached
|
||||
module CallGraph {
|
||||
/** Gets the function referenced by `node`, as determined by the type inference. */
|
||||
cached
|
||||
Function getAFunctionValue(AnalyzedNode node) {
|
||||
result = node.getAValue().(AbstractCallable).getFunction()
|
||||
}
|
||||
|
||||
/** Holds if the type inferred for `node` is indefinite due to global flow. */
|
||||
cached
|
||||
predicate isIndefiniteGlobal(AnalyzedNode node) {
|
||||
node.analyze().getAValue().isIndefinite("global")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node that refers to the given function.
|
||||
*
|
||||
* Note that functions are not currently type-tracked, but this exposes the type-tracker `t`
|
||||
* from underlying class tracking if the function came from a class or instance.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private
|
||||
DataFlow::SourceNode getAFunctionReference(DataFlow::FunctionNode function, int imprecision, DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
exists(Function fun |
|
||||
fun = function.getFunction() and
|
||||
fun = getAFunctionValue(result)
|
||||
|
|
||||
if isIndefiniteGlobal(result) then
|
||||
fun.getFile() = result.getFile() and imprecision = 0
|
||||
or
|
||||
fun.inExternsFile() and imprecision = 1
|
||||
or
|
||||
imprecision = 2
|
||||
else
|
||||
imprecision = 0
|
||||
)
|
||||
or
|
||||
imprecision = 0 and
|
||||
t.start() and
|
||||
exists(string name |
|
||||
GlobalAccessPath::isAssignedInUniqueFile(name) and
|
||||
GlobalAccessPath::fromRhs(function) = name and
|
||||
GlobalAccessPath::fromReference(result) = name
|
||||
)
|
||||
or
|
||||
imprecision = 0 and
|
||||
exists(DataFlow::ClassNode cls |
|
||||
exists(string name |
|
||||
function = cls.getInstanceMethod(name) and
|
||||
getAnInstanceMemberAccess(cls, name, t.continue()).flowsTo(result)
|
||||
or
|
||||
function = cls.getStaticMethod(name) and
|
||||
cls.getAClassReference(t.continue()).getAPropertyRead(name).flowsTo(result)
|
||||
)
|
||||
or
|
||||
function = cls.getConstructor() and
|
||||
cls.getAClassReference(t.continue()).flowsTo(result)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node that refers to the given function.
|
||||
*/
|
||||
cached
|
||||
DataFlow::SourceNode getAFunctionReference(DataFlow::FunctionNode function, int imprecision) {
|
||||
result = getAFunctionReference(function, imprecision, DataFlow::TypeTracker::end())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node that refers to the result of the given partial function invocation,
|
||||
* with `function` as the underlying function.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private
|
||||
DataFlow::SourceNode getABoundFunctionReference(DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t) {
|
||||
exists(DataFlow::PartialInvokeNode partial, DataFlow::Node callback |
|
||||
result = partial.getBoundFunction(callback, boundArgs) and
|
||||
getAFunctionReference(function, 0, t.continue()).flowsTo(callback)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::StepSummary summary, DataFlow::TypeTracker t2 |
|
||||
result = getABoundFunctionReferenceAux(function, boundArgs, t2, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private
|
||||
DataFlow::SourceNode getABoundFunctionReferenceAux(DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t, DataFlow::StepSummary summary) {
|
||||
exists(DataFlow::SourceNode prev |
|
||||
prev = getABoundFunctionReference(function, boundArgs, t) and
|
||||
DataFlow::StepSummary::step(prev, result, summary)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node that refers to the result of the given partial function invocation,
|
||||
* with `function` as the underlying function.
|
||||
*/
|
||||
cached
|
||||
DataFlow::SourceNode getABoundFunctionReference(DataFlow::FunctionNode function, int boundArgs) {
|
||||
result = getABoundFunctionReference(function, boundArgs, DataFlow::TypeTracker::end())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a property read that accesses the property `name` on an instance of this class.
|
||||
*
|
||||
* Concretely, this holds when the base is an instance of this class or a subclass thereof.
|
||||
*
|
||||
* This predicate may be overridden to customize the class hierarchy analysis.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private
|
||||
DataFlow::PropRead getAnInstanceMemberAccess(DataFlow::ClassNode cls, string name, DataFlow::TypeTracker t) {
|
||||
result = cls.getAnInstanceReference(t.continue()).getAPropertyRead(name)
|
||||
or
|
||||
exists(DataFlow::ClassNode subclass |
|
||||
result = getAnInstanceMemberAccess(subclass, name, t) and
|
||||
not exists(subclass.getInstanceMember(name, _)) and
|
||||
cls = subclass.getADirectSuperClass()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a possible callee of `node` with the given `imprecision`.
|
||||
*
|
||||
* Does not include custom call edges.
|
||||
*/
|
||||
cached
|
||||
DataFlow::FunctionNode getACallee(DataFlow::InvokeNode node, int imprecision) {
|
||||
getAFunctionReference(result, imprecision).flowsTo(node.getCalleeNode())
|
||||
or
|
||||
imprecision = 0 and
|
||||
exists(InvokeExpr expr | expr = node.(DataFlow::Impl::ExplicitInvokeNode).asExpr() |
|
||||
result.getFunction() = expr.getResolvedCallee()
|
||||
or
|
||||
exists(DataFlow::ClassNode cls |
|
||||
expr.(SuperCall).getBinder() = cls.getConstructor().getFunction() and
|
||||
result = cls.getADirectSuperClass().getConstructor()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -97,54 +97,11 @@ private module CachedSteps {
|
||||
f = cap.getContainer()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the method invoked by `invoke` resolved to a member named `name` in `cls`
|
||||
* or one of its super classes.
|
||||
*/
|
||||
cached
|
||||
predicate callResolvesToMember(DataFlow::InvokeNode invoke, DataFlow::ClassNode cls, string name) {
|
||||
invoke = cls.getAnInstanceReference().getAMethodCall(name)
|
||||
or
|
||||
exists(DataFlow::ClassNode subclass |
|
||||
callResolvesToMember(invoke, subclass, name) and
|
||||
not exists(subclass.getAnInstanceMember(name)) and
|
||||
cls = subclass.getADirectSuperClass()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `invk` may invoke `f`.
|
||||
*/
|
||||
cached
|
||||
predicate calls(DataFlow::InvokeNode invk, Function f) {
|
||||
f = invk.getACallee(0)
|
||||
or
|
||||
exists(DataFlow::ClassNode cls |
|
||||
// Call to class member
|
||||
exists(string name |
|
||||
callResolvesToMember(invk, cls, name) and
|
||||
f = cls.getInstanceMethod(name).getFunction()
|
||||
or
|
||||
invk = cls.getAClassReference().getAMethodCall(name) and
|
||||
f = cls.getStaticMethod(name).getFunction()
|
||||
)
|
||||
or
|
||||
// Call to constructor
|
||||
invk = cls.getAClassReference().getAnInvocation() and
|
||||
f = cls.getConstructor().getFunction()
|
||||
or
|
||||
// Super call to constructor
|
||||
invk.asExpr().(SuperCall).getBinder() = cls.getConstructor().getFunction() and
|
||||
f = cls.getADirectSuperClass().getConstructor().getFunction()
|
||||
)
|
||||
or
|
||||
// Call from `foo.bar.baz()` to `foo.bar.baz = function()`
|
||||
exists(string name |
|
||||
GlobalAccessPath::isAssignedInUniqueFile(name) and
|
||||
GlobalAccessPath::fromRhs(f.flow()) = name and
|
||||
GlobalAccessPath::fromReference(invk.getCalleeNode()) = name
|
||||
)
|
||||
}
|
||||
predicate calls(DataFlow::InvokeNode invk, Function f) { f = invk.getACallee(0) }
|
||||
|
||||
/**
|
||||
* Holds if `invk` may invoke `f` indirectly through the given `callback` argument.
|
||||
@@ -152,7 +109,7 @@ private module CachedSteps {
|
||||
* This only holds for explicitly modeled partial calls.
|
||||
*/
|
||||
private predicate partiallyCalls(
|
||||
DataFlow::AdditionalPartialInvokeNode invk, DataFlow::AnalyzedNode callback, Function f
|
||||
DataFlow::PartialInvokeNode invk, DataFlow::AnalyzedNode callback, Function f
|
||||
) {
|
||||
invk.isPartialArgument(callback, _, _) and
|
||||
exists(AbstractFunction callee | callee = callback.getAValue() |
|
||||
@@ -184,7 +141,7 @@ private module CachedSteps {
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node callback, int i, Parameter p |
|
||||
invk.(DataFlow::AdditionalPartialInvokeNode).isPartialArgument(callback, arg, i) and
|
||||
invk.(DataFlow::PartialInvokeNode).isPartialArgument(callback, arg, i) and
|
||||
partiallyCalls(invk, callback, f) and
|
||||
f.getParameter(i) = p and
|
||||
not p.isRestParameter() and
|
||||
@@ -525,3 +482,4 @@ module PathSummary {
|
||||
*/
|
||||
PathSummary return() { exists(FlowLabel lbl | result = MkPathSummary(true, false, lbl, lbl)) }
|
||||
}
|
||||
|
||||
|
||||
@@ -1060,21 +1060,42 @@ private class RouteInstantiatedController extends Controller {
|
||||
/**
|
||||
* Dataflow for the arguments of AngularJS dependency-injected functions.
|
||||
*/
|
||||
private class DependencyInjectedArgumentInitializer extends DataFlow::AnalyzedValueNode {
|
||||
private class DependencyInjectedArgumentInitializer extends DataFlow::AnalyzedNode {
|
||||
DataFlow::AnalyzedNode service;
|
||||
|
||||
DependencyInjectedArgumentInitializer() {
|
||||
exists(
|
||||
AngularJS::InjectableFunction f, SimpleParameter param, AngularJS::CustomServiceDefinition def
|
||||
AngularJS::InjectableFunction f, Parameter param, AngularJS::CustomServiceDefinition def
|
||||
|
|
||||
astNode = param.getAnInitialUse() and
|
||||
this = DataFlow::parameterNode(param) and
|
||||
def.getServiceReference() = f.getAResolvedDependency(param) and
|
||||
service = def.getAService()
|
||||
)
|
||||
}
|
||||
|
||||
override AbstractValue getAValue() {
|
||||
result = DataFlow::AnalyzedValueNode.super.getAValue() or
|
||||
result = DataFlow::AnalyzedNode.super.getAValue() or
|
||||
result = service.getALocalValue()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `angular.bind`, as a partial function invocation.
|
||||
*/
|
||||
private class BindCall extends DataFlow::PartialInvokeNode::Range, DataFlow::CallNode {
|
||||
BindCall() { this = angular().getAMemberCall("bind") }
|
||||
|
||||
override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
|
||||
index >= 0 and
|
||||
callback = getArgument(1) and
|
||||
argument = getArgument(index + 2)
|
||||
}
|
||||
|
||||
override DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) {
|
||||
callback = getArgument(1) and
|
||||
boundArgs = getNumArgument() - 2 and
|
||||
result = this
|
||||
}
|
||||
|
||||
override DataFlow::Node getBoundReceiver() { result = getArgument(0) }
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ module AsyncPackage {
|
||||
* to the first parameter of the final callback, while `result1, result2, ...` are propagated to
|
||||
* the parameters of the following task.
|
||||
*/
|
||||
private class WaterfallNextTaskCall extends DataFlow::AdditionalPartialInvokeNode {
|
||||
private class WaterfallNextTaskCall extends DataFlow::PartialInvokeNode::Range, DataFlow::CallNode {
|
||||
Waterfall waterfall;
|
||||
int n;
|
||||
|
||||
|
||||
@@ -174,18 +174,20 @@ private module PersistentWebStorage {
|
||||
* An event handler that handles `postMessage` events.
|
||||
*/
|
||||
class PostMessageEventHandler extends Function {
|
||||
int paramIndex;
|
||||
|
||||
PostMessageEventHandler() {
|
||||
exists(CallExpr addEventListener |
|
||||
addEventListener.getCallee().accessesGlobal("addEventListener") and
|
||||
exists(DataFlow::CallNode addEventListener |
|
||||
addEventListener = DataFlow::globalVarRef("addEventListener").getACall() and
|
||||
addEventListener.getArgument(0).mayHaveStringValue("message") and
|
||||
addEventListener.getArgument(1).analyze().getAValue().(AbstractFunction).getFunction() = this
|
||||
addEventListener.getArgument(1).getABoundFunctionValue(paramIndex).getFunction() = this
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parameter that contains the event.
|
||||
*/
|
||||
SimpleParameter getEventParameter() { result = getParameter(0) }
|
||||
Parameter getEventParameter() { result = getParameter(paramIndex) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -60,7 +60,6 @@ test_getAFunctionValue
|
||||
| classes.js:12:3:16:3 | class B ... }\\n } | classes.js:12:21:12:20 | (...arg ... rgs); } |
|
||||
| classes.js:12:19:12:19 | A | classes.js:2:11:2:10 | () {} |
|
||||
| classes.js:12:21:12:20 | (...arg ... rgs); } | classes.js:12:21:12:20 | (...arg ... rgs); } |
|
||||
| classes.js:12:21:12:20 | super | classes.js:2:11:2:10 | () {} |
|
||||
| classes.js:13:10:15:5 | () {\\n ... ;\\n } | classes.js:13:10:15:5 | () {\\n ... ;\\n } |
|
||||
| classes.js:18:3:18:15 | new B().hello | classes.js:13:10:15:5 | () {\\n ... ;\\n } |
|
||||
| classes.js:18:7:18:7 | B | classes.js:12:21:12:20 | (...arg ... rgs); } |
|
||||
@@ -76,12 +75,10 @@ test_getAFunctionValue
|
||||
| es2015.js:14:20:14:46 | "Wait f ... leClass | es2015.js:2:14:4:3 | () {\\n ... ");\\n } |
|
||||
| es2015.js:14:35:14:46 | ExampleClass | es2015.js:2:14:4:3 | () {\\n ... ");\\n } |
|
||||
| es2015.js:15:14:17:3 | () {\\n ... ();\\n } | es2015.js:15:14:17:3 | () {\\n ... ();\\n } |
|
||||
| es2015.js:16:5:16:9 | super | es2015.js:2:14:4:3 | () {\\n ... ");\\n } |
|
||||
| es2015.js:20:1:22:1 | functio ... = 42;\\n} | es2015.js:20:1:22:1 | functio ... = 42;\\n} |
|
||||
| es2015.js:24:1:29:1 | class O ... ;\\n }\\n} | es2015.js:25:14:28:3 | () {\\n ... x);\\n } |
|
||||
| es2015.js:24:24:24:34 | PseudoClass | es2015.js:20:1:22:1 | functio ... = 42;\\n} |
|
||||
| es2015.js:25:14:28:3 | () {\\n ... x);\\n } | es2015.js:25:14:28:3 | () {\\n ... x);\\n } |
|
||||
| es2015.js:26:5:26:9 | super | es2015.js:20:1:22:1 | functio ... = 42;\\n} |
|
||||
| es2015.js:31:1:33:1 | functio ... +y+z;\\n} | es2015.js:31:1:33:1 | functio ... +y+z;\\n} |
|
||||
| es2015.js:34:1:34:3 | sum | es2015.js:31:1:33:1 | functio ... +y+z;\\n} |
|
||||
| es2015.js:35:1:35:3 | sum | es2015.js:31:1:33:1 | functio ... +y+z;\\n} |
|
||||
@@ -184,8 +181,8 @@ test_getAFunctionValue
|
||||
| tst.js:64:17:64:17 | B | tst.js:50:1:50:15 | function B() {} |
|
||||
| tst.js:65:5:65:23 | b.f = function() {} | tst.js:65:11:65:23 | function() {} |
|
||||
| tst.js:65:11:65:23 | function() {} | tst.js:65:11:65:23 | function() {} |
|
||||
| tst.js:66:5:66:7 | b.f | tst.js:52:5:54:2 | functio ... g();\\n\\t} |
|
||||
| tst.js:66:5:66:7 | b.f | tst.js:65:11:65:23 | function() {} |
|
||||
| tst.js:69:1:69:8 | globalfn | tst3.js:1:1:1:22 | functio ... fn() {} |
|
||||
| tst.js:70:1:70:9 | globalfn2 | tst3.js:2:1:2:23 | functio ... n2() {} |
|
||||
test_getArgument
|
||||
| classes.js:4:7:4:26 | console.log("Hello") | 0 | classes.js:4:19:4:25 | "Hello" |
|
||||
@@ -404,7 +401,6 @@ test_getACallee
|
||||
| es2015.js:6:1:6:18 | new ExampleClass() | es2015.js:2:14:4:3 | () {\\n ... ");\\n } |
|
||||
| es2015.js:10:5:10:22 | arguments.callee() | es2015.js:8:2:12:1 | functio ... \\n };\\n} |
|
||||
| es2015.js:16:5:16:11 | super() | es2015.js:2:14:4:3 | () {\\n ... ");\\n } |
|
||||
| es2015.js:26:5:26:11 | super() | es2015.js:20:1:22:1 | functio ... = 42;\\n} |
|
||||
| es2015.js:34:1:34:17 | sum(...[1, 2, 3]) | es2015.js:31:1:33:1 | functio ... +y+z;\\n} |
|
||||
| es2015.js:35:1:35:17 | sum(1, ...[2, 3]) | es2015.js:31:1:33:1 | functio ... +y+z;\\n} |
|
||||
| es2015.js:36:1:36:17 | sum(1, ...[2], 3) | es2015.js:31:1:33:1 | functio ... +y+z;\\n} |
|
||||
@@ -441,8 +437,8 @@ test_getACallee
|
||||
| tst.js:53:3:53:10 | this.g() | tst.js:57:39:57:51 | function() {} |
|
||||
| tst.js:60:15:60:21 | new A() | tst.js:44:1:44:15 | function A() {} |
|
||||
| tst.js:64:13:64:19 | new B() | tst.js:50:1:50:15 | function B() {} |
|
||||
| tst.js:66:5:66:9 | b.f() | tst.js:52:5:54:2 | functio ... g();\\n\\t} |
|
||||
| tst.js:66:5:66:9 | b.f() | tst.js:65:11:65:23 | function() {} |
|
||||
| tst.js:69:1:69:10 | globalfn() | tst3.js:1:1:1:22 | functio ... fn() {} |
|
||||
| tst.js:70:1:70:11 | globalfn2() | tst3.js:2:1:2:23 | functio ... n2() {} |
|
||||
test_getCalleeName
|
||||
| a.js:2:1:2:5 | foo() | foo |
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
goog.module('test');
|
||||
|
||||
function test(x) {
|
||||
addEventListener("click", goog.bind(function(x, event) {}, this, x));
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
getBoundFunction
|
||||
| closure.js:4:29:4:69 | goog.bi ... his, x) | closure.js:4:39:4:59 | functio ... ent) {} | 1 | closure.js:4:29:4:69 | goog.bi ... his, x) |
|
||||
| tst.js:5:29:5:63 | functio ... his, x) | tst.js:5:29:5:49 | functio ... ent) {} | 1 | tst.js:5:29:5:63 | functio ... his, x) |
|
||||
| tst.js:6:29:6:68 | lodash. ... {}, x) | tst.js:6:44:6:64 | functio ... ent) {} | 1 | tst.js:6:29:6:68 | lodash. ... {}, x) |
|
||||
| tst.js:7:29:7:65 | R.parti ... }, [x]) | tst.js:7:39:7:59 | functio ... ent) {} | 1 | tst.js:7:29:7:65 | R.parti ... }, [x]) |
|
||||
| tst.js:8:29:8:72 | angular ... {}, x) | tst.js:8:48:8:68 | functio ... ent) {} | 1 | tst.js:8:29:8:72 | angular ... {}, x) |
|
||||
| tst.js:11:29:11:43 | f.bind(this, x) | tst.js:11:29:11:29 | f | 1 | tst.js:11:29:11:43 | f.bind(this, x) |
|
||||
isPartialArgument
|
||||
| closure.js:4:29:4:69 | goog.bi ... his, x) | closure.js:4:39:4:59 | functio ... ent) {} | closure.js:4:68:4:68 | x | 0 |
|
||||
| tst.js:5:29:5:63 | functio ... his, x) | tst.js:5:29:5:49 | functio ... ent) {} | tst.js:5:62:5:62 | x | 0 |
|
||||
| tst.js:6:29:6:68 | lodash. ... {}, x) | tst.js:6:44:6:64 | functio ... ent) {} | tst.js:6:67:6:67 | x | 0 |
|
||||
| tst.js:7:29:7:65 | R.parti ... }, [x]) | tst.js:7:39:7:59 | functio ... ent) {} | tst.js:7:63:7:63 | x | 0 |
|
||||
| tst.js:8:29:8:72 | angular ... {}, x) | tst.js:8:48:8:68 | functio ... ent) {} | tst.js:8:71:8:71 | x | 0 |
|
||||
| tst.js:11:29:11:43 | f.bind(this, x) | tst.js:11:29:11:29 | f | tst.js:11:42:11:42 | x | 0 |
|
||||
getBoundReceiver
|
||||
| closure.js:4:29:4:69 | goog.bi ... his, x) | closure.js:4:62:4:65 | this |
|
||||
| tst.js:5:29:5:63 | functio ... his, x) | tst.js:5:56:5:59 | this |
|
||||
| tst.js:8:29:8:72 | angular ... {}, x) | tst.js:8:42:8:45 | this |
|
||||
| tst.js:11:29:11:43 | f.bind(this, x) | tst.js:11:36:11:39 | this |
|
||||
clickEvent
|
||||
| closure.js:4:51:4:55 | event |
|
||||
| tst.js:5:41:5:45 | event |
|
||||
| tst.js:6:56:6:60 | event |
|
||||
| tst.js:7:51:7:55 | event |
|
||||
| tst.js:8:60:8:64 | event |
|
||||
| tst.js:10:17:10:21 | event |
|
||||
21
javascript/ql/test/library-tests/PartialInvokeNode/test.ql
Normal file
21
javascript/ql/test/library-tests/PartialInvokeNode/test.ql
Normal file
@@ -0,0 +1,21 @@
|
||||
import javascript
|
||||
|
||||
query
|
||||
DataFlow::Node getBoundFunction(DataFlow::PartialInvokeNode invoke, DataFlow::Node callback, int boundArgs) {
|
||||
result = invoke.getBoundFunction(callback, boundArgs)
|
||||
}
|
||||
|
||||
query
|
||||
predicate isPartialArgument(DataFlow::PartialInvokeNode invoke, DataFlow::Node callback, DataFlow::Node argument, int index) {
|
||||
invoke.isPartialArgument(callback, argument, index)
|
||||
}
|
||||
|
||||
query
|
||||
DataFlow::Node getBoundReceiver(DataFlow::PartialInvokeNode invoke) {
|
||||
result = invoke.getBoundReceiver()
|
||||
}
|
||||
|
||||
query
|
||||
DataFlow::Node clickEvent() {
|
||||
result = DataFlow::globalVarRef("addEventListener").getACall().getABoundCallbackParameter(1, 0)
|
||||
}
|
||||
12
javascript/ql/test/library-tests/PartialInvokeNode/tst.js
Normal file
12
javascript/ql/test/library-tests/PartialInvokeNode/tst.js
Normal file
@@ -0,0 +1,12 @@
|
||||
let lodash = require('lodash');
|
||||
let R = require('ramda');
|
||||
|
||||
function test(x) {
|
||||
addEventListener("click", function(x, event) {}.bind(this, x));
|
||||
addEventListener("click", lodash.partial(function(x, event) {}, x));
|
||||
addEventListener("click", R.partial(function(x, event) {}, [x]));
|
||||
addEventListener("click", angular.bind(this, function(x, event) {}, x));
|
||||
|
||||
function f(x, event) {}
|
||||
addEventListener("click", f.bind(this, x));
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
| tst.ts:4:5:4:21 | x.method("Hello") | tst.ts:15:3:17:3 | public ... x);\\n } |
|
||||
| tst.ts:9:17:9:33 | new AngryLogger() | tst.ts:20:34:20:33 | (...arg ... rgs); } |
|
||||
| tst.ts:10:5:10:49 | (newLog ... hello") | tst.ts:15:3:17:3 | public ... x);\\n } |
|
||||
| tst.ts:10:5:10:49 | (newLog ... hello") | tst.ts:21:3:23:3 | public ... ");\\n } |
|
||||
| tst.ts:20:34:20:33 | super(...args) | tst.ts:14:14:14:13 | () {} |
|
||||
|
||||
@@ -4,7 +4,7 @@ class MyClass {
|
||||
x.method("Hello");
|
||||
|
||||
// Resolve based on local dataflow.
|
||||
// Type information should not degrade call graph precision.
|
||||
// Type information may degrade call graph precision.
|
||||
var newLogger: Logger;
|
||||
newLogger = new AngryLogger();
|
||||
(newLogger as Logger).method("I said, hello");
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
| tst.js:12:1:12:3 | C() | Illegal invocation of $@ as a function. | tst.js:1:9:1:8 | () {} | a constructor |
|
||||
| tst.js:13:1:13:10 | new (x=>x) | Illegal invocation of $@ using 'new'. | tst.js:13:6:13:9 | x=>x | an arrow function |
|
||||
| tst.js:15:1:15:9 | new c.m() | Illegal invocation of $@ using 'new'. | tst.js:2:4:2:8 | () {} | a method |
|
||||
| tst.js:24:1:24:9 | new o.g() | Illegal invocation of $@ using 'new'. | tst.js:19:4:19:8 | () {} | a method |
|
||||
| tst.js:42:1:42:7 | new g() | Illegal invocation of $@ using 'new'. | tst.js:39:1:39:16 | function* g() {} | a generator function |
|
||||
| tst.js:43:1:43:7 | new h() | Illegal invocation of $@ using 'new'. | tst.js:40:1:40:21 | async f ... h() {} | an async function |
|
||||
| tst.js:45:1:45:8 | reflective call | Illegal invocation of $@ as a function. | tst.js:1:9:1:8 | () {} | a constructor |
|
||||
|
||||
@@ -12,7 +12,7 @@ let c = new C(); // OK
|
||||
C(); // NOT OK
|
||||
new (x=>x); // NOT OK
|
||||
c.m(); // OK
|
||||
new c.m(); // NOT OK
|
||||
new c.m(); // NOT OK - but not flagged
|
||||
|
||||
var o = {
|
||||
f: function() {},
|
||||
@@ -21,7 +21,7 @@ var o = {
|
||||
o.f(); // OK
|
||||
new o.f(); // OK
|
||||
o.g(); // OK
|
||||
new o.g(); // NOT OK
|
||||
new o.g(); // NOT OK - but not flagged
|
||||
|
||||
function f(b) {
|
||||
var g;
|
||||
@@ -53,4 +53,12 @@ class E {
|
||||
E.call(); // OK
|
||||
E.apply(); // OK
|
||||
|
||||
//semmle-extractor-options: --experimental
|
||||
function invoke(fn) {
|
||||
if (typeof fn === "function" && fn.hasOwnProperty("foo")) {
|
||||
fn(); // OK
|
||||
}
|
||||
}
|
||||
invoke(C);
|
||||
invoke(function() {});
|
||||
|
||||
//semmle-extractor-options: --experimental
|
||||
@@ -0,0 +1,2 @@
|
||||
Array(45); // OK
|
||||
new Array(45); // OK
|
||||
@@ -0,0 +1,2 @@
|
||||
function Array() {}
|
||||
Array.prototype.foo = function() {};
|
||||
@@ -1,4 +1,10 @@
|
||||
function Error() {}
|
||||
Error.prototype.toString = function() {};
|
||||
|
||||
function String() {}
|
||||
String.prototype.toString = function() {};
|
||||
|
||||
function Array() {}
|
||||
Array.prototype.toString = function() {};
|
||||
|
||||
//semmle-extractor-options: --externs
|
||||
@@ -2,6 +2,13 @@ nodes
|
||||
| addEventListener.js:1:43:1:47 | event |
|
||||
| addEventListener.js:2:20:2:24 | event |
|
||||
| addEventListener.js:2:20:2:29 | event.data |
|
||||
| addEventListener.js:5:43:5:48 | data |
|
||||
| addEventListener.js:5:43:5:48 | {data} |
|
||||
| addEventListener.js:5:44:5:47 | data |
|
||||
| addEventListener.js:6:20:6:23 | data |
|
||||
| addEventListener.js:10:21:10:25 | event |
|
||||
| addEventListener.js:12:24:12:28 | event |
|
||||
| addEventListener.js:12:24:12:33 | event.data |
|
||||
| jquery.js:2:7:2:40 | tainted |
|
||||
| jquery.js:2:17:2:33 | document.location |
|
||||
| jquery.js:2:17:2:40 | documen ... .search |
|
||||
@@ -195,6 +202,11 @@ nodes
|
||||
edges
|
||||
| addEventListener.js:1:43:1:47 | event | addEventListener.js:2:20:2:24 | event |
|
||||
| addEventListener.js:2:20:2:24 | event | addEventListener.js:2:20:2:29 | event.data |
|
||||
| addEventListener.js:5:43:5:48 | data | addEventListener.js:6:20:6:23 | data |
|
||||
| addEventListener.js:5:43:5:48 | {data} | addEventListener.js:5:44:5:47 | data |
|
||||
| addEventListener.js:5:44:5:47 | data | addEventListener.js:5:43:5:48 | data |
|
||||
| addEventListener.js:10:21:10:25 | event | addEventListener.js:12:24:12:28 | event |
|
||||
| addEventListener.js:12:24:12:28 | event | addEventListener.js:12:24:12:33 | event.data |
|
||||
| jquery.js:2:7:2:40 | tainted | jquery.js:4:5:4:11 | tainted |
|
||||
| jquery.js:2:7:2:40 | tainted | jquery.js:7:20:7:26 | tainted |
|
||||
| jquery.js:2:7:2:40 | tainted | jquery.js:8:28:8:34 | tainted |
|
||||
@@ -354,6 +366,8 @@ edges
|
||||
| winjs.js:2:17:2:53 | documen ... ring(1) | winjs.js:2:7:2:53 | tainted |
|
||||
#select
|
||||
| addEventListener.js:2:20:2:29 | event.data | addEventListener.js:1:43:1:47 | event | addEventListener.js:2:20:2:29 | event.data | Cross-site scripting vulnerability due to $@. | addEventListener.js:1:43:1:47 | event | user-provided value |
|
||||
| addEventListener.js:6:20:6:23 | data | addEventListener.js:5:43:5:48 | {data} | addEventListener.js:6:20:6:23 | data | Cross-site scripting vulnerability due to $@. | addEventListener.js:5:43:5:48 | {data} | user-provided value |
|
||||
| addEventListener.js:12:24:12:33 | event.data | addEventListener.js:10:21:10:25 | event | addEventListener.js:12:24:12:33 | event.data | Cross-site scripting vulnerability due to $@. | addEventListener.js:10:21:10:25 | event | user-provided value |
|
||||
| jquery.js:4:5:4:11 | tainted | jquery.js:2:17:2:33 | document.location | jquery.js:4:5:4:11 | tainted | Cross-site scripting vulnerability due to $@. | jquery.js:2:17:2:33 | document.location | user-provided value |
|
||||
| jquery.js:7:5:7:34 | "<div i ... + "\\">" | jquery.js:2:17:2:33 | document.location | jquery.js:7:5:7:34 | "<div i ... + "\\">" | Cross-site scripting vulnerability due to $@. | jquery.js:2:17:2:33 | document.location | user-provided value |
|
||||
| jquery.js:8:18:8:34 | "XSS: " + tainted | jquery.js:2:17:2:33 | document.location | jquery.js:8:18:8:34 | "XSS: " + tainted | Cross-site scripting vulnerability due to $@. | jquery.js:2:17:2:33 | document.location | user-provided value |
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
this.addEventListener('message', function(event) {
|
||||
document.write(event.data); // NOT OK
|
||||
})
|
||||
|
||||
this.addEventListener('message', function({data}) {
|
||||
document.write(data); // NOT OK
|
||||
})
|
||||
|
||||
function test() {
|
||||
function foo(x, event, y) {
|
||||
document.write(x.data); // OK
|
||||
document.write(event.data); // NOT OK
|
||||
document.write(y.data); // OK
|
||||
}
|
||||
|
||||
window.addEventListener("message", foo.bind(null, {data: 'items'}));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user