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