ConcatString.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. #include "RuntimeLibraryPch.h"
  6. #include "Codex/Utf8Helper.h"
  7. namespace Js
  8. {
  9. DEFINE_RECYCLER_TRACKER_PERF_COUNTER(ConcatString);
  10. // Note: see also: ConcatString.inl
  11. LiteralStringWithPropertyStringPtr::LiteralStringWithPropertyStringPtr(StaticType* stringType) :
  12. LiteralString(stringType),
  13. propertyString(nullptr),
  14. propertyRecord(nullptr)
  15. {
  16. }
  17. LiteralStringWithPropertyStringPtr::LiteralStringWithPropertyStringPtr(const char16 * wString,
  18. const CharCount stringLength, JavascriptLibrary *const library) :
  19. LiteralString(library->GetStringTypeStatic(), wString, stringLength),
  20. propertyString(nullptr),
  21. propertyRecord(nullptr)
  22. {
  23. }
  24. JavascriptString * LiteralStringWithPropertyStringPtr::
  25. NewFromWideString(const char16 * wideString, const CharCount charCount, JavascriptLibrary *const library)
  26. {
  27. Assert(library != nullptr && wideString != nullptr);
  28. switch (charCount)
  29. {
  30. case 0:
  31. {
  32. JavascriptString * emptyString = library->GetEmptyString();
  33. AssertMsg(VirtualTableInfo<Js::LiteralStringWithPropertyStringPtr>::HasVirtualTable(emptyString),
  34. "Library::GetEmptyString is no longer LiteralStringWithPropertyStringPtr ?");
  35. return emptyString;
  36. }
  37. case 1:
  38. {
  39. return library->GetCharStringCache().GetStringForChar((char16(*wideString)));
  40. }
  41. default:
  42. break;
  43. }
  44. Recycler * recycler = library->GetRecycler();
  45. ScriptContext * scriptContext = library->GetScriptContext();
  46. char16* destString = RecyclerNewArrayLeaf(recycler, WCHAR, charCount + 1);
  47. if (destString == nullptr)
  48. {
  49. Js::JavascriptError::ThrowOutOfMemoryError(scriptContext);
  50. }
  51. js_wmemcpy_s(destString, charCount, wideString, charCount);
  52. destString[charCount] = char16(0);
  53. return (JavascriptString*) RecyclerNew(library->GetRecycler(), LiteralStringWithPropertyStringPtr, destString, charCount, library);
  54. }
  55. JavascriptString * LiteralStringWithPropertyStringPtr::CreateEmptyString(JavascriptLibrary *const library)
  56. {
  57. return (JavascriptString*) RecyclerNew(library->GetRecycler(), LiteralStringWithPropertyStringPtr, _u(""), 0, library);
  58. }
  59. JavascriptString * LiteralStringWithPropertyStringPtr::
  60. NewFromCString(const char * cString, const CharCount charCount, JavascriptLibrary *const library)
  61. {
  62. Assert(library != nullptr && cString != nullptr);
  63. switch (charCount)
  64. {
  65. case 0:
  66. {
  67. JavascriptString * emptyString = library->GetEmptyString();
  68. AssertMsg(VirtualTableInfo<Js::LiteralStringWithPropertyStringPtr>::HasVirtualTable(emptyString),
  69. "Library::GetEmptyString is no longer LiteralStringWithPropertyStringPtr ?");
  70. return (LiteralStringWithPropertyStringPtr*) emptyString;
  71. }
  72. case 1:
  73. {
  74. // If the high bit of the byte is set, it cannot be a complete utf8 codepoint, so fall back to the unicode replacement char
  75. if ((*cString & 0x80) != 0x80)
  76. {
  77. return library->GetCharStringCache().GetStringForChar((char16(*cString)));
  78. }
  79. else
  80. {
  81. return library->GetCharStringCache().GetStringForChar(0xFFFD);
  82. }
  83. }
  84. default:
  85. break;
  86. }
  87. ScriptContext * scriptContext = library->GetScriptContext();
  88. if (charCount > MaxCharCount)
  89. {
  90. Js::JavascriptError::ThrowOutOfMemoryError(scriptContext);
  91. }
  92. Recycler * recycler = library->GetRecycler();
  93. char16* destString = RecyclerNewArrayLeaf(recycler, WCHAR, charCount + 1);
  94. if (destString == nullptr)
  95. {
  96. Js::JavascriptError::ThrowOutOfMemoryError(scriptContext);
  97. }
  98. charcount_t cchDestString = 0;
  99. HRESULT result = utf8::NarrowStringToWideNoAlloc(cString, charCount, destString, charCount + 1, &cchDestString);
  100. if (result == S_OK)
  101. {
  102. return (JavascriptString*) RecyclerNew(library->GetRecycler(), LiteralStringWithPropertyStringPtr, destString, cchDestString, library);
  103. }
  104. Js::JavascriptError::ThrowOutOfMemoryError(scriptContext);
  105. }
  106. PropertyString * LiteralStringWithPropertyStringPtr::GetPropertyString() const
  107. {
  108. return this->propertyString;
  109. }
  110. PropertyString * LiteralStringWithPropertyStringPtr::GetOrAddPropertyString()
  111. {
  112. if (this->propertyString != nullptr)
  113. {
  114. return this->propertyString;
  115. }
  116. Js::PropertyRecord const * propertyRecord = nullptr;
  117. GetPropertyRecordImpl(&propertyRecord);
  118. this->propertyString = this->GetScriptContext()->GetPropertyString(propertyRecord->GetPropertyId());
  119. return this->propertyString;
  120. }
  121. void LiteralStringWithPropertyStringPtr::SetPropertyString(PropertyString * propStr)
  122. {
  123. this->propertyString = propStr;
  124. if (propStr != nullptr)
  125. {
  126. Js::PropertyRecord const * localPropertyRecord;
  127. propStr->GetPropertyRecord(&localPropertyRecord);
  128. this->propertyRecord = localPropertyRecord;
  129. }
  130. }
  131. template <> bool VarIsImpl<LiteralStringWithPropertyStringPtr>(RecyclableObject * obj)
  132. {
  133. return VirtualTableInfo<Js::LiteralStringWithPropertyStringPtr>::HasVirtualTable(obj);
  134. }
  135. void LiteralStringWithPropertyStringPtr::GetPropertyRecord(_Out_ PropertyRecord const** propRecord, bool dontLookupFromDictionary)
  136. {
  137. return GetPropertyRecordImpl(propRecord, dontLookupFromDictionary);
  138. }
  139. void LiteralStringWithPropertyStringPtr::GetPropertyRecordImpl(_Out_ PropertyRecord const** propRecord, bool dontLookupFromDictionary)
  140. {
  141. if (this->propertyRecord)
  142. {
  143. *propRecord = this->propertyRecord;
  144. return;
  145. }
  146. __super::GetPropertyRecord(propRecord, dontLookupFromDictionary);
  147. if (*propRecord)
  148. {
  149. CachePropertyRecordImpl(*propRecord);
  150. }
  151. }
  152. void LiteralStringWithPropertyStringPtr::CachePropertyRecord(_In_ PropertyRecord const* propertyRecord)
  153. {
  154. return CachePropertyRecordImpl(propertyRecord);
  155. }
  156. void LiteralStringWithPropertyStringPtr::CachePropertyRecordImpl(_In_ PropertyRecord const* propertyRecord)
  157. {
  158. this->propertyRecord = propertyRecord;
  159. Assert(this->GetLength() == propertyRecord->GetLength());
  160. // PropertyRecord has its own copy of the string content, so we can drop the reference to our old copy.
  161. // This is okay because the PropertyRecord pointer will keep the data alive.
  162. this->SetBuffer(propertyRecord->GetBuffer());
  163. }
  164. void const * LiteralStringWithPropertyStringPtr::GetOriginalStringReference()
  165. {
  166. // If we have a property record, it's the string owner. Otherwise,
  167. // the string is guaranteed to itself be an allocation block (as
  168. // was asserted during the constructor).
  169. return this->propertyRecord != nullptr ? static_cast<const void*>(this->propertyRecord) : this->GetString();
  170. }
  171. RecyclableObject* LiteralStringWithPropertyStringPtr::CloneToScriptContext(ScriptContext* requestContext)
  172. {
  173. if (this->propertyRecord == nullptr)
  174. {
  175. // Without a property record, we can safely multi-reference the underlying buffer. Assertions in
  176. // the constructor of LiteralString will verify this.
  177. return __super::CloneToScriptContext(requestContext);
  178. }
  179. // We have a property record, so go ahead and make this be a property string in the request context.
  180. // The strings in both contexts will refer to the same property record, since property records are
  181. // shared among all script contexts on a thead.
  182. return requestContext->GetPropertyString(this->propertyRecord);
  183. }
  184. /////////////////////// ConcatStringBase //////////////////////////
  185. ConcatStringBase::ConcatStringBase(StaticType* stringType) : LiteralString(stringType)
  186. {
  187. }
  188. // Copy the content of items into specified buffer.
  189. void ConcatStringBase::CopyImpl(_Out_writes_(m_charLength) char16 *const buffer,
  190. int itemCount, _In_reads_(itemCount) JavascriptString * const * items,
  191. StringCopyInfoStack &nestedStringTreeCopyInfos, const byte recursionDepth)
  192. {
  193. Assert(!IsFinalized());
  194. Assert(buffer);
  195. CharCount copiedCharLength = 0;
  196. for(int i = 0; i < itemCount; ++i)
  197. {
  198. JavascriptString *const s = items[i];
  199. if(!s)
  200. {
  201. continue;
  202. }
  203. if (s->IsFinalized())
  204. {
  205. // If we have the buffer already, just copy it
  206. const CharCount copyCharLength = s->GetLength();
  207. AnalysisAssert(copiedCharLength + copyCharLength <= this->GetLength());
  208. CopyHelper(&buffer[copiedCharLength], s->GetString(), copyCharLength);
  209. copiedCharLength += copyCharLength;
  210. continue;
  211. }
  212. if(i == itemCount - 1)
  213. {
  214. JavascriptString * const * newItems;
  215. int newItemCount = s->GetRandomAccessItemsFromConcatString(newItems);
  216. if (newItemCount != -1)
  217. {
  218. // Optimize for right-weighted ConcatString tree (the append case). Even though appending to a ConcatString will
  219. // transition into a CompoundString fairly quickly, strings created by doing just a few appends are very common.
  220. items = newItems;
  221. itemCount = newItemCount;
  222. i = -1;
  223. continue;
  224. }
  225. }
  226. const CharCount copyCharLength = s->GetLength();
  227. AnalysisAssert(copyCharLength <= GetLength() - copiedCharLength);
  228. if(recursionDepth == MaxCopyRecursionDepth && s->IsTree())
  229. {
  230. // Don't copy nested string trees yet, as that involves a recursive call, and the recursion can become
  231. // excessive. Just collect the nested string trees and the buffer location where they should be copied, and
  232. // the caller can deal with those after returning.
  233. nestedStringTreeCopyInfos.Push(StringCopyInfo(s, &buffer[copiedCharLength]));
  234. }
  235. else
  236. {
  237. Assert(recursionDepth <= MaxCopyRecursionDepth);
  238. s->Copy(&buffer[copiedCharLength], nestedStringTreeCopyInfos, recursionDepth + 1);
  239. }
  240. copiedCharLength += copyCharLength;
  241. }
  242. Assert(copiedCharLength == GetLength());
  243. }
  244. bool ConcatStringBase::IsTree() const
  245. {
  246. Assert(!IsFinalized());
  247. return true;
  248. }
  249. /////////////////////// ConcatString //////////////////////////
  250. ConcatString::ConcatString(JavascriptString* a, JavascriptString* b) :
  251. ConcatStringN<2>(a->GetLibrary()->GetStringTypeStatic(), false)
  252. {
  253. Assert(a);
  254. Assert(b);
  255. a = CompoundString::GetImmutableOrScriptUnreferencedString(a);
  256. b = CompoundString::GetImmutableOrScriptUnreferencedString(b);
  257. m_slots[0] = a;
  258. m_slots[1] = b;
  259. this->SetLength(a->GetLength() + b->GetLength()); // does not include null character
  260. }
  261. ConcatString* ConcatString::New(JavascriptString* left, JavascriptString* right)
  262. {
  263. Assert(left);
  264. #ifdef PROFILE_STRINGS
  265. StringProfiler::RecordConcatenation( left->GetScriptContext(), left->GetLength(), right->GetLength(), ConcatType_ConcatTree);
  266. #endif
  267. Recycler* recycler = left->GetScriptContext()->GetRecycler();
  268. return RecyclerNew(recycler, ConcatString, left, right);
  269. }
  270. /////////////////////// ConcatStringBuilder //////////////////////////
  271. // MAX number of slots in one chunk. Until we fit into this, we realloc, otherwise create new chunk.
  272. // The VS2013 linker treats this as a redefinition of an already
  273. // defined constant and complains. So skip the declaration if we're compiling
  274. // with VS2013 or below.
  275. #if !defined(_MSC_VER) || _MSC_VER >= 1900
  276. const int ConcatStringBuilder::c_maxChunkSlotCount;
  277. #endif
  278. ConcatStringBuilder::ConcatStringBuilder(ScriptContext* scriptContext, int initialSlotCount) :
  279. ConcatStringBase(scriptContext->GetLibrary()->GetStringTypeStatic()),
  280. m_count(0), m_prevChunk(nullptr)
  281. {
  282. Assert(scriptContext);
  283. // Note: m_slotCount is a valid scenario -- when you don't know how many will be there.
  284. this->AllocateSlots(initialSlotCount);
  285. this->SetLength(0); // does not include null character
  286. }
  287. ConcatStringBuilder::ConcatStringBuilder(const ConcatStringBuilder& other):
  288. ConcatStringBase(other.GetScriptContext()->GetLibrary()->GetStringTypeStatic())
  289. {
  290. m_slots = other.m_slots;
  291. m_count = other.m_count;
  292. m_slotCount = other.m_slotCount;
  293. m_prevChunk = other.m_prevChunk;
  294. this->SetLength(other.GetLength());
  295. // TODO: should we copy the JavascriptString buffer and if so, how do we pass over the ownership?
  296. }
  297. ConcatStringBuilder* ConcatStringBuilder::New(ScriptContext* scriptContext, int initialSlotCount)
  298. {
  299. Assert(scriptContext);
  300. return RecyclerNew(scriptContext->GetRecycler(), ConcatStringBuilder, scriptContext, initialSlotCount);
  301. }
  302. const char16 * ConcatStringBuilder::GetSz()
  303. {
  304. const char16 * sz = GetSzImpl<ConcatStringBuilder>();
  305. // Allow a/b to be garbage collected if no more refs.
  306. ConcatStringBuilder* current = this;
  307. while (current != NULL)
  308. {
  309. ClearArray(current->m_slots, current->m_count);
  310. current = current->m_prevChunk;
  311. }
  312. LiteralStringWithPropertyStringPtr::ConvertString(this);
  313. return sz;
  314. }
  315. // Append/concat a new string to us.
  316. // The idea is that we will grow/realloc current slot if new size fits into MAX chunk size (c_maxChunkSlotCount).
  317. // Otherwise we will create a new chunk.
  318. void ConcatStringBuilder::Append(JavascriptString* str)
  319. {
  320. // Note: we are quite lucky here because we always add 1 (no more) string to us.
  321. Assert(str);
  322. charcount_t len = this->GetLength(); // This is len of all chunks.
  323. if (m_count == m_slotCount)
  324. {
  325. // Out of free slots, current chunk is full, need to grow.
  326. int oldItemCount = this->GetItemCount();
  327. int newItemCount = oldItemCount > 0 ?
  328. oldItemCount > 1 ? oldItemCount + oldItemCount / 2 : 2 :
  329. 1;
  330. Assert(newItemCount > oldItemCount);
  331. int growDelta = newItemCount - oldItemCount; // # of items to grow by.
  332. int newSlotCount = m_slotCount + growDelta;
  333. if (newSlotCount <= c_maxChunkSlotCount)
  334. {
  335. // While we fit into MAX chunk size, realloc/grow current chunk.
  336. Field(JavascriptString*)* newSlots = RecyclerNewArray(
  337. this->GetScriptContext()->GetRecycler(), Field(JavascriptString*), newSlotCount);
  338. CopyArray(newSlots, newSlotCount, m_slots, m_slotCount);
  339. m_slots = newSlots;
  340. m_slotCount = newSlotCount;
  341. }
  342. else
  343. {
  344. // Create new chunk with MAX size, swap new instance's data with this's data.
  345. // We never create more than one chunk at a time.
  346. ConcatStringBuilder* newChunk = RecyclerNew(this->GetScriptContext()->GetRecycler(), ConcatStringBuilder, *this); // Create a copy.
  347. m_prevChunk = newChunk;
  348. m_count = 0;
  349. AllocateSlots(this->c_maxChunkSlotCount);
  350. Assert(m_slots);
  351. }
  352. }
  353. str = CompoundString::GetImmutableOrScriptUnreferencedString(str);
  354. m_slots[m_count++] = str;
  355. len += str->GetLength();
  356. this->SetLength(len);
  357. }
  358. // Allocate slots, set m_slots and m_slotCount.
  359. // Note: the amount of slots allocated can be less than the requestedSlotCount parameter.
  360. void ConcatStringBuilder::AllocateSlots(int requestedSlotCount)
  361. {
  362. if (requestedSlotCount > 0)
  363. {
  364. m_slotCount = min(requestedSlotCount, this->c_maxChunkSlotCount);
  365. m_slots = RecyclerNewArray(this->GetScriptContext()->GetRecycler(), Field(JavascriptString*), m_slotCount);
  366. }
  367. else
  368. {
  369. m_slotCount = 0;
  370. m_slots = nullptr;
  371. }
  372. }
  373. // Returns the number of JavascriptString* items accumulated so far in all chunks.
  374. int ConcatStringBuilder::GetItemCount() const
  375. {
  376. int count = 0;
  377. const ConcatStringBuilder* current = this;
  378. while (current != NULL)
  379. {
  380. count += current->m_count;
  381. current = current->m_prevChunk;
  382. }
  383. return count;
  384. }
  385. ConcatStringBuilder* ConcatStringBuilder::GetHead() const
  386. {
  387. ConcatStringBuilder* current = const_cast<ConcatStringBuilder*>(this);
  388. ConcatStringBuilder* head;
  389. do
  390. {
  391. head = current;
  392. current = current->m_prevChunk;
  393. } while (current != NULL);
  394. return head;
  395. }
  396. void ConcatStringBuilder::CopyVirtual(
  397. _Out_writes_(m_charLength) char16 *const buffer,
  398. StringCopyInfoStack &nestedStringTreeCopyInfos,
  399. const byte recursionDepth)
  400. {
  401. Assert(!this->IsFinalized());
  402. Assert(buffer);
  403. CharCount remainingCharLengthToCopy = GetLength();
  404. for(const ConcatStringBuilder *current = this; current; current = current->m_prevChunk)
  405. {
  406. for(int i = current->m_count - 1; i >= 0; --i)
  407. {
  408. JavascriptString *const s = current->m_slots[i];
  409. if(!s)
  410. {
  411. continue;
  412. }
  413. const CharCount copyCharLength = s->GetLength();
  414. Assert(remainingCharLengthToCopy >= copyCharLength);
  415. remainingCharLengthToCopy -= copyCharLength;
  416. if(recursionDepth == MaxCopyRecursionDepth && s->IsTree())
  417. {
  418. // Don't copy nested string trees yet, as that involves a recursive call, and the recursion can become
  419. // excessive. Just collect the nested string trees and the buffer location where they should be copied, and
  420. // the caller can deal with those after returning.
  421. nestedStringTreeCopyInfos.Push(StringCopyInfo(s, &buffer[remainingCharLengthToCopy]));
  422. }
  423. else
  424. {
  425. Assert(recursionDepth <= MaxCopyRecursionDepth);
  426. s->Copy(&buffer[remainingCharLengthToCopy], nestedStringTreeCopyInfos, recursionDepth + 1);
  427. }
  428. }
  429. }
  430. }
  431. /////////////////////// ConcatStringMulti //////////////////////////
  432. ConcatStringMulti::ConcatStringMulti(uint slotCount, JavascriptString * a1, JavascriptString * a2, StaticType* stringTypeStatic) :
  433. ConcatStringBase(stringTypeStatic), slotCount(slotCount)
  434. {
  435. #if DBG
  436. ClearArray(m_slots, slotCount);
  437. #endif
  438. m_slots[0] = CompoundString::GetImmutableOrScriptUnreferencedString(a1);
  439. m_slots[1] = CompoundString::GetImmutableOrScriptUnreferencedString(a2);
  440. this->SetLength(a1->GetLength() + a2->GetLength());
  441. }
  442. size_t
  443. ConcatStringMulti::GetAllocSize(uint slotCount)
  444. {
  445. return sizeof(ConcatStringMulti) + (sizeof(JavascriptString *) * slotCount);
  446. }
  447. ConcatStringMulti*
  448. ConcatStringMulti::New(uint slotCount, JavascriptString * a1, JavascriptString * a2, ScriptContext * scriptContext)
  449. {
  450. return RecyclerNewPlus(scriptContext->GetRecycler(),
  451. sizeof(JavascriptString *) * slotCount, ConcatStringMulti, slotCount, a1, a2,
  452. scriptContext->GetLibrary()->GetStringTypeStatic());
  453. }
  454. template <> bool VarIsImpl<ConcatStringMulti>(RecyclableObject* obj)
  455. {
  456. return VirtualTableInfo<ConcatStringMulti>::HasVirtualTable(obj);
  457. }
  458. const char16 *
  459. ConcatStringMulti::GetSz()
  460. {
  461. Assert(IsFilled());
  462. const char16 * sz = GetSzImpl<ConcatStringMulti>();
  463. // Allow slots to be garbage collected if no more refs.
  464. ClearArray(m_slots, slotCount);
  465. LiteralStringWithPropertyStringPtr::ConvertString(this);
  466. return sz;
  467. }
  468. void
  469. ConcatStringMulti::SetItem(_In_range_(0, slotCount - 1) uint index, JavascriptString* value)
  470. {
  471. Assert(index < slotCount);
  472. Assert(m_slots[index] == nullptr);
  473. value = CompoundString::GetImmutableOrScriptUnreferencedString(value);
  474. this->SetLength(this->GetLength() + value->GetLength());
  475. m_slots[index] = value;
  476. }
  477. #if DBG
  478. bool
  479. ConcatStringMulti::IsFilled() const
  480. {
  481. for (uint i = slotCount; i > 0; i--)
  482. {
  483. if (m_slots[i - 1] == nullptr) { return false; }
  484. }
  485. return true;
  486. }
  487. #endif
  488. } // namespace Js.