JS: Move call graph computation into CallGraphs.qll

This commit is contained in:
Asger F
2019-09-02 15:00:14 +01:00
parent 96a13ff5d6
commit d6d89a0703
4 changed files with 135 additions and 69 deletions

View File

@@ -19,6 +19,7 @@
*/
import javascript
private import internal.CallGraphs
module DataFlow {
cached
@@ -118,57 +119,15 @@ module DataFlow {
/** Gets a function value that may reach this node. */
final FunctionNode getAFunctionValue() {
result = getAFunctionValue(_)
CallGraph::getAFunctionReference(result, 0).flowsTo(this)
}
/**
* Gets a function value that may reach this node.
*
* This predicate may be overridden to customize the call graph.
*
* Note that any additional values added by overiding predicates will not
* be propagated along data flow edges.
*
* `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.
* Gets a function value that may reach this node,
* possibly derived from a partial function invocation.
*/
cached
FunctionNode getAFunctionValue(int imprecision) {
exists(Function fun |
result = fun.flow() and
fun = analyze().getAValue().(AbstractCallable).getFunction()
|
if analyze().getAValue().isIndefinite("global") then
fun.getFile() = getFile() and imprecision = 0
or
fun.inExternsFile() and imprecision = 1
or
imprecision = 2
else
imprecision = 0
)
or
imprecision = 0 and
exists(string name |
GlobalAccessPath::isAssignedInUniqueFile(name) and
GlobalAccessPath::fromRhs(result) = name and
GlobalAccessPath::fromReference(this) = name
)
or
imprecision = 0 and
exists(DataFlow::ClassNode cls |
exists(string name |
cls.getAnInstanceMemberAccess(name).flowsTo(this) and
result = cls.getInstanceMethod(name)
or
cls.getAStaticMemberAccess(name).flowsTo(this) and
result = cls.getStaticMethod(name)
)
or
cls.getAClassReference().flowsTo(this) and
result = cls.getConstructor()
)
final FunctionNode getABoundFunctionValue(int boundArgs) {
CallGraph::getABoundFunctionReference(result, boundArgs).flowsTo(this)
}
/**

View File

@@ -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,24 @@ 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.
* Use `getACallbackSource` to handle interprocedural flow of callback functions.
*/
FunctionNode getCallback(int i) { result.flowsTo(getArgument(i)) }
/**
* Gets a function passed as the `i`th argument of this invocation,
* possibly through non-local data flow and bound by partial function invocations.
*/
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`.
@@ -127,14 +143,14 @@ class InvokeNode extends DataFlow::SourceNode {
*/
cached
Function getACallee(int imprecision) {
result.flow() = getCalleeNode().getAFunctionValue(imprecision)
CallGraph::getAFunctionReference(result.flow(), imprecision).flowsTo(getCalleeNode())
or
imprecision = 0 and
exists(InvokeExpr expr | expr = this.(DataFlow::Impl::ExplicitInvokeNode).asExpr() |
result = expr.getResolvedCallee()
or
exists(DataFlow::ClassNode cls |
expr.(SuperCall).getBinder() = cls.getAnInstanceMethodOrConstructor().getFunction() and
expr.(SuperCall).getBinder() = cls.getConstructor().getFunction() and
result = cls.getADirectSuperClass().getConstructor().getFunction()
)
)
@@ -726,23 +742,6 @@ class ClassNode extends DataFlow::SourceNode {
result = getAnInstanceReference(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.
*/
DataFlow::PropRead getAnInstanceMemberAccess(string name) {
result = getAnInstanceReference().getAPropertyRead(name)
or
exists(DataFlow::ClassNode subclass |
result = subclass.getAnInstanceMemberAccess(name) and
not exists(subclass.getAnInstanceMember(name)) and
subclass = getADirectSubClass()
)
}
/**
* Gets an access to a static member of this class.
*/
@@ -986,7 +985,7 @@ 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)

View File

@@ -0,0 +1,108 @@
private import javascript
cached
module CallGraph {
/**
* Gets a data flow node that refers to the given function.
*/
private
DataFlow::SourceNode getAFunctionReference(DataFlow::FunctionNode function, int imprecision, DataFlow::TypeTracker t) {
t.start() and
exists(Function fun |
fun = function.getFunction() and
fun = result.analyze().getAValue().(AbstractCallable).getFunction()
|
if result.analyze().getAValue().isIndefinite("global") 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.
*/
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
result = getABoundFunctionReferenceAux(function, boundArgs, t)
}
pragma[noopt]
private
DataFlow::SourceNode getABoundFunctionReferenceAux(DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t) {
exists(DataFlow::TypeTracker t2, DataFlow::SourceNode prev |
prev = getABoundFunctionReference(function, boundArgs, t2) and
exists(DataFlow::StepSummary summary |
DataFlow::StepSummary::step(prev, result, summary) and
t = t2.append(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.
*/
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.getAnInstanceMember(name)) and
cls = subclass.getADirectSuperClass()
)
}
}

View File

@@ -109,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() |