JS: Improve flow through partial invokes

This commit is contained in:
Asger Feldthaus
2020-02-05 14:18:47 +00:00
parent 3c8aeb946a
commit f942e69482
5 changed files with 57 additions and 5 deletions

View File

@@ -267,6 +267,9 @@ module Closure {
result = this result = this
} }
override DataFlow::Node getBoundReceiver() { result = getArgument(1) } override DataFlow::Node getBoundReceiver(DataFlow::Node callback) {
callback = getArgument(0) and
result = getArgument(1)
}
} }
} }

View File

@@ -1311,6 +1311,9 @@ class MidPathNode extends PathNode, MkMidNode {
or or
// Skip the exceptional return on functions, as this highlights the entire function. // Skip the exceptional return on functions, as this highlights the entire function.
nd = any(DataFlow::FunctionNode f).getExceptionalReturn() nd = any(DataFlow::FunctionNode f).getExceptionalReturn()
or
// Skip the synthetic 'this' node, as a ThisExpr will be the next node anyway
nd = DataFlow::thisNode(_)
} }
} }

View File

@@ -1199,6 +1199,13 @@ class PartialInvokeNode extends DataFlow::Node {
PartialInvokeNode() { this = range } PartialInvokeNode() { this = range }
/** Gets a node holding a callback invoked by this partial invocation node. */
DataFlow::Node getACallbackNode() {
isPartialArgument(result, _, _)
or
exists(getBoundReceiver(result))
}
/** /**
* Holds if `argument` is passed as argument `index` to the function in `callback`. * Holds if `argument` is passed as argument `index` to the function in `callback`.
*/ */
@@ -1216,7 +1223,12 @@ class PartialInvokeNode extends DataFlow::Node {
/** /**
* Gets the node holding the receiver to be passed to the bound function, if specified. * Gets the node holding the receiver to be passed to the bound function, if specified.
*/ */
DataFlow::Node getBoundReceiver() { result = range.getBoundReceiver() } DataFlow::Node getBoundReceiver() { result = range.getBoundReceiver(_) }
/**
* Gets the node holding the receiver to be passed to the bound function, if specified.
*/
DataFlow::Node getBoundReceiver(DataFlow::Node callback) { result = range.getBoundReceiver(callback) }
} }
module PartialInvokeNode { module PartialInvokeNode {
@@ -1235,9 +1247,17 @@ module PartialInvokeNode {
DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) { none() } DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) { none() }
/** /**
* DEPRECATED. Use the two-argument version of `getBoundReceiver` instead.
*
* Gets the node holding the receiver to be passed to the bound function, if specified. * Gets the node holding the receiver to be passed to the bound function, if specified.
*/ */
deprecated
DataFlow::Node getBoundReceiver() { none() } DataFlow::Node getBoundReceiver() { none() }
/**
* Gets the node holding the receiver to be passed to `callback`.
*/
DataFlow::Node getBoundReceiver(DataFlow::Node callback) { none() }
} }
/** /**
@@ -1264,7 +1284,8 @@ module PartialInvokeNode {
result = this result = this
} }
override DataFlow::Node getBoundReceiver() { override DataFlow::Node getBoundReceiver(DataFlow::Node callback) {
callback = getReceiver() and
result = getArgument(0) result = getArgument(0)
} }
} }
@@ -1309,6 +1330,22 @@ module PartialInvokeNode {
result = this result = this
} }
} }
/**
* A call to `for-in` or `for-own`, passing the context parameter to the target function.
*/
class ForOwnInPartialCall extends PartialInvokeNode::Range, DataFlow::CallNode {
ForOwnInPartialCall() {
exists(string name | name = "for-in" or name = "for-own" |
this = moduleImport(name).getACall()
)
}
override DataFlow::Node getBoundReceiver(DataFlow::Node callback) {
callback = getArgument(1) and
result = getArgument(2)
}
}
} }
/** /**

View File

@@ -99,7 +99,7 @@ private module CachedSteps {
private predicate partiallyCalls( private predicate partiallyCalls(
DataFlow::PartialInvokeNode invk, DataFlow::AnalyzedNode callback, Function f DataFlow::PartialInvokeNode invk, DataFlow::AnalyzedNode callback, Function f
) { ) {
invk.isPartialArgument(callback, _, _) and callback = invk.getACallbackNode() and
exists(AbstractFunction callee | callee = callback.getAValue() | exists(AbstractFunction callee | callee = callback.getAValue() |
if callback.getAValue().isIndefinite("global") if callback.getAValue().isIndefinite("global")
then f = callee.getFunction() and f.getFile() = invk.getFile() then f = callee.getFunction() and f.getFile() = invk.getFile()
@@ -135,6 +135,12 @@ private module CachedSteps {
not p.isRestParameter() and not p.isRestParameter() and
parm = DataFlow::parameterNode(p) parm = DataFlow::parameterNode(p)
) )
or
exists(DataFlow::Node callback |
arg = invk.(DataFlow::PartialInvokeNode).getBoundReceiver(callback) and
partiallyCalls(invk, callback, f) and
parm = DataFlow::thisNode(f)
)
} }
/** /**

View File

@@ -1097,5 +1097,8 @@ private class BindCall extends DataFlow::PartialInvokeNode::Range, DataFlow::Cal
result = this result = this
} }
override DataFlow::Node getBoundReceiver() { result = getArgument(0) } override DataFlow::Node getBoundReceiver(DataFlow::Node callback) {
callback = getArgument(1) and
result = getArgument(0)
}
} }