JS: Add helper library for call graph exploration

This commit is contained in:
Asger Feldthaus
2020-02-19 22:13:24 +00:00
parent 2ecef33c9d
commit 759e1dfe45

View File

@@ -0,0 +1,85 @@
/**
* Provides predicates for visualizing the call paths leading to or from a specific function.
*
* It defines three predicates: `callEdge`, `isStartOfCallPath` and `isEndOfCallPath`,
* as well as the `nodes` and `edges` predicates needed for a path problem query.
*
* To use this library, make sure the query has `@kind path-problem`
* and selects columns appropriate for a path problem query.
* For example:
* ```
* import javascript
* import semmle.javascript.explore.CallGraph
* import DataFlow
*
* from InvokeNode invoke, FunctionNode function
* where callEdge*(invoke, function)
* and isStartOfCallPath(invoke)
* and function.getName() = "targetFunction"
* select invoke, invoke, function, "Call path to 'targetFunction'"
* ```
*/
import javascript
private import DataFlow
/**
* Holds if `pred -> succ` is an edge in the call graph.
*
* There are edges from calls to their callees,
* and from functions to their contained calls and in some cases
* their inner functions.
*/
predicate callEdge(Node pred, Node succ) {
exists(InvokeNode invoke, Function f |
invoke.getACallee() = f and
pred = invoke and
succ = f.flow()
or
invoke.getContainer() = f and
pred = f.flow() and
succ = invoke
)
or
exists(Function inner, Function outer |
inner.getEnclosingContainer() = outer and
not inner = outer.getAReturnedExpr() and
pred = outer.flow() and
succ = inner.flow()
)
}
/** Holds if `pred -> succ` is an edge in the call graph. */
query predicate edges = callEdge/2;
/** Holds if `node` is part of the call graph. */
query predicate nodes(Node node) {
node instanceof InvokeNode or
node instanceof FunctionNode
}
/** Gets a call in a function that has no known call sites. */
private InvokeNode rootCall() {
not any(InvokeNode i).getACallee() = result.getContainer()
}
/**
* Holds if `invoke` should be used as the starting point of a call path.
*/
predicate isStartOfCallPath(InvokeNode invoke) {
// `invoke` should either be a root call or be part of a cycle with no root.
// An equivalent requirement is that `invoke` is not reachable from a root.
not callEdge+(rootCall(), invoke)
}
/** Gets a function contains no calls to other functions. */
private FunctionNode leafFunction() {
not callEdge(result, _)
}
/**
* Holds if `invoke` should be used as the starting point of a call path.
*/
predicate isEndOfCallPath(FunctionNode fun) {
// `fun` should either be a leaf function or part of a cycle with no leaves.
not callEdge+(fun, leafFunction())
}