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/. */
7 #ifndef vm_StringType_inl_h
8 #define vm_StringType_inl_h
10 #include "vm/StringType.h"
12 #include "mozilla/PodOperations.h"
13 #include "mozilla/Range.h"
15 #include "gc/GCEnum.h"
16 #include "gc/MaybeRooted.h"
17 #include "gc/StoreBuffer.h"
18 #include "js/UniquePtr.h"
19 #include "vm/StaticStrings.h"
21 #include "gc/GCContext-inl.h"
22 #include "gc/StoreBuffer-inl.h"
23 #include "vm/JSContext-inl.h"
27 // Allocate a thin inline string if possible, and a fat inline string if not.
28 template <AllowGC allowGC
, typename CharT
>
29 static MOZ_ALWAYS_INLINE JSInlineString
* AllocateInlineString(
30 JSContext
* cx
, size_t len
, CharT
** chars
, js::gc::Heap heap
) {
31 MOZ_ASSERT(JSInlineString::lengthFits
<CharT
>(len
));
33 if (JSThinInlineString::lengthFits
<CharT
>(len
)) {
34 return cx
->newCell
<JSThinInlineString
, allowGC
>(heap
, len
, chars
);
36 return cx
->newCell
<JSFatInlineString
, allowGC
>(heap
, len
, chars
);
39 template <typename CharT
>
40 static MOZ_ALWAYS_INLINE JSAtom
* AllocateInlineAtom(JSContext
* cx
, size_t len
,
42 js::HashNumber hash
) {
43 MOZ_ASSERT(JSInlineString::lengthFits
<CharT
>(len
));
45 if (JSThinInlineString::lengthFits
<CharT
>(len
)) {
46 return cx
->newCell
<js::NormalAtom
, js::NoGC
>(len
, chars
, hash
);
48 return cx
->newCell
<js::FatInlineAtom
, js::NoGC
>(len
, chars
, hash
);
51 // Create a thin inline string if possible, and a fat inline string if not.
52 template <AllowGC allowGC
, typename CharT
>
53 static MOZ_ALWAYS_INLINE JSInlineString
* NewInlineString(
54 JSContext
* cx
, mozilla::Range
<const CharT
> chars
,
55 js::gc::Heap heap
= js::gc::Heap::Default
) {
57 * Don't bother trying to find a static atom; measurement shows that not
58 * many get here (for one, Atomize is catching them).
61 size_t len
= chars
.length();
63 JSInlineString
* str
= AllocateInlineString
<allowGC
>(cx
, len
, &storage
, heap
);
68 mozilla::PodCopy(storage
, chars
.begin().get(), len
);
72 // Create a thin inline string if possible, and a fat inline string if not.
73 template <AllowGC allowGC
, typename CharT
, size_t N
>
74 static MOZ_ALWAYS_INLINE JSInlineString
* NewInlineString(
75 JSContext
* cx
, const CharT (&chars
)[N
], size_t len
,
76 js::gc::Heap heap
= js::gc::Heap::Default
) {
80 * Don't bother trying to find a static atom; measurement shows that not
81 * many get here (for one, Atomize is catching them).
85 JSInlineString
* str
= AllocateInlineString
<allowGC
>(cx
, len
, &storage
, heap
);
90 if (JSThinInlineString::lengthFits
<CharT
>(len
)) {
91 constexpr size_t MaxLength
= std::is_same_v
<CharT
, Latin1Char
>
92 ? JSThinInlineString::MAX_LENGTH_LATIN1
93 : JSThinInlineString::MAX_LENGTH_TWO_BYTE
;
95 // memcpy with a constant length can be optimized more easily by compilers.
96 constexpr size_t toCopy
= std::min(N
, MaxLength
) * sizeof(CharT
);
97 std::memcpy(storage
, chars
, toCopy
);
99 constexpr size_t MaxLength
= std::is_same_v
<CharT
, Latin1Char
>
100 ? JSFatInlineString::MAX_LENGTH_LATIN1
101 : JSFatInlineString::MAX_LENGTH_TWO_BYTE
;
103 // memcpy with a constant length can be optimized more easily by compilers.
104 constexpr size_t toCopy
= std::min(N
, MaxLength
) * sizeof(CharT
);
105 std::memcpy(storage
, chars
, toCopy
);
110 template <typename CharT
>
111 static MOZ_ALWAYS_INLINE JSAtom
* NewInlineAtom(JSContext
* cx
,
114 js::HashNumber hash
) {
116 JSAtom
* str
= AllocateInlineAtom(cx
, length
, &storage
, hash
);
121 mozilla::PodCopy(storage
, chars
, length
);
125 // Create a thin inline string if possible, and a fat inline string if not.
126 template <typename CharT
>
127 static MOZ_ALWAYS_INLINE JSInlineString
* NewInlineString(
128 JSContext
* cx
, Handle
<JSLinearString
*> base
, size_t start
, size_t length
,
130 MOZ_ASSERT(JSInlineString::lengthFits
<CharT
>(length
));
133 JSInlineString
* s
= AllocateInlineString
<CanGC
>(cx
, length
, &chars
, heap
);
138 JS::AutoCheckCannotGC nogc
;
139 mozilla::PodCopy(chars
, base
->chars
<CharT
>(nogc
) + start
, length
);
143 template <typename CharT
>
144 static MOZ_ALWAYS_INLINE JSLinearString
* TryEmptyOrStaticString(
145 JSContext
* cx
, const CharT
* chars
, size_t n
) {
146 // Measurements on popular websites indicate empty strings are pretty common
147 // and most strings with length 1 or 2 are in the StaticStrings table. For
148 // length 3 strings that's only about 1%, so we check n <= 2.
151 return cx
->emptyString();
154 if (JSLinearString
* str
= cx
->staticStrings().lookup(chars
, n
)) {
164 MOZ_ALWAYS_INLINE
bool JSString::validateLength(JSContext
* maybecx
,
166 return validateLengthInternal
<js::CanGC
>(maybecx
, length
);
169 template <js::AllowGC allowGC
>
170 MOZ_ALWAYS_INLINE
bool JSString::validateLengthInternal(JSContext
* maybecx
,
172 if (MOZ_UNLIKELY(length
> JSString::MAX_LENGTH
)) {
173 if constexpr (allowGC
) {
174 js::ReportOversizedAllocation(maybecx
, JSMSG_ALLOC_OVERFLOW
);
183 MOZ_ALWAYS_INLINE
const char16_t
* JSString::nonInlineCharsRaw() const {
184 return d
.s
.u2
.nonInlineCharsTwoByte
;
188 MOZ_ALWAYS_INLINE
const JS::Latin1Char
* JSString::nonInlineCharsRaw() const {
189 return d
.s
.u2
.nonInlineCharsLatin1
;
192 inline JSRope::JSRope(JSString
* left
, JSString
* right
, size_t length
) {
193 // JITs expect rope children aren't empty.
194 MOZ_ASSERT(!left
->empty() && !right
->empty());
196 // |length| must be the sum of the length of both child nodes.
197 MOZ_ASSERT(left
->length() + right
->length() == length
);
199 bool isLatin1
= left
->hasLatin1Chars() && right
->hasLatin1Chars();
201 // Do not try to make a rope that could fit inline.
202 MOZ_ASSERT_IF(!isLatin1
, !JSInlineString::lengthFits
<char16_t
>(length
));
203 MOZ_ASSERT_IF(isLatin1
, !JSInlineString::lengthFits
<JS::Latin1Char
>(length
));
206 setLengthAndFlags(length
, INIT_ROPE_FLAGS
| LATIN1_CHARS_BIT
);
208 setLengthAndFlags(length
, INIT_ROPE_FLAGS
);
211 d
.s
.u3
.right
= right
;
213 // Post-barrier by inserting into the whole cell buffer if either
214 // this -> left or this -> right is a tenured -> nursery edge.
216 js::gc::StoreBuffer
* sb
= left
->storeBuffer();
218 sb
= right
->storeBuffer();
221 sb
->putWholeCell(this);
226 template <js::AllowGC allowGC
>
227 MOZ_ALWAYS_INLINE JSRope
* JSRope::new_(
229 typename
js::MaybeRooted
<JSString
*, allowGC
>::HandleType left
,
230 typename
js::MaybeRooted
<JSString
*, allowGC
>::HandleType right
,
231 size_t length
, js::gc::Heap heap
) {
232 if (MOZ_UNLIKELY(!validateLengthInternal
<allowGC
>(cx
, length
))) {
235 return cx
->newCell
<JSRope
, allowGC
>(heap
, left
, right
, length
);
238 inline JSDependentString::JSDependentString(JSLinearString
* base
, size_t start
,
240 MOZ_ASSERT(start
+ length
<= base
->length());
241 JS::AutoCheckCannotGC nogc
;
242 if (base
->hasLatin1Chars()) {
243 setLengthAndFlags(length
, INIT_DEPENDENT_FLAGS
| LATIN1_CHARS_BIT
);
244 d
.s
.u2
.nonInlineCharsLatin1
= base
->latin1Chars(nogc
) + start
;
246 setLengthAndFlags(length
, INIT_DEPENDENT_FLAGS
);
247 d
.s
.u2
.nonInlineCharsTwoByte
= base
->twoByteChars(nogc
) + start
;
250 if (isTenured() && !base
->isTenured()) {
251 base
->storeBuffer()->putWholeCell(this);
255 MOZ_ALWAYS_INLINE JSLinearString
* JSDependentString::new_(
256 JSContext
* cx
, JSLinearString
* baseArg
, size_t start
, size_t length
,
258 // Do not try to make a dependent string that could fit inline.
259 MOZ_ASSERT_IF(baseArg
->hasTwoByteChars(),
260 !JSInlineString::lengthFits
<char16_t
>(length
));
261 MOZ_ASSERT_IF(!baseArg
->hasTwoByteChars(),
262 !JSInlineString::lengthFits
<JS::Latin1Char
>(length
));
265 * Try to avoid long chains of dependent strings. We can't avoid these
266 * entirely, however, due to how ropes are flattened.
268 if (baseArg
->isDependent()) {
269 start
+= baseArg
->asDependent().baseOffset();
270 baseArg
= baseArg
->asDependent().base();
273 MOZ_ASSERT(start
+ length
<= baseArg
->length());
275 JSDependentString
* str
=
276 cx
->newCell
<JSDependentString
, js::NoGC
>(heap
, baseArg
, start
, length
);
281 JS::Rooted
<JSLinearString
*> base(cx
, baseArg
);
282 return cx
->newCell
<JSDependentString
>(heap
, base
, start
, length
);
285 inline JSLinearString::JSLinearString(const char16_t
* chars
, size_t length
) {
286 setLengthAndFlags(length
, INIT_LINEAR_FLAGS
);
287 // Check that the new buffer is located in the StringBufferArena
288 checkStringCharsArena(chars
);
289 d
.s
.u2
.nonInlineCharsTwoByte
= chars
;
292 inline JSLinearString::JSLinearString(const JS::Latin1Char
* chars
,
294 setLengthAndFlags(length
, INIT_LINEAR_FLAGS
| LATIN1_CHARS_BIT
);
295 // Check that the new buffer is located in the StringBufferArena
296 checkStringCharsArena(chars
);
297 d
.s
.u2
.nonInlineCharsLatin1
= chars
;
300 void JSLinearString::disownCharsBecauseError() {
301 setLengthAndFlags(0, INIT_LINEAR_FLAGS
| LATIN1_CHARS_BIT
);
302 d
.s
.u2
.nonInlineCharsLatin1
= nullptr;
305 template <js::AllowGC allowGC
, typename CharT
>
306 MOZ_ALWAYS_INLINE JSLinearString
* JSLinearString::new_(
307 JSContext
* cx
, js::UniquePtr
<CharT
[], JS::FreePolicy
> chars
, size_t length
,
309 if (MOZ_UNLIKELY(!validateLengthInternal
<allowGC
>(cx
, length
))) {
313 return newValidLength
<allowGC
>(cx
, std::move(chars
), length
, heap
);
316 template <js::AllowGC allowGC
, typename CharT
>
317 MOZ_ALWAYS_INLINE JSLinearString
* JSLinearString::newValidLength(
318 JSContext
* cx
, js::UniquePtr
<CharT
[], JS::FreePolicy
> chars
, size_t length
,
320 MOZ_ASSERT(!cx
->zone()->isAtomsZone());
321 MOZ_ASSERT(!JSInlineString::lengthFits
<CharT
>(length
));
323 JSLinearString
* str
=
324 cx
->newCell
<JSLinearString
, allowGC
>(heap
, chars
.get(), length
);
329 if (!str
->isTenured()) {
330 // If the following registration fails, the string is partially initialized
331 // and must be made valid, or its finalizer may attempt to free
332 // uninitialized memory.
333 if (!cx
->runtime()->gc
.nursery().registerMallocedBuffer(
334 chars
.get(), length
* sizeof(CharT
))) {
335 str
->disownCharsBecauseError();
337 ReportOutOfMemory(cx
);
342 cx
->zone()->addCellMemory(str
, length
* sizeof(CharT
),
343 js::MemoryUse::StringContents
);
346 (void)chars
.release();
350 template <typename CharT
>
351 MOZ_ALWAYS_INLINE JSAtom
* JSAtom::newValidLength(
352 JSContext
* cx
, js::UniquePtr
<CharT
[], JS::FreePolicy
> chars
, size_t length
,
353 js::HashNumber hash
) {
354 MOZ_ASSERT(validateLength(cx
, length
));
355 MOZ_ASSERT(cx
->zone()->isAtomsZone());
357 cx
->newCell
<js::NormalAtom
, js::NoGC
>(chars
.get(), length
, hash
);
361 (void)chars
.release();
363 MOZ_ASSERT(str
->isTenured());
364 cx
->zone()->addCellMemory(str
, length
* sizeof(CharT
),
365 js::MemoryUse::StringContents
);
370 inline js::PropertyName
* JSLinearString::toPropertyName(JSContext
* cx
) {
373 MOZ_ASSERT(!isIndex(&dummy
));
376 return asAtom().asPropertyName();
378 JSAtom
* atom
= js::AtomizeString(cx
, this);
382 return atom
->asPropertyName();
385 template <js::AllowGC allowGC
>
386 MOZ_ALWAYS_INLINE JSThinInlineString
* JSThinInlineString::new_(
387 JSContext
* cx
, js::gc::Heap heap
) {
388 MOZ_ASSERT(!cx
->zone()->isAtomsZone());
389 return cx
->newCell
<JSThinInlineString
, allowGC
>(heap
);
392 template <js::AllowGC allowGC
>
393 MOZ_ALWAYS_INLINE JSFatInlineString
* JSFatInlineString::new_(
394 JSContext
* cx
, js::gc::Heap heap
) {
395 MOZ_ASSERT(!cx
->zone()->isAtomsZone());
396 return cx
->newCell
<JSFatInlineString
, allowGC
>(heap
);
399 inline JSThinInlineString::JSThinInlineString(size_t length
,
400 JS::Latin1Char
** chars
) {
401 MOZ_ASSERT(lengthFits
<JS::Latin1Char
>(length
));
402 setLengthAndFlags(length
, INIT_THIN_INLINE_FLAGS
| LATIN1_CHARS_BIT
);
403 *chars
= d
.inlineStorageLatin1
;
406 inline JSThinInlineString::JSThinInlineString(size_t length
, char16_t
** chars
) {
407 MOZ_ASSERT(lengthFits
<char16_t
>(length
));
408 setLengthAndFlags(length
, INIT_THIN_INLINE_FLAGS
);
409 *chars
= d
.inlineStorageTwoByte
;
412 inline JSFatInlineString::JSFatInlineString(size_t length
,
413 JS::Latin1Char
** chars
) {
414 MOZ_ASSERT(lengthFits
<JS::Latin1Char
>(length
));
415 setLengthAndFlags(length
, INIT_FAT_INLINE_FLAGS
| LATIN1_CHARS_BIT
);
416 *chars
= d
.inlineStorageLatin1
;
419 inline JSFatInlineString::JSFatInlineString(size_t length
, char16_t
** chars
) {
420 MOZ_ASSERT(lengthFits
<char16_t
>(length
));
421 setLengthAndFlags(length
, INIT_FAT_INLINE_FLAGS
);
422 *chars
= d
.inlineStorageTwoByte
;
425 inline JSExternalString::JSExternalString(
426 const char16_t
* chars
, size_t length
,
427 const JSExternalStringCallbacks
* callbacks
) {
428 MOZ_ASSERT(callbacks
);
429 setLengthAndFlags(length
, EXTERNAL_FLAGS
);
430 d
.s
.u2
.nonInlineCharsTwoByte
= chars
;
431 d
.s
.u3
.externalCallbacks
= callbacks
;
434 MOZ_ALWAYS_INLINE JSExternalString
* JSExternalString::new_(
435 JSContext
* cx
, const char16_t
* chars
, size_t length
,
436 const JSExternalStringCallbacks
* callbacks
) {
437 if (MOZ_UNLIKELY(!validateLength(cx
, length
))) {
440 auto* str
= cx
->newCell
<JSExternalString
>(chars
, length
, callbacks
);
444 size_t nbytes
= length
* sizeof(char16_t
);
446 MOZ_ASSERT(str
->isTenured());
447 js::AddCellMemory(str
, nbytes
, js::MemoryUse::StringContents
);
452 inline js::NormalAtom::NormalAtom(size_t length
, JS::Latin1Char
** chars
,
455 MOZ_ASSERT(JSInlineString::lengthFits
<JS::Latin1Char
>(length
));
456 setLengthAndFlags(length
,
457 INIT_THIN_INLINE_FLAGS
| LATIN1_CHARS_BIT
| ATOM_BIT
);
458 *chars
= d
.inlineStorageLatin1
;
461 inline js::NormalAtom::NormalAtom(size_t length
, char16_t
** chars
,
464 MOZ_ASSERT(JSInlineString::lengthFits
<char16_t
>(length
));
465 setLengthAndFlags(length
, INIT_THIN_INLINE_FLAGS
| ATOM_BIT
);
466 *chars
= d
.inlineStorageTwoByte
;
469 inline js::NormalAtom::NormalAtom(const char16_t
* chars
, size_t length
,
472 setLengthAndFlags(length
, INIT_LINEAR_FLAGS
| ATOM_BIT
);
473 // Check that the new buffer is located in the StringBufferArena
474 checkStringCharsArena(chars
);
475 d
.s
.u2
.nonInlineCharsTwoByte
= chars
;
478 inline js::NormalAtom::NormalAtom(const JS::Latin1Char
* chars
, size_t length
,
481 setLengthAndFlags(length
, INIT_LINEAR_FLAGS
| LATIN1_CHARS_BIT
| ATOM_BIT
);
482 // Check that the new buffer is located in the StringBufferArena
483 checkStringCharsArena(chars
);
484 d
.s
.u2
.nonInlineCharsLatin1
= chars
;
487 inline js::FatInlineAtom::FatInlineAtom(size_t length
, JS::Latin1Char
** chars
,
490 MOZ_ASSERT(JSFatInlineString::lengthFits
<JS::Latin1Char
>(length
));
491 setLengthAndFlags(length
,
492 INIT_FAT_INLINE_FLAGS
| LATIN1_CHARS_BIT
| ATOM_BIT
);
493 *chars
= d
.inlineStorageLatin1
;
496 inline js::FatInlineAtom::FatInlineAtom(size_t length
, char16_t
** chars
,
499 MOZ_ASSERT(JSFatInlineString::lengthFits
<char16_t
>(length
));
500 setLengthAndFlags(length
, INIT_FAT_INLINE_FLAGS
| ATOM_BIT
);
501 *chars
= d
.inlineStorageTwoByte
;
504 inline JSLinearString
* js::StaticStrings::getUnitString(JSContext
* cx
,
506 if (c
< UNIT_STATIC_LIMIT
) {
509 return js::NewInlineString
<CanGC
>(cx
, {c
}, 1);
512 inline JSLinearString
* js::StaticStrings::getUnitStringForElement(
513 JSContext
* cx
, JSString
* str
, size_t index
) {
514 MOZ_ASSERT(index
< str
->length());
517 if (!str
->getChar(cx
, index
, &c
)) {
520 return getUnitString(cx
, c
);
523 inline JSLinearString
* js::StaticStrings::getUnitStringForElement(
524 JSContext
* cx
, JSLinearString
* str
, size_t index
) {
525 MOZ_ASSERT(index
< str
->length());
527 char16_t c
= str
->latin1OrTwoByteChar(index
);
528 return getUnitString(cx
, c
);
531 MOZ_ALWAYS_INLINE
void JSString::finalize(JS::GCContext
* gcx
) {
532 /* FatInline strings are in a different arena. */
533 MOZ_ASSERT(getAllocKind() != js::gc::AllocKind::FAT_INLINE_STRING
);
534 MOZ_ASSERT(getAllocKind() != js::gc::AllocKind::FAT_INLINE_ATOM
);
537 asLinear().finalize(gcx
);
539 MOZ_ASSERT(isRope());
543 inline void JSLinearString::finalize(JS::GCContext
* gcx
) {
544 MOZ_ASSERT(getAllocKind() != js::gc::AllocKind::FAT_INLINE_STRING
);
545 MOZ_ASSERT(getAllocKind() != js::gc::AllocKind::FAT_INLINE_ATOM
);
547 if (!isInline() && !isDependent()) {
548 gcx
->free_(this, nonInlineCharsRaw(), allocSize(),
549 js::MemoryUse::StringContents
);
553 inline void JSFatInlineString::finalize(JS::GCContext
* gcx
) {
554 MOZ_ASSERT(getAllocKind() == js::gc::AllocKind::FAT_INLINE_STRING
);
555 MOZ_ASSERT(isInline());
560 inline void js::FatInlineAtom::finalize(JS::GCContext
* gcx
) {
561 MOZ_ASSERT(JSString::isAtom());
562 MOZ_ASSERT(getAllocKind() == js::gc::AllocKind::FAT_INLINE_ATOM
);
567 inline void JSExternalString::finalize(JS::GCContext
* gcx
) {
568 MOZ_ASSERT(JSString::isExternal());
570 size_t nbytes
= length() * sizeof(char16_t
);
571 gcx
->removeCellMemory(this, nbytes
, js::MemoryUse::StringContents
);
573 callbacks()->finalize(const_cast<char16_t
*>(rawTwoByteChars()));
576 #endif /* vm_StringType_inl_h */