JavaScript: Teach type trackers to track flow through one level of properties.

This commit is contained in:
Max Schaefer
2019-03-21 10:16:57 +00:00
parent 9fbc0eb717
commit 084159dcfd
13 changed files with 128 additions and 92 deletions

View File

@@ -9,13 +9,19 @@
import javascript import javascript
private import internal.FlowSteps private import internal.FlowSteps
private class PropertyName extends string {
PropertyName() { this = any(DataFlow::PropRef pr).getPropertyName() }
}
/** /**
* A description of a step on an inter-procedural data flow path. * A description of a step on an inter-procedural data flow path.
*/ */
private newtype TStepSummary = private newtype TStepSummary =
LevelStep() or LevelStep() or
CallStep() or CallStep() or
ReturnStep() ReturnStep() or
StoreStep(PropertyName prop) or
LoadStep(PropertyName prop)
/** /**
* INTERNAL: Use `TypeTracker` or `TypeBackTracker` instead. * INTERNAL: Use `TypeTracker` or `TypeBackTracker` instead.
@@ -64,6 +70,14 @@ module StepSummary {
// Flow through an instance field between members of the same class // Flow through an instance field between members of the same class
DataFlow::localFieldStep(predNode, succ) and DataFlow::localFieldStep(predNode, succ) and
summary = LevelStep() summary = LevelStep()
or
exists(string prop |
basicStoreStep(predNode, succ, prop) and
summary = StoreStep(prop)
or
loadStep(predNode, succ, prop) and
summary = LoadStep(prop)
)
) )
} }
@@ -73,8 +87,22 @@ module StepSummary {
* Appends a step summary onto a type-tracking summary. * Appends a step summary onto a type-tracking summary.
*/ */
TypeTracker append(TypeTracker type, StepSummary summary) { TypeTracker append(TypeTracker type, StepSummary summary) {
not (type.hasCall() = true and summary.hasReturn() = true) and exists(boolean hadCall, boolean hasCall, string oldProp, string newProp |
result.hasCall() = type.hasCall().booleanOr(summary.hasCall()) hadCall = type.hasCall() and
oldProp = type.getProp()
|
not (hadCall = true and summary.hasReturn() = true) and
hasCall = hadCall.booleanOr(summary.hasCall()) and
(
if summary instanceof StoreStep
then oldProp = "" and summary = StoreStep(newProp)
else
if summary instanceof LoadStep
then summary = LoadStep(oldProp) and newProp = ""
else newProp = oldProp
) and
result = MkTypeTracker(hasCall, newProp)
)
} }
/** /**
@@ -83,12 +111,27 @@ module StepSummary {
* Prepends a step summary before a backwards type-tracking summary. * Prepends a step summary before a backwards type-tracking summary.
*/ */
TypeBackTracker prepend(StepSummary summary, TypeBackTracker type) { TypeBackTracker prepend(StepSummary summary, TypeBackTracker type) {
not (type.hasReturn() = true and summary.hasCall() = true) and exists(boolean hadReturn, boolean hasReturn, string oldProp, string newProp |
result.hasReturn() = type.hasReturn().booleanOr(summary.hasReturn()) hadReturn = type.hasReturn() and
oldProp = type.getProp()
|
not (hadReturn = true and summary.hasCall() = true) and
hasReturn = hadReturn.booleanOr(summary.hasReturn()) and
(
if summary instanceof StoreStep
then summary = StoreStep(oldProp) and newProp = ""
else
if summary instanceof LoadStep
then oldProp = "" and summary = LoadStep(newProp)
else newProp = oldProp
) and
result = MkTypeBackTracker(hasReturn, newProp)
)
} }
} }
private newtype TTypeTracker = MkTypeTracker(Boolean hasCall) private newtype TTypeTracker =
MkTypeTracker(Boolean hasCall, string prop) { prop = "" or prop instanceof PropertyName }
/** /**
* EXPERIMENTAL. * EXPERIMENTAL.
@@ -112,7 +155,7 @@ private newtype TTypeTracker = MkTypeTracker(Boolean hasCall)
* ) * )
* } * }
* *
* DataFlow::SourceNode myType() { result = myType(_) } * DataFlow::SourceNode myType() { result = myType(DataFlow::TypeTracker::end()) }
* ``` * ```
* *
* To track values backwards, which can be useful for tracking * To track values backwards, which can be useful for tracking
@@ -121,18 +164,24 @@ private newtype TTypeTracker = MkTypeTracker(Boolean hasCall)
class TypeTracker extends TTypeTracker { class TypeTracker extends TTypeTracker {
Boolean hasCall; Boolean hasCall;
TypeTracker() { this = MkTypeTracker(hasCall) } string prop;
TypeTracker() { this = MkTypeTracker(hasCall, prop) }
string toString() { string toString() {
hasCall = true and result = "type tracker with call steps" exists(string withCall, string withProp |
or (if hasCall = true then withCall = "with" else withCall = "without") and
hasCall = false and result = "type tracker without call steps" (if prop != "" then withProp = " with property " + prop else withProp = "") and
result = "type tracker " + withCall + " call steps" + withProp
)
} }
/** /**
* Holds if this is the starting point of type tracking. * Holds if this is the starting point of type tracking.
*/ */
predicate start() { hasCall = false } predicate start() { hasCall = false and prop = "" }
predicate end() { prop = "" }
/** /**
* INTERNAL. DO NOT USE. * INTERNAL. DO NOT USE.
@@ -140,9 +189,16 @@ class TypeTracker extends TTypeTracker {
* Holds if this type has been tracked into a call. * Holds if this type has been tracked into a call.
*/ */
boolean hasCall() { result = hasCall } boolean hasCall() { result = hasCall }
string getProp() { result = prop }
} }
private newtype TTypeBackTracker = MkTypeBackTracker(Boolean hasReturn) module TypeTracker {
TypeTracker end() { result.end() }
}
private newtype TTypeBackTracker =
MkTypeBackTracker(Boolean hasReturn, string prop) { prop = "" or prop instanceof PropertyName }
/** /**
* EXPERIMENTAL. * EXPERIMENTAL.
@@ -168,24 +224,30 @@ private newtype TTypeBackTracker = MkTypeBackTracker(Boolean hasReturn)
* ) * )
* } * }
* *
* DataFlow::SourceNode myCallback() { result = myCallback(_) } * DataFlow::SourceNode myCallback() { result = myCallback(DataFlow::TypeBackTracker::end()) }
* ``` * ```
*/ */
class TypeBackTracker extends TTypeBackTracker { class TypeBackTracker extends TTypeBackTracker {
Boolean hasReturn; Boolean hasReturn;
TypeBackTracker() { this = MkTypeBackTracker(hasReturn) } string prop;
TypeBackTracker() { this = MkTypeBackTracker(hasReturn, prop) }
string toString() { string toString() {
hasReturn = true and result = "type back-tracker with return steps" exists(string withReturn, string withProp |
or (if hasReturn = true then withReturn = "with" else withReturn = "without") and
hasReturn = false and result = "type back-tracker without return steps" (if prop != "" then withProp = " with property " + prop else withProp = "") and
result = "type back-tracker " + withReturn + " return steps" + withProp
)
} }
/** /**
* Holds if this is the starting point of type tracking. * Holds if this is the starting point of type tracking.
*/ */
predicate start() { hasReturn = false } predicate start() { hasReturn = false and prop = "" }
predicate end() { prop = "" }
/** /**
* INTERNAL. DO NOT USE. * INTERNAL. DO NOT USE.
@@ -193,4 +255,10 @@ class TypeBackTracker extends TTypeBackTracker {
* Holds if this type has been back-tracked into a call through return edge. * Holds if this type has been back-tracked into a call through return edge.
*/ */
boolean hasReturn() { result = hasReturn } boolean hasReturn() { result = hasReturn }
string getProp() { result = prop }
}
module TypeBackTracker {
TypeBackTracker end() { result.end() }
} }

