| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568 |
- //-------------------------------------------------------------------------------------------------------
- // Copyright (C) Microsoft. All rights reserved.
- // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
- //-------------------------------------------------------------------------------------------------------
- #include "RuntimeLibraryPch.h"
- #include "Codex/Utf8Helper.h"
- namespace Js
- {
- DEFINE_RECYCLER_TRACKER_PERF_COUNTER(ConcatString);
- // Note: see also: ConcatString.inl
- LiteralStringWithPropertyStringPtr::LiteralStringWithPropertyStringPtr(StaticType* stringType) :
- LiteralString(stringType),
- propertyString(nullptr),
- propertyRecord(nullptr)
- {
- }
- LiteralStringWithPropertyStringPtr::LiteralStringWithPropertyStringPtr(const char16 * wString,
- const CharCount stringLength, JavascriptLibrary *const library) :
- LiteralString(library->GetStringTypeStatic(), wString, stringLength),
- propertyString(nullptr),
- propertyRecord(nullptr)
- {
- }
- JavascriptString * LiteralStringWithPropertyStringPtr::
- NewFromWideString(const char16 * wideString, const CharCount charCount, JavascriptLibrary *const library)
- {
- Assert(library != nullptr && wideString != nullptr);
- switch (charCount)
- {
- case 0:
- {
- JavascriptString * emptyString = library->GetEmptyString();
- AssertMsg(VirtualTableInfo<Js::LiteralStringWithPropertyStringPtr>::HasVirtualTable(emptyString),
- "Library::GetEmptyString is no longer LiteralStringWithPropertyStringPtr ?");
- return emptyString;
- }
- case 1:
- {
- return library->GetCharStringCache().GetStringForChar((char16(*wideString)));
- }
- default:
- break;
- }
- Recycler * recycler = library->GetRecycler();
- ScriptContext * scriptContext = library->GetScriptContext();
- char16* destString = RecyclerNewArrayLeaf(recycler, WCHAR, charCount + 1);
- if (destString == nullptr)
- {
- Js::JavascriptError::ThrowOutOfMemoryError(scriptContext);
- }
- js_wmemcpy_s(destString, charCount, wideString, charCount);
- destString[charCount] = char16(0);
- return (JavascriptString*) RecyclerNew(library->GetRecycler(), LiteralStringWithPropertyStringPtr, destString, charCount, library);
- }
- JavascriptString * LiteralStringWithPropertyStringPtr::CreateEmptyString(JavascriptLibrary *const library)
- {
- return (JavascriptString*) RecyclerNew(library->GetRecycler(), LiteralStringWithPropertyStringPtr, _u(""), 0, library);
- }
- JavascriptString * LiteralStringWithPropertyStringPtr::
- NewFromCString(const char * cString, const CharCount charCount, JavascriptLibrary *const library)
- {
- Assert(library != nullptr && cString != nullptr);
- switch (charCount)
- {
- case 0:
- {
- JavascriptString * emptyString = library->GetEmptyString();
- AssertMsg(VirtualTableInfo<Js::LiteralStringWithPropertyStringPtr>::HasVirtualTable(emptyString),
- "Library::GetEmptyString is no longer LiteralStringWithPropertyStringPtr ?");
- return (LiteralStringWithPropertyStringPtr*) emptyString;
- }
- case 1:
- {
- // If the high bit of the byte is set, it cannot be a complete utf8 codepoint, so fall back to the unicode replacement char
- if ((*cString & 0x80) != 0x80)
- {
- return library->GetCharStringCache().GetStringForChar((char16(*cString)));
- }
- else
- {
- return library->GetCharStringCache().GetStringForChar(0xFFFD);
- }
- }
- default:
- break;
- }
- ScriptContext * scriptContext = library->GetScriptContext();
- if (charCount > MaxCharCount)
- {
- Js::JavascriptError::ThrowOutOfMemoryError(scriptContext);
- }
- Recycler * recycler = library->GetRecycler();
- char16* destString = RecyclerNewArrayLeaf(recycler, WCHAR, charCount + 1);
- if (destString == nullptr)
- {
- Js::JavascriptError::ThrowOutOfMemoryError(scriptContext);
- }
- charcount_t cchDestString = 0;
- HRESULT result = utf8::NarrowStringToWideNoAlloc(cString, charCount, destString, charCount + 1, &cchDestString);
- if (result == S_OK)
- {
- return (JavascriptString*) RecyclerNew(library->GetRecycler(), LiteralStringWithPropertyStringPtr, destString, cchDestString, library);
- }
- Js::JavascriptError::ThrowOutOfMemoryError(scriptContext);
- }
- PropertyString * LiteralStringWithPropertyStringPtr::GetPropertyString() const
- {
- return this->propertyString;
- }
- PropertyString * LiteralStringWithPropertyStringPtr::GetOrAddPropertyString()
- {
- if (this->propertyString != nullptr)
- {
- return this->propertyString;
- }
- ScriptContext * scriptContext = this->GetScriptContext();
- if (this->propertyRecord == nullptr)
- {
- Js::PropertyRecord const * propertyRecord = nullptr;
- scriptContext->GetOrAddPropertyRecord(this->GetSz(), static_cast<int>(this->GetLength()),
- &propertyRecord);
- this->propertyRecord = propertyRecord;
- }
- this->propertyString = scriptContext->GetPropertyString(propertyRecord->GetPropertyId());
- return this->propertyString;
- }
- void LiteralStringWithPropertyStringPtr::SetPropertyString(PropertyString * propStr)
- {
- this->propertyString = propStr;
- if (propStr != nullptr)
- {
- Js::PropertyRecord const * localPropertyRecord;
- propStr->GetPropertyRecord(&localPropertyRecord);
- this->propertyRecord = localPropertyRecord;
- }
- }
- /* static */
- bool LiteralStringWithPropertyStringPtr::Is(RecyclableObject * obj)
- {
- return VirtualTableInfo<Js::LiteralStringWithPropertyStringPtr>::HasVirtualTable(obj);
- }
- /* static */
- bool LiteralStringWithPropertyStringPtr::Is(Var var)
- {
- return RecyclableObject::Is(var) && LiteralStringWithPropertyStringPtr::Is(RecyclableObject::UnsafeFromVar(var));
- }
- void LiteralStringWithPropertyStringPtr::GetPropertyRecord(_Out_ PropertyRecord const** propRecord, bool dontLookupFromDictionary)
- {
- *propRecord = nullptr;
- ScriptContext * scriptContext = this->GetScriptContext();
- if (this->propertyRecord == nullptr)
- {
- if (!dontLookupFromDictionary)
- {
- // cache PropertyRecord
- Js::PropertyRecord const * localPropertyRecord;
- scriptContext->GetOrAddPropertyRecord(this->GetSz(),
- static_cast<int>(this->GetLength()),
- &localPropertyRecord);
- this->propertyRecord = localPropertyRecord;
- }
- else
- {
- return;
- }
- }
- *propRecord = this->propertyRecord;
- }
- /////////////////////// ConcatStringBase //////////////////////////
- ConcatStringBase::ConcatStringBase(StaticType* stringType) : LiteralString(stringType)
- {
- }
- // Copy the content of items into specified buffer.
- void ConcatStringBase::CopyImpl(_Out_writes_(m_charLength) char16 *const buffer,
- int itemCount, _In_reads_(itemCount) JavascriptString * const * items,
- StringCopyInfoStack &nestedStringTreeCopyInfos, const byte recursionDepth)
- {
- Assert(!IsFinalized());
- Assert(buffer);
- CharCount copiedCharLength = 0;
- for(int i = 0; i < itemCount; ++i)
- {
- JavascriptString *const s = items[i];
- if(!s)
- {
- continue;
- }
- if (s->IsFinalized())
- {
- // If we have the buffer already, just copy it
- const CharCount copyCharLength = s->GetLength();
- AnalysisAssert(copiedCharLength + copyCharLength <= this->GetLength());
- CopyHelper(&buffer[copiedCharLength], s->GetString(), copyCharLength);
- copiedCharLength += copyCharLength;
- continue;
- }
- if(i == itemCount - 1)
- {
- JavascriptString * const * newItems;
- int newItemCount = s->GetRandomAccessItemsFromConcatString(newItems);
- if (newItemCount != -1)
- {
- // Optimize for right-weighted ConcatString tree (the append case). Even though appending to a ConcatString will
- // transition into a CompoundString fairly quickly, strings created by doing just a few appends are very common.
- items = newItems;
- itemCount = newItemCount;
- i = -1;
- continue;
- }
- }
- const CharCount copyCharLength = s->GetLength();
- AnalysisAssert(copyCharLength <= GetLength() - copiedCharLength);
- if(recursionDepth == MaxCopyRecursionDepth && s->IsTree())
- {
- // Don't copy nested string trees yet, as that involves a recursive call, and the recursion can become
- // excessive. Just collect the nested string trees and the buffer location where they should be copied, and
- // the caller can deal with those after returning.
- nestedStringTreeCopyInfos.Push(StringCopyInfo(s, &buffer[copiedCharLength]));
- }
- else
- {
- Assert(recursionDepth <= MaxCopyRecursionDepth);
- s->Copy(&buffer[copiedCharLength], nestedStringTreeCopyInfos, recursionDepth + 1);
- }
- copiedCharLength += copyCharLength;
- }
- Assert(copiedCharLength == GetLength());
- }
- bool ConcatStringBase::IsTree() const
- {
- Assert(!IsFinalized());
- return true;
- }
- /////////////////////// ConcatString //////////////////////////
- ConcatString::ConcatString(JavascriptString* a, JavascriptString* b) :
- ConcatStringN<2>(a->GetLibrary()->GetStringTypeStatic(), false)
- {
- Assert(a);
- Assert(b);
- a = CompoundString::GetImmutableOrScriptUnreferencedString(a);
- b = CompoundString::GetImmutableOrScriptUnreferencedString(b);
- m_slots[0] = a;
- m_slots[1] = b;
- this->SetLength(a->GetLength() + b->GetLength()); // does not include null character
- }
- ConcatString* ConcatString::New(JavascriptString* left, JavascriptString* right)
- {
- Assert(left);
- #ifdef PROFILE_STRINGS
- StringProfiler::RecordConcatenation( left->GetScriptContext(), left->GetLength(), right->GetLength(), ConcatType_ConcatTree);
- #endif
- Recycler* recycler = left->GetScriptContext()->GetRecycler();
- return RecyclerNew(recycler, ConcatString, left, right);
- }
- /////////////////////// ConcatStringBuilder //////////////////////////
- // MAX number of slots in one chunk. Until we fit into this, we realloc, otherwise create new chunk.
- // The VS2013 linker treats this as a redefinition of an already
- // defined constant and complains. So skip the declaration if we're compiling
- // with VS2013 or below.
- #if !defined(_MSC_VER) || _MSC_VER >= 1900
- const int ConcatStringBuilder::c_maxChunkSlotCount;
- #endif
- ConcatStringBuilder::ConcatStringBuilder(ScriptContext* scriptContext, int initialSlotCount) :
- ConcatStringBase(scriptContext->GetLibrary()->GetStringTypeStatic()),
- m_count(0), m_prevChunk(nullptr)
- {
- Assert(scriptContext);
- // Note: m_slotCount is a valid scenario -- when you don't know how many will be there.
- this->AllocateSlots(initialSlotCount);
- this->SetLength(0); // does not include null character
- }
- ConcatStringBuilder::ConcatStringBuilder(const ConcatStringBuilder& other):
- ConcatStringBase(other.GetScriptContext()->GetLibrary()->GetStringTypeStatic())
- {
- m_slots = other.m_slots;
- m_count = other.m_count;
- m_slotCount = other.m_slotCount;
- m_prevChunk = other.m_prevChunk;
- this->SetLength(other.GetLength());
- // TODO: should we copy the JavascriptString buffer and if so, how do we pass over the ownership?
- }
- ConcatStringBuilder* ConcatStringBuilder::New(ScriptContext* scriptContext, int initialSlotCount)
- {
- Assert(scriptContext);
- return RecyclerNew(scriptContext->GetRecycler(), ConcatStringBuilder, scriptContext, initialSlotCount);
- }
- const char16 * ConcatStringBuilder::GetSz()
- {
- const char16 * sz = GetSzImpl<ConcatStringBuilder>();
- // Allow a/b to be garbage collected if no more refs.
- ConcatStringBuilder* current = this;
- while (current != NULL)
- {
- ClearArray(current->m_slots, current->m_count);
- current = current->m_prevChunk;
- }
- LiteralStringWithPropertyStringPtr::ConvertString(this);
- return sz;
- }
- // Append/concat a new string to us.
- // The idea is that we will grow/realloc current slot if new size fits into MAX chunk size (c_maxChunkSlotCount).
- // Otherwise we will create a new chunk.
- void ConcatStringBuilder::Append(JavascriptString* str)
- {
- // Note: we are quite lucky here because we always add 1 (no more) string to us.
- Assert(str);
- charcount_t len = this->GetLength(); // This is len of all chunks.
- if (m_count == m_slotCount)
- {
- // Out of free slots, current chunk is full, need to grow.
- int oldItemCount = this->GetItemCount();
- int newItemCount = oldItemCount > 0 ?
- oldItemCount > 1 ? oldItemCount + oldItemCount / 2 : 2 :
- 1;
- Assert(newItemCount > oldItemCount);
- int growDelta = newItemCount - oldItemCount; // # of items to grow by.
- int newSlotCount = m_slotCount + growDelta;
- if (newSlotCount <= c_maxChunkSlotCount)
- {
- // While we fit into MAX chunk size, realloc/grow current chunk.
- Field(JavascriptString*)* newSlots = RecyclerNewArray(
- this->GetScriptContext()->GetRecycler(), Field(JavascriptString*), newSlotCount);
- CopyArray(newSlots, newSlotCount, m_slots, m_slotCount);
- m_slots = newSlots;
- m_slotCount = newSlotCount;
- }
- else
- {
- // Create new chunk with MAX size, swap new instance's data with this's data.
- // We never create more than one chunk at a time.
- ConcatStringBuilder* newChunk = RecyclerNew(this->GetScriptContext()->GetRecycler(), ConcatStringBuilder, *this); // Create a copy.
- m_prevChunk = newChunk;
- m_count = 0;
- AllocateSlots(this->c_maxChunkSlotCount);
- Assert(m_slots);
- }
- }
- str = CompoundString::GetImmutableOrScriptUnreferencedString(str);
- m_slots[m_count++] = str;
- len += str->GetLength();
- this->SetLength(len);
- }
- // Allocate slots, set m_slots and m_slotCount.
- // Note: the amount of slots allocated can be less than the requestedSlotCount parameter.
- void ConcatStringBuilder::AllocateSlots(int requestedSlotCount)
- {
- if (requestedSlotCount > 0)
- {
- m_slotCount = min(requestedSlotCount, this->c_maxChunkSlotCount);
- m_slots = RecyclerNewArray(this->GetScriptContext()->GetRecycler(), Field(JavascriptString*), m_slotCount);
- }
- else
- {
- m_slotCount = 0;
- m_slots = nullptr;
- }
- }
- // Returns the number of JavascriptString* items accumulated so far in all chunks.
- int ConcatStringBuilder::GetItemCount() const
- {
- int count = 0;
- const ConcatStringBuilder* current = this;
- while (current != NULL)
- {
- count += current->m_count;
- current = current->m_prevChunk;
- }
- return count;
- }
- ConcatStringBuilder* ConcatStringBuilder::GetHead() const
- {
- ConcatStringBuilder* current = const_cast<ConcatStringBuilder*>(this);
- ConcatStringBuilder* head;
- do
- {
- head = current;
- current = current->m_prevChunk;
- } while (current != NULL);
- return head;
- }
- void ConcatStringBuilder::CopyVirtual(
- _Out_writes_(m_charLength) char16 *const buffer,
- StringCopyInfoStack &nestedStringTreeCopyInfos,
- const byte recursionDepth)
- {
- Assert(!this->IsFinalized());
- Assert(buffer);
- CharCount remainingCharLengthToCopy = GetLength();
- for(const ConcatStringBuilder *current = this; current; current = current->m_prevChunk)
- {
- for(int i = current->m_count - 1; i >= 0; --i)
- {
- JavascriptString *const s = current->m_slots[i];
- if(!s)
- {
- continue;
- }
- const CharCount copyCharLength = s->GetLength();
- Assert(remainingCharLengthToCopy >= copyCharLength);
- remainingCharLengthToCopy -= copyCharLength;
- if(recursionDepth == MaxCopyRecursionDepth && s->IsTree())
- {
- // Don't copy nested string trees yet, as that involves a recursive call, and the recursion can become
- // excessive. Just collect the nested string trees and the buffer location where they should be copied, and
- // the caller can deal with those after returning.
- nestedStringTreeCopyInfos.Push(StringCopyInfo(s, &buffer[remainingCharLengthToCopy]));
- }
- else
- {
- Assert(recursionDepth <= MaxCopyRecursionDepth);
- s->Copy(&buffer[remainingCharLengthToCopy], nestedStringTreeCopyInfos, recursionDepth + 1);
- }
- }
- }
- }
- /////////////////////// ConcatStringMulti //////////////////////////
- ConcatStringMulti::ConcatStringMulti(uint slotCount, JavascriptString * a1, JavascriptString * a2, StaticType* stringTypeStatic) :
- ConcatStringBase(stringTypeStatic), slotCount(slotCount)
- {
- #if DBG
- ClearArray(m_slots, slotCount);
- #endif
- m_slots[0] = CompoundString::GetImmutableOrScriptUnreferencedString(a1);
- m_slots[1] = CompoundString::GetImmutableOrScriptUnreferencedString(a2);
- this->SetLength(a1->GetLength() + a2->GetLength());
- }
- size_t
- ConcatStringMulti::GetAllocSize(uint slotCount)
- {
- return sizeof(ConcatStringMulti) + (sizeof(JavascriptString *) * slotCount);
- }
- ConcatStringMulti*
- ConcatStringMulti::New(uint slotCount, JavascriptString * a1, JavascriptString * a2, ScriptContext * scriptContext)
- {
- return RecyclerNewPlus(scriptContext->GetRecycler(),
- sizeof(JavascriptString *) * slotCount, ConcatStringMulti, slotCount, a1, a2,
- scriptContext->GetLibrary()->GetStringTypeStatic());
- }
- bool
- ConcatStringMulti::Is(Var var)
- {
- return VirtualTableInfo<ConcatStringMulti>::HasVirtualTable(var);
- }
- ConcatStringMulti *
- ConcatStringMulti::FromVar(Var var)
- {
- AssertOrFailFast(ConcatStringMulti::Is(var));
- return static_cast<ConcatStringMulti *>(var);
- }
- ConcatStringMulti *
- ConcatStringMulti::UnsafeFromVar(Var var)
- {
- Assert(ConcatStringMulti::Is(var));
- return static_cast<ConcatStringMulti *>(var);
- }
- const char16 *
- ConcatStringMulti::GetSz()
- {
- Assert(IsFilled());
- const char16 * sz = GetSzImpl<ConcatStringMulti>();
- // Allow slots to be garbage collected if no more refs.
- ClearArray(m_slots, slotCount);
- LiteralStringWithPropertyStringPtr::ConvertString(this);
- return sz;
- }
- void
- ConcatStringMulti::SetItem(_In_range_(0, slotCount - 1) uint index, JavascriptString* value)
- {
- Assert(index < slotCount);
- Assert(m_slots[index] == nullptr);
- value = CompoundString::GetImmutableOrScriptUnreferencedString(value);
- this->SetLength(this->GetLength() + value->GetLength());
- m_slots[index] = value;
- }
- #if DBG
- bool
- ConcatStringMulti::IsFilled() const
- {
- for (uint i = slotCount; i > 0; i--)
- {
- if (m_slots[i - 1] == nullptr) { return false; }
- }
- return true;
- }
- #endif
- } // namespace Js.
|