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/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"
35 
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
39 
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
44 // (0x6 is unused)
45 #define JSID_TYPE_MASK 0x7
46 
47 namespace JS {
48 
49 enum class SymbolCode : uint32_t;
50 
51 struct PropertyKey {
52   size_t asBits;
53 
PropertyKeyPropertyKey54   constexpr PropertyKey() : asBits(JSID_TYPE_VOID) {}
55 
fromRawBitsPropertyKey56   static constexpr MOZ_ALWAYS_INLINE PropertyKey fromRawBits(size_t bits) {
57     PropertyKey id;
58     id.asBits = bits;
59     return id;
60   }
61 
62   bool operator==(const PropertyKey& rhs) const { return asBits == rhs.asBits; }
63   bool operator!=(const PropertyKey& rhs) const { return asBits != rhs.asBits; }
64 
isVoidPropertyKey65   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;
69   }
70 
isIntPropertyKey71   MOZ_ALWAYS_INLINE bool isInt() const {
72     return !!(asBits & JSID_TYPE_INT_BIT);
73   }
74 
isStringPropertyKey75   MOZ_ALWAYS_INLINE bool isString() const {
76     return (asBits & JSID_TYPE_MASK) == JSID_TYPE_STRING;
77   }
78 
isSymbolPropertyKey79   MOZ_ALWAYS_INLINE bool isSymbol() const {
80     return (asBits & JSID_TYPE_MASK) == JSID_TYPE_SYMBOL;
81   }
82 
isGCThingPropertyKey83   MOZ_ALWAYS_INLINE bool isGCThing() const { return isString() || isSymbol(); }
84 
toIntPropertyKey85   MOZ_ALWAYS_INLINE int32_t toInt() const {
86     MOZ_ASSERT(isInt());
87     uint32_t bits = static_cast<uint32_t>(asBits) >> 1;
88     return static_cast<int32_t>(bits);
89   }
90 
toStringPropertyKey91   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);
96   }
97 
toSymbolPropertyKey98   MOZ_ALWAYS_INLINE JS::Symbol* toSymbol() const {
99     MOZ_ASSERT(isSymbol());
100     return reinterpret_cast<JS::Symbol*>(asBits ^ JSID_TYPE_SYMBOL);
101   }
102 
toGCThingPropertyKey103   js::gc::Cell* toGCThing() const {
104     MOZ_ASSERT(isGCThing());
105     return reinterpret_cast<js::gc::Cell*>(asBits & ~(size_t)JSID_TYPE_MASK);
106   }
107 
toGCCellPtrPropertyKey108   GCCellPtr toGCCellPtr() const {
109     js::gc::Cell* thing = toGCThing();
110     if (isString()) {
111       return JS::GCCellPtr(thing, JS::TraceKind::String);
112     }
113     MOZ_ASSERT(isSymbol());
114     return JS::GCCellPtr(thing, JS::TraceKind::Symbol);
115   }
116 
117   bool isPrivateName() const;
118 
119   bool isWellKnownSymbol(JS::SymbolCode code) const;
120 
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.
124   //
125   // Only use this API when absolutely necessary, otherwise use JS_StringToId.
126   static PropertyKey fromPinnedString(JSString* str);
127 
128   // Must not be used on atoms that are representable as integer PropertyKey.
129   // Prefer NameToId or AtomToId over this function:
130   //
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:
138   //
139   // - When given an arbitrary JSAtom*, AtomToId must be used, which checks for
140   //   integer atoms representable as integer PropertyKey, and does this
141   //   conversion.
142   //
143   // - When given a PropertyName*, NameToId can be used which does not need
144   //   to do any dynamic checks.
145   //
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
148   // PropertyKey.
fromNonIntAtomPropertyKey149   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);
153   }
154 
155   // The JSAtom/JSString type exposed to embedders is opaque.
fromNonIntAtomPropertyKey156   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);
160   }
161 
162   // Internal API!
163   // All string PropertyKeys are actually atomized.
isAtomPropertyKey164   MOZ_ALWAYS_INLINE bool isAtom() const { return isString(); }
165 
isAtomPropertyKey166   MOZ_ALWAYS_INLINE bool isAtom(JSAtom* atom) const {
167     MOZ_ASSERT(PropertyKey::isNonIntAtom(atom));
168     return isAtom() && toAtom() == atom;
169   }
170 
toAtomPropertyKey171   MOZ_ALWAYS_INLINE JSAtom* toAtom() const { return (JSAtom*)toString(); }
172 
173  private:
174   static bool isNonIntAtom(JSAtom* atom);
175   static bool isNonIntAtom(JSString* atom);
176 } JS_HAZ_GC_POINTER;
177 
178 }  // namespace JS
179 
180 using jsid = JS::PropertyKey;
181 
182 #define JSID_BITS(id) (id.asBits)
183 
JSID_IS_STRING(jsid id)184 static MOZ_ALWAYS_INLINE bool JSID_IS_STRING(jsid id) { return id.isString(); }
185 
JSID_TO_STRING(jsid id)186 static MOZ_ALWAYS_INLINE JSString* JSID_TO_STRING(jsid id) {
187   return id.toString();
188 }
189 
JSID_IS_INT(jsid id)190 static MOZ_ALWAYS_INLINE bool JSID_IS_INT(jsid id) { return id.isInt(); }
191 
JSID_TO_INT(jsid id)192 static MOZ_ALWAYS_INLINE int32_t JSID_TO_INT(jsid id) { return id.toInt(); }
193 
194 #define JSID_INT_MIN 0
195 #define JSID_INT_MAX INT32_MAX
196 
INT_FITS_IN_JSID(int32_t i)197 static MOZ_ALWAYS_INLINE bool INT_FITS_IN_JSID(int32_t i) { return i >= 0; }
198 
INT_TO_JSID(int32_t i)199 static MOZ_ALWAYS_INLINE jsid INT_TO_JSID(int32_t i) {
200   jsid id;
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);
204   return id;
205 }
206 
SYMBOL_TO_JSID(JS::Symbol * sym)207 static MOZ_ALWAYS_INLINE jsid SYMBOL_TO_JSID(JS::Symbol* sym) {
208   jsid id;
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);
213   return id;
214 }
215 
JSID_IS_VOID(const jsid id)216 static MOZ_ALWAYS_INLINE bool JSID_IS_VOID(const jsid id) {
217   return id.isVoid();
218 }
219 
220 constexpr const jsid JSID_VOID;
221 
222 extern JS_PUBLIC_DATA const JS::HandleId JSID_VOIDHANDLE;
223 
224 namespace JS {
225 
226 template <>
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
230     // marking.
231     UnsafeTraceRoot(trc, idp, name);
232   }
233   static bool isValid(jsid id) {
234     return !id.isGCThing() ||
235            js::gc::IsCellPointerValid(id.toGCCellPtr().asCell());
236   }
237 
238   static bool isTenured(jsid id) {
239     MOZ_ASSERT_IF(id.isGCThing(),
240                   !js::gc::IsInsideNursery(id.toGCCellPtr().asCell()));
241     return true;
242   }
243 };
244 
245 #ifdef DEBUG
246 MOZ_ALWAYS_INLINE void AssertIdIsNotGray(jsid id) {
247   if (id.isGCThing()) {
248     AssertCellIsNotGray(id.toGCCellPtr().asCell());
249   }
250 }
251 #endif
252 
253 }  // namespace JS
254 
255 namespace js {
256 
257 template <>
258 struct BarrierMethods<jsid> {
259   static gc::Cell* asGCThingOrNull(jsid id) {
260     if (id.isGCThing()) {
261       return id.toGCThing();
262     }
263     return nullptr;
264   }
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)));
268   }
269   static void exposeToJS(jsid id) {
270     if (id.isGCThing()) {
271       js::gc::ExposeGCThingToActiveJS(id.toGCCellPtr());
272     }
273   }
274 };
275 
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) {
280   if (id.isString()) {
281     return mozilla::Some(f(id.toString()));
282   }
283   if (id.isSymbol()) {
284     return mozilla::Some(f(id.toSymbol()));
285   }
286   MOZ_ASSERT(!id.isGCThing());
287   using ReturnType = decltype(f(static_cast<JSString*>(nullptr)));
288   return mozilla::Maybe<ReturnType>();
289 }
290 
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,
296                          [&f](auto t) {
297                            f(t);
298                            return true;
299                          })
300       .isSome();
301 }
302 
303 template <typename Wrapper>
304 class WrappedPtrOperations<JS::PropertyKey, Wrapper> {
305   const JS::PropertyKey& id() const {
306     return static_cast<const Wrapper*>(this)->get();
307   }
308 
309  public:
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(); }
315 
316   int32_t toInt() const { return id().toInt(); }
317   JSString* toString() const { return id().toString(); }
318   JS::Symbol* toSymbol() const { return id().toSymbol(); }
319 
320   bool isPrivateName() const { return id().isPrivateName(); }
321 
322   bool isWellKnownSymbol(JS::SymbolCode code) const {
323     return id().isWellKnownSymbol(code);
324   }
325 
326   // Internal API
327   bool isAtom() const { return id().isAtom(); }
328   bool isAtom(JSAtom* atom) const { return id().isAtom(atom); }
329   JSAtom* toAtom() const { return id().toAtom(); }
330 };
331 
332 }  // namespace js
333 
334 #endif /* js_Id_h */
335