mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Merge pull request #2886 from asger-semmle/js/call-graph-exploration
Approved by erik-krogh, esbena
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
85
javascript/ql/src/semmle/javascript/explore/CallGraph.qll
Normal file
85
javascript/ql/src/semmle/javascript/explore/CallGraph.qll
Normal 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())
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user