Skip to content

Commit 57f44c5

Browse files
authored
Merge pull request github#2886 from asger-semmle/js/call-graph-exploration
Approved by erik-krogh, esbena
2 parents 384df88 + ac44cb4 commit 57f44c5

File tree

6 files changed

+172
-76
lines changed

6 files changed

+172
-76
lines changed

change-notes/1.25/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,5 @@
2929

3030
## Changes to libraries
3131

32+
* A library `semmle.javascript.explore.CallGraph` has been added to help write queries for exploring the call graph.
3233
* Added data flow for `Map` and `Set`, and added matching type-tracking steps that can accessed using the `CollectionsTypeTracking` module.
Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,5 @@
11
/**
2-
* Provides machinery for performing backward data-flow exploration.
3-
*
4-
* Importing this module effectively makes all data-flow and taint-tracking configurations
5-
* ignore their `isSource` predicate. Instead, flow is tracked from any _initial node_ (that is,
6-
* a node without incoming flow) to a sink node. All initial nodes are then treated as source
7-
* nodes.
8-
*
9-
* Data-flow exploration cannot be used with configurations depending on other configurations.
10-
*
11-
* NOTE: This library should only be used for debugging, not in production code. Backward
12-
* exploration in particular does not scale on non-trivial code bases and hence is of limited
13-
* usefulness as it stands.
2+
* Alias for the library `semmle.javascript.explore.BackwardDataFlow`.
143
*/
154

16-
import javascript
17-
18-
private class BackwardExploringConfiguration extends DataFlow::Configuration {
19-
DataFlow::Configuration cfg;
20-
21-
BackwardExploringConfiguration() { this = cfg }
22-
23-
override predicate isSource(DataFlow::Node node) { any() }
24-
25-
override predicate isSource(DataFlow::Node node, DataFlow::FlowLabel lbl) { any() }
26-
27-
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
28-
exists(DataFlow::PathNode src, DataFlow::PathNode snk | hasFlowPath(src, snk) |
29-
source = src.getNode() and
30-
sink = snk.getNode()
31-
)
32-
}
33-
34-
override predicate hasFlowPath(DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink) {
35-
exists(DataFlow::MidPathNode first |
36-
source.getConfiguration() = this and
37-
source.getASuccessor() = first and
38-
not exists(DataFlow::MidPathNode mid | mid.getASuccessor() = first) and
39-
first.getASuccessor*() = sink
40-
)
41-
}
42-
}
5+
import semmle.javascript.explore.BackwardDataFlow
Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,5 @@
11
/**
2-
* Provides machinery for performing forward data-flow exploration.
3-
*
4-
* Importing this module effectively makes all data-flow and taint-tracking configurations
5-
* ignore their `isSink` predicate. Instead, flow is tracked from source nodes as far as
6-
* possible, until a _terminal node_ (that is, a node without any outgoing flow) is reached.
7-
* All terminal nodes are then treated as sink nodes.
8-
*
9-
* Data-flow exploration cannot be used with configurations depending on other configurations.
10-
*
11-
* NOTE: This library should only be used for debugging, not in production code.
2+
* Alias for the library `semmle.javascript.explore.ForwardDataFlow`.
123
*/
134

