Skip to content

Commit 801ce89

Browse files
authored
Merge pull request github#3099 from esbena/js/introduce-poi-utility
Approved by erik-krogh
2 parents 312e622 + a66b4b5 commit 801ce89

18 files changed

+563
-79
lines changed
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
/**
2+
* Provides classes and predicates for discovering points of interest
3+
* in an unknown code base.
4+
*
5+
* To use this module, subclass the
6+
* `ActivePoI` class, override *one* of its `is` predicates, and use
7+
* `alertQuery` as a `@kind problem` query . This will present
8+
* the desired points of interest as alerts that are easily browsable
9+
* in a codeql IDE. By itself, this is no different from an ordinary
10+
* query, but the strength of this module lies in its extensibility
11+
* and standard library:
12+
*
13+
* - points of interest can be added, removed and mixed seamlessly
14+
* - this module comes with a collection of standard points of interest (see `StandardPoIs`)
15+
*
16+
* A global configuration for the points of interest (see
17+
* `PoIConfiguration`) can be used to easily manage multiple points of
18+
* interests, and to restrict the points of interest to specific
19+
* corners of the code base.
20+
*
21+
* Below is an example use of this module that will produce an alert
22+
* for each route handler and route handler setup in a file named
23+
* "server-core.js". The route setup alerts will contain a link to its
24+
* associated route handler.
25+
*
26+
* ```
27+
* /**
28+
* * @kind problem
29+
* *\/
30+
*
31+
* import PoI
32+
*
33+
* class Configuration extends PoIConfiguration {
34+
* Configuration() { this = "Configuration" }
35+
*
36+
* override predicate shown(DataFlow::Node n) { n.getFile().getBaseName() = "server-core.js" }
37+
* }
38+
*
39+
* class RouteHandlerPoI extends ActivePoI {
40+
* RouteHandlerPoI() { this = "RouteHandlerPoI" }
41+
* override predicate is(DataFlow::Node l0) { l0 instanceof Express::RouteHandler }
42+
* }
43+
*
44+
* class RouteSetupAndRouteHandlerPoI extends ActivePoI {
45+
* RouteSetupAndRouteHandlerPoI() { this = "RouteSetupAndRouteHandlerPoI" }
46+
*
47+
* override predicate is(DataFlow::Node l0, DataFlow::Node l1, string t1) {
48+
* l0.asExpr().(Express::RouteSetup).getARouteHandler() = l1 and t1 = "routehandler"
49+
* }
50+
* }
51+
*
52+
* query predicate problems = alertQuery/6;
53+
* ```
54+
*/
55+
56+
import javascript
57+
private import DataFlow
58+
private import filters.ClassifyFiles
59+
private import semmle.javascript.RestrictedLocations
60+
61+
/**
62+
* Provides often used points of interest.
63+
*
64+
* Note that these points of interest should not extend
65+
* `ActivePoI`, and that they can be enabled on
66+
* demand like this:
67+
*
68+
* ```
69+
* class MyPoI extends ServerRelatedPoI, ActivePoI {}
70+
* ```
71+
*/
72+
private module StandardPoIs {
73+
/**
74+
* An unpromoted route setup candidate.
75+
*/
76+
class UnpromotedRouteSetupPoI extends PoI {
77+
UnpromotedRouteSetupPoI() { this = "UnpromotedRouteSetupPoI" }
78+
79+
override predicate is(Node l0) {
80+
l0 instanceof HTTP::RouteSetupCandidate and not l0.asExpr() instanceof HTTP::RouteSetup
81+
}
82+
}
83+
84+
/**
85+
* An unpromoted route handler candidate.
86+
*/
87+
class UnpromotedRouteHandlerPoI extends PoI {
88+
UnpromotedRouteHandlerPoI() { this = "UnpromotedRouteHandlerPoI" }
89+
90+
override predicate is(Node l0) {
91+
l0 instanceof HTTP::RouteHandlerCandidate and not l0 instanceof HTTP::RouteHandler
92+
}
93+
}
94+
95+
/**
96+
* An unpromoted route handler candidate, with explanatory data flow information.
97+
*/
98+
class UnpromotedRouteHandlerWithFlowPoI extends PoI {
99+
UnpromotedRouteHandlerWithFlowPoI() { this = "UnpromotedRouteHandlerWithFlowPoI" }
100+
101+
private DataFlow::SourceNode track(HTTP::RouteHandlerCandidate cand, DataFlow::TypeTracker t) {
102+
t.start() and
103+
result = cand
104+
or
105+
exists(DataFlow::TypeTracker t2 | result = track(cand, t2).track(t2, t))
106+
}
107+
108+
override predicate is(Node l0, Node l1, string t1) {
109+
l0 instanceof HTTP::RouteHandlerCandidate and
110+
not l0 instanceof HTTP::RouteHandler and
111+
l1 = track(l0, TypeTracker::end()) and
112+
(if l1 = l0 then t1 = "ends here" else t1 = "starts/ends here")
113+
}
114+
}
115+
116+
/**
117+
* A callee that is unknown.
118+
*/
119+
class UnknownCalleePoI extends PoI {
120+
UnknownCalleePoI() { this = "UnknownCalleePoI" }
121+
122+
override predicate is(Node l0) {
123+
exists(InvokeNode invk | l0 = invk.getCalleeNode() and not exists(invk.getACallee()))
124+
}
125+
}
126+
127+
/**
128+
* A source of remote flow.
129+
*/
130+
class RemoteFlowSourcePoI extends PoI {
131+
RemoteFlowSourcePoI() { this = "RemoteFlowSourcePoI" }
132+
133+
override predicate is(Node l0) { l0 instanceof RemoteFlowSource }
134+
}
135+
136+
/**
137+
* A "source" for any active configuration.
138+
*/
139+
class SourcePoI extends PoI {
140+
SourcePoI() { this = "SourcePoI" }
141+
142+
override predicate is(Node l0) {
143+
exists(Configuration cfg | cfg.isSource(l0) or cfg.isSource(l0, _))
144+
}
145+
}
146+
147+
/**
148+
* A "sink" for any active configuration.
149+
*/
150+
class SinkPoI extends PoI {
151+
SinkPoI() { this = "SinkPoI" }
152+
153+
override predicate is(Node l0) {
154+
exists(Configuration cfg | cfg.isSink(l0) or cfg.isSink(l0, _))
155+
}
156+
}
157+
158+
/**
159+
* A "barrier" for any active configuration.
160+
*/
161+
class BarrierPoI extends PoI {
162+
BarrierPoI() { this = "BarrierPoI" }
163+
164+
override predicate is(Node l0) {
165+
exists(Configuration cfg |
166+
cfg.isBarrier(l0) or
167+
cfg.isBarrierEdge(l0, _) or
168+
cfg.isBarrierEdge(l0, _, _) or
169+
cfg.isLabeledBarrier(l0, _)
170+
)
171+
}
172+
}
173+
174+
/**
175+
* Provides groups of often used points of interest.
176+
*/
177+
module StandardPoIGroups {
178+
/**
179+
* A server-related point of interest.
180+
*/
181+
class ServerRelatedPoI extends PoI {
182+
ServerRelatedPoI() {
183+
this instanceof UnpromotedRouteSetupPoI or
184+
this instanceof UnpromotedRouteHandlerPoI or
185+
this instanceof UnpromotedRouteHandlerWithFlowPoI
186+
}
187+
}
188+
189+
/**
190+
* A configuration-related point of interest.
191+
*/
192+
class DataFlowConfigurationPoI extends PoI {
193+
DataFlowConfigurationPoI() {
194+
this instanceof SourcePoI or
195+
this instanceof SinkPoI
196+
}
197+
}
198+
}
199+
200+
import StandardPoIGroups
201+
}
202+
203+
import StandardPoIs
204+
205+
/**
206+
* A tagging interface for a custom point of interest that should be
207+
* enabled in the absence of an explicit
208+
* `PoIConfiguration::enabled/1`.
209+
*/
210+
abstract class ActivePoI extends PoI {
211+
bindingset[this]
212+
ActivePoI() { any() }
213+
}
214+
215+
private module PoIConfigDefaults {
216+
predicate enabled(PoI poi) { poi instanceof ActivePoI }
217+
218+
predicate shown(Node n) { not classify(n.getFile(), _) }
219+
}
220+
221+
/**
222+
* A configuration for the points of interest to display.
223+
*/
224+
abstract class PoIConfiguration extends string {
225+
bindingset[this]
226+
PoIConfiguration() { any() }
227+
228+
/**
229+
* Holds if the points of interest from `poi` should be shown.
230+
*/
231+
predicate enabled(PoI poi) { PoIConfigDefaults::enabled(poi) }
232+
233+
/**
234+
* Holds if the points of interest `n` should be shown.
235+
*/
236+
predicate shown(Node n) { PoIConfigDefaults::shown(n) }
237+
}
238+
239+
/**
240+
* A class of points of interest.
241+
*
242+
* Note that only one of the `is/1`, `is/3`, `is/5` methods should
243+
* be overridden, as two overrides will degrade the alert UI
244+
* slightly.
245+
*/
246+
abstract class PoI extends string {
247+
bindingset[this]
248+
PoI() { any() }
249+
250+
/**
251+
* Holds if `l0` is a point of interest.
252+
*/
253+
predicate is(Node l0) { none() }
254+
255+
/**
256+
* Holds if `l0` is a point of interest, with `l1` as an auxiliary location described by `t1`.
257+
*/
258+
predicate is(Node l0, Node l1, string t1) { none() }
259+
260+
/**
261+
* Holds if `l0` is a point of interest, with `l1` and `l2` as auxiliary locations described by `t1` and `t2`.
262+
*/
263+
predicate is(Node l0, Node l1, string t1, Node l2, string t2) { none() }
264+
265+
/**
266+
* Gets the message format for the point of interest.
267+
*/
268+
string getFormat() {
269+
is(_) and result = ""
270+
or
271+
is(_, _, _) and result = "$@"
272+
or
273+
is(_, _, _, _, _) and result = "$@ $@"
274+
}
275+
}
276+
277+
/**
278+
* An alert query for a point of interest.
279+
*
280+
* Should be used as:
281+
*
282+
* ```
283+
* query predicate problems = alertQuery/6;
284+
* ```
285+
*
286+
* Or alternatively:
287+
*
288+
* ```
289+
* from Locatable l1line, string msg, Node l2, string s2, Node l3, string s3
290+
* where alertQuery(l1line, msg, l2, s2, l3, s3)
291+
* select l1line, msg, l2, s2, l3, s3
292+
* ```
293+
*
294+
* Note that some points of interest do not have auxiliary
295+
* locations, so `l2`,`l3`, `s2`, `s3` may have placeholder values.
296+
*/
297+
predicate alertQuery(Locatable l1line, string msg, Node l2, string s2, Node l3, string s3) {
298+
exists(PoI poi, Node l1, string m |
299+
l1.getAstNode().(FirstLineOf) = l1line and
300+
(
301+
not exists(PoIConfiguration cfg) and
302+
PoIConfigDefaults::enabled(poi) and
303+
PoIConfigDefaults::shown(l1)
304+
or
305+
exists(PoIConfiguration cfg |
306+
cfg.enabled(poi) and
307+
cfg.shown(l1)
308+
)
309+
) and
310+
m = poi.getFormat() and
311+
if m = "" then msg = poi else msg = poi + ": " + m
312+
|
313+
poi.is(l1) and
314+
l1 = l2 and
315+
s2 = "irrelevant" and
316+
l1 = l3 and
317+
s3 = "irrelevant"
318+
or
319+
poi.is(l1, l2, s2) and
320+
l1 = l3 and
321+
s3 = "irrelevant"
322+
or
323+
poi.is(l1, l2, s2, l3, s3)
324+
)
325+
}

0 commit comments

Comments
 (0)