diff options
Diffstat (limited to 'xpcom/string/nsTSubstring.cpp')
-rw-r--r-- | xpcom/string/nsTSubstring.cpp | 1089 |
1 files changed, 1089 insertions, 0 deletions
diff --git a/xpcom/string/nsTSubstring.cpp b/xpcom/string/nsTSubstring.cpp new file mode 100644 index 000000000..a3a830b9d --- /dev/null +++ b/xpcom/string/nsTSubstring.cpp @@ -0,0 +1,1089 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/CheckedInt.h" +#include "mozilla/double-conversion.h" +#include "mozilla/MemoryReporting.h" + +using double_conversion::DoubleToStringConverter; + +const nsTSubstring_CharT::size_type nsTSubstring_CharT::kMaxCapacity = + (nsTSubstring_CharT::size_type(-1) / + 2 - sizeof(nsStringBuffer)) / + sizeof(nsTSubstring_CharT::char_type) - 2; + +#ifdef XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE +nsTSubstring_CharT::nsTSubstring_CharT(char_type* aData, size_type aLength, + uint32_t aFlags) + : mData(aData), + mLength(aLength), + mFlags(aFlags) +{ + MOZ_RELEASE_ASSERT(CheckCapacity(aLength), "String is too large."); + + if (aFlags & F_OWNED) { + STRING_STAT_INCREMENT(Adopt); + MOZ_LOG_CTOR(mData, "StringAdopt", 1); + } +} +#endif /* XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE */ + +/** + * helper function for down-casting a nsTSubstring to a nsTFixedString. + */ +inline const nsTFixedString_CharT* +AsFixedString(const nsTSubstring_CharT* aStr) +{ + return static_cast<const nsTFixedString_CharT*>(aStr); +} + + +/** + * this function is called to prepare mData for writing. the given capacity + * indicates the required minimum storage size for mData, in sizeof(char_type) + * increments. this function returns true if the operation succeeds. it also + * returns the old data and old flags members if mData is newly allocated. + * the old data must be released by the caller. + */ +bool +nsTSubstring_CharT::MutatePrep(size_type aCapacity, char_type** aOldData, + uint32_t* aOldFlags) +{ + // initialize to no old data + *aOldData = nullptr; + *aOldFlags = 0; + + size_type curCapacity = Capacity(); + + // If |aCapacity > kMaxCapacity|, then our doubling algorithm may not be + // able to allocate it. Just bail out in cases like that. We don't want + // to be allocating 2GB+ strings anyway. + static_assert((sizeof(nsStringBuffer) & 0x1) == 0, + "bad size for nsStringBuffer"); + if (!CheckCapacity(aCapacity)) { + return false; + } + + // |curCapacity == 0| means that the buffer is immutable or 0-sized, so we + // need to allocate a new buffer. We cannot use the existing buffer even + // though it might be large enough. + + if (curCapacity != 0) { + if (aCapacity <= curCapacity) { + mFlags &= ~F_VOIDED; // mutation clears voided flag + return true; + } + } + + if (curCapacity < aCapacity) { + // We increase our capacity so that the allocated buffer grows + // exponentially, which gives us amortized O(1) appending. Below the + // threshold, we use powers-of-two. Above the threshold, we grow by at + // least 1.125, rounding up to the nearest MiB. + const size_type slowGrowthThreshold = 8 * 1024 * 1024; + + // nsStringBuffer allocates sizeof(nsStringBuffer) + passed size, and + // storageSize below wants extra 1 * sizeof(char_type). + const size_type neededExtraSpace = + sizeof(nsStringBuffer) / sizeof(char_type) + 1; + + size_type temp; + if (aCapacity >= slowGrowthThreshold) { + size_type minNewCapacity = curCapacity + (curCapacity >> 3); // multiply by 1.125 + temp = XPCOM_MAX(aCapacity, minNewCapacity) + neededExtraSpace; + + // Round up to the next multiple of MiB, but ensure the expected + // capacity doesn't include the extra space required by nsStringBuffer + // and null-termination. + const size_t MiB = 1 << 20; + temp = (MiB * ((temp + MiB - 1) / MiB)) - neededExtraSpace; + } else { + // Round up to the next power of two. + temp = + mozilla::RoundUpPow2(aCapacity + neededExtraSpace) - neededExtraSpace; + } + + MOZ_ASSERT(XPCOM_MIN(temp, kMaxCapacity) >= aCapacity, + "should have hit the early return at the top"); + aCapacity = XPCOM_MIN(temp, kMaxCapacity); + } + + // + // several cases: + // + // (1) we have a shared buffer (mFlags & F_SHARED) + // (2) we have an owned buffer (mFlags & F_OWNED) + // (3) we have a fixed buffer (mFlags & F_FIXED) + // (4) we have a readonly buffer + // + // requiring that we in some cases preserve the data before creating + // a new buffer complicates things just a bit ;-) + // + + size_type storageSize = (aCapacity + 1) * sizeof(char_type); + + // case #1 + if (mFlags & F_SHARED) { + nsStringBuffer* hdr = nsStringBuffer::FromData(mData); + if (!hdr->IsReadonly()) { + nsStringBuffer* newHdr = nsStringBuffer::Realloc(hdr, storageSize); + if (!newHdr) { + return false; // out-of-memory (original header left intact) + } + + hdr = newHdr; + mData = (char_type*)hdr->Data(); + mFlags &= ~F_VOIDED; // mutation clears voided flag + return true; + } + } + + char_type* newData; + uint32_t newDataFlags; + + // if we have a fixed buffer of sufficient size, then use it. this helps + // avoid heap allocations. + if ((mFlags & F_CLASS_FIXED) && + (aCapacity < AsFixedString(this)->mFixedCapacity)) { + newData = AsFixedString(this)->mFixedBuf; + newDataFlags = F_TERMINATED | F_FIXED; + } else { + // if we reach here then, we must allocate a new buffer. we cannot + // make use of our F_OWNED or F_FIXED buffers because they are not + // large enough. + + nsStringBuffer* newHdr = + nsStringBuffer::Alloc(storageSize).take(); + if (!newHdr) { + return false; // we are still in a consistent state + } + + newData = (char_type*)newHdr->Data(); + newDataFlags = F_TERMINATED | F_SHARED; + } + + // save old data and flags + *aOldData = mData; + *aOldFlags = mFlags; + + mData = newData; + SetDataFlags(newDataFlags); + + // mLength does not change + + // though we are not necessarily terminated at the moment, now is probably + // still the best time to set F_TERMINATED. + + return true; +} + +void +nsTSubstring_CharT::Finalize() +{ + ::ReleaseData(mData, mFlags); + // mData, mLength, and mFlags are purposefully left dangling +} + +bool +nsTSubstring_CharT::ReplacePrep(index_type aCutStart, + size_type aCutLength, + size_type aNewLength) +{ + aCutLength = XPCOM_MIN(aCutLength, mLength - aCutStart); + + mozilla::CheckedInt<size_type> newTotalLen = mLength; + newTotalLen += aNewLength; + newTotalLen -= aCutLength; + if (!newTotalLen.isValid()) { + return false; + } + + if (aCutStart == mLength && Capacity() > newTotalLen.value()) { + mFlags &= ~F_VOIDED; + mData[newTotalLen.value()] = char_type(0); + mLength = newTotalLen.value(); + return true; + } + + return ReplacePrepInternal(aCutStart, aCutLength, aNewLength, + newTotalLen.value()); +} + +bool +nsTSubstring_CharT::ReplacePrepInternal(index_type aCutStart, size_type aCutLen, + size_type aFragLen, size_type aNewLen) +{ + char_type* oldData; + uint32_t oldFlags; + if (!MutatePrep(aNewLen, &oldData, &oldFlags)) { + return false; // out-of-memory + } + + if (oldData) { + // determine whether or not we need to copy part of the old string + // over to the new string. + + if (aCutStart > 0) { + // copy prefix from old string + char_traits::copy(mData, oldData, aCutStart); + } + + if (aCutStart + aCutLen < mLength) { + // copy suffix from old string to new offset + size_type from = aCutStart + aCutLen; + size_type fromLen = mLength - from; + uint32_t to = aCutStart + aFragLen; + char_traits::copy(mData + to, oldData + from, fromLen); + } + + ::ReleaseData(oldData, oldFlags); + } else { + // original data remains intact + + // determine whether or not we need to move part of the existing string + // to make room for the requested hole. + if (aFragLen != aCutLen && aCutStart + aCutLen < mLength) { + uint32_t from = aCutStart + aCutLen; + uint32_t fromLen = mLength - from; + uint32_t to = aCutStart + aFragLen; + char_traits::move(mData + to, mData + from, fromLen); + } + } + + // add null terminator (mutable mData always has room for the null- + // terminator). + mData[aNewLen] = char_type(0); + mLength = aNewLen; + + return true; +} + +nsTSubstring_CharT::size_type +nsTSubstring_CharT::Capacity() const +{ + // return 0 to indicate an immutable or 0-sized buffer + + size_type capacity; + if (mFlags & F_SHARED) { + // if the string is readonly, then we pretend that it has no capacity. + nsStringBuffer* hdr = nsStringBuffer::FromData(mData); + if (hdr->IsReadonly()) { + capacity = 0; + } else { + capacity = (hdr->StorageSize() / sizeof(char_type)) - 1; + } + } else if (mFlags & F_FIXED) { + capacity = AsFixedString(this)->mFixedCapacity; + } else if (mFlags & F_OWNED) { + // we don't store the capacity of an adopted buffer because that would + // require an additional member field. the best we can do is base the + // capacity on our length. remains to be seen if this is the right + // trade-off. + capacity = mLength; + } else { + capacity = 0; + } + + return capacity; +} + +bool +nsTSubstring_CharT::EnsureMutable(size_type aNewLen) +{ + if (aNewLen == size_type(-1) || aNewLen == mLength) { + if (mFlags & (F_FIXED | F_OWNED)) { + return true; + } + if ((mFlags & F_SHARED) && + !nsStringBuffer::FromData(mData)->IsReadonly()) { + return true; + } + + aNewLen = mLength; + } + return SetLength(aNewLen, mozilla::fallible); +} + +// --------------------------------------------------------------------------- + +// This version of Assign is optimized for single-character assignment. +void +nsTSubstring_CharT::Assign(char_type aChar) +{ + if (!ReplacePrep(0, mLength, 1)) { + AllocFailed(mLength); + } + + *mData = aChar; +} + +bool +nsTSubstring_CharT::Assign(char_type aChar, const fallible_t&) +{ + if (!ReplacePrep(0, mLength, 1)) { + return false; + } + + *mData = aChar; + return true; +} + +void +nsTSubstring_CharT::Assign(const char_type* aData) +{ + if (!Assign(aData, mozilla::fallible)) { + AllocFailed(char_traits::length(aData)); + } +} + +bool +nsTSubstring_CharT::Assign(const char_type* aData, const fallible_t&) +{ + return Assign(aData, size_type(-1), mozilla::fallible); +} + +void +nsTSubstring_CharT::Assign(const char_type* aData, size_type aLength) +{ + if (!Assign(aData, aLength, mozilla::fallible)) { + AllocFailed(aLength == size_type(-1) ? char_traits::length(aData) + : aLength); + } +} + +bool +nsTSubstring_CharT::Assign(const char_type* aData, size_type aLength, + const fallible_t& aFallible) +{ + if (!aData || aLength == 0) { + Truncate(); + return true; + } + + if (aLength == size_type(-1)) { + aLength = char_traits::length(aData); + } + + if (IsDependentOn(aData, aData + aLength)) { + return Assign(string_type(aData, aLength), aFallible); + } + + if (!ReplacePrep(0, mLength, aLength)) { + return false; + } + + char_traits::copy(mData, aData, aLength); + return true; +} + +void +nsTSubstring_CharT::AssignASCII(const char* aData, size_type aLength) +{ + if (!AssignASCII(aData, aLength, mozilla::fallible)) { + AllocFailed(aLength); + } +} + +bool +nsTSubstring_CharT::AssignASCII(const char* aData, size_type aLength, + const fallible_t& aFallible) +{ + // A Unicode string can't depend on an ASCII string buffer, + // so this dependence check only applies to CStrings. +#ifdef CharT_is_char + if (IsDependentOn(aData, aData + aLength)) { + return Assign(string_type(aData, aLength), aFallible); + } +#endif + + if (!ReplacePrep(0, mLength, aLength)) { + return false; + } + + char_traits::copyASCII(mData, aData, aLength); + return true; +} + +void +nsTSubstring_CharT::AssignLiteral(const char_type* aData, size_type aLength) +{ + ::ReleaseData(mData, mFlags); + mData = const_cast<char_type*>(aData); + mLength = aLength; + SetDataFlags(F_TERMINATED | F_LITERAL); +} + +void +nsTSubstring_CharT::Assign(const self_type& aStr) +{ + if (!Assign(aStr, mozilla::fallible)) { + AllocFailed(aStr.Length()); + } +} + +bool +nsTSubstring_CharT::Assign(const self_type& aStr, const fallible_t& aFallible) +{ + // |aStr| could be sharable. We need to check its flags to know how to + // deal with it. + + if (&aStr == this) { + return true; + } + + if (!aStr.mLength) { + Truncate(); + mFlags |= aStr.mFlags & F_VOIDED; + return true; + } + + if (aStr.mFlags & F_SHARED) { + // nice! we can avoid a string copy :-) + + // |aStr| should be null-terminated + NS_ASSERTION(aStr.mFlags & F_TERMINATED, "shared, but not terminated"); + + ::ReleaseData(mData, mFlags); + + mData = aStr.mData; + mLength = aStr.mLength; + SetDataFlags(F_TERMINATED | F_SHARED); + + // get an owning reference to the mData + nsStringBuffer::FromData(mData)->AddRef(); + return true; + } else if (aStr.mFlags & F_LITERAL) { + MOZ_ASSERT(aStr.mFlags & F_TERMINATED, "Unterminated literal"); + + AssignLiteral(aStr.mData, aStr.mLength); + return true; + } + + // else, treat this like an ordinary assignment. + return Assign(aStr.Data(), aStr.Length(), aFallible); +} + +void +nsTSubstring_CharT::Assign(const substring_tuple_type& aTuple) +{ + if (!Assign(aTuple, mozilla::fallible)) { + AllocFailed(aTuple.Length()); + } +} + +bool +nsTSubstring_CharT::Assign(const substring_tuple_type& aTuple, + const fallible_t& aFallible) +{ + if (aTuple.IsDependentOn(mData, mData + mLength)) { + // take advantage of sharing here... + return Assign(string_type(aTuple), aFallible); + } + + size_type length = aTuple.Length(); + + // don't use ReplacePrep here because it changes the length + char_type* oldData; + uint32_t oldFlags; + if (!MutatePrep(length, &oldData, &oldFlags)) { + return false; + } + + if (oldData) { + ::ReleaseData(oldData, oldFlags); + } + + aTuple.WriteTo(mData, length); + mData[length] = 0; + mLength = length; + return true; +} + +void +nsTSubstring_CharT::Adopt(char_type* aData, size_type aLength) +{ + if (aData) { + ::ReleaseData(mData, mFlags); + + if (aLength == size_type(-1)) { + aLength = char_traits::length(aData); + } + + MOZ_RELEASE_ASSERT(CheckCapacity(aLength), "adopting a too-long string"); + + mData = aData; + mLength = aLength; + SetDataFlags(F_TERMINATED | F_OWNED); + + STRING_STAT_INCREMENT(Adopt); + // Treat this as construction of a "StringAdopt" object for leak + // tracking purposes. + MOZ_LOG_CTOR(mData, "StringAdopt", 1); + } else { + SetIsVoid(true); + } +} + + +// This version of Replace is optimized for single-character replacement. +void +nsTSubstring_CharT::Replace(index_type aCutStart, size_type aCutLength, + char_type aChar) +{ + aCutStart = XPCOM_MIN(aCutStart, Length()); + + if (ReplacePrep(aCutStart, aCutLength, 1)) { + mData[aCutStart] = aChar; + } +} + +bool +nsTSubstring_CharT::Replace(index_type aCutStart, size_type aCutLength, + char_type aChar, + const fallible_t&) +{ + aCutStart = XPCOM_MIN(aCutStart, Length()); + + if (!ReplacePrep(aCutStart, aCutLength, 1)) { + return false; + } + + mData[aCutStart] = aChar; + + return true; +} + +void +nsTSubstring_CharT::Replace(index_type aCutStart, size_type aCutLength, + const char_type* aData, size_type aLength) +{ + if (!Replace(aCutStart, aCutLength, aData, aLength, + mozilla::fallible)) { + AllocFailed(Length() - aCutLength + 1); + } +} + +bool +nsTSubstring_CharT::Replace(index_type aCutStart, size_type aCutLength, + const char_type* aData, size_type aLength, + const fallible_t& aFallible) +{ + // unfortunately, some callers pass null :-( + if (!aData) { + aLength = 0; + } else { + if (aLength == size_type(-1)) { + aLength = char_traits::length(aData); + } + + if (IsDependentOn(aData, aData + aLength)) { + nsTAutoString_CharT temp(aData, aLength); + return Replace(aCutStart, aCutLength, temp, aFallible); + } + } + + aCutStart = XPCOM_MIN(aCutStart, Length()); + + bool ok = ReplacePrep(aCutStart, aCutLength, aLength); + if (!ok) { + return false; + } + + if (aLength > 0) { + char_traits::copy(mData + aCutStart, aData, aLength); + } + + return true; +} + +void +nsTSubstring_CharT::ReplaceASCII(index_type aCutStart, size_type aCutLength, + const char* aData, size_type aLength) +{ + if (!ReplaceASCII(aCutStart, aCutLength, aData, aLength, mozilla::fallible)) { + AllocFailed(Length() - aCutLength + 1); + } +} + +bool +nsTSubstring_CharT::ReplaceASCII(index_type aCutStart, size_type aCutLength, + const char* aData, size_type aLength, + const fallible_t& aFallible) +{ + if (aLength == size_type(-1)) { + aLength = strlen(aData); + } + + // A Unicode string can't depend on an ASCII string buffer, + // so this dependence check only applies to CStrings. +#ifdef CharT_is_char + if (IsDependentOn(aData, aData + aLength)) { + nsTAutoString_CharT temp(aData, aLength); + return Replace(aCutStart, aCutLength, temp, aFallible); + } +#endif + + aCutStart = XPCOM_MIN(aCutStart, Length()); + + bool ok = ReplacePrep(aCutStart, aCutLength, aLength); + if (!ok) { + return false; + } + + if (aLength > 0) { + char_traits::copyASCII(mData + aCutStart, aData, aLength); + } + + return true; +} + +void +nsTSubstring_CharT::Replace(index_type aCutStart, size_type aCutLength, + const substring_tuple_type& aTuple) +{ + if (aTuple.IsDependentOn(mData, mData + mLength)) { + nsTAutoString_CharT temp(aTuple); + Replace(aCutStart, aCutLength, temp); + return; + } + + size_type length = aTuple.Length(); + + aCutStart = XPCOM_MIN(aCutStart, Length()); + + if (ReplacePrep(aCutStart, aCutLength, length) && length > 0) { + aTuple.WriteTo(mData + aCutStart, length); + } +} + +void +nsTSubstring_CharT::ReplaceLiteral(index_type aCutStart, size_type aCutLength, + const char_type* aData, size_type aLength) +{ + aCutStart = XPCOM_MIN(aCutStart, Length()); + + if (!aCutStart && aCutLength == Length()) { + AssignLiteral(aData, aLength); + } else if (ReplacePrep(aCutStart, aCutLength, aLength) && aLength > 0) { + char_traits::copy(mData + aCutStart, aData, aLength); + } +} + +void +nsTSubstring_CharT::SetCapacity(size_type aCapacity) +{ + if (!SetCapacity(aCapacity, mozilla::fallible)) { + AllocFailed(aCapacity); + } +} + +bool +nsTSubstring_CharT::SetCapacity(size_type aCapacity, const fallible_t&) +{ + // capacity does not include room for the terminating null char + + // if our capacity is reduced to zero, then free our buffer. + if (aCapacity == 0) { + ::ReleaseData(mData, mFlags); + mData = char_traits::sEmptyBuffer; + mLength = 0; + SetDataFlags(F_TERMINATED); + return true; + } + + char_type* oldData; + uint32_t oldFlags; + if (!MutatePrep(aCapacity, &oldData, &oldFlags)) { + return false; // out-of-memory + } + + // compute new string length + size_type newLen = XPCOM_MIN(mLength, aCapacity); + + if (oldData) { + // preserve old data + if (mLength > 0) { + char_traits::copy(mData, oldData, newLen); + } + + ::ReleaseData(oldData, oldFlags); + } + + // adjust mLength if our buffer shrunk down in size + if (newLen < mLength) { + mLength = newLen; + } + + // always null-terminate here, even if the buffer got longer. this is + // for backwards compat with the old string implementation. + mData[aCapacity] = char_type(0); + + return true; +} + +void +nsTSubstring_CharT::SetLength(size_type aLength) +{ + SetCapacity(aLength); + mLength = aLength; +} + +bool +nsTSubstring_CharT::SetLength(size_type aLength, const fallible_t& aFallible) +{ + if (!SetCapacity(aLength, aFallible)) { + return false; + } + + mLength = aLength; + return true; +} + +void +nsTSubstring_CharT::SetIsVoid(bool aVal) +{ + if (aVal) { + Truncate(); + mFlags |= F_VOIDED; + } else { + mFlags &= ~F_VOIDED; + } +} + +bool +nsTSubstring_CharT::Equals(const self_type& aStr) const +{ + return mLength == aStr.mLength && + char_traits::compare(mData, aStr.mData, mLength) == 0; +} + +bool +nsTSubstring_CharT::Equals(const self_type& aStr, + const comparator_type& aComp) const +{ + return mLength == aStr.mLength && + aComp(mData, aStr.mData, mLength, aStr.mLength) == 0; +} + +bool +nsTSubstring_CharT::Equals(const char_type* aData) const +{ + // unfortunately, some callers pass null :-( + if (!aData) { + NS_NOTREACHED("null data pointer"); + return mLength == 0; + } + + // XXX avoid length calculation? + size_type length = char_traits::length(aData); + return mLength == length && + char_traits::compare(mData, aData, mLength) == 0; +} + +bool +nsTSubstring_CharT::Equals(const char_type* aData, + const comparator_type& aComp) const +{ + // unfortunately, some callers pass null :-( + if (!aData) { + NS_NOTREACHED("null data pointer"); + return mLength == 0; + } + + // XXX avoid length calculation? + size_type length = char_traits::length(aData); + return mLength == length && aComp(mData, aData, mLength, length) == 0; +} + +bool +nsTSubstring_CharT::EqualsASCII(const char* aData, size_type aLen) const +{ + return mLength == aLen && + char_traits::compareASCII(mData, aData, aLen) == 0; +} + +bool +nsTSubstring_CharT::EqualsASCII(const char* aData) const +{ + return char_traits::compareASCIINullTerminated(mData, mLength, aData) == 0; +} + +bool +nsTSubstring_CharT::LowerCaseEqualsASCII(const char* aData, + size_type aLen) const +{ + return mLength == aLen && + char_traits::compareLowerCaseToASCII(mData, aData, aLen) == 0; +} + +bool +nsTSubstring_CharT::LowerCaseEqualsASCII(const char* aData) const +{ + return char_traits::compareLowerCaseToASCIINullTerminated(mData, + mLength, + aData) == 0; +} + +nsTSubstring_CharT::size_type +nsTSubstring_CharT::CountChar(char_type aChar) const +{ + const char_type* start = mData; + const char_type* end = mData + mLength; + + return NS_COUNT(start, end, aChar); +} + +int32_t +nsTSubstring_CharT::FindChar(char_type aChar, index_type aOffset) const +{ + if (aOffset < mLength) { + const char_type* result = char_traits::find(mData + aOffset, + mLength - aOffset, aChar); + if (result) { + return result - mData; + } + } + return -1; +} + +void +nsTSubstring_CharT::StripChar(char_type aChar, int32_t aOffset) +{ + if (mLength == 0 || aOffset >= int32_t(mLength)) { + return; + } + + if (!EnsureMutable()) { // XXX do this lazily? + AllocFailed(mLength); + } + + // XXX(darin): this code should defer writing until necessary. + + char_type* to = mData + aOffset; + char_type* from = mData + aOffset; + char_type* end = mData + mLength; + + while (from < end) { + char_type theChar = *from++; + if (aChar != theChar) { + *to++ = theChar; + } + } + *to = char_type(0); // add the null + mLength = to - mData; +} + +void +nsTSubstring_CharT::StripChars(const char_type* aChars, uint32_t aOffset) +{ + if (aOffset >= uint32_t(mLength)) { + return; + } + + if (!EnsureMutable()) { // XXX do this lazily? + AllocFailed(mLength); + } + + // XXX(darin): this code should defer writing until necessary. + + char_type* to = mData + aOffset; + char_type* from = mData + aOffset; + char_type* end = mData + mLength; + + while (from < end) { + char_type theChar = *from++; + const char_type* test = aChars; + + for (; *test && *test != theChar; ++test); + + if (!*test) { + // Not stripped, copy this char. + *to++ = theChar; + } + } + *to = char_type(0); // add the null + mLength = to - mData; +} + +int +nsTSubstring_CharT::AppendFunc(void* aArg, const char* aStr, uint32_t aLen) +{ + self_type* self = static_cast<self_type*>(aArg); + + // NSPR sends us the final null terminator even though we don't want it + if (aLen && aStr[aLen - 1] == '\0') { + --aLen; + } + + self->AppendASCII(aStr, aLen); + + return aLen; +} + +void +nsTSubstring_CharT::AppendPrintf(const char* aFormat, ...) +{ + va_list ap; + va_start(ap, aFormat); + uint32_t r = PR_vsxprintf(AppendFunc, this, aFormat, ap); + if (r == (uint32_t)-1) { + NS_RUNTIMEABORT("Allocation or other failure in PR_vsxprintf"); + } + va_end(ap); +} + +void +nsTSubstring_CharT::AppendPrintf(const char* aFormat, va_list aAp) +{ + uint32_t r = PR_vsxprintf(AppendFunc, this, aFormat, aAp); + if (r == (uint32_t)-1) { + NS_RUNTIMEABORT("Allocation or other failure in PR_vsxprintf"); + } +} + +/* hack to make sure we define FormatWithoutTrailingZeros only once */ +#ifdef CharT_is_PRUnichar +// Returns the length of the formatted aDouble in aBuf. +static int +FormatWithoutTrailingZeros(char (&aBuf)[40], double aDouble, + int aPrecision) +{ + static const DoubleToStringConverter converter(DoubleToStringConverter::UNIQUE_ZERO | + DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN, + "Infinity", + "NaN", + 'e', + -6, 21, + 6, 1); + double_conversion::StringBuilder builder(aBuf, sizeof(aBuf)); + bool exponential_notation = false; + converter.ToPrecision(aDouble, aPrecision, &exponential_notation, &builder); + int length = builder.position(); + char* formattedDouble = builder.Finalize(); + + // If we have a shorter string than aPrecision, it means we have a special + // value (NaN or Infinity). All other numbers will be formatted with at + // least aPrecision digits. + if (length <= aPrecision) { + return length; + } + + char* end = formattedDouble + length; + char* decimalPoint = strchr(aBuf, '.'); + // No trailing zeros to remove. + if (!decimalPoint) { + return length; + } + + if (MOZ_UNLIKELY(exponential_notation)) { + // We need to check for cases like 1.00000e-10 (yes, this is + // disgusting). + char* exponent = end - 1; + for (; ; --exponent) { + if (*exponent == 'e') { + break; + } + } + char* zerosBeforeExponent = exponent - 1; + for (; zerosBeforeExponent != decimalPoint; --zerosBeforeExponent) { + if (*zerosBeforeExponent != '0') { + break; + } + } + if (zerosBeforeExponent == decimalPoint) { + --zerosBeforeExponent; + } + // Slide the exponent to the left over the trailing zeros. Don't + // worry about copying the trailing NUL character. + size_t exponentSize = end - exponent; + memmove(zerosBeforeExponent + 1, exponent, exponentSize); + length -= exponent - (zerosBeforeExponent + 1); + } else { + char* trailingZeros = end - 1; + for (; trailingZeros != decimalPoint; --trailingZeros) { + if (*trailingZeros != '0') { + break; + } + } + if (trailingZeros == decimalPoint) { + --trailingZeros; + } + length -= end - (trailingZeros + 1); + } + + return length; +} +#endif /* CharT_is_PRUnichar */ + +void +nsTSubstring_CharT::AppendFloat(float aFloat) +{ + char buf[40]; + int length = FormatWithoutTrailingZeros(buf, aFloat, 6); + AppendASCII(buf, length); +} + +void +nsTSubstring_CharT::AppendFloat(double aFloat) +{ + char buf[40]; + int length = FormatWithoutTrailingZeros(buf, aFloat, 15); + AppendASCII(buf, length); +} + +size_t +nsTSubstring_CharT::SizeOfExcludingThisIfUnshared( + mozilla::MallocSizeOf aMallocSizeOf) const +{ + if (mFlags & F_SHARED) { + return nsStringBuffer::FromData(mData)-> + SizeOfIncludingThisIfUnshared(aMallocSizeOf); + } + if (mFlags & F_OWNED) { + return aMallocSizeOf(mData); + } + + // If we reach here, exactly one of the following must be true: + // - F_VOIDED is set, and mData points to sEmptyBuffer; + // - F_FIXED is set, and mData points to a buffer within a string + // object (e.g. nsAutoString); + // - None of F_SHARED, F_OWNED, F_FIXED is set, and mData points to a buffer + // owned by something else. + // + // In all three cases, we don't measure it. + return 0; +} + +size_t +nsTSubstring_CharT::SizeOfExcludingThisEvenIfShared( + mozilla::MallocSizeOf aMallocSizeOf) const +{ + // This is identical to SizeOfExcludingThisIfUnshared except for the + // F_SHARED case. + if (mFlags & F_SHARED) { + return nsStringBuffer::FromData(mData)-> + SizeOfIncludingThisEvenIfShared(aMallocSizeOf); + } + if (mFlags & F_OWNED) { + return aMallocSizeOf(mData); + } + return 0; +} + +size_t +nsTSubstring_CharT::SizeOfIncludingThisIfUnshared( + mozilla::MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this) + SizeOfExcludingThisIfUnshared(aMallocSizeOf); +} + +size_t +nsTSubstring_CharT::SizeOfIncludingThisEvenIfShared( + mozilla::MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this) + SizeOfExcludingThisEvenIfShared(aMallocSizeOf); +} + |