1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
10 // [SMDOC] Property Key / JSID
12 // A jsid is an identifier for a property or method of an object which is
13 // either a 31-bit unsigned integer, interned string or symbol.
15 // Also, there is an additional jsid value, JSID_VOID, which does not occur in
16 // JS scripts but may be used to indicate the absence of a valid jsid. A void
17 // jsid is not a valid id and only arises as an exceptional API return value,
18 // such as in JS_NextProperty. Embeddings must not pass JSID_VOID into JSAPI
19 // entry points expecting a jsid and do not need to handle JSID_VOID in hooks
20 // receiving a jsid except when explicitly noted in the API contract.
22 // A jsid is not implicitly convertible to or from a Value; JS_ValueToId or
23 // JS_IdToValue must be used instead.
25 #include "mozilla/Maybe.h"
29 #include "js/GCAnnotations.h"
30 #include "js/HeapAPI.h"
31 #include "js/RootingAPI.h"
32 #include "js/TraceKind.h"
33 #include "js/TracingAPI.h"
34 #include "js/TypeDecls.h"
36 // All jsids with the low bit set are integer ids. This means the other type
37 // tags must all be even.
38 #define JSID_TYPE_INT_BIT 0x1
40 // Use 0 for JSID_TYPE_STRING to avoid a bitwise op for atom <-> id conversions.
41 #define JSID_TYPE_STRING 0x0
42 #define JSID_TYPE_VOID 0x2
43 #define JSID_TYPE_SYMBOL 0x4
45 #define JSID_TYPE_MASK 0x7
49 enum class SymbolCode
: uint32_t;
54 constexpr PropertyKey() : asBits(JSID_TYPE_VOID
) {}
56 static constexpr MOZ_ALWAYS_INLINE PropertyKey
fromRawBits(size_t bits
) {
62 bool operator==(const PropertyKey
& rhs
) const { return asBits
== rhs
.asBits
; }
63 bool operator!=(const PropertyKey
& rhs
) const { return asBits
!= rhs
.asBits
; }
65 MOZ_ALWAYS_INLINE
bool isVoid() const {
66 MOZ_ASSERT_IF((asBits
& JSID_TYPE_MASK
) == JSID_TYPE_VOID
,
67 asBits
== JSID_TYPE_VOID
);
68 return asBits
== JSID_TYPE_VOID
;
71 MOZ_ALWAYS_INLINE
bool isInt() const {
72 return !!(asBits
& JSID_TYPE_INT_BIT
);
75 MOZ_ALWAYS_INLINE
bool isString() const {
76 return (asBits
& JSID_TYPE_MASK
) == JSID_TYPE_STRING
;
79 MOZ_ALWAYS_INLINE
bool isSymbol() const {
80 return (asBits
& JSID_TYPE_MASK
) == JSID_TYPE_SYMBOL
;
83 MOZ_ALWAYS_INLINE
bool isGCThing() const { return isString() || isSymbol(); }
85 MOZ_ALWAYS_INLINE
int32_t toInt() const {
87 uint32_t bits
= static_cast<uint32_t>(asBits
) >> 1;
88 return static_cast<int32_t>(bits
);
91 MOZ_ALWAYS_INLINE JSString
* toString() const {
92 MOZ_ASSERT(isString());
93 // Use XOR instead of `& ~JSID_TYPE_MASK` because small immediates can be
94 // encoded more efficiently on some platorms.
95 return reinterpret_cast<JSString
*>(asBits
^ JSID_TYPE_STRING
);
98 MOZ_ALWAYS_INLINE
JS::Symbol
* toSymbol() const {
99 MOZ_ASSERT(isSymbol());
100 return reinterpret_cast<JS::Symbol
*>(asBits
^ JSID_TYPE_SYMBOL
);
103 js::gc::Cell
* toGCThing() const {
104 MOZ_ASSERT(isGCThing());
105 return reinterpret_cast<js::gc::Cell
*>(asBits
& ~(size_t)JSID_TYPE_MASK
);
108 GCCellPtr
toGCCellPtr() const {
109 js::gc::Cell
* thing
= toGCThing();
111 return JS::GCCellPtr(thing
, JS::TraceKind::String
);
113 MOZ_ASSERT(isSymbol());
114 return JS::GCCellPtr(thing
, JS::TraceKind::Symbol
);
117 bool isPrivateName() const;
119 bool isWellKnownSymbol(JS::SymbolCode code
) const;
121 // This API can be used by embedders to convert pinned (aka interned) strings,
122 // as created by JS_AtomizeAndPinJSString, into PropertyKeys.
123 // This means the string does not have to be explicitly rooted.
125 // Only use this API when absolutely necessary, otherwise use JS_StringToId.
126 static PropertyKey
fromPinnedString(JSString
* str
);
128 // Must not be used on atoms that are representable as integer PropertyKey.
129 // Prefer NameToId or AtomToId over this function:
131 // A PropertyName is an atom that does not contain an integer in the range
132 // [0, UINT32_MAX]. However, PropertyKey can only hold an integer in the range
133 // [0, JSID_INT_MAX] (where JSID_INT_MAX == 2^31-1). Thus, for the range of
134 // integers (JSID_INT_MAX, UINT32_MAX], to represent as a 'id', it must be
135 // the case id.isString() and id.toString()->isIndex(). In most
136 // cases when creating a PropertyKey, code does not have to care about
137 // this corner case because:
139 // - When given an arbitrary JSAtom*, AtomToId must be used, which checks for
140 // integer atoms representable as integer PropertyKey, and does this
143 // - When given a PropertyName*, NameToId can be used which does not need
144 // to do any dynamic checks.
146 // Thus, it is only the rare third case which needs this function, which
147 // handles any JSAtom* that is known not to be representable with an int
149 static PropertyKey
fromNonIntAtom(JSAtom
* atom
) {
150 MOZ_ASSERT((size_t(atom
) & JSID_TYPE_MASK
) == 0);
151 MOZ_ASSERT(PropertyKey::isNonIntAtom(atom
));
152 return PropertyKey::fromRawBits(size_t(atom
) | JSID_TYPE_STRING
);
155 // The JSAtom/JSString type exposed to embedders is opaque.
156 static PropertyKey
fromNonIntAtom(JSString
* str
) {
157 MOZ_ASSERT((size_t(str
) & JSID_TYPE_MASK
) == 0);
158 MOZ_ASSERT(PropertyKey::isNonIntAtom(str
));
159 return PropertyKey::fromRawBits(size_t(str
) | JSID_TYPE_STRING
);
163 // All string PropertyKeys are actually atomized.
164 MOZ_ALWAYS_INLINE
bool isAtom() const { return isString(); }
166 MOZ_ALWAYS_INLINE
bool isAtom(JSAtom
* atom
) const {
167 MOZ_ASSERT(PropertyKey::isNonIntAtom(atom
));
168 return isAtom() && toAtom() == atom
;
171 MOZ_ALWAYS_INLINE JSAtom
* toAtom() const { return (JSAtom
*)toString(); }
174 static bool isNonIntAtom(JSAtom
* atom
);
175 static bool isNonIntAtom(JSString
* atom
);
180 using jsid
= JS::PropertyKey
;
182 #define JSID_BITS(id) (id.asBits)
184 static MOZ_ALWAYS_INLINE
bool JSID_IS_STRING(jsid id
) { return id
.isString(); }
186 static MOZ_ALWAYS_INLINE JSString
* JSID_TO_STRING(jsid id
) {
187 return id
.toString();
190 static MOZ_ALWAYS_INLINE
bool JSID_IS_INT(jsid id
) { return id
.isInt(); }
192 static MOZ_ALWAYS_INLINE
int32_t JSID_TO_INT(jsid id
) { return id
.toInt(); }
194 #define JSID_INT_MIN 0
195 #define JSID_INT_MAX INT32_MAX
197 static MOZ_ALWAYS_INLINE
bool INT_FITS_IN_JSID(int32_t i
) { return i
>= 0; }
199 static MOZ_ALWAYS_INLINE jsid
INT_TO_JSID(int32_t i
) {
201 MOZ_ASSERT(INT_FITS_IN_JSID(i
));
202 uint32_t bits
= (static_cast<uint32_t>(i
) << 1) | JSID_TYPE_INT_BIT
;
203 JSID_BITS(id
) = static_cast<size_t>(bits
);
207 static MOZ_ALWAYS_INLINE jsid
SYMBOL_TO_JSID(JS::Symbol
* sym
) {
209 MOZ_ASSERT(sym
!= nullptr);
210 MOZ_ASSERT((size_t(sym
) & JSID_TYPE_MASK
) == 0);
211 MOZ_ASSERT(!js::gc::IsInsideNursery(reinterpret_cast<js::gc::Cell
*>(sym
)));
212 JSID_BITS(id
) = (size_t(sym
) | JSID_TYPE_SYMBOL
);
216 static MOZ_ALWAYS_INLINE
bool JSID_IS_VOID(const jsid id
) {
220 constexpr const jsid JSID_VOID
;
222 extern JS_PUBLIC_DATA
const JS::HandleId JSID_VOIDHANDLE
;
227 struct GCPolicy
<jsid
> {
228 static void trace(JSTracer
* trc
, jsid
* idp
, const char* name
) {
229 // It's not safe to trace unbarriered pointers except as part of root
231 UnsafeTraceRoot(trc
, idp
, name
);
233 static bool isValid(jsid id
) {
234 return !id
.isGCThing() ||
235 js::gc::IsCellPointerValid(id
.toGCCellPtr().asCell());
238 static bool isTenured(jsid id
) {
239 MOZ_ASSERT_IF(id
.isGCThing(),
240 !js::gc::IsInsideNursery(id
.toGCCellPtr().asCell()));
246 MOZ_ALWAYS_INLINE
void AssertIdIsNotGray(jsid id
) {
247 if (id
.isGCThing()) {
248 AssertCellIsNotGray(id
.toGCCellPtr().asCell());
258 struct BarrierMethods
<jsid
> {
259 static gc::Cell
* asGCThingOrNull(jsid id
) {
260 if (id
.isGCThing()) {
261 return id
.toGCThing();
265 static void postWriteBarrier(jsid
* idp
, jsid prev
, jsid next
) {
266 MOZ_ASSERT_IF(JSID_IS_STRING(next
),
267 !gc::IsInsideNursery(JSID_TO_STRING(next
)));
269 static void exposeToJS(jsid id
) {
270 if (id
.isGCThing()) {
271 js::gc::ExposeGCThingToActiveJS(id
.toGCCellPtr());
276 // If the jsid is a GC pointer type, convert to that type and call |f| with the
277 // pointer and return the result wrapped in a Maybe, otherwise return None().
278 template <typename F
>
279 auto MapGCThingTyped(const jsid
& id
, F
&& f
) {
281 return mozilla::Some(f(id
.toString()));
284 return mozilla::Some(f(id
.toSymbol()));
286 MOZ_ASSERT(!id
.isGCThing());
287 using ReturnType
= decltype(f(static_cast<JSString
*>(nullptr)));
288 return mozilla::Maybe
<ReturnType
>();
291 // If the jsid is a GC pointer type, convert to that type and call |f| with the
292 // pointer. Return whether this happened.
293 template <typename F
>
294 bool ApplyGCThingTyped(const jsid
& id
, F
&& f
) {
295 return MapGCThingTyped(id
,
303 template <typename Wrapper
>
304 class WrappedPtrOperations
<JS::PropertyKey
, Wrapper
> {
305 const JS::PropertyKey
& id() const {
306 return static_cast<const Wrapper
*>(this)->get();
310 bool isVoid() const { return id().isVoid(); }
311 bool isInt() const { return id().isInt(); }
312 bool isString() const { return id().isString(); }
313 bool isSymbol() const { return id().isSymbol(); }
314 bool isGCThing() const { return id().isGCThing(); }
316 int32_t toInt() const { return id().toInt(); }
317 JSString
* toString() const { return id().toString(); }
318 JS::Symbol
* toSymbol() const { return id().toSymbol(); }
320 bool isPrivateName() const { return id().isPrivateName(); }
322 bool isWellKnownSymbol(JS::SymbolCode code
) const {
323 return id().isWellKnownSymbol(code
);
327 bool isAtom() const { return id().isAtom(); }
328 bool isAtom(JSAtom
* atom
) const { return id().isAtom(atom
); }
329 JSAtom
* toAtom() const { return id().toAtom(); }