Skip to content

Commit 759e1df

Browse files
committed
JS: Add helper library for call graph exploration
1 parent 2ecef33 commit 759e1df

File tree

1 file changed

+85
-0
lines changed

1 file changed

+85
-0
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* Provides predicates for visualizing the call paths leading to or from a specific function.
3+
*
4+
* It defines three predicates: `callEdge`, `isStartOfCallPath` and `isEndOfCallPath`,
5+
* as well as the `nodes` and `edges` predicates needed for a path problem query.
6+
*
7+
* To use this library, make sure the query has `@kind path-problem`
8+
* and selects columns appropriate for a path problem query.
9+
* For example:
10+
* ```
11+
* import javascript
12+
* import semmle.javascript.explore.CallGraph
13+
* import DataFlow
14+
*
15+
* from InvokeNode invoke, FunctionNode function
16+
* where callEdge*(invoke, function)
17+
* and isStartOfCallPath(invoke)
18+
* and function.getName() = "targetFunction"
19+
* select invoke, invoke, function, "Call path to 'targetFunction'"
20+
* ```
21+
*/
22+
import javascript
23+
private import DataFlow
24+
25+
/**
26+
* Holds if `pred -> succ` is an edge in the call graph.
27+
*
28+
* There are edges from calls to their callees,
29+
* and from functions to their contained calls and in some cases
30+
* their inner functions.
31+
*/
32+
predicate callEdge(Node pred, Node succ) {
33+
exists(InvokeNode invoke, Function f |
34+
invoke.getACallee() = f and
35+
pred = invoke and
36+
succ = f.flow()
37+
or
38+
invoke.getContainer() = f and
39+
pred = f.flow() and
40+
succ = invoke
41+
)
42+
or
43+
exists(Function inner, Function outer |
44+
inner.getEnclosingContainer() = outer and
45+
not inner = outer.getAReturnedExpr() and
46+
pred = outer.flow() and
47+
succ = inner.flow()
48+
)
49+
}
50+
51+
/** Holds if `pred -> succ` is an edge in the call graph. */
52+
query predicate edges = callEdge/2;
53+
54+
/** Holds if `node` is part of the call graph. */
55+
query predicate nodes(Node node) {
56+
node instanceof InvokeNode or
57+
node instanceof FunctionNode
58+
}
59+
60+
/** Gets a call in a function that has no known call sites. */
61+
private InvokeNode rootCall() {
62+
not any(InvokeNode i).getACallee() = result.getContainer()
63+
}
64+
65+
/**
66+
* Holds if `invoke` should be used as the starting point of a call path.
67+
*/
68+
predicate isStartOfCallPath(InvokeNode invoke) {
69+
// `invoke` should either be a root call or be part of a cycle with no root.
70+
// An equivalent requirement is that `invoke` is not reachable from a root.
71+
not callEdge+(rootCall(), invoke)
72+
}
73+
74+
/** Gets a function contains no calls to other functions. */
75+
private FunctionNode leafFunction() {
76+
not callEdge(result, _)
77+
}
78+
79+
/**
80+
* Holds if `invoke` should be used as the starting point of a call path.
81+
*/
82+
predicate isEndOfCallPath(FunctionNode fun) {
83+
// `fun` should either be a leaf function or part of a cycle with no leaves.
84+
not callEdge+(fun, leafFunction())
85+
}

0 commit comments

Comments
 (0)