View File

@@ -118,7 +118,7 @@ module Connect {
} }
override DataFlow::SourceNode getARouteHandler() { override DataFlow::SourceNode getARouteHandler() {
result = getARouteHandler(_) result = getARouteHandler(DataFlow::TypeBackTracker::end())
} }
private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) { private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) {

View File

@@ -69,7 +69,7 @@ module Electron {
* A data flow node whose value may originate from a browser object instantiation. * A data flow node whose value may originate from a browser object instantiation.
*/ */
private class BrowserObjectByFlow extends BrowserObject { private class BrowserObjectByFlow extends BrowserObject {
BrowserObjectByFlow() { browserObject(_).flowsTo(this) } BrowserObjectByFlow() { browserObject(DataFlow::TypeTracker::end()).flowsTo(this) }
} }
/** /**

View File

@@ -111,7 +111,7 @@ module Express {
Expr getLastRouteHandlerExpr() { result = max(int i | | getRouteHandlerExpr(i) order by i) } Expr getLastRouteHandlerExpr() { result = max(int i | | getRouteHandlerExpr(i) order by i) }
override DataFlow::SourceNode getARouteHandler() { override DataFlow::SourceNode getARouteHandler() {
result = getARouteHandler(_) result = getARouteHandler(DataFlow::TypeBackTracker::end())
} }
private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) { private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) {

View File

@@ -282,7 +282,7 @@ module HTTP {
*/ */
abstract RouteHandler getRouteHandler(); abstract RouteHandler getRouteHandler();
predicate flowsTo(DataFlow::Node nd) { flowsToSourceNode(_).flowsTo(nd) } predicate flowsTo(DataFlow::Node nd) { flowsToSourceNode(DataFlow::TypeTracker::end()).flowsTo(nd) }
private DataFlow::SourceNode flowsToSourceNode(DataFlow::TypeTracker t) { private DataFlow::SourceNode flowsToSourceNode(DataFlow::TypeTracker t) {
t.start() and t.start() and
@@ -303,7 +303,7 @@ module HTTP {
*/ */
abstract RouteHandler getRouteHandler(); abstract RouteHandler getRouteHandler();
predicate flowsTo(DataFlow::Node nd) { flowsToSourceNode(_).flowsTo(nd) } predicate flowsTo(DataFlow::Node nd) { flowsToSourceNode(DataFlow::TypeTracker::end()).flowsTo(nd) }
private DataFlow::SourceNode flowsToSourceNode(DataFlow::TypeTracker t) { private DataFlow::SourceNode flowsToSourceNode(DataFlow::TypeTracker t) {
t.start() and t.start() and

View File

@@ -193,7 +193,7 @@ module Hapi {
} }
override DataFlow::SourceNode getARouteHandler() { override DataFlow::SourceNode getARouteHandler() {
result = getARouteHandler(_) result = getARouteHandler(DataFlow::TypeBackTracker::end())
} }
private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) { private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) {

View File

@@ -79,7 +79,7 @@ module Koa {
RouteHandler getRouteHandler() { result = rh } RouteHandler getRouteHandler() { result = rh }
predicate flowsTo(DataFlow::Node nd) { predicate flowsTo(DataFlow::Node nd) {
flowsToSourceNode(_).flowsTo(nd) flowsToSourceNode(DataFlow::TypeTracker::end()).flowsTo(nd)
} }
private DataFlow::SourceNode flowsToSourceNode(DataFlow::TypeTracker t) { private DataFlow::SourceNode flowsToSourceNode(DataFlow::TypeTracker t) {

View File

@@ -189,7 +189,7 @@ module NodeJSLib {
} }
override DataFlow::SourceNode getARouteHandler() { override DataFlow::SourceNode getARouteHandler() {
result = getARouteHandler(_) result = getARouteHandler(DataFlow::TypeBackTracker::end())
} }
private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) { private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) {

View File

@@ -61,7 +61,7 @@ module SocketIO {
class ServerNode extends DataFlow::SourceNode { class ServerNode extends DataFlow::SourceNode {
ServerObject srv; ServerObject srv;
ServerNode() { this = server(srv, _) } ServerNode() { this = server(srv, DataFlow::TypeTracker::end()) }
/** Gets the server to which this node refers. */ /** Gets the server to which this node refers. */
ServerObject getServer() { result = srv } ServerObject getServer() { result = srv }
@@ -130,7 +130,7 @@ module SocketIO {
class NamespaceNode extends DataFlow::SourceNode { class NamespaceNode extends DataFlow::SourceNode {
NamespaceObject ns; NamespaceObject ns;
NamespaceNode() { this = namespace(ns, _) } NamespaceNode() { this = namespace(ns, DataFlow::TypeTracker::end()) }
/** Gets the namespace to which this node refers. */ /** Gets the namespace to which this node refers. */
NamespaceObject getNamespace() { result = ns } NamespaceObject getNamespace() { result = ns }
@@ -194,7 +194,7 @@ module SocketIO {
class SocketNode extends DataFlow::SourceNode { class SocketNode extends DataFlow::SourceNode {
NamespaceObject ns; NamespaceObject ns;
SocketNode() { this = socket(ns, _) } SocketNode() { this = socket(ns, DataFlow::TypeTracker::end()) }
/** Gets the namespace to which this socket belongs. */ /** Gets the namespace to which this socket belongs. */
NamespaceObject getNamespace() { result = ns } NamespaceObject getNamespace() { result = ns }
@@ -417,7 +417,7 @@ module SocketIOClient {
class SocketNode extends DataFlow::SourceNode { class SocketNode extends DataFlow::SourceNode {
DataFlow::InvokeNode invk; DataFlow::InvokeNode invk;
SocketNode() { this = socket(invk, _) } SocketNode() { this = socket(invk, DataFlow::TypeTracker::end()) }
/** Gets the path of the namespace this socket belongs to, if it can be determined. */ /** Gets the path of the namespace this socket belongs to, if it can be determined. */
string getNamespacePath() { string getNamespacePath() {

View File

@@ -6,86 +6,68 @@ string chainableMethod() {
} }
class ApiObject extends DataFlow::NewNode { class ApiObject extends DataFlow::NewNode {
ApiObject() { ApiObject() { this = DataFlow::moduleImport("@test/myapi").getAnInstantiation() }
this = DataFlow::moduleImport("@test/myapi").getAnInstantiation()
}
DataFlow::SourceNode ref(DataFlow::TypeTracker t) { DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
t.start() and t.start() and
result = this result = this
or or
t.start() and t.start() and
result = ref(_).getAMethodCall(chainableMethod()) result = ref(DataFlow::TypeTracker::end()).getAMethodCall(chainableMethod())
or or
exists(DataFlow::TypeTracker t2 | exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t))
result = ref(t2).track(t2, t)
)
}
DataFlow::SourceNode ref() {
result = ref(_)
} }
DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) }
} }
class Connection extends DataFlow::SourceNode { class Connection extends DataFlow::SourceNode {
ApiObject api; ApiObject api;
Connection() { Connection() { this = api.ref().getAMethodCall("createConnection") }
this = api.ref().getAMethodCall("createConnection")
}
DataFlow::SourceNode ref(DataFlow::TypeTracker t) { DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
t.start() and t.start() and
result = this result = this
or or
exists(DataFlow::TypeTracker t2 | exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t))
result = ref(t2).track(t2, t)
)
}
DataFlow::SourceNode ref() {
result = ref(_)
} }
DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) }
DataFlow::SourceNode getACallbackNode(DataFlow::TypeBackTracker t) { DataFlow::SourceNode getACallbackNode(DataFlow::TypeBackTracker t) {
t.start() and t.start() and
result = ref().getAMethodCall("getData").getArgument(0).getALocalSource() result = ref().getAMethodCall("getData").getArgument(0).getALocalSource()
or or
exists(DataFlow::TypeBackTracker t2 | exists(DataFlow::TypeBackTracker t2 | result = getACallbackNode(t2).backtrack(t2, t))
result = getACallbackNode(t2).backtrack(t2, t)
)
} }
DataFlow::FunctionNode getACallback() { DataFlow::FunctionNode getACallback() {
result = getACallbackNode(_).getAFunctionValue() result = getACallbackNode(DataFlow::TypeBackTracker::end()).getAFunctionValue()
} }
} }
class DataValue extends DataFlow::SourceNode { class DataValue extends DataFlow::SourceNode {
Connection connection; Connection connection;
DataValue() { DataValue() { this = connection.getACallback().getParameter(0) }
this = connection.getACallback().getParameter(0)
}
DataFlow::SourceNode ref(DataFlow::TypeTracker t) { DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
t.start() and t.start() and
result = this result = this
or or
exists(DataFlow::TypeTracker t2 | exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t))
result = ref(t2).track(t2, t)
)
}
DataFlow::SourceNode ref() {
result = ref(_)
} }
DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) }
} }
query DataFlow::SourceNode test_ApiObject() { result = any(ApiObject obj).ref() } query DataFlow::SourceNode test_ApiObject() { result = any(ApiObject obj).ref() }
query DataFlow::SourceNode test_Connection() { result = any(Connection c).ref() } query DataFlow::SourceNode test_Connection() { result = any(Connection c).ref() }
query DataFlow::SourceNode test_DataCallback() { result = any(Connection c).getACallbackNode(_) } query DataFlow::SourceNode test_DataCallback() {
result = any(Connection c).getACallbackNode(DataFlow::TypeBackTracker::end())
}
query DataFlow::SourceNode test_DataValue() { result = any(DataValue v).ref() } query DataFlow::SourceNode test_DataValue() { result = any(DataValue v).ref() }

View File

@@ -10,52 +10,38 @@ DataFlow::SourceNode apiObject(DataFlow::TypeTracker t) {
result = DataFlow::moduleImport("@test/myapi").getAnInstantiation() result = DataFlow::moduleImport("@test/myapi").getAnInstantiation()
or or
t.start() and t.start() and
result = apiObject(_).getAMethodCall(chainableMethod()) result = apiObject(DataFlow::TypeTracker::end()).getAMethodCall(chainableMethod())
or or
exists(DataFlow::TypeTracker t2 | exists(DataFlow::TypeTracker t2 | result = apiObject(t2).track(t2, t))
result = apiObject(t2).track(t2, t)
)
} }
query DataFlow::SourceNode apiObject() { query DataFlow::SourceNode apiObject() { result = apiObject(DataFlow::TypeTracker::end()) }
result = apiObject(_)
}
query DataFlow::SourceNode connection(DataFlow::TypeTracker t) { query DataFlow::SourceNode connection(DataFlow::TypeTracker t) {
t.start() and t.start() and
result = apiObject().getAMethodCall("createConnection") result = apiObject().getAMethodCall("createConnection")
or or
exists(DataFlow::TypeTracker t2 | exists(DataFlow::TypeTracker t2 | result = connection(t2).track(t2, t))
result = connection(t2).track(t2, t)
)
} }
DataFlow::SourceNode connection() { DataFlow::SourceNode connection() { result = connection(DataFlow::TypeTracker::end()) }
result = connection(_)
}
DataFlow::SourceNode dataCallback(DataFlow::TypeBackTracker t) { DataFlow::SourceNode dataCallback(DataFlow::TypeBackTracker t) {
t.start() and t.start() and
result = connection().getAMethodCall("getData").getArgument(0).getALocalSource() result = connection().getAMethodCall("getData").getArgument(0).getALocalSource()
or or
exists(DataFlow::TypeBackTracker t2 | exists(DataFlow::TypeBackTracker t2 | result = dataCallback(t2).backtrack(t2, t))
result = dataCallback(t2).backtrack(t2, t)
)
} }
query DataFlow::SourceNode dataCallback() { query DataFlow::SourceNode dataCallback() {
result = dataCallback(_) result = dataCallback(DataFlow::TypeBackTracker::end())
} }
DataFlow::SourceNode dataValue(DataFlow::TypeTracker t) { DataFlow::SourceNode dataValue(DataFlow::TypeTracker t) {
t.start() and t.start() and
result = dataCallback().getAFunctionValue().getParameter(0) result = dataCallback().getAFunctionValue().getParameter(0)
or or
exists(DataFlow::TypeTracker t2 | exists(DataFlow::TypeTracker t2 | result = dataValue(t2).track(t2, t))
result = dataValue(t2).track(t2, t)
)
} }
query DataFlow::SourceNode dataValue() { query DataFlow::SourceNode dataValue() { result = dataValue(DataFlow::TypeTracker::end()) }
result = dataValue(_)
}

View File

@@ -11,6 +11,8 @@
| src/exported-handler.js:1:19:1:55 | functio ... res) {} | | src/exported-handler.js:1:19:1:55 | functio ... res) {} |
| src/exported-middleware-attacher-2.js:2:13:2:32 | function(req, res){} | | src/exported-middleware-attacher-2.js:2:13:2:32 | function(req, res){} |
| src/exported-middleware-attacher.js:2:13:2:32 | function(req, res){} | | src/exported-middleware-attacher.js:2:13:2:32 | function(req, res){} |
| src/handler-in-property.js:5:14:5:33 | function(req, res){} |
| src/handler-in-property.js:12:18:12:37 | function(req, res){} |
| src/middleware-attacher-getter.js:4:17:4:36 | function(req, res){} | | src/middleware-attacher-getter.js:4:17:4:36 | function(req, res){} |
| src/middleware-attacher-getter.js:19:19:19:38 | function(req, res){} | | src/middleware-attacher-getter.js:19:19:19:38 | function(req, res){} |
| src/middleware-attacher.js:3:13:3:32 | function(req, res){} | | src/middleware-attacher.js:3:13:3:32 | function(req, res){} |

View File

@@ -1,7 +1,5 @@
| src/bound-handler.js:4:1:4:28 | functio ... res){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. | | src/bound-handler.js:4:1:4:28 | functio ... res){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/bound-handler.js:9:12:9:31 | function(req, res){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. | | src/bound-handler.js:9:12:9:31 | function(req, res){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/handler-in-property.js:5:14:5:33 | function(req, res){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/handler-in-property.js:12:18:12:37 | function(req, res){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/hapi.js:1:1:1:30 | functio ... t, h){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. | | src/hapi.js:1:1:1:30 | functio ... t, h){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/iterated-handlers.js:4:2:4:22 | functio ... res){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. | | src/iterated-handlers.js:4:2:4:22 | functio ... res){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/middleware-attacher-getter.js:29:32:29:51 | function(req, res){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. | | src/middleware-attacher-getter.js:29:32:29:51 | function(req, res){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |