/* -*- 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 // uint8_t #include "jstypes.h" // JS_PUBLIC_API #include "js/Class.h" // JS{Getter,Setter}Op #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 when neither * JSPROP_GETTER nor JSPROP_SETTER is set. */ 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; /* (0x08 is unused; add to JSPROP_FLAGS_MASK if ever defined) */ /** The property has a getter function. */ static constexpr uint8_t JSPROP_GETTER = 0x10; /** The property has a setter function. */ static constexpr uint8_t JSPROP_SETTER = 0x20; /* (0x40 is unused; add to JSPROP_FLAGS_MASK if ever defined) */ /** A bit for internal JS engine use only. */ static constexpr uint8_t JSPROP_INTERNAL_USE_BIT = 0x80; /* (0x1000 is unused; add to JSPROP_FLAGS_MASK if ever defined) */ /** * 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 = 0x2000; /** * When redefining an existing property, ignore the value of the * JSPROP_ENUMERATE flag. This flag is ignored in other situations. */ static constexpr unsigned JSPROP_IGNORE_ENUMERATE = 0x4000; /** * When redefining an existing property, ignore the value of the JSPROP_READONLY * flag. This flag is ignored in other situations. */ static constexpr unsigned JSPROP_IGNORE_READONLY = 0x8000; /** * When redefining an existing property, ignore the value of the * JSPROP_PERMANENT flag. This flag is ignored in other situations. */ static constexpr unsigned JSPROP_IGNORE_PERMANENT = 0x10000; /** * When redefining an existing property, ignore the Value in the descriptor. * This flag is ignored in other situations. */ static constexpr unsigned JSPROP_IGNORE_VALUE = 0x20000; /* (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_GETTER | JSPROP_SETTER | JSPROP_INTERNAL_USE_BIT | JSPROP_RESOLVING | JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_READONLY | JSPROP_IGNORE_PERMANENT | JSPROP_IGNORE_VALUE; namespace JS { /** * 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. */ struct JS_PUBLIC_API PropertyDescriptor { JSObject* obj = nullptr; unsigned attrs = 0; JSGetterOp getter = nullptr; JSSetterOp setter = nullptr; Value value; PropertyDescriptor() = default; void trace(JSTracer* trc); }; } // namespace JS namespace js { template class WrappedPtrOperations { const JS::PropertyDescriptor& desc() const { return static_cast(this)->get(); } bool has(unsigned bit) const { MOZ_ASSERT(bit != 0); MOZ_ASSERT((bit & (bit - 1)) == 0); // only a single bit return (desc().attrs & bit) != 0; } bool hasAny(unsigned bits) const { return (desc().attrs & bits) != 0; } bool hasAll(unsigned bits) const { return (desc().attrs & bits) == bits; } public: // Descriptors with JSGetterOp/JSSetterOp are considered data // descriptors. It's complicated. bool isAccessorDescriptor() const { return hasAny(JSPROP_GETTER | JSPROP_SETTER); } bool isGenericDescriptor() const { return (desc().attrs & (JSPROP_GETTER | JSPROP_SETTER | JSPROP_IGNORE_READONLY | JSPROP_IGNORE_VALUE)) == (JSPROP_IGNORE_READONLY | JSPROP_IGNORE_VALUE); } bool isDataDescriptor() const { return !isAccessorDescriptor() && !isGenericDescriptor(); } bool hasConfigurable() const { return !has(JSPROP_IGNORE_PERMANENT); } bool configurable() const { MOZ_ASSERT(hasConfigurable()); return !has(JSPROP_PERMANENT); } bool hasEnumerable() const { return !has(JSPROP_IGNORE_ENUMERATE); } bool enumerable() const { MOZ_ASSERT(hasEnumerable()); return has(JSPROP_ENUMERATE); } bool hasValue() const { return !isAccessorDescriptor() && !has(JSPROP_IGNORE_VALUE); } JS::HandleValue value() const { return JS::Handle::fromMarkedLocation(&desc().value); } bool hasWritable() const { return !isAccessorDescriptor() && !has(JSPROP_IGNORE_READONLY); } bool writable() const { MOZ_ASSERT(hasWritable()); return !has(JSPROP_READONLY); } bool hasGetterObject() const { return has(JSPROP_GETTER); } JS::Handle getterObject() const { MOZ_ASSERT(hasGetterObject()); return JS::Handle::fromMarkedLocation( reinterpret_cast(&desc().getter)); } bool hasSetterObject() const { return has(JSPROP_SETTER); } JS::Handle setterObject() const { MOZ_ASSERT(hasSetterObject()); return JS::Handle::fromMarkedLocation( reinterpret_cast(&desc().setter)); } bool hasGetterOrSetter() const { return desc().getter || desc().setter; } JS::Handle object() const { return JS::Handle::fromMarkedLocation(&desc().obj); } unsigned attributes() const { return desc().attrs; } JSGetterOp getter() const { return desc().getter; } JSSetterOp setter() const { return desc().setter; } void assertValid() const { #ifdef DEBUG MOZ_ASSERT( (attributes() & ~(JSPROP_ENUMERATE | JSPROP_IGNORE_ENUMERATE | JSPROP_PERMANENT | JSPROP_IGNORE_PERMANENT | JSPROP_READONLY | JSPROP_IGNORE_READONLY | JSPROP_IGNORE_VALUE | JSPROP_GETTER | JSPROP_SETTER | JSPROP_RESOLVING | JSPROP_INTERNAL_USE_BIT)) == 0); MOZ_ASSERT(!hasAll(JSPROP_IGNORE_ENUMERATE | JSPROP_ENUMERATE)); MOZ_ASSERT(!hasAll(JSPROP_IGNORE_PERMANENT | JSPROP_PERMANENT)); if (isAccessorDescriptor()) { MOZ_ASSERT(!has(JSPROP_READONLY)); MOZ_ASSERT(!has(JSPROP_IGNORE_READONLY)); MOZ_ASSERT(!has(JSPROP_IGNORE_VALUE)); MOZ_ASSERT(!has(JSPROP_INTERNAL_USE_BIT)); MOZ_ASSERT(value().isUndefined()); MOZ_ASSERT_IF(!has(JSPROP_GETTER), !getter()); MOZ_ASSERT_IF(!has(JSPROP_SETTER), !setter()); } else { MOZ_ASSERT(!hasAll(JSPROP_IGNORE_READONLY | JSPROP_READONLY)); MOZ_ASSERT_IF(has(JSPROP_IGNORE_VALUE), value().isUndefined()); } MOZ_ASSERT_IF(has(JSPROP_RESOLVING), !has(JSPROP_IGNORE_ENUMERATE)); MOZ_ASSERT_IF(has(JSPROP_RESOLVING), !has(JSPROP_IGNORE_PERMANENT)); MOZ_ASSERT_IF(has(JSPROP_RESOLVING), !has(JSPROP_IGNORE_READONLY)); MOZ_ASSERT_IF(has(JSPROP_RESOLVING), !has(JSPROP_IGNORE_VALUE)); #endif } void assertComplete() const { #ifdef DEBUG assertValid(); MOZ_ASSERT( (attributes() & ~(JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY | JSPROP_GETTER | JSPROP_SETTER | JSPROP_RESOLVING | JSPROP_INTERNAL_USE_BIT)) == 0); MOZ_ASSERT_IF(isAccessorDescriptor(), has(JSPROP_GETTER) && has(JSPROP_SETTER)); #endif } void assertCompleteIfFound() const { #ifdef DEBUG if (object()) { assertComplete(); } #endif } }; template class MutableWrappedPtrOperations : public js::WrappedPtrOperations { JS::PropertyDescriptor& desc() { return static_cast(this)->get(); } public: void clear() { object().set(nullptr); setAttributes(0); setGetter(nullptr); setSetter(nullptr); value().setUndefined(); } void initFields(JS::Handle obj, JS::Handle v, unsigned attrs, JSGetterOp getterOp, JSSetterOp setterOp) { object().set(obj); value().set(v); setAttributes(attrs); setGetter(getterOp); setSetter(setterOp); } void assign(JS::PropertyDescriptor& other) { object().set(other.obj); setAttributes(other.attrs); setGetter(other.getter); setSetter(other.setter); value().set(other.value); } void setDataDescriptor(JS::Handle v, unsigned attrs) { MOZ_ASSERT((attrs & ~(JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY | JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_PERMANENT | JSPROP_IGNORE_READONLY)) == 0); object().set(nullptr); setAttributes(attrs); setGetter(nullptr); setSetter(nullptr); value().set(v); } JS::MutableHandle object() { return JS::MutableHandle::fromMarkedLocation(&desc().obj); } unsigned& attributesRef() { return desc().attrs; } JSGetterOp& getter() { return desc().getter; } JSSetterOp& setter() { return desc().setter; } JS::MutableHandle value() { return JS::MutableHandle::fromMarkedLocation(&desc().value); } void setValue(JS::Handle v) { MOZ_ASSERT(!(desc().attrs & (JSPROP_GETTER | JSPROP_SETTER))); attributesRef() &= ~JSPROP_IGNORE_VALUE; value().set(v); } void setConfigurable(bool configurable) { setAttributes( (desc().attrs & ~(JSPROP_IGNORE_PERMANENT | JSPROP_PERMANENT)) | (configurable ? 0 : JSPROP_PERMANENT)); } void setEnumerable(bool enumerable) { setAttributes( (desc().attrs & ~(JSPROP_IGNORE_ENUMERATE | JSPROP_ENUMERATE)) | (enumerable ? JSPROP_ENUMERATE : 0)); } void setWritable(bool writable) { MOZ_ASSERT(!(desc().attrs & (JSPROP_GETTER | JSPROP_SETTER))); setAttributes((desc().attrs & ~(JSPROP_IGNORE_READONLY | JSPROP_READONLY)) | (writable ? 0 : JSPROP_READONLY)); } void setAttributes(unsigned attrs) { desc().attrs = attrs; } void setGetter(JSGetterOp op) { desc().getter = op; } void setSetter(JSSetterOp op) { desc().setter = op; } void setGetterObject(JSObject* obj) { desc().getter = reinterpret_cast(obj); desc().attrs &= ~(JSPROP_IGNORE_VALUE | JSPROP_IGNORE_READONLY | JSPROP_READONLY); desc().attrs |= JSPROP_GETTER; } void setSetterObject(JSObject* obj) { desc().setter = reinterpret_cast(obj); desc().attrs &= ~(JSPROP_IGNORE_VALUE | JSPROP_IGNORE_READONLY | JSPROP_READONLY); desc().attrs |= JSPROP_SETTER; } JS::MutableHandle getterObject() { MOZ_ASSERT(this->hasGetterObject()); return JS::MutableHandle::fromMarkedLocation( reinterpret_cast(&desc().getter)); } JS::MutableHandle setterObject() { MOZ_ASSERT(this->hasSetterObject()); return JS::MutableHandle::fromMarkedLocation( reinterpret_cast(&desc().setter)); } }; } // namespace js 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.object() is null, then vp is set to undefined. */ extern JS_PUBLIC_API bool FromPropertyDescriptor( JSContext* cx, Handle desc, MutableHandle vp); } // namespace JS #endif /* js_PropertyDescriptor_h */