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