diff options
Diffstat (limited to 'js/public/Id.h')
-rw-r--r-- | js/public/Id.h | 328 |
1 files changed, 328 insertions, 0 deletions
diff --git a/js/public/Id.h b/js/public/Id.h new file mode 100644 index 0000000000..ae4cfb5613 --- /dev/null +++ b/js/public/Id.h @@ -0,0 +1,328 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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/. */ + +#ifndef js_Id_h +#define js_Id_h + +// [SMDOC] Property Key / JSID +// +// A jsid is an identifier for a property or method of an object which is +// either a 31-bit unsigned integer, interned string or symbol. +// +// Also, there is an additional jsid value, JSID_VOID, which does not occur in +// JS scripts but may be used to indicate the absence of a valid jsid. A void +// jsid is not a valid id and only arises as an exceptional API return value, +// such as in JS_NextProperty. Embeddings must not pass JSID_VOID into JSAPI +// entry points expecting a jsid and do not need to handle JSID_VOID in hooks +// receiving a jsid except when explicitly noted in the API contract. +// +// A jsid is not implicitly convertible to or from a Value; JS_ValueToId or +// JS_IdToValue must be used instead. + +#include "mozilla/Maybe.h" + +#include "jstypes.h" + +#include "js/HeapAPI.h" +#include "js/RootingAPI.h" +#include "js/TypeDecls.h" +#include "js/Utility.h" + +// All jsids with the low bit set are integer ids. This means the other type +// tags must all be even. +#define JSID_TYPE_INT_BIT 0x1 + +// Use 0 for JSID_TYPE_STRING to avoid a bitwise op for atom <-> id conversions. +#define JSID_TYPE_STRING 0x0 +#define JSID_TYPE_VOID 0x2 +#define JSID_TYPE_SYMBOL 0x4 +#define JSID_TYPE_EMPTY 0x6 +#define JSID_TYPE_MASK 0x7 + +namespace JS { + +enum class SymbolCode : uint32_t; + +struct PropertyKey { + size_t asBits; + + constexpr PropertyKey() : asBits(JSID_TYPE_VOID) {} + + static constexpr MOZ_ALWAYS_INLINE PropertyKey fromRawBits(size_t bits) { + PropertyKey id; + id.asBits = bits; + return id; + } + + bool operator==(const PropertyKey& rhs) const { return asBits == rhs.asBits; } + bool operator!=(const PropertyKey& rhs) const { return asBits != rhs.asBits; } + + MOZ_ALWAYS_INLINE bool isInt() const { + return !!(asBits & JSID_TYPE_INT_BIT); + } + + MOZ_ALWAYS_INLINE bool isString() const { + return (asBits & JSID_TYPE_MASK) == JSID_TYPE_STRING; + } + + MOZ_ALWAYS_INLINE bool isSymbol() const { + return (asBits & JSID_TYPE_MASK) == JSID_TYPE_SYMBOL; + } + + MOZ_ALWAYS_INLINE bool isGCThing() const { return isString() || isSymbol(); } + + MOZ_ALWAYS_INLINE int32_t toInt() const { + MOZ_ASSERT(isInt()); + uint32_t bits = static_cast<uint32_t>(asBits) >> 1; + return static_cast<int32_t>(bits); + } + + MOZ_ALWAYS_INLINE JSString* toString() const { + MOZ_ASSERT(isString()); + // Use XOR instead of `& ~JSID_TYPE_MASK` because small immediates can be + // encoded more efficiently on some platorms. + return reinterpret_cast<JSString*>(asBits ^ JSID_TYPE_STRING); + } + + MOZ_ALWAYS_INLINE JS::Symbol* toSymbol() const { + MOZ_ASSERT(isSymbol()); + return reinterpret_cast<JS::Symbol*>(asBits ^ JSID_TYPE_SYMBOL); + } + + js::gc::Cell* toGCThing() const { + MOZ_ASSERT(isGCThing()); + return reinterpret_cast<js::gc::Cell*>(asBits & ~(size_t)JSID_TYPE_MASK); + } + + GCCellPtr toGCCellPtr() const { + js::gc::Cell* thing = toGCThing(); + if (isString()) { + return JS::GCCellPtr(thing, JS::TraceKind::String); + } + MOZ_ASSERT(isSymbol()); + return JS::GCCellPtr(thing, JS::TraceKind::Symbol); + } + + bool isPrivateName() const; + + bool isWellKnownSymbol(JS::SymbolCode code) const; + + // This API can be used by embedders to convert pinned (aka interned) strings, + // as created by JS_AtomizeAndPinJSString, into PropertyKeys. + // This means the string does not have to be explicitly rooted. + // + // Only use this API when absolutely necessary, otherwise use JS_StringToId. + static PropertyKey fromPinnedString(JSString* str); + + // Must not be used on atoms that are representable as integer PropertyKey. + // Prefer NameToId or AtomToId over this function: + // + // A PropertyName is an atom that does not contain an integer in the range + // [0, UINT32_MAX]. However, PropertyKey can only hold an integer in the range + // [0, JSID_INT_MAX] (where JSID_INT_MAX == 2^31-1). Thus, for the range of + // integers (JSID_INT_MAX, UINT32_MAX], to represent as a 'id', it must be + // the case id.isString() and id.toString()->isIndex(). In most + // cases when creating a PropertyKey, code does not have to care about + // this corner case because: + // + // - When given an arbitrary JSAtom*, AtomToId must be used, which checks for + // integer atoms representable as integer PropertyKey, and does this + // conversion. + // + // - When given a PropertyName*, NameToId can be used which does not need + // to do any dynamic checks. + // + // Thus, it is only the rare third case which needs this function, which + // handles any JSAtom* that is known not to be representable with an int + // PropertyKey. + static PropertyKey fromNonIntAtom(JSAtom* atom) { + MOZ_ASSERT((size_t(atom) & JSID_TYPE_MASK) == 0); + MOZ_ASSERT(PropertyKey::isNonIntAtom(atom)); + return PropertyKey::fromRawBits(size_t(atom) | JSID_TYPE_STRING); + } + + // The JSAtom/JSString type exposed to embedders is opaque. + static PropertyKey fromNonIntAtom(JSString* str) { + MOZ_ASSERT((size_t(str) & JSID_TYPE_MASK) == 0); + MOZ_ASSERT(PropertyKey::isNonIntAtom(str)); + return PropertyKey::fromRawBits(size_t(str) | JSID_TYPE_STRING); + } + + private: + static bool isNonIntAtom(JSAtom* atom); + static bool isNonIntAtom(JSString* atom); +} JS_HAZ_GC_POINTER; + +} // namespace JS + +using jsid = JS::PropertyKey; + +#define JSID_BITS(id) (id.asBits) + +static MOZ_ALWAYS_INLINE bool JSID_IS_STRING(jsid id) { return id.isString(); } + +static MOZ_ALWAYS_INLINE JSString* JSID_TO_STRING(jsid id) { + return id.toString(); +} + +static MOZ_ALWAYS_INLINE bool JSID_IS_INT(jsid id) { return id.isInt(); } + +static MOZ_ALWAYS_INLINE int32_t JSID_TO_INT(jsid id) { return id.toInt(); } + +#define JSID_INT_MIN 0 +#define JSID_INT_MAX INT32_MAX + +static MOZ_ALWAYS_INLINE bool INT_FITS_IN_JSID(int32_t i) { return i >= 0; } + +static MOZ_ALWAYS_INLINE jsid INT_TO_JSID(int32_t i) { + jsid id; + MOZ_ASSERT(INT_FITS_IN_JSID(i)); + uint32_t bits = (static_cast<uint32_t>(i) << 1) | JSID_TYPE_INT_BIT; + JSID_BITS(id) = static_cast<size_t>(bits); + return id; +} + +static MOZ_ALWAYS_INLINE bool JSID_IS_SYMBOL(jsid id) { return id.isSymbol(); } + +static MOZ_ALWAYS_INLINE JS::Symbol* JSID_TO_SYMBOL(jsid id) { + return id.toSymbol(); +} + +static MOZ_ALWAYS_INLINE jsid SYMBOL_TO_JSID(JS::Symbol* sym) { + jsid id; + MOZ_ASSERT(sym != nullptr); + MOZ_ASSERT((size_t(sym) & JSID_TYPE_MASK) == 0); + MOZ_ASSERT(!js::gc::IsInsideNursery(reinterpret_cast<js::gc::Cell*>(sym))); + JSID_BITS(id) = (size_t(sym) | JSID_TYPE_SYMBOL); + return id; +} + +static MOZ_ALWAYS_INLINE bool JSID_IS_VOID(const jsid id) { + MOZ_ASSERT_IF((JSID_BITS(id) & JSID_TYPE_MASK) == JSID_TYPE_VOID, + JSID_BITS(id) == JSID_TYPE_VOID); + return JSID_BITS(id) == JSID_TYPE_VOID; +} + +static MOZ_ALWAYS_INLINE bool JSID_IS_EMPTY(const jsid id) { + MOZ_ASSERT_IF((JSID_BITS(id) & JSID_TYPE_MASK) == JSID_TYPE_EMPTY, + JSID_BITS(id) == JSID_TYPE_EMPTY); + return JSID_BITS(id) == JSID_TYPE_EMPTY; +} + +constexpr const jsid JSID_VOID; +constexpr const jsid JSID_EMPTY = jsid::fromRawBits(JSID_TYPE_EMPTY); + +extern JS_PUBLIC_DATA const JS::HandleId JSID_VOIDHANDLE; +extern JS_PUBLIC_DATA const JS::HandleId JSID_EMPTYHANDLE; + +namespace JS { + +template <> +struct GCPolicy<jsid> { + static void trace(JSTracer* trc, jsid* idp, const char* name) { + // It's not safe to trace unbarriered pointers except as part of root + // marking. + UnsafeTraceRoot(trc, idp, name); + } + static bool isValid(jsid id) { + return !id.isGCThing() || + js::gc::IsCellPointerValid(id.toGCCellPtr().asCell()); + } + + static bool isTenured(jsid id) { + MOZ_ASSERT_IF(id.isGCThing(), + !js::gc::IsInsideNursery(id.toGCCellPtr().asCell())); + return true; + } +}; + +#ifdef DEBUG +MOZ_ALWAYS_INLINE void AssertIdIsNotGray(jsid id) { + if (id.isGCThing()) { + AssertCellIsNotGray(id.toGCCellPtr().asCell()); + } +} +#endif + +} // namespace JS + +namespace js { + +template <> +struct BarrierMethods<jsid> { + static gc::Cell* asGCThingOrNull(jsid id) { + if (JSID_IS_STRING(id)) { + return reinterpret_cast<gc::Cell*>(JSID_TO_STRING(id)); + } + if (JSID_IS_SYMBOL(id)) { + return reinterpret_cast<gc::Cell*>(JSID_TO_SYMBOL(id)); + } + return nullptr; + } + static void postWriteBarrier(jsid* idp, jsid prev, jsid next) { + MOZ_ASSERT_IF(JSID_IS_STRING(next), + !gc::IsInsideNursery(JSID_TO_STRING(next))); + } + static void exposeToJS(jsid id) { + if (id.isGCThing()) { + js::gc::ExposeGCThingToActiveJS(id.toGCCellPtr()); + } + } +}; + +// If the jsid is a GC pointer type, convert to that type and call |f| with the +// pointer and return the result wrapped in a Maybe, otherwise return None(). +template <typename F> +auto MapGCThingTyped(const jsid& id, F&& f) { + if (JSID_IS_STRING(id)) { + return mozilla::Some(f(JSID_TO_STRING(id))); + } + if (JSID_IS_SYMBOL(id)) { + return mozilla::Some(f(JSID_TO_SYMBOL(id))); + } + MOZ_ASSERT(!id.isGCThing()); + using ReturnType = decltype(f(static_cast<JSString*>(nullptr))); + return mozilla::Maybe<ReturnType>(); +} + +// If the jsid is a GC pointer type, convert to that type and call |f| with the +// pointer. Return whether this happened. +template <typename F> +bool ApplyGCThingTyped(const jsid& id, F&& f) { + return MapGCThingTyped(id, + [&f](auto t) { + f(t); + return true; + }) + .isSome(); +} + +template <typename Wrapper> +class WrappedPtrOperations<JS::PropertyKey, Wrapper> { + const JS::PropertyKey& id() const { + return static_cast<const Wrapper*>(this)->get(); + } + + public: + bool isInt() const { return id().isInt(); } + bool isString() const { return id().isString(); } + bool isSymbol() const { return id().isSymbol(); } + bool isGCThing() const { return id().isGCThing(); } + + int32_t toInt() const { return id().toInt(); } + JSString* toString() const { return id().toString(); } + JS::Symbol* toSymbol() const { return id().toSymbol(); } + + bool isPrivateName() const { return id().isPrivateName(); } + + bool isWellKnownSymbol(JS::SymbolCode code) const { + return id().isWellKnownSymbol(code); + } +}; + +} // namespace js + +#endif /* js_Id_h */ |