diff options
Diffstat (limited to 'js/public/PropertyDescriptor.h')
-rw-r--r-- | js/public/PropertyDescriptor.h | 503 |
1 files changed, 503 insertions, 0 deletions
diff --git a/js/public/PropertyDescriptor.h b/js/public/PropertyDescriptor.h new file mode 100644 index 0000000000..d298aa4274 --- /dev/null +++ b/js/public/PropertyDescriptor.h @@ -0,0 +1,503 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Property descriptors and flags. */ + +#ifndef js_PropertyDescriptor_h +#define js_PropertyDescriptor_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_ASSERT_IF +#include "mozilla/EnumSet.h" // mozilla::EnumSet +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include <stddef.h> // size_t +#include <stdint.h> // uint8_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/Id.h" // jsid +#include "js/RootingAPI.h" // JS::Handle, js::{,Mutable}WrappedPtrOperations +#include "js/Value.h" // JS::Value + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSObject; +class JS_PUBLIC_API JSTracer; + +/* Property attributes, set in JSPropertySpec and passed to API functions. + * + * The data structure in which some of these values are stored only uses a + * uint8_t to store the relevant information. Proceed with caution if trying to + * reorder or change the the first byte worth of flags. + */ + +/** The property is visible in for/in loops. */ +static constexpr uint8_t JSPROP_ENUMERATE = 0x01; + +/** + * The property is non-writable. This flag is only valid for data properties. + */ +static constexpr uint8_t JSPROP_READONLY = 0x02; + +/** + * The property is non-configurable: it can't be deleted, and if it's an + * accessor descriptor, its getter and setter can't be changed. + */ +static constexpr uint8_t JSPROP_PERMANENT = 0x04; + +/** + * Resolve hooks and enumerate hooks must pass this flag when calling + * JS_Define* APIs to reify lazily-defined properties. + * + * JSPROP_RESOLVING is used only with property-defining APIs. It tells the + * engine to skip the resolve hook when performing the lookup at the beginning + * of property definition. This keeps the resolve hook from accidentally + * triggering itself: unchecked recursion. + * + * For enumerate hooks, triggering the resolve hook would be merely silly, not + * fatal, except in some cases involving non-configurable properties. + */ +static constexpr unsigned JSPROP_RESOLVING = 0x08; + +/* (higher flags are unused; add to JSPROP_FLAGS_MASK if ever defined) */ + +static constexpr unsigned JSPROP_FLAGS_MASK = + JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_RESOLVING; + +namespace JS { + +// 6.1.7.1 Property Attributes +enum class PropertyAttribute : uint8_t { + // The descriptor is [[Configurable]] := true. + Configurable, + + // The descriptor is [[Enumerable]] := true. + Enumerable, + + // The descriptor is [[Writable]] := true. Only valid for data descriptors. + Writable +}; + +class PropertyAttributes : public mozilla::EnumSet<PropertyAttribute> { + // Re-use all EnumSet constructors. + using mozilla::EnumSet<PropertyAttribute>::EnumSet; + + public: + bool configurable() const { + return contains(PropertyAttribute::Configurable); + } + bool enumerable() const { return contains(PropertyAttribute::Enumerable); } + bool writable() const { return contains(PropertyAttribute::Writable); } +}; + +/** + * A structure that represents a property on an object, or the absence of a + * property. Use {,Mutable}Handle<PropertyDescriptor> to interact with + * instances of this structure rather than interacting directly with member + * fields. + */ +class JS_PUBLIC_API PropertyDescriptor { + private: + bool hasConfigurable_ : 1; + bool configurable_ : 1; + + bool hasEnumerable_ : 1; + bool enumerable_ : 1; + + bool hasWritable_ : 1; + bool writable_ : 1; + + bool hasValue_ : 1; + bool hasGetter_ : 1; + bool hasSetter_ : 1; + + bool resolving_ : 1; + + JSObject* getter_; + JSObject* setter_; + Value value_; + + public: + PropertyDescriptor() + : hasConfigurable_(false), + configurable_(false), + hasEnumerable_(false), + enumerable_(false), + hasWritable_(false), + writable_(false), + hasValue_(false), + hasGetter_(false), + hasSetter_(false), + resolving_(false), + getter_(nullptr), + setter_(nullptr), + value_(UndefinedValue()) {} + + void trace(JSTracer* trc); + + // Construct a new complete DataDescriptor. + static PropertyDescriptor Data(const Value& value, + PropertyAttributes attributes = {}) { + PropertyDescriptor desc; + desc.setConfigurable(attributes.configurable()); + desc.setEnumerable(attributes.enumerable()); + desc.setWritable(attributes.writable()); + desc.setValue(value); + desc.assertComplete(); + return desc; + } + + // This constructor is only provided for legacy code! + static PropertyDescriptor Data(const Value& value, unsigned attrs) { + MOZ_ASSERT((attrs & ~(JSPROP_PERMANENT | JSPROP_ENUMERATE | + JSPROP_READONLY | JSPROP_RESOLVING)) == 0); + + PropertyDescriptor desc; + desc.setConfigurable(!(attrs & JSPROP_PERMANENT)); + desc.setEnumerable(attrs & JSPROP_ENUMERATE); + desc.setWritable(!(attrs & JSPROP_READONLY)); + desc.setValue(value); + desc.setResolving(attrs & JSPROP_RESOLVING); + desc.assertComplete(); + return desc; + } + + // Construct a new complete AccessorDescriptor. + // Note: This means JSPROP_GETTER and JSPROP_SETTER are always set. + static PropertyDescriptor Accessor(JSObject* getter, JSObject* setter, + PropertyAttributes attributes = {}) { + MOZ_ASSERT(!attributes.writable()); + + PropertyDescriptor desc; + desc.setConfigurable(attributes.configurable()); + desc.setEnumerable(attributes.enumerable()); + desc.setGetter(getter); + desc.setSetter(setter); + desc.assertComplete(); + return desc; + } + + // This constructor is only provided for legacy code! + static PropertyDescriptor Accessor(JSObject* getter, JSObject* setter, + unsigned attrs) { + MOZ_ASSERT((attrs & ~(JSPROP_PERMANENT | JSPROP_ENUMERATE | + JSPROP_RESOLVING)) == 0); + + PropertyDescriptor desc; + desc.setConfigurable(!(attrs & JSPROP_PERMANENT)); + desc.setEnumerable(attrs & JSPROP_ENUMERATE); + desc.setGetter(getter); + desc.setSetter(setter); + desc.setResolving(attrs & JSPROP_RESOLVING); + desc.assertComplete(); + return desc; + } + + static PropertyDescriptor Accessor(mozilla::Maybe<JSObject*> getter, + mozilla::Maybe<JSObject*> setter, + unsigned attrs) { + MOZ_ASSERT((attrs & ~(JSPROP_PERMANENT | JSPROP_ENUMERATE | + JSPROP_RESOLVING)) == 0); + + PropertyDescriptor desc; + desc.setConfigurable(!(attrs & JSPROP_PERMANENT)); + desc.setEnumerable(attrs & JSPROP_ENUMERATE); + if (getter) { + desc.setGetter(*getter); + } + if (setter) { + desc.setSetter(*setter); + } + desc.setResolving(attrs & JSPROP_RESOLVING); + desc.assertValid(); + return desc; + } + + // Construct a new incomplete empty PropertyDescriptor. + // Using the spec syntax this would be { }. Specific fields like [[Value]] + // can be added with e.g., setValue. + static PropertyDescriptor Empty() { + PropertyDescriptor desc; + desc.assertValid(); + MOZ_ASSERT(!desc.hasConfigurable() && !desc.hasEnumerable() && + !desc.hasWritable() && !desc.hasValue() && !desc.hasGetter() && + !desc.hasSetter()); + return desc; + } + + public: + bool isAccessorDescriptor() const { + MOZ_ASSERT_IF(hasGetter_ || hasSetter_, !isDataDescriptor()); + return hasGetter_ || hasSetter_; + } + bool isGenericDescriptor() const { + return !isAccessorDescriptor() && !isDataDescriptor(); + } + bool isDataDescriptor() const { + MOZ_ASSERT_IF(hasWritable_ || hasValue_, !isAccessorDescriptor()); + return hasWritable_ || hasValue_; + } + + bool hasConfigurable() const { return hasConfigurable_; } + bool configurable() const { + MOZ_ASSERT(hasConfigurable()); + return configurable_; + } + void setConfigurable(bool configurable) { + hasConfigurable_ = true; + configurable_ = configurable; + } + + bool hasEnumerable() const { return hasEnumerable_; } + bool enumerable() const { + MOZ_ASSERT(hasEnumerable()); + return enumerable_; + } + void setEnumerable(bool enumerable) { + hasEnumerable_ = true; + enumerable_ = enumerable; + } + + bool hasValue() const { return hasValue_; } + Value value() const { + MOZ_ASSERT(hasValue()); + return value_; + } + void setValue(const Value& v) { + MOZ_ASSERT(!isAccessorDescriptor()); + hasValue_ = true; + value_ = v; + } + + bool hasWritable() const { return hasWritable_; } + bool writable() const { + MOZ_ASSERT(hasWritable()); + return writable_; + } + void setWritable(bool writable) { + MOZ_ASSERT(!isAccessorDescriptor()); + hasWritable_ = true; + writable_ = writable; + } + + bool hasGetter() const { return hasGetter_; } + JSObject* getter() const { + MOZ_ASSERT(hasGetter()); + return getter_; + } + void setGetter(JSObject* obj) { + MOZ_ASSERT(!isDataDescriptor()); + hasGetter_ = true; + getter_ = obj; + } + + bool hasSetter() const { return hasSetter_; } + JSObject* setter() const { + MOZ_ASSERT(hasSetter()); + return setter_; + } + void setSetter(JSObject* obj) { + MOZ_ASSERT(!isDataDescriptor()); + hasSetter_ = true; + setter_ = obj; + } + + // Non-standard flag, which is set when defining properties in a resolve hook. + bool resolving() const { return resolving_; } + void setResolving(bool resolving) { resolving_ = resolving; } + + Value* valueDoNotUse() { return &value_; } + Value const* valueDoNotUse() const { return &value_; } + JSObject** getterDoNotUse() { return &getter_; } + JSObject* const* getterDoNotUse() const { return &getter_; } + void setGetterDoNotUse(JSObject* obj) { getter_ = obj; } + JSObject** setterDoNotUse() { return &setter_; } + JSObject* const* setterDoNotUse() const { return &setter_; } + void setSetterDoNotUse(JSObject* obj) { setter_ = obj; } + + void assertValid() const { +#ifdef DEBUG + if (isAccessorDescriptor()) { + MOZ_ASSERT(!hasWritable_); + MOZ_ASSERT(!hasValue_); + } else { + MOZ_ASSERT(isGenericDescriptor() || isDataDescriptor()); + MOZ_ASSERT(!hasGetter_); + MOZ_ASSERT(!hasSetter_); + } + + MOZ_ASSERT_IF(!hasConfigurable_, !configurable_); + MOZ_ASSERT_IF(!hasEnumerable_, !enumerable_); + MOZ_ASSERT_IF(!hasWritable_, !writable_); + MOZ_ASSERT_IF(!hasValue_, value_.isUndefined()); + MOZ_ASSERT_IF(!hasGetter_, !getter_); + MOZ_ASSERT_IF(!hasSetter_, !setter_); + + MOZ_ASSERT_IF(resolving_, !isGenericDescriptor()); +#endif + } + + void assertComplete() const { +#ifdef DEBUG + assertValid(); + MOZ_ASSERT(hasConfigurable()); + MOZ_ASSERT(hasEnumerable()); + MOZ_ASSERT(!isGenericDescriptor()); + MOZ_ASSERT_IF(isDataDescriptor(), hasValue() && hasWritable()); + MOZ_ASSERT_IF(isAccessorDescriptor(), hasGetter() && hasSetter()); +#endif + } +}; + +} // namespace JS + +namespace js { + +template <typename Wrapper> +class WrappedPtrOperations<JS::PropertyDescriptor, Wrapper> { + const JS::PropertyDescriptor& desc() const { + return static_cast<const Wrapper*>(this)->get(); + } + + public: + bool isAccessorDescriptor() const { return desc().isAccessorDescriptor(); } + bool isGenericDescriptor() const { return desc().isGenericDescriptor(); } + bool isDataDescriptor() const { return desc().isDataDescriptor(); } + + bool hasConfigurable() const { return desc().hasConfigurable(); } + bool configurable() const { return desc().configurable(); } + + bool hasEnumerable() const { return desc().hasEnumerable(); } + bool enumerable() const { return desc().enumerable(); } + + bool hasValue() const { return desc().hasValue(); } + JS::Handle<JS::Value> value() const { + MOZ_ASSERT(hasValue()); + return JS::Handle<JS::Value>::fromMarkedLocation(desc().valueDoNotUse()); + } + + bool hasWritable() const { return desc().hasWritable(); } + bool writable() const { return desc().writable(); } + + bool hasGetter() const { return desc().hasGetter(); } + JS::Handle<JSObject*> getter() const { + MOZ_ASSERT(hasGetter()); + return JS::Handle<JSObject*>::fromMarkedLocation(desc().getterDoNotUse()); + } + bool hasSetter() const { return desc().hasSetter(); } + JS::Handle<JSObject*> setter() const { + MOZ_ASSERT(hasSetter()); + return JS::Handle<JSObject*>::fromMarkedLocation(desc().setterDoNotUse()); + } + + bool resolving() const { return desc().resolving(); } + + void assertValid() const { desc().assertValid(); } + void assertComplete() const { desc().assertComplete(); } +}; + +template <typename Wrapper> +class MutableWrappedPtrOperations<JS::PropertyDescriptor, Wrapper> + : public js::WrappedPtrOperations<JS::PropertyDescriptor, Wrapper> { + JS::PropertyDescriptor& desc() { return static_cast<Wrapper*>(this)->get(); } + + public: + JS::MutableHandle<JS::Value> value() { + MOZ_ASSERT(desc().hasValue()); + return JS::MutableHandle<JS::Value>::fromMarkedLocation( + desc().valueDoNotUse()); + } + void setValue(JS::Handle<JS::Value> v) { desc().setValue(v); } + + void setConfigurable(bool configurable) { + desc().setConfigurable(configurable); + } + void setEnumerable(bool enumerable) { desc().setEnumerable(enumerable); } + void setWritable(bool writable) { desc().setWritable(writable); } + + void setGetter(JSObject* obj) { desc().setGetter(obj); } + void setSetter(JSObject* obj) { desc().setSetter(obj); } + + JS::MutableHandle<JSObject*> getter() { + MOZ_ASSERT(desc().hasGetter()); + return JS::MutableHandle<JSObject*>::fromMarkedLocation( + desc().getterDoNotUse()); + } + JS::MutableHandle<JSObject*> setter() { + MOZ_ASSERT(desc().hasSetter()); + return JS::MutableHandle<JSObject*>::fromMarkedLocation( + desc().setterDoNotUse()); + } + + void setResolving(bool resolving) { desc().setResolving(resolving); } +}; + +} // namespace js + +/** + * Get a description of one of obj's own properties. If no such property exists + * on obj, return true with desc.object() set to null. + * + * Implements: ES6 [[GetOwnProperty]] internal method. + */ +extern JS_PUBLIC_API bool JS_GetOwnPropertyDescriptorById( + JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc); + +extern JS_PUBLIC_API bool JS_GetOwnPropertyDescriptor( + JSContext* cx, JS::Handle<JSObject*> obj, const char* name, + JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc); + +extern JS_PUBLIC_API bool JS_GetOwnUCPropertyDescriptor( + JSContext* cx, JS::Handle<JSObject*> obj, const char16_t* name, + size_t namelen, + JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc); + +/** + * DEPRECATED + * + * Like JS_GetOwnPropertyDescriptorById, but also searches the prototype chain + * if no own property is found directly on obj. The object on which the + * property is found is returned in holder. If the property is not found + * on the prototype chain, then desc is Nothing. + */ +extern JS_PUBLIC_API bool JS_GetPropertyDescriptorById( + JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc, + JS::MutableHandle<JSObject*> holder); + +extern JS_PUBLIC_API bool JS_GetPropertyDescriptor( + JSContext* cx, JS::Handle<JSObject*> obj, const char* name, + JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc, + JS::MutableHandle<JSObject*> holder); + +extern JS_PUBLIC_API bool JS_GetUCPropertyDescriptor( + JSContext* cx, JS::Handle<JSObject*> obj, const char16_t* name, + size_t namelen, + JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc, + JS::MutableHandle<JSObject*> holder); + +namespace JS { + +// https://tc39.es/ecma262/#sec-topropertydescriptor +// https://tc39.es/ecma262/#sec-completepropertydescriptor +// +// Implements ToPropertyDescriptor combined with CompletePropertyDescriptor, +// if the former is successful. +extern JS_PUBLIC_API bool ToCompletePropertyDescriptor( + JSContext* cx, Handle<Value> descriptor, + MutableHandle<PropertyDescriptor> desc); + +/* + * ES6 draft rev 32 (2015 Feb 2) 6.2.4.4 FromPropertyDescriptor(Desc). + * + * If desc.isNothing(), then vp is set to undefined. + */ +extern JS_PUBLIC_API bool FromPropertyDescriptor( + JSContext* cx, Handle<mozilla::Maybe<PropertyDescriptor>> desc, + MutableHandle<Value> vp); + +} // namespace JS + +#endif /* js_PropertyDescriptor_h */ |