ConcatString.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  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. ScriptContext * scriptContext = this->GetScriptContext();
  117. if (this->propertyRecord == nullptr)
  118. {
  119. Js::PropertyRecord const * propertyRecord = nullptr;
  120. scriptContext->GetOrAddPropertyRecord(this->GetSz(), static_cast<int>(this->GetLength()),
  121. &propertyRecord);
  122. this->propertyRecord = propertyRecord;
  123. }
  124. this->propertyString = scriptContext->GetPropertyString(propertyRecord->GetPropertyId());
  125. return this->propertyString;
  126. }
  127. void LiteralStringWithPropertyStringPtr::SetPropertyString(PropertyString * propStr)
  128. {
  129. this->propertyString = propStr;
  130. if (propStr != nullptr)
  131. {
  132. Js::PropertyRecord const * localPropertyRecord;
  133. propStr->GetPropertyRecord(&localPropertyRecord);
  134. this->propertyRecord = localPropertyRecord;
  135. }
  136. }
  137. /* static */
  138. bool LiteralStringWithPropertyStringPtr::Is(RecyclableObject * obj)
  139. {
  140. return VirtualTableInfo<Js::LiteralStringWithPropertyStringPtr>::HasVirtualTable(obj);
  141. }
  142. /* static */
  143. bool LiteralStringWithPropertyStringPtr::Is(Var var)
  144. {
  145. return RecyclableObject::Is(var) && LiteralStringWithPropertyStringPtr::Is(RecyclableObject::UnsafeFromVar(var));
  146. }
  147. void LiteralStringWithPropertyStringPtr::GetPropertyRecord(_Out_ PropertyRecord const** propRecord, bool dontLookupFromDictionary)
  148. {
  149. *propRecord = nullptr;
  150. ScriptContext * scriptContext = this->GetScriptContext();
  151. if (this->propertyRecord == nullptr)
  152. {
  153. if (!dontLookupFromDictionary)
  154. {
  155. // cache PropertyRecord
  156. Js::PropertyRecord const * localPropertyRecord;
  157. scriptContext->GetOrAddPropertyRecord(this->GetSz(),
  158. static_cast<int>(this->GetLength()),
  159. &localPropertyRecord);
  160. this->propertyRecord = localPropertyRecord;
  161. }
  162. else
  163. {
  164. return;
  165. }
  166. }
  167. *propRecord = this->propertyRecord;
  168. }
  169. /////////////////////// ConcatStringBase //////////////////////////
  170. ConcatStringBase::ConcatStringBase(StaticType* stringType) : LiteralString(stringType)
  171. {
  172. }
  173. // Copy the content of items into specified buffer.
  174. void ConcatStringBase::CopyImpl(_Out_writes_(m_charLength) char16 *const buffer,
  175. int itemCount, _In_reads_(itemCount) JavascriptString * const * items,
  176. StringCopyInfoStack &nestedStringTreeCopyInfos, const byte recursionDepth)
  177. {
  178. Assert(!IsFinalized());
  179. Assert(buffer);
  180. CharCount copiedCharLength = 0;
  181. for(int i = 0; i < itemCount; ++i)
  182. {
  183. JavascriptString *const s = items[i];
  184. if(!s)
  185. {
  186. continue;
  187. }
  188. if (s->IsFinalized())
  189. {
  190. // If we have the buffer already, just copy it
  191. const CharCount copyCharLength = s->GetLength();
  192. AnalysisAssert(copiedCharLength + copyCharLength <= this->GetLength());
  193. CopyHelper(&buffer[copiedCharLength], s->GetString(), copyCharLength);
  194. copiedCharLength += copyCharLength;
  195. continue;
  196. }
  197. if(i == itemCount - 1)
  198. {
  199. JavascriptString * const * newItems;
  200. int newItemCount = s->GetRandomAccessItemsFromConcatString(newItems);
  201. if (newItemCount != -1)
  202. {
  203. // Optimize for right-weighted ConcatString tree (the append case). Even though appending to a ConcatString will
  204. // transition into a CompoundString fairly quickly, strings created by doing just a few appends are very common.
  205. items = newItems;
  206. itemCount = newItemCount;
  207. i = -1;
  208. continue;
  209. }
  210. }
  211. const CharCount copyCharLength = s->GetLength();
  212. AnalysisAssert(copyCharLength <= GetLength() - copiedCharLength);
  213. if(recursionDepth == MaxCopyRecursionDepth && s->IsTree())
  214. {
  215. // Don't copy nested string trees yet, as that involves a recursive call, and the recursion can become
  216. // excessive. Just collect the nested string trees and the buffer location where they should be copied, and
  217. // the caller can deal with those after returning.
  218. nestedStringTreeCopyInfos.Push(StringCopyInfo(s, &buffer[copiedCharLength]));
  219. }
  220. else
  221. {
  222. Assert(recursionDepth <= MaxCopyRecursionDepth);
  223. s->Copy(&buffer[copiedCharLength], nestedStringTreeCopyInfos, recursionDepth + 1);
  224. }
  225. copiedCharLength += copyCharLength;
  226. }
  227. Assert(copiedCharLength == GetLength());
  228. }
  229. bool ConcatStringBase::IsTree() const
  230. {
  231. Assert(!IsFinalized());
  232. return true;
  233. }
  234. /////////////////////// ConcatString //////////////////////////
  235. ConcatString::ConcatString(JavascriptString* a, JavascriptString* b) :
  236. ConcatStringN<2>(a->GetLibrary()->GetStringTypeStatic(), false)
  237. {
  238. Assert(a);
  239. Assert(b);
  240. a = CompoundString::GetImmutableOrScriptUnreferencedString(a);
  241. b = CompoundString::GetImmutableOrScriptUnreferencedString(b);
  242. m_slots[0] = a;
  243. m_slots[1] = b;
  244. this->SetLength(a->GetLength() + b->GetLength()); // does not include null character
  245. }
  246. ConcatString* ConcatString::New(JavascriptString* left, JavascriptString* right)
  247. {
  248. Assert(left);
  249. #ifdef PROFILE_STRINGS
  250. StringProfiler::RecordConcatenation( left->GetScriptContext(), left->GetLength(), right->GetLength(), ConcatType_ConcatTree);
  251. #endif
  252. Recycler* recycler = left->GetScriptContext()->GetRecycler();
  253. return RecyclerNew(recycler, ConcatString, left, right);
  254. }
  255. /////////////////////// ConcatStringBuilder //////////////////////////
  256. // MAX number of slots in one chunk. Until we fit into this, we realloc, otherwise create new chunk.
  257. // The VS2013 linker treats this as a redefinition of an already
  258. // defined constant and complains. So skip the declaration if we're compiling
  259. // with VS2013 or below.
  260. #if !defined(_MSC_VER) || _MSC_VER >= 1900
  261. const int ConcatStringBuilder::c_maxChunkSlotCount;
  262. #endif
  263. ConcatStringBuilder::ConcatStringBuilder(ScriptContext* scriptContext, int initialSlotCount) :
  264. ConcatStringBase(scriptContext->GetLibrary()->GetStringTypeStatic()),
  265. m_count(0), m_prevChunk(nullptr)
  266. {
  267. Assert(scriptContext);
  268. // Note: m_slotCount is a valid scenario -- when you don't know how many will be there.
  269. this->AllocateSlots(initialSlotCount);
  270. this->SetLength(0); // does not include null character
  271. }
  272. ConcatStringBuilder::ConcatStringBuilder(const ConcatStringBuilder& other):
  273. ConcatStringBase(other.GetScriptContext()->GetLibrary()->GetStringTypeStatic())
  274. {
  275. m_slots = other.m_slots;
  276. m_count = other.m_count;
  277. m_slotCount = other.m_slotCount;
  278. m_prevChunk = other.m_prevChunk;
  279. this->SetLength(other.GetLength());
  280. // TODO: should we copy the JavascriptString buffer and if so, how do we pass over the ownership?
  281. }
  282. ConcatStringBuilder* ConcatStringBuilder::New(ScriptContext* scriptContext, int initialSlotCount)
  283. {
  284. Assert(scriptContext);
  285. return RecyclerNew(scriptContext->GetRecycler(), ConcatStringBuilder, scriptContext, initialSlotCount);
  286. }
  287. const char16 * ConcatStringBuilder::GetSz()
  288. {
  289. const char16 * sz = GetSzImpl<ConcatStringBuilder>();
  290. // Allow a/b to be garbage collected if no more refs.
  291. ConcatStringBuilder* current = this;
  292. while (current != NULL)
  293. {
  294. ClearArray(current->m_slots, current->m_count);
  295. current = current->m_prevChunk;
  296. }
  297. LiteralStringWithPropertyStringPtr::ConvertString(this);
  298. return sz;
  299. }
  300. // Append/concat a new string to us.
  301. // The idea is that we will grow/realloc current slot if new size fits into MAX chunk size (c_maxChunkSlotCount).
  302. // Otherwise we will create a new chunk.
  303. void ConcatStringBuilder::Append(JavascriptString* str)
  304. {
  305. // Note: we are quite lucky here because we always add 1 (no more) string to us.
  306. Assert(str);
  307. charcount_t len = this->GetLength(); // This is len of all chunks.
  308. if (m_count == m_slotCount)
  309. {
  310. // Out of free slots, current chunk is full, need to grow.
  311. int oldItemCount = this->GetItemCount();
  312. int newItemCount = oldItemCount > 0 ?
  313. oldItemCount > 1 ? oldItemCount + oldItemCount / 2 : 2 :
  314. 1;
  315. Assert(newItemCount > oldItemCount);
  316. int growDelta = newItemCount - oldItemCount; // # of items to grow by.
  317. int newSlotCount = m_slotCount + growDelta;
  318. if (newSlotCount <= c_maxChunkSlotCount)
  319. {
  320. // While we fit into MAX chunk size, realloc/grow current chunk.
  321. Field(JavascriptString*)* newSlots = RecyclerNewArray(
  322. this->GetScriptContext()->GetRecycler(), Field(JavascriptString*), newSlotCount);
  323. CopyArray(newSlots, newSlotCount, m_slots, m_slotCount);
  324. m_slots = newSlots;
  325. m_slotCount = newSlotCount;
  326. }
  327. else
  328. {
  329. // Create new chunk with MAX size, swap new instance's data with this's data.
  330. // We never create more than one chunk at a time.
  331. ConcatStringBuilder* newChunk = RecyclerNew(this->GetScriptContext()->GetRecycler(), ConcatStringBuilder, *this); // Create a copy.
  332. m_prevChunk = newChunk;
  333. m_count = 0;
  334. AllocateSlots(this->c_maxChunkSlotCount);
  335. Assert(m_slots);
  336. }
  337. }
  338. str = CompoundString::GetImmutableOrScriptUnreferencedString(str);
  339. m_slots[m_count++] = str;
  340. len += str->GetLength();
  341. this->SetLength(len);
  342. }
  343. // Allocate slots, set m_slots and m_slotCount.
  344. // Note: the amount of slots allocated can be less than the requestedSlotCount parameter.
  345. void ConcatStringBuilder::AllocateSlots(int requestedSlotCount)
  346. {
  347. if (requestedSlotCount > 0)
  348. {
  349. m_slotCount = min(requestedSlotCount, this->c_maxChunkSlotCount);
  350. m_slots = RecyclerNewArray(this->GetScriptContext()->GetRecycler(), Field(JavascriptString*), m_slotCount);
  351. }
  352. else
  353. {
  354. m_slotCount = 0;
  355. m_slots = nullptr;
  356. }
  357. }
  358. // Returns the number of JavascriptString* items accumulated so far in all chunks.
  359. int ConcatStringBuilder::GetItemCount() const
  360. {
  361. int count = 0;
  362. const ConcatStringBuilder* current = this;
  363. while (current != NULL)
  364. {
  365. count += current->m_count;
  366. current = current->m_prevChunk;
  367. }
  368. return count;
  369. }
  370. ConcatStringBuilder* ConcatStringBuilder::GetHead() const
  371. {
  372. ConcatStringBuilder* current = const_cast<ConcatStringBuilder*>(this);
  373. ConcatStringBuilder* head;
  374. do
  375. {
  376. head = current;
  377. current = current->m_prevChunk;
  378. } while (current != NULL);
  379. return head;
  380. }
  381. void ConcatStringBuilder::CopyVirtual(
  382. _Out_writes_(m_charLength) char16 *const buffer,
  383. StringCopyInfoStack &nestedStringTreeCopyInfos,
  384. const byte recursionDepth)
  385. {
  386. Assert(!this->IsFinalized());
  387. Assert(buffer);
  388. CharCount remainingCharLengthToCopy = GetLength();
  389. for(const ConcatStringBuilder *current = this; current; current = current->m_prevChunk)
  390. {
  391. for(int i = current->m_count - 1; i >= 0; --i)
  392. {
  393. JavascriptString *const s = current->m_slots[i];
  394. if(!s)
  395. {
  396. continue;
  397. }
  398. const CharCount copyCharLength = s->GetLength();
  399. Assert(remainingCharLengthToCopy >= copyCharLength);
  400. remainingCharLengthToCopy -= copyCharLength;
  401. if(recursionDepth == MaxCopyRecursionDepth && s->IsTree())
  402. {
  403. // Don't copy nested string trees yet, as that involves a recursive call, and the recursion can become
  404. // excessive. Just collect the nested string trees and the buffer location where they should be copied, and
  405. // the caller can deal with those after returning.
  406. nestedStringTreeCopyInfos.Push(StringCopyInfo(s, &buffer[remainingCharLengthToCopy]));
  407. }
  408. else
  409. {
  410. Assert(recursionDepth <= MaxCopyRecursionDepth);
  411. s->Copy(&buffer[remainingCharLengthToCopy], nestedStringTreeCopyInfos, recursionDepth + 1);
  412. }
  413. }
  414. }
  415. }
  416. /////////////////////// ConcatStringMulti //////////////////////////
  417. ConcatStringMulti::ConcatStringMulti(uint slotCount, JavascriptString * a1, JavascriptString * a2, StaticType* stringTypeStatic) :
  418. ConcatStringBase(stringTypeStatic), slotCount(slotCount)
  419. {
  420. #if DBG
  421. ClearArray(m_slots, slotCount);
  422. #endif
  423. m_slots[0] = CompoundString::GetImmutableOrScriptUnreferencedString(a1);
  424. m_slots[1] = CompoundString::GetImmutableOrScriptUnreferencedString(a2);
  425. this->SetLength(a1->GetLength() + a2->GetLength());
  426. }
  427. size_t
  428. ConcatStringMulti::GetAllocSize(uint slotCount)
  429. {
  430. return sizeof(ConcatStringMulti) + (sizeof(JavascriptString *) * slotCount);
  431. }
  432. ConcatStringMulti*
  433. ConcatStringMulti::New(uint slotCount, JavascriptString * a1, JavascriptString * a2, ScriptContext * scriptContext)
  434. {
  435. return RecyclerNewPlus(scriptContext->GetRecycler(),
  436. sizeof(JavascriptString *) * slotCount, ConcatStringMulti, slotCount, a1, a2,
  437. scriptContext->GetLibrary()->GetStringTypeStatic());
  438. }
  439. bool
  440. ConcatStringMulti::Is(Var var)
  441. {
  442. return VirtualTableInfo<ConcatStringMulti>::HasVirtualTable(var);
  443. }
  444. ConcatStringMulti *
  445. ConcatStringMulti::FromVar(Var var)
  446. {
  447. AssertOrFailFast(ConcatStringMulti::Is(var));
  448. return static_cast<ConcatStringMulti *>(var);
  449. }
  450. ConcatStringMulti *
  451. ConcatStringMulti::UnsafeFromVar(Var var)
  452. {
  453. Assert(ConcatStringMulti::Is(var));
  454. return static_cast<ConcatStringMulti *>(var);
  455. }
  456. const char16 *
  457. ConcatStringMulti::GetSz()
  458. {
  459. Assert(IsFilled());
  460. const char16 * sz = GetSzImpl<ConcatStringMulti>();
  461. // Allow slots to be garbage collected if no more refs.
  462. ClearArray(m_slots, slotCount);
  463. LiteralStringWithPropertyStringPtr::ConvertString(this);
  464. return sz;
  465. }
  466. void
  467. ConcatStringMulti::SetItem(_In_range_(0, slotCount - 1) uint index, JavascriptString* value)
  468. {
  469. Assert(index < slotCount);
  470. Assert(m_slots[index] == nullptr);
  471. value = CompoundString::GetImmutableOrScriptUnreferencedString(value);
  472. this->SetLength(this->GetLength() + value->GetLength());
  473. m_slots[index] = value;
  474. }
  475. #if DBG
  476. bool
  477. ConcatStringMulti::IsFilled() const
  478. {
  479. for (uint i = slotCount; i > 0; i--)
  480. {
  481. if (m_slots[i - 1] == nullptr) { return false; }
  482. }
  483. return true;
  484. }
  485. #endif
  486. } // namespace Js.