JS: Add isMiddlewareSetup() hook to Routing model

This commit is contained in:
Asger F
2025-04-22 12:00:02 +02:00
parent 5c3556da66
commit 00661b62dc
5 changed files with 77 additions and 6 deletions

View File

@@ -139,6 +139,8 @@ module Routing {
predicate mayResumeDispatch() {
this.getLastChild().mayResumeDispatch()
or
isInMiddlewareSetup(this)
or
exists(this.(RouteHandler).getAContinuationInvocation())
or
// Leaf nodes that aren't functions are assumed to invoke their continuation
@@ -155,6 +157,8 @@ module Routing {
predicate definitelyResumesDispatch() {
this.getLastChild().definitelyResumesDispatch()
or
isInMiddlewareSetup(this)
or
exists(this.(RouteHandler).getAContinuationInvocation())
or
this instanceof MkRouter
@@ -325,6 +329,19 @@ module Routing {
DataFlow::Node getValueImplicitlyStoredInAccessPath(int n, string path) { none() }
}
/**
* Holds if `node` is installed at a route handler that is declared to be a middleware setup,
* and is therefore assume to resume dispatch.
*/
private predicate isInMiddlewareSetup(Node node) {
exists(RouteSetup::Range range |
node = getRouteSetupNode(range) and
range.isMiddlewareSetup()
)
or
isInMiddlewareSetup(node.getParent())
}
/** Holds if `pred` and `succ` are adjacent siblings and `succ` is installed after `pred`. */
private predicate areSiblings(Node pred, Node succ) {
exists(ValueNode::Range base, int n |
@@ -612,6 +629,20 @@ module Routing {
* Holds if this route setup targets `router` and occurs at the given `cfgNode`.
*/
abstract predicate isInstalledAt(Router::Range router, ControlFlowNode cfgNode);
/**
* Holds if this is a middleware setup, meaning dispatch will resume after the
* route handlers in this route setup have completed (usually meaning that they have returned a promise, which has resolved).
*
* This should only be overridden when the route setup itself determines whether subsequent
* route handlers are invoked afterwards.
* - For Express-like libraries, the route _handler_ determines whether to resume dispatch,
* based on whether the `next` callback is invoked. For such libraries, do not override `isMiddlewareSetup`.
* - For Fastify-like libraries, the route _setup_ determines whether to resume dispatch.
* For example, `.addHook()` will resume dispatch whereas `.get()` will not. `isMiddlewareSetup()` should thus
* hold for `.addHook()` but not for `.get()` calls.
*/
predicate isMiddlewareSetup() { none() }
}
/**
@@ -892,10 +923,14 @@ module Routing {
* based on `Node::Range::getValueAtAccessPath`.
*/
private DataFlow::Node getAnAccessPathRhs(Node base, int n, string path) {
// Assigned in the body of a route handler function, whi
// Assigned in the body of a route handler function, which is a middleware
exists(RouteHandler handler | base = handler |
result = AccessPath::getAnAssignmentTo(handler.getParameter(n).ref(), path) and
exists(handler.getAContinuationInvocation())
(
exists(handler.getAContinuationInvocation())
or
isInMiddlewareSetup(handler)
)
)
or
// Implicit assignment contributed by framework model

View File

@@ -164,13 +164,19 @@ module Fastify {
private class ShorthandRoutingTreeSetup extends Routing::RouteSetup::MethodCall instanceof RouteSetup
{
ShorthandRoutingTreeSetup() { not this.getMethodName() = "route" }
ShorthandRoutingTreeSetup() { not this.getMethodName() = ["route", "addHook"] }
override string getRelativePath() { result = this.getArgument(0).getStringValue() }
override Http::RequestMethodName getHttpMethod() { result = this.getMethodName().toUpperCase() }
}
private class AddHookRouteSetup extends Routing::RouteSetup::MethodCall instanceof RouteSetup {
AddHookRouteSetup() { this.getMethodName() = "addHook" }
override predicate isMiddlewareSetup() { any() }
}
/** Gets the name of the `n`th handler function that can be installed a route setup, in order of execution. */
private string getNthHandlerName(int n) {
result =