| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439 |
- //-------------------------------------------------------------------------------------------------------
- // Copyright (C) Microsoft. All rights reserved.
- // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
- //-------------------------------------------------------------------------------------------------------
- #pragma once
- namespace Js
- {
- template <typename TAllocator>
- class StringBuilder
- {
- private:
- struct Data
- {
- public:
- union {
- struct st_Single
- {
- char16 buffer[];
- } single;
- struct st_Chained
- {
- charcount_t length;
- Data *next;
- char16 buffer[];
- } chained;
- }u;
- };
- private:
- static const charcount_t MaxLength = INT_MAX - 1;
- const static charcount_t MaxRealloc = 64;
- TAllocator* alloc;
- // First chunk is just a buffer, and which can be detached without copying.
- Data *firstChunk;
- // Second chunk is a chained list of chunks. UnChain() needs to be called to copy the first chunk
- // and the list of chained chunks to a single buffer on calls to GetBuffer().
- Data *secondChunk;
- Data *lastChunk;
- char16 * appendPtr;
- charcount_t length; // Total capacity (allocated number of elements - 1), in all chunks. Note that we keep one allocated element which is not accounted in length for terminating '\0'.
- charcount_t count; // Total number of elements, in all chunks.
- charcount_t firstChunkLength;
- charcount_t initialSize;
- bool IsChained() { return this->secondChunk != NULL; }
- Data *NewChainedChunk(charcount_t bufLengthRequested)
- {
- CompileAssert(sizeof(charcount_t) == sizeof(uint32));
- // allocation = (bufLengthRequested * sizeof(char16) + sizeof(Data)
- charcount_t alloc32 = UInt32Math::MulAdd<sizeof(char16), sizeof(Data)>(bufLengthRequested);
- size_t allocation = TAllocator::GetAlignedSize(alloc32);
- size_t size_t_length = (allocation - sizeof(Data)) / sizeof(char16);
- charcount_t bufLength = (charcount_t)size_t_length;
- Assert(bufLength == size_t_length);
- Data *newChunk = AllocatorNewStructPlus(TAllocator, this->alloc, allocation, Data);
- newChunk->u.chained.length = bufLength;
- newChunk->u.chained.next = NULL;
- // Recycler gives zeroed memory, so rely on that instead of memsetting the tail
- #if 0
- // Align memset to machine register size for perf
- bufLengthRequested &= ~(sizeof(size_t) - 1);
- memset(newChunk->u.chained.buffer + bufLengthRequested, 0, (bufLength - bufLengthRequested) * sizeof(char16));
- #endif
- return newChunk;
- }
- Data *NewSingleChunk(charcount_t *pBufLengthRequested)
- {
- Assert(*pBufLengthRequested <= MaxLength);
- // Let's just grow the current chunk in place
- CompileAssert(sizeof(charcount_t) == sizeof(uint32));
- //// allocation = (bufLengthRequested+1) * sizeof(char16)
- charcount_t alloc32 = UInt32Math::AddMul< 1, sizeof(char16) >(*pBufLengthRequested);
- size_t allocation = HeapInfo::GetAlignedSize(alloc32);
- size_t size_t_newLength = allocation / sizeof(char16) - 1;
- charcount_t newLength = (charcount_t)size_t_newLength;
- Assert(newLength == size_t_newLength);
- Assert(newLength <= MaxLength + 1);
- if (newLength == MaxLength + 1)
- {
- // newLength could be MaxLength + 1 because of alignment.
- // In this case alloc size is 2 elements more than newLength (normally 1 elements more for NULL), that's fine.
- newLength = MaxLength;
- }
- Assert(newLength <= MaxLength);
- Data* newChunk = AllocatorNewStructPlus(TAllocator, this->alloc, allocation, Data);
- newChunk->u.single.buffer[newLength] = _u('\0');
- *pBufLengthRequested = newLength;
- return newChunk;
- }
- _NOINLINE void ExtendBuffer(charcount_t newLength)
- {
- Data *newChunk;
- // To maintain this->length under MaxLength, check it here/throw, this is the only place we grow the buffer.
- if (newLength > MaxLength)
- {
- Throw::OutOfMemory();
- }
- Assert(this->length <= MaxLength);
- charcount_t newLengthTryGrowPolicy = newLength + (this->length*2/3); // Note: this would never result in uint32 overflow.
- if (newLengthTryGrowPolicy <= MaxLength)
- {
- newLength = newLengthTryGrowPolicy;
- }
- Assert(newLength <= MaxLength);
- // We already have linked chunks
- if (this->IsChained() || (this->firstChunk != NULL && newLength - this->length > MaxRealloc))
- {
- newChunk = this->NewChainedChunk(newLength - this->count);
- if (this->IsChained())
- {
- this->lastChunk->u.chained.next = newChunk;
- // We're not going to use the extra space in the current chunk...
- Assert(this->lastChunk->u.chained.length > this->length - this->count);
- this->lastChunk->u.chained.length -= (this->length - this->count);
- }
- else
- {
- // Time to add our first linked chunk
- Assert(this->secondChunk == NULL);
- this->secondChunk = newChunk;
- // We're not going to use the extra space in the current chunk...
- this->firstChunkLength = this->count;
- }
- this->length = this->count + newChunk->u.chained.length;
- this->lastChunk = newChunk;
- this->appendPtr = newChunk->u.chained.buffer;
- }
- else
- {
- if (this->initialSize < MaxLength)
- {
- newLength = max(newLength, this->initialSize + 1);
- }
- else
- {
- newLength = MaxLength;
- }
- Assert(newLength <= MaxLength);
- // Let's just grow the current chunk in place
- newChunk = this->NewSingleChunk(&newLength);
- if (this->count)
- {
- js_wmemcpy_s(newChunk->u.single.buffer, newLength, this->firstChunk->u.single.buffer, this->count);
- }
- this->firstChunk = this->lastChunk = newChunk;
- this->firstChunkLength = newLength;
- this->length = newLength;
- this->appendPtr = newChunk->u.single.buffer + this->count;
- }
- }
- void EnsureBuffer(charcount_t countNeeded)
- {
- if(countNeeded == 0) return;
- if (countNeeded >= this->length - this->count)
- {
- if (countNeeded > MaxLength)
- {
- // Check upfront to prevent potential uint32 overflow caused by (this->count + countNeeded + 1).
- Throw::OutOfMemory();
- }
- ExtendBuffer(this->count + countNeeded + 1);
- }
- }
- public:
- static StringBuilder<TAllocator> *
- New(TAllocator* alloc, charcount_t initialSize)
- {
- if (initialSize > MaxLength)
- {
- Throw::OutOfMemory();
- }
- return AllocatorNew(TAllocator, alloc, StringBuilder<TAllocator>, alloc, initialSize);
- }
- StringBuilder(TAllocator* alloc)
- {
- new (this) StringBuilder(alloc, 0);
- }
- StringBuilder(TAllocator* alloc, charcount_t initialSize) : alloc(alloc), length(0), count(0), firstChunk(NULL),
- secondChunk(NULL), appendPtr(NULL), initialSize(initialSize)
- {
- if (initialSize > MaxLength)
- {
- Throw::OutOfMemory();
- }
- }
- void UnChain(__out __ecount(bufLen) char16 *pBuf, charcount_t bufLen)
- {
- charcount_t lastChunkCount = this->count;
- Assert(this->IsChained());
- Assert(bufLen >= this->count);
- char16 *pSrcBuf = this->firstChunk->u.single.buffer;
- Data *next = this->secondChunk;
- charcount_t srcLength = this->firstChunkLength;
- for (Data *chunk = this->firstChunk; chunk != this->lastChunk; next = chunk->u.chained.next)
- {
- if (bufLen < srcLength)
- {
- Throw::FatalInternalError();
- }
- js_wmemcpy_s(pBuf, bufLen, pSrcBuf, srcLength);
- bufLen -= srcLength;
- pBuf += srcLength;
- lastChunkCount -= srcLength;
- chunk = next;
- pSrcBuf = chunk->u.chained.buffer;
- srcLength = chunk->u.chained.length;
- }
- if (bufLen < lastChunkCount)
- {
- Throw::FatalInternalError();
- }
- js_wmemcpy_s(pBuf, bufLen, this->lastChunk->u.chained.buffer, lastChunkCount);
- }
- void UnChain()
- {
- Assert(this->IsChained());
- charcount_t newLength = this->count;
- Data *newChunk = this->NewSingleChunk(&newLength);
- this->length = newLength;
- this->UnChain(newChunk->u.single.buffer, newLength);
- this->firstChunk = this->lastChunk = newChunk;
- this->secondChunk = NULL;
- this->appendPtr = newChunk->u.single.buffer + this->count;
- }
- void Copy(__out __ecount(bufLen) char16 *pBuf, charcount_t bufLen)
- {
- if (this->IsChained())
- {
- this->UnChain(pBuf, bufLen);
- }
- else
- {
- if (bufLen < this->count)
- {
- Throw::FatalInternalError();
- }
- js_wmemcpy_s(pBuf, bufLen, this->firstChunk->u.single.buffer, this->count);
- }
- }
- inline const char16* Buffer()
- {
- if (this->IsChained())
- {
- this->UnChain();
- }
- if (this->firstChunk)
- {
- this->firstChunk->u.single.buffer[this->count] = _u('\0');
- return this->firstChunk->u.single.buffer;
- }
- else
- {
- return _u("");
- }
- }
- inline charcount_t Count() { return this->count; }
- void Append(char16 c)
- {
- if (this->count == this->length)
- {
- ExtendBuffer(this->length+1);
- }
- *(this->appendPtr++) = c;
- this->count++;
- }
- void AppendSz(const char16 * str)
- {
- // WARNING!!
- // Do not use this to append JavascriptStrings. They can have embedded
- // nulls which obviously won't be handled correctly here. Instead use
- // Append with a length, which will use memcpy and correctly include any
- // embedded null characters.
- // WARNING!!
- while (*str != _u('\0'))
- {
- Append(*str++);
- }
- }
- void Append(const char16 * str, charcount_t countNeeded)
- {
- EnsureBuffer(countNeeded);
- char16 *dst = this->appendPtr;
- JavascriptString::CopyHelper(dst, str, countNeeded);
- this->appendPtr += countNeeded;
- this->count += countNeeded;
- }
- template <size_t N>
- void AppendCppLiteral(const char16(&str)[N])
- {
- // Need to account for the terminating null character in C++ string literals, hence N > 2 and N - 1 below
- static_assert(N > 2, "Use Append(char16) for appending literal single characters and do not append empty string literal");
- Append(str, N - 1);
- }
- // If we expect str to be large - we should just use this version that uses memcpy directly instead of Append
- void AppendLarge(const char16 * str, charcount_t countNeeded)
- {
- EnsureBuffer(countNeeded);
- char16 *dst = this->appendPtr;
- js_wmemcpy_s(dst, countNeeded, str, countNeeded);
- this->appendPtr += countNeeded;
- this->count += countNeeded;
- }
- errno_t AppendUint64(unsigned __int64 value)
- {
- const int max_length = 20; // maximum length of 64-bit value converted to base 10 string
- const int radix = 10;
- WCHAR buf[max_length+1];
- errno_t result = _ui64tow_s(value, buf, max_length+1, radix);
- AssertMsg(result==0, "Failed to translate value to string");
- if (result == 0)
- {
- AppendSz(buf);
- }
- return result;
- }
- char16 *AllocBufferSpace(charcount_t countNeeded)
- {
- EnsureBuffer(countNeeded);
- return this->appendPtr;
- }
- void IncreaseCount(charcount_t countInc)
- {
- if(countInc == 0) return;
- this->count += countInc;
- this->appendPtr += countInc;
- Assert(this->count < this->length);
- }
- char16* Detach()
- {
- // NULL terminate the string
- Append(_u('\0'));
- // if there is a chain we need to account for that also, so that the new buffer will have the NULL at the end.
- if (this->IsChained())
- {
- this->UnChain();
- }
- // Now decrement the count to adjust according to number of chars
- this->count--;
- char16* result = this->firstChunk->u.single.buffer;
- this->firstChunk = this->lastChunk = NULL;
- return result;
- }
- void TrimTrailingNULL()
- {
- Assert(this->count);
- if (this->IsChained())
- {
- Assert(this->lastChunk->u.chained.buffer[this->count - (this->length - this->lastChunk->u.chained.length) - 1] == _u('\0'));
- }
- else
- {
- Assert(this->lastChunk->u.single.buffer[this->count - 1] == _u('\0'));
- }
- this->appendPtr--;
- this->count--;
- }
- void Reset()
- {
- this->length = 0;
- this->count = 0;
- this->firstChunk = NULL;
- this->secondChunk = NULL;
- this->lastChunk = NULL;
- }
- };
- }
|