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/. */
6
7 #ifndef js_Id_h
8 #define js_Id_h
9
10 // [SMDOC] Property Key / JSID
11 //
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.
14 //
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.
21 //
22 // A jsid is not implicitly convertible to or from a Value; JS_ValueToId or
23 // JS_IdToValue must be used instead.
24
25 #include "mozilla/Maybe.h"
26
27 #include "jstypes.h"
28
29 #include "js/HeapAPI.h"
30 #include "js/RootingAPI.h"
31 #include "js/TypeDecls.h"
32 #include "js/Utility.h"
33
34 // All jsids with the low bit set are integer ids. This means the other type
35 // tags must all be even.
36 #define JSID_TYPE_INT_BIT 0x1
37
38 // Use 0 for JSID_TYPE_STRING to avoid a bitwise op for atom <-> id conversions.
39 #define JSID_TYPE_STRING 0x0
40 #define JSID_TYPE_VOID 0x2
41 #define JSID_TYPE_SYMBOL 0x4
42 #define JSID_TYPE_EMPTY 0x6
43 #define JSID_TYPE_MASK 0x7
44
45 namespace JS {
46
47 enum class SymbolCode : uint32_t;
48
49 struct PropertyKey {
50 size_t asBits;
51
PropertyKeyPropertyKey52 constexpr PropertyKey() : asBits(JSID_TYPE_VOID) {}
53
fromRawBitsPropertyKey54 static constexpr MOZ_ALWAYS_INLINE PropertyKey fromRawBits(size_t bits) {
55 PropertyKey id;
56 id.asBits = bits;
57 return id;
58 }
59
60 bool operator==(const PropertyKey& rhs) const { return asBits == rhs.asBits; }
61 bool operator!=(const PropertyKey& rhs) const { return asBits != rhs.asBits; }
62
isIntPropertyKey63 MOZ_ALWAYS_INLINE bool isInt() const {
64 return !!(asBits & JSID_TYPE_INT_BIT);
65 }
66
isStringPropertyKey67 MOZ_ALWAYS_INLINE bool isString() const {
68 return (asBits & JSID_TYPE_MASK) == JSID_TYPE_STRING;
69 }
70
isSymbolPropertyKey71 MOZ_ALWAYS_INLINE bool isSymbol() const {
72 return (asBits & JSID_TYPE_MASK) == JSID_TYPE_SYMBOL;
73 }
74
isGCThingPropertyKey75 MOZ_ALWAYS_INLINE bool isGCThing() const { return isString() || isSymbol(); }
76
toIntPropertyKey77 MOZ_ALWAYS_INLINE int32_t toInt() const {
78 MOZ_ASSERT(isInt());
79 uint32_t bits = static_cast<uint32_t>(asBits) >> 1;
80 return static_cast<int32_t>(bits);
81 }
82
toStringPropertyKey83 MOZ_ALWAYS_INLINE JSString* toString() const {
84 MOZ_ASSERT(isString());
85 // Use XOR instead of `& ~JSID_TYPE_MASK` because small immediates can be
86 // encoded more efficiently on some platorms.
87 return reinterpret_cast<JSString*>(asBits ^ JSID_TYPE_STRING);
88 }
89
toSymbolPropertyKey90 MOZ_ALWAYS_INLINE JS::Symbol* toSymbol() const {
91 MOZ_ASSERT(isSymbol());
92 return reinterpret_cast<JS::Symbol*>(asBits ^ JSID_TYPE_SYMBOL);
93 }
94
toGCCellPtrPropertyKey95 GCCellPtr toGCCellPtr() const {
96 void* thing = (void*)(asBits & ~(size_t)JSID_TYPE_MASK);
97 if (isString()) {
98 return JS::GCCellPtr(thing, JS::TraceKind::String);
99 }
100 MOZ_ASSERT(isSymbol());
101 return JS::GCCellPtr(thing, JS::TraceKind::Symbol);
102 }
103
104 bool isWellKnownSymbol(JS::SymbolCode code) const;
105
106 // This API can be used by embedders to convert pinned (aka interned) strings,
107 // as created by JS_AtomizeAndPinJSString, into PropertyKeys.
108 // This means the string does not have to be explicitly rooted.
109 //
110 // Only use this API when absolutely necessary, otherwise use JS_StringToId.
111 static PropertyKey fromPinnedString(JSString* str);
112
113 // Must not be used on atoms that are representable as integer PropertyKey.
114 // Prefer NameToId or AtomToId over this function:
115 //
116 // A PropertyName is an atom that does not contain an integer in the range
117 // [0, UINT32_MAX]. However, PropertyKey can only hold an integer in the range
118 // [0, JSID_INT_MAX] (where JSID_INT_MAX == 2^31-1). Thus, for the range of
119 // integers (JSID_INT_MAX, UINT32_MAX], to represent as a 'id', it must be
120 // the case id.isString() and id.toString()->isIndex(). In most
121 // cases when creating a PropertyKey, code does not have to care about
122 // this corner case because:
123 //
124 // - When given an arbitrary JSAtom*, AtomToId must be used, which checks for
125 // integer atoms representable as integer PropertyKey, and does this
126 // conversion.
127 //
128 // - When given a PropertyName*, NameToId can be used which does not need
129 // to do any dynamic checks.
130 //
131 // Thus, it is only the rare third case which needs this function, which
132 // handles any JSAtom* that is known not to be representable with an int
133 // PropertyKey.
fromNonIntAtomPropertyKey134 static PropertyKey fromNonIntAtom(JSAtom* atom) {
135 MOZ_ASSERT((size_t(atom) & JSID_TYPE_MASK) == 0);
136 MOZ_ASSERT(PropertyKey::isNonIntAtom(atom));
137 return PropertyKey::fromRawBits(size_t(atom) | JSID_TYPE_STRING);
138 }
139
140 // The JSAtom/JSString type exposed to embedders is opaque.
fromNonIntAtomPropertyKey141 static PropertyKey fromNonIntAtom(JSString* str) {
142 MOZ_ASSERT((size_t(str) & JSID_TYPE_MASK) == 0);
143 MOZ_ASSERT(PropertyKey::isNonIntAtom(str));
144 return PropertyKey::fromRawBits(size_t(str) | JSID_TYPE_STRING);
145 }
146
147 private:
148 static bool isNonIntAtom(JSAtom* atom);
149 static bool isNonIntAtom(JSString* atom);
150 } JS_HAZ_GC_POINTER;
151
152 } // namespace JS
153
154 using jsid = JS::PropertyKey;
155
156 #define JSID_BITS(id) (id.asBits)
157
JSID_IS_STRING(jsid id)158 static MOZ_ALWAYS_INLINE bool JSID_IS_STRING(jsid id) { return id.isString(); }
159
JSID_TO_STRING(jsid id)160 static MOZ_ALWAYS_INLINE JSString* JSID_TO_STRING(jsid id) {
161 return id.toString();
162 }
163
JSID_IS_INT(jsid id)164 static MOZ_ALWAYS_INLINE bool JSID_IS_INT(jsid id) { return id.isInt(); }
165
JSID_TO_INT(jsid id)166 static MOZ_ALWAYS_INLINE int32_t JSID_TO_INT(jsid id) { return id.toInt(); }
167
168 #define JSID_INT_MIN 0
169 #define JSID_INT_MAX INT32_MAX
170
INT_FITS_IN_JSID(int32_t i)171 static MOZ_ALWAYS_INLINE bool INT_FITS_IN_JSID(int32_t i) { return i >= 0; }
172
INT_TO_JSID(int32_t i)173 static MOZ_ALWAYS_INLINE jsid INT_TO_JSID(int32_t i) {
174 jsid id;
175 MOZ_ASSERT(INT_FITS_IN_JSID(i));
176 uint32_t bits = (static_cast<uint32_t>(i) << 1) | JSID_TYPE_INT_BIT;
177 JSID_BITS(id) = static_cast<size_t>(bits);
178 return id;
179 }
180
JSID_IS_SYMBOL(jsid id)181 static MOZ_ALWAYS_INLINE bool JSID_IS_SYMBOL(jsid id) { return id.isSymbol(); }
182
JSID_TO_SYMBOL(jsid id)183 static MOZ_ALWAYS_INLINE JS::Symbol* JSID_TO_SYMBOL(jsid id) {
184 return id.toSymbol();
185 }
186
SYMBOL_TO_JSID(JS::Symbol * sym)187 static MOZ_ALWAYS_INLINE jsid SYMBOL_TO_JSID(JS::Symbol* sym) {
188 jsid id;
189 MOZ_ASSERT(sym != nullptr);
190 MOZ_ASSERT((size_t(sym) & JSID_TYPE_MASK) == 0);
191 MOZ_ASSERT(!js::gc::IsInsideNursery(reinterpret_cast<js::gc::Cell*>(sym)));
192 JSID_BITS(id) = (size_t(sym) | JSID_TYPE_SYMBOL);
193 return id;
194 }
195
JSID_IS_VOID(const jsid id)196 static MOZ_ALWAYS_INLINE bool JSID_IS_VOID(const jsid id) {
197 MOZ_ASSERT_IF((JSID_BITS(id) & JSID_TYPE_MASK) == JSID_TYPE_VOID,
198 JSID_BITS(id) == JSID_TYPE_VOID);
199 return JSID_BITS(id) == JSID_TYPE_VOID;
200 }
201
JSID_IS_EMPTY(const jsid id)202 static MOZ_ALWAYS_INLINE bool JSID_IS_EMPTY(const jsid id) {
203 MOZ_ASSERT_IF((JSID_BITS(id) & JSID_TYPE_MASK) == JSID_TYPE_EMPTY,
204 JSID_BITS(id) == JSID_TYPE_EMPTY);
205 return JSID_BITS(id) == JSID_TYPE_EMPTY;
206 }
207
208 constexpr const jsid JSID_VOID;
209 constexpr const jsid JSID_EMPTY = jsid::fromRawBits(JSID_TYPE_EMPTY);
210
211 extern JS_PUBLIC_DATA const JS::HandleId JSID_VOIDHANDLE;
212 extern JS_PUBLIC_DATA const JS::HandleId JSID_EMPTYHANDLE;
213
214 namespace JS {
215
216 template <>
217 struct GCPolicy<jsid> {
218 static void trace(JSTracer* trc, jsid* idp, const char* name) {
219 // It's not safe to trace unbarriered pointers except as part of root
220 // marking.
221 UnsafeTraceRoot(trc, idp, name);
222 }
223 static bool isValid(jsid id) {
224 return !id.isGCThing() ||
225 js::gc::IsCellPointerValid(id.toGCCellPtr().asCell());
226 }
227 };
228
229 #ifdef DEBUG
230 MOZ_ALWAYS_INLINE void AssertIdIsNotGray(jsid id) {
231 if (id.isGCThing()) {
232 AssertCellIsNotGray(id.toGCCellPtr().asCell());
233 }
234 }
235 #endif
236
237 } // namespace JS
238
239 namespace js {
240
241 template <>
242 struct BarrierMethods<jsid> {
243 static gc::Cell* asGCThingOrNull(jsid id) {
244 if (JSID_IS_STRING(id)) {
245 return reinterpret_cast<gc::Cell*>(JSID_TO_STRING(id));
246 }
247 if (JSID_IS_SYMBOL(id)) {
248 return reinterpret_cast<gc::Cell*>(JSID_TO_SYMBOL(id));
249 }
250 return nullptr;
251 }
252 static void postWriteBarrier(jsid* idp, jsid prev, jsid next) {
253 MOZ_ASSERT_IF(JSID_IS_STRING(next),
254 !gc::IsInsideNursery(JSID_TO_STRING(next)));
255 }
256 static void exposeToJS(jsid id) {
257 if (id.isGCThing()) {
258 js::gc::ExposeGCThingToActiveJS(id.toGCCellPtr());
259 }
260 }
261 };
262
263 // If the jsid is a GC pointer type, convert to that type and call |f| with the
264 // pointer and return the result wrapped in a Maybe, otherwise return None().
265 template <typename F>
266 auto MapGCThingTyped(const jsid& id, F&& f) {
267 if (JSID_IS_STRING(id)) {
268 return mozilla::Some(f(JSID_TO_STRING(id)));
269 }
270 if (JSID_IS_SYMBOL(id)) {
271 return mozilla::Some(f(JSID_TO_SYMBOL(id)));
272 }
273 MOZ_ASSERT(!id.isGCThing());
274 using ReturnType = decltype(f(static_cast<JSString*>(nullptr)));
275 return mozilla::Maybe<ReturnType>();
276 }
277
278 // If the jsid is a GC pointer type, convert to that type and call |f| with the
279 // pointer. Return whether this happened.
280 template <typename F>
281 bool ApplyGCThingTyped(const jsid& id, F&& f) {
282 return MapGCThingTyped(id,
283 [&f](auto t) {
284 f(t);
285 return true;
286 })
287 .isSome();
288 }
289
290 template <typename Wrapper>
291 class WrappedPtrOperations<JS::PropertyKey, Wrapper> {
292 const JS::PropertyKey& id() const {
293 return static_cast<const Wrapper*>(this)->get();
294 }
295
296 public:
297 bool isInt() const { return id().isInt(); }
298 bool isString() const { return id().isString(); }
299 bool isSymbol() const { return id().isSymbol(); }
300 bool isGCThing() const { return id().isGCThing(); }
301
302 int32_t toInt() const { return id().toInt(); }
303 JSString* toString() const { return id().toString(); }
304 JS::Symbol* toSymbol() const { return id().toSymbol(); }
305
306 bool isWellKnownSymbol(JS::SymbolCode code) const {
307 return id().isWellKnownSymbol(code);
308 }
309 };
310
311 } // namespace js
312
313 #endif /* js_Id_h */
314