From 30d192a1db06ab4aa2893bad26c347fefce22696 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 9 Jan 2025 11:19:16 +0100 Subject: [PATCH 01/10] JS: Move getName() to a shared location --- javascript/ql/src/meta/alerts/TaintSources.ql | 9 +-------- javascript/ql/src/meta/internal/TaintMetrics.qll | 7 +++++++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/javascript/ql/src/meta/alerts/TaintSources.ql b/javascript/ql/src/meta/alerts/TaintSources.ql index 95daa2a71e28..4e27f33b66e2 100644 --- a/javascript/ql/src/meta/alerts/TaintSources.ql +++ b/javascript/ql/src/meta/alerts/TaintSources.ql @@ -11,13 +11,6 @@ import javascript import meta.internal.TaintMetrics -string getName(DataFlow::Node node) { - result = node.(RemoteFlowSource).getSourceType() - or - not node instanceof RemoteFlowSource and - result = "Taint source" -} - from DataFlow::Node node where node = relevantTaintSource() -select node, getName(node) +select node, getTaintSourceName(node) diff --git a/javascript/ql/src/meta/internal/TaintMetrics.qll b/javascript/ql/src/meta/internal/TaintMetrics.qll index f6eae2eaa6e9..dbdc0fddc3c1 100644 --- a/javascript/ql/src/meta/internal/TaintMetrics.qll +++ b/javascript/ql/src/meta/internal/TaintMetrics.qll @@ -100,3 +100,10 @@ DataFlow::Node relevantSanitizerInput() { result = any(HtmlSanitizerCall call).getInput() and not result.getFile() instanceof IgnoredFile } + +string getTaintSourceName(DataFlow::Node node) { + result = node.(RemoteFlowSource).getSourceType() + or + not node instanceof RemoteFlowSource and + result = "Taint source" +} From 01f7d45e2d4a0f37c7ff6357b2384faa90efd13b Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 9 Jan 2025 11:23:27 +0100 Subject: [PATCH 02/10] JS: Add meta query for reporting threat model sources --- javascript/ql/src/meta/alerts/TaintSources.ql | 4 ++-- .../ql/src/meta/alerts/ThreatModelSources.ql | 19 +++++++++++++++++++ .../ql/src/meta/internal/TaintMetrics.qll | 8 ++++---- 3 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 javascript/ql/src/meta/alerts/ThreatModelSources.ql diff --git a/javascript/ql/src/meta/alerts/TaintSources.ql b/javascript/ql/src/meta/alerts/TaintSources.ql index 4e27f33b66e2..327aca84460c 100644 --- a/javascript/ql/src/meta/alerts/TaintSources.ql +++ b/javascript/ql/src/meta/alerts/TaintSources.ql @@ -11,6 +11,6 @@ import javascript import meta.internal.TaintMetrics -from DataFlow::Node node -where node = relevantTaintSource() +from ThreatModelSource node +where node = relevantTaintSource() and node.getThreatModel() = "remote" select node, getTaintSourceName(node) diff --git a/javascript/ql/src/meta/alerts/ThreatModelSources.ql b/javascript/ql/src/meta/alerts/ThreatModelSources.ql new file mode 100644 index 000000000000..99cd7b342a02 --- /dev/null +++ b/javascript/ql/src/meta/alerts/ThreatModelSources.ql @@ -0,0 +1,19 @@ +/** + * @name Threat model sources + * @description Sources of possibly untrusted input that can be configured via threat models. + * @kind problem + * @problem.severity recommendation + * @id js/meta/alerts/threat-model-sources + * @tags meta + * @precision very-low + */ + +import javascript +import meta.internal.TaintMetrics + +from ThreatModelSource node, string threatModel +where + node = relevantTaintSource() and + threatModel = node.getThreatModel() and + threatModel != "remote" // "remote" is reported by TaintSources.ql +select node, getTaintSourceName(node) + " (\"" + threatModel + "\" threat model)" diff --git a/javascript/ql/src/meta/internal/TaintMetrics.qll b/javascript/ql/src/meta/internal/TaintMetrics.qll index dbdc0fddc3c1..70d63a8cc3aa 100644 --- a/javascript/ql/src/meta/internal/TaintMetrics.qll +++ b/javascript/ql/src/meta/internal/TaintMetrics.qll @@ -75,9 +75,9 @@ DataFlow::Node relevantTaintSink(string kind) { DataFlow::Node relevantTaintSink() { result = relevantTaintSink(_) } /** - * Gets a relevant remote flow source. + * Gets a relevant threat model source. */ -RemoteFlowSource relevantTaintSource() { not result.getFile() instanceof IgnoredFile } +ThreatModelSource relevantTaintSource() { not result.getFile() instanceof IgnoredFile } /** * Gets the output of a call that shows intent to sanitize a value @@ -102,8 +102,8 @@ DataFlow::Node relevantSanitizerInput() { } string getTaintSourceName(DataFlow::Node node) { - result = node.(RemoteFlowSource).getSourceType() + result = node.(ThreatModelSource).getSourceType() or - not node instanceof RemoteFlowSource and + not node instanceof ThreatModelSource and result = "Taint source" } From b015c88c79c5688b954c6a01a1ff240aa00251ea Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 8 Jan 2025 13:57:02 +0100 Subject: [PATCH 03/10] JS: Add view-component-input threat model --- .../semmle/javascript/ViewComponentInput.qll | 12 ++ .../semmle/javascript/frameworks/Angular2.qll | 14 +++ .../semmle/javascript/frameworks/React.qll | 7 ++ .../lib/semmle/javascript/frameworks/Vue.qll | 89 ++++++++++++-- .../frameworks/Angular2/sink.component.ts | 22 ++-- .../frameworks/Angular2/test.expected | 10 ++ .../library-tests/frameworks/Angular2/test.ql | 4 + .../frameworks/ReactJS/tests.expected | 25 ++++ .../library-tests/frameworks/ReactJS/tests.ql | 4 + .../Vue/single-component-file-1.vue | 3 +- .../Vue/single-file-component-2.vue | 3 +- .../Vue/single-file-component-3-script.js | 3 +- .../Vue/single-file-component-4.vue | 6 + .../Vue/single-file-component-5.vue | 6 + .../Vue/single-file-component-6.vue | 8 ++ .../Vue/single-file-component-7.vue | 12 ++ .../Vue/single-file-component-8.vue | 12 ++ .../frameworks/Vue/tests.expected | 112 ++++++++++++++---- .../library-tests/frameworks/Vue/tests.ql | 4 + 19 files changed, 309 insertions(+), 47 deletions(-) create mode 100644 javascript/ql/lib/semmle/javascript/ViewComponentInput.qll create mode 100644 javascript/ql/test/library-tests/frameworks/Vue/single-file-component-6.vue create mode 100644 javascript/ql/test/library-tests/frameworks/Vue/single-file-component-7.vue create mode 100644 javascript/ql/test/library-tests/frameworks/Vue/single-file-component-8.vue diff --git a/javascript/ql/lib/semmle/javascript/ViewComponentInput.qll b/javascript/ql/lib/semmle/javascript/ViewComponentInput.qll new file mode 100644 index 000000000000..652a1502179d --- /dev/null +++ b/javascript/ql/lib/semmle/javascript/ViewComponentInput.qll @@ -0,0 +1,12 @@ +/** + * Provides a classes and predicates for contributing to the `view-component-input` threat model. + */ + +private import javascript + +/** + * An input to a view component, such as React props. + */ +abstract class ViewComponentInput extends ThreatModelSource::Range { + final override string getThreatModel() { result = "view-component-input" } +} diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Angular2.qll b/javascript/ql/lib/semmle/javascript/frameworks/Angular2.qll index 3ac3d947aced..6c52001ac527 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Angular2.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Angular2.qll @@ -8,6 +8,7 @@ private import semmle.javascript.security.dataflow.CodeInjectionCustomizations private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomizations private import semmle.javascript.DynamicPropertyAccess private import semmle.javascript.dataflow.internal.PreCallGraphStep +private import semmle.javascript.ViewComponentInput /** * Provides classes for working with Angular (also known as Angular 2.x) applications. @@ -575,4 +576,17 @@ module Angular2 { ) } } + + private class InputFieldAsViewComponentInput extends ViewComponentInput { + InputFieldAsViewComponentInput() { + this = + API::moduleImport("@angular/core") + .getMember("Input") + .getReturn() + .getADecoratedMember() + .asSource() + } + + override string getSourceType() { result = "Angular component input field" } + } } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/React.qll b/javascript/ql/lib/semmle/javascript/frameworks/React.qll index 64c9f5feb782..5ac0e419e264 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/React.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/React.qll @@ -5,6 +5,7 @@ import javascript private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps private import semmle.javascript.dataflow.internal.PreCallGraphStep +private import semmle.javascript.ViewComponentInput /** * Gets a reference to the 'React' object. @@ -868,3 +869,9 @@ private class PropsFlowStep extends PreCallGraphStep { ) } } + +private class ReactPropAsViewComponentInput extends ViewComponentInput { + ReactPropAsViewComponentInput() { this = any(ReactComponent c).getADirectPropsAccess() } + + override string getSourceType() { result = "React props" } +} diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll b/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll index ebfe042f4d06..faba601df52d 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll @@ -3,6 +3,7 @@ */ import javascript +import semmle.javascript.ViewComponentInput module Vue { /** The global variable `Vue`, as an API graph entry point. */ @@ -85,17 +86,16 @@ module Vue { * A class with a `@Component` decorator, making it usable as an "options" object in Vue. */ class ClassComponent extends DataFlow::ClassNode { + private ClassDefinition cls; DataFlow::Node decorator; ClassComponent() { - exists(ClassDefinition cls | - this = cls.flow() and - cls.getADecorator().getExpression() = decorator.asExpr() and - ( - componentDecorator().flowsTo(decorator) - or - componentDecorator().getACall() = decorator - ) + this = cls.flow() and + cls.getADecorator().getExpression() = decorator.asExpr() and + ( + componentDecorator().flowsTo(decorator) + or + componentDecorator().getACall() = decorator ) } @@ -105,6 +105,9 @@ module Vue { * These options correspond to the options one would pass to `new Vue({...})` or similar. */ API::Node getDecoratorOptions() { result = decorator.(API::CallNode).getParameter(0) } + + /** Gets the AST node for the class definition. */ + ClassDefinition getClassDefinition() { result = cls } } private string memberKindVerb(DataFlow::MemberKind kind) { @@ -460,6 +463,12 @@ module Vue { SingleFileComponent() { this = MkSingleFileComponent(file) } + /** Gets a call to `defineProps` in this component. */ + DataFlow::CallNode getDefinePropsCall() { + result = DataFlow::globalVarRef("defineProps").getACall() and + result.getFile() = file + } + override Template::Element getTemplateElement() { exists(HTML::Element e | result.(Template::HtmlElement).getElement() = e | e.getFile() = file and @@ -697,4 +706,68 @@ module Vue { override ClientSideRemoteFlowKind getKind() { result = kind } } + + /** + * Holds if the given type annotation indicates a value that is not typically considered taintable. + */ + private predicate isSafeType(TypeAnnotation type) { + type.isBooleany() or + type.isNumbery() or + type.isRawFunction() or + type instanceof FunctionTypeExpr + } + + /** + * Holds if the given field has a type that indicates that is can not contain a taintable value. + */ + private predicate isSafeField(FieldDeclaration field) { isSafeType(field.getTypeAnnotation()) } + + private DataFlow::Node getPropSpec(Component component) { + result = component.getOption("props") + or + result = component.(SingleFileComponent).getDefinePropsCall().getArgument(0) + } + + /** + * Holds if `component` has an input prop with the given name, that is of a taintable type. + */ + private predicate hasTaintableProp(Component component, string name) { + exists(DataFlow::SourceNode spec | spec = getPropSpec(component).getALocalSource() | + spec.(DataFlow::ArrayCreationNode).getAnElement().getStringValue() = name + or + exists(DataFlow::PropWrite write | + write = spec.getAPropertyWrite(name) and + not DataFlow::globalVarRef(["Number", "Boolean"]).flowsTo(write.getRhs()) + ) + ) + or + exists(FieldDeclaration field | + field = component.getAsClassComponent().getClassDefinition().getField(name) and + DataFlow::moduleMember("vue-property-decorator", "Prop") + .getACall() + .flowsToExpr(field.getADecorator().getExpression()) and + not isSafeField(field) + ) + or + // defineProps() can be called with only type arguments and then the Vue compiler will + // infer the prop types. + exists(CallExpr call, FieldDeclaration field | + call = component.(SingleFileComponent).getDefinePropsCall().asExpr() and + field = call.getTypeArgument(0).(InterfaceTypeExpr).getMember(name) and + not isSafeField(field) + ) + } + + private class PropAsViewComponentInput extends ViewComponentInput { + PropAsViewComponentInput() { + exists(Component component, string name | hasTaintableProp(component, name) | + this = component.getAnInstanceRef().getAPropertyRead(name) + or + // defineProps() returns the props + this = component.(SingleFileComponent).getDefinePropsCall().getAPropertyRead(name) + ) + } + + override string getSourceType() { result = "Vue prop" } + } } diff --git a/javascript/ql/test/library-tests/frameworks/Angular2/sink.component.ts b/javascript/ql/test/library-tests/frameworks/Angular2/sink.component.ts index 4db18a7e2d6d..7210e235d5a7 100644 --- a/javascript/ql/test/library-tests/frameworks/Angular2/sink.component.ts +++ b/javascript/ql/test/library-tests/frameworks/Angular2/sink.component.ts @@ -1,4 +1,4 @@ -import { Component } from "@angular/core"; +import { Component, Input } from "@angular/core"; import { DomSanitizer } from '@angular/platform-browser'; @Component({ @@ -6,17 +6,17 @@ import { DomSanitizer } from '@angular/platform-browser'; template: "not important" }) export class SinkComponent { - sink1: string; - sink2: string; - sink3: string; - sink4: string; - sink5: string; - sink6: string; - sink7: string; - sink8: string; - sink9: string; + @Input() sink1: string; + @Input() sink2: string; + @Input() sink3: string; + @Input() sink4: string; + @Input() sink5: string; + @Input() sink6: string; + @Input() sink7: string; + @Input() sink8: string; + @Input() sink9: string; - constructor(private sanitizer: DomSanitizer) {} + constructor(private sanitizer: DomSanitizer) { } foo() { this.sanitizer.bypassSecurityTrustHtml(this.sink1); diff --git a/javascript/ql/test/library-tests/frameworks/Angular2/test.expected b/javascript/ql/test/library-tests/frameworks/Angular2/test.expected index acf97ab947e5..4934597c4015 100644 --- a/javascript/ql/test/library-tests/frameworks/Angular2/test.expected +++ b/javascript/ql/test/library-tests/frameworks/Angular2/test.expected @@ -36,3 +36,13 @@ taintFlow | source.component.ts:16:33:16:40 | source() | sink.component.ts:22:48:22:57 | this.sink1 | testAttrSourceLocation | inline.component.ts:8:43:8:60 | [testAttr]=taint | inline.component.ts:8:55:8:59 | | +threatModelSource +| sink.component.ts:22:48:22:57 | this.sink1 | view-component-input | +| sink.component.ts:23:48:23:57 | this.sink2 | view-component-input | +| sink.component.ts:24:48:24:57 | this.sink3 | view-component-input | +| sink.component.ts:25:48:25:57 | this.sink4 | view-component-input | +| sink.component.ts:26:48:26:57 | this.sink5 | view-component-input | +| sink.component.ts:27:48:27:57 | this.sink6 | view-component-input | +| sink.component.ts:28:48:28:57 | this.sink7 | view-component-input | +| sink.component.ts:29:48:29:57 | this.sink8 | view-component-input | +| sink.component.ts:30:48:30:57 | this.sink9 | view-component-input | diff --git a/javascript/ql/test/library-tests/frameworks/Angular2/test.ql b/javascript/ql/test/library-tests/frameworks/Angular2/test.ql index 140ae675a777..91c4dfe11a88 100644 --- a/javascript/ql/test/library-tests/frameworks/Angular2/test.ql +++ b/javascript/ql/test/library-tests/frameworks/Angular2/test.ql @@ -42,3 +42,7 @@ deprecated class LegacyConfig extends TaintTracking::Configuration { } deprecated import utils.test.LegacyDataFlowDiff::DataFlowDiff + +query predicate threatModelSource(ThreatModelSource source, string kind) { + kind = source.getThreatModel() +} diff --git a/javascript/ql/test/library-tests/frameworks/ReactJS/tests.expected b/javascript/ql/test/library-tests/frameworks/ReactJS/tests.expected index 4804e30b6f79..9a5c38ddbf90 100644 --- a/javascript/ql/test/library-tests/frameworks/ReactJS/tests.expected +++ b/javascript/ql/test/library-tests/frameworks/ReactJS/tests.expected @@ -318,3 +318,28 @@ test_JsxName_this | thisAccesses.js:61:19:61:41 | | thisAccesses.js:61:20:61:23 | this | locationSource | importedComponent.jsx:3:32:3:39 | location | +threatModelSource +| es5.js:4:24:4:33 | this.props | view-component-input | +| es5.js:20:24:20:33 | this.props | view-component-input | +| es6.js:1:37:1:36 | args | view-component-input | +| es6.js:3:24:3:33 | this.props | view-component-input | +| exportedComponent.jsx:1:29:1:33 | props | view-component-input | +| importedComponent.jsx:3:24:3:40 | {color, location} | view-component-input | +| importedComponent.jsx:3:32:3:39 | location | remote | +| namedImport.js:3:27:3:26 | args | view-component-input | +| namedImport.js:5:19:5:18 | args | view-component-input | +| plainfn.js:1:16:1:20 | props | view-component-input | +| plainfn.js:5:17:5:21 | props | view-component-input | +| plainfn.js:9:17:9:21 | props | view-component-input | +| plainfn.js:20:28:20:32 | props | view-component-input | +| preact.js:1:38:1:37 | args | view-component-input | +| preact.js:2:12:2:16 | props | view-component-input | +| preact.js:9:38:9:37 | args | view-component-input | +| probably-a-component.js:1:31:1:30 | args | view-component-input | +| probably-a-component.js:3:9:3:18 | this.props | view-component-input | +| props.js:2:37:2:36 | args | view-component-input | +| props.js:26:16:26:20 | props | view-component-input | +| rare-lifecycle-methods.js:1:33:1:32 | args | view-component-input | +| statePropertyWrites.js:38:24:38:33 | this.props | view-component-input | +| thisAccesses.js:31:12:31:16 | props | view-component-input | +| thisAccesses.js:48:18:48:18 | y | view-component-input | diff --git a/javascript/ql/test/library-tests/frameworks/ReactJS/tests.ql b/javascript/ql/test/library-tests/frameworks/ReactJS/tests.ql index b97e69b2526a..4d20306d4ed4 100644 --- a/javascript/ql/test/library-tests/frameworks/ReactJS/tests.ql +++ b/javascript/ql/test/library-tests/frameworks/ReactJS/tests.ql @@ -11,3 +11,7 @@ import ReactComponent_getAPropRead import ReactName query DataFlow::SourceNode locationSource() { result = DOM::locationSource() } + +query predicate threatModelSource(ThreatModelSource source, string kind) { + kind = source.getThreatModel() +} diff --git a/javascript/ql/test/library-tests/frameworks/Vue/single-component-file-1.vue b/javascript/ql/test/library-tests/frameworks/Vue/single-component-file-1.vue index 4f7e4b9c47fe..02045c31d29a 100644 --- a/javascript/ql/test/library-tests/frameworks/Vue/single-component-file-1.vue +++ b/javascript/ql/test/library-tests/frameworks/Vue/single-component-file-1.vue @@ -3,7 +3,8 @@ diff --git a/javascript/ql/test/library-tests/frameworks/Vue/single-file-component-7.vue b/javascript/ql/test/library-tests/frameworks/Vue/single-file-component-7.vue new file mode 100644 index 000000000000..0d099dc8b9b5 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/Vue/single-file-component-7.vue @@ -0,0 +1,12 @@ + + + diff --git a/javascript/ql/test/library-tests/frameworks/Vue/single-file-component-8.vue b/javascript/ql/test/library-tests/frameworks/Vue/single-file-component-8.vue new file mode 100644 index 000000000000..03a7b838f2bf --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/Vue/single-file-component-8.vue @@ -0,0 +1,12 @@ + + + diff --git a/javascript/ql/test/library-tests/frameworks/Vue/tests.expected b/javascript/ql/test/library-tests/frameworks/Vue/tests.expected index 7f0ea1aa9ac5..4fe66404c782 100644 --- a/javascript/ql/test/library-tests/frameworks/Vue/tests.expected +++ b/javascript/ql/test/library-tests/frameworks/Vue/tests.expected @@ -1,12 +1,14 @@ component_getAPropertyValue | compont-with-route.vue:0:0:0:0 | compont-with-route.vue | dataA | compont-with-route.vue:31:14:31:34 | this.$r ... ery.foo | | compont-with-route.vue:0:0:0:0 | compont-with-route.vue | message | compont-with-route.vue:19:15:19:22 | 'Hello!' | -| 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 | -| single-file-component-4.vue:0:0:0:0 | single-file-component-4.vue | dataA | single-file-component-4.vue:15:14:15:15 | 42 | -| single-file-component-4.vue:0:0:0:0 | single-file-component-4.vue | message | single-file-component-4.vue:12:23:12:30 | 'Hello!' | -| single-file-component-5.vue:0:0:0:0 | single-file-component-5.vue | dataA | single-file-component-5.vue:13:14:13:15 | 42 | -| single-file-component-5.vue:0:0:0:0 | single-file-component-5.vue | message | single-file-component-5.vue:10:23:10:30 | 'Hello!' | +| single-component-file-1.vue:0:0:0:0 | single-component-file-1.vue | dataA | single-component-file-1.vue:7:40:7:54 | 42 + this.input | +| single-file-component-3.vue:0:0:0:0 | single-file-component-3.vue | dataA | single-file-component-3-script.js:5:37:5:51 | 42 + this.input | +| single-file-component-4.vue:0:0:0:0 | single-file-component-4.vue | dataA | single-file-component-4.vue:17:14:17:15 | 42 | +| single-file-component-4.vue:0:0:0:0 | single-file-component-4.vue | dataB | single-file-component-4.vue:21:14:21:23 | this.input | +| single-file-component-4.vue:0:0:0:0 | single-file-component-4.vue | message | single-file-component-4.vue:13:23:13:30 | 'Hello!' | +| single-file-component-5.vue:0:0:0:0 | single-file-component-5.vue | dataA | single-file-component-5.vue:15:14:15:15 | 42 | +| single-file-component-5.vue:0:0:0:0 | single-file-component-5.vue | dataB | single-file-component-5.vue:19:14:19:23 | this.input | +| single-file-component-5.vue:0:0:0:0 | single-file-component-5.vue | message | single-file-component-5.vue:11:23:11:30 | 'Hello!' | | 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 | @@ -35,9 +37,11 @@ component_getAPropertyValue | tst.js:113:17:117:2 | base.ex ... 0\\n\\t}\\n}) | fromSubclass2 | tst.js:115:18:115:20 | 100 | component_getOption | compont-with-route.vue:0:0:0:0 | compont-with-route.vue | watch | compont-with-route.vue:10:12:16:5 | {\\n ... }\\n } | -| 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 } } | -| single-file-component-4.vue:0:0:0:0 | single-file-component-4.vue | render | single-file-component-4.vue:9:13:9:22 | (h) => { } | +| single-component-file-1.vue:0:0:0:0 | single-component-file-1.vue | data | single-component-file-1.vue:7:11:7:58 | functio ... put } } | +| single-component-file-1.vue:0:0:0:0 | single-component-file-1.vue | props | single-component-file-1.vue:6:12:6:20 | ['input'] | +| single-file-component-3.vue:0:0:0:0 | single-file-component-3.vue | data | single-file-component-3-script.js:5:8:5:55 | functio ... put } } | +| single-file-component-3.vue:0:0:0:0 | single-file-component-3.vue | props | single-file-component-3-script.js:4:9:4:17 | ['input'] | +| single-file-component-4.vue:0:0:0:0 | single-file-component-4.vue | render | single-file-component-4.vue:10:13:10:22 | (h) => { } | | 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}) | @@ -76,6 +80,9 @@ component | single-file-component-3.vue:0:0:0:0 | single-file-component-3.vue | | single-file-component-4.vue:0:0:0:0 | single-file-component-4.vue | | single-file-component-5.vue:0:0:0:0 | single-file-component-5.vue | +| single-file-component-6.vue:0:0:0:0 | single-file-component-6.vue | +| single-file-component-7.vue:0:0:0:0 | single-file-component-7.vue | +| single-file-component-8.vue:0:0:0:0 | single-file-component-8.vue | | special-syntax.vue:0:0:0:0 | special-syntax.vue | | tst.js:3:1:10:2 | new Vue ... 2\\n\\t}\\n}) | | tst.js:12:1:16:2 | new Vue ... \\t}),\\n}) | @@ -97,10 +104,10 @@ component | tst.js:113:17:117:2 | base.ex ... 0\\n\\t}\\n}) | viewComponentStep | compont-with-route.vue:31:14:31:34 | this.$r ... ery.foo | compont-with-route.vue:2:8:2:21 | v-html=dataA | -| single-component-file-1.vue:6:40:6:41 | 42 | single-component-file-1.vue:2:8:2:21 | v-html=dataA | -| single-file-component-3-script.js:4:37:4:38 | 42 | single-file-component-3.vue:2:8:2:21 | v-html=dataA | -| single-file-component-4.vue:15:14:15:15 | 42 | single-file-component-4.vue:2:8:2:21 | v-html=dataA | -| single-file-component-5.vue:13:14:13:15 | 42 | single-file-component-5.vue:2:8:2:21 | v-html=dataA | +| single-component-file-1.vue:7:40:7:54 | 42 + this.input | single-component-file-1.vue:2:8:2:21 | v-html=dataA | +| single-file-component-3-script.js:5:37:5:51 | 42 + this.input | single-file-component-3.vue:2:8:2:21 | v-html=dataA | +| single-file-component-4.vue:17:14:17:15 | 42 | single-file-component-4.vue:2:8:2:21 | v-html=dataA | +| single-file-component-5.vue:15:14:15:15 | 42 | single-file-component-5.vue:2:8:2:21 | v-html=dataA | | tst.js:100:18:100:19 | 42 | tst.js:102:20:102:29 | this.dataA | templateElement | compont-with-route.vue:1:1:3:11 |