ConcatString.cpp 20 KB

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