/* -*- 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 // size_t #include // 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 { // Re-use all EnumSet constructors. using mozilla::EnumSet::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 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 getter, mozilla::Maybe 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 class WrappedPtrOperations { const JS::PropertyDescriptor& desc() const { return static_cast(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 value() const { MOZ_ASSERT(hasValue()); return JS::Handle::fromMarkedLocation(desc().valueDoNotUse()); } bool hasWritable() const { return desc().hasWritable(); } bool writable() const { return desc().writable(); } bool hasGetter() const { return desc().hasGetter(); } JS::Handle getter() const { MOZ_ASSERT(hasGetter()); return JS::Handle::fromMarkedLocation(desc().getterDoNotUse()); } bool hasSetter() const { return desc().hasSetter(); } JS::Handle setter() const { MOZ_ASSERT(hasSetter()); return JS::Handle::fromMarkedLocation(desc().setterDoNotUse()); } bool resolving() const { return desc().resolving(); } void assertValid() const { desc().assertValid(); } void assertComplete() const { desc().assertComplete(); } }; template class MutableWrappedPtrOperations : public js::WrappedPtrOperations { JS::PropertyDescriptor& desc() { return static_cast(this)->get(); } public: JS::MutableHandle value() { MOZ_ASSERT(desc().hasValue()); return JS::MutableHandle::fromMarkedLocation( desc().valueDoNotUse()); } void setValue(JS::Handle 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 getter() { MOZ_ASSERT(desc().hasGetter()); return JS::MutableHandle::fromMarkedLocation( desc().getterDoNotUse()); } JS::MutableHandle setter() { MOZ_ASSERT(desc().hasSetter()); return JS::MutableHandle::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 obj, JS::Handle id, JS::MutableHandle> desc); extern JS_PUBLIC_API bool JS_GetOwnPropertyDescriptor( JSContext* cx, JS::Handle obj, const char* name, JS::MutableHandle> desc); extern JS_PUBLIC_API bool JS_GetOwnUCPropertyDescriptor( JSContext* cx, JS::Handle obj, const char16_t* name, size_t namelen, JS::MutableHandle> 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 obj, JS::Handle id, JS::MutableHandle> desc, JS::MutableHandle holder); extern JS_PUBLIC_API bool JS_GetPropertyDescriptor( JSContext* cx, JS::Handle obj, const char* name, JS::MutableHandle> desc, JS::MutableHandle holder); extern JS_PUBLIC_API bool JS_GetUCPropertyDescriptor( JSContext* cx, JS::Handle obj, const char16_t* name, size_t namelen, JS::MutableHandle> desc, JS::MutableHandle holder); namespace JS { extern JS_PUBLIC_API bool ObjectToCompletePropertyDescriptor( JSContext* cx, Handle obj, Handle descriptor, MutableHandle 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> desc, MutableHandle vp); } // namespace JS #endif /* js_PropertyDescriptor_h */