JavascriptString.cpp 151 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Copyright (c) 2021 ChakraCore Project Contributors. All rights reserved.
  4. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  5. //-------------------------------------------------------------------------------------------------------
  6. #include "RuntimeLibraryPch.h"
  7. #include "DataStructures/BigUInt.h"
  8. #include "Library/EngineInterfaceObject.h"
  9. #include "Library/IntlEngineInterfaceExtensionObject.h"
  10. #if ENABLE_NATIVE_CODEGEN
  11. #include "../Backend/JITRecyclableObject.h"
  12. #endif
  13. namespace Js
  14. {
  15. // White Space characters are defined in ES 2017 Section 11.2 #sec-white-space
  16. // There are 25 white space characters we need to correctly class.
  17. // - 6 of these are explicitly specified in ES 2017 Section 11.2 #sec-white-space
  18. // - 15 of these are Unicode category "Zs" ("Space_Separator") and not explicitly specified above.
  19. // - Note: In total, 17 of these are Unicode category "Zs".
  20. // - 4 of these are actually LineTerminator characters.
  21. // - Note: for various reasons it is convenient to group LineTerminator with Whitespace
  22. // in the definition of IsWhiteSpaceCharacter.
  23. // This does not cause problems because of the syntactic nature of LineTerminators
  24. // and their meaning of ending a line in RegExp.
  25. // - See: #sec-string.prototype.trim "The definition of white space is the union of WhiteSpace and LineTerminator."
  26. // Note: ES intentionally excludes characters which have Unicode property "White_Space" but which are not "Zs".
  27. // See http://www.unicode.org/Public/9.0.0/ucd/UnicodeData.txt for character classes.
  28. // The 25 white space characters are:
  29. //0x0009 // <TAB>
  30. //0x000a // <LF> LineTerminator (LINE FEED)
  31. //0x000b // <VT>
  32. //0x000c // <FF>
  33. //0x000d // <CR> LineTerminator (CARRIAGE RETURN)
  34. //0x0020 // <SP>
  35. //0x00a0 // <NBSP>
  36. //0x1680
  37. //0x2000
  38. //0x2001
  39. //0x2002
  40. //0x2003
  41. //0x2004
  42. //0x2005
  43. //0x2006
  44. //0x2007
  45. //0x2008
  46. //0x2009
  47. //0x200a
  48. //0x2028 // <LS> LineTerminator (LINE SEPARATOR)
  49. //0x2029 // <PS> LineTerminator (PARAGRAPH SEPARATOR)
  50. //0x202f
  51. //0x205f
  52. //0x3000
  53. //0xfeff // <ZWNBSP>
  54. bool IsWhiteSpaceCharacter(char16 ch)
  55. {
  56. return ch >= 0x9 &&
  57. (ch <= 0xd ||
  58. (ch <= 0x200a &&
  59. (ch >= 0x2000 || ch == 0x20 || ch == 0xa0 || ch == 0x1680)
  60. ) ||
  61. (ch >= 0x2028 &&
  62. (ch <= 0x2029 || ch == 0x202f || ch == 0x205f || ch == 0x3000 || ch == 0xfeff)
  63. )
  64. );
  65. }
  66. template <typename T, bool copyBuffer>
  67. JavascriptString* JavascriptString::NewWithBufferT(const char16 * content, charcount_t cchUseLength, ScriptContext * scriptContext)
  68. {
  69. AssertMsg(content != nullptr, "NULL value passed to JavascriptString::New");
  70. AssertMsg(IsValidCharCount(cchUseLength), "String length will overflow an int");
  71. switch (cchUseLength)
  72. {
  73. case 0:
  74. return scriptContext->GetLibrary()->GetEmptyString();
  75. case 1:
  76. return scriptContext->GetLibrary()->GetCharStringCache().GetStringForChar(*content);
  77. default:
  78. break;
  79. }
  80. Recycler* recycler = scriptContext->GetRecycler();
  81. StaticType * stringTypeStatic = scriptContext->GetLibrary()->GetStringTypeStatic();
  82. char16 const * buffer = content;
  83. charcount_t cchUseBoundLength = static_cast<charcount_t>(cchUseLength);
  84. if (copyBuffer)
  85. {
  86. buffer = JavascriptString::AllocateLeafAndCopySz(recycler, content, cchUseBoundLength);
  87. }
  88. return T::New(stringTypeStatic, buffer, cchUseBoundLength, recycler);
  89. }
  90. JavascriptString* JavascriptString::NewWithSz(__in_z const char16 * content, ScriptContext * scriptContext)
  91. {
  92. AssertMsg(content != nullptr, "NULL value passed to JavascriptString::New");
  93. return NewWithBuffer(content, GetBufferLength(content), scriptContext);
  94. }
  95. JavascriptString* JavascriptString::NewWithBuffer(__in_ecount(cchUseLength) const char16 * content, charcount_t cchUseLength, ScriptContext * scriptContext)
  96. {
  97. return NewWithBufferT<LiteralString, false>(content, cchUseLength, scriptContext);
  98. }
  99. JavascriptString* JavascriptString::NewCopySz(__in_z const char16* content, ScriptContext* scriptContext)
  100. {
  101. return NewCopyBuffer(content, GetBufferLength(content), scriptContext);
  102. }
  103. JavascriptString* JavascriptString::NewCopyBuffer(__in_ecount(cchUseLength) const char16* content, charcount_t cchUseLength, ScriptContext* scriptContext)
  104. {
  105. return NewWithBufferT<LiteralString, true>(content, cchUseLength, scriptContext);
  106. }
  107. Var JavascriptString::NewInstance(RecyclableObject* function, CallInfo callInfo, ...)
  108. {
  109. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  110. ARGUMENTS(args, callInfo);
  111. ScriptContext* scriptContext = function->GetScriptContext();
  112. AssertMsg(args.Info.Count > 0, "Negative argument count");
  113. // SkipDefaultNewObject function flag should have prevented the default object from
  114. // being created, except when call true a host dispatch.
  115. Var newTarget = args.GetNewTarget();
  116. bool isCtorSuperCall = JavascriptOperators::GetAndAssertIsConstructorSuperCall(args);
  117. JavascriptString* str;
  118. Var result;
  119. if (args.Info.Count > 1)
  120. {
  121. JavascriptSymbol * symbol = JavascriptOperators::TryFromVar<JavascriptSymbol>(args[1]);
  122. if (symbol && !(callInfo.Flags & CallFlags_New))
  123. {
  124. // By ES2015 21.1.1.1 step 2, calling the String constructor directly results in an explicit ToString, which does not throw.
  125. return JavascriptSymbol::ToString(symbol->GetValue(), scriptContext);
  126. // Calling with new is an implicit ToString on the Symbol, resulting in a throw. For this case we can let JavascriptConversion handle the call.
  127. }
  128. str = JavascriptConversion::ToString(args[1], scriptContext);
  129. }
  130. else
  131. {
  132. str = scriptContext->GetLibrary()->GetEmptyString();
  133. }
  134. if (callInfo.Flags & CallFlags_New)
  135. {
  136. result = scriptContext->GetLibrary()->CreateStringObject(str);
  137. }
  138. else
  139. {
  140. result = str;
  141. }
  142. return isCtorSuperCall ?
  143. JavascriptOperators::OrdinaryCreateFromConstructor(VarTo<RecyclableObject>(newTarget), UnsafeVarTo<RecyclableObject>(result), nullptr, scriptContext) :
  144. result;
  145. }
  146. // static
  147. bool IsValidCharCount(size_t charCount)
  148. {
  149. return charCount <= JavascriptString::MaxCharLength;
  150. }
  151. JavascriptString::JavascriptString(StaticType * type)
  152. : RecyclableObject(type), m_charLength(0), m_pszValue(nullptr)
  153. {
  154. Assert(type->GetTypeId() == TypeIds_String);
  155. }
  156. JavascriptString::JavascriptString(StaticType * type, charcount_t charLength, const char16* szValue)
  157. : RecyclableObject(type), m_pszValue(szValue)
  158. {
  159. Assert(type->GetTypeId() == TypeIds_String);
  160. SetLength(charLength);
  161. }
  162. _Ret_range_(m_charLength, m_charLength)
  163. charcount_t JavascriptString::GetLength() const
  164. {
  165. return m_charLength;
  166. }
  167. int JavascriptString::GetLengthAsSignedInt() const
  168. {
  169. Assert(IsValidCharCount(m_charLength));
  170. return static_cast<int>(m_charLength);
  171. }
  172. const char16* JavascriptString::UnsafeGetBuffer() const
  173. {
  174. return m_pszValue;
  175. }
  176. void JavascriptString::SetLength(charcount_t newLength)
  177. {
  178. if (!IsValidCharCount(newLength))
  179. {
  180. JavascriptError::ThrowRangeError(this->GetScriptContext(), JSERR_OutOfBoundString);
  181. }
  182. m_charLength = newLength;
  183. }
  184. void JavascriptString::SetBuffer(const char16* buffer)
  185. {
  186. m_pszValue = buffer;
  187. }
  188. bool JavascriptString::IsValidIndexValue(charcount_t idx) const
  189. {
  190. return IsValidCharCount(idx) && idx < GetLength();
  191. }
  192. template <> bool VarIsImpl<JavascriptString>(RecyclableObject* obj)
  193. {
  194. return JavascriptOperators::GetTypeId(obj) == TypeIds_String;
  195. }
  196. void JavascriptString::GetPropertyRecord(_Out_ Js::PropertyRecord const ** propertyRecord, bool dontLookupFromDictionary)
  197. {
  198. *propertyRecord = nullptr;
  199. if (dontLookupFromDictionary)
  200. {
  201. return;
  202. }
  203. GetScriptContext()->GetOrAddPropertyRecord(GetString(), GetLength(), propertyRecord);
  204. }
  205. void JavascriptString::CachePropertyRecord(_In_ PropertyRecord const* propertyRecord)
  206. {
  207. // Base string doesn't have enough room to keep this value, so do nothing
  208. }
  209. charcount_t
  210. JavascriptString::GetBufferLength(const char16 * content)
  211. {
  212. size_t cchActual = wcslen(content);
  213. #if defined(TARGET_64)
  214. if (!IsValidCharCount(cchActual))
  215. {
  216. // Limit javascript string to 31-bit length
  217. Js::Throw::OutOfMemory();
  218. }
  219. #else
  220. // There shouldn't be enough memory to have UINT_MAX character.
  221. // INT_MAX is the upper bound for 32-bit;
  222. Assert(IsValidCharCount(cchActual));
  223. #endif
  224. return static_cast<charcount_t>(cchActual);
  225. }
  226. charcount_t
  227. JavascriptString::GetBufferLength(
  228. const char16 * content, // Value to examine
  229. int charLengthOrMinusOne) // Optional length, in characters
  230. {
  231. //
  232. // Determine the actual length, in characters, not including a terminating '\0':
  233. // - If a length was not specified (charLength < 0), search for a terminating '\0'.
  234. //
  235. charcount_t cchActual;
  236. if (charLengthOrMinusOne < 0)
  237. {
  238. AssertMsg(charLengthOrMinusOne == -1, "The only negative value allowed is -1");
  239. cchActual = GetBufferLength(content);
  240. }
  241. else
  242. {
  243. cchActual = static_cast<charcount_t>(charLengthOrMinusOne);
  244. }
  245. #ifdef CHECK_STRING
  246. // removed this to accommodate much larger string constant in regex-dna.js
  247. if (cchActual > 64 * 1024)
  248. {
  249. //
  250. // String was probably not '\0' terminated:
  251. // - We need to validate that the string's contents always fit within 1 GB to avoid
  252. // overflow checking on 32-bit when using 'int' for 'byte *' pointer operations.
  253. //
  254. Throw::OutOfMemory(); // TODO: determine argument error
  255. }
  256. #endif
  257. return cchActual;
  258. }
  259. template< size_t N >
  260. Var JavascriptString::StringBracketHelper(Arguments args, ScriptContext *scriptContext, const char16(&tag)[N])
  261. {
  262. CompileAssert(0 < N && N <= JavascriptString::MaxCharLength);
  263. return StringBracketHelper(args, scriptContext, tag, static_cast<charcount_t>(N - 1), nullptr, 0);
  264. }
  265. template< size_t N1, size_t N2 >
  266. Var JavascriptString::StringBracketHelper(Arguments args, ScriptContext *scriptContext, const char16(&tag)[N1], const char16(&prop)[N2])
  267. {
  268. CompileAssert(0 < N1 && N1 <= JavascriptString::MaxCharLength);
  269. CompileAssert(0 < N2 && N2 <= JavascriptString::MaxCharLength);
  270. return StringBracketHelper(args, scriptContext, tag, static_cast<charcount_t>(N1 - 1), prop, static_cast<charcount_t>(N2 - 1));
  271. }
  272. BOOL JavascriptString::BufferEquals(__in_ecount(otherLength) LPCWSTR otherBuffer, __in charcount_t otherLength)
  273. {
  274. return otherLength == this->GetLength() &&
  275. JsUtil::CharacterBuffer<WCHAR>::StaticEquals(this->GetString(), otherBuffer, otherLength);
  276. }
  277. BOOL JavascriptString::HasItemAt(charcount_t index)
  278. {
  279. return IsValidIndexValue(index);
  280. }
  281. BOOL JavascriptString::GetItemAt(charcount_t index, Var* value)
  282. {
  283. if (!IsValidIndexValue(index))
  284. {
  285. return false;
  286. }
  287. char16 character = GetItem(index);
  288. *value = this->GetLibrary()->GetCharStringCache().GetStringForChar(character);
  289. return true;
  290. }
  291. char16 JavascriptString::GetItem(charcount_t index)
  292. {
  293. AssertMsg( IsValidIndexValue(index), "Must specify valid character");
  294. const char16 *str = this->GetString();
  295. return str[index];
  296. }
  297. void JavascriptString::CopyHelper(__out_ecount(countNeeded) char16 *dst, __in_ecount(countNeeded) const char16 * str, charcount_t countNeeded)
  298. {
  299. switch(countNeeded)
  300. {
  301. case 0:
  302. return;
  303. case 1:
  304. dst[0] = str[0];
  305. break;
  306. case 3:
  307. dst[2] = str[2];
  308. goto case_2;
  309. case 5:
  310. dst[4] = str[4];
  311. goto case_4;
  312. case 7:
  313. dst[6] = str[6];
  314. goto case_6;
  315. case 9:
  316. dst[8] = str[8];
  317. goto case_8;
  318. case 10:
  319. *(uint32 *)(dst+8) = *(uint32*)(str+8);
  320. // FALLTHROUGH
  321. case 8:
  322. case_8:
  323. *(uint32 *)(dst+6) = *(uint32*)(str+6);
  324. // FALLTHROUGH
  325. case 6:
  326. case_6:
  327. *(uint32 *)(dst+4) = *(uint32*)(str+4);
  328. // FALLTHROUGH
  329. case 4:
  330. case_4:
  331. *(uint32 *)(dst+2) = *(uint32*)(str+2);
  332. // FALLTHROUGH
  333. case 2:
  334. case_2:
  335. *(uint32 *)(dst) = *(uint32*)str;
  336. break;
  337. default:
  338. js_wmemcpy_s(dst, countNeeded, str, countNeeded);
  339. }
  340. }
  341. JavascriptString* JavascriptString::ConcatDestructive(JavascriptString* pstRight)
  342. {
  343. Assert(pstRight);
  344. if(!IsFinalized())
  345. {
  346. if(VarIs<CompoundString>(this))
  347. {
  348. return ConcatDestructive_Compound(pstRight);
  349. }
  350. if(VirtualTableInfo<ConcatString>::HasVirtualTable(this))
  351. {
  352. JavascriptString *const s = ConcatDestructive_ConcatToCompound(pstRight);
  353. if(s)
  354. {
  355. return s;
  356. }
  357. }
  358. }
  359. else
  360. {
  361. const CharCount leftLength = GetLength();
  362. const CharCount rightLength = pstRight->GetLength();
  363. if(leftLength == 0 || rightLength == 0)
  364. {
  365. return ConcatDestructive_OneEmpty(pstRight);
  366. }
  367. if(CompoundString::ShouldAppendChars(leftLength) && CompoundString::ShouldAppendChars(rightLength))
  368. {
  369. return ConcatDestructive_CompoundAppendChars(pstRight);
  370. }
  371. }
  372. #ifdef PROFILE_STRINGS
  373. StringProfiler::RecordConcatenation(GetScriptContext(), GetLength(), pstRight->GetLength(), ConcatType_ConcatTree);
  374. #endif
  375. if(PHASE_TRACE_StringConcat)
  376. {
  377. Output::Print(
  378. _u("JavascriptString::ConcatDestructive(\"%.8s%s\") - creating ConcatString\n"),
  379. pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
  380. !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
  381. Output::Flush();
  382. }
  383. return ConcatString::New(this, pstRight);
  384. }
  385. JavascriptString* JavascriptString::ConcatDestructive_Compound(JavascriptString* pstRight)
  386. {
  387. Assert(VarIs<CompoundString>(this));
  388. Assert(pstRight);
  389. #ifdef PROFILE_STRINGS
  390. StringProfiler::RecordConcatenation(GetScriptContext(), GetLength(), pstRight->GetLength(), ConcatType_CompoundString);
  391. #endif
  392. if(PHASE_TRACE_StringConcat)
  393. {
  394. Output::Print(
  395. _u("JavascriptString::ConcatDestructive(\"%.8s%s\") - appending to CompoundString\n"),
  396. pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
  397. !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
  398. Output::Flush();
  399. }
  400. CompoundString *const leftCs = VarTo<CompoundString>(this);
  401. leftCs->PrepareForAppend();
  402. leftCs->Append(pstRight);
  403. return this;
  404. }
  405. JavascriptString* JavascriptString::ConcatDestructive_ConcatToCompound(JavascriptString* pstRight)
  406. {
  407. Assert(VirtualTableInfo<ConcatString>::HasVirtualTable(this));
  408. Assert(pstRight);
  409. const ConcatString *const leftConcatString = static_cast<const ConcatString *>(this);
  410. JavascriptString *const leftLeftString = leftConcatString->LeftString();
  411. if(VirtualTableInfo<ConcatString>::HasVirtualTable(leftLeftString))
  412. {
  413. #ifdef PROFILE_STRINGS
  414. StringProfiler::RecordConcatenation(GetScriptContext(), GetLength(), pstRight->GetLength(), ConcatType_CompoundString);
  415. #endif
  416. if(PHASE_TRACE_StringConcat)
  417. {
  418. Output::Print(
  419. _u("JavascriptString::ConcatDestructive(\"%.8s%s\") - converting ConcatString to CompoundString\n"),
  420. pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
  421. !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
  422. Output::Flush();
  423. }
  424. const ConcatString *const leftLeftConcatString = static_cast<const ConcatString *>(leftConcatString->LeftString());
  425. CompoundString *const cs = CompoundString::NewWithPointerCapacity(8, GetLibrary());
  426. cs->Append(leftLeftConcatString->LeftString());
  427. cs->Append(leftLeftConcatString->RightString());
  428. cs->Append(leftConcatString->RightString());
  429. cs->Append(pstRight);
  430. return cs;
  431. }
  432. return nullptr;
  433. }
  434. JavascriptString* JavascriptString::ConcatDestructive_OneEmpty(JavascriptString* pstRight)
  435. {
  436. Assert(pstRight);
  437. Assert(GetLength() == 0 || pstRight->GetLength() == 0);
  438. #ifdef PROFILE_STRINGS
  439. StringProfiler::RecordConcatenation(GetScriptContext(), GetLength(), pstRight->GetLength());
  440. #endif
  441. if(PHASE_TRACE_StringConcat)
  442. {
  443. Output::Print(
  444. _u("JavascriptString::ConcatDestructive(\"%.8s%s\") - one side empty, using other side\n"),
  445. pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
  446. !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
  447. Output::Flush();
  448. }
  449. if(GetLength() == 0)
  450. {
  451. return CompoundString::GetImmutableOrScriptUnreferencedString(pstRight);
  452. }
  453. Assert(CompoundString::GetImmutableOrScriptUnreferencedString(this) == this);
  454. return this;
  455. }
  456. JavascriptString* JavascriptString::ConcatDestructive_CompoundAppendChars(JavascriptString* pstRight)
  457. {
  458. Assert(pstRight);
  459. Assert(
  460. GetLength() != 0 &&
  461. pstRight->GetLength() != 0 &&
  462. (CompoundString::ShouldAppendChars(GetLength()) || CompoundString::ShouldAppendChars(pstRight->GetLength())));
  463. #ifdef PROFILE_STRINGS
  464. StringProfiler::RecordConcatenation(GetScriptContext(), GetLength(), pstRight->GetLength(), ConcatType_CompoundString);
  465. #endif
  466. if(PHASE_TRACE_StringConcat)
  467. {
  468. Output::Print(
  469. _u("JavascriptString::ConcatDestructive(\"%.8s%s\") - creating CompoundString, appending chars\n"),
  470. pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
  471. !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
  472. Output::Flush();
  473. }
  474. CompoundString *const cs = CompoundString::NewWithPointerCapacity(4, GetLibrary());
  475. cs->AppendChars(this);
  476. cs->AppendChars(pstRight);
  477. return cs;
  478. }
  479. JavascriptString* JavascriptString::Concat(JavascriptString* pstLeft, JavascriptString* pstRight)
  480. {
  481. AssertMsg(pstLeft != nullptr, "Must have a valid left string");
  482. AssertMsg(pstRight != nullptr, "Must have a valid right string");
  483. if(!pstLeft->IsFinalized())
  484. {
  485. if(VarIs<CompoundString>(pstLeft))
  486. {
  487. return Concat_Compound(pstLeft, pstRight);
  488. }
  489. if(VirtualTableInfo<ConcatString>::HasVirtualTable(pstLeft))
  490. {
  491. return Concat_ConcatToCompound(pstLeft, pstRight);
  492. }
  493. }
  494. else if(pstLeft->GetLength() == 0 || pstRight->GetLength() == 0)
  495. {
  496. return Concat_OneEmpty(pstLeft, pstRight);
  497. }
  498. if(pstLeft->GetLength() != 1 || pstRight->GetLength() != 1)
  499. {
  500. #ifdef PROFILE_STRINGS
  501. StringProfiler::RecordConcatenation(pstLeft->GetScriptContext(), pstLeft->GetLength(), pstRight->GetLength(), ConcatType_ConcatTree);
  502. #endif
  503. if(PHASE_TRACE_StringConcat)
  504. {
  505. Output::Print(
  506. _u("JavascriptString::Concat(\"%.8s%s\") - creating ConcatString\n"),
  507. pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
  508. !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
  509. Output::Flush();
  510. }
  511. return ConcatString::New(pstLeft, pstRight);
  512. }
  513. return Concat_BothOneChar(pstLeft, pstRight);
  514. }
  515. JavascriptString* JavascriptString::Concat_Compound(JavascriptString * pstLeft, JavascriptString * pstRight)
  516. {
  517. Assert(pstLeft);
  518. Assert(VarIs<CompoundString>(pstLeft));
  519. Assert(pstRight);
  520. #ifdef PROFILE_STRINGS
  521. StringProfiler::RecordConcatenation(pstLeft->GetScriptContext(), pstLeft->GetLength(), pstRight->GetLength(), ConcatType_CompoundString);
  522. #endif
  523. if(PHASE_TRACE_StringConcat)
  524. {
  525. Output::Print(
  526. _u("JavascriptString::Concat(\"%.8s%s\") - cloning CompoundString, appending to clone\n"),
  527. pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
  528. !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
  529. Output::Flush();
  530. }
  531. // This is not a left-dead concat, but we can reuse available space in the left string
  532. // because it may be accessible by script code, append to a clone.
  533. const bool needAppend = pstRight->GetLength() != 0;
  534. CompoundString *const leftCs = VarTo<CompoundString>(pstLeft)->Clone(needAppend);
  535. if(needAppend)
  536. {
  537. leftCs->Append(pstRight);
  538. }
  539. return leftCs;
  540. }
  541. JavascriptString* JavascriptString::Concat_ConcatToCompound(JavascriptString * pstLeft, JavascriptString * pstRight)
  542. {
  543. Assert(pstLeft);
  544. Assert(VirtualTableInfo<ConcatString>::HasVirtualTable(pstLeft));
  545. Assert(pstRight);
  546. #ifdef PROFILE_STRINGS
  547. StringProfiler::RecordConcatenation(pstLeft->GetScriptContext(), pstLeft->GetLength(), pstRight->GetLength(), ConcatType_CompoundString);
  548. #endif
  549. if(PHASE_TRACE_StringConcat)
  550. {
  551. Output::Print(
  552. _u("JavascriptString::Concat(\"%.8s%s\") - converting ConcatString to CompoundString\n"),
  553. pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
  554. !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
  555. Output::Flush();
  556. }
  557. const ConcatString *const leftConcatString = static_cast<const ConcatString *>(pstLeft);
  558. CompoundString *const cs = CompoundString::NewWithPointerCapacity(8, pstLeft->GetLibrary());
  559. cs->Append(leftConcatString->LeftString());
  560. cs->Append(leftConcatString->RightString());
  561. cs->Append(pstRight);
  562. return cs;
  563. }
  564. JavascriptString* JavascriptString::Concat_OneEmpty(JavascriptString * pstLeft, JavascriptString * pstRight)
  565. {
  566. Assert(pstLeft);
  567. Assert(pstRight);
  568. Assert(pstLeft->GetLength() == 0 || pstRight->GetLength() == 0);
  569. #ifdef PROFILE_STRINGS
  570. StringProfiler::RecordConcatenation(pstLeft->GetScriptContext(), pstLeft->GetLength(), pstRight->GetLength());
  571. #endif
  572. if(PHASE_TRACE_StringConcat)
  573. {
  574. Output::Print(
  575. _u("JavascriptString::Concat(\"%.8s%s\") - one side empty, using other side\n"),
  576. pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
  577. !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
  578. Output::Flush();
  579. }
  580. if(pstLeft->GetLength() == 0)
  581. {
  582. return CompoundString::GetImmutableOrScriptUnreferencedString(pstRight);
  583. }
  584. Assert(CompoundString::GetImmutableOrScriptUnreferencedString(pstLeft) == pstLeft);
  585. return pstLeft;
  586. }
  587. JavascriptString* JavascriptString::Concat_BothOneChar(JavascriptString * pstLeft, JavascriptString * pstRight)
  588. {
  589. Assert(pstLeft);
  590. Assert(pstLeft->GetLength() == 1);
  591. Assert(pstRight);
  592. Assert(pstRight->GetLength() == 1);
  593. #ifdef PROFILE_STRINGS
  594. StringProfiler::RecordConcatenation(pstLeft->GetScriptContext(), pstLeft->GetLength(), pstRight->GetLength(), ConcatType_BufferString);
  595. #endif
  596. if(PHASE_TRACE_StringConcat)
  597. {
  598. Output::Print(
  599. _u("JavascriptString::Concat(\"%.8s%s\") - both sides length 1, creating BufferStringBuilder::WritableString\n"),
  600. pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
  601. !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
  602. Output::Flush();
  603. }
  604. ScriptContext* scriptContext = pstLeft->GetScriptContext();
  605. BufferStringBuilder builder(2, scriptContext);
  606. char16 * stringBuffer = builder.DangerousGetWritableBuffer();
  607. stringBuffer[0] = *pstLeft->GetString();
  608. stringBuffer[1] = *pstRight->GetString();
  609. return builder.ToString();
  610. }
  611. // Relative indexing proposal
  612. // String.prototype.at(index): https://tc39.es/proposal-relative-indexing-method/#sec-string.prototype.at
  613. // Spec: https://tc39.es/proposal-relative-indexing-method
  614. // Github: https://github.com/tc39/proposal-relative-indexing-method
  615. Var JavascriptString::EntryAt(RecyclableObject* function, CallInfo callInfo, ...) {
  616. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  617. ARGUMENTS(args, callInfo);
  618. ScriptContext* scriptContext = function->GetScriptContext();
  619. JS_REENTRANCY_LOCK(jsReentLock, scriptContext->GetThreadContext());
  620. Assert(!(callInfo.Flags & CallFlags_New));
  621. JavascriptString * pThis = nullptr;
  622. // 1. Let O be ? RequireObjectCoercible(this value).
  623. // 2. Let S be ? ToString(O).
  624. JS_REENTRANT(jsReentLock, GetThisStringArgument(args, scriptContext, _u("String.prototype.at"), &pThis));
  625. // 3. Let len be the length of S.
  626. charcount_t len = pThis->GetLength();
  627. // 4. Let relativeIndex be ? ToInteger(index).
  628. int64_t relativeIndex = 0;
  629. if (args.Info.Count > 1)
  630. {
  631. JS_REENTRANT(jsReentLock, relativeIndex = NumberUtilities::TryToInt64(JavascriptConversion::ToInteger(args[1], scriptContext)));
  632. }
  633. // 5. If relativeIndex >= 0, then
  634. // a. Let k be relativeIndex.
  635. // 6. Else,
  636. // a. Let k be len + relativeIndex.
  637. int64_t k = relativeIndex;
  638. if (relativeIndex < 0)
  639. {
  640. k += len;
  641. }
  642. // 7. If k < 0 or k >= len, then return undefined.
  643. if (k < 0 || k >= (int64_t)len)
  644. {
  645. return scriptContext->GetLibrary()->GetUndefined();
  646. }
  647. Var value;
  648. // 8. Return the String value consisting of only the code unit at position k in S.
  649. if (pThis->GetItemAt((charcount_t)k, &value))
  650. {
  651. #ifdef ENABLE_SPECTRE_RUNTIME_MITIGATIONS
  652. value = BreakSpeculation(value);
  653. #endif
  654. return value;
  655. }
  656. else
  657. {
  658. // Yes, i except this to be
  659. return scriptContext->GetLibrary()->GetUndefined();
  660. }
  661. }
  662. Var JavascriptString::EntryCharAt(RecyclableObject* function, CallInfo callInfo, ...)
  663. {
  664. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  665. ARGUMENTS(args, callInfo);
  666. ScriptContext* scriptContext = function->GetScriptContext();
  667. Assert(!(callInfo.Flags & CallFlags_New));
  668. //
  669. // General algorithm:
  670. // 1. Call CheckObjectCoercible passing the this value as its argument.
  671. // 2. Let S be the result of calling ToString, giving it the this value as its argument.
  672. // 3. Let position be ToInteger(pos).
  673. // 4. Let size be the number of characters in S.
  674. // 5. If position < 0 or position = size, return the empty string.
  675. // 6. Return a string of length 1, containing one character from S, where the first (leftmost) character in S is considered to be at position 0, the next one at position 1, and so on.
  676. // NOTE
  677. // The charAt function is intentionally generic; it does not require that its this value be a String object. Therefore, it can be transferred to other kinds of objects for use as a method.
  678. //
  679. JavascriptString * pThis = nullptr;
  680. GetThisStringArgument(args, scriptContext, _u("String.prototype.charAt"), &pThis);
  681. charcount_t idxPosition = 0;
  682. if (args.Info.Count > 1)
  683. {
  684. idxPosition = ConvertToIndex(args[1], scriptContext);
  685. }
  686. //
  687. // Get the character at the specified position.
  688. //
  689. Var value;
  690. if (pThis->GetItemAt(idxPosition, &value))
  691. {
  692. #ifdef ENABLE_SPECTRE_RUNTIME_MITIGATIONS
  693. value = BreakSpeculation(value);
  694. #endif
  695. return value;
  696. }
  697. else
  698. {
  699. return scriptContext->GetLibrary()->GetEmptyString();
  700. }
  701. }
  702. Var JavascriptString::EntryCharCodeAt(RecyclableObject* function, CallInfo callInfo, ...)
  703. {
  704. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  705. ARGUMENTS(args, callInfo);
  706. ScriptContext* scriptContext = function->GetScriptContext();
  707. Assert(!(callInfo.Flags & CallFlags_New));
  708. //
  709. // General algorithm:
  710. // 1. Call CheckObjectCoercible passing the this value as its argument.
  711. // 2. Let S be the result of calling ToString, giving it the this value as its argument.
  712. // 3. Let position be ToInteger(pos).
  713. // 4. Let size be the number of characters in S.
  714. // 5. If position < 0 or position = size, return NaN.
  715. // 6. Return a value of Number type, whose value is the code unit value of the character at that position in the string S, where the first (leftmost) character in S is considered to be at position 0, the next one at position 1, and so on.
  716. // NOTE
  717. // The charCodeAt function is intentionally generic; it does not require that its this value be a String object. Therefore it can be transferred to other kinds of objects for use as a method.
  718. //
  719. JavascriptString * pThis = nullptr;
  720. GetThisStringArgument(args, scriptContext, _u("String.prototype.charCodeAt"), &pThis);
  721. charcount_t idxPosition = 0;
  722. if (args.Info.Count > 1)
  723. {
  724. idxPosition = ConvertToIndex(args[1], scriptContext);
  725. }
  726. //
  727. // Get the character at the specified position.
  728. //
  729. charcount_t charLength = pThis->GetLength();
  730. if (idxPosition >= charLength)
  731. {
  732. return scriptContext->GetLibrary()->GetNaN();
  733. }
  734. Var charCode = TaggedInt::ToVarUnchecked(pThis->GetItem(idxPosition));
  735. #ifdef ENABLE_SPECTRE_RUNTIME_MITIGATIONS
  736. charCode = BreakSpeculation(charCode);
  737. #endif
  738. return charCode;
  739. }
  740. Var JavascriptString::EntryCodePointAt(RecyclableObject* function, CallInfo callInfo, ...)
  741. {
  742. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  743. ARGUMENTS(args, callInfo);
  744. ScriptContext* scriptContext = function->GetScriptContext();
  745. Assert(!(callInfo.Flags & CallFlags_New));
  746. JavascriptString * pThis = nullptr;
  747. GetThisStringArgument(args, scriptContext, _u("String.prototype.codePointAt"), &pThis);
  748. charcount_t idxPosition = 0;
  749. if (args.Info.Count > 1)
  750. {
  751. idxPosition = ConvertToIndex(args[1], scriptContext);
  752. }
  753. charcount_t charLength = pThis->GetLength();
  754. if (idxPosition >= charLength)
  755. {
  756. return scriptContext->GetLibrary()->GetUndefined();
  757. }
  758. // A surrogate pair consists of two characters, a lower part and a higher part.
  759. // Lower part is in range [0xD800 - 0xDBFF], while the higher is [0xDC00 - 0xDFFF].
  760. char16 first = pThis->GetItem(idxPosition);
  761. if (first >= 0xD800u && first < 0xDC00u && (uint)(idxPosition + 1) < pThis->GetLength())
  762. {
  763. char16 second = pThis->GetItem(idxPosition + 1);
  764. if (second >= 0xDC00 && second < 0xE000)
  765. {
  766. return TaggedInt::ToVarUnchecked(NumberUtilities::SurrogatePairAsCodePoint(first, second));
  767. }
  768. }
  769. return TaggedInt::ToVarUnchecked(first);
  770. }
  771. Var JavascriptString::EntryConcat(RecyclableObject* function, CallInfo callInfo, ...)
  772. {
  773. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  774. ARGUMENTS(args, callInfo);
  775. ScriptContext* scriptContext = function->GetScriptContext();
  776. Assert(!(callInfo.Flags & CallFlags_New));
  777. //
  778. // General algorithm:
  779. // 1. Call CheckObjectCoercible passing the this value as its argument.
  780. // 2. Let S be the result of calling ToString, giving it the this value as its argument.
  781. // 3. Let args be an internal list that is a copy of the argument list passed to this function.
  782. // 4. Let R be S.
  783. // 5. Repeat, while args is not empty
  784. // Remove the first element from args and let next be the value of that element.
  785. // Let R be the string value consisting of the characters in the previous value of R followed by the characters of ToString(next).
  786. // 6. Return R.
  787. //
  788. // NOTE
  789. // The concat function is intentionally generic; it does not require that its this value be a String object. Therefore it can be transferred to other kinds of objects for use as a method.
  790. //
  791. if(args.Info.Count == 0)
  792. {
  793. JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedString, _u("String.prototype.concat"));
  794. }
  795. AssertMsg(args.Info.Count > 0, "Negative argument count");
  796. if (!JavascriptConversion::CheckObjectCoercible(args[0], scriptContext))
  797. {
  798. JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, _u("String.prototype.concat"));
  799. }
  800. JavascriptString* accum = nullptr;
  801. for (uint index = 0; index < args.Info.Count; index++)
  802. {
  803. JavascriptString * pstr = JavascriptOperators::TryFromVar<JavascriptString>(args[index]);
  804. if (!pstr)
  805. {
  806. pstr = JavascriptConversion::ToString(args[index], scriptContext);
  807. }
  808. if (index == 0)
  809. {
  810. accum = pstr;
  811. }
  812. else
  813. {
  814. accum = Concat(accum,pstr);
  815. }
  816. }
  817. return accum;
  818. }
  819. Var JavascriptString::EntryFromCharCode(RecyclableObject* function, CallInfo callInfo, ...)
  820. {
  821. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  822. ARGUMENTS(args, callInfo);
  823. ScriptContext* scriptContext = function->GetScriptContext();
  824. Assert(!(callInfo.Flags & CallFlags_New));
  825. //
  826. // Construct a new string instance to contain all of the explicit parameters:
  827. // - Don't include the 'this' parameter.
  828. //
  829. if(args.Info.Count == 0)
  830. {
  831. JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedString, _u("String.fromCharCode"));
  832. }
  833. AssertMsg(args.Info.Count > 0, "Negative argument count");
  834. int charLength = args.Info.Count - 1;
  835. // Special case for single char
  836. if( charLength == 1 )
  837. {
  838. char16 ch = JavascriptConversion::ToUInt16(args[1], scriptContext);
  839. return scriptContext->GetLibrary()->GetCharStringCache().GetStringForChar(ch);
  840. }
  841. BufferStringBuilder builder(charLength,scriptContext);
  842. char16 * stringBuffer = builder.DangerousGetWritableBuffer();
  843. //
  844. // Call ToUInt16 for each parameter, storing the character at the appropriate position.
  845. //
  846. for (uint idxArg = 1; idxArg < args.Info.Count; idxArg++)
  847. {
  848. *stringBuffer++ = JavascriptConversion::ToUInt16(args[idxArg], scriptContext);
  849. }
  850. //
  851. // Return the new string instance.
  852. //
  853. return builder.ToString();
  854. }
  855. Var JavascriptString::EntryFromCodePoint(RecyclableObject* function, CallInfo callInfo, ...)
  856. {
  857. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  858. ARGUMENTS(args, callInfo);
  859. ScriptContext* scriptContext = function->GetScriptContext();
  860. Assert(!(callInfo.Flags & CallFlags_New));
  861. AssertMsg(args.Info.Count > 0, "Negative argument count");
  862. if (args.Info.Count <= 1)
  863. {
  864. return scriptContext->GetLibrary()->GetEmptyString();
  865. }
  866. else if (args.Info.Count == 2)
  867. {
  868. // Special case for a single char string formed from only code point in range [0x0, 0xFFFF]
  869. double num = JavascriptConversion::ToNumber(args[1], scriptContext);
  870. if (!NumberUtilities::IsFinite(num))
  871. {
  872. JavascriptError::ThrowRangeError(scriptContext, JSERR_InvalidCodePoint);
  873. }
  874. if (num < 0 || num > 0x10FFFF || floor(num) != num)
  875. {
  876. JavascriptError::ThrowRangeErrorVar(scriptContext, JSERR_InvalidCodePoint, Js::JavascriptConversion::ToString(args[1], scriptContext)->GetSz());
  877. }
  878. if (num < 0x10000)
  879. {
  880. return scriptContext->GetLibrary()->GetCharStringCache().GetStringForChar((uint16)num);
  881. }
  882. }
  883. BEGIN_TEMP_ALLOCATOR(tempAllocator, scriptContext, _u("fromCodePoint"));
  884. // Create a temporary buffer that is double the arguments count (in case all are surrogate pairs)
  885. size_t bufferLength = (args.Info.Count - 1) * 2;
  886. char16 *tempBuffer = AnewArray(tempAllocator, char16, bufferLength);
  887. uint32 count = 0;
  888. for (uint i = 1; i < args.Info.Count; i++)
  889. {
  890. double num = JavascriptConversion::ToNumber(args[i], scriptContext);
  891. if (!NumberUtilities::IsFinite(num))
  892. {
  893. JavascriptError::ThrowRangeError(scriptContext, JSERR_InvalidCodePoint);
  894. }
  895. if (num < 0 || num > 0x10FFFF || floor(num) != num)
  896. {
  897. JavascriptError::ThrowRangeErrorVar(scriptContext, JSERR_InvalidCodePoint, Js::JavascriptConversion::ToString(args[i], scriptContext)->GetSz());
  898. }
  899. if (num < 0x10000)
  900. {
  901. __analysis_assume(count < bufferLength);
  902. Assert(count < bufferLength);
  903. #pragma prefast(suppress: 22102, "I have an assert in place to guard against overflow. Even though this should never happen.")
  904. tempBuffer[count] = (char16)num;
  905. count++;
  906. }
  907. else
  908. {
  909. __analysis_assume(count + 1 < bufferLength);
  910. Assert(count + 1 < bufferLength);
  911. NumberUtilities::CodePointAsSurrogatePair((codepoint_t)num, (tempBuffer + count), (tempBuffer + count + 1));
  912. count += 2;
  913. }
  914. }
  915. // Create a string of appropriate length
  916. __analysis_assume(count <= bufferLength);
  917. Assert(count <= bufferLength);
  918. JavascriptString *toReturn = JavascriptString::NewCopyBuffer(tempBuffer, count, scriptContext);
  919. END_TEMP_ALLOCATOR(tempAllocator, scriptContext);
  920. return toReturn;
  921. }
  922. Var JavascriptString::EntryIndexOf(RecyclableObject* function, CallInfo callInfo, ...)
  923. {
  924. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  925. ARGUMENTS(args, callInfo);
  926. AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
  927. ScriptContext* scriptContext = function->GetScriptContext();
  928. Assert(!(callInfo.Flags & CallFlags_New));
  929. return JavascriptNumber::ToVar(IndexOf(args, scriptContext, _u("String.prototype.indexOf"), true), scriptContext);
  930. }
  931. int JavascriptString::IndexOf(ArgumentReader& args, ScriptContext* scriptContext, const char16* apiNameForErrorMsg, bool isRegExpAnAllowedArg)
  932. {
  933. // The algorithm steps in the spec are the same between String.prototype.indexOf and
  934. // String.prototype.includes, except that includes returns true if an index is found,
  935. // false otherwise. Share the implementation between these two APIs.
  936. //
  937. // 1. Call CheckObjectCoercible passing the this value as its argument.
  938. // 2. Let S be the result of calling ToString, giving it the this value as its argument.
  939. // 3. Let searchStr be ToString(searchString).
  940. // 4. Let pos be ToInteger(position). (If position is undefined, this step produces the value 0).
  941. // 5. Let len be the number of characters in S.
  942. // 6. Let start be min(max(pos, 0), len).
  943. // 7. Let searchLen be the number of characters in searchStr.
  944. // 8. Return the smallest possible integer k not smaller than start such that k+ searchLen is not greater than len, and for all nonnegative integers j less than searchLen, the character at position k+j of S is the same as the character at position j of searchStr); but if there is no such integer k, then return the value -1.
  945. // NOTE
  946. // The indexOf function is intentionally generic; it does not require that its this value be a String object. Therefore, it can be transferred to other kinds of objects for use as a method.
  947. //
  948. JavascriptString * pThis;
  949. JavascriptString * searchString;
  950. GetThisAndSearchStringArguments(args, scriptContext, apiNameForErrorMsg, &pThis, &searchString, isRegExpAnAllowedArg);
  951. int len = pThis->GetLength();
  952. int searchLen = searchString->GetLength();
  953. int position = 0;
  954. if (args.Info.Count > 2)
  955. {
  956. if (JavascriptOperators::IsUndefinedObject(args[2]))
  957. {
  958. position = 0;
  959. }
  960. else
  961. {
  962. position = ConvertToIndex(args[2], scriptContext); // this is to adjust corner cases like MAX_VALUE
  963. position = min(max(position, 0), len); // adjust position within string limits
  964. }
  965. }
  966. // Zero length search strings are always found at the current search position
  967. if (searchLen == 0)
  968. {
  969. return position;
  970. }
  971. int result = -1;
  972. if (position < pThis->GetLengthAsSignedInt())
  973. {
  974. const char16* searchStr = searchString->GetString();
  975. const char16* inputStr = pThis->GetString();
  976. if (searchLen == 1)
  977. {
  978. int i = position;
  979. for(; i < len && inputStr[i] != *searchStr ; i++);
  980. if (i < len)
  981. {
  982. result = i;
  983. }
  984. }
  985. else
  986. {
  987. JmpTable jmpTable;
  988. bool fAsciiJumpTable = BuildLastCharForwardBoyerMooreTable(jmpTable, searchStr, searchLen);
  989. if (!fAsciiJumpTable)
  990. {
  991. result = JavascriptString::strstr(pThis, searchString, false, position);
  992. }
  993. else
  994. {
  995. result = IndexOfUsingJmpTable(jmpTable, inputStr, len, searchStr, searchLen, position);
  996. }
  997. }
  998. }
  999. return result;
  1000. }
  1001. Var JavascriptString::EntryLastIndexOf(RecyclableObject* function, CallInfo callInfo, ...)
  1002. {
  1003. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  1004. ARGUMENTS(args, callInfo);
  1005. ScriptContext* scriptContext = function->GetScriptContext();
  1006. Assert(!(callInfo.Flags & CallFlags_New));
  1007. // ES #sec-string.prototype.lastindexof
  1008. // 21.1.3.9 String.prototype.lastIndexOf(searchString[, position])
  1009. //
  1010. // 1. Let O be ? RequireObjectCoercible(this value).
  1011. // 2. Let S be ? ToString(O).
  1012. // 3. Let searchStr be ? ToString(searchString).
  1013. // default search string if the search argument is not provided
  1014. JavascriptString * pThis = nullptr;
  1015. GetThisStringArgument(args, scriptContext, _u("String.prototype.lastIndexOf"), &pThis);
  1016. JavascriptString * searchArg = nullptr;
  1017. if(args.Info.Count > 1)
  1018. {
  1019. searchArg = JavascriptOperators::TryFromVar<JavascriptString>(args[1]);
  1020. if (!searchArg)
  1021. {
  1022. searchArg = JavascriptConversion::ToString(args[1], scriptContext);
  1023. }
  1024. }
  1025. else
  1026. {
  1027. searchArg = scriptContext->GetLibrary()->GetUndefinedDisplayString();
  1028. }
  1029. const char16* const inputStr = pThis->GetString();
  1030. const char16 * const searchStr = searchArg->GetString();
  1031. // 4. Let numPos be ? ToNumber(position). (If position is undefined, this step produces the value NaN.)
  1032. // 5. If numPos is NaN, let pos be +infinity; otherwise, let pos be ToInteger(numPos).
  1033. // 6. Let len be the number of elements in S.
  1034. // 7. Let start be min(max(pos, 0), len).
  1035. const charcount_t inputLen = pThis->GetLength();
  1036. const charcount_t searchLen = searchArg->GetLength();
  1037. charcount_t position = inputLen;
  1038. const char16* const searchLowerBound = inputStr;
  1039. // Determine if the main string can't contain the search string by length
  1040. if (searchLen > inputLen)
  1041. {
  1042. return JavascriptNumber::ToVar(-1, scriptContext);
  1043. }
  1044. if (args.Info.Count > 2)
  1045. {
  1046. double pos = JavascriptConversion::ToNumber(args[2], scriptContext);
  1047. if (!JavascriptNumber::IsNan(pos))
  1048. {
  1049. pos = JavascriptConversion::ToInteger(pos);
  1050. if (pos > inputLen - searchLen)
  1051. {
  1052. // No point searching beyond the possible end point.
  1053. pos = inputLen - searchLen;
  1054. }
  1055. position = (charcount_t)min(max(pos, (double)0), (double)inputLen); // adjust position within string limits
  1056. }
  1057. }
  1058. if (position > inputLen - searchLen)
  1059. {
  1060. // No point searching beyond the possible end point.
  1061. position = inputLen - searchLen;
  1062. }
  1063. const char16* const searchUpperBound = searchLowerBound + min(position, inputLen - 1);
  1064. // 8. Let searchLen be the number of elements in searchStr.
  1065. // 9. Return the largest possible nonnegative integer k not larger than start such that k + searchLen is
  1066. // not greater than len, and for all nonnegative integers j less than searchLen, the code unit at
  1067. // index k + j of S is the same as the code unit at index j of searchStr; but if there is no such
  1068. // integer k, return the value - 1.
  1069. // Note: The lastIndexOf function is intentionally generic; it does not require that its this value be a
  1070. // String object. Therefore, it can be transferred to other kinds of objects for use as a method.
  1071. // Zero length search strings are always found at the current search position
  1072. if (searchLen == 0)
  1073. {
  1074. return JavascriptNumber::ToVar(position, scriptContext);
  1075. }
  1076. else if (searchLen == 1)
  1077. {
  1078. char16 const * current = searchUpperBound;
  1079. while (*current != *searchStr)
  1080. {
  1081. current--;
  1082. if (current < inputStr)
  1083. {
  1084. return JavascriptNumber::ToVar(-1, scriptContext);
  1085. }
  1086. }
  1087. return JavascriptNumber::ToVar(current - inputStr, scriptContext);
  1088. }
  1089. // Structure for a partial ASCII Boyer-Moore
  1090. JmpTable jmpTable;
  1091. if (BuildFirstCharBackwardBoyerMooreTable(jmpTable, searchStr, searchLen))
  1092. {
  1093. int result = LastIndexOfUsingJmpTable(jmpTable, inputStr, inputLen, searchStr, searchLen, position);
  1094. return JavascriptNumber::ToVar(result, scriptContext);
  1095. }
  1096. // Revert to slow search if we decided not to do Boyer-Moore.
  1097. char16 const * currentPos = searchUpperBound;
  1098. Assert(currentPos - searchLowerBound + searchLen <= inputLen);
  1099. while (currentPos >= searchLowerBound)
  1100. {
  1101. if (*currentPos == *searchStr)
  1102. {
  1103. // Quick start char chec
  1104. if (wmemcmp(currentPos, searchStr, searchLen) == 0)
  1105. {
  1106. return JavascriptNumber::ToVar(currentPos - searchLowerBound, scriptContext);
  1107. }
  1108. }
  1109. --currentPos;
  1110. }
  1111. return JavascriptNumber::ToVar(-1, scriptContext);
  1112. }
  1113. // Performs common ES spec steps for getting this argument in string form:
  1114. // 1. Let O be CHeckObjectCoercible(this value).
  1115. // 2. Let S be ToString(O).
  1116. // 3. ReturnIfAbrupt(S).
  1117. void JavascriptString::GetThisStringArgument(ArgumentReader& args, ScriptContext* scriptContext, const char16* apiNameForErrorMsg, JavascriptString** ppThis)
  1118. {
  1119. if (args.Info.Count == 0)
  1120. {
  1121. JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedString, apiNameForErrorMsg);
  1122. }
  1123. AssertMsg(args.Info.Count > 0, "Negative argument count");
  1124. JavascriptString * pThis = JavascriptOperators::TryFromVar<JavascriptString>(args[0]);
  1125. if (!pThis)
  1126. {
  1127. pThis = JavascriptConversion::CoerseString(args[0], scriptContext , apiNameForErrorMsg);
  1128. }
  1129. *ppThis = pThis;
  1130. }
  1131. // Performs common ES spec steps for getting this and first parameter arguments in string form:
  1132. // 1. Let O be CHeckObjectCoercible(this value).
  1133. // 2. Let S be ToString(O).
  1134. // 3. ReturnIfAbrupt(S).
  1135. // 4. Let otherStr be ToString(firstArg).
  1136. // 5. ReturnIfAbrupt(otherStr).
  1137. void JavascriptString::GetThisAndSearchStringArguments(ArgumentReader& args, ScriptContext* scriptContext, const char16* apiNameForErrorMsg, JavascriptString** ppThis, JavascriptString** ppSearch, bool isRegExpAnAllowedArg)
  1138. {
  1139. GetThisStringArgument(args, scriptContext, apiNameForErrorMsg, ppThis);
  1140. JavascriptString * pSearch = scriptContext->GetLibrary()->GetUndefinedDisplayString();
  1141. if (args.Info.Count > 1)
  1142. {
  1143. if (!isRegExpAnAllowedArg && JavascriptRegExp::IsRegExpLike(args[1], scriptContext))
  1144. {
  1145. JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_FirstCannotBeRegExp, apiNameForErrorMsg);
  1146. }
  1147. else
  1148. {
  1149. pSearch = JavascriptOperators::TryFromVar<JavascriptString>(args[1]);
  1150. if (!pSearch)
  1151. {
  1152. pSearch = JavascriptConversion::ToString(args[1], scriptContext);
  1153. }
  1154. }
  1155. }
  1156. *ppSearch = pSearch;
  1157. }
  1158. Var JavascriptString::EntryLocaleCompare(RecyclableObject* function, CallInfo callInfo, ...)
  1159. {
  1160. using namespace PlatformAgnostic;
  1161. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  1162. ARGUMENTS(args, callInfo);
  1163. ScriptContext* scriptContext = function->GetScriptContext();
  1164. Assert(!(callInfo.Flags & CallFlags_New));
  1165. if(args.Info.Count == 0)
  1166. {
  1167. JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedString, _u("String.prototype.localeCompare"));
  1168. }
  1169. AssertMsg(args.Info.Count > 0, "Negative argument count");
  1170. JavascriptString * pThis;
  1171. JavascriptString * pThat;
  1172. GetThisAndSearchStringArguments(args, scriptContext, _u("String.prototype.localeCompare"), &pThis, &pThat, true);
  1173. #ifdef ENABLE_INTL_OBJECT
  1174. if (CONFIG_FLAG(IntlBuiltIns) && scriptContext->IsIntlEnabled())
  1175. {
  1176. EngineInterfaceObject* nativeEngineInterfaceObj = scriptContext->GetLibrary()->GetEngineInterfaceObject();
  1177. if (nativeEngineInterfaceObj)
  1178. {
  1179. IntlEngineInterfaceExtensionObject* intlExtensionObject = static_cast<IntlEngineInterfaceExtensionObject*>(
  1180. nativeEngineInterfaceObj->GetEngineExtension(EngineInterfaceExtensionKind_Intl));
  1181. #ifdef INTL_WINGLOB
  1182. if (args.Info.Count == 2)
  1183. {
  1184. auto undefined = scriptContext->GetLibrary()->GetUndefined();
  1185. CallInfo toPass(callInfo.Flags, 3);
  1186. ThreadContext *threadContext = scriptContext->GetThreadContext();
  1187. return threadContext->ExecuteImplicitCall(function, ImplicitCall_Accessor,
  1188. [threadContext, intlExtensionObject, function, toPass, undefined, pThis, pThat]() -> Var
  1189. {
  1190. return CALL_ENTRYPOINT(threadContext, intlExtensionObject->EntryIntl_CompareString,
  1191. function, toPass, undefined, pThis, pThat);
  1192. }
  1193. );
  1194. }
  1195. #endif
  1196. // Check if String.prototype.localeCompare/Intl.Collator was already initialized
  1197. JavascriptFunction* func = intlExtensionObject->GetStringLocaleCompare();
  1198. if (func)
  1199. {
  1200. BEGIN_SAFE_REENTRANT_CALL(scriptContext->GetThreadContext())
  1201. {
  1202. return func->CallFunction(args);
  1203. }
  1204. END_SAFE_REENTRANT_CALL
  1205. }
  1206. // String.prototype.localeCompare/Intl.Collator was not initialized yet, so we need to manually initialize it here
  1207. scriptContext->GetLibrary()->InitializeIntlForStringPrototype();
  1208. func = intlExtensionObject->GetStringLocaleCompare();
  1209. if (func)
  1210. {
  1211. BEGIN_SAFE_REENTRANT_CALL(scriptContext->GetThreadContext())
  1212. {
  1213. return func->CallFunction(args);
  1214. }
  1215. END_SAFE_REENTRANT_CALL
  1216. }
  1217. }
  1218. }
  1219. #endif
  1220. const char16* pThisStr = pThis->GetString();
  1221. int thisStrCount = pThis->GetLength();
  1222. const char16* pThatStr = pThat->GetString();
  1223. int thatStrCount = pThat->GetLength();
  1224. int result = UnicodeText::LogicalStringCompare(pThisStr, thisStrCount, pThatStr, thatStrCount);
  1225. // LogicalStringCompare will return -2 if CompareStringEx fails.
  1226. if (result == -2)
  1227. {
  1228. // TODO there is no spec on the error thrown here.
  1229. // When the support for HR errors is implemented replace this with the same error reported by v5.8
  1230. JavascriptError::ThrowRangeError(function->GetScriptContext(),
  1231. VBSERR_InternalError /* TODO-ERROR: _u("Failed compare operation")*/ );
  1232. }
  1233. return JavascriptNumber::ToVar(result, scriptContext);
  1234. }
  1235. Var JavascriptString::EntryMatch(RecyclableObject* function, CallInfo callInfo, ...)
  1236. {
  1237. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  1238. ARGUMENTS(args, callInfo);
  1239. ScriptContext* scriptContext = function->GetScriptContext();
  1240. Assert(!(callInfo.Flags & CallFlags_New));
  1241. PCWSTR const varName = _u("String.prototype.match");
  1242. AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, varName);
  1243. auto fallback = [&](JavascriptString* stringObj)
  1244. {
  1245. Var regExp = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
  1246. if (!scriptContext->GetConfig()->IsES6RegExSymbolsEnabled())
  1247. {
  1248. JavascriptRegExp * regExObj = JavascriptRegExp::CreateRegEx(regExp, nullptr, scriptContext);
  1249. return RegexHelper::RegexMatch(
  1250. scriptContext,
  1251. regExObj,
  1252. stringObj,
  1253. RegexHelper::IsResultNotUsed(callInfo.Flags));
  1254. }
  1255. else
  1256. {
  1257. JavascriptRegExp * regExObj = JavascriptRegExp::CreateRegExNoCoerce(regExp, nullptr, scriptContext);
  1258. Var symbolFn = GetRegExSymbolFunction(regExObj, PropertyIds::_symbolMatch, scriptContext);
  1259. return CallRegExSymbolFunction<1>(symbolFn, regExObj, args, varName, scriptContext);
  1260. }
  1261. };
  1262. return DelegateToRegExSymbolFunction<1>(args, PropertyIds::_symbolMatch, fallback, varName, scriptContext);
  1263. }
  1264. Var JavascriptString::EntryNormalize(RecyclableObject* function, CallInfo callInfo, ...)
  1265. {
  1266. using namespace PlatformAgnostic;
  1267. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  1268. ARGUMENTS(args, callInfo);
  1269. ScriptContext* scriptContext = function->GetScriptContext();
  1270. Assert(!(callInfo.Flags & CallFlags_New));
  1271. JavascriptString *pThis = nullptr;
  1272. GetThisStringArgument(args, scriptContext, _u("String.prototype.normalize"), &pThis);
  1273. UnicodeText::NormalizationForm form = UnicodeText::NormalizationForm::C;
  1274. if (args.Info.Count >= 2 && !(JavascriptOperators::IsUndefinedObject(args.Values[1])))
  1275. {
  1276. JavascriptString *formStr = JavascriptOperators::TryFromVar<JavascriptString>(args[1]);
  1277. if (!formStr)
  1278. {
  1279. formStr = JavascriptConversion::ToString(args[1], scriptContext);
  1280. }
  1281. if (formStr->BufferEquals(_u("NFD"), 3))
  1282. {
  1283. form = UnicodeText::NormalizationForm::D;
  1284. }
  1285. else if (formStr->BufferEquals(_u("NFKC"), 4))
  1286. {
  1287. form = UnicodeText::NormalizationForm::KC;
  1288. }
  1289. else if (formStr->BufferEquals(_u("NFKD"), 4))
  1290. {
  1291. form = UnicodeText::NormalizationForm::KD;
  1292. }
  1293. else if (!formStr->BufferEquals(_u("NFC"), 3))
  1294. {
  1295. JavascriptError::ThrowRangeErrorVar(scriptContext, JSERR_InvalidNormalizationForm, formStr->GetString());
  1296. }
  1297. }
  1298. if (UnicodeText::IsNormalizedString(form, pThis->GetSz(), pThis->GetLength()))
  1299. {
  1300. return pThis;
  1301. }
  1302. BEGIN_TEMP_ALLOCATOR(tempAllocator, scriptContext, _u("normalize"));
  1303. charcount_t sizeEstimate = 0;
  1304. char16* buffer = pThis->GetNormalizedString(form, tempAllocator, sizeEstimate);
  1305. JavascriptString * retVal;
  1306. if (buffer == nullptr)
  1307. {
  1308. Assert(sizeEstimate == 0);
  1309. retVal = scriptContext->GetLibrary()->GetEmptyString();
  1310. }
  1311. else
  1312. {
  1313. retVal = JavascriptString::NewCopyBuffer(buffer, sizeEstimate, scriptContext);
  1314. }
  1315. END_TEMP_ALLOCATOR(tempAllocator, scriptContext);
  1316. return retVal;
  1317. }
  1318. ///----------------------------------------------------------------------------
  1319. /// String.raw(), as described in (ES6.0 (Draft 18): S21.1.2.4).
  1320. ///----------------------------------------------------------------------------
  1321. Var JavascriptString::EntryRaw(RecyclableObject* function, CallInfo callInfo, ...)
  1322. {
  1323. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  1324. ARGUMENTS(args, callInfo);
  1325. ScriptContext* scriptContext = function->GetScriptContext();
  1326. Assert(!(callInfo.Flags & CallFlags_New));
  1327. if (args.Info.Count < 2)
  1328. {
  1329. JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, _u("String.raw"));
  1330. }
  1331. RecyclableObject* callSite;
  1332. RecyclableObject* raw;
  1333. Var rawVar;
  1334. // Call ToObject on the first argument to get the callSite (which is also cooked string array)
  1335. // ToObject returns false if the parameter is null or undefined
  1336. if (!JavascriptConversion::ToObject(args[1], scriptContext, &callSite))
  1337. {
  1338. JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, _u("String.raw"));
  1339. }
  1340. // Get the raw property from the callSite object
  1341. if (!callSite->GetProperty(callSite, Js::PropertyIds::raw, &rawVar, nullptr, scriptContext))
  1342. {
  1343. rawVar = scriptContext->GetLibrary()->GetUndefined();
  1344. }
  1345. if (!JavascriptConversion::ToObject(rawVar, scriptContext, &raw))
  1346. {
  1347. JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, _u("String.raw"));
  1348. }
  1349. int64 length = JavascriptConversion::ToLength(JavascriptOperators::OP_GetLength(raw, scriptContext), scriptContext);
  1350. // If there are no raw strings (somehow), return empty string
  1351. if (length <= 0)
  1352. {
  1353. return scriptContext->GetLibrary()->GetEmptyString();
  1354. }
  1355. // Get the first raw string
  1356. Var var = JavascriptOperators::OP_GetElementI_UInt32(raw, 0, scriptContext);
  1357. JavascriptString* string = JavascriptConversion::ToString(var, scriptContext);
  1358. // If there is only one raw string, just return that one raw string (doesn't matter if there are replacements)
  1359. if (length == 1)
  1360. {
  1361. return string;
  1362. }
  1363. // We aren't going to bail early so let's create a StringBuilder and put the first raw string in there
  1364. CompoundString::Builder<64 * sizeof(void *) / sizeof(char16)> stringBuilder(scriptContext);
  1365. stringBuilder.Append(string);
  1366. // Each raw string is followed by a substitution expression except for the last one
  1367. // We will always have one more string constant than substitution expression
  1368. // `strcon1 ${expr1} strcon2 ${expr2} strcon3` = strcon1 + expr1 + strcon2 + expr2 + strcon3
  1369. //
  1370. // strcon1 --- step 1 (above)
  1371. // expr1 \__ step 2
  1372. // strcon2 /
  1373. // expr2 \__ step 3
  1374. // strcon3 /
  1375. const auto append = [&] (Var var)
  1376. {
  1377. JavascriptString* string = JavascriptConversion::ToString(var, scriptContext);
  1378. stringBuilder.Append(string);
  1379. };
  1380. uint32 loopMax = length >= UINT_MAX ? UINT_MAX-1 : (uint32)length;
  1381. uint32 i = 1;
  1382. uint32 argsCount = args.Info.Count;
  1383. for (; i < loopMax; ++i)
  1384. {
  1385. // First append the next substitution expression if available
  1386. if (i + 1 < argsCount)
  1387. {
  1388. append(args[i + 1]);
  1389. }
  1390. // Then append the next string (this will also cover the final string case)
  1391. append(JavascriptOperators::OP_GetElementI_UInt32(raw, i, scriptContext));
  1392. }
  1393. // Length can be greater than uint32 max (unlikely in practice)
  1394. for (int64 j = (int64)i; j < length; ++j)
  1395. {
  1396. // Append whatever is left in the array/object
  1397. append(JavascriptOperators::OP_GetElementI(raw, JavascriptNumber::ToVar(j, scriptContext), scriptContext));
  1398. }
  1399. // CompoundString::Builder has saved our lives
  1400. return stringBuilder.ToString();
  1401. }
  1402. Var JavascriptString::EntryReplace(RecyclableObject* function, CallInfo callInfo, ...)
  1403. {
  1404. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  1405. ARGUMENTS(args, callInfo);
  1406. ScriptContext* scriptContext = function->GetScriptContext();
  1407. PCWSTR const varName = _u("String.prototype.replace");
  1408. AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, varName);
  1409. Assert(!(callInfo.Flags & CallFlags_New));
  1410. auto fallback = [&](JavascriptString* stringObj)
  1411. {
  1412. return DoStringReplace(args, callInfo, stringObj, scriptContext);
  1413. };
  1414. return DelegateToRegExSymbolFunction<2>(args, PropertyIds::_symbolReplace, fallback, varName, scriptContext);
  1415. }
  1416. Var JavascriptString::DoStringReplace(Arguments& args, CallInfo& callInfo, JavascriptString* input, ScriptContext* scriptContext)
  1417. {
  1418. //
  1419. // TODO: Move argument processing into DirectCall with proper handling.
  1420. //
  1421. JavascriptRegExp * pRegEx = nullptr;
  1422. JavascriptString * pMatch = nullptr;
  1423. JavascriptString * pReplace = nullptr;
  1424. RecyclableObject* replacefn = nullptr;
  1425. SearchValueHelper(scriptContext, ((args.Info.Count > 1)?args[1]:scriptContext->GetLibrary()->GetNull()), &pRegEx, &pMatch);
  1426. ReplaceValueHelper(scriptContext, ((args.Info.Count > 2) ? args[2] : scriptContext->GetLibrary()->GetUndefined()), &replacefn, &pReplace);
  1427. if (pRegEx != nullptr)
  1428. {
  1429. if (replacefn != nullptr)
  1430. {
  1431. return RegexHelper::RegexReplaceFunction(scriptContext, pRegEx, input, replacefn);
  1432. }
  1433. else
  1434. {
  1435. return RegexHelper::RegexReplace(scriptContext, pRegEx, input, pReplace, RegexHelper::IsResultNotUsed(callInfo.Flags));
  1436. }
  1437. }
  1438. AssertMsg(pMatch != nullptr, "Match string shouldn't be null");
  1439. if (replacefn != nullptr)
  1440. {
  1441. return RegexHelper::StringReplace(scriptContext, pMatch, input, replacefn);
  1442. }
  1443. else
  1444. {
  1445. if (callInfo.Flags & CallFlags_NotUsed)
  1446. {
  1447. return scriptContext->GetLibrary()->GetEmptyString();
  1448. }
  1449. return RegexHelper::StringReplace(pMatch, input, pReplace);
  1450. }
  1451. }
  1452. void JavascriptString::SearchValueHelper(ScriptContext* scriptContext, Var aValue, JavascriptRegExp ** ppSearchRegEx, JavascriptString ** ppSearchString)
  1453. {
  1454. *ppSearchRegEx = nullptr;
  1455. *ppSearchString = nullptr;
  1456. // When the config is enabled, the operation is handled by a Symbol function (e.g. Symbol.replace).
  1457. if (!scriptContext->GetConfig()->IsES6RegExSymbolsEnabled()
  1458. && VarIs<JavascriptRegExp>(aValue))
  1459. {
  1460. *ppSearchRegEx = VarTo<JavascriptRegExp>(aValue);
  1461. }
  1462. else if (VarIs<JavascriptString>(aValue))
  1463. {
  1464. *ppSearchString = VarTo<JavascriptString>(aValue);
  1465. }
  1466. else
  1467. {
  1468. *ppSearchString = JavascriptConversion::ToString(aValue, scriptContext);
  1469. }
  1470. }
  1471. void JavascriptString::ReplaceValueHelper(ScriptContext* scriptContext, Var aValue, RecyclableObject ** ppReplaceFn, JavascriptString ** ppReplaceString)
  1472. {
  1473. *ppReplaceFn = nullptr;
  1474. *ppReplaceString = nullptr;
  1475. if (JavascriptConversion::IsCallable(aValue))
  1476. {
  1477. *ppReplaceFn = VarTo<RecyclableObject>(aValue);
  1478. }
  1479. else if (VarIs<JavascriptString>(aValue))
  1480. {
  1481. *ppReplaceString = VarTo<JavascriptString>(aValue);
  1482. }
  1483. else
  1484. {
  1485. *ppReplaceString = JavascriptConversion::ToString(aValue, scriptContext);
  1486. }
  1487. }
  1488. Var JavascriptString::EntrySearch(RecyclableObject* function, CallInfo callInfo, ...)
  1489. {
  1490. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  1491. ARGUMENTS(args, callInfo);
  1492. ScriptContext* scriptContext = function->GetScriptContext();
  1493. Assert(!(callInfo.Flags & CallFlags_New));
  1494. PCWSTR const varName = _u("String.prototype.search");
  1495. AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, varName);
  1496. auto fallback = [&](JavascriptString* stringObj)
  1497. {
  1498. Var regExp = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
  1499. if (!scriptContext->GetConfig()->IsES6RegExSymbolsEnabled())
  1500. {
  1501. JavascriptRegExp * regExObj = JavascriptRegExp::CreateRegEx(regExp, nullptr, scriptContext);
  1502. return RegexHelper::RegexSearch(scriptContext, regExObj, stringObj);
  1503. }
  1504. else
  1505. {
  1506. JavascriptRegExp * regExObj = JavascriptRegExp::CreateRegExNoCoerce(regExp, nullptr, scriptContext);
  1507. Var symbolFn = GetRegExSymbolFunction(regExObj, PropertyIds::_symbolSearch, scriptContext);
  1508. return CallRegExSymbolFunction<1>(symbolFn, regExObj, args, varName, scriptContext);
  1509. }
  1510. };
  1511. return DelegateToRegExSymbolFunction<1>(args, PropertyIds::_symbolSearch, fallback, varName, scriptContext);
  1512. }
  1513. template<int argCount, typename FallbackFn>
  1514. Var JavascriptString::DelegateToRegExSymbolFunction(ArgumentReader &args, PropertyId symbolPropertyId, FallbackFn fallback, PCWSTR varName, ScriptContext* scriptContext)
  1515. {
  1516. if (scriptContext->GetConfig()->IsES6RegExSymbolsEnabled())
  1517. {
  1518. if (args.Info.Count == 0 || !JavascriptConversion::CheckObjectCoercible(args[0], scriptContext))
  1519. {
  1520. JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, varName);
  1521. }
  1522. if (args.Info.Count >= 2 && !JavascriptOperators::IsUndefinedOrNull(args[1]))
  1523. {
  1524. Var regExp = args[1];
  1525. Var symbolFn = GetRegExSymbolFunction(regExp, symbolPropertyId, scriptContext);
  1526. if (!JavascriptOperators::IsUndefinedOrNull(symbolFn))
  1527. {
  1528. return CallRegExSymbolFunction<argCount>(symbolFn, regExp, args, varName, scriptContext);
  1529. }
  1530. }
  1531. }
  1532. JavascriptString * pThis = nullptr;
  1533. GetThisStringArgument(args, scriptContext, varName, &pThis);
  1534. return fallback(pThis);
  1535. }
  1536. Var JavascriptString::GetRegExSymbolFunction(Var regExp, PropertyId propertyId, ScriptContext* scriptContext)
  1537. {
  1538. return JavascriptOperators::GetPropertyNoCache(
  1539. JavascriptOperators::ToObject(regExp, scriptContext),
  1540. propertyId,
  1541. scriptContext);
  1542. }
  1543. template<int argCount>
  1544. Var JavascriptString::CallRegExSymbolFunction(Var fn, Var regExp, Arguments& args, PCWSTR const varName, ScriptContext* scriptContext)
  1545. {
  1546. if (!JavascriptConversion::IsCallable(fn))
  1547. {
  1548. JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_Invalid, varName);
  1549. }
  1550. RecyclableObject* fnObj = UnsafeVarTo<RecyclableObject>(fn);
  1551. return CallRegExFunction<argCount>(fnObj, regExp, args, scriptContext);
  1552. }
  1553. template<>
  1554. Var JavascriptString::CallRegExFunction<1>(RecyclableObject* fnObj, Var regExp, Arguments& args, ScriptContext *scriptContext)
  1555. {
  1556. // args[0]: String
  1557. ThreadContext * threadContext = scriptContext->GetThreadContext();
  1558. return threadContext->ExecuteImplicitCall(fnObj, ImplicitCall_Accessor, [=]()->Js::Var
  1559. {
  1560. return CALL_FUNCTION(threadContext, fnObj, CallInfo(CallFlags_Value, 2), regExp, args[0]);
  1561. });
  1562. }
  1563. template<>
  1564. Var JavascriptString::CallRegExFunction<2>(RecyclableObject* fnObj, Var regExp, Arguments& args, ScriptContext * scriptContext)
  1565. {
  1566. // args[0]: String
  1567. // args[1]: RegExp (ignored since we need to create one when the argument is "undefined")
  1568. // args[2]: Var
  1569. if (args.Info.Count < 3)
  1570. {
  1571. return CallRegExFunction<1>(fnObj, regExp, args, scriptContext);
  1572. }
  1573. ThreadContext * threadContext = scriptContext->GetThreadContext();
  1574. return threadContext->ExecuteImplicitCall(fnObj, ImplicitCall_Accessor, [=]()->Js::Var
  1575. {
  1576. return CALL_FUNCTION(threadContext, fnObj, CallInfo(CallFlags_Value, 3), regExp, args[0], args[2]);
  1577. });
  1578. }
  1579. Var JavascriptString::EntrySlice(RecyclableObject* function, CallInfo callInfo, ...)
  1580. {
  1581. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  1582. ARGUMENTS(args, callInfo);
  1583. ScriptContext* scriptContext = function->GetScriptContext();
  1584. Assert(!(callInfo.Flags & CallFlags_New));
  1585. JavascriptString * pThis = nullptr;
  1586. GetThisStringArgument(args, scriptContext, _u("String.prototype.slice"), &pThis);
  1587. int len = pThis->GetLength();
  1588. int idxStart = 0;
  1589. int idxEnd = len;
  1590. if (args.Info.Count > 1)
  1591. {
  1592. idxStart = JavascriptOperators::IsUndefinedObject(args[1]) ? 0 : ConvertToIndex(args[1], scriptContext);
  1593. if (args.Info.Count > 2)
  1594. {
  1595. idxEnd = JavascriptOperators::IsUndefinedObject(args[2]) ? len : ConvertToIndex(args[2], scriptContext);
  1596. }
  1597. }
  1598. if (idxStart < 0)
  1599. {
  1600. idxStart = max(len + idxStart, 0);
  1601. }
  1602. else if (idxStart > len)
  1603. {
  1604. idxStart = len;
  1605. }
  1606. if (idxEnd < 0)
  1607. {
  1608. idxEnd = max(len + idxEnd, 0);
  1609. }
  1610. else if (idxEnd > len )
  1611. {
  1612. idxEnd = len;
  1613. }
  1614. if (idxEnd < idxStart)
  1615. {
  1616. idxEnd = idxStart;
  1617. }
  1618. #ifdef ENABLE_SPECTRE_RUNTIME_MITIGATIONS
  1619. pThis = (JavascriptString*)BreakSpeculation(pThis);
  1620. #endif
  1621. return SubstringCore(pThis, idxStart, idxEnd - idxStart, scriptContext);
  1622. }
  1623. Var JavascriptString::EntrySplit(RecyclableObject* function, CallInfo callInfo, ...)
  1624. {
  1625. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  1626. ARGUMENTS(args, callInfo);
  1627. ScriptContext* scriptContext = function->GetScriptContext();
  1628. Assert(!(callInfo.Flags & CallFlags_New));
  1629. PCWSTR const varName = _u("String.prototype.split");
  1630. AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, varName);
  1631. auto fallback = [&](JavascriptString* stringObj)
  1632. {
  1633. return DoStringSplit(args, callInfo, stringObj, scriptContext);
  1634. };
  1635. return DelegateToRegExSymbolFunction<2>(args, PropertyIds::_symbolSplit, fallback, varName, scriptContext);
  1636. }
  1637. Var JavascriptString::DoStringSplit(Arguments& args, CallInfo& callInfo, JavascriptString* input, ScriptContext* scriptContext)
  1638. {
  1639. if (args.Info.Count == 1)
  1640. {
  1641. JavascriptArray* ary = scriptContext->GetLibrary()->CreateArray(1);
  1642. ary->DirectSetItemAt(0, input);
  1643. return ary;
  1644. }
  1645. else
  1646. {
  1647. uint32 limit;
  1648. if (args.Info.Count < 3 || JavascriptOperators::IsUndefinedObject(args[2]))
  1649. {
  1650. limit = UINT_MAX;
  1651. }
  1652. else
  1653. {
  1654. limit = JavascriptConversion::ToUInt32(args[2], scriptContext);
  1655. }
  1656. // When the config is enabled, the operation is handled by RegExp.prototype[@@split].
  1657. if (!scriptContext->GetConfig()->IsES6RegExSymbolsEnabled()
  1658. && VarIs<JavascriptRegExp>(args[1]))
  1659. {
  1660. return RegexHelper::RegexSplit(scriptContext, UnsafeVarTo<JavascriptRegExp>(args[1]), input, limit,
  1661. RegexHelper::IsResultNotUsed(callInfo.Flags));
  1662. }
  1663. else
  1664. {
  1665. JavascriptString* separator = JavascriptConversion::ToString(args[1], scriptContext);
  1666. if (callInfo.Flags & CallFlags_NotUsed)
  1667. {
  1668. return scriptContext->GetLibrary()->GetNull();
  1669. }
  1670. if (!limit)
  1671. {
  1672. JavascriptArray* ary = scriptContext->GetLibrary()->CreateArray(0);
  1673. return ary;
  1674. }
  1675. if (JavascriptOperators::GetTypeId(args[1]) == TypeIds_Undefined)
  1676. {
  1677. JavascriptArray* ary = scriptContext->GetLibrary()->CreateArray(1);
  1678. ary->DirectSetItemAt(0, input);
  1679. return ary;
  1680. }
  1681. return RegexHelper::StringSplit(separator, input, limit);
  1682. }
  1683. }
  1684. }
  1685. Var JavascriptString::EntrySubstring(RecyclableObject* function, CallInfo callInfo, ...)
  1686. {
  1687. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  1688. ARGUMENTS(args, callInfo);
  1689. ScriptContext* scriptContext = function->GetScriptContext();
  1690. Assert(!(callInfo.Flags & CallFlags_New));
  1691. JavascriptString * pThis = nullptr;
  1692. GetThisStringArgument(args, scriptContext, _u("String.prototype.substring"), &pThis);
  1693. int len = pThis->GetLength();
  1694. int idxStart = 0;
  1695. int idxEnd = len;
  1696. if (args.Info.Count > 1)
  1697. {
  1698. idxStart = JavascriptOperators::IsUndefinedObject(args[1]) ? 0 : ConvertToIndex(args[1], scriptContext);
  1699. if (args.Info.Count > 2)
  1700. {
  1701. idxEnd = JavascriptOperators::IsUndefinedObject(args[2]) ? len : ConvertToIndex(args[2], scriptContext);
  1702. }
  1703. }
  1704. idxStart = min(max(idxStart, 0), len);
  1705. idxEnd = min(max(idxEnd, 0), len);
  1706. if(idxEnd < idxStart)
  1707. {
  1708. //swap
  1709. idxStart ^= idxEnd;
  1710. idxEnd ^= idxStart;
  1711. idxStart ^= idxEnd;
  1712. }
  1713. if (idxStart == 0 && idxEnd == len)
  1714. {
  1715. //return the string if we need to substring entire span
  1716. return pThis;
  1717. }
  1718. #ifdef ENABLE_SPECTRE_RUNTIME_MITIGATIONS
  1719. pThis = (JavascriptString*)BreakSpeculation(pThis);
  1720. #endif
  1721. return SubstringCore(pThis, idxStart, idxEnd - idxStart, scriptContext);
  1722. }
  1723. Var JavascriptString::EntrySubstr(RecyclableObject* function, CallInfo callInfo, ...)
  1724. {
  1725. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  1726. ARGUMENTS(args, callInfo);
  1727. ScriptContext* scriptContext = function->GetScriptContext();
  1728. Assert(!(callInfo.Flags & CallFlags_New));
  1729. JavascriptString * pThis = nullptr;
  1730. GetThisStringArgument(args, scriptContext, _u("String.prototype.substr"), &pThis);
  1731. int len = pThis->GetLength();
  1732. int idxStart = 0;
  1733. int idxEnd = len;
  1734. if (args.Info.Count > 1)
  1735. {
  1736. idxStart = JavascriptOperators::IsUndefinedObject(args[1]) ? 0 : ConvertToIndex(args[1], scriptContext);
  1737. if (args.Info.Count > 2)
  1738. {
  1739. idxEnd = JavascriptOperators::IsUndefinedObject(args[2]) ? len : ConvertToIndex(args[2], scriptContext);
  1740. }
  1741. }
  1742. if (idxStart < 0)
  1743. {
  1744. idxStart = max(len + idxStart, 0);
  1745. }
  1746. else if (idxStart > len)
  1747. {
  1748. idxStart = len;
  1749. }
  1750. if (idxEnd < 0)
  1751. {
  1752. idxEnd = idxStart;
  1753. }
  1754. else if (idxEnd > len - idxStart)
  1755. {
  1756. idxEnd = len;
  1757. }
  1758. else
  1759. {
  1760. idxEnd += idxStart;
  1761. }
  1762. if (idxStart == 0 && idxEnd == len)
  1763. {
  1764. //return the string if we need to substr entire span
  1765. return pThis;
  1766. }
  1767. #ifdef ENABLE_SPECTRE_RUNTIME_MITIGATIONS
  1768. pThis = (JavascriptString*)BreakSpeculation(pThis);
  1769. #endif
  1770. Assert(0 <= idxStart && idxStart <= idxEnd && idxEnd <= len);
  1771. return SubstringCore(pThis, idxStart, idxEnd - idxStart, scriptContext);
  1772. }
  1773. Var JavascriptString::SubstringCore(JavascriptString* pThis, int idxStart, int span, ScriptContext* scriptContext)
  1774. {
  1775. return SubString::New(pThis, idxStart, span);
  1776. }
  1777. Var JavascriptString::EntryPadStart(RecyclableObject* function, CallInfo callInfo, ...)
  1778. {
  1779. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  1780. ARGUMENTS(args, callInfo);
  1781. ScriptContext* scriptContext = function->GetScriptContext();
  1782. Assert(!(callInfo.Flags & CallFlags_New));
  1783. CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(String_Prototype_padStart);
  1784. JavascriptString * pThis = nullptr;
  1785. GetThisStringArgument(args, scriptContext, _u("String.prototype.padStart"), &pThis);
  1786. return PadCore(args, pThis, true /*isPadStart*/, scriptContext);
  1787. }
  1788. Var JavascriptString::EntryPadEnd(RecyclableObject* function, CallInfo callInfo, ...)
  1789. {
  1790. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  1791. ARGUMENTS(args, callInfo);
  1792. ScriptContext* scriptContext = function->GetScriptContext();
  1793. Assert(!(callInfo.Flags & CallFlags_New));
  1794. CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(String_Prototype_padEnd);
  1795. JavascriptString * pThis = nullptr;
  1796. GetThisStringArgument(args, scriptContext, _u("String.prototype.padEnd"), &pThis);
  1797. return PadCore(args, pThis, false /*isPadStart*/, scriptContext);
  1798. }
  1799. JavascriptString* JavascriptString::PadCore(ArgumentReader& args, JavascriptString *mainString, bool isPadStart, ScriptContext* scriptContext)
  1800. {
  1801. Assert(mainString != nullptr);
  1802. Assert(args.Info.Count > 0);
  1803. if (args.Info.Count == 1)
  1804. {
  1805. return mainString;
  1806. }
  1807. int64 maxLength = JavascriptConversion::ToLength(args[1], scriptContext);
  1808. charcount_t currentLength = mainString->GetLength();
  1809. if (maxLength <= currentLength)
  1810. {
  1811. return mainString;
  1812. }
  1813. JavascriptString * fillerString = nullptr;
  1814. if (args.Info.Count > 2 && !JavascriptOperators::IsUndefinedObject(args[2]))
  1815. {
  1816. JavascriptString *argStr = JavascriptConversion::ToString(args[2], scriptContext);
  1817. if (argStr->GetLength() > 0)
  1818. {
  1819. fillerString = argStr;
  1820. }
  1821. else
  1822. {
  1823. return mainString;
  1824. }
  1825. }
  1826. if (maxLength > JavascriptString::MaxCharLength)
  1827. {
  1828. JavascriptError::ThrowRangeError(scriptContext, JSERR_OutOfBoundString);
  1829. }
  1830. if (fillerString == nullptr)
  1831. {
  1832. fillerString = NewWithBuffer(_u(" "), 1, scriptContext);
  1833. }
  1834. Assert(fillerString->GetLength() > 0);
  1835. charcount_t fillLength = (charcount_t)(maxLength - currentLength);
  1836. charcount_t count = fillLength / fillerString->GetLength();
  1837. JavascriptString * finalPad = scriptContext->GetLibrary()->GetEmptyString();
  1838. if (count > 0)
  1839. {
  1840. finalPad = RepeatCore(fillerString, count, scriptContext);
  1841. fillLength -= (count * fillerString->GetLength());
  1842. }
  1843. if (fillLength > 0)
  1844. {
  1845. finalPad = Concat(finalPad, SubString::New(fillerString, 0, fillLength));
  1846. }
  1847. return isPadStart ? Concat(finalPad, mainString) : Concat(mainString, finalPad);
  1848. }
  1849. Var JavascriptString::EntryToLocaleLowerCase(RecyclableObject* function, CallInfo callInfo, ...)
  1850. {
  1851. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  1852. ARGUMENTS(args, callInfo);
  1853. ScriptContext* scriptContext = function->GetScriptContext();
  1854. Assert(!(callInfo.Flags & CallFlags_New));
  1855. JavascriptString * pThis = nullptr;
  1856. GetThisStringArgument(args, scriptContext, _u("String.prototype.toLocaleLowerCase"), &pThis);
  1857. return ToLocaleCaseHelper<false /* toUpper */>(pThis);
  1858. }
  1859. Var JavascriptString::EntryToLocaleUpperCase(RecyclableObject* function, CallInfo callInfo, ...)
  1860. {
  1861. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  1862. ARGUMENTS(args, callInfo);
  1863. ScriptContext* scriptContext = function->GetScriptContext();
  1864. Assert(!(callInfo.Flags & CallFlags_New));
  1865. JavascriptString * pThis = nullptr;
  1866. GetThisStringArgument(args, scriptContext, _u("String.prototype.toLocaleUpperCase"), &pThis);
  1867. return ToLocaleCaseHelper<true /* toUpper */>(pThis);
  1868. }
  1869. template<bool toUpper>
  1870. JavascriptString* JavascriptString::ToLocaleCaseHelper(JavascriptString* pThis)
  1871. {
  1872. // TODO: implement locale-sensitive Intl versions of these functions
  1873. return ToCaseCore<toUpper, false>(pThis);
  1874. }
  1875. Var JavascriptString::EntryToLowerCase(RecyclableObject* function, CallInfo callInfo, ...)
  1876. {
  1877. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  1878. ARGUMENTS(args, callInfo);
  1879. ScriptContext* scriptContext = function->GetScriptContext();
  1880. Assert(!(callInfo.Flags & CallFlags_New));
  1881. JavascriptString * pThis = nullptr;
  1882. GetThisStringArgument(args, scriptContext, _u("String.prototype.toLowerCase"), &pThis);
  1883. return ToCaseCore<false, true>(pThis);
  1884. }
  1885. Var JavascriptString::EntryToString(RecyclableObject* function, CallInfo callInfo, ...)
  1886. {
  1887. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  1888. ARGUMENTS(args, callInfo);
  1889. ScriptContext* scriptContext = function->GetScriptContext();
  1890. Assert(!(callInfo.Flags & CallFlags_New));
  1891. if(args.Info.Count == 0)
  1892. {
  1893. JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedString, _u("String.prototype.toString"));
  1894. }
  1895. AssertMsg(args.Info.Count > 0, "Negative argument count");
  1896. JavascriptString* str = nullptr;
  1897. if (!GetThisValueVar(args[0], &str, scriptContext))
  1898. {
  1899. if (JavascriptOperators::GetTypeId(args[0]) == TypeIds_HostDispatch)
  1900. {
  1901. Var result;
  1902. if (UnsafeVarTo<RecyclableObject>(args[0])->InvokeBuiltInOperationRemotely(EntryToString, args, &result))
  1903. {
  1904. return result;
  1905. }
  1906. }
  1907. JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedString, _u("String.prototype.toString"));
  1908. }
  1909. return str;
  1910. }
  1911. Var JavascriptString::EntryToUpperCase(RecyclableObject* function, CallInfo callInfo, ...)
  1912. {
  1913. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  1914. ARGUMENTS(args, callInfo);
  1915. ScriptContext* scriptContext = function->GetScriptContext();
  1916. Assert(!(callInfo.Flags & CallFlags_New));
  1917. JavascriptString* pThis = nullptr;
  1918. GetThisStringArgument(args, scriptContext, _u("String.prototype.toUpperCase"), &pThis);
  1919. return ToCaseCore<true, true>(pThis);
  1920. }
  1921. template<bool toUpper, bool useInvariant>
  1922. JavascriptString* JavascriptString::ToCaseCore(JavascriptString* pThis)
  1923. {
  1924. using namespace PlatformAgnostic::UnicodeText;
  1925. if (pThis->GetLength() == 0)
  1926. {
  1927. return pThis;
  1928. }
  1929. ScriptContext* scriptContext = pThis->type->GetScriptContext();
  1930. ApiError error = ApiError::NoError;
  1931. charcount_t pThisLength = pThis->GetLength();
  1932. if (useInvariant)
  1933. {
  1934. const char16 *pThisString = pThis->GetString();
  1935. bool isAscii = true;
  1936. for (charcount_t i = 0; i < pThisLength; i++)
  1937. {
  1938. if (pThisString[i] >= 0x80)
  1939. {
  1940. isAscii = false;
  1941. break;
  1942. }
  1943. }
  1944. if (isAscii)
  1945. {
  1946. char16 *ret = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, UInt32Math::Add(pThisLength, 1));
  1947. const char16 diffBetweenCases = 32;
  1948. for (charcount_t i = 0; i < pThisLength; i++)
  1949. {
  1950. char16 cur = pThisString[i];
  1951. if (toUpper)
  1952. {
  1953. if (cur >= _u('a') && cur <= _u('z'))
  1954. {
  1955. ret[i] = cur - diffBetweenCases;
  1956. }
  1957. else
  1958. {
  1959. ret[i] = cur;
  1960. }
  1961. }
  1962. else
  1963. {
  1964. if (cur >= _u('A') && cur <= _u('Z'))
  1965. {
  1966. ret[i] = cur + diffBetweenCases;
  1967. }
  1968. else
  1969. {
  1970. ret[i] = cur;
  1971. }
  1972. }
  1973. }
  1974. ret[pThisLength] = 0;
  1975. return JavascriptString::NewWithBuffer(ret, pThisLength, scriptContext);
  1976. }
  1977. }
  1978. // pre-flight to get the length required, as it may be longer than the original string
  1979. // ICU and Win32(/POSIX) implementations of these functions differ slightly in how to get the required number of characters.
  1980. // For Win32 (LCMapStringEx), you must provide nullptr/0, as providing a buffer that is too small will cause an error and will *not*
  1981. // report the number of characters required. For ICU, however, you can provide a buffer that is too short, and it will still return
  1982. // the length it actually needs.
  1983. //
  1984. // This is a small performance optimization because to(Upper|Lower)Case is can show up hot in certain scenarios.
  1985. // ICU still allows nullptr/0 to be passed to get the string length, and more conservative callers of ChangeStringLinguisticCase should do just that.
  1986. // TODO(jahorto): A truly PlatformAgnostic API wouldn't require cases like this. Once PlatformAgnostic is allowed to use
  1987. // Chakra's memory subsystems, this API should be converted to one that only takes a source string and returns a Recycler-allocated
  1988. // string in the correct case, performed using whatever operation is the fastest available on that platform.
  1989. #ifdef INTL_ICU
  1990. charcount_t guessBufferLength = UInt32Math::Add(pThisLength, 1);
  1991. char16 *guessBuffer = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, guessBufferLength);
  1992. #else
  1993. charcount_t guessBufferLength = 0;
  1994. char16 *guessBuffer = nullptr;
  1995. #endif
  1996. charcount_t requiredStringLength = ChangeStringLinguisticCase<toUpper, useInvariant>(pThis->GetSz(), pThis->GetLength(), guessBuffer, guessBufferLength, &error);
  1997. if (error == ApiError::OutOfMemory)
  1998. {
  1999. Throw::OutOfMemory();
  2000. }
  2001. // We exit ToCaseCore early if the source string is 0-length, and casing a non-zero length string should
  2002. // never result in a zero-length string.
  2003. AssertOrFailFast(requiredStringLength > 0 && IsValidCharCount(requiredStringLength));
  2004. #ifdef INTL_ICU
  2005. if (error == ApiError::NoError)
  2006. {
  2007. if (requiredStringLength == 1)
  2008. {
  2009. // don't create a new string in case we may have cached this string earlier
  2010. return scriptContext->GetLibrary()->GetCharStringCache().GetStringForChar(guessBuffer[0]);
  2011. }
  2012. else
  2013. {
  2014. // use requiredStringLength instead of guessBufferLength because the string can get shorter
  2015. return JavascriptString::NewWithBuffer(guessBuffer, requiredStringLength, scriptContext);
  2016. }
  2017. }
  2018. AssertOrFailFast(error == ApiError::InsufficientBuffer);
  2019. #else
  2020. AssertOrFailFast(error == ApiError::NoError);
  2021. if (requiredStringLength == 1)
  2022. {
  2023. // this one-char string special case is only for non-ICU because there should never be a case where the error
  2024. // was InsufficientBufer but the required length was 1
  2025. char16 buffer[2] = { pThis->GetSz()[0], 0 };
  2026. charcount_t actualStringLength = ChangeStringLinguisticCase<toUpper, useInvariant>(pThis->GetSz(), pThis->GetLength(), buffer, 2, &error);
  2027. AssertOrFailFast(actualStringLength == 1 && error == ApiError::NoError);
  2028. return scriptContext->GetLibrary()->GetCharStringCache().GetStringForChar(buffer[0]);
  2029. }
  2030. #endif
  2031. AssertOrFailFast(requiredStringLength > 1);
  2032. charcount_t bufferLength = UInt32Math::Add(requiredStringLength, 1);
  2033. char16* buffer = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, bufferLength);
  2034. charcount_t actualStringLength = ChangeStringLinguisticCase<toUpper, useInvariant>(pThis->GetSz(), pThis->GetLength(), buffer, bufferLength, &error);
  2035. AssertOrFailFast(actualStringLength == requiredStringLength && error == ApiError::NoError);
  2036. return JavascriptString::NewWithBuffer(buffer, actualStringLength, scriptContext);
  2037. }
  2038. Var JavascriptString::EntryTrim(RecyclableObject* function, CallInfo callInfo, ...)
  2039. {
  2040. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  2041. ARGUMENTS(args, callInfo);
  2042. ScriptContext* scriptContext = function->GetScriptContext();
  2043. CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(String_Prototype_trim);
  2044. Assert(!(callInfo.Flags & CallFlags_New));
  2045. //15.5.4.20 The following steps are taken:
  2046. //1. Call CheckObjectCoercible passing the this value as its argument.
  2047. //2. Let S be the result of calling ToString, giving it the this value as its argument.
  2048. //3. Let T be a string value that is a copy of S with both leading and trailing white space removed. The definition of white space is the union of WhiteSpace and LineTerminator.
  2049. //4. Return T.
  2050. JavascriptString* pThis = nullptr;
  2051. GetThisStringArgument(args, scriptContext, _u("String.prototype.trim"), &pThis);
  2052. return TrimLeftRightHelper<true /*trimLeft*/, true /*trimRight*/>(pThis, scriptContext);
  2053. }
  2054. Var JavascriptString::EntryTrimStart(RecyclableObject* function, CallInfo callInfo, ...)
  2055. {
  2056. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  2057. ARGUMENTS(args, callInfo);
  2058. ScriptContext* scriptContext = function->GetScriptContext();
  2059. Assert(!(callInfo.Flags & CallFlags_New));
  2060. // 1.Let O be CheckObjectCoercible(this value) .
  2061. // 2.Let S be ToString(O) .
  2062. // 3.ReturnIfAbrupt(S).
  2063. // 4.Let T be a String value that is a copy of S with leading white space removed. The definition of white space is the union of WhiteSpace and )LineTerminator.
  2064. // When determining whether a Unicode code point is in Unicode general category "Zs", code unit sequences are interpreted as UTF-16 encoded code point sequences as specified in 6.1.4.
  2065. // 5.Return T.
  2066. JavascriptString* pThis = nullptr;
  2067. GetThisStringArgument(args, scriptContext, _u("String.prototype.trimLeft"), &pThis);
  2068. return TrimLeftRightHelper< true /*trimLeft*/, false /*trimRight*/>(pThis, scriptContext);
  2069. }
  2070. Var JavascriptString::EntryTrimEnd(RecyclableObject* function, CallInfo callInfo, ...)
  2071. {
  2072. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  2073. ARGUMENTS(args, callInfo);
  2074. ScriptContext* scriptContext = function->GetScriptContext();
  2075. Assert(!(callInfo.Flags & CallFlags_New));
  2076. // 1.Let O be CheckObjectCoercible(this value) .
  2077. // 2.Let S be ToString(O) .
  2078. // 3.ReturnIfAbrupt(S).
  2079. // 4.Let T be a String value that is a copy of S with trailing white space removed.The definition of white space is the union of WhiteSpace and )LineTerminator.
  2080. // When determining whether a Unicode code point is in Unicode general category "Zs", code unit sequences are interpreted as UTF - 16 encoded code point sequences as specified in 6.1.4.
  2081. // 5.Return T.
  2082. JavascriptString* pThis = nullptr;
  2083. GetThisStringArgument(args, scriptContext, _u("String.prototype.trimRight"), &pThis);
  2084. return TrimLeftRightHelper<false /*trimLeft*/, true /*trimRight*/>(pThis, scriptContext);
  2085. }
  2086. template <bool trimLeft, bool trimRight>
  2087. Var JavascriptString::TrimLeftRightHelper(JavascriptString* arg, ScriptContext* scriptContext)
  2088. {
  2089. static_assert(trimLeft || trimRight, "bad template instance of TrimLeftRightHelper()");
  2090. int len = arg->GetLength();
  2091. const char16 *string = arg->GetString();
  2092. int idxStart = 0;
  2093. if (trimLeft)
  2094. {
  2095. for (; idxStart < len; idxStart++)
  2096. {
  2097. char16 ch = string[idxStart];
  2098. if (IsWhiteSpaceCharacter(ch))
  2099. {
  2100. continue;
  2101. }
  2102. break;
  2103. }
  2104. if (len == idxStart)
  2105. {
  2106. return (scriptContext->GetLibrary()->GetEmptyString());
  2107. }
  2108. }
  2109. int idxEnd = len - 1;
  2110. if (trimRight)
  2111. {
  2112. for (; idxEnd >= 0; idxEnd--)
  2113. {
  2114. char16 ch = string[idxEnd];
  2115. if (IsWhiteSpaceCharacter(ch))
  2116. {
  2117. continue;
  2118. }
  2119. break;
  2120. }
  2121. if (!trimLeft)
  2122. {
  2123. if (idxEnd < 0)
  2124. {
  2125. Assert(idxEnd == -1);
  2126. return (scriptContext->GetLibrary()->GetEmptyString());
  2127. }
  2128. }
  2129. else
  2130. {
  2131. Assert(idxEnd >= 0);
  2132. }
  2133. }
  2134. if (idxStart == 0 && idxEnd == len - 1)
  2135. {
  2136. AssertMsg(scriptContext == arg->GetScriptContext(), "Should have already marshaled the string in cross site thunk");
  2137. return arg;
  2138. }
  2139. return SubstringCore(arg, idxStart, idxEnd - idxStart + 1, scriptContext);
  2140. }
  2141. ///----------------------------------------------------------------------------
  2142. /// Repeat() returns a new string equal to the toString(this) repeated n times,
  2143. /// as described in (ES6.0: S21.1.3.13).
  2144. ///----------------------------------------------------------------------------
  2145. Var JavascriptString::EntryRepeat(RecyclableObject* function, CallInfo callInfo, ...)
  2146. {
  2147. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  2148. ARGUMENTS(args, callInfo);
  2149. AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
  2150. ScriptContext* scriptContext = function->GetScriptContext();
  2151. Assert(!(callInfo.Flags & CallFlags_New));
  2152. CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(String_Prototype_repeat);
  2153. JavascriptString* pThis = nullptr;
  2154. GetThisStringArgument(args, scriptContext, _u("String.prototype.repeat"), &pThis);
  2155. charcount_t count = 0;
  2156. if (args.Info.Count > 1)
  2157. {
  2158. if (!JavascriptOperators::IsUndefinedObject(args[1]))
  2159. {
  2160. double countDbl = JavascriptConversion::ToInteger(args[1], scriptContext);
  2161. if (JavascriptNumber::IsPosInf(countDbl) || countDbl < 0.0)
  2162. {
  2163. JavascriptError::ThrowRangeError(scriptContext, JSERR_ArgumentOutOfRange, _u("String.prototype.repeat"));
  2164. }
  2165. count = NumberUtilities::LuFromDblNearest(countDbl);
  2166. }
  2167. }
  2168. if (count == 0 || pThis->GetLength() == 0)
  2169. {
  2170. return scriptContext->GetLibrary()->GetEmptyString();
  2171. }
  2172. else if (count == 1)
  2173. {
  2174. return pThis;
  2175. }
  2176. return RepeatCore(pThis, count, scriptContext);
  2177. }
  2178. JavascriptString* JavascriptString::RepeatCore(JavascriptString* currentString, charcount_t count, ScriptContext* scriptContext)
  2179. {
  2180. Assert(currentString != nullptr);
  2181. Assert(currentString->GetLength() > 0);
  2182. Assert(count > 0);
  2183. const char16* currentRawString = currentString->GetString();
  2184. charcount_t currentLength = currentString->GetLength();
  2185. charcount_t finalBufferCount = UInt32Math::Add(UInt32Math::Mul(count, currentLength), 1);
  2186. char16* buffer = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, finalBufferCount);
  2187. if (currentLength == 1)
  2188. {
  2189. wmemset(buffer, currentRawString[0], finalBufferCount - 1);
  2190. buffer[finalBufferCount - 1] = '\0';
  2191. }
  2192. else
  2193. {
  2194. char16* bufferDst = buffer;
  2195. size_t bufferDstSize = finalBufferCount;
  2196. AnalysisAssert(bufferDstSize > currentLength);
  2197. for (charcount_t i = 0; i < count; i += 1)
  2198. {
  2199. js_wmemcpy_s(bufferDst, bufferDstSize, currentRawString, currentLength);
  2200. bufferDst += currentLength;
  2201. bufferDstSize -= currentLength;
  2202. }
  2203. Assert(bufferDstSize == 1);
  2204. *bufferDst = '\0';
  2205. }
  2206. return JavascriptString::NewWithBuffer(buffer, finalBufferCount - 1, scriptContext);
  2207. }
  2208. ///----------------------------------------------------------------------------
  2209. /// StartsWith() returns true if the given string matches the beginning of the
  2210. /// substring starting at the given position in toString(this), as described
  2211. /// in (ES6.0: S21.1.3.18).
  2212. ///----------------------------------------------------------------------------
  2213. Var JavascriptString::EntryStartsWith(RecyclableObject* function, CallInfo callInfo, ...)
  2214. {
  2215. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  2216. ARGUMENTS(args, callInfo);
  2217. AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
  2218. ScriptContext* scriptContext = function->GetScriptContext();
  2219. Assert(!(callInfo.Flags & CallFlags_New));
  2220. CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(String_Prototype_startsWith);
  2221. ENTER_PINNED_SCOPE(JavascriptString, pThis);
  2222. ENTER_PINNED_SCOPE(JavascriptString, pSearch);
  2223. GetThisAndSearchStringArguments(args, scriptContext, _u("String.prototype.startsWith"), &pThis, &pSearch, false);
  2224. const char16* thisStr = pThis->GetString();
  2225. int thisStrLen = pThis->GetLength();
  2226. const char16* searchStr = pSearch->GetString();
  2227. int searchStrLen = pSearch->GetLength();
  2228. int startPosition = 0;
  2229. if (args.Info.Count > 2)
  2230. {
  2231. if (!JavascriptOperators::IsUndefinedObject(args[2]))
  2232. {
  2233. startPosition = ConvertToIndex(args[2], scriptContext); // this is to adjust corner cases like MAX_VALUE
  2234. startPosition = min(max(startPosition, 0), thisStrLen);
  2235. }
  2236. }
  2237. // Avoid signed 32-bit int overflow if startPosition is large by subtracting searchStrLen from thisStrLen instead of
  2238. // adding searchStrLen and startPosition. The subtraction cannot underflow because maximum string length is
  2239. // MaxCharCount == INT_MAX-1. I.e. the RHS can be == 0 - (INT_MAX-1) == 1 - INT_MAX which would not underflow.
  2240. if (startPosition <= thisStrLen - searchStrLen)
  2241. {
  2242. Assert(searchStrLen <= thisStrLen - startPosition);
  2243. if (wmemcmp(thisStr + startPosition, searchStr, searchStrLen) == 0)
  2244. {
  2245. return scriptContext->GetLibrary()->GetTrue();
  2246. }
  2247. }
  2248. LEAVE_PINNED_SCOPE(); // pSearch
  2249. LEAVE_PINNED_SCOPE(); // pThis
  2250. return scriptContext->GetLibrary()->GetFalse();
  2251. }
  2252. ///----------------------------------------------------------------------------
  2253. /// EndsWith() returns true if the given string matches the end of the
  2254. /// substring ending at the given position in toString(this), as described
  2255. /// in (ES6.0: S21.1.3.7).
  2256. ///----------------------------------------------------------------------------
  2257. Var JavascriptString::EntryEndsWith(RecyclableObject* function, CallInfo callInfo, ...)
  2258. {
  2259. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  2260. ARGUMENTS(args, callInfo);
  2261. AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
  2262. ScriptContext* scriptContext = function->GetScriptContext();
  2263. Assert(!(callInfo.Flags & CallFlags_New));
  2264. CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(String_Prototype_endsWith);
  2265. ENTER_PINNED_SCOPE(JavascriptString, pThis);
  2266. ENTER_PINNED_SCOPE(JavascriptString, pSearch);
  2267. GetThisAndSearchStringArguments(args, scriptContext, _u("String.prototype.endsWith"), &pThis, &pSearch, false);
  2268. const char16* thisStr = pThis->GetString();
  2269. int thisStrLen = pThis->GetLength();
  2270. const char16* searchStr = pSearch->GetString();
  2271. int searchStrLen = pSearch->GetLength();
  2272. int endPosition = thisStrLen;
  2273. if (args.Info.Count > 2)
  2274. {
  2275. if (!JavascriptOperators::IsUndefinedObject(args[2]))
  2276. {
  2277. endPosition = ConvertToIndex(args[2], scriptContext); // this is to adjust corner cases like MAX_VALUE
  2278. endPosition = min(max(endPosition, 0), thisStrLen);
  2279. }
  2280. }
  2281. int startPosition = endPosition - searchStrLen;
  2282. if (startPosition >= 0)
  2283. {
  2284. Assert(startPosition <= thisStrLen);
  2285. Assert(searchStrLen <= thisStrLen - startPosition);
  2286. if (wmemcmp(thisStr + startPosition, searchStr, searchStrLen) == 0)
  2287. {
  2288. return scriptContext->GetLibrary()->GetTrue();
  2289. }
  2290. }
  2291. LEAVE_PINNED_SCOPE(); // pSearch
  2292. LEAVE_PINNED_SCOPE(); // pThis
  2293. return scriptContext->GetLibrary()->GetFalse();
  2294. }
  2295. ///----------------------------------------------------------------------------
  2296. /// Includes() returns true if the given string matches any substring (of the
  2297. /// same length) of the substring starting at the given position in
  2298. /// toString(this), as described in (ES6.0 (draft 33): S21.1.3.7).
  2299. ///----------------------------------------------------------------------------
  2300. Var JavascriptString::EntryIncludes(RecyclableObject* function, CallInfo callInfo, ...)
  2301. {
  2302. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  2303. ARGUMENTS(args, callInfo);
  2304. AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
  2305. ScriptContext* scriptContext = function->GetScriptContext();
  2306. Assert(!(callInfo.Flags & CallFlags_New));
  2307. CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(String_Prototype_contains);
  2308. return JavascriptBoolean::ToVar(IndexOf(args, scriptContext, _u("String.prototype.includes"), false) != -1, scriptContext);
  2309. }
  2310. Var JavascriptString::EntryValueOf(RecyclableObject* function, CallInfo callInfo, ...)
  2311. {
  2312. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  2313. ARGUMENTS(args, callInfo);
  2314. ScriptContext* scriptContext = function->GetScriptContext();
  2315. Assert(!(callInfo.Flags & CallFlags_New));
  2316. if(args.Info.Count == 0)
  2317. {
  2318. JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedString, _u("String.prototype.valueOf"));
  2319. }
  2320. AssertMsg(args.Info.Count > 0, "Negative argument count");
  2321. JavascriptString* str = nullptr;
  2322. if (!GetThisValueVar(args[0], &str, scriptContext))
  2323. {
  2324. if (JavascriptOperators::GetTypeId(args[0]) == TypeIds_HostDispatch)
  2325. {
  2326. Var result;
  2327. if (UnsafeVarTo<RecyclableObject>(args[0])->InvokeBuiltInOperationRemotely(EntryValueOf, args, &result))
  2328. {
  2329. return result;
  2330. }
  2331. }
  2332. JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedString, _u("String.prototype.valueOf"));
  2333. }
  2334. return str;
  2335. }
  2336. Var JavascriptString::EntrySymbolIterator(RecyclableObject* function, CallInfo callInfo, ...)
  2337. {
  2338. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  2339. ARGUMENTS(args, callInfo);
  2340. ScriptContext* scriptContext = function->GetScriptContext();
  2341. Assert(!(callInfo.Flags & CallFlags_New));
  2342. if (args.Info.Count == 0)
  2343. {
  2344. JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedString, _u("String.prototype[Symbol.iterator]"));
  2345. }
  2346. AssertMsg(args.Info.Count > 0, "Negative argument count");
  2347. if (!JavascriptConversion::CheckObjectCoercible(args[0], scriptContext))
  2348. {
  2349. JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, _u("String.prototype[Symbol.iterator]"));
  2350. }
  2351. JavascriptString* str = JavascriptConversion::ToString(args[0], scriptContext);
  2352. return scriptContext->GetLibrary()->CreateStringIterator(str);
  2353. }
  2354. const char16 * JavascriptString::GetSz()
  2355. {
  2356. Assert(m_pszValue[m_charLength] == _u('\0'));
  2357. return m_pszValue;
  2358. }
  2359. const char16 * JavascriptString::GetString()
  2360. {
  2361. if (!this->IsFinalized())
  2362. {
  2363. this->GetSz();
  2364. Assert(m_pszValue);
  2365. }
  2366. return m_pszValue;
  2367. }
  2368. void const * JavascriptString::GetOriginalStringReference()
  2369. {
  2370. // Just return the string buffer
  2371. return GetString();
  2372. }
  2373. size_t JavascriptString::GetAllocatedByteCount() const
  2374. {
  2375. if (!this->IsFinalized())
  2376. {
  2377. return 0;
  2378. }
  2379. return this->m_charLength * sizeof(WCHAR);
  2380. }
  2381. bool JavascriptString::IsSubstring() const
  2382. {
  2383. return false;
  2384. }
  2385. bool JavascriptString::IsNegZero(JavascriptString *string)
  2386. {
  2387. return string->GetLength() == 2 && wmemcmp(string->GetString(), _u("-0"), 2) == 0;
  2388. }
  2389. void JavascriptString::FinishCopy(__inout_xcount(m_charLength) char16 *const buffer, StringCopyInfoStack &nestedStringTreeCopyInfos)
  2390. {
  2391. while (!nestedStringTreeCopyInfos.IsEmpty())
  2392. {
  2393. const StringCopyInfo copyInfo(nestedStringTreeCopyInfos.Pop());
  2394. Assert(copyInfo.SourceString()->GetLength() <= GetLength());
  2395. Assert(copyInfo.DestinationBuffer() >= buffer);
  2396. Assert(copyInfo.DestinationBuffer() <= buffer + (GetLength() - copyInfo.SourceString()->GetLength()));
  2397. copyInfo.SourceString()->Copy(copyInfo.DestinationBuffer(), nestedStringTreeCopyInfos, 0);
  2398. }
  2399. }
  2400. void JavascriptString::CopyVirtual(
  2401. _Out_writes_(m_charLength) char16 *const buffer,
  2402. StringCopyInfoStack &nestedStringTreeCopyInfos,
  2403. const byte recursionDepth)
  2404. {
  2405. Assert(buffer);
  2406. Assert(!this->IsFinalized()); // CopyVirtual should only be called for unfinalized buffers
  2407. CopyHelper(buffer, GetString(), GetLength());
  2408. }
  2409. char16* JavascriptString::GetSzCopy()
  2410. {
  2411. return AllocateLeafAndCopySz(this->GetScriptContext()->GetRecycler(), GetString(), GetLength());
  2412. }
  2413. LPCWSTR JavascriptString::GetSzCopy(ArenaAllocator* alloc)
  2414. {
  2415. return AllocateAndCopySz(alloc, GetString(), GetLength());
  2416. }
  2417. /*
  2418. Table generated using the following:
  2419. var invalidValue = 37;
  2420. function toStringTable()
  2421. {
  2422. var stringTable = new Array(128);
  2423. for(var i = 0; i < 128; i++)
  2424. {
  2425. var ch = i;
  2426. if ('0'.charCodeAt(0) <= ch && '9'.charCodeAt(0) >= ch)
  2427. ch -= '0'.charCodeAt(0);
  2428. else if ('A'.charCodeAt(0) <= ch && 'Z'.charCodeAt(0) >= ch)
  2429. ch -= 'A'.charCodeAt(0) - 10;
  2430. else if ('a'.charCodeAt(0) <= ch && 'z'.charCodeAt(0) >= ch)
  2431. ch -= 'a'.charCodeAt(0) - 10;
  2432. else
  2433. ch = 37;
  2434. stringTable[i] = ch;
  2435. }
  2436. WScript.Echo("{" + stringTable + "}");
  2437. }
  2438. toStringTable();*/
  2439. const char JavascriptString::stringToIntegerMap[] = {
  2440. 37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37
  2441. ,37,37,37,37,37,37,37,37,0,1,2,3,4,5,6,7,8,9,37,37,37,37,37,37,37,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,
  2442. 28,29,30,31,32,33,34,35,37,37,37,37,37,37,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,
  2443. 37,37,37,37,37};
  2444. /*
  2445. Table generated using the following:
  2446. function logMaxUintTable()
  2447. {
  2448. var MAX_UINT = 4294967295;
  2449. var logTable = new Array(37);
  2450. logTable[0] = 0;
  2451. logTable[1] = 0;
  2452. for(var i = 2; i < logTable.length; i++)
  2453. {
  2454. logTable[i] = Math.floor(Math.log(MAX_UINT + 1) / Math.log(i));
  2455. }
  2456. WScript.Echo("{" + logTable + "}");
  2457. }
  2458. logMaxUintTable();
  2459. */
  2460. const uint8 JavascriptString::maxUintStringLengthTable[] =
  2461. { 0,0,32,20,16,13,12,11,10,10,9,9,8,8,8,8,8,7,7,7,7,7,7,7,6,6,6,6,6,6,6,6,6,6,6,6,6 };
  2462. // NumberUtil::FIntRadStrToDbl and parts of GlobalObject::EntryParseInt were refactored into ToInteger
  2463. Var JavascriptString::ToInteger(int radix)
  2464. {
  2465. AssertMsg(radix == 0 || radix >= 2 && radix <= 36, "'radix' is invalid");
  2466. const char16* pchStart = GetString();
  2467. const char16* pchEnd = pchStart + m_charLength;
  2468. const char16 *pch = this->GetScriptContext()->GetCharClassifier()->SkipWhiteSpace(pchStart, pchEnd);
  2469. bool isNegative = false;
  2470. if (pch < pchEnd)
  2471. {
  2472. switch (*pch)
  2473. {
  2474. case '-':
  2475. isNegative = true;
  2476. // Fall through.
  2477. case '+':
  2478. pch++;
  2479. break;
  2480. }
  2481. }
  2482. if (0 == radix)
  2483. {
  2484. if (pch < pchEnd && '0' != pch[0])
  2485. {
  2486. radix = 10;
  2487. }
  2488. else if (pchEnd - pch >= 2 && ('x' == pch[1] || 'X' == pch[1]))
  2489. {
  2490. radix = 16;
  2491. pch += 2;
  2492. }
  2493. else
  2494. {
  2495. // ES5's 'parseInt' does not allow treating a string beginning with a '0' as an octal value. ES3 does not specify a
  2496. // behavior
  2497. radix = 10;
  2498. }
  2499. }
  2500. else if (16 == radix)
  2501. {
  2502. if(pchEnd - pch >= 2 && '0' == pch[0] && ('x' == pch[1] || 'X' == pch[1]))
  2503. {
  2504. pch += 2;
  2505. }
  2506. }
  2507. Assert(radix <= _countof(maxUintStringLengthTable));
  2508. Assert(pchEnd >= pch);
  2509. size_t length = pchEnd - pch;
  2510. const char16 *const pchMin = pch;
  2511. __analysis_assume(radix < _countof(maxUintStringLengthTable));
  2512. if(length <= maxUintStringLengthTable[radix])
  2513. {
  2514. // Use uint32 as integer being parsed - much faster than BigInt
  2515. uint32 value = 0;
  2516. for ( ; pch < pchEnd ; pch++)
  2517. {
  2518. char16 ch = *pch;
  2519. if(ch >= _countof(stringToIntegerMap) || (ch = stringToIntegerMap[ch]) >= radix)
  2520. {
  2521. break;
  2522. }
  2523. uint32 beforeValue = value;
  2524. value = value * radix + ch;
  2525. AssertMsg(value >= beforeValue, "uint overflow");
  2526. }
  2527. if(pchMin == pch)
  2528. {
  2529. return GetScriptContext()->GetLibrary()->GetNaN();
  2530. }
  2531. if(isNegative)
  2532. {
  2533. // negative zero can only be represented by doubles
  2534. if(value <= INT_MAX && value != 0)
  2535. {
  2536. int32 result = -((int32)value);
  2537. return JavascriptNumber::ToVar(result, this->GetScriptContext());
  2538. }
  2539. double result = -((double)(value));
  2540. return JavascriptNumber::New(result, this->GetScriptContext());
  2541. }
  2542. return JavascriptNumber::ToVar(value, this->GetScriptContext());
  2543. }
  2544. BigUInt bi;
  2545. for ( ; pch < pchEnd ; pch++)
  2546. {
  2547. char16 ch = *pch;
  2548. if(ch >= _countof(stringToIntegerMap) || (ch = stringToIntegerMap[ch]) >= radix)
  2549. {
  2550. break;
  2551. }
  2552. if (!bi.FMulAdd(radix, ch))
  2553. {
  2554. //Mimic IE8 which threw an OutOfMemory exception in this case.
  2555. JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
  2556. }
  2557. // If we ever have more than 32 ulongs, the result must be infinite.
  2558. if (bi.Clu() > 32)
  2559. {
  2560. Var result = isNegative ?
  2561. GetScriptContext()->GetLibrary()->GetNegativeInfinite() :
  2562. GetScriptContext()->GetLibrary()->GetPositiveInfinite();
  2563. return result;
  2564. }
  2565. }
  2566. if (pchMin == pch)
  2567. {
  2568. return GetScriptContext()->GetLibrary()->GetNaN();
  2569. }
  2570. // Convert to a double.
  2571. double result = bi.GetDbl();
  2572. if(isNegative)
  2573. {
  2574. result = -result;
  2575. }
  2576. return Js::JavascriptNumber::ToVarIntCheck(result, GetScriptContext());
  2577. }
  2578. bool JavascriptString::ToDouble(double * result)
  2579. {
  2580. const char16* pch;
  2581. int32 len = this->m_charLength;
  2582. if (0 == len)
  2583. {
  2584. *result = 0;
  2585. return true;
  2586. }
  2587. if (1 == len && NumberUtilities::IsDigit(this->GetString()[0]))
  2588. {
  2589. *result = (double)(this->GetString()[0] - '0');
  2590. return true;
  2591. }
  2592. // TODO: Use GetString here instead of GetSz (need to modify DblFromHex and StrToDbl to take a length)
  2593. for (pch = this->GetSz(); IsWhiteSpaceCharacter(*pch); pch++)
  2594. ;
  2595. if (pch == this->m_pszValue + len)
  2596. {
  2597. *result = 0;
  2598. return true;
  2599. }
  2600. bool isNumericLiteral = false;
  2601. if (*pch == '0')
  2602. {
  2603. const char16 *pchT = pch + 2;
  2604. switch (pch[1])
  2605. {
  2606. case 'x':
  2607. case 'X':
  2608. *result = NumberUtilities::DblFromHex(pchT, &pch);
  2609. isNumericLiteral = true;
  2610. break;
  2611. case 'o':
  2612. case 'O':
  2613. *result = NumberUtilities::DblFromOctal(pchT, &pch);
  2614. isNumericLiteral = true;
  2615. break;
  2616. case 'b':
  2617. case 'B':
  2618. *result = NumberUtilities::DblFromBinary(pchT, &pch);
  2619. isNumericLiteral = true;
  2620. break;
  2621. }
  2622. if (pchT == pch && isNumericLiteral)
  2623. {
  2624. *result = JavascriptNumber::NaN;
  2625. return false;
  2626. }
  2627. }
  2628. if (!isNumericLiteral)
  2629. {
  2630. *result = NumberUtilities::StrToDbl(pch, &pch, GetScriptContext());
  2631. }
  2632. while (IsWhiteSpaceCharacter(*pch))
  2633. pch++;
  2634. if (pch != this->m_pszValue + len)
  2635. {
  2636. *result = JavascriptNumber::NaN;
  2637. return false;
  2638. }
  2639. return true;
  2640. }
  2641. double JavascriptString::ToDouble()
  2642. {
  2643. double result;
  2644. this->ToDouble(&result);
  2645. return result;
  2646. }
  2647. bool JavascriptString::Equals(JavascriptString* aLeft, JavascriptString* aRight)
  2648. {
  2649. return JavascriptStringHelpers<JavascriptString>::Equals(aLeft, aRight);
  2650. }
  2651. //
  2652. // LessThan implements algorithm of ES5 11.8.5 step 4
  2653. // returns false for same string pattern
  2654. //
  2655. bool JavascriptString::LessThan(Var aLeft, Var aRight)
  2656. {
  2657. AssertMsg(VarIs<JavascriptString>(aLeft) && VarIs<JavascriptString>(aRight), "string LessThan");
  2658. JavascriptString *leftString = VarTo<JavascriptString>(aLeft);
  2659. JavascriptString *rightString = VarTo<JavascriptString>(aRight);
  2660. if (JavascriptString::strcmp(leftString, rightString) < 0)
  2661. {
  2662. return true;
  2663. }
  2664. return false;
  2665. }
  2666. // thisStringValue(value) abstract operation as defined in ES6.0 (Draft 25) Section 21.1.3
  2667. BOOL JavascriptString::GetThisValueVar(Var aValue, JavascriptString** pString, ScriptContext* scriptContext)
  2668. {
  2669. Assert(pString);
  2670. // 1. If Type(value) is String, return value.
  2671. if (VarIs<JavascriptString>(aValue))
  2672. {
  2673. *pString = VarTo<JavascriptString>(aValue);
  2674. return TRUE;
  2675. }
  2676. // 2. If Type(value) is Object and value has a [[StringData]] internal slot
  2677. else if ( VarIs<JavascriptStringObject>(aValue))
  2678. {
  2679. JavascriptStringObject* pStringObj = VarTo<JavascriptStringObject>(aValue);
  2680. // a. Let s be the value of value's [[StringData]] internal slot.
  2681. // b. If s is not undefined, then return s.
  2682. *pString = pStringObj->Unwrap();
  2683. *pString = VarTo<JavascriptString>(CrossSite::MarshalVar(scriptContext,
  2684. *pString, pStringObj->GetScriptContext()));
  2685. return TRUE;
  2686. }
  2687. // 3. Throw a TypeError exception.
  2688. // Note: We don't throw a TypeError here, choosing to return FALSE and let the caller throw the error
  2689. return FALSE;
  2690. }
  2691. #ifdef TAGENTRY
  2692. #undef TAGENTRY
  2693. #endif
  2694. #define TAGENTRY(name, ...) \
  2695. Var JavascriptString::Entry##name(RecyclableObject* function, CallInfo callInfo, ...) \
  2696. { \
  2697. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); \
  2698. \
  2699. ARGUMENTS(args, callInfo); \
  2700. ScriptContext* scriptContext = function->GetScriptContext(); \
  2701. \
  2702. Assert(!(callInfo.Flags & CallFlags_New)); \
  2703. \
  2704. return StringBracketHelper(args, scriptContext, __VA_ARGS__); \
  2705. }
  2706. #include "JavascriptStringTagEntries.h"
  2707. #undef TAGENTRY
  2708. Var JavascriptString::StringBracketHelper(Arguments args, ScriptContext *scriptContext, __in_ecount(cchTag) char16 const *pszTag,
  2709. charcount_t cchTag, __in_ecount_opt(cchProp) char16 const *pszProp, charcount_t cchProp)
  2710. {
  2711. charcount_t cchThis;
  2712. charcount_t cchPropertyValue;
  2713. charcount_t cchTotalChars;
  2714. charcount_t ich;
  2715. JavascriptString * pThis = nullptr;
  2716. JavascriptString * pPropertyValue = nullptr;
  2717. const char16 * propertyValueStr = nullptr;
  2718. uint quotesCount = 0;
  2719. const char16 quotStr[] = _u("&quot;");
  2720. const charcount_t quotStrLen = _countof(quotStr) - 1;
  2721. bool ES6FixesEnabled = scriptContext->GetConfig()->IsES6StringPrototypeFixEnabled();
  2722. // Assemble the component pieces of a string tag function (ex: String.prototype.link).
  2723. // In the general case, result is as below:
  2724. //
  2725. // pszProp = _u("href");
  2726. // pszTag = _u("a");
  2727. // pThis = VarTo<JavascriptString>(args[0]);
  2728. // pPropertyValue = VarTo<JavascriptString>(args[1]);
  2729. //
  2730. // pResult = _u("<a href=\"[[pPropertyValue]]\">[[pThis]]</a>");
  2731. //
  2732. // cchTotalChars = 5 // <></>
  2733. // + cchTag * 2 // a
  2734. // + cchProp // href
  2735. // + 4 // _=""
  2736. // + cchPropertyValue
  2737. // + cchThis;
  2738. //
  2739. // Note: With ES6FixesEnabled, we need to escape quote characters (_u('"')) in pPropertyValue.
  2740. // Note: Without ES6FixesEnabled, the tag and prop strings should be capitalized.
  2741. if(args.Info.Count == 0)
  2742. {
  2743. JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedString);
  2744. }
  2745. if (ES6FixesEnabled)
  2746. {
  2747. if (!JavascriptConversion::CheckObjectCoercible(args[0], scriptContext))
  2748. {
  2749. JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, pszTag);
  2750. }
  2751. }
  2752. pThis = JavascriptOperators::TryFromVar<JavascriptString>(args[0]);
  2753. if (!pThis)
  2754. {
  2755. pThis = JavascriptConversion::ToString(args[0], scriptContext);
  2756. }
  2757. cchThis = pThis->GetLength();
  2758. cchTotalChars = UInt32Math::Add(cchTag, cchTag);
  2759. // 5 is for the <></> characters
  2760. cchTotalChars = UInt32Math::Add(cchTotalChars, 5);
  2761. if (nullptr != pszProp)
  2762. {
  2763. // Need one string argument.
  2764. if (args.Info.Count >= 2)
  2765. {
  2766. pPropertyValue = JavascriptOperators::TryFromVar<JavascriptString>(args[1]);
  2767. if (!pPropertyValue)
  2768. {
  2769. pPropertyValue = JavascriptConversion::ToString(args[1], scriptContext);
  2770. }
  2771. }
  2772. else
  2773. {
  2774. pPropertyValue = scriptContext->GetLibrary()->GetUndefinedDisplayString();
  2775. }
  2776. cchPropertyValue = pPropertyValue->GetLength();
  2777. propertyValueStr = pPropertyValue->GetString();
  2778. if (ES6FixesEnabled)
  2779. {
  2780. // Count the number of " characters we need to escape.
  2781. for (ich = 0; ich < cchPropertyValue; ich++)
  2782. {
  2783. if (propertyValueStr[ich] == _u('"'))
  2784. {
  2785. ++quotesCount;
  2786. }
  2787. }
  2788. }
  2789. cchTotalChars = UInt32Math::Add(cchTotalChars, cchProp);
  2790. // 4 is for the _="" characters
  2791. cchTotalChars = UInt32Math::Add(cchTotalChars, 4);
  2792. if (ES6FixesEnabled)
  2793. {
  2794. // Account for the " escaping (&quot;)
  2795. cchTotalChars = UInt32Math::Add(cchTotalChars, UInt32Math::Mul(quotesCount, quotStrLen)) - quotesCount;
  2796. }
  2797. }
  2798. else
  2799. {
  2800. cchPropertyValue = 0;
  2801. cchProp = 0;
  2802. }
  2803. cchTotalChars = UInt32Math::Add(cchTotalChars, cchThis);
  2804. cchTotalChars = UInt32Math::Add(cchTotalChars, cchPropertyValue);
  2805. if (!IsValidCharCount(cchTotalChars) || cchTotalChars < cchThis || cchTotalChars < cchPropertyValue)
  2806. {
  2807. Js::JavascriptError::ThrowOutOfMemoryError(scriptContext);
  2808. }
  2809. BufferStringBuilder builder(cchTotalChars, scriptContext);
  2810. char16 *pResult = builder.DangerousGetWritableBuffer();
  2811. *pResult++ = _u('<');
  2812. for (ich = 0; ich < cchTag; ich++)
  2813. {
  2814. *pResult++ = ES6FixesEnabled ? pszTag[ich] : towupper(pszTag[ich]);
  2815. }
  2816. if (nullptr != pszProp)
  2817. {
  2818. *pResult++ = _u(' ');
  2819. for (ich = 0; ich < cchProp; ich++)
  2820. {
  2821. *pResult++ = ES6FixesEnabled ? pszProp[ich] : towupper(pszProp[ich]);
  2822. }
  2823. *pResult++ = _u('=');
  2824. *pResult++ = _u('"');
  2825. Assert(propertyValueStr != nullptr);
  2826. if (!ES6FixesEnabled || quotesCount == 0)
  2827. {
  2828. js_wmemcpy_s(pResult,
  2829. cchTotalChars - (pResult - builder.DangerousGetWritableBuffer() + 1),
  2830. propertyValueStr,
  2831. cchPropertyValue);
  2832. pResult += cchPropertyValue;
  2833. }
  2834. else {
  2835. for (ich = 0; ich < cchPropertyValue; ich++)
  2836. {
  2837. if (propertyValueStr[ich] == _u('"'))
  2838. {
  2839. charcount_t destLengthLeft = (cchTotalChars - (charcount_t)(pResult - builder.DangerousGetWritableBuffer() + 1));
  2840. // Copy the quote string into result beginning at the index where the quote would appear
  2841. js_wmemcpy_s(pResult,
  2842. destLengthLeft,
  2843. quotStr,
  2844. quotStrLen);
  2845. // Move result ahead by the length of the quote string
  2846. pResult += quotStrLen;
  2847. // We ate one of the quotes
  2848. quotesCount--;
  2849. // We only need to check to see if we have no more quotes after eating a quote
  2850. if (quotesCount == 0)
  2851. {
  2852. // Skip the quote character.
  2853. // Note: If ich is currently the last character (cchPropertyValue-1), it becomes cchPropertyValue after incrementing.
  2854. // At that point, cchPropertyValue - ich == 0 so we will not increment pResult and will call memcpy for zero bytes.
  2855. ich++;
  2856. // Copy the rest from the property value string starting at the index after the last quote
  2857. js_wmemcpy_s(pResult,
  2858. destLengthLeft - quotStrLen,
  2859. propertyValueStr + ich,
  2860. cchPropertyValue - ich);
  2861. // Move result ahead by the length of the rest of the property string
  2862. pResult += (cchPropertyValue - ich);
  2863. break;
  2864. }
  2865. }
  2866. else
  2867. {
  2868. // Each non-quote character just gets copied into result string
  2869. *pResult++ = propertyValueStr[ich];
  2870. }
  2871. }
  2872. }
  2873. *pResult++ = _u('"');
  2874. }
  2875. *pResult++ = _u('>');
  2876. const char16 *pThisString = pThis->GetString();
  2877. js_wmemcpy_s(pResult, cchTotalChars - (pResult - builder.DangerousGetWritableBuffer() + 1), pThisString, cchThis);
  2878. pResult += cchThis;
  2879. *pResult++ = _u('<');
  2880. *pResult++ = _u('/');
  2881. for (ich = 0; ich < cchTag; ich++)
  2882. {
  2883. *pResult++ = ES6FixesEnabled ? pszTag[ich] : towupper(pszTag[ich]);
  2884. }
  2885. *pResult++ = _u('>');
  2886. // Assert we ended at the right place.
  2887. AssertMsg((charcount_t)(pResult - builder.DangerousGetWritableBuffer()) == cchTotalChars, "Exceeded allocated string limit");
  2888. return builder.ToString();
  2889. }
  2890. int JavascriptString::IndexOfUsingJmpTable(JmpTable jmpTable, const char16* inputStr, charcount_t len, const char16* searchStr, int searchLen, int position)
  2891. {
  2892. int result = -1;
  2893. const char16 searchLast = searchStr[searchLen-1];
  2894. uint32 lMatchedJump = searchLen;
  2895. if (jmpTable[searchLast].shift > 0)
  2896. {
  2897. lMatchedJump = jmpTable[searchLast].shift;
  2898. }
  2899. char16 const * p = inputStr + position + searchLen-1;
  2900. WCHAR c;
  2901. while(p < inputStr + len)
  2902. {
  2903. // first character match, keep checking
  2904. if (*p == searchLast)
  2905. {
  2906. if ( wmemcmp(p-searchLen+1, searchStr, searchLen) == 0 )
  2907. {
  2908. break;
  2909. }
  2910. p += lMatchedJump;
  2911. }
  2912. else
  2913. {
  2914. c = *p;
  2915. if ( 0 == ( c & ~0x7f ) && jmpTable[c].shift != 0 )
  2916. {
  2917. p += jmpTable[c].shift;
  2918. }
  2919. else
  2920. {
  2921. p += searchLen;
  2922. }
  2923. }
  2924. }
  2925. if (p >= inputStr+position && p < inputStr + len)
  2926. {
  2927. result = (int)(p - inputStr) - searchLen + 1;
  2928. }
  2929. return result;
  2930. }
  2931. int JavascriptString::LastIndexOfUsingJmpTable(JmpTable jmpTable, const char16* inputStr, charcount_t len, const char16* searchStr, charcount_t searchLen, charcount_t position)
  2932. {
  2933. Assert(searchLen > 0);
  2934. const char16 searchFirst = searchStr[0];
  2935. uint32 lMatchedJump = searchLen;
  2936. if (jmpTable[searchFirst].shift > 0)
  2937. {
  2938. lMatchedJump = jmpTable[searchFirst].shift;
  2939. }
  2940. WCHAR c;
  2941. char16 const * p = inputStr + min(len - searchLen, position);
  2942. while (true)
  2943. {
  2944. uint32 remaining = (uint32)(p - inputStr);
  2945. uint32 backwardOffset = 0;
  2946. // first character match, keep checking
  2947. if (*p == searchFirst)
  2948. {
  2949. if (wmemcmp(p, searchStr, searchLen) == 0)
  2950. {
  2951. return (int)remaining;
  2952. }
  2953. backwardOffset = lMatchedJump;
  2954. }
  2955. else
  2956. {
  2957. c = *p;
  2958. if (0 == (c & ~0x7f) && jmpTable[c].shift != 0)
  2959. {
  2960. backwardOffset = jmpTable[c].shift;
  2961. }
  2962. else
  2963. {
  2964. backwardOffset = searchLen;
  2965. }
  2966. }
  2967. AssertOrFailFast(backwardOffset > 0);
  2968. if (backwardOffset > remaining)
  2969. {
  2970. break;
  2971. }
  2972. p -= backwardOffset;
  2973. }
  2974. return -1;
  2975. }
  2976. bool JavascriptString::BuildLastCharForwardBoyerMooreTable(JmpTable jmpTable, const char16* searchStr, int searchLen)
  2977. {
  2978. AssertMsg(searchLen >= 1, "Table for non-empty string");
  2979. memset(jmpTable, 0, sizeof(JmpTable));
  2980. const char16 * p2 = searchStr + searchLen - 1;
  2981. const char16 * const begin = searchStr;
  2982. // Determine if we can do a partial ASCII Boyer-Moore
  2983. while (p2 >= begin)
  2984. {
  2985. WCHAR c = *p2;
  2986. if ( 0 == ( c & ~0x7f ))
  2987. {
  2988. if ( jmpTable[c].shift == 0 )
  2989. {
  2990. jmpTable[c].shift = (uint32)(searchStr + searchLen - 1 - p2);
  2991. }
  2992. }
  2993. else
  2994. {
  2995. return false;
  2996. }
  2997. p2--;
  2998. }
  2999. return true;
  3000. }
  3001. bool JavascriptString::BuildFirstCharBackwardBoyerMooreTable(JmpTable jmpTable, const char16* searchStr, int searchLen)
  3002. {
  3003. AssertMsg(searchLen >= 1, "Table for non-empty string");
  3004. memset(jmpTable, 0, sizeof(JmpTable));
  3005. const char16 * p2 = searchStr;
  3006. const char16 * const end = searchStr + searchLen;
  3007. // Determine if we can do a partial ASCII Boyer-Moore
  3008. while (p2 < end)
  3009. {
  3010. WCHAR c = *p2;
  3011. if ( 0 == ( c & ~0x7f ))
  3012. {
  3013. if ( jmpTable[c].shift == 0 )
  3014. {
  3015. jmpTable[c].shift = (uint32)(p2 - searchStr);
  3016. }
  3017. }
  3018. else
  3019. {
  3020. return false;
  3021. }
  3022. p2++;
  3023. }
  3024. return true;
  3025. }
  3026. uint JavascriptString::strstr(JavascriptString *string, JavascriptString *substring, bool useBoyerMoore, uint start)
  3027. {
  3028. uint i;
  3029. const char16 *stringOrig = string->GetString();
  3030. uint stringLenOrig = string->GetLength();
  3031. const char16 *stringSz = stringOrig + start;
  3032. const char16 *substringSz = substring->GetString();
  3033. uint stringLen = stringLenOrig - start;
  3034. uint substringLen = substring->GetLength();
  3035. if (useBoyerMoore && substringLen > 2)
  3036. {
  3037. JmpTable jmpTable;
  3038. bool fAsciiJumpTable = BuildLastCharForwardBoyerMooreTable(jmpTable, substringSz, substringLen);
  3039. if (fAsciiJumpTable)
  3040. {
  3041. int result = IndexOfUsingJmpTable(jmpTable, stringOrig, stringLenOrig, substringSz, substringLen, start);
  3042. if (result != -1)
  3043. {
  3044. return result;
  3045. }
  3046. else
  3047. {
  3048. return (uint)-1;
  3049. }
  3050. }
  3051. }
  3052. if (stringLen >= substringLen)
  3053. {
  3054. // If substring is empty, it matches anything...
  3055. if (substringLen == 0)
  3056. {
  3057. return 0;
  3058. }
  3059. for (i = 0; i <= stringLen - substringLen; i++)
  3060. {
  3061. // Quick check for first character.
  3062. if (stringSz[i] == substringSz[0])
  3063. {
  3064. if (substringLen == 1 || wmemcmp(stringSz + i + 1, substringSz + 1, (substringLen - 1)) == 0)
  3065. {
  3066. return i + start;
  3067. }
  3068. }
  3069. }
  3070. }
  3071. return (uint)-1;
  3072. }
  3073. int JavascriptString::strcmp(JavascriptString *string1, JavascriptString *string2)
  3074. {
  3075. uint string1Len = string1->GetLength();
  3076. uint string2Len = string2->GetLength();
  3077. // We want to pin the strings string1 and string2 because flattening of any of these strings could cause a GC and result in the other string getting collected if it was optimized
  3078. // away by the compiler. We would normally have called the EnterPinnedScope/LeavePinnedScope methods here but it adds extra call instructions to the assembly code. As Equals
  3079. // methods could get called a lot of times this can show up as regressions in benchmarks.
  3080. volatile Js::JavascriptString** keepAliveString1 = (volatile Js::JavascriptString**)& string1;
  3081. volatile Js::JavascriptString** keepAliveString2 = (volatile Js::JavascriptString**)& string2;
  3082. auto keepAliveLambda = [&]() {
  3083. UNREFERENCED_PARAMETER(keepAliveString1);
  3084. UNREFERENCED_PARAMETER(keepAliveString2);
  3085. };
  3086. int result = wmemcmp(string1->GetString(), string2->GetString(), min(string1Len, string2Len));
  3087. return (result == 0) ? (int)(string1Len - string2Len) : result;
  3088. }
  3089. /*static*/ charcount_t JavascriptString::SafeSzSize(charcount_t cch)
  3090. {
  3091. // JavascriptString::MaxCharLength is valid; however, we are incrementing below by 1 and want to make sure we aren't overflowing
  3092. // Nor going outside of valid range.
  3093. if (cch >= JavascriptString::MaxCharLength)
  3094. {
  3095. Throw::OutOfMemory();
  3096. }
  3097. // Compute cch + 1, checking for overflow
  3098. ++cch;
  3099. return cch;
  3100. }
  3101. charcount_t JavascriptString::SafeSzSize() const
  3102. {
  3103. return SafeSzSize(GetLength());
  3104. }
  3105. /*static*/ __ecount(length+1) char16* JavascriptString::AllocateLeafAndCopySz(__in Recycler* recycler, __in_ecount(length) const char16* content, charcount_t length)
  3106. {
  3107. // Note: Intentionally not using SafeSzSize nor hoisting common
  3108. // sub-expression "length + 1" into a local variable otherwise
  3109. // Prefast gets confused and cannot track buffer's length.
  3110. // JavascriptString::MaxCharLength is valid; however, we are incrementing below by 1 and want to make sure we aren't overflowing
  3111. // Nor going outside of valid range.
  3112. if (length >= JavascriptString::MaxCharLength)
  3113. {
  3114. Throw::OutOfMemory();
  3115. }
  3116. charcount_t bufLen = length + 1;
  3117. // Allocate recycler memory to store the string plus a terminating NUL
  3118. char16* buffer = RecyclerNewArrayLeaf(recycler, char16, bufLen);
  3119. js_wmemcpy_s(buffer, bufLen, content, length);
  3120. buffer[length] = _u('\0');
  3121. return buffer;
  3122. }
  3123. /*static*/ __ecount(length+1) char16* JavascriptString::AllocateAndCopySz(__in ArenaAllocator* arena, __in_ecount(length) const char16* content, charcount_t length)
  3124. {
  3125. // Note: Intentionally not using SafeSzSize nor hoisting common
  3126. // sub-expression "length + 1" into a local variable otherwise
  3127. // Prefast gets confused and cannot track buffer's length.
  3128. // JavascriptString::MaxCharLength is valid; however, we are incrementing below by 1 and want to make sure we aren't overflowing
  3129. // Nor going outside of valid range.
  3130. if (length >= JavascriptString::MaxCharLength)
  3131. {
  3132. Throw::OutOfMemory();
  3133. }
  3134. // Allocate arena memory to store the string plus a terminating NUL
  3135. char16* buffer = AnewArray(arena, char16, length + 1);
  3136. js_wmemcpy_s(buffer, length + 1, content, length);
  3137. buffer[length] = _u('\0');
  3138. return buffer;
  3139. }
  3140. RecyclableObject * JavascriptString::CloneToScriptContext(ScriptContext* requestContext)
  3141. {
  3142. return JavascriptString::NewWithBuffer(this->GetSz(), this->GetLength(), requestContext);
  3143. }
  3144. charcount_t JavascriptString::ConvertToIndex(Var varIndex, ScriptContext *scriptContext)
  3145. {
  3146. if (TaggedInt::Is(varIndex))
  3147. {
  3148. return TaggedInt::ToInt32(varIndex);
  3149. }
  3150. return NumberUtilities::LwFromDblNearest(JavascriptConversion::ToInteger(varIndex, scriptContext));
  3151. }
  3152. char16* JavascriptString::GetNormalizedString(PlatformAgnostic::UnicodeText::NormalizationForm form, ArenaAllocator* tempAllocator, charcount_t& sizeOfNormalizedStringWithoutNullTerminator)
  3153. {
  3154. using namespace PlatformAgnostic;
  3155. ScriptContext* scriptContext = this->GetScriptContext();
  3156. if (this->GetLength() == 0)
  3157. {
  3158. sizeOfNormalizedStringWithoutNullTerminator = 0;
  3159. return nullptr;
  3160. }
  3161. // IMPORTANT: Implementation Notes
  3162. // Normalize string estimates the required size of the buffer based on averages and other data.
  3163. // It is very hard to get a precise size from an input string without expanding/contracting it on the buffer.
  3164. // It is estimated that the maximum size the string after an NFC is 6x the input length, and 18x for NFD. This approach isn't very feasible as well.
  3165. // The approach taken is based on the simple example in the MSDN article.
  3166. // - Loop until the return value is either an error (apart from insufficient buffer size), or success.
  3167. // - Each time recreate a temporary buffer based on the last guess.
  3168. // - When creating the JS string, use the positive return value and copy the buffer across.
  3169. // Design choice for "guesses" comes from data Windows collected; and in most cases the loop will not iterate more than 2 times.
  3170. Assert(!UnicodeText::IsNormalizedString(form, this->GetSz(), this->GetLength()));
  3171. //Get the first size estimate
  3172. UnicodeText::ApiError error;
  3173. int32 sizeEstimate = UnicodeText::NormalizeString(form, this->GetSz(), this->GetLength() + 1, nullptr, 0, &error);
  3174. char16 *tmpBuffer = nullptr;
  3175. //Loop while the size estimate is bigger than 0
  3176. while (error == UnicodeText::ApiError::InsufficientBuffer)
  3177. {
  3178. tmpBuffer = AnewArray(tempAllocator, char16, sizeEstimate);
  3179. sizeEstimate = UnicodeText::NormalizeString(form, this->GetSz(), this->GetLength() + 1, tmpBuffer, sizeEstimate, &error);
  3180. // Success, sizeEstimate is the exact size including the null terminator
  3181. if (sizeEstimate > 0)
  3182. {
  3183. sizeOfNormalizedStringWithoutNullTerminator = sizeEstimate - 1;
  3184. return tmpBuffer;
  3185. }
  3186. // Anything less than 0, we have an error, flip sizeEstimate now. As both times we need to use it, we need positive anyways.
  3187. sizeEstimate *= -1;
  3188. }
  3189. switch (error)
  3190. {
  3191. case UnicodeText::ApiError::InvalidParameter:
  3192. //some invalid parameter, coding error
  3193. AssertMsg(false, "Invalid Parameter- check pointers passed to NormalizeString");
  3194. JavascriptError::ThrowRangeError(scriptContext, JSERR_FailedToNormalize);
  3195. break;
  3196. case UnicodeText::ApiError::InvalidUnicodeText:
  3197. //the value returned is the negative index of an invalid unicode character
  3198. JavascriptError::ThrowRangeErrorVar(scriptContext, JSERR_InvalidUnicodeCharacter, sizeEstimate);
  3199. break;
  3200. case UnicodeText::ApiError::NoError:
  3201. //The actual size of the output string is zero.
  3202. //Theoretically only empty input string should produce this, which is handled above, thus the code path should not be hit.
  3203. AssertMsg(false, "This code path should not be hit, empty string case is handled above. Perhaps a false error (sizeEstimate <= 0; but lastError == 0; ERROR_SUCCESS and NO_ERRROR == 0)");
  3204. sizeOfNormalizedStringWithoutNullTerminator = 0;
  3205. return nullptr; // scriptContext->GetLibrary()->GetEmptyString();
  3206. break;
  3207. default:
  3208. AssertMsg(false, "Unknown error");
  3209. JavascriptError::ThrowRangeError(scriptContext, JSERR_FailedToNormalize);
  3210. break;
  3211. }
  3212. }
  3213. void JavascriptString::InstantiateForceInlinedMembers()
  3214. {
  3215. // Force-inlined functions defined in a translation unit need a reference from an extern non-force-inlined function in
  3216. // the same translation unit to force an instantiation of the force-inlined function. Otherwise, if the force-inlined
  3217. // function is not referenced in the same translation unit, it will not be generated and the linker is not able to find
  3218. // the definition to inline the function in other translation units.
  3219. Assert(false);
  3220. JavascriptString *const s = nullptr;
  3221. s->ConcatDestructive(nullptr);
  3222. }
  3223. JavascriptString *
  3224. JavascriptString::Concat3(JavascriptString * pstLeft, JavascriptString * pstCenter, JavascriptString * pstRight)
  3225. {
  3226. ConcatStringMulti * concatString = ConcatStringMulti::New(3, pstLeft, pstCenter, pstLeft->GetScriptContext());
  3227. concatString->SetItem(2, pstRight);
  3228. return concatString;
  3229. }
  3230. PropertyQueryFlags JavascriptString::HasPropertyQuery(PropertyId propertyId, _Inout_opt_ PropertyValueInfo* info)
  3231. {
  3232. if (propertyId == PropertyIds::length)
  3233. {
  3234. return PropertyQueryFlags::Property_Found;
  3235. }
  3236. ScriptContext* scriptContext = GetScriptContext();
  3237. charcount_t index;
  3238. if (scriptContext->IsNumericPropertyId(propertyId, &index))
  3239. {
  3240. if (index < this->GetLength())
  3241. {
  3242. return PropertyQueryFlags::Property_Found;
  3243. }
  3244. }
  3245. return PropertyQueryFlags::Property_NotFound;
  3246. }
  3247. BOOL JavascriptString::IsEnumerable(PropertyId propertyId)
  3248. {
  3249. ScriptContext* scriptContext = GetScriptContext();
  3250. charcount_t index;
  3251. if (scriptContext->IsNumericPropertyId(propertyId, &index))
  3252. {
  3253. if (index < this->GetLength())
  3254. {
  3255. return true;
  3256. }
  3257. }
  3258. return false;
  3259. }
  3260. PropertyQueryFlags JavascriptString::GetPropertyQuery(Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
  3261. {
  3262. return JavascriptConversion::BooleanToPropertyQueryFlags(GetPropertyBuiltIns(propertyId, value, requestContext));
  3263. }
  3264. PropertyQueryFlags JavascriptString::GetPropertyQuery(Var originalInstance, JavascriptString* propertyNameString, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
  3265. {
  3266. PropertyRecord const* propertyRecord;
  3267. this->GetScriptContext()->FindPropertyRecord(propertyNameString, &propertyRecord);
  3268. if (propertyRecord != nullptr && GetPropertyBuiltIns(propertyRecord->GetPropertyId(), value, requestContext))
  3269. {
  3270. return PropertyQueryFlags::Property_Found;
  3271. }
  3272. *value = requestContext->GetMissingPropertyResult();
  3273. return PropertyQueryFlags::Property_NotFound;
  3274. }
  3275. bool JavascriptString::GetPropertyBuiltIns(PropertyId propertyId, Var* value, ScriptContext* requestContext)
  3276. {
  3277. if (propertyId == PropertyIds::length)
  3278. {
  3279. *value = JavascriptNumber::ToVar(this->GetLength(), requestContext);
  3280. return true;
  3281. }
  3282. *value = requestContext->GetMissingPropertyResult();
  3283. return false;
  3284. }
  3285. PropertyQueryFlags JavascriptString::GetPropertyReferenceQuery(Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
  3286. {
  3287. return JavascriptString::GetPropertyQuery(originalInstance, propertyId, value, info, requestContext);
  3288. }
  3289. BOOL JavascriptString::SetItem(uint32 index, Var value, PropertyOperationFlags propertyOperationFlags)
  3290. {
  3291. if (this->HasItemAt(index))
  3292. {
  3293. JavascriptError::ThrowCantAssignIfStrictMode(propertyOperationFlags, this->GetScriptContext());
  3294. return FALSE;
  3295. }
  3296. return __super::SetItem(index, value, propertyOperationFlags);
  3297. }
  3298. BOOL JavascriptString::DeleteItem(uint32 index, PropertyOperationFlags propertyOperationFlags)
  3299. {
  3300. if (this->HasItemAt(index))
  3301. {
  3302. JavascriptError::ThrowCantDeleteIfStrictMode(propertyOperationFlags, this->GetScriptContext(), TaggedInt::ToString(index, this->GetScriptContext())->GetString());
  3303. return FALSE;
  3304. }
  3305. return __super::DeleteItem(index, propertyOperationFlags);
  3306. }
  3307. PropertyQueryFlags JavascriptString::HasItemQuery(uint32 index)
  3308. {
  3309. return JavascriptConversion::BooleanToPropertyQueryFlags(this->HasItemAt(index));
  3310. }
  3311. PropertyQueryFlags JavascriptString::GetItemQuery(Var originalInstance, uint32 index, Var* value, ScriptContext* requestContext)
  3312. {
  3313. // String should always be marshalled to the current context
  3314. Assert(requestContext == this->GetScriptContext());
  3315. return JavascriptConversion::BooleanToPropertyQueryFlags(this->GetItemAt(index, value));
  3316. }
  3317. PropertyQueryFlags JavascriptString::GetItemReferenceQuery(Var originalInstance, uint32 index, Var* value, ScriptContext* requestContext)
  3318. {
  3319. // String should always be marshalled to the current context
  3320. return JavascriptConversion::BooleanToPropertyQueryFlags(this->GetItemAt(index, value));
  3321. }
  3322. BOOL JavascriptString::GetEnumerator(JavascriptStaticEnumerator * enumerator, EnumeratorFlags flags, ScriptContext* requestContext, EnumeratorCache * enumeratorCache)
  3323. {
  3324. return enumerator->Initialize(
  3325. RecyclerNew(GetScriptContext()->GetRecycler(), JavascriptStringEnumerator, this, requestContext),
  3326. nullptr, nullptr, flags, requestContext, enumeratorCache);
  3327. }
  3328. BOOL JavascriptString::DeleteProperty(PropertyId propertyId, PropertyOperationFlags propertyOperationFlags)
  3329. {
  3330. if (propertyId == PropertyIds::length)
  3331. {
  3332. JavascriptError::ThrowCantDeleteIfStrictMode(propertyOperationFlags, this->GetScriptContext(), this->GetScriptContext()->GetPropertyName(propertyId)->GetBuffer());
  3333. return FALSE;
  3334. }
  3335. return __super::DeleteProperty(propertyId, propertyOperationFlags);
  3336. }
  3337. BOOL JavascriptString::DeleteProperty(JavascriptString *propertyNameString, PropertyOperationFlags propertyOperationFlags)
  3338. {
  3339. if (BuiltInPropertyRecords::length.Equals(propertyNameString))
  3340. {
  3341. JavascriptError::ThrowCantDeleteIfStrictMode(propertyOperationFlags, this->GetScriptContext(), propertyNameString->GetString());
  3342. return FALSE;
  3343. }
  3344. return __super::DeleteProperty(propertyNameString, propertyOperationFlags);
  3345. }
  3346. BOOL JavascriptString::GetDiagValueString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
  3347. {
  3348. stringBuilder->Append(_u('"'));
  3349. stringBuilder->Append(this->GetString(), this->GetLength());
  3350. stringBuilder->Append(_u('"'));
  3351. return TRUE;
  3352. }
  3353. BOOL JavascriptString::GetDiagTypeString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
  3354. {
  3355. stringBuilder->AppendCppLiteral(_u("String"));
  3356. return TRUE;
  3357. }
  3358. RecyclableObject* JavascriptString::ToObject(ScriptContext * requestContext)
  3359. {
  3360. return requestContext->GetLibrary()->CreateStringObject(this);
  3361. }
  3362. Var JavascriptString::GetTypeOfString(ScriptContext * requestContext)
  3363. {
  3364. return requestContext->GetLibrary()->GetStringTypeDisplayString();
  3365. }
  3366. /* static */
  3367. template <typename T>
  3368. bool JavascriptStringHelpers<T>::Equals(T* aLeft, T* aRight)
  3369. {
  3370. if (aLeft == aRight) return true;
  3371. // methods could get called a lot of times this can show up as regressions in benchmarks.
  3372. volatile T** keepAliveLeftString = (volatile T**)& aLeft;
  3373. volatile T** keepAliveRightString = (volatile T**)& aRight;
  3374. auto keepAliveLambda = [&]() {
  3375. UNREFERENCED_PARAMETER(keepAliveLeftString);
  3376. UNREFERENCED_PARAMETER(keepAliveRightString);
  3377. };
  3378. if (aLeft->GetLength() != aRight->GetLength())
  3379. {
  3380. return false;
  3381. }
  3382. return JsUtil::CharacterBuffer<char16>::StaticEquals(aLeft->GetString(), aRight->GetString(), aLeft->GetLength());
  3383. }
  3384. #if ENABLE_NATIVE_CODEGEN
  3385. template bool JavascriptStringHelpers<JITJavascriptString>::Equals(JITJavascriptString* aLeft, JITJavascriptString* aRight);
  3386. #endif
  3387. }