mirror of
https://github.com/github/codeql.git
synced 2026-05-02 04:05:14 +02:00
Merge pull request #6574 from asgerf/js/vue-api-graphs
Approved by erik-krogh
This commit is contained in:
@@ -313,6 +313,19 @@ class AmdModule extends Module {
|
||||
name = pwn.getPropertyName()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getABulkExportedNode() {
|
||||
// Assigned to `module.exports` via the factory's `module` parameter
|
||||
exists(AbstractModuleObject m, DataFlow::PropWrite write |
|
||||
m.getModule() = this and
|
||||
write.getPropertyName() = "exports" and
|
||||
write.getBase().analyze().getAValue() = m and
|
||||
result = write.getRhs()
|
||||
)
|
||||
or
|
||||
// Returned from factory function
|
||||
result = getDefine().getModuleExpr().flow()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -177,6 +177,10 @@ module Closure {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getABulkExportedNode() {
|
||||
result = getExportsVariable().getAnAssignedExpr().flow()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -111,6 +111,23 @@ abstract class Module extends TopLevel {
|
||||
cached
|
||||
abstract DataFlow::Node getAnExportedValue(string name);
|
||||
|
||||
/**
|
||||
* Gets a value that is exported as the whole exports object of this module.
|
||||
*/
|
||||
cached
|
||||
DataFlow::Node getABulkExportedNode() { none() } // overridden in subclasses
|
||||
|
||||
/**
|
||||
* Gets the ES2015 `default` export from this module, or for other types of modules,
|
||||
* gets a bulk exported node.
|
||||
*
|
||||
* This can be used to determine which value a default-import will likely refer to,
|
||||
* as the interaction between different module types is not standardized.
|
||||
*/
|
||||
DataFlow::Node getDefaultOrBulkExport() {
|
||||
result = [getAnExportedValue("default"), getABulkExportedNode()]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the root folder relative to which the given import path (which must
|
||||
* appear in this module) is resolved.
|
||||
|
||||
@@ -99,6 +99,14 @@ class NodeModule extends Module {
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getABulkExportedNode() {
|
||||
exists(DataFlow::PropWrite write |
|
||||
write.getBase().asExpr() = getModuleVariable().getAnAccess() and
|
||||
write.getPropertyName() = "exports" and
|
||||
result = write.getRhs()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a symbol that the module object inherits from its prototypes. */
|
||||
private string getAnImplicitlyExportedSymbol() {
|
||||
exists(ExternalConstructor ec | ec = getPrototypeOfExportedExpr() |
|
||||
|
||||
@@ -164,11 +164,9 @@ private DataFlow::Node getAValueExportedByPackage() {
|
||||
* Gets an exported node from the module `mod`.
|
||||
*/
|
||||
private DataFlow::Node getAnExportFromModule(Module mod) {
|
||||
result.analyze().getAValue() = mod.(NodeModule).getAModuleExportsValue()
|
||||
or
|
||||
result = mod.(Closure::ClosureModule).getExportsVariable().getAnAssignedExpr().flow()
|
||||
or
|
||||
result.analyze().getAValue() = mod.(AmdModule).getDefine().getAModuleExportsValue()
|
||||
or
|
||||
result = mod.getAnExportedValue(_)
|
||||
or
|
||||
result = mod.getABulkExportedNode()
|
||||
or
|
||||
result.analyze().getAValue() = TAbstractModuleObject(mod)
|
||||
}
|
||||
|
||||
@@ -5,29 +5,69 @@
|
||||
import javascript
|
||||
|
||||
module Vue {
|
||||
/** The global variable `Vue`, as an API graph entry point. */
|
||||
private class GlobalVueEntryPoint extends API::EntryPoint {
|
||||
GlobalVueEntryPoint() { this = "VueEntryPoint" }
|
||||
|
||||
override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef("Vue") }
|
||||
|
||||
override DataFlow::Node getARhs() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A value exported from a `.vue` file.
|
||||
*
|
||||
* This `EntryPoint` is used by `SingleFileComponent::getOwnOptions()`.
|
||||
*/
|
||||
private class VueExportEntryPoint extends API::EntryPoint {
|
||||
VueExportEntryPoint() { this = "VueExportEntryPoint" }
|
||||
|
||||
override DataFlow::SourceNode getAUse() { none() }
|
||||
|
||||
override DataFlow::Node getARhs() {
|
||||
result = any(SingleFileComponent c).getModule().getDefaultOrBulkExport()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the `Vue` object.
|
||||
*/
|
||||
API::Node vueLibrary() {
|
||||
result = API::moduleImport("vue")
|
||||
or
|
||||
result = API::root().getASuccessor(any(GlobalVueEntryPoint e))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the 'Vue' object.
|
||||
*/
|
||||
DataFlow::SourceNode vue() {
|
||||
result = DataFlow::globalVarRef("Vue")
|
||||
DataFlow::SourceNode vue() { result = vueLibrary().getAnImmediateUse() }
|
||||
|
||||
/** An API node referring to a component or `Vue`. */
|
||||
private API::Node component() {
|
||||
result = vueLibrary()
|
||||
or
|
||||
result = DataFlow::moduleImport("vue")
|
||||
result = component().getMember("extend").getReturn()
|
||||
or
|
||||
result = vueLibrary().getMember("component").getReturn()
|
||||
or
|
||||
result = API::root().getASuccessor(any(VueFileImportEntryPoint e))
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `vue.extend`.
|
||||
* A call to `Vue.extend` or `extend` on a component.
|
||||
*/
|
||||
private class VueExtend extends DataFlow::CallNode {
|
||||
VueExtend() { this = vue().getAMemberCall("extend") }
|
||||
private class VueExtendCall extends API::CallNode {
|
||||
VueExtendCall() { this = component().getMember("extend").getACall() }
|
||||
}
|
||||
|
||||
/** A component created by an explicit or implicit call to `Vue.extend`. */
|
||||
private newtype TComponent =
|
||||
MkVueInstance(DataFlow::NewNode def) { def = vue().getAnInstantiation() } or
|
||||
MkExtendedVue(VueExtend extend) or
|
||||
MkExtendedInstance(VueExtend extend, DataFlow::NewNode sub) {
|
||||
sub = extend.getAnInstantiation()
|
||||
MkComponentExtension(VueExtendCall extend) or
|
||||
MkComponentInstantiation(API::NewNode sub) { sub = component().getAnInstantiation() } or
|
||||
MkComponentRegistration(API::CallNode def) {
|
||||
def = vueLibrary().getMember("component").getACall()
|
||||
} or
|
||||
MkComponentRegistration(DataFlow::CallNode def) { def = vue().getAMemberCall("component") } or
|
||||
MkSingleFileComponent(VueFile file)
|
||||
|
||||
/** Gets the name of a lifecycle hook method. */
|
||||
@@ -50,7 +90,7 @@ module Vue {
|
||||
/**
|
||||
* A class with a `@Component` decorator, making it usable as an "options" object in Vue.
|
||||
*/
|
||||
private class ClassComponent extends DataFlow::ClassNode {
|
||||
class ClassComponent extends DataFlow::ClassNode {
|
||||
DataFlow::Node decorator;
|
||||
|
||||
ClassComponent() {
|
||||
@@ -66,13 +106,11 @@ module Vue {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an option passed to the `@Component` decorator.
|
||||
* Gets the options object passed to the `@Component` decorator, if any.
|
||||
*
|
||||
* These options correspond to the options one would pass to `new Vue({...})` or similar.
|
||||
*/
|
||||
DataFlow::Node getDecoratorOption(string name) {
|
||||
result = decorator.(DataFlow::CallNode).getOptionArgument(0, name)
|
||||
}
|
||||
API::Node getDecoratorOptions() { result = decorator.(API::CallNode).getParameter(0) }
|
||||
}
|
||||
|
||||
private string memberKindVerb(DataFlow::MemberKind kind) {
|
||||
@@ -126,10 +164,43 @@ module Vue {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an API node referring to the component itself.
|
||||
*/
|
||||
API::Node getComponentRef() { none() } // overridden in subclass
|
||||
|
||||
/**
|
||||
* Gets an API node referring to the options passed to the Vue object,
|
||||
* such as the object literal `{...}` in `new Vue{{...})` or the default export of a single-file component.
|
||||
*/
|
||||
API::Node getOwnOptions() { none() } // overridden in subclass
|
||||
|
||||
/** Gets a component which is extended by this one. */
|
||||
Component getABaseComponent() {
|
||||
result.getComponentRef().getAUse() =
|
||||
getOwnOptions().getMember(["extends", "mixins"]).getARhs()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an API node referring to the options passed to the Vue object or one
|
||||
* of its base component.
|
||||
*/
|
||||
API::Node getOptions() {
|
||||
result = getOwnOptions()
|
||||
or
|
||||
result = getOwnOptions().getMember(["extends", "mixins"]).getAMember()
|
||||
or
|
||||
result = getABaseComponent().getOptions()
|
||||
or
|
||||
result = getAsClassComponent().getDecoratorOptions()
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED. Use `getOwnOptions().getARhs()`.
|
||||
*
|
||||
* Gets the options passed to the Vue object, such as the object literal `{...}` in `new Vue{{...})`
|
||||
* or the default export of a single-file component.
|
||||
*/
|
||||
DataFlow::Node getOwnOptionsObject() { none() } // overridden in subclasses
|
||||
deprecated DataFlow::Node getOwnOptionsObject() { result = getOwnOptions().getARhs() }
|
||||
|
||||
/**
|
||||
* Gets the class implementing this Vue component, if any.
|
||||
@@ -137,47 +208,28 @@ module Vue {
|
||||
* Specifically, this is a class annotated with `@Component` which flows to the options
|
||||
* object of this Vue component.
|
||||
*/
|
||||
ClassComponent getAsClassComponent() { result.flowsTo(getOwnOptionsObject()) }
|
||||
ClassComponent getAsClassComponent() { result = getOwnOptions().getAValueReachingRhs() }
|
||||
|
||||
/**
|
||||
* Gets the node for option `name` for this component, not including
|
||||
* those from extended objects and mixins.
|
||||
*/
|
||||
DataFlow::Node getOwnOption(string name) {
|
||||
result = getOwnOptionsObject().getALocalSource().getAPropertyWrite(name).getRhs()
|
||||
}
|
||||
DataFlow::Node getOwnOption(string name) { result = getOwnOptions().getMember(name).getARhs() }
|
||||
|
||||
/**
|
||||
* Gets the node for option `name` for this component, 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()
|
||||
)
|
||||
or
|
||||
result = getAsClassComponent().getDecoratorOption(name)
|
||||
}
|
||||
DataFlow::Node getOption(string name) { result = getOptions().getMember(name).getARhs() }
|
||||
|
||||
/**
|
||||
* Gets a source node flowing into the option `name` of this component, including those from
|
||||
* extended objects and mixins.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
DataFlow::SourceNode getOptionSource(string name) { result = getOption(name).getALocalSource() }
|
||||
DataFlow::SourceNode getOptionSource(string name) {
|
||||
result = getOptions().getMember(name).getAValueReachingRhs()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the template element used by this component, if any.
|
||||
@@ -188,15 +240,9 @@ module Vue {
|
||||
* Gets the node for the `data` option object of this component.
|
||||
*/
|
||||
DataFlow::Node getData() {
|
||||
exists(DataFlow::Node data | data = getOption("data") |
|
||||
result = data
|
||||
or
|
||||
// a constructor variant is available for all component definitions
|
||||
exists(DataFlow::FunctionNode f |
|
||||
f.flowsTo(data) and
|
||||
result = f.getAReturn()
|
||||
)
|
||||
)
|
||||
result = getOption("data")
|
||||
or
|
||||
result = getOptionSource("data").(DataFlow::FunctionNode).getReturnNode()
|
||||
or
|
||||
result = getAsClassComponent().getAReceiverNode()
|
||||
or
|
||||
@@ -241,37 +287,12 @@ module Vue {
|
||||
* Gets the function responding to changes to the given `propName`.
|
||||
*/
|
||||
DataFlow::FunctionNode getWatchHandler(string propName) {
|
||||
exists(DataFlow::SourceNode watcher | watcher = getWatch().getAPropertySource(propName) |
|
||||
result = watcher
|
||||
or
|
||||
result = watcher.getAPropertySource("handler")
|
||||
exists(API::Node propWatch |
|
||||
propWatch = getOptions().getMember("watch").getMember(propName) and
|
||||
result = [propWatch, propWatch.getMember("handler")].getAValueReachingRhs()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node for a member of the `methods` option of this component.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private DataFlow::SourceNode getAMethod() {
|
||||
result = getMethods().getAPropertySource()
|
||||
or
|
||||
result = getAsClassComponent().getAnInstanceMethod() and
|
||||
not result = getAsClassComponent().getInstanceMethod([lifecycleHookName(), "render", "data"])
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node for a member of the `computed` option of this component that matches `kind`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private DataFlow::SourceNode getAnAccessor(DataFlow::MemberKind kind) {
|
||||
result = getComputed().getAPropertySource() and kind = DataFlow::MemberKind::getter()
|
||||
or
|
||||
result = getComputed().getAPropertySource().getAPropertySource(memberKindVerb(kind))
|
||||
or
|
||||
result = getAsClassComponent().getAnInstanceMember(kind) and
|
||||
kind.isAccessor()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node for a member `name` of the `computed` option of this component that matches `kind`.
|
||||
*/
|
||||
@@ -301,27 +322,22 @@ module Vue {
|
||||
* Gets a node for a function that will be invoked with `this` bound to this component.
|
||||
*/
|
||||
DataFlow::FunctionNode getABoundFunction() {
|
||||
result = getAMethod()
|
||||
result = getOptions().getAMember+().getAValueReachingRhs()
|
||||
or
|
||||
result = getAnAccessor(_)
|
||||
or
|
||||
result = getALifecycleHook(_)
|
||||
or
|
||||
result = getOptionSource(_)
|
||||
or
|
||||
result = getOptionSource(_).getAPropertySource()
|
||||
result = getAsClassComponent().getAnInstanceMember()
|
||||
}
|
||||
|
||||
/** Gets an API node referring to an instance of this component. */
|
||||
API::Node getInstance() { result.getAnImmediateUse() = getABoundFunction().getReceiver() }
|
||||
|
||||
/** Gets a data flow node referring to an instance of this component. */
|
||||
DataFlow::SourceNode getAnInstanceRef() { result = getInstance().getAnImmediateUse() }
|
||||
|
||||
pragma[noinline]
|
||||
private DataFlow::PropWrite getAPropertyValueWrite(string name) {
|
||||
result = getData().getALocalSource().getAPropertyWrite(name)
|
||||
or
|
||||
result =
|
||||
getABoundFunction()
|
||||
.getALocalSource()
|
||||
.(DataFlow::FunctionNode)
|
||||
.getReceiver()
|
||||
.getAPropertyWrite(name)
|
||||
result = getAnInstanceRef().getAPropertyWrite(name)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -339,12 +355,12 @@ module Vue {
|
||||
}
|
||||
|
||||
/**
|
||||
* A Vue component from `new Vue({...})`.
|
||||
* A Vue component created implicitly at an invocation of form `new Vue({...})` or `new CustomComponent({...})`.
|
||||
*/
|
||||
class VueInstance extends Component, MkVueInstance {
|
||||
DataFlow::NewNode def;
|
||||
private class ComponentInstantiation extends Component, MkComponentInstantiation {
|
||||
API::NewNode def;
|
||||
|
||||
VueInstance() { this = MkVueInstance(def) }
|
||||
ComponentInstantiation() { this = MkComponentInstantiation(def) }
|
||||
|
||||
override string toString() { result = def.toString() }
|
||||
|
||||
@@ -354,18 +370,47 @@ module Vue {
|
||||
def.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
override DataFlow::Node getOwnOptionsObject() { result = def.getArgument(0) }
|
||||
override API::Node getComponentRef() {
|
||||
// The Vue.extend call is made in the Vue framework; there is no explicit reference
|
||||
// to the component in user code.
|
||||
none()
|
||||
}
|
||||
|
||||
override API::Node getOwnOptions() { result = def.getParameter(0) }
|
||||
|
||||
override Template::Element getTemplateElement() { none() }
|
||||
|
||||
override Component getABaseComponent() {
|
||||
result = Component.super.getABaseComponent()
|
||||
or
|
||||
result.getComponentRef().getAnInstantiation() = def
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An extended Vue from `Vue.extend({...})`.
|
||||
* DEPRECATED. Use `Vue::Component` instead.
|
||||
*
|
||||
* A Vue component from `new Vue({...})`.
|
||||
*/
|
||||
class ExtendedVue extends Component, MkExtendedVue {
|
||||
VueExtend extend;
|
||||
deprecated class VueInstance extends Component {
|
||||
VueInstance() {
|
||||
// restrict charpred to match original behavior
|
||||
this = MkComponentInstantiation(vueLibrary().getAnInstantiation())
|
||||
}
|
||||
}
|
||||
|
||||
ExtendedVue() { this = MkExtendedVue(extend) }
|
||||
/**
|
||||
* DEPRECATED. Use `Vue::ComponentExtension` or `Vue::Component` instead.
|
||||
*/
|
||||
deprecated class ExtendedVue = ComponentExtension;
|
||||
|
||||
/**
|
||||
* A component created via an explicit call to `Vue.extend({...})` or `CustomComponent.extend({...})`.
|
||||
*/
|
||||
class ComponentExtension extends Component, MkComponentExtension {
|
||||
VueExtendCall extend;
|
||||
|
||||
ComponentExtension() { this = MkComponentExtension(extend) }
|
||||
|
||||
override string toString() { result = extend.toString() }
|
||||
|
||||
@@ -375,44 +420,37 @@ module Vue {
|
||||
extend.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
override DataFlow::Node getOwnOptionsObject() { result = extend.getArgument(0) }
|
||||
override API::Node getComponentRef() { result = extend.getReturn() }
|
||||
|
||||
override API::Node getOwnOptions() { result = extend.getParameter(0) }
|
||||
|
||||
override Template::Element getTemplateElement() { none() }
|
||||
|
||||
override Component getABaseComponent() {
|
||||
result = Component.super.getABaseComponent()
|
||||
or
|
||||
result.getComponentRef().getMember("extend").getACall() = extend
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED. Use `Vue::Component` instead.
|
||||
*
|
||||
* An instance of an extended Vue, for example `instance` of `var Ext = Vue.extend({...}); var instance = new Ext({...})`.
|
||||
*/
|
||||
class ExtendedInstance extends Component, 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)
|
||||
deprecated class ExtendedInstance extends Component {
|
||||
ExtendedInstance() {
|
||||
// restrict charpred to match original behavior
|
||||
this =
|
||||
MkComponentInstantiation(vueLibrary().getMember("extend").getReturn().getAnInstantiation())
|
||||
}
|
||||
|
||||
override DataFlow::Node getOwnOptionsObject() { result = sub.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getOption(string name) {
|
||||
result = Component.super.getOption(name)
|
||||
or
|
||||
result = MkExtendedVue(extend).(ExtendedVue).getOption(name)
|
||||
}
|
||||
|
||||
override Template::Element getTemplateElement() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Vue component from `Vue.component("my-component", { ... })`.
|
||||
*/
|
||||
class ComponentRegistration extends Component, MkComponentRegistration {
|
||||
DataFlow::CallNode def;
|
||||
API::CallNode def;
|
||||
|
||||
ComponentRegistration() { this = MkComponentRegistration(def) }
|
||||
|
||||
@@ -424,11 +462,38 @@ module Vue {
|
||||
def.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
override DataFlow::Node getOwnOptionsObject() { result = def.getArgument(1) }
|
||||
override API::Node getComponentRef() {
|
||||
// The component can be obtained via 1-argument calls to `Vue.component()` with the
|
||||
// same name, but we don't model this at the moment.
|
||||
none()
|
||||
}
|
||||
|
||||
override API::Node getOwnOptions() { result = def.getParameter(1) }
|
||||
|
||||
override Template::Element getTemplateElement() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An import referring to a `.vue` file, seen as an API entry point.
|
||||
*
|
||||
* Concretely, such an import receives the Vue component generated from the .vue file,
|
||||
* not the actual exports of the script tag in the file.
|
||||
*
|
||||
* This entry point is used in `SingleFileComponent::getComponentRef()`.
|
||||
*/
|
||||
private class VueFileImportEntryPoint extends API::EntryPoint {
|
||||
VueFileImportEntryPoint() { this = "VueFileImportEntryPoint" }
|
||||
|
||||
override DataFlow::SourceNode getAUse() {
|
||||
exists(Import imprt |
|
||||
imprt.getImportedPath().resolve() instanceof VueFile and
|
||||
result = imprt.getImportedModuleNode()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getARhs() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A single file Vue component in a `.vue` file.
|
||||
*/
|
||||
@@ -455,39 +520,26 @@ module Vue {
|
||||
endcolumn = 0
|
||||
}
|
||||
|
||||
private Module getModule() {
|
||||
/** Gets the module defined by the `script` tag in this .vue file, if any. */
|
||||
Module getModule() {
|
||||
exists(HTML::ScriptElement elem |
|
||||
xmlElements(elem, _, _, _, file) and // Avoid materializing all of Locatable.getFile()
|
||||
result.getTopLevel() = elem.getScript()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getOwnOptionsObject() {
|
||||
exists(ExportDefaultDeclaration decl |
|
||||
decl.getTopLevel() = getModule() and
|
||||
result = DataFlow::valueNode(decl.getOperand())
|
||||
override API::Node getComponentRef() {
|
||||
// There is no explicit `new Vue()` call in .vue files, so instead get all the imports
|
||||
// of the .vue file.
|
||||
exists(Import imprt |
|
||||
imprt.getImportedPath().resolve() = file and
|
||||
result.getAnImmediateUse() = imprt.getImportedModuleNode()
|
||||
)
|
||||
}
|
||||
|
||||
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 API::Node getOwnOptions() {
|
||||
// Use the entry point generated by `VueExportEntryPoint`
|
||||
result.getARhs() = getModule().getDefaultOrBulkExport()
|
||||
}
|
||||
|
||||
override string toString() { result = file.toString() }
|
||||
@@ -500,20 +552,31 @@ module Vue {
|
||||
VueFile() { getExtension() = "vue" }
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node propStepPred(Component comp, string name) {
|
||||
result = comp.getAPropertyValue(name)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node propStepSucc(Component comp, string name) {
|
||||
result = comp.getAnInstanceRef().getAPropertyRead(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint propagating data flow edge through a Vue instance property.
|
||||
*/
|
||||
class InstanceHeapStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(Component i, string name, DataFlow::FunctionNode bound |
|
||||
bound.flowsTo(i.getABoundFunction()) and
|
||||
not bound.getFunction() instanceof ArrowFunctionExpr and
|
||||
succ = bound.getReceiver().getAPropertyRead(name) and
|
||||
pred = i.getAPropertyValue(name)
|
||||
private class PropStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate viewComponentStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(Component comp, string name |
|
||||
pred = propStepPred(comp, name) and
|
||||
succ = propStepSucc(comp, name)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED. Do not use. */
|
||||
deprecated class InstanceHeapStep = PropStep;
|
||||
|
||||
/**
|
||||
* A Vue `v-html` attribute.
|
||||
*/
|
||||
@@ -538,11 +601,11 @@ module Vue {
|
||||
* of `inst = new Vue({ ..., data: { prop: source } })`, if the
|
||||
* `div` element is part of the template for `inst`.
|
||||
*/
|
||||
class VHtmlSourceWrite extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(Vue::Component component, string expr, VHtmlAttribute attr |
|
||||
private class VHtmlAttributeStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate viewComponentStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(Component component, string expr, VHtmlAttribute attr |
|
||||
attr.getAttr().getRoot() =
|
||||
component.getTemplateElement().(Vue::Template::HtmlElement).getElement() and
|
||||
component.getTemplateElement().(Template::HtmlElement).getElement() and
|
||||
expr = attr.getAttr().getValue() and
|
||||
// only support for simple identifier expressions
|
||||
expr.regexpMatch("(?i)[a-z0-9_]+") and
|
||||
@@ -552,6 +615,11 @@ module Vue {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED. Do not use.
|
||||
*/
|
||||
deprecated class VHtmlSourceWrite = VHtmlAttributeStep;
|
||||
|
||||
/*
|
||||
* Provides classes for working with Vue templates.
|
||||
*/
|
||||
|
||||
@@ -5,38 +5,6 @@
|
||||
private import javascript
|
||||
private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
|
||||
|
||||
/** A minimal adapter for the `vue` model based on API nodes. */
|
||||
private module VueAPI {
|
||||
/** A value exported from a `.vue` file. */
|
||||
private class VueExportEntryPoint extends API::EntryPoint {
|
||||
VueExportEntryPoint() { this = "VueExportEntryPoint" }
|
||||
|
||||
override DataFlow::SourceNode getAUse() { none() }
|
||||
|
||||
override DataFlow::Node getARhs() {
|
||||
exists(Module mod |
|
||||
mod.getFile() instanceof Vue::VueFile and
|
||||
result = mod.getAnExportedValue("default")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An API node representing the object passed to the Vue constructor `new Vue({...})`
|
||||
* or equivalent.
|
||||
*/
|
||||
class VueConfigObject extends API::Node {
|
||||
VueConfigObject() { this.getARhs() = any(Vue::Component c).getOwnOptionsObject() }
|
||||
|
||||
/** Gets an API node representing `this` in the Vue component. */
|
||||
API::Node getAnInstanceRef() {
|
||||
result = getAMember().getReceiver()
|
||||
or
|
||||
result = getAMember().getAMember().getReceiver()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides classes and predicates for working with the `vuex` library.
|
||||
*/
|
||||
@@ -63,7 +31,7 @@ module Vuex {
|
||||
API::Node storeRef(string namespace) {
|
||||
result = vuex().getMember("Store").getInstance() and namespace = ""
|
||||
or
|
||||
result = any(VueAPI::VueConfigObject v).getAnInstanceRef().getMember("$store") and
|
||||
result = any(Vue::Component v).getInstance().getMember("$store") and
|
||||
namespace = ""
|
||||
or
|
||||
result =
|
||||
@@ -146,13 +114,13 @@ module Vuex {
|
||||
}
|
||||
|
||||
/** Gets the Vue component in which the generated functions are installed. */
|
||||
VueAPI::VueConfigObject getVueConfigObject() {
|
||||
Vue::Component getVueComponent() {
|
||||
exists(DataFlow::ObjectLiteralNode obj |
|
||||
obj.getASpreadProperty() = getReturn().getAUse() and
|
||||
result.getAMember().getARhs() = obj
|
||||
result.getOwnOptions().getAMember().getARhs() = obj
|
||||
)
|
||||
or
|
||||
result.getAMember().getARhs() = this
|
||||
result.getOwnOptions().getAMember().getARhs() = this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +135,7 @@ module Vuex {
|
||||
exists(MapHelperCall call, string localName |
|
||||
call.getHelperName() = helperName and
|
||||
call.hasMapping(localName, storeName) and
|
||||
result = call.getVueConfigObject().getAnInstanceRef().getMember(localName) and
|
||||
result = call.getVueComponent().getInstance().getMember(localName) and
|
||||
localName != "*"
|
||||
)
|
||||
}
|
||||
@@ -353,10 +321,10 @@ module Vuex {
|
||||
/**
|
||||
* Gets the `x` in `mapState({name: () => x})`.
|
||||
*/
|
||||
DataFlow::Node mapStateHelperPred(VueAPI::VueConfigObject vue, string name) {
|
||||
DataFlow::Node mapStateHelperPred(Vue::Component component, string name) {
|
||||
exists(MapHelperCall call |
|
||||
call.getHelperName() = "mapState" and
|
||||
vue = call.getVueConfigObject() and
|
||||
component = call.getVueComponent() and
|
||||
result = call.getLastParameter().getMember(name).getReturn().getARhs()
|
||||
)
|
||||
}
|
||||
@@ -366,9 +334,9 @@ module Vuex {
|
||||
* corresponding property access.
|
||||
*/
|
||||
predicate mapStateHelperStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(VueAPI::VueConfigObject vue, string name |
|
||||
pred = mapStateHelperPred(vue, name) and
|
||||
succ = pragma[only_bind_out](vue).getAnInstanceRef().getMember(name).getAnImmediateUse()
|
||||
exists(Vue::Component component, string name |
|
||||
pred = mapStateHelperPred(component, name) and
|
||||
succ = pragma[only_bind_out](component).getInstance().getMember(name).getAnImmediateUse()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ component_getAPropertyValue
|
||||
| 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:58:1:61:2 | new Vue ... 42 }\\n}) | fromSuper | tst.js:54:18:54:19 | 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 |
|
||||
@@ -28,6 +29,10 @@ component_getAPropertyValue
|
||||
| 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 |
|
||||
| tst.js:99:2:104:3 | new Vue ... \\t\\t}\\n\\t}) | dataA | tst.js:100:18:100:19 | 42 |
|
||||
| tst.js:107:12:109:2 | Vue.ext ... 23 }\\n}) | fromBase | tst.js:108:20:108:22 | 123 |
|
||||
| tst.js:110:16:112:2 | new Vue ... base\\n}) | fromBase | tst.js:108:20:108:22 | 123 |
|
||||
| tst.js:113:17:117:2 | base.ex ... 0\\n\\t}\\n}) | fromBase | tst.js:108:20:108:22 | 123 |
|
||||
| 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 } } |
|
||||
@@ -44,6 +49,7 @@ component_getOption
|
||||
| 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: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 } |
|
||||
@@ -58,6 +64,11 @@ component_getOption
|
||||
| tst.js:94:2:96:3 | new Vue ... f,\\n\\t}) | data | tst.js:95:9:95:9 | f |
|
||||
| tst.js:99:2:104:3 | new Vue ... \\t\\t}\\n\\t}) | data | tst.js:100:9:100:21 | { dataA: 42 } |
|
||||
| tst.js:99:2:104:3 | new Vue ... \\t\\t}\\n\\t}) | methods | tst.js:101:12:103:3 | {\\n\\t\\t\\tm: ... ; }\\n\\t\\t} |
|
||||
| tst.js:107:12:109:2 | Vue.ext ... 23 }\\n}) | data | tst.js:108:8:108:24 | { fromBase: 123 } |
|
||||
| tst.js:110:16:112:2 | new Vue ... base\\n}) | data | tst.js:108:8:108:24 | { fromBase: 123 } |
|
||||
| tst.js:110:16:112:2 | new Vue ... base\\n}) | extends | tst.js:111:11:111:14 | base |
|
||||
| tst.js:113:17:117:2 | base.ex ... 0\\n\\t}\\n}) | data | tst.js:108:8:108:24 | { fromBase: 123 } |
|
||||
| tst.js:113:17:117:2 | base.ex ... 0\\n\\t}\\n}) | data | tst.js:114:8:116:2 | {\\n\\t\\tfro ... 100\\n\\t} |
|
||||
component
|
||||
| compont-with-route.vue:0:0:0:0 | compont-with-route.vue |
|
||||
| single-component-file-1.vue:0:0:0:0 | single-component-file-1.vue |
|
||||
@@ -80,13 +91,16 @@ component
|
||||
| tst.js:85:1:87:2 | new Vue ... e; }\\n}) |
|
||||
| tst.js:94:2:96:3 | new Vue ... f,\\n\\t}) |
|
||||
| tst.js:99:2:104:3 | new Vue ... \\t\\t}\\n\\t}) |
|
||||
instance_heapStep
|
||||
| Unit | compont-with-route.vue:31:14:31:34 | this.$r ... ery.foo | compont-with-route.vue:2:8:2:21 | v-html=dataA |
|
||||
| Unit | single-component-file-1.vue:6:40:6:41 | 42 | single-component-file-1.vue:2:8:2:21 | v-html=dataA |
|
||||
| Unit | single-file-component-3-script.js:4:37:4:38 | 42 | single-file-component-3.vue:2:8:2:21 | v-html=dataA |
|
||||
| Unit | single-file-component-4.vue:15:14:15:15 | 42 | single-file-component-4.vue:2:8:2:21 | v-html=dataA |
|
||||
| Unit | single-file-component-5.vue:13:14:13:15 | 42 | single-file-component-5.vue:2:8:2:21 | v-html=dataA |
|
||||
| Unit | tst.js:100:18:100:19 | 42 | tst.js:102:20:102:29 | this.dataA |
|
||||
| tst.js:107:12:109:2 | Vue.ext ... 23 }\\n}) |
|
||||
| tst.js:110:16:112:2 | new Vue ... base\\n}) |
|
||||
| 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 |
|
||||
| tst.js:100:18:100:19 | 42 | tst.js:102:20:102:29 | this.dataA |
|
||||
templateElement
|
||||
| compont-with-route.vue:1:1:3:11 | <template>...</> |
|
||||
| compont-with-route.vue:2:5:51:9 | <p>...</> |
|
||||
@@ -112,13 +126,6 @@ templateElement
|
||||
| single-file-component-5.vue:2:5:18:9 | <p>...</> |
|
||||
| single-file-component-5.vue:4:1:16:9 | <script>...</> |
|
||||
| single-file-component-5.vue:17:1:18:8 | <style>...</> |
|
||||
vhtmlSourceWrite
|
||||
| Unit | compont-with-route.vue:31:14:31:34 | this.$r ... ery.foo | compont-with-route.vue:2:8:2:21 | v-html=dataA |
|
||||
| Unit | single-component-file-1.vue:6:40:6:41 | 42 | single-component-file-1.vue:2:8:2:21 | v-html=dataA |
|
||||
| Unit | single-file-component-3-script.js:4:37:4:38 | 42 | single-file-component-3.vue:2:8:2:21 | v-html=dataA |
|
||||
| Unit | single-file-component-4.vue:15:14:15:15 | 42 | single-file-component-4.vue:2:8:2:21 | v-html=dataA |
|
||||
| Unit | single-file-component-5.vue:13:14:13:15 | 42 | single-file-component-5.vue:2:8:2:21 | v-html=dataA |
|
||||
| Unit | tst.js:100:18:100:19 | 42 | tst.js:102:20:102:29 | this.dataA |
|
||||
xssSink
|
||||
| compont-with-route.vue:2:8:2:21 | v-html=dataA |
|
||||
| single-component-file-1.vue:2:8:2:21 | v-html=dataA |
|
||||
|
||||
@@ -11,18 +11,12 @@ query predicate component_getOption(Vue::Component c, string name, DataFlow::Nod
|
||||
|
||||
query predicate component(Vue::Component c) { any() }
|
||||
|
||||
query predicate instance_heapStep(
|
||||
Vue::InstanceHeapStep step, DataFlow::Node pred, DataFlow::Node succ
|
||||
) {
|
||||
step.step(pred, succ)
|
||||
query predicate viewComponentStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
TaintTracking::viewComponentStep(pred, succ)
|
||||
}
|
||||
|
||||
query predicate templateElement(Vue::Template::Element template) { any() }
|
||||
|
||||
query predicate vhtmlSourceWrite(Vue::VHtmlSourceWrite w, DataFlow::Node pred, DataFlow::Node succ) {
|
||||
w.step(pred, succ)
|
||||
}
|
||||
|
||||
query predicate xssSink(DomBasedXss::Sink s) { any() }
|
||||
|
||||
query RemoteFlowSource remoteFlowSource() { any() }
|
||||
|
||||
@@ -103,3 +103,15 @@ new Vue({
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let base = Vue.extend({
|
||||
data: { fromBase: 123 }
|
||||
});
|
||||
let subclass = new Vue({
|
||||
extends: base
|
||||
});
|
||||
let subclass2 = base.extend({
|
||||
data: {
|
||||
fromSubclass2: 100
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user