mirror of
https://github.com/github/codeql.git
synced 2026-04-30 19:26:02 +02:00
Merge pull request #828 from esben-semmle/js/vue-support-1
JS: basic Vue support
This commit is contained in:
49
javascript/ql/src/Vue/ArrowMethodOnVueInstance.qhelp
Normal file
49
javascript/ql/src/Vue/ArrowMethodOnVueInstance.qhelp
Normal file
@@ -0,0 +1,49 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
|
||||
The Vue framework invokes the methods of a Vue instance with
|
||||
the instance as the receiver. It is however impossible to perform
|
||||
this binding of instance and receiver for arrow functions, so the
|
||||
<code>this</code> variable in an arrow function on a Vue instance may
|
||||
not have the value that the programmer expects.
|
||||
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Ensure that the methods on a Vue instance can have their receiver bound to the instance.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
|
||||
<p>
|
||||
|
||||
The following example shows two similar Vue instances, the only
|
||||
difference is how the <code>created</code> life cycle hook
|
||||
callback is defined.
|
||||
|
||||
The first Vue instance uses an arrow function as the callback.
|
||||
This means that the <code>this</code> variable will have the global
|
||||
object as its value, causing <code>this.myProperty</code> to evaluate
|
||||
to <code>undefined</code>, which may not be intended.
|
||||
|
||||
Instead, the second Vue instance uses an ordinary function as the callback,
|
||||
causing <code>this.myProperty</code> to evaluate to <code>42</code>.
|
||||
|
||||
</p>
|
||||
|
||||
<sample src="examples/ArrowMethodOnVueInstance.js"/>
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>Vue documentation: <a href="https://vuejs.org/v2/guide/instance.html">The Vue Instance</a></li>
|
||||
</references>
|
||||
</qhelp>
|
||||
19
javascript/ql/src/Vue/ArrowMethodOnVueInstance.ql
Normal file
19
javascript/ql/src/Vue/ArrowMethodOnVueInstance.ql
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @name Arrow method on Vue instance
|
||||
* @description An arrow method on a Vue instance doesn't have its `this` variable bound to the Vue instance.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @id js/vue/arrow-method-on-vue-instance
|
||||
* @tags reliability
|
||||
* frameworks/vue
|
||||
* @precision high
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
from Vue::Instance instance, DataFlow::Node def, DataFlow::FunctionNode arrow, ThisExpr dis
|
||||
where instance.getABoundFunction() = def and
|
||||
arrow.flowsTo(def) and
|
||||
arrow.asExpr() instanceof ArrowFunctionExpr and
|
||||
arrow.asExpr() = dis.getEnclosingFunction()
|
||||
select def, "The $@ of this $@ it will not be bound to the Vue instance.", dis, "`this` variable", arrow, "arrow function"
|
||||
19
javascript/ql/src/Vue/examples/ArrowMethodOnVueInstance.js
Normal file
19
javascript/ql/src/Vue/examples/ArrowMethodOnVueInstance.js
Normal file
@@ -0,0 +1,19 @@
|
||||
new Vue({
|
||||
data: {
|
||||
myProperty: 42
|
||||
},
|
||||
created: () => {
|
||||
// BAD: prints: "myProperty is: undefined"
|
||||
console.log('myProperty is: ' + this.myProperty);
|
||||
}
|
||||
});
|
||||
|
||||
new Vue({
|
||||
data: {
|
||||
myProperty: 42
|
||||
},
|
||||
created: function () {
|
||||
// GOOD: prints: "myProperty is: 1"
|
||||
console.log('myProperty is: ' + this.myProperty);
|
||||
}
|
||||
});
|
||||
@@ -78,6 +78,7 @@ import semmle.javascript.frameworks.Request
|
||||
import semmle.javascript.frameworks.SQL
|
||||
import semmle.javascript.frameworks.StringFormatters
|
||||
import semmle.javascript.frameworks.UriLibraries
|
||||
import semmle.javascript.frameworks.Vue
|
||||
import semmle.javascript.frameworks.XmlParsers
|
||||
import semmle.javascript.frameworks.xUnit
|
||||
import semmle.javascript.linters.ESLint
|
||||
|
||||
@@ -159,6 +159,40 @@ module HTML {
|
||||
* if it can be determined.
|
||||
*/
|
||||
Script resolveSource() { result.getFile().getAbsolutePath() = resolveSourcePath() }
|
||||
|
||||
/**
|
||||
* Gets the inline script of this script element, if any.
|
||||
*/
|
||||
private InlineScript getInlineScript() {
|
||||
exists(string f, Location l1, int sl1, int sc1, int el1, int ec1, Location l2, int sl2, int sc2, int el2, int ec2 |
|
||||
l1 = getLocation() and
|
||||
l2 = result.getLocation() and
|
||||
l1.hasLocationInfo(f, sl1, sc1, el1, ec1) and
|
||||
l2.hasLocationInfo(f, sl2, sc2, el2, ec2)
|
||||
|
|
||||
(
|
||||
sl1 = sl2 and sc1 < sc2
|
||||
or
|
||||
sl1 < sl2
|
||||
) and
|
||||
(
|
||||
el1 = el2 and ec1 > ec2
|
||||
or
|
||||
el1 > el2
|
||||
)
|
||||
) and
|
||||
// the src attribute has precedence
|
||||
not exists(getSourcePath())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the script of this element, if it can be determined.
|
||||
*/
|
||||
Script getScript() {
|
||||
result = getInlineScript() or
|
||||
result = resolveSource()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
369
javascript/ql/src/semmle/javascript/frameworks/Vue.qll
Normal file
369
javascript/ql/src/semmle/javascript/frameworks/Vue.qll
Normal file
@@ -0,0 +1,369 @@
|
||||
/**
|
||||
* Provides classes for working with Vue code.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
module Vue {
|
||||
/**
|
||||
* Gets a reference to the 'Vue' object.
|
||||
*/
|
||||
DataFlow::SourceNode vue() {
|
||||
result = DataFlow::globalVarRef("Vue")
|
||||
or
|
||||
result = DataFlow::moduleImport("vue")
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `vue.extend`.
|
||||
*/
|
||||
private class VueExtend extends DataFlow::CallNode {
|
||||
VueExtend() { this = vue().getAMemberCall("extend") }
|
||||
}
|
||||
|
||||
private newtype TInstance =
|
||||
MkVueInstance(DataFlow::NewNode def) { def = vue().getAnInstantiation() } or
|
||||
MkExtendedVue(VueExtend extend) or
|
||||
MkExtendedInstance(VueExtend extend, DataFlow::NewNode sub) {
|
||||
sub = extend.getAnInstantiation()
|
||||
} or
|
||||
MkComponent(DataFlow::CallNode def) { def = vue().getAMemberCall("component") } or
|
||||
MkSingleFileComponent(VueFile file)
|
||||
|
||||
/**
|
||||
* A Vue instance definition.
|
||||
*
|
||||
* This includes both explicit instantiations of Vue objects, and
|
||||
* implicit instantiations in the form of components or Vue
|
||||
* extensions that have not yet been instantiated to a Vue instance.
|
||||
*
|
||||
* The following instances are recognized:
|
||||
* - `new Vue({...})`
|
||||
* - `Vue.extend({...})`
|
||||
* - `new ExtendedVue({...})`
|
||||
* - `Vue.component("my-component", {...})`
|
||||
* - single file components in .vue files
|
||||
*/
|
||||
abstract class Instance extends TInstance {
|
||||
/** Gets a textual representation of this element. */
|
||||
abstract string toString();
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [LGTM locations](https://lgtm.com/help/ql/locations).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
filepath = "" and
|
||||
startline = 0 and
|
||||
startcolumn = 0 and
|
||||
endline = 0 and
|
||||
endcolumn = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node for option `name` for this instance, this does not include
|
||||
* those from extended objects and mixins.
|
||||
*/
|
||||
abstract DataFlow::Node getOwnOption(string name);
|
||||
|
||||
/**
|
||||
* Gets the node for option `name` for this instance, including those from
|
||||
* extended objects and mixins.
|
||||
*/
|
||||
DataFlow::Node getOption(string name) {
|
||||
result = getOwnOption(name)
|
||||
or
|
||||
exists(DataFlow::SourceNode extendsVal | extendsVal.flowsTo(getOwnOption("extends")) |
|
||||
result = extendsVal.(DataFlow::ObjectLiteralNode).getAPropertyWrite(name).getRhs()
|
||||
or
|
||||
exists(ExtendedVue extend |
|
||||
MkExtendedVue(extendsVal) = extend and
|
||||
result = extend.getOption(name)
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::ArrayCreationNode mixins, DataFlow::ObjectLiteralNode mixin |
|
||||
mixins.flowsTo(getOwnOption("mixins")) and
|
||||
mixin.flowsTo(mixins.getAnElement()) and
|
||||
result = mixin.getAPropertyWrite(name).getRhs()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node for the `data` option object of this instance.
|
||||
*/
|
||||
DataFlow::Node getData() {
|
||||
exists(DataFlow::Node data | data = getOption("data") |
|
||||
result = data
|
||||
or
|
||||
// a constructor variant is available for all instance definitions
|
||||
exists(DataFlow::FunctionNode f |
|
||||
f.flowsTo(data) and
|
||||
result = f.getAReturn()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node for the `template` option of this instance.
|
||||
*/
|
||||
DataFlow::Node getTemplate() { result = getOption("template") }
|
||||
|
||||
/**
|
||||
* Gets the node for the `render` option of this instance.
|
||||
*/
|
||||
DataFlow::Node getRender() { result = getOption("render") }
|
||||
|
||||
/**
|
||||
* Gets the node for the `methods` option of this instance.
|
||||
*/
|
||||
DataFlow::Node getMethods() { result = getOption("methods") }
|
||||
|
||||
/**
|
||||
* Gets the node for the `computed` option of this instance.
|
||||
*/
|
||||
DataFlow::Node getComputed() { result = getOption("computed") }
|
||||
|
||||
/**
|
||||
* Gets a node for a member of the `methods` option of this instance.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private DataFlow::Node getAMethod() {
|
||||
exists(DataFlow::SourceNode methods |
|
||||
methods.flowsTo(getMethods()) and
|
||||
result = methods.getAPropertyWrite().getRhs()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node for a member of the `computed` option of this instance that matches `kind` ("get" or "set").
|
||||
*/
|
||||
pragma[noinline]
|
||||
private DataFlow::Node getAnAccessor(string kind) {
|
||||
exists(DataFlow::SourceNode computedObj, DataFlow::Node accessorObjOrGetter |
|
||||
computedObj.flowsTo(getComputed()) and
|
||||
computedObj.getAPropertyWrite().getRhs() = accessorObjOrGetter |
|
||||
result = accessorObjOrGetter and kind = "get"
|
||||
or
|
||||
exists (DataFlow::SourceNode accessorObj |
|
||||
accessorObj.flowsTo(accessorObjOrGetter) and
|
||||
result = accessorObj.getAPropertyWrite(kind).getRhs()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node for a member `name` of the `computed` option of this instance that matches `kind` ("get" or "set").
|
||||
*/
|
||||
private DataFlow::Node getAccessor(string name, string kind) {
|
||||
exists(DataFlow::SourceNode computedObj, DataFlow::SourceNode accessorObjOrGetter |
|
||||
computedObj.flowsTo(getComputed()) and
|
||||
accessorObjOrGetter.flowsTo(computedObj.getAPropertyWrite(name).getRhs())
|
||||
|
|
||||
result = accessorObjOrGetter and kind = "get" or
|
||||
exists (DataFlow::SourceNode accessorObj |
|
||||
accessorObj.flowsTo(accessorObjOrGetter) and
|
||||
result = accessorObj.getAPropertyWrite(kind).getRhs()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node for the life cycle hook of the `hookName` option of this instance.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private DataFlow::Node getALifecycleHook(string hookName) {
|
||||
(
|
||||
hookName = "beforeCreate" or
|
||||
hookName = "created" or
|
||||
hookName = "beforeMount" or
|
||||
hookName = "mounted" or
|
||||
hookName = "beforeUpdate" or
|
||||
hookName = "updated" or
|
||||
hookName = "activated" or
|
||||
hookName = "deactivated" or
|
||||
hookName = "beforeDestroy" or
|
||||
hookName = "destroyed" or
|
||||
hookName = "errorCaptured"
|
||||
) and
|
||||
result = getOption(hookName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node for a function that will be invoked with `this` bound to this instance.
|
||||
*/
|
||||
DataFlow::Node getABoundFunction() {
|
||||
result = getAMethod()
|
||||
or
|
||||
result = getAnAccessor(_)
|
||||
or
|
||||
result = getALifecycleHook(_)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node for the value for property `name` of this instance.
|
||||
*/
|
||||
DataFlow::Node getAPropertyValue(string name) {
|
||||
exists(DataFlow::SourceNode obj | obj.getAPropertyWrite(name).getRhs() = result |
|
||||
obj.flowsTo(getData())
|
||||
or
|
||||
exists(DataFlow::FunctionNode bound |
|
||||
bound.flowsTo(getABoundFunction()) and
|
||||
not bound.getFunction() instanceof ArrowFunctionExpr and
|
||||
obj = bound.getReceiver()
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::FunctionNode getter |
|
||||
getter.flowsTo(getAccessor(name, "get")) and
|
||||
result = getter.getAReturn()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Vue instance from `new Vue({...})`.
|
||||
*/
|
||||
class VueInstance extends Instance, MkVueInstance {
|
||||
DataFlow::NewNode def;
|
||||
|
||||
VueInstance() { this = MkVueInstance(def) }
|
||||
|
||||
override string toString() { result = def.toString() }
|
||||
|
||||
override predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
def.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
override DataFlow::Node getOwnOption(string name) { result = def.getOptionArgument(0, name) }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An extended Vue from `Vue.extend({...})`.
|
||||
*/
|
||||
class ExtendedVue extends Instance, MkExtendedVue {
|
||||
VueExtend extend;
|
||||
|
||||
ExtendedVue() { this = MkExtendedVue(extend) }
|
||||
|
||||
override string toString() { result = extend.toString() }
|
||||
|
||||
override predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
extend.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
override DataFlow::Node getOwnOption(string name) { result = extend.getOptionArgument(0, name) }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An instance of an extended Vue, for example `instance` of `var Ext = Vue.extend({...}); var instance = new Ext({...})`.
|
||||
*/
|
||||
class ExtendedInstance extends Instance, MkExtendedInstance {
|
||||
VueExtend extend;
|
||||
|
||||
DataFlow::NewNode sub;
|
||||
|
||||
ExtendedInstance() { this = MkExtendedInstance(extend, sub) }
|
||||
|
||||
override string toString() { result = sub.toString() }
|
||||
|
||||
override predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
sub.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
override DataFlow::Node getOwnOption(string name) { result = sub.getOptionArgument(0, name) }
|
||||
|
||||
override DataFlow::Node getOption(string name) {
|
||||
result = Instance.super.getOption(name)
|
||||
or
|
||||
result = MkExtendedVue(extend).(ExtendedVue).getOption(name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A Vue component from `Vue.component("my-component", { ... })`.
|
||||
*/
|
||||
class Component extends Instance, MkComponent {
|
||||
DataFlow::CallNode def;
|
||||
|
||||
Component() { this = MkComponent(def) }
|
||||
|
||||
override string toString() { result = def.toString() }
|
||||
|
||||
override predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
def.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
override DataFlow::Node getOwnOption(string name) { result = def.getOptionArgument(1, name) }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A single file Vue component in a `.vue` file.
|
||||
*/
|
||||
class SingleFileComponent extends Instance, MkSingleFileComponent {
|
||||
VueFile file;
|
||||
|
||||
SingleFileComponent() { this = MkSingleFileComponent(file) }
|
||||
|
||||
override predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
filepath = file.getAbsolutePath() and
|
||||
startline = 0 and
|
||||
startcolumn = 0 and
|
||||
endline = 0 and
|
||||
endcolumn = 0
|
||||
}
|
||||
|
||||
private Module getModule() {
|
||||
exists(HTML::ScriptElement elem | elem.getFile() = file |
|
||||
result.getTopLevel() = elem.getScript()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getOwnOption(string name) {
|
||||
// The options of a single file component are defined by the exported object of the script element.
|
||||
// Our current module model does not support reads on this object very well, so we use custom steps for the common cases for now.
|
||||
exists(Module m, DefiniteAbstractValue abstractOptions |
|
||||
any(AnalyzedPropertyWrite write).writes(abstractOptions, name, result) and
|
||||
m = getModule()
|
||||
|
|
||||
// ES2015 exports
|
||||
exists(ExportDeclaration export, DataFlow::AnalyzedNode exported |
|
||||
export.getEnclosingModule() = m and
|
||||
abstractOptions = exported.getAValue()
|
||||
|
|
||||
exported = export.(BulkReExportDeclaration).getSourceNode("default") or
|
||||
exported.asExpr() = export.(ExportDefaultDeclaration).getOperand()
|
||||
)
|
||||
or
|
||||
// Node.js exports
|
||||
abstractOptions = m.(NodeModule).getAModuleExportsValue()
|
||||
)
|
||||
}
|
||||
|
||||
override string toString() { result = file.toString() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `.vue` file.
|
||||
*/
|
||||
class VueFile extends File { VueFile() { getExtension() = "vue" } }
|
||||
}
|
||||
@@ -188,4 +188,26 @@ module DomBasedXss {
|
||||
|
||||
override string getVulnerabilityKind() { result = "HTML injection" }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A write to the `template` option of a Vue instance, viewed as an XSS sink.
|
||||
*/
|
||||
class VueTemplateSink extends DomBasedXss::Sink {
|
||||
VueTemplateSink() { this = any(Vue::Instance i).getTemplate() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The tag name argument to the `createElement` parameter of the
|
||||
* `render` method of a Vue instance, viewed as an XSS sink.
|
||||
*/
|
||||
class VueCreateElementSink extends DomBasedXss::Sink {
|
||||
VueCreateElementSink() {
|
||||
exists(Vue::Instance i, DataFlow::FunctionNode f |
|
||||
f.flowsTo(i.getRender()) and
|
||||
this = f.getParameter(0).getACall().getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
| tst.html:2:3:2:32 | <script>...</> | tst.js:1:1:2:0 | <toplevel> |
|
||||
| tst.html:5:3:5:38 | <script>...</> | tst.js:1:1:2:0 | <toplevel> |
|
||||
| tst.html:8:3:8:25 | <script>...</> | tst.html:8:11:8:16 | <toplevel> |
|
||||
| tst.html:9:3:11:11 | <script>...</> | tst.html:9:11:11:2 | <toplevel> |
|
||||
@@ -0,0 +1,5 @@
|
||||
import javascript
|
||||
|
||||
|
||||
from HTML::ScriptElement e
|
||||
select e, e.getScript()
|
||||
12
javascript/ql/test/library-tests/HTML/HtmlScript/tst.html
Normal file
12
javascript/ql/test/library-tests/HTML/HtmlScript/tst.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<html>
|
||||
<script src="tst.js"></script>
|
||||
<script src="does-not-exist.js"></script>
|
||||
|
||||
<script src="tst.js">inline</script>
|
||||
<script src="does-not-exist.js">inline</script>
|
||||
|
||||
<script>inline</script>
|
||||
<script>
|
||||
inline
|
||||
</script>
|
||||
</html>
|
||||
1
javascript/ql/test/library-tests/HTML/HtmlScript/tst.js
Normal file
1
javascript/ql/test/library-tests/HTML/HtmlScript/tst.js
Normal file
@@ -0,0 +1 @@
|
||||
not_inline
|
||||
@@ -0,0 +1,17 @@
|
||||
| single-component-file-1.vue:0:0:0:0 | single-component-file-1.vue |
|
||||
| single-file-component-2.vue:0:0:0:0 | single-file-component-2.vue |
|
||||
| single-file-component-3.vue:0:0:0:0 | single-file-component-3.vue |
|
||||
| tst.js:3:1:10:2 | new Vue ... 2\\n\\t}\\n}) |
|
||||
| tst.js:12:1:16:2 | new Vue ... \\t}),\\n}) |
|
||||
| tst.js:18:1:27:2 | Vue.com ... }\\n\\t}\\n}) |
|
||||
| tst.js:29:1:35:2 | new Vue ... }\\n\\t}\\n}) |
|
||||
| tst.js:37:1:39:2 | new Vue ... nger\\n}) |
|
||||
| tst.js:41:17:47:2 | Vue.ext ... \\n }\\n}) |
|
||||
| tst.js:48:1:50:2 | new Ext ... 42 }\\n}) |
|
||||
| tst.js:51:17:57:2 | Vue.ext ... \\n }\\n}) |
|
||||
| tst.js:58:1:61:2 | new Vue ... 42 }\\n}) |
|
||||
| tst.js:63:1:66:2 | new Vue ... 42 }\\n}) |
|
||||
| tst.js:72:1:75:2 | new Vue ... 42 }\\n}) |
|
||||
| tst.js:77:20:83:2 | Vue.ext ... \\n }\\n}) |
|
||||
| tst.js:85:1:87:2 | new Vue ... e; }\\n}) |
|
||||
| tst.js:94:2:96:3 | new Vue ... f,\\n\\t}) |
|
||||
@@ -0,0 +1,3 @@
|
||||
import javascript
|
||||
|
||||
select any(Vue::Instance i)
|
||||
@@ -0,0 +1,22 @@
|
||||
| single-component-file-1.vue:0:0:0:0 | single-component-file-1.vue | dataA | single-component-file-1.vue:6:40:6:41 | 42 |
|
||||
| single-file-component-3.vue:0:0:0:0 | single-file-component-3.vue | dataA | single-file-component-3-script.js:4:37:4:38 | 42 |
|
||||
| tst.js:3:1:10:2 | new Vue ... 2\\n\\t}\\n}) | dataA | tst.js:8:10:8:11 | 42 |
|
||||
| tst.js:12:1:16:2 | new Vue ... \\t}),\\n}) | dataA | tst.js:14:10:14:11 | 42 |
|
||||
| tst.js:18:1:27:2 | Vue.com ... }\\n\\t}\\n}) | dataA | tst.js:20:10:20:11 | 42 |
|
||||
| tst.js:18:1:27:2 | Vue.com ... }\\n\\t}\\n}) | dataB | tst.js:24:17:24:20 | true |
|
||||
| tst.js:29:1:35:2 | new Vue ... }\\n\\t}\\n}) | x | tst.js:31:12:31:13 | 42 |
|
||||
| tst.js:29:1:35:2 | new Vue ... }\\n\\t}\\n}) | y | tst.js:32:19:32:20 | 42 |
|
||||
| tst.js:29:1:35:2 | new Vue ... }\\n\\t}\\n}) | z2 | tst.js:33:36:33:37 | 42 |
|
||||
| tst.js:41:17:47:2 | Vue.ext ... \\n }\\n}) | fromSuper | tst.js:44:18:44:19 | 42 |
|
||||
| tst.js:48:1:50:2 | new Ext ... 42 }\\n}) | fromSub | tst.js:49:19:49:20 | 42 |
|
||||
| tst.js:48:1:50:2 | new Ext ... 42 }\\n}) | fromSuper | tst.js:44:18:44:19 | 42 |
|
||||
| tst.js:51:17:57:2 | Vue.ext ... \\n }\\n}) | fromSuper | tst.js:54:18:54:19 | 42 |
|
||||
| tst.js:58:1:61:2 | new Vue ... 42 }\\n}) | fromSub | tst.js:60:19:60:20 | 42 |
|
||||
| tst.js:63:1:66:2 | new Vue ... 42 }\\n}) | fromMixin1 | tst.js:64:32:64:33 | 42 |
|
||||
| tst.js:63:1:66:2 | new Vue ... 42 }\\n}) | fromMixin2 | tst.js:64:61:64:62 | 42 |
|
||||
| tst.js:63:1:66:2 | new Vue ... 42 }\\n}) | fromSub | tst.js:65:19:65:20 | 42 |
|
||||
| tst.js:72:1:75:2 | new Vue ... 42 }\\n}) | fromMixinValue | tst.js:69:28:69:29 | 42 |
|
||||
| tst.js:72:1:75:2 | new Vue ... 42 }\\n}) | fromSub | tst.js:74:19:74:20 | 42 |
|
||||
| tst.js:77:20:83:2 | Vue.ext ... \\n }\\n}) | deadExtended | tst.js:80:21:80:22 | 42 |
|
||||
| tst.js:85:1:87:2 | new Vue ... e; }\\n}) | created | tst.js:86:38:86:41 | true |
|
||||
| tst.js:94:2:96:3 | new Vue ... f,\\n\\t}) | dataA | tst.js:89:22:89:23 | 42 |
|
||||
@@ -0,0 +1,4 @@
|
||||
import javascript
|
||||
|
||||
from Vue::Instance i, string name
|
||||
select i, name, i.getAPropertyValue(name)
|
||||
@@ -0,0 +1,25 @@
|
||||
| single-component-file-1.vue:0:0:0:0 | single-component-file-1.vue | data | single-component-file-1.vue:6:11:6:45 | functio ... 42 } } |
|
||||
| single-file-component-3.vue:0:0:0:0 | single-file-component-3.vue | data | single-file-component-3-script.js:4:8:4:42 | functio ... 42 } } |
|
||||
| tst.js:3:1:10:2 | new Vue ... 2\\n\\t}\\n}) | data | tst.js:7:8:9:2 | {\\n\\t\\tdataA: 42\\n\\t} |
|
||||
| tst.js:3:1:10:2 | new Vue ... 2\\n\\t}\\n}) | render | tst.js:4:10:6:2 | functio ... c);\\n\\t} |
|
||||
| tst.js:12:1:16:2 | new Vue ... \\t}),\\n}) | data | tst.js:13:8:15:3 | () => ( ... 42\\n\\t}) |
|
||||
| tst.js:18:1:27:2 | Vue.com ... }\\n\\t}\\n}) | data | tst.js:19:8:21:3 | () => ( ... 42\\n\\t}) |
|
||||
| tst.js:18:1:27:2 | Vue.com ... }\\n\\t}\\n}) | methods | tst.js:22:11:26:2 | {\\n\\t\\tmet ... \\n\\t\\t}\\n\\t} |
|
||||
| tst.js:29:1:35:2 | new Vue ... }\\n\\t}\\n}) | computed | tst.js:30:12:34:2 | {\\n\\t\\tx: ... } }\\n\\t} |
|
||||
| tst.js:37:1:39:2 | new Vue ... nger\\n}) | template | tst.js:38:12:38:17 | danger |
|
||||
| tst.js:41:17:47:2 | Vue.ext ... \\n }\\n}) | data | tst.js:42:9:46:3 | functio ... };\\n } |
|
||||
| tst.js:48:1:50:2 | new Ext ... 42 }\\n}) | data | tst.js:42:9:46:3 | functio ... };\\n } |
|
||||
| tst.js:48:1:50:2 | new Ext ... 42 }\\n}) | data | tst.js:49:8:49:22 | { fromSub: 42 } |
|
||||
| tst.js:51:17:57:2 | Vue.ext ... \\n }\\n}) | data | tst.js:52:9:56:3 | functio ... };\\n } |
|
||||
| tst.js:58:1:61:2 | new Vue ... 42 }\\n}) | data | tst.js:60:8:60:22 | { fromSub: 42 } |
|
||||
| tst.js:58:1:61:2 | new Vue ... 42 }\\n}) | mixins | tst.js:59:10:59:18 | Extended2 |
|
||||
| tst.js:63:1:66:2 | new Vue ... 42 }\\n}) | data | tst.js:64:18:64:35 | { fromMixin1: 42 } |
|
||||
| tst.js:63:1:66:2 | new Vue ... 42 }\\n}) | data | tst.js:64:47:64:64 | { fromMixin2: 42 } |
|
||||
| tst.js:63:1:66:2 | new Vue ... 42 }\\n}) | data | tst.js:65:8:65:22 | { fromSub: 42 } |
|
||||
| tst.js:63:1:66:2 | new Vue ... 42 }\\n}) | mixins | tst.js:64:10:64:67 | [{data: ... 42 } }] |
|
||||
| tst.js:72:1:75:2 | new Vue ... 42 }\\n}) | data | tst.js:70:20:70:28 | mixinData |
|
||||
| tst.js:72:1:75:2 | new Vue ... 42 }\\n}) | data | tst.js:74:8:74:22 | { fromSub: 42 } |
|
||||
| tst.js:72:1:75:2 | new Vue ... 42 }\\n}) | mixins | tst.js:73:10:73:15 | mixins |
|
||||
| tst.js:77:20:83:2 | Vue.ext ... \\n }\\n}) | data | tst.js:78:9:82:3 | functio ... };\\n } |
|
||||
| tst.js:85:1:87:2 | new Vue ... e; }\\n}) | created | tst.js:86:11:86:44 | functio ... true; } |
|
||||
| tst.js:94:2:96:3 | new Vue ... f,\\n\\t}) | data | tst.js:95:9:95:9 | f |
|
||||
@@ -0,0 +1,4 @@
|
||||
import javascript
|
||||
|
||||
from Vue::Instance i, string name
|
||||
select i, name, i.getOption(name)
|
||||
@@ -0,0 +1,2 @@
|
||||
| tst.js:5:13:5:13 | a |
|
||||
| tst.js:38:12:38:17 | danger |
|
||||
@@ -0,0 +1,4 @@
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.DomBasedXss
|
||||
|
||||
select any(DomBasedXss::Sink s)
|
||||
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<p v-html="dataA"/>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data: function() { return { dataA: 42 } }
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<p v-html="dataA"/>
|
||||
</template>
|
||||
<script>
|
||||
var x = require('x');
|
||||
module.exports = { // not properly detected by the module system yet
|
||||
data: function() { return { dataA: 42 } }
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,5 @@
|
||||
var x = require('x');
|
||||
|
||||
module.exports = {
|
||||
data: function() { return { dataA: 42 } }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<p v-html="dataA"/>
|
||||
</template>
|
||||
<script src="./single-file-component-3-script.js">
|
||||
</script>
|
||||
<style>
|
||||
</style>
|
||||
97
javascript/ql/test/library-tests/frameworks/Vue/tst.js
Normal file
97
javascript/ql/test/library-tests/frameworks/Vue/tst.js
Normal file
@@ -0,0 +1,97 @@
|
||||
var Vue = require('vue');
|
||||
|
||||
new Vue({
|
||||
render: function(ce) {
|
||||
return ce(a, b, c);
|
||||
},
|
||||
data: {
|
||||
dataA: 42
|
||||
}
|
||||
});
|
||||
|
||||
new Vue({
|
||||
data: () => ({
|
||||
dataA: 42
|
||||
}),
|
||||
});
|
||||
|
||||
Vue.component("my-component", {
|
||||
data: () => ({
|
||||
dataA: 42
|
||||
}),
|
||||
methods: {
|
||||
method: function() {
|
||||
this.dataB = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
new Vue({
|
||||
computed: {
|
||||
x: () => 42,
|
||||
y: { get: () => 42 },
|
||||
z1: { set: function(){ this.z2 = 42; } }
|
||||
}
|
||||
});
|
||||
|
||||
new Vue({
|
||||
template: danger
|
||||
});
|
||||
|
||||
var Extended1 = Vue.extend({
|
||||
data: function () {
|
||||
return {
|
||||
fromSuper: 42
|
||||
};
|
||||
}
|
||||
});
|
||||
new Extended1({
|
||||
data: { fromSub: 42 }
|
||||
});
|
||||
var Extended2 = Vue.extend({
|
||||
data: function () {
|
||||
return {
|
||||
fromSuper: 42
|
||||
};
|
||||
}
|
||||
});
|
||||
new Vue({
|
||||
mixins: Extended2,
|
||||
data: { fromSub: 42 }
|
||||
});
|
||||
|
||||
new Vue({
|
||||
mixins: [{data: { fromMixin1: 42 } }, {data: { fromMixin2: 42 } }],
|
||||
data: { fromSub: 42 }
|
||||
});
|
||||
|
||||
var mixinData = { };
|
||||
mixinData.fromMixinValue = 42;
|
||||
var mixin = {data: mixinData };
|
||||
var mixins = [mixin];
|
||||
new Vue({
|
||||
mixins: mixins,
|
||||
data: { fromSub: 42 }
|
||||
});
|
||||
|
||||
var DeadExtended = Vue.extend({
|
||||
data: function () {
|
||||
return {
|
||||
deadExtended: 42
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
new Vue({
|
||||
created: function(){ this.created = true; }
|
||||
});
|
||||
(function() {
|
||||
var data = { dataA: 42 };
|
||||
function f() {
|
||||
return data;
|
||||
}
|
||||
|
||||
new Vue({
|
||||
data: f,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
| tst.js:4:11:4:20 | () => this | The $@ of this $@ it will not be bound to the Vue instance. | tst.js:4:17:4:20 | this | `this` variable | tst.js:4:11:4:20 | () => this | arrow function |
|
||||
| tst.js:6:6:6:15 | () => this | The $@ of this $@ it will not be bound to the Vue instance. | tst.js:6:12:6:15 | this | `this` variable | tst.js:6:6:6:15 | () => this | arrow function |
|
||||
| tst.js:7:13:7:22 | () => this | The $@ of this $@ it will not be bound to the Vue instance. | tst.js:7:19:7:22 | this | `this` variable | tst.js:7:13:7:22 | () => this | arrow function |
|
||||
| tst.js:8:13:8:22 | () => this | The $@ of this $@ it will not be bound to the Vue instance. | tst.js:8:19:8:22 | this | `this` variable | tst.js:8:13:8:22 | () => this | arrow function |
|
||||
| tst.js:11:10:11:19 | () => this | The $@ of this $@ it will not be bound to the Vue instance. | tst.js:11:16:11:19 | this | `this` variable | tst.js:11:10:11:19 | () => this | arrow function |
|
||||
@@ -0,0 +1 @@
|
||||
Vue/ArrowMethodOnVueInstance.ql
|
||||
16
javascript/ql/test/query-tests/Vue/tst.js
Normal file
16
javascript/ql/test/query-tests/Vue/tst.js
Normal file
@@ -0,0 +1,16 @@
|
||||
let Vue = require('vue');
|
||||
|
||||
new Vue( {
|
||||
created: () => this, // NOT OK
|
||||
computed: {
|
||||
x: () => this, // NOT OK
|
||||
y: { get: () => this }, // NOT OK
|
||||
z: { set: () => this } // NOT OK
|
||||
},
|
||||
methods: {
|
||||
arrow: () => this, // NOT OK
|
||||
nonArrow: function() { this; }, // OK
|
||||
arrowWithoutThis: () => 42, // OK
|
||||
arrowWithNestedThis: () => (() => this) // OK
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user