ConcatString.cpp 20 KB

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