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 #include "double-conversion/double-conversion.h"
8 #include "mozilla/CheckedInt.h"
9 #include "mozilla/MathAlgorithms.h"
10 #include "mozilla/MemoryReporting.h"
11 #include "mozilla/Printf.h"
12 #include "mozilla/ResultExtensions.h"
14 #include "nsASCIIMask.h"
15 #include "nsCharTraits.h"
16 #include "nsISupports.h"
21 # include "nsStringStats.h"
23 # define STRING_STAT_INCREMENT(_s)
26 // It's not worthwhile to reallocate the buffer and memcpy the
27 // contents over when the size difference isn't large. With
28 // power-of-two allocation buckets and 64 as the typical inline
29 // capacity, considering that above 1000 there performance aspects
30 // of realloc and memcpy seem to be absorbed, relative to the old
31 // code, by the performance benefits of the new code being exact,
32 // we need to choose which transitions of 256 to 128, 512 to 256
33 // and 1024 to 512 to allow. As a guess, let's pick the middle
34 // one as the the largest potential transition that we forgo. So
35 // we'll shrink from 1024 bucket to 512 bucket but not from 512
36 // bucket to 256 bucket. We'll decide by comparing the difference
37 // of capacities. As bucket differences, the differences are 256
38 // and 512. Since the capacities have various overheads, we
39 // can't compare with 256 or 512 exactly but it's easier to
40 // compare to some number that's between the two, so it's
41 // far away from either to ignore the overheads.
42 const uint32_t kNsStringBufferShrinkingThreshold
= 384;
44 using double_conversion::DoubleToStringConverter
;
46 // ---------------------------------------------------------------------------
48 static const char16_t gNullChar
= 0;
50 char* const nsCharTraits
<char>::sEmptyBuffer
=
51 (char*)const_cast<char16_t
*>(&gNullChar
);
52 char16_t
* const nsCharTraits
<char16_t
>::sEmptyBuffer
=
53 const_cast<char16_t
*>(&gNullChar
);
55 // ---------------------------------------------------------------------------
57 static void ReleaseData(void* aData
, nsAString::DataFlags aFlags
) {
58 if (aFlags
& nsAString::DataFlags::REFCOUNTED
) {
59 nsStringBuffer::FromData(aData
)->Release();
60 } else if (aFlags
& nsAString::DataFlags::OWNED
) {
61 // Treat this as destruction of a "StringAdopt" object for leak
63 MOZ_LOG_DTOR(aData
, "StringAdopt", 1);
66 STRING_STAT_INCREMENT(AdoptFree
);
68 // otherwise, nothing to do.
71 // ---------------------------------------------------------------------------
73 #ifdef XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE
75 nsTSubstring
<T
>::nsTSubstring(char_type
* aData
, size_type aLength
,
76 DataFlags aDataFlags
, ClassFlags aClassFlags
)
77 : ::mozilla::detail::nsTStringRepr
<T
>(aData
, aLength
, aDataFlags
,
81 if (aDataFlags
& DataFlags::OWNED
) {
82 STRING_STAT_INCREMENT(Adopt
);
83 MOZ_LOG_CTOR(this->mData
, "StringAdopt", 1);
86 #endif /* XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE */
89 * helper function for down-casting a nsTSubstring to an nsTAutoString.
92 inline const nsTAutoString
<T
>* AsAutoString(const nsTSubstring
<T
>* aStr
) {
93 return static_cast<const nsTAutoString
<T
>*>(aStr
);
97 mozilla::Result
<mozilla::BulkWriteHandle
<T
>, nsresult
>
98 nsTSubstring
<T
>::BulkWrite(size_type aCapacity
, size_type aPrefixToPreserve
,
99 bool aAllowShrinking
) {
100 auto r
= StartBulkWriteImpl(aCapacity
, aPrefixToPreserve
, aAllowShrinking
);
101 if (MOZ_UNLIKELY(r
.isErr())) {
102 return r
.propagateErr();
104 return mozilla::BulkWriteHandle
<T
>(this, r
.unwrap());
107 template <typename T
>
108 auto nsTSubstring
<T
>::StartBulkWriteImpl(size_type aCapacity
,
109 size_type aPrefixToPreserve
,
110 bool aAllowShrinking
,
111 size_type aSuffixLength
,
112 size_type aOldSuffixStart
,
113 size_type aNewSuffixStart
)
114 -> mozilla::Result
<size_type
, nsresult
> {
115 // Note! Capacity does not include room for the terminating null char.
117 MOZ_ASSERT(aPrefixToPreserve
<= aCapacity
,
118 "Requested preservation of an overlong prefix.");
119 MOZ_ASSERT(aNewSuffixStart
+ aSuffixLength
<= aCapacity
,
120 "Requesed move of suffix to out-of-bounds location.");
121 // Can't assert aOldSuffixStart, because mLength may not be valid anymore,
122 // since this method allows itself to be called more than once.
124 // If zero capacity is requested, set the string to the special empty
126 if (MOZ_UNLIKELY(!aCapacity
)) {
127 ReleaseData(this->mData
, this->mDataFlags
);
132 // Note! Capacity() returns 0 when the string is immutable.
133 const size_type curCapacity
= Capacity();
135 bool shrinking
= false;
137 // We've established that aCapacity > 0.
138 // |curCapacity == 0| means that the buffer is immutable or 0-sized, so we
139 // need to allocate a new buffer. We cannot use the existing buffer even
140 // though it might be large enough.
142 if (aCapacity
<= curCapacity
) {
143 if (aAllowShrinking
) {
146 char_traits::move(this->mData
+ aNewSuffixStart
,
147 this->mData
+ aOldSuffixStart
, aSuffixLength
);
149 char_traits::uninitialize(this->mData
+ aPrefixToPreserve
,
150 XPCOM_MIN(aNewSuffixStart
- aPrefixToPreserve
,
151 kNsStringBufferMaxPoison
));
152 char_traits::uninitialize(
153 this->mData
+ aNewSuffixStart
+ aSuffixLength
,
154 XPCOM_MIN(curCapacity
+ 1 - aNewSuffixStart
- aSuffixLength
,
155 kNsStringBufferMaxPoison
));
157 char_traits::uninitialize(this->mData
+ aPrefixToPreserve
,
158 XPCOM_MIN(curCapacity
+ 1 - aPrefixToPreserve
,
159 kNsStringBufferMaxPoison
));
165 char_type
* oldData
= this->mData
;
166 DataFlags oldFlags
= this->mDataFlags
;
169 DataFlags newDataFlags
;
170 size_type newCapacity
;
172 // If this is an nsTAutoStringN, it's possible that we can use the inline
174 if ((this->mClassFlags
& ClassFlags::INLINE
) &&
175 (aCapacity
<= AsAutoString(this)->mInlineCapacity
)) {
176 newCapacity
= AsAutoString(this)->mInlineCapacity
;
177 newData
= (char_type
*)AsAutoString(this)->mStorage
;
178 newDataFlags
= DataFlags::TERMINATED
| DataFlags::INLINE
;
180 // If |aCapacity > kMaxCapacity|, then our doubling algorithm may not be
181 // able to allocate it. Just bail out in cases like that. We don't want
182 // to be allocating 2GB+ strings anyway.
183 static_assert((sizeof(nsStringBuffer
) & 0x1) == 0,
184 "bad size for nsStringBuffer");
185 if (MOZ_UNLIKELY(!this->CheckCapacity(aCapacity
))) {
186 return mozilla::Err(NS_ERROR_OUT_OF_MEMORY
);
189 // We increase our capacity so that the allocated buffer grows
190 // exponentially, which gives us amortized O(1) appending. Below the
191 // threshold, we use powers-of-two. Above the threshold, we grow by at
192 // least 1.125, rounding up to the nearest MiB.
193 const size_type slowGrowthThreshold
= 8 * 1024 * 1024;
195 // nsStringBuffer allocates sizeof(nsStringBuffer) + passed size, and
196 // storageSize below wants extra 1 * sizeof(char_type).
197 const size_type neededExtraSpace
=
198 sizeof(nsStringBuffer
) / sizeof(char_type
) + 1;
201 if (aCapacity
>= slowGrowthThreshold
) {
202 size_type minNewCapacity
=
203 curCapacity
+ (curCapacity
>> 3); // multiply by 1.125
204 temp
= XPCOM_MAX(aCapacity
, minNewCapacity
) + neededExtraSpace
;
206 // Round up to the next multiple of MiB, but ensure the expected
207 // capacity doesn't include the extra space required by nsStringBuffer
208 // and null-termination.
209 const size_t MiB
= 1 << 20;
210 temp
= (MiB
* ((temp
+ MiB
- 1) / MiB
)) - neededExtraSpace
;
212 // Round up to the next power of two.
214 mozilla::RoundUpPow2(aCapacity
+ neededExtraSpace
) - neededExtraSpace
;
217 newCapacity
= XPCOM_MIN(temp
, base_string_type::kMaxCapacity
);
218 MOZ_ASSERT(newCapacity
>= aCapacity
,
219 "should have hit the early return at the top");
220 // Avoid shrinking if the new buffer size is close to the old. Note that
221 // unsigned underflow is defined behavior.
222 if ((curCapacity
- newCapacity
) <= kNsStringBufferShrinkingThreshold
&&
223 (this->mDataFlags
& DataFlags::REFCOUNTED
)) {
224 MOZ_ASSERT(aAllowShrinking
, "How come we didn't return earlier?");
225 // We're already close enough to the right size.
227 newCapacity
= curCapacity
;
229 size_type storageSize
= (newCapacity
+ 1) * sizeof(char_type
);
230 // Since we allocate only by powers of 2 we always fit into a full
231 // mozjemalloc bucket, it's not useful to use realloc, which may spend
232 // time uselessly copying too much.
233 nsStringBuffer
* newHdr
= nsStringBuffer::Alloc(storageSize
).take();
235 newData
= (char_type
*)newHdr
->Data();
236 } else if (shrinking
) {
237 // We're still in a consistent state.
239 // Since shrinking is just a memory footprint optimization, we
240 // don't propagate OOM if we tried to shrink in order to avoid
241 // OOM crashes from infallible callers. If we're lucky, soon enough
242 // a fallible caller reaches OOM and is able to deal or we end up
243 // disposing of this string before reaching OOM again.
245 newCapacity
= curCapacity
;
247 return mozilla::Err(NS_ERROR_OUT_OF_MEMORY
);
250 newDataFlags
= DataFlags::TERMINATED
| DataFlags::REFCOUNTED
;
253 this->mData
= newData
;
254 this->mDataFlags
= newDataFlags
;
256 if (oldData
== newData
) {
257 char_traits::move(newData
+ aNewSuffixStart
, oldData
+ aOldSuffixStart
,
260 char_traits::uninitialize(this->mData
+ aPrefixToPreserve
,
261 XPCOM_MIN(aNewSuffixStart
- aPrefixToPreserve
,
262 kNsStringBufferMaxPoison
));
263 char_traits::uninitialize(
264 this->mData
+ aNewSuffixStart
+ aSuffixLength
,
265 XPCOM_MIN(newCapacity
+ 1 - aNewSuffixStart
- aSuffixLength
,
266 kNsStringBufferMaxPoison
));
268 char_traits::uninitialize(this->mData
+ aPrefixToPreserve
,
269 XPCOM_MIN(newCapacity
+ 1 - aPrefixToPreserve
,
270 kNsStringBufferMaxPoison
));
273 char_traits::copy(newData
, oldData
, aPrefixToPreserve
);
274 char_traits::copy(newData
+ aNewSuffixStart
, oldData
+ aOldSuffixStart
,
276 ReleaseData(oldData
, oldFlags
);
282 template <typename T
>
283 void nsTSubstring
<T
>::FinishBulkWriteImpl(size_type aLength
) {
285 FinishBulkWriteImplImpl(aLength
);
287 ReleaseData(this->mData
, this->mDataFlags
);
293 template <typename T
>
294 void nsTSubstring
<T
>::Finalize() {
295 ReleaseData(this->mData
, this->mDataFlags
);
296 // this->mData, this->mLength, and this->mDataFlags are purposefully left
300 template <typename T
>
301 bool nsTSubstring
<T
>::ReplacePrep(index_type aCutStart
, size_type aCutLength
,
302 size_type aNewLength
) {
303 aCutLength
= XPCOM_MIN(aCutLength
, this->mLength
- aCutStart
);
305 mozilla::CheckedInt
<size_type
> newTotalLen
= this->Length();
306 newTotalLen
+= aNewLength
;
307 newTotalLen
-= aCutLength
;
308 if (!newTotalLen
.isValid()) {
312 if (aCutStart
== this->mLength
&& Capacity() > newTotalLen
.value()) {
313 this->mDataFlags
&= ~DataFlags::VOIDED
;
314 this->mData
[newTotalLen
.value()] = char_type(0);
315 this->mLength
= newTotalLen
.value();
319 return ReplacePrepInternal(aCutStart
, aCutLength
, aNewLength
,
320 newTotalLen
.value());
323 template <typename T
>
324 bool nsTSubstring
<T
>::ReplacePrepInternal(index_type aCutStart
,
325 size_type aCutLen
, size_type aFragLen
,
327 size_type newSuffixStart
= aCutStart
+ aFragLen
;
328 size_type oldSuffixStart
= aCutStart
+ aCutLen
;
329 size_type suffixLength
= this->mLength
- oldSuffixStart
;
331 mozilla::Result
<size_type
, nsresult
> r
= StartBulkWriteImpl(
332 aNewLen
, aCutStart
, false, suffixLength
, oldSuffixStart
, newSuffixStart
);
336 FinishBulkWriteImpl(aNewLen
);
340 template <typename T
>
341 typename nsTSubstring
<T
>::size_type nsTSubstring
<T
>::Capacity() const {
342 // return 0 to indicate an immutable or 0-sized buffer
345 if (this->mDataFlags
& DataFlags::REFCOUNTED
) {
346 // if the string is readonly, then we pretend that it has no capacity.
347 nsStringBuffer
* hdr
= nsStringBuffer::FromData(this->mData
);
348 if (hdr
->IsReadonly()) {
351 capacity
= (size_t(hdr
->StorageSize()) / sizeof(char_type
)) - 1;
353 } else if (this->mDataFlags
& DataFlags::INLINE
) {
354 MOZ_ASSERT(this->mClassFlags
& ClassFlags::INLINE
);
355 capacity
= AsAutoString(this)->mInlineCapacity
;
356 } else if (this->mDataFlags
& DataFlags::OWNED
) {
357 // we don't store the capacity of an adopted buffer because that would
358 // require an additional member field. the best we can do is base the
359 // capacity on our length. remains to be seen if this is the right
361 capacity
= this->mLength
;
369 template <typename T
>
370 bool nsTSubstring
<T
>::EnsureMutable(size_type aNewLen
) {
371 if (aNewLen
== size_type(-1) || aNewLen
== this->mLength
) {
372 if (this->mDataFlags
& (DataFlags::INLINE
| DataFlags::OWNED
)) {
375 if ((this->mDataFlags
& DataFlags::REFCOUNTED
) &&
376 !nsStringBuffer::FromData(this->mData
)->IsReadonly()) {
380 aNewLen
= this->mLength
;
382 return SetLength(aNewLen
, mozilla::fallible
);
385 // ---------------------------------------------------------------------------
387 // This version of Assign is optimized for single-character assignment.
388 template <typename T
>
389 void nsTSubstring
<T
>::Assign(char_type aChar
) {
390 if (MOZ_UNLIKELY(!Assign(aChar
, mozilla::fallible
))) {
395 template <typename T
>
396 bool nsTSubstring
<T
>::Assign(char_type aChar
, const fallible_t
&) {
397 auto r
= StartBulkWriteImpl(1, 0, true);
398 if (MOZ_UNLIKELY(r
.isErr())) {
401 *this->mData
= aChar
;
402 FinishBulkWriteImpl(1);
406 template <typename T
>
407 void nsTSubstring
<T
>::Assign(const char_type
* aData
, size_type aLength
) {
408 if (MOZ_UNLIKELY(!Assign(aData
, aLength
, mozilla::fallible
))) {
409 AllocFailed(aLength
== size_type(-1) ? char_traits::length(aData
)
414 template <typename T
>
415 bool nsTSubstring
<T
>::Assign(const char_type
* aData
,
416 const fallible_t
& aFallible
) {
417 return Assign(aData
, size_type(-1), aFallible
);
420 template <typename T
>
421 bool nsTSubstring
<T
>::Assign(const char_type
* aData
, size_type aLength
,
422 const fallible_t
& aFallible
) {
423 if (!aData
|| aLength
== 0) {
428 if (MOZ_UNLIKELY(aLength
== size_type(-1))) {
429 aLength
= char_traits::length(aData
);
432 if (MOZ_UNLIKELY(this->IsDependentOn(aData
, aData
+ aLength
))) {
433 return Assign(string_type(aData
, aLength
), aFallible
);
436 auto r
= StartBulkWriteImpl(aLength
, 0, true);
437 if (MOZ_UNLIKELY(r
.isErr())) {
440 char_traits::copy(this->mData
, aData
, aLength
);
441 FinishBulkWriteImpl(aLength
);
445 template <typename T
>
446 void nsTSubstring
<T
>::AssignASCII(const char* aData
, size_type aLength
) {
447 if (MOZ_UNLIKELY(!AssignASCII(aData
, aLength
, mozilla::fallible
))) {
448 AllocFailed(aLength
);
452 template <typename T
>
453 bool nsTSubstring
<T
>::AssignASCII(const char* aData
, size_type aLength
,
454 const fallible_t
& aFallible
) {
455 MOZ_ASSERT(aLength
!= size_type(-1));
457 // A Unicode string can't depend on an ASCII string buffer,
458 // so this dependence check only applies to CStrings.
459 if constexpr (std::is_same_v
<T
, char>) {
460 if (this->IsDependentOn(aData
, aData
+ aLength
)) {
461 return Assign(string_type(aData
, aLength
), aFallible
);
465 auto r
= StartBulkWriteImpl(aLength
, 0, true);
466 if (MOZ_UNLIKELY(r
.isErr())) {
469 char_traits::copyASCII(this->mData
, aData
, aLength
);
470 FinishBulkWriteImpl(aLength
);
474 template <typename T
>
475 void nsTSubstring
<T
>::AssignLiteral(const char_type
* aData
, size_type aLength
) {
476 ReleaseData(this->mData
, this->mDataFlags
);
477 SetData(const_cast<char_type
*>(aData
), aLength
,
478 DataFlags::TERMINATED
| DataFlags::LITERAL
);
481 template <typename T
>
482 void nsTSubstring
<T
>::Assign(const self_type
& aStr
) {
483 if (!Assign(aStr
, mozilla::fallible
)) {
484 AllocFailed(aStr
.Length());
488 template <typename T
>
489 bool nsTSubstring
<T
>::Assign(const self_type
& aStr
,
490 const fallible_t
& aFallible
) {
491 // |aStr| could be sharable. We need to check its flags to know how to
500 this->mDataFlags
|= aStr
.mDataFlags
& DataFlags::VOIDED
;
504 if (aStr
.mDataFlags
& DataFlags::REFCOUNTED
) {
505 // nice! we can avoid a string copy :-)
507 // |aStr| should be null-terminated
508 NS_ASSERTION(aStr
.mDataFlags
& DataFlags::TERMINATED
,
509 "shared, but not terminated");
511 ReleaseData(this->mData
, this->mDataFlags
);
513 SetData(aStr
.mData
, aStr
.mLength
,
514 DataFlags::TERMINATED
| DataFlags::REFCOUNTED
);
516 // get an owning reference to the this->mData
517 nsStringBuffer::FromData(this->mData
)->AddRef();
520 if (aStr
.mDataFlags
& DataFlags::LITERAL
) {
521 MOZ_ASSERT(aStr
.mDataFlags
& DataFlags::TERMINATED
, "Unterminated literal");
523 AssignLiteral(aStr
.mData
, aStr
.mLength
);
527 // else, treat this like an ordinary assignment.
528 return Assign(aStr
.Data(), aStr
.Length(), aFallible
);
531 template <typename T
>
532 void nsTSubstring
<T
>::Assign(self_type
&& aStr
) {
533 if (!Assign(std::move(aStr
), mozilla::fallible
)) {
534 AllocFailed(aStr
.Length());
538 template <typename T
>
539 void nsTSubstring
<T
>::AssignOwned(self_type
&& aStr
) {
540 MOZ_ASSERT(aStr
.mDataFlags
& (DataFlags::REFCOUNTED
| DataFlags::OWNED
),
541 "neither shared nor owned");
543 // If they have a REFCOUNTED or OWNED buffer, we can avoid a copy - so steal
544 // their buffer and reset them to the empty string.
546 // |aStr| should be null-terminated
547 MOZ_ASSERT(aStr
.mDataFlags
& DataFlags::TERMINATED
,
548 "shared or owned, but not terminated");
550 ReleaseData(this->mData
, this->mDataFlags
);
552 SetData(aStr
.mData
, aStr
.mLength
, aStr
.mDataFlags
);
553 aStr
.SetToEmptyBuffer();
556 template <typename T
>
557 bool nsTSubstring
<T
>::Assign(self_type
&& aStr
, const fallible_t
& aFallible
) {
558 // We're moving |aStr| in this method, so we need to try to steal the data,
559 // and in the fallback perform a copy-assignment followed by a truncation of
560 // the original string.
563 NS_WARNING("Move assigning a string to itself?");
567 if (aStr
.mDataFlags
& (DataFlags::REFCOUNTED
| DataFlags::OWNED
)) {
568 AssignOwned(std::move(aStr
));
572 // Otherwise treat this as a normal assignment, and truncate the moved string.
573 // We don't truncate the source string if the allocation failed.
574 if (!Assign(aStr
, aFallible
)) {
581 template <typename T
>
582 void nsTSubstring
<T
>::Assign(const substring_tuple_type
& aTuple
) {
583 if (!Assign(aTuple
, mozilla::fallible
)) {
584 AllocFailed(aTuple
.Length());
588 template <typename T
>
589 bool nsTSubstring
<T
>::AssignNonDependent(const substring_tuple_type
& aTuple
,
590 size_type aTupleLength
,
591 const mozilla::fallible_t
& aFallible
) {
592 NS_ASSERTION(aTuple
.Length() == aTupleLength
, "wrong length passed");
594 auto r
= StartBulkWriteImpl(aTupleLength
);
599 aTuple
.WriteTo(this->mData
, aTupleLength
);
601 FinishBulkWriteImpl(aTupleLength
);
605 template <typename T
>
606 bool nsTSubstring
<T
>::Assign(const substring_tuple_type
& aTuple
,
607 const fallible_t
& aFallible
) {
608 const auto [isDependentOnThis
, tupleLength
] =
609 aTuple
.IsDependentOnWithLength(this->mData
, this->mData
+ this->mLength
);
610 if (isDependentOnThis
) {
612 self_type
& tempSubstring
= temp
;
613 if (!tempSubstring
.AssignNonDependent(aTuple
, tupleLength
, aFallible
)) {
616 AssignOwned(std::move(temp
));
620 return AssignNonDependent(aTuple
, tupleLength
, aFallible
);
623 template <typename T
>
624 void nsTSubstring
<T
>::Adopt(char_type
* aData
, size_type aLength
) {
626 ReleaseData(this->mData
, this->mDataFlags
);
628 if (aLength
== size_type(-1)) {
629 aLength
= char_traits::length(aData
);
632 SetData(aData
, aLength
, DataFlags::TERMINATED
| DataFlags::OWNED
);
634 STRING_STAT_INCREMENT(Adopt
);
635 // Treat this as construction of a "StringAdopt" object for leak
636 // tracking purposes.
637 MOZ_LOG_CTOR(this->mData
, "StringAdopt", 1);
643 // This version of Replace is optimized for single-character replacement.
644 template <typename T
>
645 void nsTSubstring
<T
>::Replace(index_type aCutStart
, size_type aCutLength
,
647 aCutStart
= XPCOM_MIN(aCutStart
, this->Length());
649 if (ReplacePrep(aCutStart
, aCutLength
, 1)) {
650 this->mData
[aCutStart
] = aChar
;
654 template <typename T
>
655 bool nsTSubstring
<T
>::Replace(index_type aCutStart
, size_type aCutLength
,
656 char_type aChar
, const fallible_t
&) {
657 aCutStart
= XPCOM_MIN(aCutStart
, this->Length());
659 if (!ReplacePrep(aCutStart
, aCutLength
, 1)) {
663 this->mData
[aCutStart
] = aChar
;
668 template <typename T
>
669 void nsTSubstring
<T
>::Replace(index_type aCutStart
, size_type aCutLength
,
670 const char_type
* aData
, size_type aLength
) {
671 if (!Replace(aCutStart
, aCutLength
, aData
, aLength
, mozilla::fallible
)) {
672 AllocFailed(this->Length() - aCutLength
+ 1);
676 template <typename T
>
677 bool nsTSubstring
<T
>::Replace(index_type aCutStart
, size_type aCutLength
,
678 const char_type
* aData
, size_type aLength
,
679 const fallible_t
& aFallible
) {
680 // unfortunately, some callers pass null :-(
684 if (aLength
== size_type(-1)) {
685 aLength
= char_traits::length(aData
);
688 if (this->IsDependentOn(aData
, aData
+ aLength
)) {
689 nsTAutoString
<T
> temp(aData
, aLength
);
690 return Replace(aCutStart
, aCutLength
, temp
, aFallible
);
694 aCutStart
= XPCOM_MIN(aCutStart
, this->Length());
696 bool ok
= ReplacePrep(aCutStart
, aCutLength
, aLength
);
702 char_traits::copy(this->mData
+ aCutStart
, aData
, aLength
);
708 template <typename T
>
709 void nsTSubstring
<T
>::Replace(index_type aCutStart
, size_type aCutLength
,
710 const substring_tuple_type
& aTuple
) {
711 const auto [isDependentOnThis
, tupleLength
] =
712 aTuple
.IsDependentOnWithLength(this->mData
, this->mData
+ this->mLength
);
714 if (isDependentOnThis
) {
715 nsTAutoString
<T
> temp
;
716 if (!temp
.AssignNonDependent(aTuple
, tupleLength
, mozilla::fallible
)) {
717 AllocFailed(tupleLength
);
719 Replace(aCutStart
, aCutLength
, temp
);
723 aCutStart
= XPCOM_MIN(aCutStart
, this->Length());
725 if (ReplacePrep(aCutStart
, aCutLength
, tupleLength
) && tupleLength
> 0) {
726 aTuple
.WriteTo(this->mData
+ aCutStart
, tupleLength
);
730 template <typename T
>
731 void nsTSubstring
<T
>::ReplaceLiteral(index_type aCutStart
, size_type aCutLength
,
732 const char_type
* aData
,
734 aCutStart
= XPCOM_MIN(aCutStart
, this->Length());
736 if (!aCutStart
&& aCutLength
== this->Length() &&
737 !(this->mDataFlags
& DataFlags::REFCOUNTED
)) {
738 // Check for REFCOUNTED above to avoid undoing the effect of
740 AssignLiteral(aData
, aLength
);
741 } else if (ReplacePrep(aCutStart
, aCutLength
, aLength
) && aLength
> 0) {
742 char_traits::copy(this->mData
+ aCutStart
, aData
, aLength
);
746 template <typename T
>
747 void nsTSubstring
<T
>::Append(char_type aChar
) {
748 if (MOZ_UNLIKELY(!Append(aChar
, mozilla::fallible
))) {
749 AllocFailed(this->mLength
+ 1);
753 template <typename T
>
754 bool nsTSubstring
<T
>::Append(char_type aChar
, const fallible_t
& aFallible
) {
755 size_type oldLen
= this->mLength
;
756 size_type newLen
= oldLen
+ 1; // Can't overflow
757 auto r
= StartBulkWriteImpl(newLen
, oldLen
, false);
758 if (MOZ_UNLIKELY(r
.isErr())) {
761 this->mData
[oldLen
] = aChar
;
762 FinishBulkWriteImpl(newLen
);
766 template <typename T
>
767 void nsTSubstring
<T
>::Append(const char_type
* aData
, size_type aLength
) {
768 if (MOZ_UNLIKELY(!Append(aData
, aLength
, mozilla::fallible
))) {
769 AllocFailed(this->mLength
+ (aLength
== size_type(-1)
770 ? char_traits::length(aData
)
775 template <typename T
>
776 bool nsTSubstring
<T
>::Append(const char_type
* aData
, size_type aLength
,
777 const fallible_t
& aFallible
) {
778 if (MOZ_UNLIKELY(aLength
== size_type(-1))) {
779 aLength
= char_traits::length(aData
);
782 if (MOZ_UNLIKELY(!aLength
)) {
783 // Avoid undoing the effect of SetCapacity() if both
784 // mLength and aLength are zero.
788 if (MOZ_UNLIKELY(this->IsDependentOn(aData
, aData
+ aLength
))) {
789 return Append(string_type(aData
, aLength
), mozilla::fallible
);
791 size_type oldLen
= this->mLength
;
792 mozilla::CheckedInt
<size_type
> newLen(oldLen
);
794 if (MOZ_UNLIKELY(!newLen
.isValid())) {
797 auto r
= StartBulkWriteImpl(newLen
.value(), oldLen
, false);
798 if (MOZ_UNLIKELY(r
.isErr())) {
801 char_traits::copy(this->mData
+ oldLen
, aData
, aLength
);
802 FinishBulkWriteImpl(newLen
.value());
806 template <typename T
>
807 void nsTSubstring
<T
>::AppendASCII(const char* aData
, size_type aLength
) {
808 if (MOZ_UNLIKELY(!AppendASCII(aData
, aLength
, mozilla::fallible
))) {
809 AllocFailed(this->mLength
+
810 (aLength
== size_type(-1) ? strlen(aData
) : aLength
));
814 template <typename T
>
815 bool nsTSubstring
<T
>::AppendASCII(const char* aData
,
816 const fallible_t
& aFallible
) {
817 return AppendASCII(aData
, size_type(-1), aFallible
);
820 template <typename T
>
821 bool nsTSubstring
<T
>::AppendASCII(const char* aData
, size_type aLength
,
822 const fallible_t
& aFallible
) {
823 if (MOZ_UNLIKELY(aLength
== size_type(-1))) {
824 aLength
= strlen(aData
);
827 if (MOZ_UNLIKELY(!aLength
)) {
828 // Avoid undoing the effect of SetCapacity() if both
829 // mLength and aLength are zero.
833 if constexpr (std::is_same_v
<T
, char>) {
834 // 16-bit string can't depend on an 8-bit buffer
835 if (MOZ_UNLIKELY(this->IsDependentOn(aData
, aData
+ aLength
))) {
836 return Append(string_type(aData
, aLength
), mozilla::fallible
);
840 size_type oldLen
= this->mLength
;
841 mozilla::CheckedInt
<size_type
> newLen(oldLen
);
843 if (MOZ_UNLIKELY(!newLen
.isValid())) {
846 auto r
= StartBulkWriteImpl(newLen
.value(), oldLen
, false);
847 if (MOZ_UNLIKELY(r
.isErr())) {
850 char_traits::copyASCII(this->mData
+ oldLen
, aData
, aLength
);
851 FinishBulkWriteImpl(newLen
.value());
855 template <typename T
>
856 void nsTSubstring
<T
>::Append(const self_type
& aStr
) {
857 if (MOZ_UNLIKELY(!Append(aStr
, mozilla::fallible
))) {
858 AllocFailed(this->mLength
+ aStr
.Length());
862 template <typename T
>
863 bool nsTSubstring
<T
>::Append(const self_type
& aStr
,
864 const fallible_t
& aFallible
) {
865 // Check refcounted to avoid undoing the effects of SetCapacity().
866 if (MOZ_UNLIKELY(!this->mLength
&&
867 !(this->mDataFlags
& DataFlags::REFCOUNTED
))) {
868 return Assign(aStr
, mozilla::fallible
);
870 return Append(aStr
.BeginReading(), aStr
.Length(), mozilla::fallible
);
873 template <typename T
>
874 void nsTSubstring
<T
>::Append(const substring_tuple_type
& aTuple
) {
875 if (MOZ_UNLIKELY(!Append(aTuple
, mozilla::fallible
))) {
876 AllocFailed(this->mLength
+ aTuple
.Length());
880 template <typename T
>
881 bool nsTSubstring
<T
>::Append(const substring_tuple_type
& aTuple
,
882 const fallible_t
& aFallible
) {
883 const auto [isDependentOnThis
, tupleLength
] =
884 aTuple
.IsDependentOnWithLength(this->mData
, this->mData
+ this->mLength
);
886 if (MOZ_UNLIKELY(!tupleLength
)) {
887 // Avoid undoing the effect of SetCapacity() if both
888 // mLength and tupleLength are zero.
892 if (MOZ_UNLIKELY(isDependentOnThis
)) {
893 return Append(string_type(aTuple
), aFallible
);
896 size_type oldLen
= this->mLength
;
897 mozilla::CheckedInt
<size_type
> newLen(oldLen
);
898 newLen
+= tupleLength
;
899 if (MOZ_UNLIKELY(!newLen
.isValid())) {
902 auto r
= StartBulkWriteImpl(newLen
.value(), oldLen
, false);
903 if (MOZ_UNLIKELY(r
.isErr())) {
906 aTuple
.WriteTo(this->mData
+ oldLen
, tupleLength
);
907 FinishBulkWriteImpl(newLen
.value());
911 template <typename T
>
912 void nsTSubstring
<T
>::SetCapacity(size_type aCapacity
) {
913 if (!SetCapacity(aCapacity
, mozilla::fallible
)) {
914 AllocFailed(aCapacity
);
918 template <typename T
>
919 bool nsTSubstring
<T
>::SetCapacity(size_type aCapacity
, const fallible_t
&) {
920 size_type length
= this->mLength
;
921 // This method can no longer be used to shorten the
923 size_type capacity
= XPCOM_MAX(aCapacity
, length
);
925 auto r
= StartBulkWriteImpl(capacity
, length
, true);
930 if (MOZ_UNLIKELY(!capacity
)) {
931 // Zero capacity was requested on a zero-length
932 // string. In this special case, we are pointing
933 // to the special empty buffer, which is already
934 // zero-terminated and not writable, so we must
935 // not attempt to zero-terminate it.
940 // FinishBulkWriteImpl with argument zero releases
941 // the heap-allocated buffer. However, SetCapacity()
942 // is a special case that allows mLength to be zero
943 // while a heap-allocated buffer exists.
944 // By calling FinishBulkWriteImplImpl, we skip the
945 // zero case handling that's inappropriate in the
946 // SetCapacity() case.
947 FinishBulkWriteImplImpl(length
);
951 template <typename T
>
952 void nsTSubstring
<T
>::SetLength(size_type aLength
) {
953 if (!SetLength(aLength
, mozilla::fallible
)) {
954 AllocFailed(aLength
);
958 template <typename T
>
959 bool nsTSubstring
<T
>::SetLength(size_type aLength
,
960 const fallible_t
& aFallible
) {
961 size_type preserve
= XPCOM_MIN(aLength
, this->Length());
962 auto r
= StartBulkWriteImpl(aLength
, preserve
, true);
967 FinishBulkWriteImpl(aLength
);
972 template <typename T
>
973 void nsTSubstring
<T
>::Truncate() {
974 ReleaseData(this->mData
, this->mDataFlags
);
979 template <typename T
>
980 void nsTSubstring
<T
>::SetIsVoid(bool aVal
) {
983 this->mDataFlags
|= DataFlags::VOIDED
;
985 this->mDataFlags
&= ~DataFlags::VOIDED
;
989 template <typename T
>
990 void nsTSubstring
<T
>::StripChar(char_type aChar
) {
991 if (this->mLength
== 0) {
995 if (!EnsureMutable()) { // XXX do this lazily?
996 AllocFailed(this->mLength
);
999 // XXX(darin): this code should defer writing until necessary.
1001 char_type
* to
= this->mData
;
1002 char_type
* from
= this->mData
;
1003 char_type
* end
= this->mData
+ this->mLength
;
1005 while (from
< end
) {
1006 char_type theChar
= *from
++;
1007 if (aChar
!= theChar
) {
1011 *to
= char_type(0); // add the null
1012 this->mLength
= to
- this->mData
;
1015 template <typename T
>
1016 void nsTSubstring
<T
>::StripChars(const char_type
* aChars
) {
1017 if (this->mLength
== 0) {
1021 if (!EnsureMutable()) { // XXX do this lazily?
1022 AllocFailed(this->mLength
);
1025 // XXX(darin): this code should defer writing until necessary.
1027 char_type
* to
= this->mData
;
1028 char_type
* from
= this->mData
;
1029 char_type
* end
= this->mData
+ this->mLength
;
1031 while (from
< end
) {
1032 char_type theChar
= *from
++;
1033 const char_type
* test
= aChars
;
1035 for (; *test
&& *test
!= theChar
; ++test
)
1039 // Not stripped, copy this char.
1043 *to
= char_type(0); // add the null
1044 this->mLength
= to
- this->mData
;
1047 template <typename T
>
1048 void nsTSubstring
<T
>::StripTaggedASCII(const ASCIIMaskArray
& aToStrip
) {
1049 if (this->mLength
== 0) {
1053 size_t untaggedPrefixLength
= 0;
1054 for (; untaggedPrefixLength
< this->mLength
; ++untaggedPrefixLength
) {
1055 uint32_t theChar
= (uint32_t)this->mData
[untaggedPrefixLength
];
1056 if (mozilla::ASCIIMask::IsMasked(aToStrip
, theChar
)) {
1061 if (untaggedPrefixLength
== this->mLength
) {
1065 if (!EnsureMutable()) {
1066 AllocFailed(this->mLength
);
1069 char_type
* to
= this->mData
+ untaggedPrefixLength
;
1070 char_type
* from
= to
;
1071 char_type
* end
= this->mData
+ this->mLength
;
1073 while (from
< end
) {
1074 uint32_t theChar
= (uint32_t)*from
++;
1075 // Replacing this with a call to ASCIIMask::IsMasked
1076 // regresses performance somewhat, so leaving it inlined.
1077 if (!mozilla::ASCIIMask::IsMasked(aToStrip
, theChar
)) {
1078 // Not stripped, copy this char.
1079 *to
++ = (char_type
)theChar
;
1082 *to
= char_type(0); // add the null
1083 this->mLength
= to
- this->mData
;
1086 template <typename T
>
1087 void nsTSubstring
<T
>::StripCRLF() {
1088 // Expanding this call to copy the code from StripTaggedASCII
1089 // instead of just calling it does somewhat help with performance
1090 // but it is not worth it given the duplicated code.
1091 StripTaggedASCII(mozilla::ASCIIMask::MaskCRLF());
1094 template <typename T
>
1095 struct MOZ_STACK_CLASS PrintfAppend
: public mozilla::PrintfTarget
{
1096 explicit PrintfAppend(nsTSubstring
<T
>* aString
) : mString(aString
) {}
1098 bool append(const char* aStr
, size_t aLen
) override
{
1103 mString
->AppendASCII(aStr
, aLen
);
1108 nsTSubstring
<T
>* mString
;
1111 template <typename T
>
1112 void nsTSubstring
<T
>::AppendPrintf(const char* aFormat
, ...) {
1113 PrintfAppend
<T
> appender(this);
1115 va_start(ap
, aFormat
);
1116 bool r
= appender
.vprint(aFormat
, ap
);
1118 MOZ_CRASH("Allocation or other failure in PrintfTarget::print");
1123 template <typename T
>
1124 void nsTSubstring
<T
>::AppendVprintf(const char* aFormat
, va_list aAp
) {
1125 PrintfAppend
<T
> appender(this);
1126 bool r
= appender
.vprint(aFormat
, aAp
);
1128 MOZ_CRASH("Allocation or other failure in PrintfTarget::print");
1132 template <typename T
>
1133 void nsTSubstring
<T
>::AppendIntDec(int32_t aInteger
) {
1134 PrintfAppend
<T
> appender(this);
1135 bool r
= appender
.appendIntDec(aInteger
);
1136 if (MOZ_UNLIKELY(!r
)) {
1137 MOZ_CRASH("Allocation or other failure while appending integers");
1141 template <typename T
>
1142 void nsTSubstring
<T
>::AppendIntDec(uint32_t aInteger
) {
1143 PrintfAppend
<T
> appender(this);
1144 bool r
= appender
.appendIntDec(aInteger
);
1145 if (MOZ_UNLIKELY(!r
)) {
1146 MOZ_CRASH("Allocation or other failure while appending integers");
1150 template <typename T
>
1151 void nsTSubstring
<T
>::AppendIntOct(uint32_t aInteger
) {
1152 PrintfAppend
<T
> appender(this);
1153 bool r
= appender
.appendIntOct(aInteger
);
1154 if (MOZ_UNLIKELY(!r
)) {
1155 MOZ_CRASH("Allocation or other failure while appending integers");
1159 template <typename T
>
1160 void nsTSubstring
<T
>::AppendIntHex(uint32_t aInteger
) {
1161 PrintfAppend
<T
> appender(this);
1162 bool r
= appender
.appendIntHex(aInteger
);
1163 if (MOZ_UNLIKELY(!r
)) {
1164 MOZ_CRASH("Allocation or other failure while appending integers");
1168 template <typename T
>
1169 void nsTSubstring
<T
>::AppendIntDec(int64_t aInteger
) {
1170 PrintfAppend
<T
> appender(this);
1171 bool r
= appender
.appendIntDec(aInteger
);
1172 if (MOZ_UNLIKELY(!r
)) {
1173 MOZ_CRASH("Allocation or other failure while appending integers");
1177 template <typename T
>
1178 void nsTSubstring
<T
>::AppendIntDec(uint64_t aInteger
) {
1179 PrintfAppend
<T
> appender(this);
1180 bool r
= appender
.appendIntDec(aInteger
);
1181 if (MOZ_UNLIKELY(!r
)) {
1182 MOZ_CRASH("Allocation or other failure while appending integers");
1186 template <typename T
>
1187 void nsTSubstring
<T
>::AppendIntOct(uint64_t aInteger
) {
1188 PrintfAppend
<T
> appender(this);
1189 bool r
= appender
.appendIntOct(aInteger
);
1190 if (MOZ_UNLIKELY(!r
)) {
1191 MOZ_CRASH("Allocation or other failure while appending integers");
1195 template <typename T
>
1196 void nsTSubstring
<T
>::AppendIntHex(uint64_t aInteger
) {
1197 PrintfAppend
<T
> appender(this);
1198 bool r
= appender
.appendIntHex(aInteger
);
1199 if (MOZ_UNLIKELY(!r
)) {
1200 MOZ_CRASH("Allocation or other failure while appending integers");
1204 // Returns the length of the formatted aDouble in aBuf.
1205 static int FormatWithoutTrailingZeros(char (&aBuf
)[40], double aDouble
,
1207 static const DoubleToStringConverter
converter(
1208 DoubleToStringConverter::UNIQUE_ZERO
|
1209 DoubleToStringConverter::NO_TRAILING_ZERO
|
1210 DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN
,
1211 "Infinity", "NaN", 'e', -6, 21, 6, 1);
1212 double_conversion::StringBuilder
builder(aBuf
, sizeof(aBuf
));
1213 converter
.ToPrecision(aDouble
, aPrecision
, &builder
);
1214 int length
= builder
.position();
1219 template <typename T
>
1220 void nsTSubstring
<T
>::AppendFloat(float aFloat
) {
1222 int length
= FormatWithoutTrailingZeros(buf
, aFloat
, 6);
1223 AppendASCII(buf
, length
);
1226 template <typename T
>
1227 void nsTSubstring
<T
>::AppendFloat(double aFloat
) {
1229 int length
= FormatWithoutTrailingZeros(buf
, aFloat
, 15);
1230 AppendASCII(buf
, length
);
1233 template <typename T
>
1234 size_t nsTSubstring
<T
>::SizeOfExcludingThisIfUnshared(
1235 mozilla::MallocSizeOf aMallocSizeOf
) const {
1236 if (this->mDataFlags
& DataFlags::REFCOUNTED
) {
1237 return nsStringBuffer::FromData(this->mData
)
1238 ->SizeOfIncludingThisIfUnshared(aMallocSizeOf
);
1240 if (this->mDataFlags
& DataFlags::OWNED
) {
1241 return aMallocSizeOf(this->mData
);
1244 // If we reach here, exactly one of the following must be true:
1245 // - DataFlags::VOIDED is set, and this->mData points to sEmptyBuffer;
1246 // - DataFlags::INLINE is set, and this->mData points to a buffer within a
1247 // string object (e.g. nsAutoString);
1248 // - None of DataFlags::REFCOUNTED, DataFlags::OWNED, DataFlags::INLINE is
1249 // set, and this->mData points to a buffer owned by something else.
1251 // In all three cases, we don't measure it.
1255 template <typename T
>
1256 size_t nsTSubstring
<T
>::SizeOfExcludingThisEvenIfShared(
1257 mozilla::MallocSizeOf aMallocSizeOf
) const {
1258 // This is identical to SizeOfExcludingThisIfUnshared except for the
1259 // DataFlags::REFCOUNTED case.
1260 if (this->mDataFlags
& DataFlags::REFCOUNTED
) {
1261 return nsStringBuffer::FromData(this->mData
)
1262 ->SizeOfIncludingThisEvenIfShared(aMallocSizeOf
);
1264 if (this->mDataFlags
& DataFlags::OWNED
) {
1265 return aMallocSizeOf(this->mData
);
1270 template <typename T
>
1271 size_t nsTSubstring
<T
>::SizeOfIncludingThisIfUnshared(
1272 mozilla::MallocSizeOf aMallocSizeOf
) const {
1273 return aMallocSizeOf(this) + SizeOfExcludingThisIfUnshared(aMallocSizeOf
);
1276 template <typename T
>
1277 size_t nsTSubstring
<T
>::SizeOfIncludingThisEvenIfShared(
1278 mozilla::MallocSizeOf aMallocSizeOf
) const {
1279 return aMallocSizeOf(this) + SizeOfExcludingThisEvenIfShared(aMallocSizeOf
);
1282 template <typename T
>
1283 nsTSubstringSplitter
<T
> nsTSubstring
<T
>::Split(const char_type aChar
) const {
1284 return nsTSubstringSplitter
<T
>(
1285 nsTCharSeparatedTokenizerTemplate
<
1286 NS_TokenizerIgnoreNothing
, T
,
1287 nsTokenizerFlags::IncludeEmptyTokenAtEnd
>(*this, aChar
));
1290 // Common logic for nsTSubstring<T>::ToInteger and nsTSubstring<T>::ToInteger64.
1291 template <typename T
, typename int_type
>
1292 int_type
ToIntegerCommon(const nsTSubstring
<T
>& aSrc
, nsresult
* aErrorCode
,
1294 MOZ_ASSERT(aRadix
== 10 || aRadix
== 16);
1296 // Initial value, override if we find an integer.
1297 *aErrorCode
= NS_ERROR_ILLEGAL_VALUE
;
1299 // Begin by skipping over leading chars that shouldn't be part of the number.
1300 auto cp
= aSrc
.BeginReading();
1301 auto endcp
= aSrc
.EndReading();
1302 bool negate
= false;
1305 // NB: For backwards compatibility I'm not going to change this logic but
1306 // it seems really odd. Previously there was logic to auto-detect the
1307 // radix if kAutoDetect was passed in. In practice this value was never
1308 // used, so it pretended to auto detect and skipped some preceding
1309 // letters (excluding valid hex digits) but never used the result.
1311 // For example if you pass in "Get the number: 10", aRadix = 10 we'd
1312 // skip the 'G', and then fail to parse "et the number: 10". If aRadix =
1313 // 16 we'd skip the 'G', and parse just 'e' returning 14.
1314 while ((cp
< endcp
) && (!done
)) {
1317 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
1318 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
1319 case '0': case '1': case '2': case '3': case '4':
1320 case '5': case '6': case '7': case '8': case '9':
1333 // No base 16 or base 10 digits were found.
1340 mozilla::CheckedInt
<int_type
> result
;
1342 // Now iterate the numeric chars and build our result.
1343 while (cp
< endcp
) {
1344 auto theChar
= *cp
++;
1345 if (('0' <= theChar
) && (theChar
<= '9')) {
1346 result
= (aRadix
* result
) + (theChar
- '0');
1347 } else if ((theChar
>= 'A') && (theChar
<= 'F')) {
1349 // Invalid base 10 digit, error out.
1352 result
= (aRadix
* result
) + ((theChar
- 'A') + 10);
1353 } else if ((theChar
>= 'a') && (theChar
<= 'f')) {
1355 // Invalid base 10 digit, error out.
1358 result
= (aRadix
* result
) + ((theChar
- 'a') + 10);
1359 } else if ((('X' == theChar
) || ('x' == theChar
)) && result
== 0) {
1360 // For some reason we support a leading 'x' regardless of radix. For
1361 // example: "000000x500", aRadix = 10 would be parsed as 500 rather
1365 // We've encountered a char that's not a legal number or sign and we can
1366 // terminate processing.
1370 if (!result
.isValid()) {
1377 *aErrorCode
= NS_OK
;
1383 return result
.value();
1386 template <typename T
>
1387 int32_t nsTSubstring
<T
>::ToInteger(nsresult
* aErrorCode
,
1388 uint32_t aRadix
) const {
1389 return ToIntegerCommon
<T
, int32_t>(*this, aErrorCode
, aRadix
);
1393 * nsTSubstring::ToInteger64
1395 template <typename T
>
1396 int64_t nsTSubstring
<T
>::ToInteger64(nsresult
* aErrorCode
,
1397 uint32_t aRadix
) const {
1398 return ToIntegerCommon
<T
, int64_t>(*this, aErrorCode
, aRadix
);
1404 template <typename T
>
1405 typename nsTSubstring
<T
>::size_type nsTSubstring
<T
>::Mid(
1406 self_type
& aResult
, index_type aStartPos
, size_type aLengthToCopy
) const {
1407 if (aStartPos
== 0 && aLengthToCopy
>= this->mLength
) {
1410 aResult
= Substring(*this, aStartPos
, aLengthToCopy
);
1413 return aResult
.mLength
;
1417 * nsTSubstring::StripWhitespace
1420 template <typename T
>
1421 void nsTSubstring
<T
>::StripWhitespace() {
1422 if (!StripWhitespace(mozilla::fallible
)) {
1423 this->AllocFailed(this->mLength
);
1427 template <typename T
>
1428 bool nsTSubstring
<T
>::StripWhitespace(const fallible_t
&) {
1429 if (!this->EnsureMutable()) {
1433 this->StripTaggedASCII(mozilla::ASCIIMask::MaskWhitespace());
1438 * nsTSubstring::ReplaceChar,ReplaceSubstring
1441 template <typename T
>
1442 void nsTSubstring
<T
>::ReplaceChar(char_type aOldChar
, char_type aNewChar
) {
1443 int32_t i
= this->FindChar(aOldChar
);
1444 if (i
== kNotFound
) {
1448 if (!this->EnsureMutable()) {
1449 this->AllocFailed(this->mLength
);
1451 for (; i
!= kNotFound
; i
= this->FindChar(aOldChar
, i
+ 1)) {
1452 this->mData
[i
] = aNewChar
;
1456 template <typename T
>
1457 void nsTSubstring
<T
>::ReplaceChar(const string_view
& aSet
, char_type aNewChar
) {
1458 int32_t i
= this->FindCharInSet(aSet
);
1459 if (i
== kNotFound
) {
1463 if (!this->EnsureMutable()) {
1464 this->AllocFailed(this->mLength
);
1466 for (; i
!= kNotFound
; i
= this->FindCharInSet(aSet
, i
+ 1)) {
1467 this->mData
[i
] = aNewChar
;
1471 template <typename T
>
1472 void nsTSubstring
<T
>::ReplaceSubstring(const char_type
* aTarget
,
1473 const char_type
* aNewValue
) {
1474 ReplaceSubstring(nsTDependentString
<T
>(aTarget
),
1475 nsTDependentString
<T
>(aNewValue
));
1478 template <typename T
>
1479 bool nsTSubstring
<T
>::ReplaceSubstring(const char_type
* aTarget
,
1480 const char_type
* aNewValue
,
1481 const fallible_t
& aFallible
) {
1482 return ReplaceSubstring(nsTDependentString
<T
>(aTarget
),
1483 nsTDependentString
<T
>(aNewValue
), aFallible
);
1486 template <typename T
>
1487 void nsTSubstring
<T
>::ReplaceSubstring(const self_type
& aTarget
,
1488 const self_type
& aNewValue
) {
1489 if (!ReplaceSubstring(aTarget
, aNewValue
, mozilla::fallible
)) {
1490 // Note that this may wildly underestimate the allocation that failed, as
1491 // we could have been replacing multiple copies of aTarget.
1492 this->AllocFailed(this->mLength
+ (aNewValue
.Length() - aTarget
.Length()));
1496 template <typename T
>
1497 bool nsTSubstring
<T
>::ReplaceSubstring(const self_type
& aTarget
,
1498 const self_type
& aNewValue
,
1499 const fallible_t
&) {
1501 uint32_t mBegin
, mLength
;
1502 Segment(uint32_t aBegin
, uint32_t aLength
)
1503 : mBegin(aBegin
), mLength(aLength
) {}
1506 if (aTarget
.Length() == 0) {
1510 // Remember all of the non-matching parts.
1511 AutoTArray
<Segment
, 16> nonMatching
;
1513 mozilla::CheckedUint32 newLength
;
1515 int32_t r
= this->Find(aTarget
, i
);
1516 int32_t until
= (r
== kNotFound
) ? this->Length() - i
: r
- i
;
1517 nonMatching
.AppendElement(Segment(i
, until
));
1519 if (r
== kNotFound
) {
1523 newLength
+= aNewValue
.Length();
1524 i
= r
+ aTarget
.Length();
1525 if (i
>= this->Length()) {
1526 // Add an auxiliary entry at the end of the list to help as an edge case
1527 // for the algorithms below.
1528 nonMatching
.AppendElement(Segment(this->Length(), 0));
1533 if (!newLength
.isValid()) {
1537 // If there's only one non-matching segment, then the target string was not
1538 // found, and there's nothing to do.
1539 if (nonMatching
.Length() == 1) {
1541 nonMatching
[0].mBegin
== 0 && nonMatching
[0].mLength
== this->Length(),
1542 "We should have the correct non-matching segment.");
1546 // Make sure that we can mutate our buffer.
1547 // Note that we always allocate at least an this->mLength sized buffer,
1548 // because the rest of the algorithm relies on having access to all of the
1549 // original string. In other words, we over-allocate in the shrinking case.
1550 uint32_t oldLen
= this->Length();
1552 this->StartBulkWriteImpl(XPCOM_MAX(oldLen
, newLength
.value()), oldLen
);
1557 if (aTarget
.Length() >= aNewValue
.Length()) {
1558 // In the shrinking case, start filling the buffer from the beginning.
1559 const uint32_t delta
= (aTarget
.Length() - aNewValue
.Length());
1560 for (i
= 1; i
< nonMatching
.Length(); ++i
) {
1561 // When we move the i'th non-matching segment into position, we need to
1562 // account for the characters deleted by the previous |i| replacements by
1563 // subtracting |i * delta|.
1564 const char_type
* sourceSegmentPtr
= this->mData
+ nonMatching
[i
].mBegin
;
1565 char_type
* destinationSegmentPtr
=
1566 this->mData
+ nonMatching
[i
].mBegin
- i
* delta
;
1567 // Write the i'th replacement immediately before the new i'th non-matching
1569 char_traits::copy(destinationSegmentPtr
- aNewValue
.Length(),
1570 aNewValue
.Data(), aNewValue
.Length());
1571 char_traits::move(destinationSegmentPtr
, sourceSegmentPtr
,
1572 nonMatching
[i
].mLength
);
1575 // In the growing case, start filling the buffer from the end.
1576 const uint32_t delta
= (aNewValue
.Length() - aTarget
.Length());
1577 for (i
= nonMatching
.Length() - 1; i
> 0; --i
) {
1578 // When we move the i'th non-matching segment into position, we need to
1579 // account for the characters added by the previous |i| replacements by
1580 // adding |i * delta|.
1581 const char_type
* sourceSegmentPtr
= this->mData
+ nonMatching
[i
].mBegin
;
1582 char_type
* destinationSegmentPtr
=
1583 this->mData
+ nonMatching
[i
].mBegin
+ i
* delta
;
1584 char_traits::move(destinationSegmentPtr
, sourceSegmentPtr
,
1585 nonMatching
[i
].mLength
);
1586 // Write the i'th replacement immediately before the new i'th non-matching
1588 char_traits::copy(destinationSegmentPtr
- aNewValue
.Length(),
1589 aNewValue
.Data(), aNewValue
.Length());
1593 // Adjust the length and make sure the string is null terminated.
1594 this->FinishBulkWriteImpl(newLength
.value());
1600 * nsTSubstring::Trim
1603 template <typename T
>
1604 void nsTSubstring
<T
>::Trim(const std::string_view
& aSet
, bool aTrimLeading
,
1605 bool aTrimTrailing
, bool aIgnoreQuotes
) {
1606 char_type
* start
= this->mData
;
1607 char_type
* end
= this->mData
+ this->mLength
;
1609 // skip over quotes if requested
1610 if (aIgnoreQuotes
&& this->mLength
> 2 &&
1611 this->mData
[0] == this->mData
[this->mLength
- 1] &&
1612 (this->mData
[0] == '\'' || this->mData
[0] == '"')) {
1618 uint32_t cutStart
= start
- this->mData
;
1619 uint32_t cutLength
= 0;
1621 // walk forward from start to end
1622 for (; start
!= end
; ++start
, ++cutLength
) {
1623 if ((*start
& ~0x7F) || // non-ascii
1624 aSet
.find(char(*start
)) == std::string_view::npos
) {
1630 this->Cut(cutStart
, cutLength
);
1633 start
= this->mData
+ cutStart
;
1634 end
= this->mData
+ this->mLength
- cutStart
;
1638 if (aTrimTrailing
) {
1639 uint32_t cutEnd
= end
- this->mData
;
1640 uint32_t cutLength
= 0;
1642 // walk backward from end to start
1644 for (; end
>= start
; --end
, ++cutLength
) {
1645 if ((*end
& ~0x7F) || // non-ascii
1646 aSet
.find(char(*end
)) == std::string_view::npos
) {
1652 this->Cut(cutEnd
- cutLength
, cutLength
);
1658 * nsTSubstring::CompressWhitespace.
1661 template <typename T
>
1662 void nsTSubstring
<T
>::CompressWhitespace(bool aTrimLeading
,
1663 bool aTrimTrailing
) {
1665 if (this->mLength
== 0) {
1669 if (!this->EnsureMutable()) {
1670 this->AllocFailed(this->mLength
);
1673 const ASCIIMaskArray
& mask
= mozilla::ASCIIMask::MaskWhitespace();
1675 char_type
* to
= this->mData
;
1676 char_type
* from
= this->mData
;
1677 char_type
* end
= this->mData
+ this->mLength
;
1679 // Compresses runs of whitespace down to a normal space ' ' and convert
1680 // any whitespace to a normal space. This assumes that whitespace is
1681 // all standard 7-bit ASCII.
1682 bool skipWS
= aTrimLeading
;
1683 while (from
< end
) {
1684 uint32_t theChar
= *from
++;
1685 if (mozilla::ASCIIMask::IsMasked(mask
, theChar
)) {
1696 // If we need to trim the trailing whitespace, back up one character.
1697 if (aTrimTrailing
&& skipWS
&& to
> this->mData
) {
1701 *to
= char_type(0); // add the null
1702 this->mLength
= to
- this->mData
;
1705 template class nsTSubstring
<char>;
1706 template class nsTSubstring
<char16_t
>;