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 vm_StringType_inl_h
8 #define vm_StringType_inl_h
9 
10 #include "vm/StringType.h"
11 
12 #include "mozilla/PodOperations.h"
13 #include "mozilla/Range.h"
14 
15 #include "gc/Allocator.h"
16 #include "gc/Marking.h"
17 #include "gc/MaybeRooted.h"
18 #include "gc/StoreBuffer.h"
19 #include "js/UniquePtr.h"
20 #include "vm/JSContext.h"
21 #include "vm/Realm.h"
22 #include "vm/StaticStrings.h"
23 
24 #include "gc/FreeOp-inl.h"
25 #include "gc/StoreBuffer-inl.h"
26 
27 namespace js {
28 
29 // Allocate a thin inline string if possible, and a fat inline string if not.
30 template <AllowGC allowGC, typename CharT>
AllocateInlineString(JSContext * cx,size_t len,CharT ** chars,js::gc::InitialHeap heap)31 static MOZ_ALWAYS_INLINE JSInlineString* AllocateInlineString(
32     JSContext* cx, size_t len, CharT** chars, js::gc::InitialHeap heap) {
33   MOZ_ASSERT(JSInlineString::lengthFits<CharT>(len));
34 
35   if (JSThinInlineString::lengthFits<CharT>(len)) {
36     JSThinInlineString* str = JSThinInlineString::new_<allowGC>(cx, heap);
37     if (!str) {
38       return nullptr;
39     }
40     *chars = str->init<CharT>(len);
41     return str;
42   }
43 
44   JSFatInlineString* str = JSFatInlineString::new_<allowGC>(cx, heap);
45   if (!str) {
46     return nullptr;
47   }
48   *chars = str->init<CharT>(len);
49   return str;
50 }
51 
52 template <typename CharT>
AllocateInlineStringForAtom(JSContext * cx,size_t len,CharT ** chars)53 static MOZ_ALWAYS_INLINE JSInlineString* AllocateInlineStringForAtom(
54     JSContext* cx, size_t len, CharT** chars) {
55   MOZ_ASSERT(JSInlineString::lengthFits<CharT>(len));
56 
57   if (JSThinInlineString::lengthFits<CharT>(len)) {
58     JSThinInlineString* str = JSThinInlineString::newForAtom(cx);
59     if (!str) {
60       return nullptr;
61     }
62     *chars = str->init<CharT>(len);
63     return str;
64   }
65 
66   JSFatInlineString* str = JSFatInlineString::newForAtom(cx);
67   if (!str) {
68     return nullptr;
69   }
70   *chars = str->init<CharT>(len);
71   return str;
72 }
73 
74 // Create a thin inline string if possible, and a fat inline string if not.
75 template <AllowGC allowGC, typename CharT>
76 static MOZ_ALWAYS_INLINE JSInlineString* NewInlineString(
77     JSContext* cx, mozilla::Range<const CharT> chars,
78     js::gc::InitialHeap heap = js::gc::DefaultHeap) {
79   /*
80    * Don't bother trying to find a static atom; measurement shows that not
81    * many get here (for one, Atomize is catching them).
82    */
83 
84   size_t len = chars.length();
85   CharT* storage;
86   JSInlineString* str = AllocateInlineString<allowGC>(cx, len, &storage, heap);
87   if (!str) {
88     return nullptr;
89   }
90 
91   mozilla::PodCopy(storage, chars.begin().get(), len);
92   return str;
93 }
94 
95 template <typename CharT>
NewInlineStringForAtom(JSContext * cx,const CharT * chars,size_t length)96 static MOZ_ALWAYS_INLINE JSInlineString* NewInlineStringForAtom(
97     JSContext* cx, const CharT* chars, size_t length) {
98   CharT* storage;
99   JSInlineString* str = AllocateInlineStringForAtom(cx, length, &storage);
100   if (!str) {
101     return nullptr;
102   }
103 
104   mozilla::PodCopy(storage, chars, length);
105   return str;
106 }
107 
108 // Create a thin inline string if possible, and a fat inline string if not.
109 template <typename CharT>
NewInlineString(JSContext * cx,HandleLinearString base,size_t start,size_t length,js::gc::InitialHeap heap)110 static MOZ_ALWAYS_INLINE JSInlineString* NewInlineString(
111     JSContext* cx, HandleLinearString base, size_t start, size_t length,
112     js::gc::InitialHeap heap) {
113   MOZ_ASSERT(JSInlineString::lengthFits<CharT>(length));
114 
115   CharT* chars;
116   JSInlineString* s = AllocateInlineString<CanGC>(cx, length, &chars, heap);
117   if (!s) {
118     return nullptr;
119   }
120 
121   JS::AutoCheckCannotGC nogc;
122   mozilla::PodCopy(chars, base->chars<CharT>(nogc) + start, length);
123   return s;
124 }
125 
126 template <typename CharT>
TryEmptyOrStaticString(JSContext * cx,const CharT * chars,size_t n)127 static MOZ_ALWAYS_INLINE JSLinearString* TryEmptyOrStaticString(
128     JSContext* cx, const CharT* chars, size_t n) {
129   // Measurements on popular websites indicate empty strings are pretty common
130   // and most strings with length 1 or 2 are in the StaticStrings table. For
131   // length 3 strings that's only about 1%, so we check n <= 2.
132   if (n <= 2) {
133     if (n == 0) {
134       return cx->emptyString();
135     }
136 
137     if (JSLinearString* str = cx->staticStrings().lookup(chars, n)) {
138       return str;
139     }
140   }
141 
142   return nullptr;
143 }
144 
145 } /* namespace js */
146 
validateLength(JSContext * maybecx,size_t length)147 MOZ_ALWAYS_INLINE bool JSString::validateLength(JSContext* maybecx,
148                                                 size_t length) {
149   return validateLengthInternal<js::CanGC>(maybecx, length);
150 }
151 
152 template <js::AllowGC allowGC>
validateLengthInternal(JSContext * maybecx,size_t length)153 MOZ_ALWAYS_INLINE bool JSString::validateLengthInternal(JSContext* maybecx,
154                                                         size_t length) {
155   if (MOZ_UNLIKELY(length > JSString::MAX_LENGTH)) {
156     if constexpr (allowGC) {
157       js::ReportOversizedAllocation(maybecx, JSMSG_ALLOC_OVERFLOW);
158     }
159     return false;
160   }
161 
162   return true;
163 }
164 
165 template <>
nonInlineCharsRaw()166 MOZ_ALWAYS_INLINE const char16_t* JSString::nonInlineCharsRaw() const {
167   return d.s.u2.nonInlineCharsTwoByte;
168 }
169 
170 template <>
nonInlineCharsRaw()171 MOZ_ALWAYS_INLINE const JS::Latin1Char* JSString::nonInlineCharsRaw() const {
172   return d.s.u2.nonInlineCharsLatin1;
173 }
174 
init(JSContext * cx,JSString * left,JSString * right,size_t length)175 MOZ_ALWAYS_INLINE void JSRope::init(JSContext* cx, JSString* left,
176                                     JSString* right, size_t length) {
177   if (left->hasLatin1Chars() && right->hasLatin1Chars()) {
178     setLengthAndFlags(length, INIT_ROPE_FLAGS | LATIN1_CHARS_BIT);
179   } else {
180     setLengthAndFlags(length, INIT_ROPE_FLAGS);
181   }
182   d.s.u2.left = left;
183   d.s.u3.right = right;
184 
185   // Post-barrier by inserting into the whole cell buffer if either
186   // this -> left or this -> right is a tenured -> nursery edge.
187   if (isTenured()) {
188     js::gc::StoreBuffer* sb = left->storeBuffer();
189     if (!sb) {
190       sb = right->storeBuffer();
191     }
192     if (sb) {
193       sb->putWholeCell(this);
194     }
195   }
196 }
197 
198 template <js::AllowGC allowGC>
new_(JSContext * cx,typename js::MaybeRooted<JSString *,allowGC>::HandleType left,typename js::MaybeRooted<JSString *,allowGC>::HandleType right,size_t length,js::gc::InitialHeap heap)199 MOZ_ALWAYS_INLINE JSRope* JSRope::new_(
200     JSContext* cx,
201     typename js::MaybeRooted<JSString*, allowGC>::HandleType left,
202     typename js::MaybeRooted<JSString*, allowGC>::HandleType right,
203     size_t length, js::gc::InitialHeap heap) {
204   if (MOZ_UNLIKELY(!validateLengthInternal<allowGC>(cx, length))) {
205     return nullptr;
206   }
207   JSRope* str = js::AllocateString<JSRope, allowGC>(cx, heap);
208   if (!str) {
209     return nullptr;
210   }
211   str->init(cx, left, right, length);
212   return str;
213 }
214 
init(JSContext * cx,JSLinearString * base,size_t start,size_t length)215 MOZ_ALWAYS_INLINE void JSDependentString::init(JSContext* cx,
216                                                JSLinearString* base,
217                                                size_t start, size_t length) {
218   MOZ_ASSERT(start + length <= base->length());
219   JS::AutoCheckCannotGC nogc;
220   if (base->hasLatin1Chars()) {
221     setLengthAndFlags(length, INIT_DEPENDENT_FLAGS | LATIN1_CHARS_BIT);
222     d.s.u2.nonInlineCharsLatin1 = base->latin1Chars(nogc) + start;
223   } else {
224     setLengthAndFlags(length, INIT_DEPENDENT_FLAGS);
225     d.s.u2.nonInlineCharsTwoByte = base->twoByteChars(nogc) + start;
226   }
227   d.s.u3.base = base;
228   if (isTenured() && !base->isTenured()) {
229     base->storeBuffer()->putWholeCell(this);
230   }
231 }
232 
new_(JSContext * cx,JSLinearString * baseArg,size_t start,size_t length,js::gc::InitialHeap heap)233 MOZ_ALWAYS_INLINE JSLinearString* JSDependentString::new_(
234     JSContext* cx, JSLinearString* baseArg, size_t start, size_t length,
235     js::gc::InitialHeap heap) {
236   /*
237    * Try to avoid long chains of dependent strings. We can't avoid these
238    * entirely, however, due to how ropes are flattened.
239    */
240   if (baseArg->isDependent()) {
241     start += baseArg->asDependent().baseOffset();
242     baseArg = baseArg->asDependent().base();
243   }
244 
245   MOZ_ASSERT(start + length <= baseArg->length());
246 
247   /*
248    * Do not create a string dependent on inline chars from another string,
249    * both to avoid the awkward moving-GC hazard this introduces and because it
250    * is more efficient to immediately undepend here.
251    */
252   bool useInline = baseArg->hasTwoByteChars()
253                        ? JSInlineString::lengthFits<char16_t>(length)
254                        : JSInlineString::lengthFits<JS::Latin1Char>(length);
255   if (useInline) {
256     js::RootedLinearString base(cx, baseArg);
257     return baseArg->hasLatin1Chars()
258                ? js::NewInlineString<JS::Latin1Char>(cx, base, start, length,
259                                                      heap)
260                : js::NewInlineString<char16_t>(cx, base, start, length, heap);
261   }
262 
263   JSDependentString* str =
264       js::AllocateString<JSDependentString, js::NoGC>(cx, heap);
265   if (str) {
266     str->init(cx, baseArg, start, length);
267     return str;
268   }
269 
270   js::RootedLinearString base(cx, baseArg);
271 
272   str = js::AllocateString<JSDependentString>(cx, heap);
273   if (!str) {
274     return nullptr;
275   }
276   str->init(cx, base, start, length);
277   return str;
278 }
279 
init(const char16_t * chars,size_t length)280 MOZ_ALWAYS_INLINE void JSLinearString::init(const char16_t* chars,
281                                             size_t length) {
282   setLengthAndFlags(length, INIT_LINEAR_FLAGS);
283   // Check that the new buffer is located in the StringBufferArena
284   checkStringCharsArena(chars);
285   d.s.u2.nonInlineCharsTwoByte = chars;
286 }
287 
init(const JS::Latin1Char * chars,size_t length)288 MOZ_ALWAYS_INLINE void JSLinearString::init(const JS::Latin1Char* chars,
289                                             size_t length) {
290   setLengthAndFlags(length, INIT_LINEAR_FLAGS | LATIN1_CHARS_BIT);
291   // Check that the new buffer is located in the StringBufferArena
292   checkStringCharsArena(chars);
293   d.s.u2.nonInlineCharsLatin1 = chars;
294 }
295 
296 template <js::AllowGC allowGC, typename CharT>
new_(JSContext * cx,js::UniquePtr<CharT[],JS::FreePolicy> chars,size_t length,js::gc::InitialHeap heap)297 MOZ_ALWAYS_INLINE JSLinearString* JSLinearString::new_(
298     JSContext* cx, js::UniquePtr<CharT[], JS::FreePolicy> chars, size_t length,
299     js::gc::InitialHeap heap) {
300   if (MOZ_UNLIKELY(!validateLengthInternal<allowGC>(cx, length))) {
301     return nullptr;
302   }
303 
304   return newValidLength<allowGC>(cx, std::move(chars), length, heap);
305 }
306 
307 template <js::AllowGC allowGC, typename CharT>
newValidLength(JSContext * cx,js::UniquePtr<CharT[],JS::FreePolicy> chars,size_t length,js::gc::InitialHeap heap)308 MOZ_ALWAYS_INLINE JSLinearString* JSLinearString::newValidLength(
309     JSContext* cx, js::UniquePtr<CharT[], JS::FreePolicy> chars, size_t length,
310     js::gc::InitialHeap heap) {
311   MOZ_ASSERT(!cx->zone()->isAtomsZone());
312   JSLinearString* str = js::AllocateString<JSLinearString, allowGC>(cx, heap);
313   if (!str) {
314     return nullptr;
315   }
316 
317   if (!str->isTenured()) {
318     // If the following registration fails, the string is partially initialized
319     // and must be made valid, or its finalizer may attempt to free
320     // uninitialized memory.
321     if (!cx->runtime()->gc.nursery().registerMallocedBuffer(
322             chars.get(), length * sizeof(CharT))) {
323       str->init(static_cast<JS::Latin1Char*>(nullptr), 0);
324       if (allowGC) {
325         ReportOutOfMemory(cx);
326       }
327       return nullptr;
328     }
329   } else {
330     // This can happen off the main thread for the atoms zone.
331     cx->zone()->addCellMemory(str, length * sizeof(CharT),
332                               js::MemoryUse::StringContents);
333   }
334 
335   str->init(chars.release(), length);
336   return str;
337 }
338 
339 template <typename CharT>
newForAtomValidLength(JSContext * cx,js::UniquePtr<CharT[],JS::FreePolicy> chars,size_t length)340 MOZ_ALWAYS_INLINE JSLinearString* JSLinearString::newForAtomValidLength(
341     JSContext* cx, js::UniquePtr<CharT[], JS::FreePolicy> chars,
342     size_t length) {
343   MOZ_ASSERT(validateLength(cx, length));
344   MOZ_ASSERT(cx->zone()->isAtomsZone());
345   JSLinearString* str = js::Allocate<js::NormalAtom, js::NoGC>(cx);
346   if (!str) {
347     return nullptr;
348   }
349 
350   MOZ_ASSERT(str->isTenured());
351   cx->zone()->addCellMemory(str, length * sizeof(CharT),
352                             js::MemoryUse::StringContents);
353 
354   str->init(chars.release(), length);
355   return str;
356 }
357 
toPropertyName(JSContext * cx)358 inline js::PropertyName* JSLinearString::toPropertyName(JSContext* cx) {
359 #ifdef DEBUG
360   uint32_t dummy;
361   MOZ_ASSERT(!isIndex(&dummy));
362 #endif
363   if (isAtom()) {
364     return asAtom().asPropertyName();
365   }
366   JSAtom* atom = js::AtomizeString(cx, this);
367   if (!atom) {
368     return nullptr;
369   }
370   return atom->asPropertyName();
371 }
372 
373 template <js::AllowGC allowGC>
new_(JSContext * cx,js::gc::InitialHeap heap)374 MOZ_ALWAYS_INLINE JSThinInlineString* JSThinInlineString::new_(
375     JSContext* cx, js::gc::InitialHeap heap) {
376   MOZ_ASSERT(!cx->zone()->isAtomsZone());
377   return js::AllocateString<JSThinInlineString, allowGC>(cx, heap);
378 }
379 
newForAtom(JSContext * cx)380 MOZ_ALWAYS_INLINE JSThinInlineString* JSThinInlineString::newForAtom(
381     JSContext* cx) {
382   MOZ_ASSERT(cx->zone()->isAtomsZone());
383   return (JSThinInlineString*)(js::Allocate<js::NormalAtom, js::NoGC>(cx));
384 }
385 
386 template <js::AllowGC allowGC>
new_(JSContext * cx,js::gc::InitialHeap heap)387 MOZ_ALWAYS_INLINE JSFatInlineString* JSFatInlineString::new_(
388     JSContext* cx, js::gc::InitialHeap heap) {
389   MOZ_ASSERT(!cx->zone()->isAtomsZone());
390   return js::AllocateString<JSFatInlineString, allowGC>(cx, heap);
391 }
392 
newForAtom(JSContext * cx)393 MOZ_ALWAYS_INLINE JSFatInlineString* JSFatInlineString::newForAtom(
394     JSContext* cx) {
395   MOZ_ASSERT(cx->zone()->isAtomsZone());
396   return (JSFatInlineString*)(js::Allocate<js::FatInlineAtom, js::NoGC>(cx));
397 }
398 
399 template <>
400 MOZ_ALWAYS_INLINE JS::Latin1Char* JSThinInlineString::init<JS::Latin1Char>(
401     size_t length) {
402   MOZ_ASSERT(lengthFits<JS::Latin1Char>(length));
403   setLengthAndFlags(length, INIT_THIN_INLINE_FLAGS | LATIN1_CHARS_BIT);
404   return d.inlineStorageLatin1;
405 }
406 
407 template <>
408 MOZ_ALWAYS_INLINE char16_t* JSThinInlineString::init<char16_t>(size_t length) {
409   MOZ_ASSERT(lengthFits<char16_t>(length));
410   setLengthAndFlags(length, INIT_THIN_INLINE_FLAGS);
411   return d.inlineStorageTwoByte;
412 }
413 
414 template <>
415 MOZ_ALWAYS_INLINE JS::Latin1Char* JSFatInlineString::init<JS::Latin1Char>(
416     size_t length) {
417   MOZ_ASSERT(lengthFits<JS::Latin1Char>(length));
418   setLengthAndFlags(length, INIT_FAT_INLINE_FLAGS | LATIN1_CHARS_BIT);
419   return d.inlineStorageLatin1;
420 }
421 
422 template <>
423 MOZ_ALWAYS_INLINE char16_t* JSFatInlineString::init<char16_t>(size_t length) {
424   MOZ_ASSERT(lengthFits<char16_t>(length));
425   setLengthAndFlags(length, INIT_FAT_INLINE_FLAGS);
426   return d.inlineStorageTwoByte;
427 }
428 
init(const char16_t * chars,size_t length,const JSExternalStringCallbacks * callbacks)429 MOZ_ALWAYS_INLINE void JSExternalString::init(
430     const char16_t* chars, size_t length,
431     const JSExternalStringCallbacks* callbacks) {
432   MOZ_ASSERT(callbacks);
433   setLengthAndFlags(length, EXTERNAL_FLAGS);
434   d.s.u2.nonInlineCharsTwoByte = chars;
435   d.s.u3.externalCallbacks = callbacks;
436 }
437 
new_(JSContext * cx,const char16_t * chars,size_t length,const JSExternalStringCallbacks * callbacks)438 MOZ_ALWAYS_INLINE JSExternalString* JSExternalString::new_(
439     JSContext* cx, const char16_t* chars, size_t length,
440     const JSExternalStringCallbacks* callbacks) {
441   if (MOZ_UNLIKELY(!validateLength(cx, length))) {
442     return nullptr;
443   }
444   JSExternalString* str = js::Allocate<JSExternalString>(cx);
445   if (!str) {
446     return nullptr;
447   }
448   str->init(chars, length, callbacks);
449   size_t nbytes = length * sizeof(char16_t);
450 
451   MOZ_ASSERT(str->isTenured());
452   js::AddCellMemory(str, nbytes, js::MemoryUse::StringContents);
453 
454   return str;
455 }
456 
getUnitStringForElement(JSContext * cx,JSString * str,size_t index)457 inline JSLinearString* js::StaticStrings::getUnitStringForElement(
458     JSContext* cx, JSString* str, size_t index) {
459   MOZ_ASSERT(index < str->length());
460 
461   char16_t c;
462   if (!str->getChar(cx, index, &c)) {
463     return nullptr;
464   }
465   if (c < UNIT_STATIC_LIMIT) {
466     return getUnit(c);
467   }
468   return js::NewInlineString<CanGC>(cx, mozilla::Range<const char16_t>(&c, 1),
469                                     js::gc::DefaultHeap);
470 }
471 
finalize(JSFreeOp * fop)472 MOZ_ALWAYS_INLINE void JSString::finalize(JSFreeOp* fop) {
473   /* FatInline strings are in a different arena. */
474   MOZ_ASSERT(getAllocKind() != js::gc::AllocKind::FAT_INLINE_STRING);
475   MOZ_ASSERT(getAllocKind() != js::gc::AllocKind::FAT_INLINE_ATOM);
476 
477   if (isLinear()) {
478     asLinear().finalize(fop);
479   } else {
480     MOZ_ASSERT(isRope());
481   }
482 }
483 
finalize(JSFreeOp * fop)484 inline void JSLinearString::finalize(JSFreeOp* fop) {
485   MOZ_ASSERT(getAllocKind() != js::gc::AllocKind::FAT_INLINE_STRING);
486   MOZ_ASSERT(getAllocKind() != js::gc::AllocKind::FAT_INLINE_ATOM);
487 
488   if (!isInline() && !isDependent()) {
489     fop->free_(this, nonInlineCharsRaw(), allocSize(),
490                js::MemoryUse::StringContents);
491   }
492 }
493 
finalize(JSFreeOp * fop)494 inline void JSFatInlineString::finalize(JSFreeOp* fop) {
495   MOZ_ASSERT(getAllocKind() == js::gc::AllocKind::FAT_INLINE_STRING);
496   MOZ_ASSERT(isInline());
497 
498   // Nothing to do.
499 }
500 
finalize(JSFreeOp * fop)501 inline void js::FatInlineAtom::finalize(JSFreeOp* fop) {
502   MOZ_ASSERT(JSString::isAtom());
503   MOZ_ASSERT(getAllocKind() == js::gc::AllocKind::FAT_INLINE_ATOM);
504 
505   // Nothing to do.
506 }
507 
finalize(JSFreeOp * fop)508 inline void JSExternalString::finalize(JSFreeOp* fop) {
509   MOZ_ASSERT(JSString::isExternal());
510 
511   size_t nbytes = length() * sizeof(char16_t);
512   fop->removeCellMemory(this, nbytes, js::MemoryUse::StringContents);
513 
514   callbacks()->finalize(const_cast<char16_t*>(rawTwoByteChars()));
515 }
516 
517 #endif /* vm_StringType_inl_h */
518