Skip to content

Commit 1bd12e6

Browse files
authored
Merge pull request #5199 from asgerf/js/vue-router
Approved by erik-krogh
2 parents 505d04b + 55a1ab5 commit 1bd12e6

File tree

8 files changed

+254
-42
lines changed

8 files changed

+254
-42
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
lgtm,codescanning
2+
* Support for Vue has improved. Taint sources from [vue-router](https://npmjs.com/package/vue-router)
3+
route parameters are now recognized.

javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,7 @@ module DataFlow {
12631263
/**
12641264
* Gets the data flow node corresponding to `e`.
12651265
*/
1266+
pragma[inline]
12661267
ExprNode exprNode(Expr e) { result = valueNode(e) }
12671268

12681269
/** Gets the data flow node corresponding to `ssa`. */

javascript/ql/src/semmle/javascript/dataflow/Nodes.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ private import internal.CallGraphs
1919
*/
2020
class ExprNode extends DataFlow::ValueNode {
2121
override Expr astNode;
22+
23+
pragma[nomagic]
24+
ExprNode() { any() }
2225
}
2326

2427
/**

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

Lines changed: 120 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ module Vue {
3535
result =
3636
[
3737
"beforeCreate", "created", "beforeMount", "mounted", "beforeUpdate", "updated", "activated",
38-
"deactivated", "beforeDestroy", "destroyed", "errorCaptured"
38+
"deactivated", "beforeDestroy", "destroyed", "errorCaptured", "beforeRouteEnter",
39+
"beforeRouteUpdate", "beforeRouteLeave"
3940
]
4041
}
4142

@@ -162,6 +163,13 @@ module Vue {
162163
result = getAsClassComponent().getDecoratorOption(name)
163164
}
164165

166+
/**
167+
* Gets a source node flowing into the option `name` of this instance, including those from
168+
* extended objects and mixins.
169+
*/
170+
pragma[nomagic]
171+
DataFlow::SourceNode getOptionSource(string name) { result = getOption(name).getALocalSource() }
172+
165173
/**
166174
* Gets the template element used by this instance, if any.
167175
*/
@@ -189,36 +197,54 @@ module Vue {
189197
/**
190198
* Gets the node for the `template` option of this instance.
191199
*/
192-
DataFlow::Node getTemplate() { result = getOption("template") }
200+
pragma[nomagic]
201+
DataFlow::SourceNode getTemplate() { result = getOptionSource("template") }
193202

194203
/**
195204
* Gets the node for the `render` option of this instance.
196205
*/
197-
DataFlow::Node getRender() {
198-
result = getOption("render")
206+
pragma[nomagic]
207+
DataFlow::SourceNode getRender() {
208+
result = getOptionSource("render")
199209
or
200210
result = getAsClassComponent().getInstanceMethod("render")
201211
}
202212

203213
/**
204214
* Gets the node for the `methods` option of this instance.
205215
*/
206-
DataFlow::Node getMethods() { result = getOption("methods") }
216+
pragma[nomagic]
217+
DataFlow::SourceNode getMethods() { result = getOptionSource("methods") }
207218

208219
/**
209220
* Gets the node for the `computed` option of this instance.
210221
*/
211-
DataFlow::Node getComputed() { result = getOption("computed") }
222+
pragma[nomagic]
223+
DataFlow::SourceNode getComputed() { result = getOptionSource("computed") }
212224

213225
/**
214-
* Gets a node for a member of the `methods` option of this instance.
226+
* Gets the node for the `watch` option of this instance.
227+
*/
228+
pragma[nomagic]
229+
DataFlow::SourceNode getWatch() { result = getOptionSource("watch") }
230+
231+
/**
232+
* Gets the function responding to changes to the given `propName`.
215233
*/
216-
pragma[noinline]
217-
private DataFlow::Node getAMethod() {
218-
exists(DataFlow::ObjectLiteralNode methods |
219-
methods.flowsTo(getMethods()) and
220-
result = methods.getAPropertyWrite().getRhs()
234+
DataFlow::FunctionNode getWatchHandler(string propName) {
235+
exists(DataFlow::SourceNode watcher | watcher = getWatch().getAPropertySource(propName) |
236+
result = watcher
237+
or
238+
result = watcher.getAPropertySource("handler")
221239
)
240+
}
241+
242+
/**
243+
* Gets a node for a member of the `methods` option of this instance.
244+
*/
245+
pragma[nomagic]
246+
private DataFlow::SourceNode getAMethod() {
247+
result = getMethods().getAPropertySource()
222248
or
223249
result = getAsClassComponent().getAnInstanceMethod() and
224250
not result = getAsClassComponent().getInstanceMethod([lifecycleHookName(), "render", "data"])
@@ -227,19 +253,11 @@ module Vue {
227253
/**
228254
* Gets a node for a member of the `computed` option of this instance that matches `kind`.
229255
*/
230-
pragma[noinline]
231-
private DataFlow::Node getAnAccessor(DataFlow::MemberKind kind) {
232-
exists(DataFlow::ObjectLiteralNode computedObj, DataFlow::Node accessorObjOrGetter |
233-
computedObj.flowsTo(getComputed()) and
234-
computedObj.getAPropertyWrite().getRhs() = accessorObjOrGetter
235-
|
236-
result = accessorObjOrGetter and kind = DataFlow::MemberKind::getter()
237-
or
238-
exists(DataFlow::ObjectLiteralNode accessorObj |
239-
accessorObj.flowsTo(accessorObjOrGetter) and
240-
result = accessorObj.getAPropertyWrite(memberKindVerb(kind)).getRhs()
241-
)
242-
)
256+
pragma[nomagic]
257+
private DataFlow::SourceNode getAnAccessor(DataFlow::MemberKind kind) {
258+
result = getComputed().getAPropertySource() and kind = DataFlow::MemberKind::getter()
259+
or
260+
result = getComputed().getAPropertySource().getAPropertySource(memberKindVerb(kind))
243261
or
244262
result = getAsClassComponent().getAnInstanceMember(kind) and
245263
kind.isAccessor()
@@ -248,18 +266,10 @@ module Vue {
248266
/**
249267
* Gets a node for a member `name` of the `computed` option of this instance that matches `kind`.
250268
*/
251-
private DataFlow::Node getAccessor(string name, DataFlow::MemberKind kind) {
252-
exists(DataFlow::ObjectLiteralNode computedObj, DataFlow::SourceNode accessorObjOrGetter |
253-
computedObj.flowsTo(getComputed()) and
254-
accessorObjOrGetter.flowsTo(computedObj.getAPropertyWrite(name).getRhs())
255-
|
256-
result = accessorObjOrGetter and kind = DataFlow::MemberKind::getter()
257-
or
258-
exists(DataFlow::ObjectLiteralNode accessorObj |
259-
accessorObj.flowsTo(accessorObjOrGetter) and
260-
result = accessorObj.getAPropertyWrite(memberKindVerb(kind)).getRhs()
261-
)
262-
)
269+
private DataFlow::SourceNode getAccessor(string name, DataFlow::MemberKind kind) {
270+
result = getComputed().getAPropertySource(name) and kind = DataFlow::MemberKind::getter()
271+
or
272+
result = getComputed().getAPropertySource(name).getAPropertySource(memberKindVerb(kind))
263273
or
264274
result = getAsClassComponent().getInstanceMember(name, kind) and
265275
kind.isAccessor()
@@ -268,11 +278,11 @@ module Vue {
268278
/**
269279
* Gets the node for the life cycle hook of the `hookName` option of this instance.
270280
*/
271-
pragma[noinline]
272-
private DataFlow::Node getALifecycleHook(string hookName) {
281+
pragma[nomagic]
282+
DataFlow::SourceNode getALifecycleHook(string hookName) {
273283
hookName = lifecycleHookName() and
274284
(
275-
result = getOption(hookName)
285+
result = getOptionSource(hookName)
276286
or
277287
result = getAsClassComponent().getInstanceMethod(hookName)
278288
)
@@ -281,16 +291,21 @@ module Vue {
281291
/**
282292
* Gets a node for a function that will be invoked with `this` bound to this instance.
283293
*/
284-
DataFlow::Node getABoundFunction() {
294+
DataFlow::FunctionNode getABoundFunction() {
285295
result = getAMethod()
286296
or
287297
result = getAnAccessor(_)
288298
or
289299
result = getALifecycleHook(_)
300+
or
301+
result = getOptionSource(_)
302+
or
303+
result = getOptionSource(_).getAPropertySource()
290304
}
291305

292306
/**
293-
* Gets a node for the value for property `name` of this instance.
307+
* Gets the data flow node that flows into the property `name` of this instance, or is
308+
* returned form a getter defining that property.
294309
*/
295310
DataFlow::Node getAPropertyValue(string name) {
296311
exists(DataFlow::SourceNode obj | obj.getAPropertyWrite(name).getRhs() = result |
@@ -552,4 +567,67 @@ module Vue {
552567
HTML::Element getElement() { result = elem }
553568
}
554569
}
570+
571+
/** An API node referring to a `RouteConfig` being passed to `vue-router`. */
572+
private API::Node routeConfig() {
573+
result = API::moduleImport("vue-router").getParameter(0).getMember("routes").getAMember()
574+
or
575+
result = routeConfig().getMember("children").getAMember()
576+
}
577+
578+
/** Gets a data flow node that refers to a `Route` object from `vue-router`. */
579+
private DataFlow::SourceNode routeObject(DataFlow::TypeTracker t) {
580+
t.start() and
581+
(
582+
exists(API::Node router | router = API::moduleImport("vue-router") |
583+
result = router.getInstance().getMember("currentRoute").getAnImmediateUse()
584+
or
585+
result =
586+
router
587+
.getInstance()
588+
.getMember(["beforeEach", "beforeResolve", "afterEach"])
589+
.getParameter(0)
590+
.getParameter([0, 1])
591+
.getAnImmediateUse()
592+
or
593+
result =
594+
router
595+
.getParameter(0)
596+
.getMember("scrollBehavior")
597+
.getParameter([0, 1])
598+
.getAnImmediateUse()
599+
)
600+
or
601+
result = routeConfig().getMember("beforeEnter").getParameter([0, 1]).getAnImmediateUse()
602+
or
603+
exists(Instance i |
604+
result = i.getABoundFunction().getAFunctionValue().getReceiver().getAPropertyRead("$route")
605+
or
606+
result =
607+
i.getALifecycleHook(["beforeRouteEnter", "beforeRouteUpdate", "beforeRouteLeave"])
608+
.getAFunctionValue()
609+
.getParameter([0, 1])
610+
or
611+
result = i.getWatchHandler("$route").getParameter([0, 1])
612+
)
613+
)
614+
or
615+
exists(DataFlow::TypeTracker t2 | result = routeObject(t2).track(t2, t))
616+
}
617+
618+
/** Gets a data flow node that refers to a `Route` object from `vue-router`. */
619+
DataFlow::SourceNode routeObject() { result = routeObject(DataFlow::TypeTracker::end()) }
620+
621+
private class VueRouterFlowSource extends RemoteFlowSource {
622+
VueRouterFlowSource() {
623+
this = routeObject().getAPropertyRead(["params", "query", "hash", "path", "fullPath"])
624+
or
625+
exists(Instance i, string prop |
626+
this = i.getWatchHandler(prop).getParameter([0, 1]) and
627+
prop.regexpMatch("\\$route\\.(params|query|hash|path|fullPath)\\b.*")
628+
)
629+
}
630+
631+
override string getSourceType() { result = "Vue route parameter" }
632+
}
555633
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<template>
2+
<p v-html="dataA"/>
3+
</template>
4+
<script>
5+
import Vue from 'vue'
6+
import Component from 'vue-class-component'
7+
import { router } from './router';
8+
9+
@Component({
10+
watch: {
11+
'$route.params.id': {
12+
deep: true,
13+
handler(newId, oldId) { }
14+
},
15+
$route(to, from) { }
16+
}
17+
})
18+
export default class MyComponent extends Vue {
19+
message = 'Hello!'
20+
21+
sources() {
22+
this.$route.params.x;
23+
this.$route.query.x;
24+
this.$route.hash.x;
25+
this.$route.path;
26+
this.$route.fullPath;
27+
router.currentRoute.query.x;
28+
}
29+
30+
get dataA() {
31+
return this.$route.query.foo; // NOT OK
32+
}
33+
34+
beforeRouteEnter(to, from, next) {
35+
to.query.x;
36+
from.query.x;
37+
}
38+
39+
beforeRouteUpdate(to, from, next) {
40+
to.query.x;
41+
from.query.x;
42+
}
43+
44+
beforeRouteLeave(to, from, next) {
45+
to.query.x;
46+
from.query.x;
47+
}
48+
}
49+
</script>
50+
<style>
51+
</style>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Router from 'vue-router';
2+
3+
export const router = new Router({
4+
routes: [
5+
{
6+
path: '/foo',
7+
beforeEnter: (to, from, next) => {
8+
to.query.x;
9+
from.query.x;
10+
},
11+
children: [
12+
{
13+
path: '/bar',
14+
beforeEnter: (to, from, next) => {
15+
to.query.x;
16+
from.query.x;
17+
}
18+
}
19+
]
20+
}
21+
],
22+
scrollBehavior(to, from, savedPosition) {
23+
to.query.x;
24+
from.query.x;
25+
}
26+
});
27+
28+
router.beforeEach((to, from, next) => {
29+
to.query.x;
30+
from.query.x;
31+
});
32+
33+
router.afterEach((to, from) => {
34+
to.query.x;
35+
from.query.x;
36+
});
37+

0 commit comments

Comments
 (0)