From a78dd422b6d2f82ab00dea8e730d3706b07ace0b Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Fri, 25 Jan 2019 12:35:12 +0100 Subject: [PATCH] JS: add query `js/vue/arrow-method-on-vue-instance` --- change-notes/1.20/analysis-javascript.md | 1 + .../config/suites/javascript/frameworks-more | 1 + .../ql/src/Vue/ArrowMethodOnVueInstance.qhelp | 49 +++++++++++++++++++ .../ql/src/Vue/ArrowMethodOnVueInstance.ql | 19 +++++++ .../Vue/examples/ArrowMethodOnVueInstance.js | 19 +++++++ .../Vue/ArrowMethodOnVueInstance.expected | 5 ++ .../Vue/ArrowMethodOnVueInstance.qlref | 1 + javascript/ql/test/query-tests/Vue/tst.js | 16 ++++++ 8 files changed, 111 insertions(+) create mode 100644 javascript/ql/src/Vue/ArrowMethodOnVueInstance.qhelp create mode 100644 javascript/ql/src/Vue/ArrowMethodOnVueInstance.ql create mode 100644 javascript/ql/src/Vue/examples/ArrowMethodOnVueInstance.js create mode 100644 javascript/ql/test/query-tests/Vue/ArrowMethodOnVueInstance.expected create mode 100644 javascript/ql/test/query-tests/Vue/ArrowMethodOnVueInstance.qlref create mode 100644 javascript/ql/test/query-tests/Vue/tst.js diff --git a/change-notes/1.20/analysis-javascript.md b/change-notes/1.20/analysis-javascript.md index 1b1b2502db3..f4008aca1fc 100644 --- a/change-notes/1.20/analysis-javascript.md +++ b/change-notes/1.20/analysis-javascript.md @@ -14,6 +14,7 @@ | **Query** | **Tags** | **Purpose** | |-----------------------------------------------|------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Arrow method on Vue instance (`js/vue/arrow-method-on-vue-instance`) | reliability, frameworks/vue | Highlights arrow functions that are used as methods on Vue instances. Results are shown on LGTM by default.| | Double escaping or unescaping (`js/double-escaping`) | correctness, security, external/cwe/cwe-116 | Highlights potential double escaping or unescaping of special characters, indicating a possible violation of [CWE-116](https://cwe.mitre.org/data/definitions/116.html). Results are shown on LGTM by default. | | Incomplete regular expression for hostnames (`js/incomplete-hostname-regexp`) | correctness, security, external/cwe/cwe-020 | Highlights hostname sanitizers that are likely to be incomplete, indicating a violation of [CWE-020](https://cwe.mitre.org/data/definitions/20.html). Results are shown on LGTM by default.| | Incomplete URL substring sanitization | correctness, security, external/cwe/cwe-020 | Highlights URL sanitizers that are likely to be incomplete, indicating a violation of [CWE-020](https://cwe.mitre.org/data/definitions/20.html). Results shown on LGTM by default. | diff --git a/javascript/config/suites/javascript/frameworks-more b/javascript/config/suites/javascript/frameworks-more index 1a627c728f4..3445428825f 100644 --- a/javascript/config/suites/javascript/frameworks-more +++ b/javascript/config/suites/javascript/frameworks-more @@ -23,3 +23,4 @@ + semmlecode-javascript-queries/React/UnusedOrUndefinedStateProperty.ql: /Frameworks/React + semmlecode-javascript-queries/Electron/DisablingWebSecurity.ql: /Frameworks/Electron + semmlecode-javascript-queries/Electron/AllowRunningInsecureContent.ql: /Frameworks/Electron ++ semmlecode-javascript-queries/Vue/ArrowMethodOnVueInstance.ql: /Frameworks/Vue diff --git a/javascript/ql/src/Vue/ArrowMethodOnVueInstance.qhelp b/javascript/ql/src/Vue/ArrowMethodOnVueInstance.qhelp new file mode 100644 index 00000000000..b8df26fdd8a --- /dev/null +++ b/javascript/ql/src/Vue/ArrowMethodOnVueInstance.qhelp @@ -0,0 +1,49 @@ + + + + +

+ + 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 + this variable in an arrow function on a Vue instance may + not have the value that the programmer expects. + +

+
+ + +

+ Ensure that the methods on a Vue instance can have their receiver bound to the instance. +

+
+ + + +

+ + The following example shows two similar Vue instances, the only + difference is how the created life cycle hook + callback is defined. + + The first Vue instance uses an arrow function as the callback. + This means that the this variable will have the global + object as its value, causing this.myProperty to evaluate + to undefined, which may not be intended. + + Instead, the second Vue instance uses an ordinary function as the callback, + causing this.myProperty to evaluate to 42. + +

+ + + +
+ + +
  • Vue documentation: The Vue Instance
  • +
    +
    diff --git a/javascript/ql/src/Vue/ArrowMethodOnVueInstance.ql b/javascript/ql/src/Vue/ArrowMethodOnVueInstance.ql new file mode 100644 index 00000000000..0633ef94c21 --- /dev/null +++ b/javascript/ql/src/Vue/ArrowMethodOnVueInstance.ql @@ -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" diff --git a/javascript/ql/src/Vue/examples/ArrowMethodOnVueInstance.js b/javascript/ql/src/Vue/examples/ArrowMethodOnVueInstance.js new file mode 100644 index 00000000000..5efec7bae4d --- /dev/null +++ b/javascript/ql/src/Vue/examples/ArrowMethodOnVueInstance.js @@ -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); + } +}); diff --git a/javascript/ql/test/query-tests/Vue/ArrowMethodOnVueInstance.expected b/javascript/ql/test/query-tests/Vue/ArrowMethodOnVueInstance.expected new file mode 100644 index 00000000000..ea34f916157 --- /dev/null +++ b/javascript/ql/test/query-tests/Vue/ArrowMethodOnVueInstance.expected @@ -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 | diff --git a/javascript/ql/test/query-tests/Vue/ArrowMethodOnVueInstance.qlref b/javascript/ql/test/query-tests/Vue/ArrowMethodOnVueInstance.qlref new file mode 100644 index 00000000000..1203930af86 --- /dev/null +++ b/javascript/ql/test/query-tests/Vue/ArrowMethodOnVueInstance.qlref @@ -0,0 +1 @@ +Vue/ArrowMethodOnVueInstance.ql \ No newline at end of file diff --git a/javascript/ql/test/query-tests/Vue/tst.js b/javascript/ql/test/query-tests/Vue/tst.js new file mode 100644 index 00000000000..27fe31488cb --- /dev/null +++ b/javascript/ql/test/query-tests/Vue/tst.js @@ -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 + } +});