Skip to content
1 change: 1 addition & 0 deletions change-notes/1.25/analysis-javascript.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,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.
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
42 changes: 42 additions & 0 deletions javascript/ql/src/semmle/javascript/explore/BackwardDataFlow.qll
Original file line number Diff line number Diff line change
@@ -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 changes: 85 additions & 0 deletions javascript/ql/src/semmle/javascript/explore/CallGraph.qll
Original file line number Diff line number Diff line change
@@ -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())
}
40 changes: 40 additions & 0 deletions javascript/ql/src/semmle/javascript/explore/ForwardDataFlow.qll
Original file line number Diff line number Diff line change
@@ -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
)
}
}