14-
import javascript
15-
16-
private class ForwardExploringConfiguration extends DataFlow::Configuration {
17-
DataFlow::Configuration cfg;
18-
19-
ForwardExploringConfiguration() { this = cfg }
20-
21-
override predicate isSink(DataFlow::Node node) { any() }
22-
23-
override predicate isSink(DataFlow::Node node, DataFlow::FlowLabel lbl) { any() }
24-
25-
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
26-
exists(DataFlow::PathNode src, DataFlow::PathNode snk | hasFlowPath(src, snk) |
27-
source = src.getNode() and
28-
sink = snk.getNode()
29-
)
30-
}
31-
32-
override predicate hasFlowPath(DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink) {
33-
exists(DataFlow::MidPathNode last |
34-
source.getConfiguration() = this and
35-
source.getASuccessor*() = last and
36-
not last.getASuccessor() instanceof DataFlow::MidPathNode and
37-
last.getASuccessor() = sink
38-
)
39-
}
40-
}
5+
import semmle.javascript.explore.ForwardDataFlow
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Provides machinery for performing backward data-flow exploration.
3+
*
4+
* Importing this module effectively makes all data-flow and taint-tracking configurations
5+
* ignore their `isSource` predicate. Instead, flow is tracked from any _initial node_ (that is,
6+
* a node without incoming flow) to a sink node. All initial nodes are then treated as source
7+
* nodes.
8+
*
9+
* Data-flow exploration cannot be used with configurations depending on other configurations.
10+
*
11+
* NOTE: This library should only be used for debugging and exploration, not in production code.
12+
* Backward exploration in particular does not scale on non-trivial code bases and hence is of limited
13+
* usefulness as it stands.
14+
*/
15+
16+
import javascript
17+
18+
private class BackwardExploringConfiguration extends DataFlow::Configuration {
19+
DataFlow::Configuration cfg;
20+
21+
BackwardExploringConfiguration() { this = cfg }
22+
23+
override predicate isSource(DataFlow::Node node) { any() }
24+
25+
override predicate isSource(DataFlow::Node node, DataFlow::FlowLabel lbl) { any() }
26+
27+
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
28+
exists(DataFlow::PathNode src, DataFlow::PathNode snk | hasFlowPath(src, snk) |
29+
source = src.getNode() and
30+
sink = snk.getNode()
31+
)
32+
}
33+
34+
override predicate hasFlowPath(DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink) {
35+
exists(DataFlow::MidPathNode first |
36+
source.getConfiguration() = this and
37+
source.getASuccessor() = first and
38+
not exists(DataFlow::MidPathNode mid | mid.getASuccessor() = first) and
39+
first.getASuccessor*() = sink
40+
)
41+
}
42+
}
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+
* NOTE: This library should only be used for debugging and exploration, not in production code.
23+
*/
24+
25+
import javascript
26+
private import DataFlow
27+
28+
/**
29+
* Holds if `pred -> succ` is an edge in the call graph.
30+
*
31+
* There are edges from calls to their callees,
32+
* and from functions to their contained calls and in some cases
33+
* their inner functions to model functions invoked indirectly
34+
* by being passed to another call.
35+
*/
36+
predicate callEdge(Node pred, Node succ) {
37+
exists(InvokeNode invoke, Function f |
38+
invoke.getACallee() = f and
39+
pred = invoke and
40+
succ = f.flow()
41+
or
42+
invoke.getContainer() = f and
43+
pred = f.flow() and
44+
succ = invoke
45+
)
46+
or
47+
exists(Function inner, Function outer |
48+
inner.getEnclosingContainer() = outer and
49+
not inner = outer.getAReturnedExpr() and
50+
pred = outer.flow() and
51+
succ = inner.flow()
52+
)
53+
}
54+
55+
/** Holds if `pred -> succ` is an edge in the call graph. */
56+
query predicate edges = callEdge/2;
57+
58+
/** Holds if `node` is part of the call graph. */
59+
query predicate nodes(Node node) {
60+
node instanceof InvokeNode or
61+
node instanceof FunctionNode
62+
}
63+
64+
/** Gets a call in a function that has no known call sites. */
65+
private InvokeNode rootCall() { not any(InvokeNode i).getACallee() = result.getContainer() }
66+
67+
/**
68+
* Holds if `invoke` should be used as the starting point of a call path.
69+
*/
70+
predicate isStartOfCallPath(InvokeNode invoke) {
71+
// `invoke` should either be a root call or be part of a cycle with no root.
72+
// An equivalent requirement is that `invoke` is not reachable from a root.
73+
not callEdge+(rootCall(), invoke)
74+
}
75+
76+
/** Gets a function that contains no calls to other functions. */
77+
private FunctionNode leafFunction() { not callEdge(result, _) }
78+
79+
/**
80+
* Holds if `fun` should be used as the end 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+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Provides machinery for performing forward data-flow exploration.
3+
*
4+
* Importing this module effectively makes all data-flow and taint-tracking configurations
5+
* ignore their `isSink` predicate. Instead, flow is tracked from source nodes as far as
6+
* possible, until a _terminal node_ (that is, a node without any outgoing flow) is reached.
7+
* All terminal nodes are then treated as sink nodes.
8+
*
9+
* Data-flow exploration cannot be used with configurations depending on other configurations.
10+
*
11+
* NOTE: This library should only be used for debugging and exploration, not in production code.
12+
*/
13+
14+
import javascript
15+
16+
private class ForwardExploringConfiguration extends DataFlow::Configuration {
17+
DataFlow::Configuration cfg;
18+
19+
ForwardExploringConfiguration() { this = cfg }
20+
21+
override predicate isSink(DataFlow::Node node) { any() }
22+
23+
override predicate isSink(DataFlow::Node node, DataFlow::FlowLabel lbl) { any() }
24+
25+
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
26+
exists(DataFlow::PathNode src, DataFlow::PathNode snk | hasFlowPath(src, snk) |
27+
source = src.getNode() and
28+
sink = snk.getNode()
29+
)
30+
}
31+
32+
override predicate hasFlowPath(DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink) {
33+
exists(DataFlow::MidPathNode last |
34+
source.getConfiguration() = this and
35+
source.getASuccessor*() = last and
36+
not last.getASuccessor() instanceof DataFlow::MidPathNode and
37+
last.getASuccessor() = sink
38+
)
39+
}
40+
}

0 commit comments

Comments
 (0)