diff options
Diffstat (limited to 'js/public/Id.h')
-rw-r--r-- | js/public/Id.h | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/js/public/Id.h b/js/public/Id.h new file mode 100644 index 0000000000..087f9e61c9 --- /dev/null +++ b/js/public/Id.h @@ -0,0 +1,371 @@ +/* -*- 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] PropertyKey / jsid +// +// A PropertyKey is an identifier for a property of an object which is either a +// 31-bit unsigned integer, interned string or symbol. +// +// Also, there is an additional PropertyKey value, PropertyKey::Void(), which +// does not occur in JS scripts but may be used to indicate the absence of a +// valid key. A void PropertyKey is not a valid key and only arises as an +// exceptional API return value. Embeddings must not pass a void PropertyKey +// into JSAPI entry points expecting a PropertyKey and do not need to handle +// void keys in hooks receiving a PropertyKey except when explicitly noted in +// the API contract. +// +// A PropertyKey is not implicitly convertible to or from a Value; JS_ValueToId +// or JS_IdToValue must be used instead. +// +// jsid is an alias for JS::PropertyKey. New code should use PropertyKey instead +// of jsid. + +#include "mozilla/Maybe.h" + +#include "jstypes.h" + +#include "js/GCAnnotations.h" +#include "js/HeapAPI.h" +#include "js/RootingAPI.h" +#include "js/TraceKind.h" +#include "js/TracingAPI.h" +#include "js/TypeDecls.h" + +namespace js { +class JS_PUBLIC_API GenericPrinter; +class JSONPrinter; +} // namespace js + +namespace JS { + +enum class SymbolCode : uint32_t; + +class PropertyKey { + uintptr_t asBits_; + + public: + // All keys with the low bit set are integer keys. This means the other type + // tags must all be even. These constants are public only for the JITs. + static constexpr uintptr_t IntTagBit = 0x1; + // Use 0 for StringTypeTag to avoid a bitwise op for atom <-> id conversions. + static constexpr uintptr_t StringTypeTag = 0x0; + static constexpr uintptr_t VoidTypeTag = 0x2; + static constexpr uintptr_t SymbolTypeTag = 0x4; + // (0x6 is unused) + static constexpr uintptr_t TypeMask = 0x7; + + static constexpr uint32_t IntMin = 0; + static constexpr uint32_t IntMax = INT32_MAX; + + constexpr PropertyKey() : asBits_(VoidTypeTag) {} + + static constexpr MOZ_ALWAYS_INLINE PropertyKey fromRawBits(uintptr_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 isVoid() const { + MOZ_ASSERT_IF((asBits_ & TypeMask) == VoidTypeTag, asBits_ == VoidTypeTag); + return asBits_ == VoidTypeTag; + } + + MOZ_ALWAYS_INLINE bool isInt() const { return !!(asBits_ & IntTagBit); } + + MOZ_ALWAYS_INLINE bool isString() const { + return (asBits_ & TypeMask) == StringTypeTag; + } + + MOZ_ALWAYS_INLINE bool isSymbol() const { + return (asBits_ & TypeMask) == SymbolTypeTag; + } + + MOZ_ALWAYS_INLINE bool isGCThing() const { return isString() || isSymbol(); } + + constexpr uintptr_t asRawBits() const { return asBits_; } + + 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 `& ~TypeMask` because small immediates can be + // encoded more efficiently on some platorms. + return reinterpret_cast<JSString*>(asBits_ ^ StringTypeTag); + } + + MOZ_ALWAYS_INLINE JS::Symbol* toSymbol() const { + MOZ_ASSERT(isSymbol()); + return reinterpret_cast<JS::Symbol*>(asBits_ ^ SymbolTypeTag); + } + + js::gc::Cell* toGCThing() const { + MOZ_ASSERT(isGCThing()); + return reinterpret_cast<js::gc::Cell*>(asBits_ & ~TypeMask); + } + + 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; + + // A void PropertyKey. This is equivalent to a PropertyKey created by the + // default constructor. + static constexpr PropertyKey Void() { return PropertyKey(); } + + static constexpr bool fitsInInt(int32_t i) { return i >= 0; } + + static constexpr PropertyKey Int(int32_t i) { + MOZ_ASSERT(fitsInInt(i)); + uint32_t bits = (static_cast<uint32_t>(i) << 1) | IntTagBit; + return PropertyKey::fromRawBits(bits); + } + + static PropertyKey Symbol(JS::Symbol* sym) { + MOZ_ASSERT(sym != nullptr); + MOZ_ASSERT((uintptr_t(sym) & TypeMask) == 0); + MOZ_ASSERT(!js::gc::IsInsideNursery(reinterpret_cast<js::gc::Cell*>(sym))); + return PropertyKey::fromRawBits(uintptr_t(sym) | SymbolTypeTag); + } + + // 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, IntMax] (where IntMax == 2^31-1). Thus, for the range of integers + // (IntMax, 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 NonIntAtom(JSAtom* atom) { + MOZ_ASSERT((uintptr_t(atom) & TypeMask) == 0); + MOZ_ASSERT(PropertyKey::isNonIntAtom(atom)); + return PropertyKey::fromRawBits(uintptr_t(atom) | StringTypeTag); + } + + // The JSAtom/JSString type exposed to embedders is opaque. + static PropertyKey NonIntAtom(JSString* str) { + MOZ_ASSERT((uintptr_t(str) & TypeMask) == 0); + MOZ_ASSERT(PropertyKey::isNonIntAtom(str)); + return PropertyKey::fromRawBits(uintptr_t(str) | StringTypeTag); + } + + // This API can be used by embedders to convert pinned (aka interned) strings, + // as created by JS_AtomizeAndPinString, 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); + + // Internal API! + // All string PropertyKeys are actually atomized. + MOZ_ALWAYS_INLINE bool isAtom() const { return isString(); } + + MOZ_ALWAYS_INLINE bool isAtom(JSAtom* atom) const { + MOZ_ASSERT(PropertyKey::isNonIntAtom(atom)); + return isAtom() && toAtom() == atom; + } + + MOZ_ALWAYS_INLINE JSAtom* toAtom() const { + return reinterpret_cast<JSAtom*>(toString()); + } + MOZ_ALWAYS_INLINE JSLinearString* toLinearString() const { + return reinterpret_cast<JSLinearString*>(toString()); + } + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dump(js::GenericPrinter& out) const; + void dump(js::JSONPrinter& json) const; + + void dumpFields(js::JSONPrinter& json) const; + void dumpPropertyName(js::GenericPrinter& out) const; + void dumpStringContent(js::GenericPrinter& out) const; +#endif + + private: + static bool isNonIntAtom(JSAtom* atom); + static bool isNonIntAtom(JSString* atom); +} JS_HAZ_GC_POINTER; + +} // namespace JS + +using jsid = JS::PropertyKey; + +namespace JS { + +// Handle<PropertyKey> version of PropertyKey::Void(). +extern JS_PUBLIC_DATA const JS::HandleId VoidHandlePropertyKey; + +template <> +struct GCPolicy<jsid> { + static void trace(JSTracer* trc, jsid* idp, const char* name) { + // This should only be called as part of root marking since that's the only + // time we should trace unbarriered GC thing pointers. This will assert if + // called at other times. + TraceRoot(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 + +/** + * Get one of the well-known symbols defined by ES6 as PropertyKey. This is + * equivalent to calling JS::GetWellKnownSymbol and then creating a PropertyKey. + * + * `which` must be in the range [0, WellKnownSymbolLimit). + */ +extern JS_PUBLIC_API PropertyKey GetWellKnownSymbolKey(JSContext* cx, + SymbolCode which); + +/** + * Generate getter/setter id for given id, by adding "get " or "set " prefix. + */ +extern JS_PUBLIC_API bool ToGetterId( + JSContext* cx, JS::Handle<JS::PropertyKey> id, + JS::MutableHandle<JS::PropertyKey> getterId); +extern JS_PUBLIC_API bool ToSetterId( + JSContext* cx, JS::Handle<JS::PropertyKey> id, + JS::MutableHandle<JS::PropertyKey> setterId); + +} // namespace JS + +namespace js { + +template <> +struct BarrierMethods<jsid> { + static gc::Cell* asGCThingOrNull(jsid id) { + if (id.isGCThing()) { + return id.toGCThing(); + } + return nullptr; + } + static void postWriteBarrier(jsid* idp, jsid prev, jsid next) { + MOZ_ASSERT_IF(next.isString(), !gc::IsInsideNursery(next.toString())); + } + static void exposeToJS(jsid id) { + if (id.isGCThing()) { + js::gc::ExposeGCThingToActiveJS(id.toGCCellPtr()); + } + } + static void readBarrier(jsid id) { + if (id.isGCThing()) { + js::gc::IncrementalReadBarrier(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 (id.isString()) { + return mozilla::Some(f(id.toString())); + } + if (id.isSymbol()) { + return mozilla::Some(f(id.toSymbol())); + } + 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 isVoid() const { return id().isVoid(); } + 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); + } + + uintptr_t asRawBits() const { return id().asRawBits(); } + + // Internal API + bool isAtom() const { return id().isAtom(); } + bool isAtom(JSAtom* atom) const { return id().isAtom(atom); } + JSAtom* toAtom() const { return id().toAtom(); } + JSLinearString* toLinearString() const { return id().toLinearString(); } +}; + +} // namespace js + +#endif /* js_Id_h */ |