Merge pull request #2886 from asger-semmle/js/call-graph-exploration

Approved by erik-krogh, esbena
This commit is contained in:
semmle-qlci
2020-05-14 14:01:23 +01:00
committed by GitHub
6 changed files with 172 additions and 76 deletions

View File

@@ -29,4 +29,5 @@
## Changes to libraries
* A library `semmle.javascript.explore.CallGraph` has been added to help write queries for exploring the call graph.
* Added data flow for `Map` and `Set`, and added matching type-tracking steps that can accessed using the `CollectionsTypeTracking` module.

View File

@@ -1,42 +1,5 @@
/**
* Provides machinery for performing backward data-flow exploration.
*
* Importing this module effectively makes all data-flow and taint-tracking configurations
* ignore their `isSource` predicate. Instead, flow is tracked from any _initial node_ (that is,
* a node without incoming flow) to a sink node. All initial nodes are then treated as source
* nodes.
*
* Data-flow exploration cannot be used with configurations depending on other configurations.
*
* NOTE: This library should only be used for debugging, not in production code. Backward
* exploration in particular does not scale on non-trivial code bases and hence is of limited
* usefulness as it stands.
* Alias for the library `semmle.javascript.explore.BackwardDataFlow`.
*/
import javascript
private class BackwardExploringConfiguration extends DataFlow::Configuration {
DataFlow::Configuration cfg;
BackwardExploringConfiguration() { this = cfg }
override predicate isSource(DataFlow::Node node) { any() }
override predicate isSource(DataFlow::Node node, DataFlow::FlowLabel lbl) { any() }
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
exists(DataFlow::PathNode src, DataFlow::PathNode snk | hasFlowPath(src, snk) |
source = src.getNode() and
sink = snk.getNode()
)
}
override predicate hasFlowPath(DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink) {
exists(DataFlow::MidPathNode first |
source.getConfiguration() = this and
source.getASuccessor() = first and
not exists(DataFlow::MidPathNode mid | mid.getASuccessor() = first) and
first.getASuccessor*() = sink
)
}
}
import semmle.javascript.explore.BackwardDataFlow

View File

@@ -1,40 +1,5 @@
/**
* Provides machinery for performing forward data-flow exploration.
*
* Importing this module effectively makes all data-flow and taint-tracking configurations
* ignore their `isSink` predicate. Instead, flow is tracked from source nodes as far as
* possible, until a _terminal node_ (that is, a node without any outgoing flow) is reached.
* All terminal nodes are then treated as sink nodes.
*
* Data-flow exploration cannot be used with configurations depending on other configurations.
*
* NOTE: This library should only be used for debugging, not in production code.
* Alias for the library `semmle.javascript.explore.ForwardDataFlow`.
*/
import javascript
private class ForwardExploringConfiguration extends DataFlow::Configuration {
DataFlow::Configuration cfg;
ForwardExploringConfiguration() { this = cfg }
override predicate isSink(DataFlow::Node node) { any() }
override predicate isSink(DataFlow::Node node, DataFlow::FlowLabel lbl) { any() }
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
exists(DataFlow::PathNode src, DataFlow::PathNode snk | hasFlowPath(src, snk) |
source = src.getNode() and
sink = snk.getNode()
)
}
override predicate hasFlowPath(DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink) {
exists(DataFlow::MidPathNode last |
source.getConfiguration() = this and
source.getASuccessor*() = last and
not last.getASuccessor() instanceof DataFlow::MidPathNode and
last.getASuccessor() = sink
)
}
}
import semmle.javascript.explore.ForwardDataFlow

View File

@@ -0,0 +1,42 @@
/**
* Provides machinery for performing backward data-flow exploration.
*
* Importing this module effectively makes all data-flow and taint-tracking configurations
* ignore their `isSource` predicate. Instead, flow is tracked from any _initial node_ (that is,
* a node without incoming flow) to a sink node. All initial nodes are then treated as source
* nodes.
*
* Data-flow exploration cannot be used with configurations depending on other configurations.
*
* NOTE: This library should only be used for debugging and exploration, not in production code.
* Backward exploration in particular does not scale on non-trivial code bases and hence is of limited
* usefulness as it stands.
*/
import javascript
private class BackwardExploringConfiguration extends DataFlow::Configuration {
DataFlow::Configuration cfg;
BackwardExploringConfiguration() { this = cfg }
override predicate isSource(DataFlow::Node node) { any() }
override predicate isSource(DataFlow::Node node, DataFlow::FlowLabel lbl) { any() }
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
exists(DataFlow::PathNode src, DataFlow::PathNode snk | hasFlowPath(src, snk) |
source = src.getNode() and
sink = snk.getNode()
)
}
override predicate hasFlowPath(DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink) {
exists(DataFlow::MidPathNode first |
source.getConfiguration() = this and
source.getASuccessor() = first and
not exists(DataFlow::MidPathNode mid | mid.getASuccessor() = first) and
first.getASuccessor*() = sink
)
}
}

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'"
* ```
*
* NOTE: This library should only be used for debugging and exploration, not in production code.
*/
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 to model functions invoked indirectly
* by being passed to another call.
*/
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 that contains no calls to other functions. */
private FunctionNode leafFunction() { not callEdge(result, _) }
/**
* Holds if `fun` should be used as the end 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())
}

View File

@@ -0,0 +1,40 @@
/**
* Provides machinery for performing forward data-flow exploration.
*
* Importing this module effectively makes all data-flow and taint-tracking configurations
* ignore their `isSink` predicate. Instead, flow is tracked from source nodes as far as
* possible, until a _terminal node_ (that is, a node without any outgoing flow) is reached.
* All terminal nodes are then treated as sink nodes.
*
* Data-flow exploration cannot be used with configurations depending on other configurations.
*
* NOTE: This library should only be used for debugging and exploration, not in production code.
*/
import javascript
private class ForwardExploringConfiguration extends DataFlow::Configuration {
DataFlow::Configuration cfg;
ForwardExploringConfiguration() { this = cfg }
override predicate isSink(DataFlow::Node node) { any() }
override predicate isSink(DataFlow::Node node, DataFlow::FlowLabel lbl) { any() }
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
exists(DataFlow::PathNode src, DataFlow::PathNode snk | hasFlowPath(src, snk) |
source = src.getNode() and
sink = snk.getNode()
)
}
override predicate hasFlowPath(DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink) {
exists(DataFlow::MidPathNode last |
source.getConfiguration() = this and
source.getASuccessor*() = last and
not last.getASuccessor() instanceof DataFlow::MidPathNode and
last.getASuccessor() = sink
)
}
}