Skip to content

Commit b58c263

Browse files
authored
Merge pull request #602 from esben-semmle/js/additional-route-handlers-from-context
Approved by xiemaisi
2 parents 43d14ce + 104eafe commit b58c263

File tree

13 files changed

+99
-19
lines changed

13 files changed

+99
-19
lines changed

change-notes/1.20/analysis-javascript.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## General improvements
44

5+
* Support for popular libraries has been improved. Consequently, queries may produce more results on code bases that use the following features:
6+
- servers, for example [hapi](https://hapijs.com/)
7+
58
## New queries
69

710
| **Query** | **Tags** | **Purpose** |

javascript/ql/src/semmle/javascript/frameworks/ConnectExpressShared.qll

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ module ConnectExpressShared {
3232
*
3333
* For example, this could be the function `function(req, res, next){...}`.
3434
*/
35-
class RouteHandlerCandidate extends HTTP::RouteHandlerCandidate, DataFlow::FunctionNode {
36-
37-
override Function astNode;
35+
class RouteHandlerCandidate extends HTTP::RouteHandlerCandidate {
3836

3937
RouteHandlerCandidate() {
4038
exists (string request, string response, string next, string error |

javascript/ql/src/semmle/javascript/frameworks/Hapi.qll

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -194,26 +194,83 @@ module Hapi {
194194
*/
195195
class RouteSetup extends MethodCallExpr, HTTP::Servers::StandardRouteSetup {
196196
ServerDefinition server;
197-
string methodName;
197+
Expr handler;
198198

199199
RouteSetup() {
200200
server.flowsTo(getReceiver()) and
201-
methodName = getMethodName() and
202-
(methodName = "route" or methodName = "ext")
201+
(
202+
// server.route({ handler: fun })
203+
getMethodName() = "route" and
204+
hasOptionArgument(0, "handler", handler)
205+
or
206+
// server.ext('/', fun)
207+
getMethodName() = "ext" and
208+
handler = getArgument(1)
209+
)
203210
}
204211

205212
override DataFlow::SourceNode getARouteHandler() {
206-
// server.route({ handler: fun })
207-
methodName = "route" and
208-
result.flowsToExpr(any(Expr e | hasOptionArgument(0, "handler", e)))
209-
or
210-
// server.ext('/', fun)
211-
methodName = "ext" and
212-
result.flowsToExpr(getArgument(1))
213+
result.(DataFlow::SourceNode).flowsTo(handler.flow()) or
214+
result.(DataFlow::TrackedNode).flowsTo(handler.flow())
215+
}
216+
217+
Expr getRouteHandlerExpr() {
218+
result = handler
213219
}
214220

215221
override Expr getServer() {
216222
result = server
217223
}
218224
}
225+
226+
/**
227+
* A function that looks like a Hapi route handler.
228+
*
229+
* For example, this could be the function `function(request, h){...}`.
230+
*/
231+
class RouteHandlerCandidate extends HTTP::RouteHandlerCandidate {
232+
233+
RouteHandlerCandidate() {
234+
exists (string request, string responseToolkit |
235+
(request = "request" or request = "req") and
236+
responseToolkit = "h" and
237+
// heuristic: parameter names match the Hapi documentation
238+
astNode.getNumParameter() = 2 and
239+
astNode.getParameter(0).getName() = request and
240+
astNode.getParameter(1).getName() = responseToolkit |
241+
not (
242+
// heuristic: is not invoked (Hapi invokes this at a call site we cannot reason precisely about)
243+
exists(DataFlow::InvokeNode cs | cs.getACallee() = astNode)
244+
)
245+
)
246+
}
247+
}
248+
249+
/**
250+
* Tracking for `RouteHandlerCandidate`.
251+
*/
252+
private class TrackedRouteHandlerCandidate extends DataFlow::TrackedNode {
253+
254+
TrackedRouteHandlerCandidate() {
255+
this instanceof RouteHandlerCandidate
256+
}
257+
258+
}
259+
260+
/**
261+
* A function that looks like a Hapi route handler and flows to a route setup.
262+
*/
263+
private class TrackedRouteHandlerCandidateWithSetup extends RouteHandler, HTTP::Servers::StandardRouteHandler, DataFlow::ValueNode {
264+
265+
override Function astNode;
266+
267+
TrackedRouteHandlerCandidateWithSetup() {
268+
exists(TrackedRouteHandlerCandidate tracked |
269+
tracked.flowsTo(any(RouteSetup s).getRouteHandlerExpr().flow()) and
270+
this = tracked
271+
)
272+
}
273+
274+
}
275+
219276
}

javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -597,9 +597,7 @@ module NodeJSLib {
597597
*
598598
* For example, this could be the function `function(req, res){...}`.
599599
*/
600-
class RouteHandlerCandidate extends HTTP::RouteHandlerCandidate, DataFlow::FunctionNode {
601-
602-
override Function astNode;
600+
class RouteHandlerCandidate extends HTTP::RouteHandlerCandidate {
603601

604602
RouteHandlerCandidate() {
605603
exists (string request, string response |

javascript/ql/src/semmle/javascript/heuristics/AdditionalRouteHandlers.qll

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import javascript
88
private import semmle.javascript.frameworks.ConnectExpressShared
99

1010
/**
11-
* Adds `NodeJSLib::RouteHandlerCandidate` to the extent of `NodeJSLib::RouteHandler`.
11+
* Add `NodeJSLib::RouteHandlerCandidate` to the extent of `NodeJSLib::RouteHandler`.
1212
*/
1313
private class PromotedNodeJSLibCandidate extends NodeJSLib::RouteHandler, HTTP::Servers::StandardRouteHandler {
1414

@@ -19,7 +19,18 @@ private class PromotedNodeJSLibCandidate extends NodeJSLib::RouteHandler, HTTP::
1919
}
2020

2121
/**
22-
* Adds `ConnectExpressShared::RouteHandlerCandidate` to the extent of `Express::RouteHandler`.
22+
* Add `Hapi::RouteHandlerCandidate` to the extent of `Hapi::RouteHandler`.
23+
*/
24+
private class PromotedHapiCandidate extends Hapi::RouteHandler, HTTP::Servers::StandardRouteHandler {
25+
26+
PromotedHapiCandidate() {
27+
this instanceof Hapi::RouteHandlerCandidate
28+
}
29+
30+
}
31+
32+
/**
33+
* Add `ConnectExpressShared::RouteHandlerCandidate` to the extent of `Express::RouteHandler`.
2334
*/
2435
private class PromotedExpressCandidate extends Express::RouteHandler, HTTP::Servers::StandardRouteHandler {
2536

@@ -34,7 +45,7 @@ private class PromotedExpressCandidate extends Express::RouteHandler, HTTP::Serv
3445
}
3546

3647
/**
37-
* Adds `ConnectExpressShared::RouteHandlerCandidate` to the extent of `Connect::RouteHandler`.
48+
* Add `ConnectExpressShared::RouteHandlerCandidate` to the extent of `Connect::RouteHandler`.
3849
*/
3950
private class PromotedConnectCandidate extends Connect::RouteHandler, HTTP::Servers::StandardRouteHandler {
4051

javascript/ql/test/library-tests/frameworks/HTTP-heuristics/RouteHandlerCandidate.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
| src/exported-middleware-attacher.js:2:13:2:32 | function(req, res){} |
1616
| src/handler-in-property.js:5:14:5:33 | function(req, res){} |
1717
| src/handler-in-property.js:12:18:12:37 | function(req, res){} |
18+
| src/hapi.js:1:1:1:30 | functio ... t, h){} |
1819
| src/iterated-handlers.js:4:2:4:22 | functio ... res){} |
1920
| src/middleware-attacher-getter.js:4:17:4:36 | function(req, res){} |
2021
| src/middleware-attacher-getter.js:19:19:19:38 | function(req, res){} |

javascript/ql/test/library-tests/frameworks/HTTP-heuristics/UnpromotedRouteHandlerCandidate.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
| 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`. |
22
| 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`. |
3+
| 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`. |
34
| 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`. |
45
| 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`. |
56
| src/route-objects.js:7:19:7:38 | function(req, res){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
function handler(request, h){}

javascript/ql/test/library-tests/frameworks/hapi/RouteHandler.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
| src/hapi.js:13:14:15:5 | functio ... n\\n } | src/hapi.js:4:15:4:31 | new Hapi.Server() |
33
| src/hapi.js:17:30:18:1 | functio ... ndler\\n} | src/hapi.js:4:15:4:31 | new Hapi.Server() |
44
| src/hapi.js:20:1:27:1 | functio ... oken;\\n} | src/hapi.js:4:15:4:31 | new Hapi.Server() |
5+
| src/hapi.js:34:12:34:30 | function (req, h){} | src/hapi.js:4:15:4:31 | new Hapi.Server() |

javascript/ql/test/library-tests/frameworks/hapi/RouteSetup.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
| src/hapi.js:12:1:15:7 | server2 ... }}) |
33
| src/hapi.js:17:1:18:2 | server2 ... dler\\n}) |
44
| src/hapi.js:29:1:29:20 | server2.route(route) |
5+
| src/hapi.js:36:1:36:38 | server2 ... ler()}) |

0 commit comments

Comments
 (0)