Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sts=4 et sw=4 tw=99:
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 : /*
8 : * JS atom table.
9 : */
10 :
11 : #include "vm/JSAtom-inl.h"
12 :
13 : #include "mozilla/ArrayUtils.h"
14 : #include "mozilla/EndianUtils.h"
15 : #include "mozilla/RangedPtr.h"
16 : #include "mozilla/Unused.h"
17 :
18 : #include <string.h>
19 :
20 : #include "jstypes.h"
21 :
22 : #include "builtin/String.h"
23 : #include "gc/Marking.h"
24 : #include "util/Text.h"
25 : #include "vm/JSContext.h"
26 : #include "vm/SymbolType.h"
27 : #include "vm/Xdr.h"
28 :
29 : #include "gc/AtomMarking-inl.h"
30 : #include "vm/JSContext-inl.h"
31 : #include "vm/JSObject-inl.h"
32 : #include "vm/Realm-inl.h"
33 : #include "vm/StringType-inl.h"
34 :
35 : using namespace js;
36 : using namespace js::gc;
37 :
38 : using mozilla::ArrayEnd;
39 : using mozilla::ArrayLength;
40 : using mozilla::Maybe;
41 : using mozilla::Nothing;
42 : using mozilla::RangedPtr;
43 :
44 0 : struct js::AtomHasher::Lookup
45 : {
46 : union {
47 : const JS::Latin1Char* latin1Chars;
48 : const char16_t* twoByteChars;
49 : };
50 : bool isLatin1;
51 : size_t length;
52 : const JSAtom* atom; /* Optional. */
53 : JS::AutoCheckCannotGC nogc;
54 :
55 : HashNumber hash;
56 :
57 0 : MOZ_ALWAYS_INLINE Lookup(const char16_t* chars, size_t length)
58 0 : : twoByteChars(chars), isLatin1(false), length(length), atom(nullptr),
59 0 : hash(mozilla::HashString(chars, length))
60 0 : {}
61 :
62 0 : MOZ_ALWAYS_INLINE Lookup(const JS::Latin1Char* chars, size_t length)
63 0 : : latin1Chars(chars), isLatin1(true), length(length), atom(nullptr),
64 0 : hash(mozilla::HashString(chars, length))
65 0 : {}
66 :
67 0 : inline explicit Lookup(const JSAtom* atom)
68 0 : : isLatin1(atom->hasLatin1Chars()), length(atom->length()), atom(atom),
69 0 : hash(atom->hash())
70 : {
71 0 : if (isLatin1) {
72 0 : latin1Chars = atom->latin1Chars(nogc);
73 0 : MOZ_ASSERT(mozilla::HashString(latin1Chars, length) == hash);
74 : } else {
75 0 : twoByteChars = atom->twoByteChars(nogc);
76 0 : MOZ_ASSERT(mozilla::HashString(twoByteChars, length) == hash);
77 : }
78 0 : }
79 : };
80 :
81 : inline HashNumber
82 : js::AtomHasher::hash(const Lookup& l)
83 : {
84 : return l.hash;
85 : }
86 :
87 : MOZ_ALWAYS_INLINE bool
88 0 : js::AtomHasher::match(const AtomStateEntry& entry, const Lookup& lookup)
89 : {
90 0 : JSAtom* key = entry.asPtrUnbarriered();
91 0 : if (lookup.atom)
92 0 : return lookup.atom == key;
93 0 : if (key->length() != lookup.length || key->hash() != lookup.hash)
94 : return false;
95 :
96 0 : if (key->hasLatin1Chars()) {
97 0 : const Latin1Char* keyChars = key->latin1Chars(lookup.nogc);
98 0 : if (lookup.isLatin1)
99 0 : return mozilla::PodEqual(keyChars, lookup.latin1Chars, lookup.length);
100 0 : return EqualChars(keyChars, lookup.twoByteChars, lookup.length);
101 : }
102 :
103 0 : const char16_t* keyChars = key->twoByteChars(lookup.nogc);
104 0 : if (lookup.isLatin1)
105 0 : return EqualChars(lookup.latin1Chars, keyChars, lookup.length);
106 0 : return mozilla::PodEqual(keyChars, lookup.twoByteChars, lookup.length);
107 : }
108 :
109 : inline JSAtom*
110 0 : js::AtomStateEntry::asPtr(JSContext* cx) const
111 : {
112 0 : JSAtom* atom = asPtrUnbarriered();
113 0 : if (!cx->helperThread())
114 0 : JSString::readBarrier(atom);
115 0 : return atom;
116 : }
117 :
118 : const char*
119 0 : js::AtomToPrintableString(JSContext* cx, JSAtom* atom, JSAutoByteString* bytes)
120 : {
121 0 : JSString* str = QuoteString(cx, atom, 0);
122 0 : if (!str)
123 : return nullptr;
124 0 : return bytes->encodeLatin1(cx, str);
125 : }
126 :
127 : #define DEFINE_PROTO_STRING(name,init,clasp) const char js_##name##_str[] = #name;
128 : JS_FOR_EACH_PROTOTYPE(DEFINE_PROTO_STRING)
129 : #undef DEFINE_PROTO_STRING
130 :
131 : #define CONST_CHAR_STR(idpart, id, text) const char js_##idpart##_str[] = text;
132 : FOR_EACH_COMMON_PROPERTYNAME(CONST_CHAR_STR)
133 : #undef CONST_CHAR_STR
134 :
135 : // Use a low initial capacity for atom hash tables to avoid penalizing runtimes
136 : // which create a small number of atoms.
137 : static const uint32_t JS_STRING_HASH_COUNT = 64;
138 :
139 : MOZ_ALWAYS_INLINE AtomSet::Ptr
140 : js::FrozenAtomSet::readonlyThreadsafeLookup(const AtomSet::Lookup& l) const
141 : {
142 0 : return mSet->readonlyThreadsafeLookup(l);
143 : }
144 :
145 : struct CommonNameInfo
146 : {
147 : const char* str;
148 : size_t length;
149 : };
150 :
151 : bool
152 0 : JSRuntime::initializeAtoms(JSContext* cx)
153 : {
154 0 : atoms_ = js_new<AtomSet>();
155 0 : if (!atoms_ || !atoms_->init(JS_STRING_HASH_COUNT))
156 : return false;
157 :
158 : // |permanentAtoms| hasn't been created yet.
159 0 : MOZ_ASSERT(!permanentAtoms);
160 :
161 0 : if (parentRuntime) {
162 0 : staticStrings = parentRuntime->staticStrings;
163 0 : commonNames = parentRuntime->commonNames;
164 0 : emptyString = parentRuntime->emptyString;
165 0 : permanentAtoms = parentRuntime->permanentAtoms;
166 0 : wellKnownSymbols = parentRuntime->wellKnownSymbols;
167 0 : return true;
168 : }
169 :
170 0 : staticStrings = js_new<StaticStrings>();
171 0 : if (!staticStrings || !staticStrings->init(cx))
172 : return false;
173 :
174 : static const CommonNameInfo cachedNames[] = {
175 : #define COMMON_NAME_INFO(idpart, id, text) { js_##idpart##_str, sizeof(text) - 1 },
176 : FOR_EACH_COMMON_PROPERTYNAME(COMMON_NAME_INFO)
177 : #undef COMMON_NAME_INFO
178 : #define COMMON_NAME_INFO(name, init, clasp) { js_##name##_str, sizeof(#name) - 1 },
179 : JS_FOR_EACH_PROTOTYPE(COMMON_NAME_INFO)
180 : #undef COMMON_NAME_INFO
181 : #define COMMON_NAME_INFO(name) { #name, sizeof(#name) - 1 },
182 : JS_FOR_EACH_WELL_KNOWN_SYMBOL(COMMON_NAME_INFO)
183 : #undef COMMON_NAME_INFO
184 : #define COMMON_NAME_INFO(name) { "Symbol." #name, sizeof("Symbol." #name) - 1 },
185 : JS_FOR_EACH_WELL_KNOWN_SYMBOL(COMMON_NAME_INFO)
186 : #undef COMMON_NAME_INFO
187 : };
188 :
189 0 : commonNames = js_new<JSAtomState>();
190 0 : if (!commonNames)
191 : return false;
192 :
193 : ImmutablePropertyNamePtr* names = reinterpret_cast<ImmutablePropertyNamePtr*>(commonNames.ref());
194 0 : for (size_t i = 0; i < ArrayLength(cachedNames); i++, names++) {
195 0 : JSAtom* atom = Atomize(cx, cachedNames[i].str, cachedNames[i].length, PinAtom);
196 0 : if (!atom)
197 : return false;
198 0 : names->init(atom->asPropertyName());
199 : }
200 0 : MOZ_ASSERT(uintptr_t(names) == uintptr_t(commonNames + 1));
201 :
202 0 : emptyString = commonNames->empty;
203 :
204 : // Create the well-known symbols.
205 0 : wellKnownSymbols = js_new<WellKnownSymbols>();
206 0 : if (!wellKnownSymbols)
207 : return false;
208 :
209 0 : ImmutablePropertyNamePtr* descriptions = commonNames->wellKnownSymbolDescriptions();
210 0 : ImmutableSymbolPtr* symbols = reinterpret_cast<ImmutableSymbolPtr*>(wellKnownSymbols.ref());
211 0 : for (size_t i = 0; i < JS::WellKnownSymbolLimit; i++) {
212 0 : JS::Symbol* symbol = JS::Symbol::new_(cx, JS::SymbolCode(i), descriptions[i]);
213 0 : if (!symbol) {
214 0 : ReportOutOfMemory(cx);
215 0 : return false;
216 : }
217 0 : symbols[i].init(symbol);
218 : }
219 :
220 : return true;
221 : }
222 :
223 : void
224 0 : JSRuntime::finishAtoms()
225 : {
226 0 : js_delete(atoms_.ref());
227 :
228 0 : if (!parentRuntime) {
229 0 : js_delete(staticStrings.ref());
230 0 : js_delete(commonNames.ref());
231 0 : js_delete(permanentAtoms.ref());
232 0 : js_delete(wellKnownSymbols.ref());
233 : }
234 :
235 0 : atoms_ = nullptr;
236 0 : staticStrings = nullptr;
237 0 : commonNames = nullptr;
238 0 : permanentAtoms = nullptr;
239 0 : wellKnownSymbols = nullptr;
240 0 : emptyString = nullptr;
241 0 : }
242 :
243 : static inline void
244 0 : TracePinnedAtoms(JSTracer* trc, const AtomSet& atoms)
245 : {
246 0 : for (auto r = atoms.all(); !r.empty(); r.popFront()) {
247 0 : const AtomStateEntry& entry = r.front();
248 0 : MOZ_ASSERT(entry.isPinned() == entry.asPtrUnbarriered()->isPinned());
249 0 : if (entry.isPinned()) {
250 0 : JSAtom* atom = entry.asPtrUnbarriered();
251 0 : TraceRoot(trc, &atom, "interned_atom");
252 0 : MOZ_ASSERT(entry.asPtrUnbarriered() == atom);
253 : }
254 : }
255 0 : }
256 :
257 : void
258 0 : js::TraceAtoms(JSTracer* trc, AutoLockForExclusiveAccess& lock)
259 : {
260 0 : JSRuntime* rt = trc->runtime();
261 :
262 0 : if (rt->atomsAreFinished())
263 : return;
264 :
265 0 : TracePinnedAtoms(trc, rt->atoms(lock));
266 0 : if (rt->atomsAddedWhileSweeping())
267 0 : TracePinnedAtoms(trc, *rt->atomsAddedWhileSweeping());
268 : }
269 :
270 : void
271 0 : js::TracePermanentAtoms(JSTracer* trc)
272 : {
273 0 : JSRuntime* rt = trc->runtime();
274 :
275 : // Permanent atoms only need to be traced in the runtime which owns them.
276 0 : if (rt->parentRuntime)
277 : return;
278 :
279 : // Static strings are not included in the permanent atoms table.
280 0 : if (rt->staticStrings)
281 0 : rt->staticStrings->trace(trc);
282 :
283 0 : if (rt->permanentAtoms) {
284 0 : for (FrozenAtomSet::Range r(rt->permanentAtoms->all()); !r.empty(); r.popFront()) {
285 0 : const AtomStateEntry& entry = r.front();
286 :
287 0 : JSAtom* atom = entry.asPtrUnbarriered();
288 0 : MOZ_ASSERT(atom->isPinned());
289 0 : TraceProcessGlobalRoot(trc, atom, "permanent atom");
290 : }
291 : }
292 : }
293 :
294 : void
295 0 : js::TraceWellKnownSymbols(JSTracer* trc)
296 : {
297 0 : JSRuntime* rt = trc->runtime();
298 :
299 0 : if (rt->parentRuntime)
300 : return;
301 :
302 0 : if (WellKnownSymbols* wks = rt->wellKnownSymbols) {
303 0 : for (size_t i = 0; i < JS::WellKnownSymbolLimit; i++)
304 0 : TraceProcessGlobalRoot(trc, wks->get(i).get(), "well_known_symbol");
305 : }
306 : }
307 :
308 : bool
309 0 : JSRuntime::transformToPermanentAtoms(JSContext* cx)
310 : {
311 0 : MOZ_ASSERT(!parentRuntime);
312 :
313 : // All static strings were created as permanent atoms, now move the contents
314 : // of the atoms table into permanentAtoms and mark each as permanent.
315 :
316 0 : MOZ_ASSERT(!permanentAtoms);
317 0 : permanentAtoms = js_new<FrozenAtomSet>(atoms_); // takes ownership of atoms_
318 :
319 0 : atoms_ = js_new<AtomSet>();
320 0 : if (!atoms_ || !atoms_->init(JS_STRING_HASH_COUNT))
321 : return false;
322 :
323 0 : for (FrozenAtomSet::Range r(permanentAtoms->all()); !r.empty(); r.popFront()) {
324 0 : AtomStateEntry entry = r.front();
325 0 : JSAtom* atom = entry.asPtr(cx);
326 0 : atom->morphIntoPermanentAtom();
327 : }
328 :
329 0 : return true;
330 : }
331 :
332 : static inline AtomSet::Ptr
333 0 : LookupAtomState(JSRuntime* rt, const AtomHasher::Lookup& lookup)
334 : {
335 0 : MOZ_ASSERT(rt->currentThreadHasExclusiveAccess());
336 :
337 0 : AtomSet::Ptr p = rt->unsafeAtoms().lookup(lookup); // Safe because we hold the lock.
338 0 : if (!p && rt->atomsAddedWhileSweeping())
339 0 : p = rt->atomsAddedWhileSweeping()->lookup(lookup);
340 0 : return p;
341 : }
342 :
343 : template <typename CharT>
344 : MOZ_ALWAYS_INLINE
345 : static JSAtom*
346 : AtomizeAndCopyCharsInner(JSContext* cx, const CharT* tbchars, size_t length, PinningBehavior pin,
347 : const Maybe<uint32_t>& indexValue, const AtomHasher::Lookup& lookup);
348 :
349 : /* |tbchars| must not point into an inline or short string. */
350 : template <typename CharT>
351 : MOZ_ALWAYS_INLINE
352 : static JSAtom*
353 0 : AtomizeAndCopyChars(JSContext* cx, const CharT* tbchars, size_t length, PinningBehavior pin,
354 : const Maybe<uint32_t>& indexValue)
355 : {
356 0 : if (JSAtom* s = cx->staticStrings().lookup(tbchars, length))
357 : return s;
358 :
359 0 : AtomHasher::Lookup lookup(tbchars, length);
360 :
361 : // Try the per-Zone cache first. If we find the atom there we can avoid the
362 : // atoms lock, the markAtom call, and the multiple HashSet lookups below.
363 : // We don't use the per-Zone cache if we want a pinned atom: handling that
364 : // is more complicated and pinning atoms is relatively uncommon.
365 0 : Zone* zone = cx->zone();
366 0 : Maybe<AtomSet::AddPtr> zonePtr;
367 0 : if (MOZ_LIKELY(zone && pin == DoNotPinAtom)) {
368 0 : zonePtr.emplace(zone->atomCache().lookupForAdd(lookup));
369 0 : if (zonePtr.ref()) {
370 : // The cache is purged on GC so if we're in the middle of an
371 : // incremental GC we should have barriered the atom when we put
372 : // it in the cache.
373 0 : JSAtom* atom = zonePtr.ref()->asPtrUnbarriered();
374 0 : MOZ_ASSERT(AtomIsMarked(zone, atom));
375 : return atom;
376 : }
377 : }
378 :
379 : // Note: when this function is called while the permanent atoms table is
380 : // being initialized (in initializeAtoms()), |permanentAtoms| is not yet
381 : // initialized so this lookup is always skipped. Only once
382 : // transformToPermanentAtoms() is called does |permanentAtoms| get
383 : // initialized and then this lookup will go ahead.
384 0 : if (cx->isPermanentAtomsInitialized()) {
385 0 : AtomSet::Ptr pp = cx->permanentAtoms().readonlyThreadsafeLookup(lookup);
386 0 : if (pp) {
387 0 : JSAtom* atom = pp->asPtr(cx);
388 0 : if (zonePtr && !zone->atomCache().add(*zonePtr, AtomStateEntry(atom, false))) {
389 0 : ReportOutOfMemory(cx);
390 0 : return nullptr;
391 : }
392 :
393 : return atom;
394 : }
395 : }
396 :
397 : // Validate the length before taking the exclusive access lock, as throwing
398 : // an exception here may reenter this code.
399 0 : if (MOZ_UNLIKELY(!JSString::validateLength(cx, length)))
400 : return nullptr;
401 :
402 66681 : JSAtom* atom = AtomizeAndCopyCharsInner(cx, tbchars, length, pin, indexValue, lookup);
403 0 : if (!atom)
404 : return nullptr;
405 :
406 0 : cx->atomMarking().inlinedMarkAtom(cx, atom);
407 :
408 250407 : if (zonePtr && !zone->atomCache().add(*zonePtr, AtomStateEntry(atom, false))) {
409 0 : ReportOutOfMemory(cx);
410 0 : return nullptr;
411 : }
412 :
413 : return atom;
414 : }
415 :
416 : template <typename CharT>
417 : MOZ_ALWAYS_INLINE
418 : static JSAtom*
419 0 : AtomizeAndCopyCharsInner(JSContext* cx, const CharT* tbchars, size_t length, PinningBehavior pin,
420 : const Maybe<uint32_t>& indexValue, const AtomHasher::Lookup& lookup)
421 : {
422 0 : AutoLockForExclusiveAccess lock(cx);
423 :
424 0 : JSRuntime* rt = cx->runtime();
425 0 : AtomSet& atoms = rt->atoms(lock);
426 66682 : AtomSet* atomsAddedWhileSweeping = rt->atomsAddedWhileSweeping();
427 66682 : AtomSet::AddPtr p;
428 :
429 0 : if (!atomsAddedWhileSweeping) {
430 0 : p = atoms.lookupForAdd(lookup);
431 : } else {
432 : // We're currently sweeping the main atoms table and all new atoms will
433 : // be added to a secondary table. Check this first.
434 0 : MOZ_ASSERT(rt->atomsZone(lock)->isGCSweeping());
435 0 : p = atomsAddedWhileSweeping->lookupForAdd(lookup);
436 :
437 : // If that fails check the main table but check if any atom found there
438 : // is dead.
439 0 : if (!p) {
440 0 : if (AtomSet::AddPtr p2 = atoms.lookupForAdd(lookup)) {
441 0 : JSAtom* atom = p2->asPtrUnbarriered();
442 0 : if (!IsAboutToBeFinalizedUnbarriered(&atom))
443 0 : p = p2;
444 : }
445 : }
446 : }
447 :
448 66682 : if (p) {
449 18134 : JSAtom* atom = p->asPtr(cx);
450 21944 : if (pin && !atom->isPinned()) {
451 1144 : atom->setPinned();
452 1144 : p->setPinned(true);
453 : }
454 : return atom;
455 : }
456 :
457 : JSAtom* atom;
458 : {
459 97096 : AutoAtomsZone az(cx, lock);
460 :
461 0 : JSFlatString* flat = NewStringCopyN<NoGC>(cx, tbchars, length);
462 0 : if (!flat) {
463 : // Grudgingly forgo last-ditch GC. The alternative would be to release
464 : // the lock, manually GC here, and retry from the top. If you fix this,
465 : // please also fix or comment the similar case in Symbol::new_.
466 0 : ReportOutOfMemory(cx);
467 0 : return nullptr;
468 : }
469 :
470 48548 : atom = flat->morphAtomizedStringIntoAtom(lookup.hash);
471 0 : MOZ_ASSERT(atom->hash() == lookup.hash);
472 :
473 48548 : if (pin)
474 4349 : atom->setPinned();
475 :
476 48548 : if (indexValue)
477 0 : atom->maybeInitializeIndex(*indexValue, true);
478 :
479 : // We have held the lock since looking up p, and the operations we've done
480 : // since then can't GC; therefore the atoms table has not been modified and
481 : // p is still valid.
482 48548 : AtomSet* addSet = atomsAddedWhileSweeping ? atomsAddedWhileSweeping : &atoms;
483 97096 : if (!addSet->add(p, AtomStateEntry(atom, bool(pin)))) {
484 0 : ReportOutOfMemory(cx); /* SystemAllocPolicy does not report OOM. */
485 0 : return nullptr;
486 : }
487 : }
488 :
489 48548 : return atom;
490 : }
491 :
492 : template JSAtom*
493 : AtomizeAndCopyChars(JSContext* cx, const char16_t* tbchars, size_t length, PinningBehavior pin,
494 : const Maybe<uint32_t>& indexValue);
495 :
496 : template JSAtom*
497 : AtomizeAndCopyChars(JSContext* cx, const Latin1Char* tbchars, size_t length, PinningBehavior pin,
498 : const Maybe<uint32_t>& indexValue);
499 :
500 : JSAtom*
501 34701 : js::AtomizeString(JSContext* cx, JSString* str,
502 : js::PinningBehavior pin /* = js::DoNotPinAtom */)
503 : {
504 69402 : if (str->isAtom()) {
505 0 : JSAtom& atom = str->asAtom();
506 : /* N.B. static atoms are effectively always interned. */
507 0 : if (pin != PinAtom || atom.isPinned())
508 : return &atom;
509 :
510 0 : AtomHasher::Lookup lookup(&atom);
511 :
512 0 : AutoLockForExclusiveAccess lock(cx);
513 :
514 0 : AtomSet::Ptr p = LookupAtomState(cx->runtime(), lookup);
515 0 : MOZ_ASSERT(p); // Unpinned atoms must exist in atoms table.
516 0 : MOZ_ASSERT(p->asPtrUnbarriered() == &atom);
517 :
518 0 : MOZ_ASSERT(pin == PinAtom);
519 0 : atom.setPinned();
520 0 : p->setPinned(true);
521 :
522 : return &atom;
523 : }
524 :
525 0 : JSLinearString* linear = str->ensureLinear(cx);
526 0 : if (!linear)
527 : return nullptr;
528 :
529 0 : Maybe<uint32_t> indexValue;
530 0 : if (str->hasIndexValue())
531 0 : indexValue.emplace(str->getIndexValue());
532 :
533 32626 : JS::AutoCheckCannotGC nogc;
534 16313 : return linear->hasLatin1Chars()
535 0 : ? AtomizeAndCopyChars(cx, linear->latin1Chars(nogc), linear->length(), pin, indexValue)
536 18269 : : AtomizeAndCopyChars(cx, linear->twoByteChars(nogc), linear->length(), pin, indexValue);
537 : }
538 :
539 : JSAtom*
540 0 : js::Atomize(JSContext* cx, const char* bytes, size_t length, PinningBehavior pin,
541 : const Maybe<uint32_t>& indexValue)
542 : {
543 90226 : CHECK_REQUEST(cx);
544 :
545 45114 : const Latin1Char* chars = reinterpret_cast<const Latin1Char*>(bytes);
546 0 : return AtomizeAndCopyChars(cx, chars, length, pin, indexValue);
547 : }
548 :
549 : template <typename CharT>
550 : JSAtom*
551 309892 : js::AtomizeChars(JSContext* cx, const CharT* chars, size_t length, PinningBehavior pin)
552 : {
553 619783 : CHECK_REQUEST(cx);
554 929673 : return AtomizeAndCopyChars(cx, chars, length, pin, Nothing());
555 : }
556 :
557 : template JSAtom*
558 : js::AtomizeChars(JSContext* cx, const Latin1Char* chars, size_t length, PinningBehavior pin);
559 :
560 : template JSAtom*
561 : js::AtomizeChars(JSContext* cx, const char16_t* chars, size_t length, PinningBehavior pin);
562 :
563 : JSAtom*
564 1351 : js::AtomizeUTF8Chars(JSContext* cx, const char* utf8Chars, size_t utf8ByteLength)
565 : {
566 : // This could be optimized to hand the char16_t's directly to the JSAtom
567 : // instead of making a copy. UTF8CharsToNewTwoByteCharsZ should be
568 : // refactored to take an JSContext so that this function could also.
569 :
570 1351 : UTF8Chars utf8(utf8Chars, utf8ByteLength);
571 :
572 : size_t length;
573 4053 : UniqueTwoByteChars chars(JS::UTF8CharsToNewTwoByteCharsZ(cx, utf8, &length).get());
574 1351 : if (!chars)
575 : return nullptr;
576 :
577 2702 : return AtomizeChars(cx, chars.get(), length);
578 : }
579 :
580 : bool
581 0 : js::IndexToIdSlow(JSContext* cx, uint32_t index, MutableHandleId idp)
582 : {
583 0 : MOZ_ASSERT(index > JSID_INT_MAX);
584 :
585 : char16_t buf[UINT32_CHAR_BUFFER_LENGTH];
586 0 : RangedPtr<char16_t> end(ArrayEnd(buf), buf, ArrayEnd(buf));
587 0 : RangedPtr<char16_t> start = BackfillIndexInCharBuffer(index, end);
588 :
589 0 : JSAtom* atom = AtomizeChars(cx, start.get(), end - start);
590 0 : if (!atom)
591 : return false;
592 :
593 0 : idp.set(JSID_FROM_BITS((size_t)atom | JSID_TYPE_STRING));
594 0 : return true;
595 : }
596 :
597 : template <AllowGC allowGC>
598 : static JSAtom*
599 0 : ToAtomSlow(JSContext* cx, typename MaybeRooted<Value, allowGC>::HandleType arg)
600 : {
601 0 : MOZ_ASSERT(!arg.isString());
602 :
603 0 : Value v = arg;
604 0 : if (!v.isPrimitive()) {
605 0 : MOZ_ASSERT(!cx->helperThread());
606 : if (!allowGC)
607 0 : return nullptr;
608 0 : RootedValue v2(cx, v);
609 0 : if (!ToPrimitive(cx, JSTYPE_STRING, &v2))
610 0 : return nullptr;
611 0 : v = v2;
612 : }
613 :
614 0 : if (v.isString()) {
615 0 : JSAtom* atom = AtomizeString(cx, v.toString());
616 0 : if (!allowGC && !atom)
617 0 : cx->recoverFromOutOfMemory();
618 0 : return atom;
619 : }
620 0 : if (v.isInt32()) {
621 0 : JSAtom* atom = Int32ToAtom(cx, v.toInt32());
622 0 : if (!allowGC && !atom)
623 0 : cx->recoverFromOutOfMemory();
624 0 : return atom;
625 : }
626 0 : if (v.isDouble()) {
627 0 : JSAtom* atom = NumberToAtom(cx, v.toDouble());
628 0 : if (!allowGC && !atom)
629 0 : cx->recoverFromOutOfMemory();
630 0 : return atom;
631 : }
632 0 : if (v.isBoolean())
633 0 : return v.toBoolean() ? cx->names().true_ : cx->names().false_;
634 0 : if (v.isNull())
635 0 : return cx->names().null;
636 0 : if (v.isSymbol()) {
637 0 : MOZ_ASSERT(!cx->helperThread());
638 : if (allowGC) {
639 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
640 : JSMSG_SYMBOL_TO_STRING);
641 : }
642 0 : return nullptr;
643 : }
644 : #ifdef ENABLE_BIGINT
645 : if (v.isBigInt()) {
646 : JSAtom* atom = BigIntToAtom(cx, v.toBigInt());
647 : if (!allowGC && !atom)
648 : cx->recoverFromOutOfMemory();
649 : return atom;
650 : }
651 : #endif
652 0 : MOZ_ASSERT(v.isUndefined());
653 0 : return cx->names().undefined;
654 : }
655 :
656 : template <AllowGC allowGC>
657 : JSAtom*
658 0 : js::ToAtom(JSContext* cx, typename MaybeRooted<Value, allowGC>::HandleType v)
659 : {
660 0 : if (!v.isString())
661 0 : return ToAtomSlow<allowGC>(cx, v);
662 :
663 0 : JSString* str = v.toString();
664 1 : if (str->isAtom())
665 1 : return &str->asAtom();
666 :
667 0 : JSAtom* atom = AtomizeString(cx, str);
668 0 : if (!atom && !allowGC) {
669 0 : MOZ_ASSERT_IF(!cx->helperThread(), cx->isThrowingOutOfMemory());
670 0 : cx->recoverFromOutOfMemory();
671 : }
672 11799 : return atom;
673 : }
674 :
675 : template JSAtom*
676 : js::ToAtom<CanGC>(JSContext* cx, HandleValue v);
677 :
678 : template JSAtom*
679 : js::ToAtom<NoGC>(JSContext* cx, const Value& v);
680 :
681 : template<XDRMode mode>
682 : XDRResult
683 304105 : js::XDRAtom(XDRState<mode>* xdr, MutableHandleAtom atomp)
684 : {
685 0 : bool latin1 = false;
686 0 : uint32_t length = 0;
687 0 : uint32_t lengthAndEncoding = 0;
688 : if (mode == XDR_ENCODE) {
689 : static_assert(JSString::MAX_LENGTH <= INT32_MAX, "String length must fit in 31 bits");
690 0 : latin1 = atomp->hasLatin1Chars();
691 608210 : length = atomp->length();
692 304105 : lengthAndEncoding = (length << 1) | uint32_t(latin1);
693 : }
694 :
695 912315 : MOZ_TRY(xdr->codeUint32(&lengthAndEncoding));
696 :
697 : if (mode == XDR_DECODE) {
698 0 : length = lengthAndEncoding >> 1;
699 0 : latin1 = lengthAndEncoding & 0x1;
700 : }
701 :
702 : // We need to align the string in the XDR buffer such that we can avoid
703 : // non-align loads of 16bits characters.
704 0 : if (!latin1)
705 0 : MOZ_TRY(xdr->codeAlign(sizeof(char16_t)));
706 :
707 : if (mode == XDR_ENCODE) {
708 608210 : JS::AutoCheckCannotGC nogc;
709 304105 : if (latin1)
710 912261 : return xdr->codeChars(atomp->latin1Chars(nogc), length);
711 0 : return xdr->codeChars(const_cast<char16_t*>(atomp->twoByteChars(nogc)), length);
712 : }
713 :
714 : MOZ_ASSERT(mode == XDR_DECODE);
715 : /* Avoid JSString allocation for already existing atoms. See bug 321985. */
716 0 : JSContext* cx = xdr->cx();
717 : JSAtom* atom;
718 0 : if (latin1) {
719 0 : const Latin1Char* chars = nullptr;
720 0 : if (length) {
721 : const uint8_t *ptr;
722 0 : size_t nbyte = length * sizeof(Latin1Char);
723 0 : MOZ_TRY(xdr->peekData(&ptr, nbyte));
724 0 : chars = reinterpret_cast<const Latin1Char*>(ptr);
725 : }
726 0 : atom = AtomizeChars(cx, chars, length);
727 : } else {
728 : #if MOZ_LITTLE_ENDIAN
729 : /* Directly access the little endian chars in the XDR buffer. */
730 0 : const char16_t* chars = nullptr;
731 0 : if (length) {
732 : const uint8_t *ptr;
733 0 : size_t nbyte = length * sizeof(char16_t);
734 0 : MOZ_TRY(xdr->peekData(&ptr, nbyte));
735 0 : MOZ_ASSERT(reinterpret_cast<uintptr_t>(ptr) % sizeof(char16_t) == 0,
736 : "non-aligned buffer during JSAtom decoding");
737 0 : chars = reinterpret_cast<const char16_t*>(ptr);
738 : }
739 0 : atom = AtomizeChars(cx, chars, length);
740 : #else
741 : /*
742 : * We must copy chars to a temporary buffer to convert between little and
743 : * big endian data.
744 : */
745 : char16_t* chars;
746 : char16_t stackChars[256];
747 : if (length <= ArrayLength(stackChars)) {
748 : chars = stackChars;
749 : } else {
750 : /*
751 : * This is very uncommon. Don't use the tempLifoAlloc arena for this as
752 : * most allocations here will be bigger than tempLifoAlloc's default
753 : * chunk size.
754 : */
755 : chars = cx->pod_malloc<char16_t>(length);
756 : if (!chars)
757 : return xdr->fail(JS::TranscodeResult_Throw);
758 : }
759 :
760 : MOZ_TRY(xdr->codeChars(chars, length));
761 : atom = AtomizeChars(cx, chars, length);
762 : if (chars != stackChars)
763 : js_free(chars);
764 : #endif /* !MOZ_LITTLE_ENDIAN */
765 : }
766 :
767 0 : if (!atom)
768 0 : return xdr->fail(JS::TranscodeResult_Throw);
769 0 : atomp.set(atom);
770 0 : return Ok();
771 : }
772 :
773 : template XDRResult
774 : js::XDRAtom(XDRState<XDR_ENCODE>* xdr, MutableHandleAtom atomp);
775 :
776 : template XDRResult
777 : js::XDRAtom(XDRState<XDR_DECODE>* xdr, MutableHandleAtom atomp);
778 :
779 : Handle<PropertyName*>
780 603 : js::ClassName(JSProtoKey key, JSContext* cx)
781 : {
782 603 : return ClassName(key, cx->names());
783 : }
784 :
785 : void
786 0 : js::gc::MergeAtomsAddedWhileSweeping(JSRuntime* rt)
787 : {
788 : // Add atoms that were added to the secondary table while we were sweeping
789 : // the main table.
790 :
791 0 : AutoEnterOOMUnsafeRegion oomUnsafe;
792 0 : AtomSet* atomsTable = rt->atomsForSweeping();
793 0 : MOZ_ASSERT(atomsTable);
794 : for (auto r = rt->atomsAddedWhileSweeping()->all(); !r.empty(); r.popFront()) {
795 : if (!atomsTable->putNew(AtomHasher::Lookup(r.front().asPtrUnbarriered()), r.front()))
796 : oomUnsafe.crash("Adding atom from secondary table after sweep");
797 : }
798 : }
|