| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799 |
- //-------------------------------------------------------------------------------------------------------
- // Copyright (C) Microsoft. All rights reserved.
- // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
- //-------------------------------------------------------------------------------------------------------
- #include "RuntimeLibraryPch.h"
- #include "DataStructures/BigInt.h"
- #include "Library/EngineInterfaceObject.h"
- #include "Library/IntlEngineInterfaceExtensionObject.h"
- namespace Js
- {
- // White Space characters are defined in ES6 Section 11.2
- // There are 26 white space characters we need to correctly class:
- //0x0009
- //0x000a
- //0x000b
- //0x000c
- //0x000d
- //0x0020
- //0x00a0
- //0x1680
- //0x180e
- //0x2000
- //0x2001
- //0x2002
- //0x2003
- //0x2004
- //0x2005
- //0x2006
- //0x2007
- //0x2008
- //0x2009
- //0x200a
- //0x2028
- //0x2029
- //0x202f
- //0x205f
- //0x3000
- //0xfeff
- bool IsWhiteSpaceCharacter(char16 ch)
- {
- return ch >= 0x9 &&
- (ch <= 0xd ||
- (ch <= 0x200a &&
- (ch >= 0x2000 || ch == 0x20 || ch == 0xa0 || ch == 0x1680 || ch == 0x180e)
- ) ||
- (ch >= 0x2028 &&
- (ch <= 0x2029 || ch == 0x202f || ch == 0x205f || ch == 0x3000 || ch == 0xfeff)
- )
- );
- }
- template <typename T, bool copyBuffer>
- JavascriptString* JavascriptString::NewWithBufferT(const char16 * content, charcount_t cchUseLength, ScriptContext * scriptContext)
- {
- AssertMsg(content != nullptr, "NULL value passed to JavascriptString::New");
- AssertMsg(IsValidCharCount(cchUseLength), "String length will overflow an int");
- switch (cchUseLength)
- {
- case 0:
- return scriptContext->GetLibrary()->GetEmptyString();
- case 1:
- return scriptContext->GetLibrary()->GetCharStringCache().GetStringForChar(*content);
- default:
- break;
- }
- Recycler* recycler = scriptContext->GetRecycler();
- StaticType * stringTypeStatic = scriptContext->GetLibrary()->GetStringTypeStatic();
- char16 const * buffer = content;
- charcount_t cchUseBoundLength = static_cast<charcount_t>(cchUseLength);
- if (copyBuffer)
- {
- buffer = JavascriptString::AllocateLeafAndCopySz(recycler, content, cchUseBoundLength);
- }
- return T::New(stringTypeStatic, buffer, cchUseBoundLength, recycler);
- }
- JavascriptString* JavascriptString::NewWithSz(__in_z const char16 * content, ScriptContext * scriptContext)
- {
- AssertMsg(content != nullptr, "NULL value passed to JavascriptString::New");
- return NewWithBuffer(content, GetBufferLength(content), scriptContext);
- }
- JavascriptString* JavascriptString::NewWithArenaSz(__in_z const char16 * content, ScriptContext * scriptContext)
- {
- AssertMsg(content != nullptr, "NULL value passed to JavascriptString::New");
- return NewWithArenaBuffer(content, GetBufferLength(content), scriptContext);
- }
- JavascriptString* JavascriptString::NewWithBuffer(__in_ecount(cchUseLength) const char16 * content, charcount_t cchUseLength, ScriptContext * scriptContext)
- {
- return NewWithBufferT<LiteralString, false>(content, cchUseLength, scriptContext);
- }
- JavascriptString* JavascriptString::NewWithArenaBuffer(__in_ecount(cchUseLength) const char16* content, charcount_t cchUseLength, ScriptContext* scriptContext)
- {
- return NewWithBufferT<ArenaLiteralString, false>(content, cchUseLength, scriptContext);
- }
- JavascriptString* JavascriptString::NewCopySz(__in_z const char16* content, ScriptContext* scriptContext)
- {
- return NewCopyBuffer(content, GetBufferLength(content), scriptContext);
- }
- JavascriptString* JavascriptString::NewCopyBuffer(__in_ecount(cchUseLength) const char16* content, charcount_t cchUseLength, ScriptContext* scriptContext)
- {
- return NewWithBufferT<LiteralString, true>(content, cchUseLength, scriptContext);
- }
- JavascriptString* JavascriptString::NewCopySzFromArena(__in_z const char16* content, ScriptContext* scriptContext, ArenaAllocator *arena)
- {
- AssertMsg(content != nullptr, "NULL value passed to JavascriptString::New");
- charcount_t cchUseLength = JavascriptString::GetBufferLength(content);
- char16* buffer = JavascriptString::AllocateAndCopySz(arena, content, cchUseLength);
- return ArenaLiteralString::New(scriptContext->GetLibrary()->GetStringTypeStatic(),
- buffer, cchUseLength, arena);
- }
- Var JavascriptString::NewInstance(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- AssertMsg(args.Info.Count > 0, "Negative argument count");
- // SkipDefaultNewObject function flag should have prevented the default object from
- // being created, except when call true a host dispatch.
- Var newTarget = callInfo.Flags & CallFlags_NewTarget ? args.Values[args.Info.Count] : args[0];
- bool isCtorSuperCall = (callInfo.Flags & CallFlags_New) && newTarget != nullptr && RecyclableObject::Is(newTarget);
- Assert(isCtorSuperCall || !(callInfo.Flags & CallFlags_New) || args[0] == nullptr
- || JavascriptOperators::GetTypeId(args[0]) == TypeIds_HostDispatch);
- JavascriptString* str;
- Var result;
- if (args.Info.Count > 1)
- {
- if (JavascriptSymbol::Is(args[1]) && !(callInfo.Flags & CallFlags_New))
- {
- // By ES2015 21.1.1.1 step 2, calling the String constructor directly results in an explicit ToString, which does not throw.
- return JavascriptSymbol::ToString(JavascriptSymbol::FromVar(args[1])->GetValue(), scriptContext);
- // Calling with new is an implicit ToString on the Symbol, resulting in a throw. For this case we can let JavascriptConversion handle the call.
- }
- str = JavascriptConversion::ToString(args[1], scriptContext);
- }
- else
- {
- str = scriptContext->GetLibrary()->GetEmptyString();
- }
- if (callInfo.Flags & CallFlags_New)
- {
- result = scriptContext->GetLibrary()->CreateStringObject(str);
- }
- else
- {
- result = str;
- }
- return isCtorSuperCall ?
- JavascriptOperators::OrdinaryCreateFromConstructor(RecyclableObject::FromVar(newTarget), RecyclableObject::FromVar(result), nullptr, scriptContext) :
- result;
- }
- // static
- bool IsValidCharCount(size_t charCount)
- {
- return charCount <= JavascriptString::MaxCharLength;
- }
- JavascriptString::JavascriptString(StaticType * type)
- : RecyclableObject(type), m_charLength(0), m_pszValue(0)
- {
- Assert(type->GetTypeId() == TypeIds_String);
- }
- JavascriptString::JavascriptString(StaticType * type, charcount_t charLength, const char16* szValue)
- : RecyclableObject(type), m_charLength(charLength), m_pszValue(szValue)
- {
- Assert(type->GetTypeId() == TypeIds_String);
- AssertMsg(IsValidCharCount(charLength), "String length is out of range");
- }
- _Ret_range_(m_charLength, m_charLength)
- charcount_t JavascriptString::GetLength() const
- {
- return m_charLength;
- }
- int JavascriptString::GetLengthAsSignedInt() const
- {
- Assert(IsValidCharCount(m_charLength));
- return static_cast<int>(m_charLength);
- }
- const char16* JavascriptString::UnsafeGetBuffer() const
- {
- return m_pszValue;
- }
- void JavascriptString::SetLength(charcount_t newLength)
- {
- if (!IsValidCharCount(newLength))
- {
- JavascriptExceptionOperators::ThrowOutOfMemory(this->GetScriptContext());
- }
- m_charLength = newLength;
- }
- void JavascriptString::SetBuffer(const char16* buffer)
- {
- m_pszValue = buffer;
- }
- bool JavascriptString::IsValidIndexValue(charcount_t idx) const
- {
- return IsValidCharCount(idx) && idx < GetLength();
- }
- bool JavascriptString::Is(Var aValue)
- {
- return JavascriptOperators::GetTypeId(aValue) == TypeIds_String;
- }
- JavascriptString* JavascriptString::FromVar(Var aValue)
- {
- AssertMsg(Is(aValue), "Ensure var is actually a 'JavascriptString'");
- return static_cast<JavascriptString *>(RecyclableObject::FromVar(aValue));
- }
- charcount_t
- JavascriptString::GetBufferLength(const char16 * content)
- {
- size_t cchActual = wcslen(content);
- #if defined(_M_X64_OR_ARM64)
- if (!IsValidCharCount(cchActual))
- {
- // Limit javascript string to 31-bit length
- Js::Throw::OutOfMemory();
- }
- #else
- // There shouldn't be enough memory to have UINT_MAX character.
- // INT_MAX is the upper bound for 32-bit;
- Assert(IsValidCharCount(cchActual));
- #endif
- return static_cast<charcount_t>(cchActual);
- }
- charcount_t
- JavascriptString::GetBufferLength(
- const char16 * content, // Value to examine
- int charLengthOrMinusOne) // Optional length, in characters
- {
- //
- // Determine the actual length, in characters, not including a terminating '\0':
- // - If a length was not specified (charLength < 0), search for a terminating '\0'.
- //
- charcount_t cchActual;
- if (charLengthOrMinusOne < 0)
- {
- AssertMsg(charLengthOrMinusOne == -1, "The only negative value allowed is -1");
- cchActual = GetBufferLength(content);
- }
- else
- {
- cchActual = static_cast<charcount_t>(charLengthOrMinusOne);
- }
- #ifdef CHECK_STRING
- // removed this to accommodate much larger string constant in regex-dna.js
- if (cchActual > 64 * 1024)
- {
- //
- // String was probably not '\0' terminated:
- // - We need to validate that the string's contents always fit within 1 GB to avoid
- // overflow checking on 32-bit when using 'int' for 'byte *' pointer operations.
- //
- Throw::OutOfMemory(); // TODO: determine argument error
- }
- #endif
- return cchActual;
- }
- template< size_t N >
- Var JavascriptString::StringBracketHelper(Arguments args, ScriptContext *scriptContext, const char16(&tag)[N])
- {
- CompileAssert(0 < N && N <= JavascriptString::MaxCharLength);
- return StringBracketHelper(args, scriptContext, tag, static_cast<charcount_t>(N - 1), nullptr, 0);
- }
- template< size_t N1, size_t N2 >
- Var JavascriptString::StringBracketHelper(Arguments args, ScriptContext *scriptContext, const char16(&tag)[N1], const char16(&prop)[N2])
- {
- CompileAssert(0 < N1 && N1 <= JavascriptString::MaxCharLength);
- CompileAssert(0 < N2 && N2 <= JavascriptString::MaxCharLength);
- return StringBracketHelper(args, scriptContext, tag, static_cast<charcount_t>(N1 - 1), prop, static_cast<charcount_t>(N2 - 1));
- }
- BOOL JavascriptString::BufferEquals(__in_ecount(otherLength) LPCWSTR otherBuffer, __in charcount_t otherLength)
- {
- return otherLength == this->GetLength() &&
- JsUtil::CharacterBuffer<WCHAR>::StaticEquals(this->GetString(), otherBuffer, otherLength);
- }
- BOOL JavascriptString::HasItemAt(charcount_t index)
- {
- return IsValidIndexValue(index);
- }
- BOOL JavascriptString::GetItemAt(charcount_t index, Var* value)
- {
- if (!IsValidIndexValue(index))
- {
- return false;
- }
- char16 character = GetItem(index);
- *value = this->GetLibrary()->GetCharStringCache().GetStringForChar(character);
- return true;
- }
- char16 JavascriptString::GetItem(charcount_t index)
- {
- AssertMsg( IsValidIndexValue(index), "Must specify valid character");
- const char16 *str = this->GetString();
- return str[index];
- }
- void JavascriptString::CopyHelper(__out_ecount(countNeeded) char16 *dst, __in_ecount(countNeeded) const char16 * str, charcount_t countNeeded)
- {
- switch(countNeeded)
- {
- case 0:
- return;
- case 1:
- dst[0] = str[0];
- break;
- case 3:
- dst[2] = str[2];
- goto case_2;
- case 5:
- dst[4] = str[4];
- goto case_4;
- case 7:
- dst[6] = str[6];
- goto case_6;
- case 9:
- dst[8] = str[8];
- goto case_8;
- case 10:
- *(uint32 *)(dst+8) = *(uint32*)(str+8);
- // FALLTHROUGH
- case 8:
- case_8:
- *(uint32 *)(dst+6) = *(uint32*)(str+6);
- // FALLTHROUGH
- case 6:
- case_6:
- *(uint32 *)(dst+4) = *(uint32*)(str+4);
- // FALLTHROUGH
- case 4:
- case_4:
- *(uint32 *)(dst+2) = *(uint32*)(str+2);
- // FALLTHROUGH
- case 2:
- case_2:
- *(uint32 *)(dst) = *(uint32*)str;
- break;
- default:
- js_memcpy_s(dst, sizeof(char16) * countNeeded, str, sizeof(char16) * countNeeded);
- }
- }
- __inline JavascriptString* JavascriptString::ConcatDestructive(JavascriptString* pstRight)
- {
- Assert(pstRight);
- if(!IsFinalized())
- {
- if(CompoundString::Is(this))
- {
- return ConcatDestructive_Compound(pstRight);
- }
- if(VirtualTableInfo<ConcatString>::HasVirtualTable(this))
- {
- JavascriptString *const s = ConcatDestructive_ConcatToCompound(pstRight);
- if(s)
- {
- return s;
- }
- }
- }
- else
- {
- const CharCount leftLength = GetLength();
- const CharCount rightLength = pstRight->GetLength();
- if(leftLength == 0 || rightLength == 0)
- {
- return ConcatDestructive_OneEmpty(pstRight);
- }
- if(CompoundString::ShouldAppendChars(leftLength) && CompoundString::ShouldAppendChars(rightLength))
- {
- return ConcatDestructive_CompoundAppendChars(pstRight);
- }
- }
- #ifdef PROFILE_STRINGS
- StringProfiler::RecordConcatenation(GetScriptContext(), GetLength(), pstRight->GetLength(), ConcatType_ConcatTree);
- #endif
- if(PHASE_TRACE_StringConcat)
- {
- Output::Print(
- _u("JavascriptString::ConcatDestructive(\"%.8s%s\") - creating ConcatString\n"),
- pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
- !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
- Output::Flush();
- }
- return ConcatString::New(this, pstRight);
- }
- JavascriptString* JavascriptString::ConcatDestructive_Compound(JavascriptString* pstRight)
- {
- Assert(CompoundString::Is(this));
- Assert(pstRight);
- #ifdef PROFILE_STRINGS
- StringProfiler::RecordConcatenation(GetScriptContext(), GetLength(), pstRight->GetLength(), ConcatType_CompoundString);
- #endif
- if(PHASE_TRACE_StringConcat)
- {
- Output::Print(
- _u("JavascriptString::ConcatDestructive(\"%.8s%s\") - appending to CompoundString\n"),
- pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
- !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
- Output::Flush();
- }
- CompoundString *const leftCs = CompoundString::FromVar(this);
- leftCs->PrepareForAppend();
- leftCs->Append(pstRight);
- return this;
- }
- JavascriptString* JavascriptString::ConcatDestructive_ConcatToCompound(JavascriptString* pstRight)
- {
- Assert(VirtualTableInfo<ConcatString>::HasVirtualTable(this));
- Assert(pstRight);
- const ConcatString *const leftConcatString = static_cast<const ConcatString *>(this);
- JavascriptString *const leftLeftString = leftConcatString->LeftString();
- if(VirtualTableInfo<ConcatString>::HasVirtualTable(leftLeftString))
- {
- #ifdef PROFILE_STRINGS
- StringProfiler::RecordConcatenation(GetScriptContext(), GetLength(), pstRight->GetLength(), ConcatType_CompoundString);
- #endif
- if(PHASE_TRACE_StringConcat)
- {
- Output::Print(
- _u("JavascriptString::ConcatDestructive(\"%.8s%s\") - converting ConcatString to CompoundString\n"),
- pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
- !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
- Output::Flush();
- }
- const ConcatString *const leftLeftConcatString = static_cast<const ConcatString *>(leftConcatString->LeftString());
- CompoundString *const cs = CompoundString::NewWithPointerCapacity(8, GetLibrary());
- cs->Append(leftLeftConcatString->LeftString());
- cs->Append(leftLeftConcatString->RightString());
- cs->Append(leftConcatString->RightString());
- cs->Append(pstRight);
- return cs;
- }
- return nullptr;
- }
- JavascriptString* JavascriptString::ConcatDestructive_OneEmpty(JavascriptString* pstRight)
- {
- Assert(pstRight);
- Assert(GetLength() == 0 || pstRight->GetLength() == 0);
- #ifdef PROFILE_STRINGS
- StringProfiler::RecordConcatenation(GetScriptContext(), GetLength(), pstRight->GetLength());
- #endif
- if(PHASE_TRACE_StringConcat)
- {
- Output::Print(
- _u("JavascriptString::ConcatDestructive(\"%.8s%s\") - one side empty, using other side\n"),
- pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
- !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
- Output::Flush();
- }
- if(GetLength() == 0)
- {
- return CompoundString::GetImmutableOrScriptUnreferencedString(pstRight);
- }
- Assert(CompoundString::GetImmutableOrScriptUnreferencedString(this) == this);
- return this;
- }
- JavascriptString* JavascriptString::ConcatDestructive_CompoundAppendChars(JavascriptString* pstRight)
- {
- Assert(pstRight);
- Assert(
- GetLength() != 0 &&
- pstRight->GetLength() != 0 &&
- (CompoundString::ShouldAppendChars(GetLength()) || CompoundString::ShouldAppendChars(pstRight->GetLength())));
- #ifdef PROFILE_STRINGS
- StringProfiler::RecordConcatenation(GetScriptContext(), GetLength(), pstRight->GetLength(), ConcatType_CompoundString);
- #endif
- if(PHASE_TRACE_StringConcat)
- {
- Output::Print(
- _u("JavascriptString::ConcatDestructive(\"%.8s%s\") - creating CompoundString, appending chars\n"),
- pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
- !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
- Output::Flush();
- }
- CompoundString *const cs = CompoundString::NewWithPointerCapacity(4, GetLibrary());
- cs->AppendChars(this);
- cs->AppendChars(pstRight);
- return cs;
- }
- __inline JavascriptString* JavascriptString::Concat(JavascriptString* pstLeft, JavascriptString* pstRight)
- {
- AssertMsg(pstLeft != nullptr, "Must have a valid left string");
- AssertMsg(pstRight != nullptr, "Must have a valid right string");
- if(!pstLeft->IsFinalized())
- {
- if(CompoundString::Is(pstLeft))
- {
- return Concat_Compound(pstLeft, pstRight);
- }
- if(VirtualTableInfo<ConcatString>::HasVirtualTable(pstLeft))
- {
- return Concat_ConcatToCompound(pstLeft, pstRight);
- }
- }
- else if(pstLeft->GetLength() == 0 || pstRight->GetLength() == 0)
- {
- return Concat_OneEmpty(pstLeft, pstRight);
- }
- if(pstLeft->GetLength() != 1 || pstRight->GetLength() != 1)
- {
- #ifdef PROFILE_STRINGS
- StringProfiler::RecordConcatenation(pstLeft->GetScriptContext(), pstLeft->GetLength(), pstRight->GetLength(), ConcatType_ConcatTree);
- #endif
- if(PHASE_TRACE_StringConcat)
- {
- Output::Print(
- _u("JavascriptString::Concat(\"%.8s%s\") - creating ConcatString\n"),
- pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
- !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
- Output::Flush();
- }
- return ConcatString::New(pstLeft, pstRight);
- }
- return Concat_BothOneChar(pstLeft, pstRight);
- }
- JavascriptString* JavascriptString::Concat_Compound(JavascriptString * pstLeft, JavascriptString * pstRight)
- {
- Assert(pstLeft);
- Assert(CompoundString::Is(pstLeft));
- Assert(pstRight);
- #ifdef PROFILE_STRINGS
- StringProfiler::RecordConcatenation(pstLeft->GetScriptContext(), pstLeft->GetLength(), pstRight->GetLength(), ConcatType_CompoundString);
- #endif
- if(PHASE_TRACE_StringConcat)
- {
- Output::Print(
- _u("JavascriptString::Concat(\"%.8s%s\") - cloning CompoundString, appending to clone\n"),
- pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
- !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
- Output::Flush();
- }
- // This is not a left-dead concat, but we can reuse available space in the left string
- // because it may be accessible by script code, append to a clone.
- const bool needAppend = pstRight->GetLength() != 0;
- CompoundString *const leftCs = CompoundString::FromVar(pstLeft)->Clone(needAppend);
- if(needAppend)
- {
- leftCs->Append(pstRight);
- }
- return leftCs;
- }
- JavascriptString* JavascriptString::Concat_ConcatToCompound(JavascriptString * pstLeft, JavascriptString * pstRight)
- {
- Assert(pstLeft);
- Assert(VirtualTableInfo<ConcatString>::HasVirtualTable(pstLeft));
- Assert(pstRight);
- #ifdef PROFILE_STRINGS
- StringProfiler::RecordConcatenation(pstLeft->GetScriptContext(), pstLeft->GetLength(), pstRight->GetLength(), ConcatType_CompoundString);
- #endif
- if(PHASE_TRACE_StringConcat)
- {
- Output::Print(
- _u("JavascriptString::Concat(\"%.8s%s\") - converting ConcatString to CompoundString\n"),
- pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
- !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
- Output::Flush();
- }
- const ConcatString *const leftConcatString = static_cast<const ConcatString *>(pstLeft);
- CompoundString *const cs = CompoundString::NewWithPointerCapacity(8, pstLeft->GetLibrary());
- cs->Append(leftConcatString->LeftString());
- cs->Append(leftConcatString->RightString());
- cs->Append(pstRight);
- return cs;
- }
- JavascriptString* JavascriptString::Concat_OneEmpty(JavascriptString * pstLeft, JavascriptString * pstRight)
- {
- Assert(pstLeft);
- Assert(pstRight);
- Assert(pstLeft->GetLength() == 0 || pstRight->GetLength() == 0);
- #ifdef PROFILE_STRINGS
- StringProfiler::RecordConcatenation(pstLeft->GetScriptContext(), pstLeft->GetLength(), pstRight->GetLength());
- #endif
- if(PHASE_TRACE_StringConcat)
- {
- Output::Print(
- _u("JavascriptString::Concat(\"%.8s%s\") - one side empty, using other side\n"),
- pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
- !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
- Output::Flush();
- }
- if(pstLeft->GetLength() == 0)
- {
- return CompoundString::GetImmutableOrScriptUnreferencedString(pstRight);
- }
- Assert(CompoundString::GetImmutableOrScriptUnreferencedString(pstLeft) == pstLeft);
- return pstLeft;
- }
- JavascriptString* JavascriptString::Concat_BothOneChar(JavascriptString * pstLeft, JavascriptString * pstRight)
- {
- Assert(pstLeft);
- Assert(pstLeft->GetLength() == 1);
- Assert(pstRight);
- Assert(pstRight->GetLength() == 1);
- #ifdef PROFILE_STRINGS
- StringProfiler::RecordConcatenation(pstLeft->GetScriptContext(), pstLeft->GetLength(), pstRight->GetLength(), ConcatType_BufferString);
- #endif
- if(PHASE_TRACE_StringConcat)
- {
- Output::Print(
- _u("JavascriptString::Concat(\"%.8s%s\") - both sides length 1, creating BufferStringBuilder::WritableString\n"),
- pstRight->IsFinalized() ? pstRight->GetString() : _u(""),
- !pstRight->IsFinalized() || pstRight->GetLength() > 8 ? _u("...") : _u(""));
- Output::Flush();
- }
- ScriptContext* scriptContext = pstLeft->GetScriptContext();
- BufferStringBuilder builder(2, scriptContext);
- char16 * stringBuffer = builder.DangerousGetWritableBuffer();
- stringBuffer[0] = *pstLeft->GetString();
- stringBuffer[1] = *pstRight->GetString();
- return builder.ToString();
- }
- Var JavascriptString::EntryCharAt(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- //
- // General algorithm:
- // 1. Call CheckObjectCoercible passing the this value as its argument.
- // 2. Let S be the result of calling ToString, giving it the this value as its argument.
- // 3. Let position be ToInteger(pos).
- // 4. Let size be the number of characters in S.
- // 5. If position < 0 or position = size, return the empty string.
- // 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.
- // NOTE
- // 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.
- //
- JavascriptString * pThis = nullptr;
- GetThisStringArgument(args, scriptContext, _u("String.prototype.charAt"), &pThis);
- charcount_t idxPosition = 0;
- if (args.Info.Count > 1)
- {
- idxPosition = ConvertToIndex(args[1], scriptContext);
- }
- //
- // Get the character at the specified position.
- //
- Var value;
- if (pThis->GetItemAt(idxPosition, &value))
- {
- return value;
- }
- else
- {
- return scriptContext->GetLibrary()->GetEmptyString();
- }
- }
- Var JavascriptString::EntryCharCodeAt(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- //
- // General algorithm:
- // 1. Call CheckObjectCoercible passing the this value as its argument.
- // 2. Let S be the result of calling ToString, giving it the this value as its argument.
- // 3. Let position be ToInteger(pos).
- // 4. Let size be the number of characters in S.
- // 5. If position < 0 or position = size, return NaN.
- // 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.
- // NOTE
- // 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.
- //
- JavascriptString * pThis = nullptr;
- GetThisStringArgument(args, scriptContext, _u("String.prototype.charCodeAt"), &pThis);
- charcount_t idxPosition = 0;
- if (args.Info.Count > 1)
- {
- idxPosition = ConvertToIndex(args[1], scriptContext);
- }
- //
- // Get the character at the specified position.
- //
- charcount_t charLength = pThis->GetLength();
- if (idxPosition >= charLength)
- {
- return scriptContext->GetLibrary()->GetNaN();
- }
- return TaggedInt::ToVarUnchecked(pThis->GetItem(idxPosition));
- }
- Var JavascriptString::EntryCodePointAt(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- JavascriptString * pThis = nullptr;
- GetThisStringArgument(args, scriptContext, _u("String.prototype.codePointAt"), &pThis);
- charcount_t idxPosition = 0;
- if (args.Info.Count > 1)
- {
- idxPosition = ConvertToIndex(args[1], scriptContext);
- }
- charcount_t charLength = pThis->GetLength();
- if (idxPosition >= charLength)
- {
- return scriptContext->GetLibrary()->GetUndefined();
- }
- // A surrogate pair consists of two characters, a lower part and a higher part.
- // Lower part is in range [0xD800 - 0xDBFF], while the higher is [0xDC00 - 0xDFFF].
- char16 first = pThis->GetItem(idxPosition);
- if (first >= 0xD800u && first < 0xDC00u && (uint)(idxPosition + 1) < pThis->GetLength())
- {
- char16 second = pThis->GetItem(idxPosition + 1);
- if (second >= 0xDC00 && second < 0xE000)
- {
- return TaggedInt::ToVarUnchecked(NumberUtilities::SurrogatePairAsCodePoint(first, second));
- }
- }
- return TaggedInt::ToVarUnchecked(first);
- }
- Var JavascriptString::EntryConcat(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- //
- // General algorithm:
- // 1. Call CheckObjectCoercible passing the this value as its argument.
- // 2. Let S be the result of calling ToString, giving it the this value as its argument.
- // 3. Let args be an internal list that is a copy of the argument list passed to this function.
- // 4. Let R be S.
- // 5. Repeat, while args is not empty
- // Remove the first element from args and let next be the value of that element.
- // Let R be the string value consisting of the characters in the previous value of R followed by the characters of ToString(next).
- // 6. Return R.
- //
- // NOTE
- // 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.
- //
- if(args.Info.Count == 0)
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedString, _u("String.prototype.concat"));
- }
- AssertMsg(args.Info.Count > 0, "Negative argument count");
- if (!JavascriptConversion::CheckObjectCoercible(args[0], scriptContext))
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, _u("String.prototype.concat"));
- }
- JavascriptString* pstr = nullptr;
- JavascriptString* accum = nullptr;
- for (uint index = 0; index < args.Info.Count; index++)
- {
- if (JavascriptString::Is(args[index]))
- {
- pstr = JavascriptString::FromVar(args[index]);
- }
- else
- {
- pstr = JavascriptConversion::ToString(args[index], scriptContext);
- }
- if (index == 0)
- {
- accum = pstr;
- }
- else
- {
- accum = Concat(accum,pstr);
- }
- }
- return accum;
- }
- Var JavascriptString::EntryFromCharCode(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- //
- // Construct a new string instance to contain all of the explicit parameters:
- // - Don't include the 'this' parameter.
- //
- if(args.Info.Count == 0)
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedString, _u("String.fromCharCode"));
- }
- AssertMsg(args.Info.Count > 0, "Negative argument count");
- int charLength = args.Info.Count - 1;
- // Special case for single char
- if( charLength == 1 )
- {
- char16 ch = JavascriptConversion::ToUInt16(args[1], scriptContext);
- return scriptContext->GetLibrary()->GetCharStringCache().GetStringForChar(ch);
- }
- BufferStringBuilder builder(charLength,scriptContext);
- char16 * stringBuffer = builder.DangerousGetWritableBuffer();
- //
- // Call ToUInt16 for each parameter, storing the character at the appropriate position.
- //
- for (uint idxArg = 1; idxArg < args.Info.Count; idxArg++)
- {
- *stringBuffer++ = JavascriptConversion::ToUInt16(args[idxArg], scriptContext);
- }
- //
- // Return the new string instance.
- //
- return builder.ToString();
- }
- Var JavascriptString::EntryFromCodePoint(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- AssertMsg(args.Info.Count > 0, "Negative argument count");
- if (args.Info.Count <= 1)
- {
- return scriptContext->GetLibrary()->GetEmptyString();
- }
- else if (args.Info.Count == 2)
- {
- // Special case for a single char string formed from only code point in range [0x0, 0xFFFF]
- double num = JavascriptConversion::ToNumber(args[1], scriptContext);
- if (!NumberUtilities::IsFinite(num))
- {
- JavascriptError::ThrowRangeError(scriptContext, JSERR_InvalidCodePoint);
- }
- if (num < 0 || num > 0x10FFFF || floor(num) != num)
- {
- JavascriptError::ThrowRangeErrorVar(scriptContext, JSERR_InvalidCodePoint, Js::JavascriptConversion::ToString(args[1], scriptContext)->GetSz());
- }
- if (num < 0x10000)
- {
- return scriptContext->GetLibrary()->GetCharStringCache().GetStringForChar((uint16)num);
- }
- }
- BEGIN_TEMP_ALLOCATOR(tempAllocator, scriptContext, _u("fromCodePoint"));
- // Create a temporary buffer that is double the arguments count (in case all are surrogate pairs)
- size_t bufferLength = (args.Info.Count - 1) * 2;
- char16 *tempBuffer = AnewArray(tempAllocator, char16, bufferLength);
- uint32 count = 0;
- for (uint i = 1; i < args.Info.Count; i++)
- {
- double num = JavascriptConversion::ToNumber(args[i], scriptContext);
- if (!NumberUtilities::IsFinite(num))
- {
- JavascriptError::ThrowRangeError(scriptContext, JSERR_InvalidCodePoint);
- }
- if (num < 0 || num > 0x10FFFF || floor(num) != num)
- {
- JavascriptError::ThrowRangeErrorVar(scriptContext, JSERR_InvalidCodePoint, Js::JavascriptConversion::ToString(args[i], scriptContext)->GetSz());
- }
- if (num < 0x10000)
- {
- __analysis_assume(count < bufferLength);
- Assert(count < bufferLength);
- #pragma prefast(suppress: 22102, "I have an assert in place to guard against overflow. Even though this should never happen.")
- tempBuffer[count] = (char16)num;
- count++;
- }
- else
- {
- __analysis_assume(count + 1 < bufferLength);
- Assert(count + 1 < bufferLength);
- NumberUtilities::CodePointAsSurrogatePair((codepoint_t)num, (tempBuffer + count), (tempBuffer + count + 1));
- count += 2;
- }
- }
- // Create a string of appropriate length
- __analysis_assume(count <= bufferLength);
- Assert(count <= bufferLength);
- JavascriptString *toReturn = JavascriptString::NewCopyBuffer(tempBuffer, count, scriptContext);
- END_TEMP_ALLOCATOR(tempAllocator, scriptContext);
- return toReturn;
- }
- Var JavascriptString::EntryIndexOf(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- return JavascriptNumber::ToVar(IndexOf(args, scriptContext, _u("String.prototype.indexOf"), true), scriptContext);
- }
- int JavascriptString::IndexOf(ArgumentReader& args, ScriptContext* scriptContext, const char16* apiNameForErrorMsg, bool isRegExpAnAllowedArg)
- {
- // The algorithm steps in the spec are the same between String.prototype.indexOf and
- // String.prototype.includes, except that includes returns true if an index is found,
- // false otherwise. Share the implementation between these two APIs.
- //
- // 1. Call CheckObjectCoercible passing the this value as its argument.
- // 2. Let S be the result of calling ToString, giving it the this value as its argument.
- // 3. Let searchStr be ToString(searchString).
- // 4. Let pos be ToInteger(position). (If position is undefined, this step produces the value 0).
- // 5. Let len be the number of characters in S.
- // 6. Let start be min(max(pos, 0), len).
- // 7. Let searchLen be the number of characters in searchStr.
- // 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.
- // NOTE
- // 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.
- //
- JavascriptString * pThis;
- JavascriptString * searchString;
- GetThisAndSearchStringArguments(args, scriptContext, apiNameForErrorMsg, &pThis, &searchString, isRegExpAnAllowedArg);
- int len = pThis->GetLength();
- int searchLen = searchString->GetLength();
- int position = 0;
- if (args.Info.Count > 2)
- {
- if (JavascriptOperators::IsUndefinedObject(args[2], scriptContext))
- {
- position = 0;
- }
- else
- {
- position = ConvertToIndex(args[2], scriptContext); // this is to adjust corner cases like MAX_VALUE
- position = min(max(position, 0), len); // adjust position within string limits
- }
- }
- // Zero length search strings are always found at the current search position
- if (searchLen == 0)
- {
- return position;
- }
- int result = -1;
- if (position < pThis->GetLengthAsSignedInt())
- {
- const char16* searchStr = searchString->GetString();
- const char16* inputStr = pThis->GetString();
- if (searchLen == 1)
- {
- int i = position;
- for(; i < len && inputStr[i] != *searchStr ; i++);
- if (i < len)
- {
- result = i;
- }
- }
- else
- {
- JmpTable jmpTable;
- bool fAsciiJumpTable = BuildLastCharForwardBoyerMooreTable(jmpTable, searchStr, searchLen);
- if (!fAsciiJumpTable)
- {
- result = JavascriptString::strstr(pThis, searchString, false, position);
- }
- else
- {
- result = IndexOfUsingJmpTable(jmpTable, inputStr, len, searchStr, searchLen, position);
- }
- }
- }
- return result;
- }
- Var JavascriptString::EntryLastIndexOf(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- //
- // 1. Call CheckObjectCoercible passing the this value as its argument.
- // 2. Let S be the result of calling ToString, giving it the this value as its argument.
- // 3. Let searchStr be ToString(searchString).
- // 4. Let numPos be ToNumber(position). (If position is undefined, this step produces the value NaN).
- // 5. If numPos is NaN, let pos be +?; otherwise, let pos be ToInteger(numPos).
- // 6. Let len be the number of characters in S.
- // 7. Let start min(max(pos, 0), len).
- // 8. Let searchLen be the number of characters in searchStr.
- // 9. Return the largest possible nonnegative integer k not larger 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.
- // NOTE
- // The lastIndexOf 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.
- //
- JavascriptString * pThis = nullptr;
- GetThisStringArgument(args, scriptContext, _u("String.prototype.lastIndexOf"), &pThis);
- // default search string if the search argument is not provided
- JavascriptString * searchArg;
- if(args.Info.Count > 1)
- {
- if (JavascriptString::Is(args[1]))
- {
- searchArg = JavascriptString::FromVar(args[1]);
- }
- else
- {
- searchArg = JavascriptConversion::ToString(args[1], scriptContext);
- }
- }
- else
- {
- searchArg = scriptContext->GetLibrary()->GetUndefinedDisplayString();
- }
- char16 const * const inputStr = pThis->GetString();
- const char16 * const searchStr = searchArg->GetString();
- const int len = pThis->GetLength();
- int position = len;
- int const searchLen = searchArg->GetLength();
- // Determine if the main string can't contain the search string by length
- if ( searchLen > len )
- {
- return JavascriptNumber::ToVar(-1, scriptContext);
- }
- if(args.Info.Count > 2)
- {
- double pos = JavascriptConversion::ToNumber(args[2], scriptContext);
- if (!JavascriptNumber::IsNan(pos))
- {
- pos = JavascriptConversion::ToInteger(pos);
- position = (int)min(max(pos, (double)0), (double)len); // adjust position within string limits
- }
- }
- int result = -1;
- // Zero length search strings are always found at the current search position
- if ( searchLen == 0 )
- {
- return JavascriptNumber::ToVar(position, scriptContext);
- }
- else if (searchLen == 1)
- {
- char16 const * current = inputStr + min(position,len - 1);
- while (*current != *searchStr)
- {
- current--;
- if (current < inputStr)
- {
- return JavascriptNumber::ToVar(result, scriptContext); //return -1
- }
- }
- result = (int)(current - inputStr);
- return JavascriptNumber::ToVar(result, scriptContext);
- }
- // Structure for a partial ASCII Boyer-Moore
- JmpTable jmpTable;
- bool fAsciiJumpTable = BuildFirstCharBackwardBoyerMooreTable(jmpTable, searchStr, searchLen);
- if (!fAsciiJumpTable)
- {
- char16 const * start = inputStr;
- char16 const * current = inputStr + min(position, len - 1);
- char16 const * searchStrEnd = searchStr + searchLen - 1;
- while (current >= (start + searchLen - 1))
- {
- char16 const * s1 = current;
- char16 const * s2 = searchStrEnd;
- while (s1 >= start && s2 >= searchStr && !(*s1 - *s2))
- {
- s1--, s2--;
- }
- if (s2 < searchStr)
- {
- result = (int)(current - start) - searchLen + 1;
- return JavascriptNumber::ToVar(result, scriptContext);
- }
- current--;
- }
- return JavascriptNumber::ToVar(result, scriptContext);
- }
- else
- {
- result = LastIndexOfUsingJmpTable(jmpTable, inputStr, len, searchStr, searchLen, position);
- }
- return JavascriptNumber::ToVar(result, scriptContext);
- }
- // Performs common ES spec steps for getting this argument in string form:
- // 1. Let O be CHeckObjectCoercible(this value).
- // 2. Let S be ToString(O).
- // 3. ReturnIfAbrupt(S).
- void JavascriptString::GetThisStringArgument(ArgumentReader& args, ScriptContext* scriptContext, const char16* apiNameForErrorMsg, JavascriptString** ppThis)
- {
- if (args.Info.Count == 0)
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedString, apiNameForErrorMsg);
- }
- AssertMsg(args.Info.Count > 0, "Negative argument count");
- JavascriptString * pThis;
- if (JavascriptString::Is(args[0]))
- {
- pThis = JavascriptString::FromVar(args[0]);
- }
- else
- {
- pThis = JavascriptConversion::CoerseString(args[0], scriptContext , apiNameForErrorMsg);
- }
- *ppThis = pThis;
- }
- // Performs common ES spec steps for getting this and first parameter arguments in string form:
- // 1. Let O be CHeckObjectCoercible(this value).
- // 2. Let S be ToString(O).
- // 3. ReturnIfAbrupt(S).
- // 4. Let otherStr be ToString(firstArg).
- // 5. ReturnIfAbrupt(otherStr).
- void JavascriptString::GetThisAndSearchStringArguments(ArgumentReader& args, ScriptContext* scriptContext, const char16* apiNameForErrorMsg, JavascriptString** ppThis, JavascriptString** ppSearch, bool isRegExpAnAllowedArg)
- {
- GetThisStringArgument(args, scriptContext, apiNameForErrorMsg, ppThis);
- JavascriptString * pSearch = scriptContext->GetLibrary()->GetUndefinedDisplayString();
- if (args.Info.Count > 1)
- {
- if (!isRegExpAnAllowedArg && JavascriptRegExp::IsRegExpLike(args[1], scriptContext))
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_FirstCannotBeRegExp, apiNameForErrorMsg);
- }
- else if (JavascriptString::Is(args[1]))
- {
- pSearch = JavascriptString::FromVar(args[1]);
- }
- else
- {
- pSearch = JavascriptConversion::ToString(args[1], scriptContext);
- }
- }
- *ppSearch = pSearch;
- }
- Var JavascriptString::EntryLocaleCompare(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- if(args.Info.Count == 0)
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedString, _u("String.prototype.localeCompare"));
- }
- AssertMsg(args.Info.Count > 0, "Negative argument count");
- JavascriptString * pThis;
- JavascriptString * pThat;
- GetThisAndSearchStringArguments(args, scriptContext, _u("String.prototype.localeCompare"), &pThis, &pThat, true);
- #ifdef ENABLE_INTL_OBJECT
- if (CONFIG_FLAG(IntlBuiltIns) && scriptContext->IsIntlEnabled())
- {
- EngineInterfaceObject* nativeEngineInterfaceObj = scriptContext->GetLibrary()->GetEngineInterfaceObject();
- if (nativeEngineInterfaceObj)
- {
- IntlEngineInterfaceExtensionObject* intlExtensionObject = static_cast<IntlEngineInterfaceExtensionObject*>(nativeEngineInterfaceObj->GetEngineExtension(EngineInterfaceExtensionKind_Intl));
- if (args.Info.Count == 2)
- {
- auto undefined = scriptContext->GetLibrary()->GetUndefined();
- CallInfo toPass(callInfo.Flags, 7);
- return intlExtensionObject->EntryIntl_CompareString(function, toPass, undefined, pThis, pThat, undefined, undefined, undefined, undefined);
- }
- else
- {
- JavascriptFunction* func = intlExtensionObject->GetStringLocaleCompare();
- if (func)
- {
- return func->CallFunction(args);
- }
- // Initialize String.prototype.toLocaleCompare
- scriptContext->GetLibrary()->InitializeIntlForStringPrototype();
- func = intlExtensionObject->GetStringLocaleCompare();
- if (func)
- {
- return func->CallFunction(args);
- }
- }
- }
- }
- #endif
- const char16* pThisStr = pThis->GetString();
- int thisStrCount = pThis->GetLength();
- const char16* pThatStr = pThat->GetString();
- int thatStrCount = pThat->GetLength();
- LCID lcid = GetUserDefaultLCID();
- int result = CompareStringW(lcid, NULL, pThisStr, thisStrCount, pThatStr, thatStrCount );
- if (result == 0)
- {
- // TODO there is no spec on the error thrown here.
- // When the support for HR errors is implemented replace this with the same error reported by v5.8
- JavascriptError::ThrowRangeError(function->GetScriptContext(),
- VBSERR_InternalError /* TODO-ERROR: _u("Failed compare operation")*/ );
- }
- return JavascriptNumber::ToVar(result-2, scriptContext);
- }
- Var JavascriptString::EntryMatch(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- PCWSTR const varName = _u("String.prototype.match");
- AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, varName);
- auto fallback = [&](JavascriptString* stringObj)
- {
- Var regExp = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
- if (!scriptContext->GetConfig()->IsES6RegExSymbolsEnabled())
- {
- JavascriptRegExp * regExObj = JavascriptRegExp::CreateRegEx(regExp, nullptr, scriptContext);
- return RegexHelper::RegexMatch(
- scriptContext,
- regExObj,
- stringObj,
- RegexHelper::IsResultNotUsed(callInfo.Flags));
- }
- else
- {
- JavascriptRegExp * regExObj = JavascriptRegExp::CreateRegExNoCoerce(regExp, nullptr, scriptContext);
- Var symbolFn = GetRegExSymbolFunction(regExObj, PropertyIds::_symbolMatch, scriptContext);
- return CallRegExSymbolFunction<1>(symbolFn, regExObj, args, varName, scriptContext);
- }
- };
- return DelegateToRegExSymbolFunction<1>(args, PropertyIds::_symbolMatch, fallback, varName, scriptContext);
- }
- Var JavascriptString::EntryNormalize(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- JavascriptString *pThis = nullptr;
- GetThisStringArgument(args, scriptContext, _u("String.prototype.normalize"), &pThis);
- NORM_FORM form = NORM_FORM::NormalizationC;
- if (args.Info.Count >= 2 && !(JavascriptOperators::IsUndefinedObject(args.Values[1])))
- {
- JavascriptString *formStr = nullptr;
- if (JavascriptString::Is(args[1]))
- {
- formStr = JavascriptString::FromVar(args[1]);
- }
- else
- {
- formStr = JavascriptConversion::ToString(args[1], scriptContext);
- }
- if (formStr->BufferEquals(_u("NFD"), 3))
- {
- form = NORM_FORM::NormalizationD;
- }
- else if (formStr->BufferEquals(_u("NFKC"), 4))
- {
- form = NORM_FORM::NormalizationKC;
- }
- else if (formStr->BufferEquals(_u("NFKD"), 4))
- {
- form = NORM_FORM::NormalizationKD;
- }
- else if (!formStr->BufferEquals(_u("NFC"), 3))
- {
- JavascriptError::ThrowRangeErrorVar(scriptContext, JSERR_InvalidNormalizationForm, formStr->GetString());
- }
- }
- if (IsNormalizedString(form, pThis->GetSz(), pThis->GetLength()))
- {
- return JavascriptString::NewWithSz(pThis->GetSz(), scriptContext);
- }
- BEGIN_TEMP_ALLOCATOR(tempAllocator, scriptContext, _u("normalize"));
- charcount_t sizeEstimate = 0;
- char16* buffer = pThis->GetNormalizedString(form, tempAllocator, sizeEstimate);
- JavascriptString * retVal;
- if (buffer == nullptr)
- {
- Assert(sizeEstimate == 0);
- retVal = scriptContext->GetLibrary()->GetEmptyString();
- }
- else
- {
- retVal = JavascriptString::NewCopyBuffer(buffer, sizeEstimate, scriptContext);
- }
- END_TEMP_ALLOCATOR(tempAllocator, scriptContext);
- return retVal;
- }
- ///----------------------------------------------------------------------------
- /// String.raw(), as described in (ES6.0 (Draft 18): S21.1.2.4).
- ///----------------------------------------------------------------------------
- Var JavascriptString::EntryRaw(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- if (args.Info.Count < 2)
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, _u("String.raw"));
- }
- RecyclableObject* callSite;
- RecyclableObject* raw;
- Var rawVar;
- // Call ToObject on the first argument to get the callSite (which is also cooked string array)
- // ToObject returns false if the parameter is null or undefined
- if (!JavascriptConversion::ToObject(args[1], scriptContext, &callSite))
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, _u("String.raw"));
- }
- // Get the raw property from the callSite object
- if (!callSite->GetProperty(callSite, Js::PropertyIds::raw, &rawVar, nullptr, scriptContext))
- {
- rawVar = scriptContext->GetLibrary()->GetUndefined();
- }
- if (!JavascriptConversion::ToObject(rawVar, scriptContext, &raw))
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, _u("String.raw"));
- }
- int64 length = JavascriptConversion::ToLength(JavascriptOperators::OP_GetLength(raw, scriptContext), scriptContext);
- // If there are no raw strings (somehow), return empty string
- if (length <= 0)
- {
- return scriptContext->GetLibrary()->GetEmptyString();
- }
- // Get the first raw string
- Var var = JavascriptOperators::OP_GetElementI_UInt32(raw, 0, scriptContext);
- JavascriptString* string = JavascriptConversion::ToString(var, scriptContext);
- // If there is only one raw string, just return that one raw string (doesn't matter if there are replacements)
- if (length == 1)
- {
- return string;
- }
- // We aren't going to bail early so let's create a StringBuilder and put the first raw string in there
- CompoundString::Builder<64 * sizeof(void *) / sizeof(char16)> stringBuilder(scriptContext);
- stringBuilder.Append(string);
- // Each raw string is followed by a substitution expression except for the last one
- // We will always have one more string constant than substitution expression
- // `strcon1 ${expr1} strcon2 ${expr2} strcon3` = strcon1 + expr1 + strcon2 + expr2 + strcon3
- //
- // strcon1 --- step 1 (above)
- // expr1 \__ step 2
- // strcon2 /
- // expr2 \__ step 3
- // strcon3 /
- for (uint32 i = 1; i < length; ++i)
- {
- // First append the next substitution expression
- // If we have an arg at [i+1] use that one, otherwise empty string (which is nop)
- if (i+1 < args.Info.Count)
- {
- string = JavascriptConversion::ToString(args[i+1], scriptContext);
- stringBuilder.Append(string);
- }
- // Then append the next string (this will also cover the final string case)
- var = JavascriptOperators::OP_GetElementI_UInt32(raw, i, scriptContext);
- string = JavascriptConversion::ToString(var, scriptContext);
- stringBuilder.Append(string);
- }
- // CompoundString::Builder has saved our lives
- return stringBuilder.ToString();
- }
- Var JavascriptString::EntryReplace(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- PCWSTR const varName = _u("String.prototype.replace");
- AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, varName);
- Assert(!(callInfo.Flags & CallFlags_New));
- auto fallback = [&](JavascriptString* stringObj)
- {
- return DoStringReplace(args, callInfo, stringObj, scriptContext);
- };
- return DelegateToRegExSymbolFunction<2>(args, PropertyIds::_symbolReplace, fallback, varName, scriptContext);
- }
- Var JavascriptString::DoStringReplace(Arguments& args, CallInfo& callInfo, JavascriptString* input, ScriptContext* scriptContext)
- {
- //
- // TODO: Move argument processing into DirectCall with proper handling.
- //
- JavascriptRegExp * pRegEx = nullptr;
- JavascriptString * pMatch = nullptr;
- JavascriptString * pReplace = nullptr;
- JavascriptFunction* replacefn = nullptr;
- SearchValueHelper(scriptContext, ((args.Info.Count > 1)?args[1]:scriptContext->GetLibrary()->GetNull()), &pRegEx, &pMatch);
- ReplaceValueHelper(scriptContext, ((args.Info.Count > 2) ? args[2] : scriptContext->GetLibrary()->GetUndefined()), &replacefn, &pReplace);
- if (pRegEx != nullptr)
- {
- if (replacefn != nullptr)
- {
- return RegexHelper::RegexReplaceFunction(scriptContext, pRegEx, input, replacefn);
- }
- else
- {
- return RegexHelper::RegexReplace(scriptContext, pRegEx, input, pReplace, RegexHelper::IsResultNotUsed(callInfo.Flags));
- }
- }
- AssertMsg(pMatch != nullptr, "Match string shouldn't be null");
- if (replacefn != nullptr)
- {
- return RegexHelper::StringReplace(pMatch, input, replacefn);
- }
- else
- {
- if (callInfo.Flags & CallFlags_NotUsed)
- {
- return scriptContext->GetLibrary()->GetEmptyString();
- }
- return RegexHelper::StringReplace(pMatch, input, pReplace);
- }
- }
- void JavascriptString::SearchValueHelper(ScriptContext* scriptContext, Var aValue, JavascriptRegExp ** ppSearchRegEx, JavascriptString ** ppSearchString)
- {
- *ppSearchRegEx = nullptr;
- *ppSearchString = nullptr;
- // When the config is enabled, the operation is handled by a Symbol function (e.g. Symbol.replace).
- if (!scriptContext->GetConfig()->IsES6RegExSymbolsEnabled()
- && JavascriptRegExp::Is(aValue))
- {
- *ppSearchRegEx = JavascriptRegExp::FromVar(aValue);
- }
- else if (JavascriptString::Is(aValue))
- {
- *ppSearchString = JavascriptString::FromVar(aValue);
- }
- else
- {
- *ppSearchString = JavascriptConversion::ToString(aValue, scriptContext);
- }
- }
- void JavascriptString::ReplaceValueHelper(ScriptContext* scriptContext, Var aValue, JavascriptFunction ** ppReplaceFn, JavascriptString ** ppReplaceString)
- {
- *ppReplaceFn = nullptr;
- *ppReplaceString = nullptr;
- if (JavascriptFunction::Is(aValue))
- {
- *ppReplaceFn = JavascriptFunction::FromVar(aValue);
- }
- else if (JavascriptString::Is(aValue))
- {
- *ppReplaceString = JavascriptString::FromVar(aValue);
- }
- else
- {
- *ppReplaceString = JavascriptConversion::ToString(aValue, scriptContext);
- }
- }
- Var JavascriptString::EntrySearch(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- PCWSTR const varName = _u("String.prototype.search");
- AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, varName);
- auto fallback = [&](JavascriptString* stringObj)
- {
- Var regExp = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
- if (!scriptContext->GetConfig()->IsES6RegExSymbolsEnabled())
- {
- JavascriptRegExp * regExObj = JavascriptRegExp::CreateRegEx(regExp, nullptr, scriptContext);
- return RegexHelper::RegexSearch(scriptContext, regExObj, stringObj);
- }
- else
- {
- JavascriptRegExp * regExObj = JavascriptRegExp::CreateRegExNoCoerce(regExp, nullptr, scriptContext);
- Var symbolFn = GetRegExSymbolFunction(regExObj, PropertyIds::_symbolSearch, scriptContext);
- return CallRegExSymbolFunction<1>(symbolFn, regExObj, args, varName, scriptContext);
- }
- };
- return DelegateToRegExSymbolFunction<1>(args, PropertyIds::_symbolSearch, fallback, varName, scriptContext);
- }
- template<int argCount, typename FallbackFn>
- Var JavascriptString::DelegateToRegExSymbolFunction(ArgumentReader &args, PropertyId symbolPropertyId, FallbackFn fallback, PCWSTR varName, ScriptContext* scriptContext)
- {
- if (scriptContext->GetConfig()->IsES6RegExSymbolsEnabled())
- {
- if (args.Info.Count == 0 || !JavascriptConversion::CheckObjectCoercible(args[0], scriptContext))
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, varName);
- }
- if (args.Info.Count >= 2 && !JavascriptOperators::IsUndefinedOrNull(args[1]))
- {
- Var regExp = args[1];
- Var symbolFn = GetRegExSymbolFunction(regExp, symbolPropertyId, scriptContext);
- if (!JavascriptOperators::IsUndefinedOrNull(symbolFn))
- {
- return CallRegExSymbolFunction<argCount>(symbolFn, regExp, args, varName, scriptContext);
- }
- }
- }
- JavascriptString * pThis = nullptr;
- GetThisStringArgument(args, scriptContext, varName, &pThis);
- return fallback(pThis);
- }
- Var JavascriptString::GetRegExSymbolFunction(Var regExp, PropertyId propertyId, ScriptContext* scriptContext)
- {
- return JavascriptOperators::GetProperty(
- RecyclableObject::FromVar(JavascriptOperators::ToObject(regExp, scriptContext)),
- propertyId,
- scriptContext);
- }
- template<int argCount>
- Var JavascriptString::CallRegExSymbolFunction(Var fn, Var regExp, Arguments& args, PCWSTR const varName, ScriptContext* scriptContext)
- {
- if (!JavascriptConversion::IsCallable(fn))
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_Invalid, varName);
- }
- RecyclableObject* fnObj = RecyclableObject::FromVar(fn);
- return CallRegExFunction<argCount>(fnObj, regExp, args);
- }
- template<>
- Var JavascriptString::CallRegExFunction<1>(RecyclableObject* fnObj, Var regExp, Arguments& args)
- {
- // args[0]: String
- return fnObj->GetEntryPoint()(fnObj, CallInfo(CallFlags_Value, 2), regExp, args[0]);
- }
- template<>
- Var JavascriptString::CallRegExFunction<2>(RecyclableObject* fnObj, Var regExp, Arguments& args)
- {
- // args[0]: String
- // args[1]: RegExp (ignored since we need to create one when the argument is "undefined")
- // args[2]: Var
- if (args.Info.Count < 3)
- {
- return CallRegExFunction<1>(fnObj, regExp, args);
- }
- return fnObj->GetEntryPoint()(fnObj, CallInfo(CallFlags_Value, 3), regExp, args[0], args[2]);
- }
- Var JavascriptString::EntrySlice(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- JavascriptString * pThis = nullptr;
- GetThisStringArgument(args, scriptContext, _u("String.prototype.slice"), &pThis);
- int len = pThis->GetLength();
- int idxStart = 0;
- int idxEnd = len;
- if (args.Info.Count > 1)
- {
- idxStart = JavascriptOperators::IsUndefinedObject(args[1], scriptContext) ? 0 : ConvertToIndex(args[1], scriptContext);
- if (args.Info.Count > 2)
- {
- idxEnd = JavascriptOperators::IsUndefinedObject(args[2], scriptContext) ? len : ConvertToIndex(args[2], scriptContext);
- }
- }
- if (idxStart < 0)
- {
- idxStart = max(len + idxStart, 0);
- }
- else if (idxStart > len)
- {
- idxStart = len;
- }
- if (idxEnd < 0)
- {
- idxEnd = max(len + idxEnd, 0);
- }
- else if (idxEnd > len )
- {
- idxEnd = len;
- }
- if (idxEnd < idxStart)
- {
- idxEnd = idxStart;
- }
- return SubstringCore(pThis, idxStart, idxEnd - idxStart, scriptContext);
- }
- Var JavascriptString::EntrySplit(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- PCWSTR const varName = _u("String.prototype.split");
- AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, varName);
- auto fallback = [&](JavascriptString* stringObj)
- {
- return DoStringSplit(args, callInfo, stringObj, scriptContext);
- };
- return DelegateToRegExSymbolFunction<2>(args, PropertyIds::_symbolSplit, fallback, varName, scriptContext);
- }
- Var JavascriptString::DoStringSplit(Arguments& args, CallInfo& callInfo, JavascriptString* input, ScriptContext* scriptContext)
- {
- if (args.Info.Count == 1)
- {
- JavascriptArray* ary = scriptContext->GetLibrary()->CreateArray(1);
- ary->DirectSetItemAt(0, input);
- return ary;
- }
- else
- {
- uint32 limit;
- if (args.Info.Count < 3 || JavascriptOperators::IsUndefinedObject(args[2], scriptContext))
- {
- limit = UINT_MAX;
- }
- else
- {
- limit = JavascriptConversion::ToUInt32(args[2], scriptContext);
- }
- // When the config is enabled, the operation is handled by RegExp.prototype[@@split].
- if (!scriptContext->GetConfig()->IsES6RegExSymbolsEnabled()
- && JavascriptRegExp::Is(args[1]))
- {
- return RegexHelper::RegexSplit(scriptContext, JavascriptRegExp::FromVar(args[1]), input, limit,
- RegexHelper::IsResultNotUsed(callInfo.Flags));
- }
- else
- {
- JavascriptString* separator = JavascriptConversion::ToString(args[1], scriptContext);
- if (callInfo.Flags & CallFlags_NotUsed)
- {
- return scriptContext->GetLibrary()->GetNull();
- }
- if (!limit)
- {
- JavascriptArray* ary = scriptContext->GetLibrary()->CreateArray(0);
- return ary;
- }
- if (JavascriptOperators::GetTypeId(args[1]) == TypeIds_Undefined)
- {
- JavascriptArray* ary = scriptContext->GetLibrary()->CreateArray(1);
- ary->DirectSetItemAt(0, input);
- return ary;
- }
- return RegexHelper::StringSplit(separator, input, limit);
- }
- }
- }
- Var JavascriptString::EntrySubstring(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- JavascriptString * pThis = nullptr;
- GetThisStringArgument(args, scriptContext, _u("String.prototype.substring"), &pThis);
- int len = pThis->GetLength();
- int idxStart = 0;
- int idxEnd = len;
- if (args.Info.Count > 1)
- {
- idxStart = JavascriptOperators::IsUndefinedObject(args[1], scriptContext) ? 0 : ConvertToIndex(args[1], scriptContext);
- if (args.Info.Count > 2)
- {
- idxEnd = JavascriptOperators::IsUndefinedObject(args[2], scriptContext) ? len : ConvertToIndex(args[2], scriptContext);
- }
- }
- idxStart = min(max(idxStart, 0), len);
- idxEnd = min(max(idxEnd, 0), len);
- if(idxEnd < idxStart)
- {
- //swap
- idxStart ^= idxEnd;
- idxEnd ^= idxStart;
- idxStart ^= idxEnd;
- }
- if (idxStart == 0 && idxEnd == len)
- {
- //return the string if we need to substring entire span
- return pThis;
- }
- return SubstringCore(pThis, idxStart, idxEnd - idxStart, scriptContext);
- }
- Var JavascriptString::EntrySubstr(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- JavascriptString * pThis = nullptr;
- GetThisStringArgument(args, scriptContext, _u("String.prototype.substr"), &pThis);
- int len = pThis->GetLength();
- int idxStart = 0;
- int idxEnd = len;
- if (args.Info.Count > 1)
- {
- idxStart = JavascriptOperators::IsUndefinedObject(args[1], scriptContext) ? 0 : ConvertToIndex(args[1], scriptContext);
- if (args.Info.Count > 2)
- {
- idxEnd = JavascriptOperators::IsUndefinedObject(args[2], scriptContext) ? len : ConvertToIndex(args[2], scriptContext);
- }
- }
- if (idxStart < 0)
- {
- idxStart = max(len + idxStart, 0);
- }
- else if (idxStart > len)
- {
- idxStart = len;
- }
- if (idxEnd < 0)
- {
- idxEnd = idxStart;
- }
- else if (idxEnd > len - idxStart)
- {
- idxEnd = len;
- }
- else
- {
- idxEnd += idxStart;
- }
- if (idxStart == 0 && idxEnd == len)
- {
- //return the string if we need to substr entire span
- return pThis;
- }
- Assert(0 <= idxStart && idxStart <= idxEnd && idxEnd <= len);
- return SubstringCore(pThis, idxStart, idxEnd - idxStart, scriptContext);
- }
- Var JavascriptString::SubstringCore(JavascriptString* pThis, int idxStart, int span, ScriptContext* scriptContext)
- {
- return SubString::New(pThis, idxStart, span);
- }
- Var JavascriptString::EntryPadStart(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(PadStartCount);
- JavascriptString * pThis = nullptr;
- GetThisStringArgument(args, scriptContext, _u("String.prototype.padStart"), &pThis);
- return PadCore(args, pThis, true /*isPadStart*/, scriptContext);
- }
- Var JavascriptString::EntryPadEnd(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(PadEndCount);
- JavascriptString * pThis = nullptr;
- GetThisStringArgument(args, scriptContext, _u("String.prototype.padEnd"), &pThis);
- return PadCore(args, pThis, false /*isPadStart*/, scriptContext);
- }
- JavascriptString* JavascriptString::PadCore(ArgumentReader& args, JavascriptString *mainString, bool isPadStart, ScriptContext* scriptContext)
- {
- Assert(mainString != nullptr);
- Assert(args.Info.Count > 0);
- if (args.Info.Count == 1)
- {
- return mainString;
- }
- int64 maxLength = JavascriptConversion::ToLength(args[1], scriptContext);
- charcount_t currentLength = mainString->GetLength();
- if (maxLength <= currentLength)
- {
- return mainString;
- }
- if (maxLength > JavascriptString::MaxCharLength)
- {
- Throw::OutOfMemory();
- }
- JavascriptString * fillerString = nullptr;
- if (args.Info.Count > 2 && !JavascriptOperators::IsUndefinedObject(args[2], scriptContext))
- {
- JavascriptString *argStr = JavascriptConversion::ToString(args[2], scriptContext);
- if (argStr->GetLength() > 0)
- {
- fillerString = argStr;
- }
- }
- if (fillerString == nullptr)
- {
- fillerString = NewWithBuffer(_u(" "), 1, scriptContext);
- }
- Assert(fillerString->GetLength() > 0);
- charcount_t fillLength = (charcount_t)(maxLength - currentLength);
- charcount_t count = fillLength / fillerString->GetLength();
- JavascriptString * finalPad = scriptContext->GetLibrary()->GetEmptyString();
- if (count > 0)
- {
- finalPad = RepeatCore(fillerString, count, scriptContext);
- fillLength -= (count * fillerString->GetLength());
- }
- if (fillLength > 0)
- {
- finalPad = Concat(finalPad, SubString::New(fillerString, 0, fillLength));
- }
- return isPadStart ? Concat(finalPad, mainString) : Concat(mainString, finalPad);
- }
- Var JavascriptString::EntryToLocaleLowerCase(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- return ToLocaleCaseHelper(args[0], false, scriptContext);
- }
- Var JavascriptString::EntryToLocaleUpperCase(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- return ToLocaleCaseHelper(args[0], true, scriptContext);
- }
- Var JavascriptString::EntryToLowerCase(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- JavascriptString * pThis = nullptr;
- GetThisStringArgument(args, scriptContext, _u("String.prototype.toLowerCase"), &pThis);
- // Fast path for one character strings
- if (pThis->GetLength() == 1)
- {
- char16 inChar = pThis->GetString()[0];
- char16 outChar = inChar;
- #if DBG
- DWORD converted =
- #endif
- CharLowerBuffW(&outChar, 1);
- Assert(converted == 1);
- return (inChar == outChar) ? pThis : scriptContext->GetLibrary()->GetCharStringCache().GetStringForChar(outChar);
- }
- return ToCaseCore(pThis, ToLower);
- }
- Var JavascriptString::EntryToString(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- if(args.Info.Count == 0)
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedString, _u("String.prototype.toString"));
- }
- AssertMsg(args.Info.Count > 0, "Negative argument count");
- JavascriptString* str = nullptr;
- if (!GetThisValueVar(args[0], &str, scriptContext))
- {
- if (JavascriptOperators::GetTypeId(args[0]) == TypeIds_HostDispatch)
- {
- Var result;
- if (RecyclableObject::FromVar(args[0])->InvokeBuiltInOperationRemotely(EntryToString, args, &result))
- {
- return result;
- }
- }
- JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedString, _u("String.prototype.toString"));
- }
- return str;
- }
- Var JavascriptString::EntryToUpperCase(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- JavascriptString* pThis = nullptr;
- GetThisStringArgument(args, scriptContext, _u("String.prototype.toUpperCase"), &pThis);
- // Fast path for one character strings
- if (pThis->GetLength() == 1)
- {
- char16 inChar = pThis->GetString()[0];
- char16 outChar = inChar;
- #if DBG
- DWORD converted =
- #endif
- CharUpperBuffW(&outChar, 1);
- Assert(converted == 1);
- return (inChar == outChar) ? pThis : scriptContext->GetLibrary()->GetCharStringCache().GetStringForChar(outChar);
- }
- return ToCaseCore(pThis, ToUpper);
- }
- Var JavascriptString::ToCaseCore(JavascriptString* pThis, ToCase toCase)
- {
- charcount_t count = pThis->GetLength();
- BufferStringBuilder builder(count, pThis->type->GetScriptContext());
- const char16 *inStr = pThis->GetString();
- char16 *outStr = builder.DangerousGetWritableBuffer();
- char16* outStrLim = outStr + count;
- char16 *o = outStr;
- while (o < outStrLim)
- {
- *o++ = *inStr++;
- }
- if(toCase == ToUpper)
- {
- #if DBG
- DWORD converted =
- #endif
- CharUpperBuffW(outStr, count);
- Assert(converted == count);
- }
- else
- {
- Assert(toCase == ToLower);
- #if DBG
- DWORD converted =
- #endif
- CharLowerBuffW(outStr, count);
- Assert(converted == count);
- }
- return builder.ToString();
- }
- Var JavascriptString::EntryTrim(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(StringTrimCount);
- Assert(!(callInfo.Flags & CallFlags_New));
- //15.5.4.20 The following steps are taken:
- //1. Call CheckObjectCoercible passing the this value as its argument.
- //2. Let S be the result of calling ToString, giving it the this value as its argument.
- //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.
- //4. Return T.
- JavascriptString* pThis = nullptr;
- GetThisStringArgument(args, scriptContext, _u("String.prototype.trim"), &pThis);
- return TrimLeftRightHelper<true /*trimLeft*/, true /*trimRight*/>(pThis, scriptContext);
- }
- Var JavascriptString::EntryTrimLeft(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- // 1.Let O be CheckObjectCoercible(this value) .
- // 2.Let S be ToString(O) .
- // 3.ReturnIfAbrupt(S).
- // 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.
- // 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.
- // 5.Return T.
- JavascriptString* pThis = nullptr;
- GetThisStringArgument(args, scriptContext, _u("String.prototype.trimLeft"), &pThis);
- return TrimLeftRightHelper< true /*trimLeft*/, false /*trimRight*/>(pThis, scriptContext);
- }
- Var JavascriptString::EntryTrimRight(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- // 1.Let O be CheckObjectCoercible(this value) .
- // 2.Let S be ToString(O) .
- // 3.ReturnIfAbrupt(S).
- // 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.
- // 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.
- // 5.Return T.
- JavascriptString* pThis = nullptr;
- GetThisStringArgument(args, scriptContext, _u("String.prototype.trimRight"), &pThis);
- return TrimLeftRightHelper<false /*trimLeft*/, true /*trimRight*/>(pThis, scriptContext);
- }
- template <bool trimLeft, bool trimRight>
- Var JavascriptString::TrimLeftRightHelper(JavascriptString* arg, ScriptContext* scriptContext)
- {
- static_assert(trimLeft || trimRight, "bad template instance of TrimLeftRightHelper()");
- int len = arg->GetLength();
- const char16 *string = arg->GetString();
- int idxStart = 0;
- if (trimLeft)
- {
- for (; idxStart < len; idxStart++)
- {
- char16 ch = string[idxStart];
- if (IsWhiteSpaceCharacter(ch))
- {
- continue;
- }
- break;
- }
- if (len == idxStart)
- {
- return (scriptContext->GetLibrary()->GetEmptyString());
- }
- }
- int idxEnd = len - 1;
- if (trimRight)
- {
- for (; idxEnd >= 0; idxEnd--)
- {
- char16 ch = string[idxEnd];
- if (IsWhiteSpaceCharacter(ch))
- {
- continue;
- }
- break;
- }
- if (!trimLeft)
- {
- if (idxEnd < 0)
- {
- Assert(idxEnd == -1);
- return (scriptContext->GetLibrary()->GetEmptyString());
- }
- }
- else
- {
- Assert(idxEnd >= 0);
- }
- }
- return SubstringCore(arg, idxStart, idxEnd - idxStart + 1, scriptContext);
- }
- ///----------------------------------------------------------------------------
- /// Repeat() returns a new string equal to the toString(this) repeated n times,
- /// as described in (ES6.0: S21.1.3.13).
- ///----------------------------------------------------------------------------
- Var JavascriptString::EntryRepeat(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(RepeatCount);
- JavascriptString* pThis = nullptr;
- GetThisStringArgument(args, scriptContext, _u("String.prototype.repeat"), &pThis);
- charcount_t count = 0;
- if (args.Info.Count > 1)
- {
- if (!JavascriptOperators::IsUndefinedObject(args[1], scriptContext))
- {
- double countDbl = JavascriptConversion::ToInteger(args[1], scriptContext);
- if (JavascriptNumber::IsPosInf(countDbl) || countDbl < 0.0)
- {
- JavascriptError::ThrowRangeError(scriptContext, JSERR_ArgumentOutOfRange, _u("String.prototype.repeat"));
- }
- count = NumberUtilities::LuFromDblNearest(countDbl);
- }
- }
- if (count == 0 || pThis->GetLength() == 0)
- {
- return scriptContext->GetLibrary()->GetEmptyString();
- }
- else if (count == 1)
- {
- return pThis;
- }
- return RepeatCore(pThis, count, scriptContext);
- }
- JavascriptString* JavascriptString::RepeatCore(JavascriptString* currentString, charcount_t count, ScriptContext* scriptContext)
- {
- Assert(currentString != nullptr);
- Assert(currentString->GetLength() > 0);
- Assert(count > 0);
- const char16* currentRawString = currentString->GetString();
- int currentLength = currentString->GetLength();
- charcount_t finalBufferCount = UInt32Math::Add(UInt32Math::Mul(count, currentLength), 1);
- char16* buffer = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, finalBufferCount);
- if (currentLength == 1)
- {
- wmemset(buffer, currentRawString[0], finalBufferCount - 1);
- buffer[finalBufferCount - 1] = '\0';
- }
- else
- {
- char16* bufferDst = buffer;
- size_t bufferDstSize = finalBufferCount;
- for (charcount_t i = 0; i < count; i += 1)
- {
- js_wmemcpy_s(bufferDst, bufferDstSize, currentRawString, currentLength);
- bufferDst += currentLength;
- bufferDstSize -= currentLength;
- }
- Assert(bufferDstSize == 1);
- *bufferDst = '\0';
- }
- return JavascriptString::NewWithBuffer(buffer, finalBufferCount - 1, scriptContext);
- }
- ///----------------------------------------------------------------------------
- /// StartsWith() returns true if the given string matches the beginning of the
- /// substring starting at the given position in toString(this), as described
- /// in (ES6.0: S21.1.3.18).
- ///----------------------------------------------------------------------------
- Var JavascriptString::EntryStartsWith(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(StartsWithCount);
- JavascriptString * pThis;
- JavascriptString * pSearch;
- GetThisAndSearchStringArguments(args, scriptContext, _u("String.prototype.startsWith"), &pThis, &pSearch, false);
- const char16* thisStr = pThis->GetString();
- int thisStrLen = pThis->GetLength();
- const char16* searchStr = pSearch->GetString();
- int searchStrLen = pSearch->GetLength();
- int startPosition = 0;
- if (args.Info.Count > 2)
- {
- if (!JavascriptOperators::IsUndefinedObject(args[2], scriptContext))
- {
- startPosition = ConvertToIndex(args[2], scriptContext); // this is to adjust corner cases like MAX_VALUE
- startPosition = min(max(startPosition, 0), thisStrLen);
- }
- }
- // Avoid signed 32-bit int overflow if startPosition is large by subtracting searchStrLen from thisStrLen instead of
- // adding searchStrLen and startPosition. The subtraction cannot underflow because maximum string length is
- // MaxCharCount == INT_MAX-1. I.e. the RHS can be == 0 - (INT_MAX-1) == 1 - INT_MAX which would not underflow.
- if (startPosition <= thisStrLen - searchStrLen)
- {
- Assert(searchStrLen <= thisStrLen - startPosition);
- if (wmemcmp(thisStr + startPosition, searchStr, searchStrLen) == 0)
- {
- return scriptContext->GetLibrary()->GetTrue();
- }
- }
- return scriptContext->GetLibrary()->GetFalse();
- }
- ///----------------------------------------------------------------------------
- /// EndsWith() returns true if the given string matches the end of the
- /// substring ending at the given position in toString(this), as described
- /// in (ES6.0: S21.1.3.7).
- ///----------------------------------------------------------------------------
- Var JavascriptString::EntryEndsWith(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(EndsWithCount);
- JavascriptString * pThis;
- JavascriptString * pSearch;
- GetThisAndSearchStringArguments(args, scriptContext, _u("String.prototype.endsWith"), &pThis, &pSearch, false);
- const char16* thisStr = pThis->GetString();
- int thisStrLen = pThis->GetLength();
- const char16* searchStr = pSearch->GetString();
- int searchStrLen = pSearch->GetLength();
- int endPosition = thisStrLen;
- if (args.Info.Count > 2)
- {
- if (!JavascriptOperators::IsUndefinedObject(args[2], scriptContext))
- {
- endPosition = ConvertToIndex(args[2], scriptContext); // this is to adjust corner cases like MAX_VALUE
- endPosition = min(max(endPosition, 0), thisStrLen);
- }
- }
- int startPosition = endPosition - searchStrLen;
- if (startPosition >= 0)
- {
- Assert(startPosition <= thisStrLen);
- Assert(searchStrLen <= thisStrLen - startPosition);
- if (wmemcmp(thisStr + startPosition, searchStr, searchStrLen) == 0)
- {
- return scriptContext->GetLibrary()->GetTrue();
- }
- }
- return scriptContext->GetLibrary()->GetFalse();
- }
- ///----------------------------------------------------------------------------
- /// Includes() returns true if the given string matches any substring (of the
- /// same length) of the substring starting at the given position in
- /// toString(this), as described in (ES6.0 (draft 33): S21.1.3.7).
- ///----------------------------------------------------------------------------
- Var JavascriptString::EntryIncludes(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(ContainsCount);
- return JavascriptBoolean::ToVar(IndexOf(args, scriptContext, _u("String.prototype.includes"), false) != -1, scriptContext);
- }
- Var JavascriptString::EntryValueOf(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- if(args.Info.Count == 0)
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedString, _u("String.prototype.valueOf"));
- }
- AssertMsg(args.Info.Count > 0, "Negative argument count");
- JavascriptString* str = nullptr;
- if (!GetThisValueVar(args[0], &str, scriptContext))
- {
- if (JavascriptOperators::GetTypeId(args[0]) == TypeIds_HostDispatch)
- {
- Var result;
- if (RecyclableObject::FromVar(args[0])->InvokeBuiltInOperationRemotely(EntryValueOf, args, &result))
- {
- return result;
- }
- }
- JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedString, _u("String.prototype.valueOf"));
- }
- return str;
- }
- Var JavascriptString::EntrySymbolIterator(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- Assert(!(callInfo.Flags & CallFlags_New));
- if (args.Info.Count == 0)
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedString, _u("String.prototype[Symbol.iterator]"));
- }
- AssertMsg(args.Info.Count > 0, "Negative argument count");
- if (!JavascriptConversion::CheckObjectCoercible(args[0], scriptContext))
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, _u("String.prototype[Symbol.iterator]"));
- }
- JavascriptString* str = JavascriptConversion::ToString(args[0], scriptContext);
- return scriptContext->GetLibrary()->CreateStringIterator(str);
- }
- const char16 * JavascriptString::GetSz()
- {
- Assert(m_pszValue[m_charLength] == _u('\0'));
- return m_pszValue;
- }
- const char16 * JavascriptString::GetString()
- {
- if (!this->IsFinalized())
- {
- this->GetSz();
- Assert(m_pszValue);
- }
- return m_pszValue;
- }
- void const * JavascriptString::GetOriginalStringReference()
- {
- // Just return the string buffer
- return GetString();
- }
- size_t JavascriptString::GetAllocatedByteCount() const
- {
- if (!this->IsFinalized())
- {
- return 0;
- }
- return this->m_charLength * sizeof(WCHAR);
- }
- bool JavascriptString::IsSubstring() const
- {
- return false;
- }
- bool JavascriptString::IsNegZero(JavascriptString *string)
- {
- return string->GetLength() == 2 && wmemcmp(string->GetString(), _u("-0"), 2) == 0;
- }
- void JavascriptString::FinishCopy(__inout_xcount(m_charLength) char16 *const buffer, StringCopyInfoStack &nestedStringTreeCopyInfos)
- {
- while (!nestedStringTreeCopyInfos.IsEmpty())
- {
- const StringCopyInfo copyInfo(nestedStringTreeCopyInfos.Pop());
- Assert(copyInfo.SourceString()->GetLength() <= GetLength());
- Assert(copyInfo.DestinationBuffer() >= buffer);
- Assert(copyInfo.DestinationBuffer() <= buffer + (GetLength() - copyInfo.SourceString()->GetLength()));
- copyInfo.SourceString()->Copy(copyInfo.DestinationBuffer(), nestedStringTreeCopyInfos, 0);
- }
- }
- void JavascriptString::CopyVirtual(
- _Out_writes_(m_charLength) char16 *const buffer,
- StringCopyInfoStack &nestedStringTreeCopyInfos,
- const byte recursionDepth)
- {
- Assert(buffer);
- Assert(!this->IsFinalized()); // CopyVirtual should only be called for unfinalized buffers
- CopyHelper(buffer, GetString(), GetLength());
- }
- char16* JavascriptString::GetSzCopy()
- {
- return AllocateLeafAndCopySz(this->GetScriptContext()->GetRecycler(), GetString(), GetLength());
- }
- LPCWSTR JavascriptString::GetSzCopy(ArenaAllocator* alloc)
- {
- return AllocateAndCopySz(alloc, GetString(), GetLength());
- }
- /*
- Table generated using the following:
- var invalidValue = 37;
- function toStringTable()
- {
- var stringTable = new Array(128);
- for(var i = 0; i < 128; i++)
- {
- var ch = i;
- if ('0'.charCodeAt(0) <= ch && '9'.charCodeAt(0) >= ch)
- ch -= '0'.charCodeAt(0);
- else if ('A'.charCodeAt(0) <= ch && 'Z'.charCodeAt(0) >= ch)
- ch -= 'A'.charCodeAt(0) - 10;
- else if ('a'.charCodeAt(0) <= ch && 'z'.charCodeAt(0) >= ch)
- ch -= 'a'.charCodeAt(0) - 10;
- else
- ch = 37;
- stringTable[i] = ch;
- }
- WScript.Echo("{" + stringTable + "}");
- }
- toStringTable();*/
- const char JavascriptString::stringToIntegerMap[] = {
- 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
- ,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,
- 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,
- 37,37,37,37,37};
- /*
- Table generated using the following:
- function logMaxUintTable()
- {
- var MAX_UINT = 4294967295;
- var logTable = new Array(37);
- logTable[0] = 0;
- logTable[1] = 0;
- for(var i = 2; i < logTable.length; i++)
- {
- logTable[i] = Math.floor(Math.log(MAX_UINT + 1) / Math.log(i));
- }
- WScript.Echo("{" + logTable + "}");
- }
- logMaxUintTable();
- */
- const uint8 JavascriptString::maxUintStringLengthTable[] =
- { 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 };
- // NumberUtil::FIntRadStrToDbl and parts of GlobalObject::EntryParseInt were refactored into ToInteger
- Var JavascriptString::ToInteger(int radix)
- {
- AssertMsg(radix == 0 || radix >= 2 && radix <= 36, "'radix' is invalid");
- const char16* pchStart = GetString();
- const char16* pchEnd = pchStart + m_charLength;
- const char16 *pch = this->GetScriptContext()->GetCharClassifier()->SkipWhiteSpace(pchStart, pchEnd);
- bool isNegative = false;
- switch (*pch)
- {
- case '-':
- isNegative = true;
- // Fall through.
- case '+':
- if(pch < pchEnd)
- {
- pch++;
- }
- break;
- }
- if (0 == radix)
- {
- if (pch < pchEnd && '0' != pch[0])
- {
- radix = 10;
- }
- else if (('x' == pch[1] || 'X' == pch[1]) && pchEnd - pch >= 2)
- {
- radix = 16;
- pch += 2;
- }
- else
- {
- // ES5's 'parseInt' does not allow treating a string beginning with a '0' as an octal value. ES3 does not specify a
- // behavior
- radix = 10;
- }
- }
- else if (16 == radix)
- {
- if('0' == pch[0] && ('x' == pch[1] || 'X' == pch[1]) && pchEnd - pch >= 2)
- {
- pch += 2;
- }
- }
- Assert(radix <= _countof(maxUintStringLengthTable));
- Assert(pchEnd >= pch);
- size_t length = pchEnd - pch;
- const char16 *const pchMin = pch;
- __analysis_assume(radix < _countof(maxUintStringLengthTable));
- if(length <= maxUintStringLengthTable[radix])
- {
- // Use uint32 as integer being parsed - much faster than BigInt
- uint32 value = 0;
- for ( ; pch < pchEnd ; pch++)
- {
- char16 ch = *pch;
- if(ch >= _countof(stringToIntegerMap) || (ch = stringToIntegerMap[ch]) >= radix)
- {
- break;
- }
- uint32 beforeValue = value;
- value = value * radix + ch;
- AssertMsg(value >= beforeValue, "uint overflow");
- }
- if(pchMin == pch)
- {
- return GetScriptContext()->GetLibrary()->GetNaN();
- }
- if(isNegative)
- {
- // negative zero can only be represented by doubles
- if(value <= INT_MAX && value != 0)
- {
- int32 result = -((int32)value);
- return JavascriptNumber::ToVar(result, this->GetScriptContext());
- }
- double result = -((double)(value));
- return JavascriptNumber::New(result, this->GetScriptContext());
- }
- return JavascriptNumber::ToVar(value, this->GetScriptContext());
- }
- BigInt bi;
- for ( ; pch < pchEnd ; pch++)
- {
- char16 ch = *pch;
- if(ch >= _countof(stringToIntegerMap) || (ch = stringToIntegerMap[ch]) >= radix)
- {
- break;
- }
- if (!bi.FMulAdd(radix, ch))
- {
- //Mimic IE8 which threw an OutOfMemory exception in this case.
- JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
- }
- // If we ever have more than 32 ulongs, the result must be infinite.
- if (bi.Clu() > 32)
- {
- Var result = isNegative ?
- GetScriptContext()->GetLibrary()->GetNegativeInfinite() :
- GetScriptContext()->GetLibrary()->GetPositiveInfinite();
- return result;
- }
- }
- if (pchMin == pch)
- {
- return GetScriptContext()->GetLibrary()->GetNaN();
- }
- // Convert to a double.
- double result = bi.GetDbl();
- if(isNegative)
- {
- result = -result;
- }
- return Js::JavascriptNumber::ToVarIntCheck(result, GetScriptContext());
- }
- bool JavascriptString::ToDouble(double * result)
- {
- const char16* pch;
- long len = this->m_charLength;
- if (0 == len)
- {
- *result = 0;
- return true;
- }
- if (1 == len && NumberUtilities::IsDigit(this->GetString()[0]))
- {
- *result = (double)(this->GetString()[0] - '0');
- return true;
- }
- // TODO: Use GetString here instead of GetSz (need to modify DblFromHex and StrToDbl to take a length)
- for (pch = this->GetSz(); IsWhiteSpaceCharacter(*pch); pch++)
- ;
- if (0 == *pch)
- {
- *result = 0;
- return true;
- }
- bool isNumericLiteral = false;
- if (*pch == '0')
- {
- const char16 *pchT = pch + 2;
- switch (pch[1])
- {
- case 'x':
- case 'X':
- *result = NumberUtilities::DblFromHex(pchT, &pch);
- isNumericLiteral = true;
- break;
- case 'o':
- case 'O':
- *result = NumberUtilities::DblFromOctal(pchT, &pch);
- isNumericLiteral = true;
- break;
- case 'b':
- case 'B':
- *result = NumberUtilities::DblFromBinary(pchT, &pch);
- isNumericLiteral = true;
- break;
- }
- if (pchT == pch && isNumericLiteral)
- {
- *result = JavascriptNumber::NaN;
- return false;
- }
- }
- if (!isNumericLiteral)
- {
- *result = NumberUtilities::StrToDbl(pch, &pch, GetScriptContext());
- }
- while (IsWhiteSpaceCharacter(*pch))
- pch++;
- if (pch != this->m_pszValue + len)
- {
- *result = JavascriptNumber::NaN;
- return false;
- }
- return true;
- }
- double JavascriptString::ToDouble()
- {
- double result;
- this->ToDouble(&result);
- return result;
- }
- bool JavascriptString::Equals(Var aLeft, Var aRight)
- {
- AssertMsg(JavascriptString::Is(aLeft) && JavascriptString::Is(aRight), "string comparison");
- JavascriptString *leftString = JavascriptString::FromVar(aLeft);
- JavascriptString *rightString = JavascriptString::FromVar(aRight);
- if (leftString->GetLength() != rightString->GetLength())
- {
- return false;
- }
- if (wmemcmp(leftString->GetString(), rightString->GetString(), leftString->GetLength()) == 0)
- {
- return true;
- }
- return false;
- }
- //
- // LessThan implements algorithm of ES5 11.8.5 step 4
- // returns false for same string pattern
- //
- bool JavascriptString::LessThan(Var aLeft, Var aRight)
- {
- AssertMsg(JavascriptString::Is(aLeft) && JavascriptString::Is(aRight), "string LessThan");
- JavascriptString *leftString = JavascriptString::FromVar(aLeft);
- JavascriptString *rightString = JavascriptString::FromVar(aRight);
- if (JavascriptString::strcmp(leftString, rightString) < 0)
- {
- return true;
- }
- return false;
- }
- // thisStringValue(value) abstract operation as defined in ES6.0 (Draft 25) Section 21.1.3
- BOOL JavascriptString::GetThisValueVar(Var aValue, JavascriptString** pString, ScriptContext* scriptContext)
- {
- Assert(pString);
- // 1. If Type(value) is String, return value.
- if (JavascriptString::Is(aValue))
- {
- *pString = JavascriptString::FromVar(aValue);
- return TRUE;
- }
- // 2. If Type(value) is Object and value has a [[StringData]] internal slot
- else if ( JavascriptStringObject::Is(aValue))
- {
- JavascriptStringObject* pStringObj = JavascriptStringObject::FromVar(aValue);
- // a. Let s be the value of value's [[StringData]] internal slot.
- // b. If s is not undefined, then return s.
- *pString = JavascriptString::FromVar(CrossSite::MarshalVar(scriptContext, pStringObj->Unwrap()));
- return TRUE;
- }
- // 3. Throw a TypeError exception.
- // Note: We don't throw a TypeError here, choosing to return FALSE and let the caller throw the error
- return FALSE;
- }
- #ifdef TAGENTRY
- #undef TAGENTRY
- #endif
- #define TAGENTRY(name, ...) \
- Var JavascriptString::Entry##name(RecyclableObject* function, CallInfo callInfo, ...) \
- { \
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); \
- \
- ARGUMENTS(args, callInfo); \
- ScriptContext* scriptContext = function->GetScriptContext(); \
- \
- Assert(!(callInfo.Flags & CallFlags_New)); \
- \
- return StringBracketHelper(args, scriptContext, __VA_ARGS__); \
- }
- #include "JavascriptStringTagEntries.h"
- #undef TAGENTRY
- Var JavascriptString::StringBracketHelper(Arguments args, ScriptContext *scriptContext, __in_ecount(cchTag) char16 const *pszTag,
- charcount_t cchTag, __in_ecount_opt(cchProp) char16 const *pszProp, charcount_t cchProp)
- {
- charcount_t cchThis;
- charcount_t cchPropertyValue;
- charcount_t cchTotalChars;
- charcount_t ich;
- JavascriptString * pThis;
- JavascriptString * pPropertyValue = nullptr;
- const char16 * propertyValueStr = nullptr;
- uint quotesCount = 0;
- const char16 quotStr[] = _u(""");
- const charcount_t quotStrLen = _countof(quotStr) - 1;
- bool ES6FixesEnabled = scriptContext->GetConfig()->IsES6StringPrototypeFixEnabled();
- // Assemble the component pieces of a string tag function (ex: String.prototype.link).
- // In the general case, result is as below:
- //
- // pszProp = _u("href");
- // pszTag = _u("a");
- // pThis = JavascriptString::FromVar(args[0]);
- // pPropertyValue = JavascriptString::FromVar(args[1]);
- //
- // pResult = _u("<a href=\"[[pPropertyValue]]\">[[pThis]]</a>");
- //
- // cchTotalChars = 5 // <></>
- // + cchTag * 2 // a
- // + cchProp // href
- // + 4 // _=""
- // + cchPropertyValue
- // + cchThis;
- //
- // Note: With ES6FixesEnabled, we need to escape quote characters (_u('"')) in pPropertyValue.
- // Note: Without ES6FixesEnabled, the tag and prop strings should be capitalized.
- if(args.Info.Count == 0)
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedString);
- }
- if (ES6FixesEnabled)
- {
- if (!JavascriptConversion::CheckObjectCoercible(args[0], scriptContext))
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, pszTag);
- }
- }
- if (JavascriptString::Is(args[0]))
- {
- pThis = JavascriptString::FromVar(args[0]);
- }
- else
- {
- pThis = JavascriptConversion::ToString(args[0], scriptContext);
- }
- cchThis = pThis->GetLength();
- cchTotalChars = UInt32Math::Add(cchTag, cchTag);
- // 5 is for the <></> characters
- cchTotalChars = UInt32Math::Add(cchTotalChars, 5);
- if (nullptr != pszProp)
- {
- // Need one string argument.
- if (args.Info.Count >= 2)
- {
- if (JavascriptString::Is(args[1]))
- {
- pPropertyValue = JavascriptString::FromVar(args[1]);
- }
- else
- {
- pPropertyValue = JavascriptConversion::ToString(args[1], scriptContext);
- }
- }
- else
- {
- pPropertyValue = scriptContext->GetLibrary()->GetUndefinedDisplayString();
- }
- cchPropertyValue = pPropertyValue->GetLength();
- propertyValueStr = pPropertyValue->GetString();
- if (ES6FixesEnabled)
- {
- // Count the number of " characters we need to escape.
- for (ich = 0; ich < cchPropertyValue; ich++)
- {
- if (propertyValueStr[ich] == _u('"'))
- {
- ++quotesCount;
- }
- }
- }
- cchTotalChars = UInt32Math::Add(cchTotalChars, cchProp);
- // 4 is for the _="" characters
- cchTotalChars = UInt32Math::Add(cchTotalChars, 4);
- if (ES6FixesEnabled)
- {
- // Account for the " escaping (")
- cchTotalChars = UInt32Math::Add(cchTotalChars, UInt32Math::Mul(quotesCount, quotStrLen)) - quotesCount;
- }
- }
- else
- {
- cchPropertyValue = 0;
- cchProp = 0;
- }
- cchTotalChars = UInt32Math::Add(cchTotalChars, cchThis);
- cchTotalChars = UInt32Math::Add(cchTotalChars, cchPropertyValue);
- if (!IsValidCharCount(cchTotalChars) || cchTotalChars < cchThis || cchTotalChars < cchPropertyValue)
- {
- Js::JavascriptError::ThrowOutOfMemoryError(scriptContext);
- }
- BufferStringBuilder builder(cchTotalChars, scriptContext);
- char16 *pResult = builder.DangerousGetWritableBuffer();
- *pResult++ = _u('<');
- for (ich = 0; ich < cchTag; ich++)
- {
- *pResult++ = ES6FixesEnabled ? pszTag[ich] : towupper(pszTag[ich]);
- }
- if (nullptr != pszProp)
- {
- *pResult++ = _u(' ');
- for (ich = 0; ich < cchProp; ich++)
- {
- *pResult++ = ES6FixesEnabled ? pszProp[ich] : towupper(pszProp[ich]);
- }
- *pResult++ = _u('=');
- *pResult++ = _u('"');
- Assert(propertyValueStr != nullptr);
- if (!ES6FixesEnabled || quotesCount == 0)
- {
- js_wmemcpy_s(pResult,
- cchTotalChars - (pResult - builder.DangerousGetWritableBuffer() + 1),
- propertyValueStr,
- cchPropertyValue);
- pResult += cchPropertyValue;
- }
- else {
- for (ich = 0; ich < cchPropertyValue; ich++)
- {
- if (propertyValueStr[ich] == _u('"'))
- {
- charcount_t destLengthLeft = (cchTotalChars - (charcount_t)(pResult - builder.DangerousGetWritableBuffer() + 1));
- // Copy the quote string into result beginning at the index where the quote would appear
- js_wmemcpy_s(pResult,
- destLengthLeft,
- quotStr,
- quotStrLen);
- // Move result ahead by the length of the quote string
- pResult += quotStrLen;
- // We ate one of the quotes
- quotesCount--;
- // We only need to check to see if we have no more quotes after eating a quote
- if (quotesCount == 0)
- {
- // Skip the quote character.
- // Note: If ich is currently the last character (cchPropertyValue-1), it becomes cchPropertyValue after incrementing.
- // At that point, cchPropertyValue - ich == 0 so we will not increment pResult and will call memcpy for zero bytes.
- ich++;
- // Copy the rest from the property value string starting at the index after the last quote
- js_wmemcpy_s(pResult,
- destLengthLeft - quotStrLen,
- propertyValueStr + ich,
- cchPropertyValue - ich);
- // Move result ahead by the length of the rest of the property string
- pResult += (cchPropertyValue - ich);
- break;
- }
- }
- else
- {
- // Each non-quote character just gets copied into result string
- *pResult++ = propertyValueStr[ich];
- }
- }
- }
- *pResult++ = _u('"');
- }
- *pResult++ = _u('>');
- const char16 *pThisString = pThis->GetString();
- js_wmemcpy_s(pResult, cchTotalChars - (pResult - builder.DangerousGetWritableBuffer() + 1), pThisString, cchThis);
- pResult += cchThis;
- *pResult++ = _u('<');
- *pResult++ = _u('/');
- for (ich = 0; ich < cchTag; ich++)
- {
- *pResult++ = ES6FixesEnabled ? pszTag[ich] : towupper(pszTag[ich]);
- }
- *pResult++ = _u('>');
- // Assert we ended at the right place.
- AssertMsg((charcount_t)(pResult - builder.DangerousGetWritableBuffer()) == cchTotalChars, "Exceeded allocated string limit");
- return builder.ToString();
- }
- Var JavascriptString::ToLocaleCaseHelper(Var thisObj, bool toUpper, ScriptContext *scriptContext)
- {
- JavascriptString * pThis;
- if (JavascriptString::Is(thisObj))
- {
- pThis = JavascriptString::FromVar(thisObj);
- }
- else
- {
- pThis = JavascriptConversion::ToString(thisObj, scriptContext);
- }
- if (pThis->GetLength() == 0)
- {
- return pThis;
- }
- DWORD dwFlags = toUpper ? LCMAP_UPPERCASE : LCMAP_LOWERCASE;
- dwFlags |= LCMAP_LINGUISTIC_CASING;
- LCID lcid = GetUserDefaultLCID();
- const char16* str = pThis->GetString();
- // Get the number of chars in the mapped string.
- int count = LCMapStringW(lcid, dwFlags, str, pThis->GetLength(), NULL, 0);
- if (0 == count)
- {
- AssertMsg(FALSE, "LCMapString failed");
- Throw::InternalError();
- }
- BufferStringBuilder builder(count, scriptContext);
- char16 * stringBuffer = builder.DangerousGetWritableBuffer();
- int count1 = LCMapStringW(lcid, dwFlags, str, count, stringBuffer, count);
- if( 0 == count1 )
- {
- AssertMsg(FALSE, "LCMapString failed");
- Throw::InternalError();
- }
- return builder.ToString();
- }
- int JavascriptString::IndexOfUsingJmpTable(JmpTable jmpTable, const char16* inputStr, int len, const char16* searchStr, int searchLen, int position)
- {
- int result = -1;
- const char16 searchLast = searchStr[searchLen-1];
- unsigned long lMatchedJump = searchLen;
- if (jmpTable[searchLast].shift > 0)
- {
- lMatchedJump = jmpTable[searchLast].shift;
- }
- char16 const * p = inputStr + position + searchLen-1;
- WCHAR c;
- while(p < inputStr + len)
- {
- // first character match, keep checking
- if (*p == searchLast)
- {
- if ( wmemcmp(p-searchLen+1, searchStr, searchLen) == 0 )
- {
- break;
- }
- p += lMatchedJump;
- }
- else
- {
- c = *p;
- if ( 0 == ( c & ~0x7f ) && jmpTable[c].shift != 0 )
- {
- p += jmpTable[c].shift;
- }
- else
- {
- p += searchLen;
- }
- }
- }
- if (p >= inputStr+position && p < inputStr + len)
- {
- result = (int)(p - inputStr) - searchLen + 1;
- }
- return result;
- }
- int JavascriptString::LastIndexOfUsingJmpTable(JmpTable jmpTable, const char16* inputStr, int len, const char16* searchStr, int searchLen, int position)
- {
- const char16 searchFirst = searchStr[0];
- unsigned long lMatchedJump = searchLen;
- if (jmpTable[searchFirst].shift > 0)
- {
- lMatchedJump = jmpTable[searchFirst].shift;
- }
- WCHAR c;
- char16 const * p = inputStr + min(len - searchLen, position);
- while(p >= inputStr)
- {
- // first character match, keep checking
- if (*p == searchFirst)
- {
- if ( wmemcmp(p, searchStr, searchLen) == 0 )
- {
- break;
- }
- p -= lMatchedJump;
- }
- else
- {
- c = *p;
- if ( 0 == ( c & ~0x7f ) && jmpTable[c].shift != 0 )
- {
- p -= jmpTable[c].shift;
- }
- else
- {
- p -= searchLen;
- }
- }
- }
- return ((p >= inputStr) ? (int)(p - inputStr) : -1);
- }
- bool JavascriptString::BuildLastCharForwardBoyerMooreTable(JmpTable jmpTable, const char16* searchStr, int searchLen)
- {
- AssertMsg(searchLen >= 1, "Table for non-empty string");
- memset(jmpTable, 0, sizeof(JmpTable));
- const char16 * p2 = searchStr + searchLen - 1;
- const char16 * const begin = searchStr;
- // Determine if we can do a partial ASCII Boyer-Moore
- while (p2 >= begin)
- {
- WCHAR c = *p2;
- if ( 0 == ( c & ~0x7f ))
- {
- if ( jmpTable[c].shift == 0 )
- {
- jmpTable[c].shift = (unsigned long)(searchStr + searchLen - 1 - p2);
- }
- }
- else
- {
- return false;
- }
- p2--;
- }
- return true;
- }
- bool JavascriptString::BuildFirstCharBackwardBoyerMooreTable(JmpTable jmpTable, const char16* searchStr, int searchLen)
- {
- AssertMsg(searchLen >= 1, "Table for non-empty string");
- memset(jmpTable, 0, sizeof(JmpTable));
- const char16 * p2 = searchStr;
- const char16 * const end = searchStr + searchLen;
- // Determine if we can do a partial ASCII Boyer-Moore
- while (p2 < end)
- {
- WCHAR c = *p2;
- if ( 0 == ( c & ~0x7f ))
- {
- if ( jmpTable[c].shift == 0 )
- {
- jmpTable[c].shift = (unsigned long)(p2 - searchStr);
- }
- }
- else
- {
- return false;
- }
- p2++;
- }
- return true;
- }
- uint JavascriptString::strstr(JavascriptString *string, JavascriptString *substring, bool useBoyerMoore, uint start)
- {
- uint i;
- const char16 *stringOrig = string->GetString();
- uint stringLenOrig = string->GetLength();
- const char16 *stringSz = stringOrig + start;
- const char16 *substringSz = substring->GetString();
- uint stringLen = stringLenOrig - start;
- uint substringLen = substring->GetLength();
- if (useBoyerMoore && substringLen > 2)
- {
- JmpTable jmpTable;
- bool fAsciiJumpTable = BuildLastCharForwardBoyerMooreTable(jmpTable, substringSz, substringLen);
- if (fAsciiJumpTable)
- {
- int result = IndexOfUsingJmpTable(jmpTable, stringOrig, stringLenOrig, substringSz, substringLen, start);
- if (result != -1)
- {
- return result;
- }
- else
- {
- return (uint)-1;
- }
- }
- }
- if (stringLen >= substringLen)
- {
- // If substring is empty, it matches anything...
- if (substringLen == 0)
- {
- return 0;
- }
- for (i = 0; i <= stringLen - substringLen; i++)
- {
- // Quick check for first character.
- if (stringSz[i] == substringSz[0])
- {
- if (substringLen == 1 || memcmp(stringSz+i+1, substringSz+1, (substringLen-1)*sizeof(char16)) == 0)
- {
- return i + start;
- }
- }
- }
- }
- return (uint)-1;
- }
- int JavascriptString::strcmp(JavascriptString *string1, JavascriptString *string2)
- {
- uint string1Len = string1->GetLength();
- uint string2Len = string2->GetLength();
- int result = wmemcmp(string1->GetString(), string2->GetString(), min(string1Len, string2Len));
- return (result == 0) ? (int)(string1Len - string2Len) : result;
- }
- /*static*/ charcount_t JavascriptString::SafeSzSize(charcount_t cch)
- {
- // JavascriptString::MaxCharLength is valid; however, we are incrementing below by 1 and want to make sure we aren't overflowing
- // Nor going outside of valid range.
- if (cch >= JavascriptString::MaxCharLength)
- {
- Throw::OutOfMemory();
- }
- // Compute cch + 1, checking for overflow
- ++cch;
- return cch;
- }
- charcount_t JavascriptString::SafeSzSize() const
- {
- return SafeSzSize(GetLength());
- }
- /*static*/ __ecount(length+1) char16* JavascriptString::AllocateLeafAndCopySz(__in Recycler* recycler, __in_ecount(length) const char16* content, charcount_t length)
- {
- // Note: Intentionally not using SafeSzSize nor hoisting common
- // sub-expression "length + 1" into a local variable otherwise
- // Prefast gets confused and cannot track buffer's length.
- // JavascriptString::MaxCharLength is valid; however, we are incrementing below by 1 and want to make sure we aren't overflowing
- // Nor going outside of valid range.
- if (length >= JavascriptString::MaxCharLength)
- {
- Throw::OutOfMemory();
- }
- charcount_t bufLen = length + 1;
- // Allocate recycler memory to store the string plus a terminating NUL
- char16* buffer = RecyclerNewArrayLeaf(recycler, char16, bufLen);
- js_wmemcpy_s(buffer, bufLen, content, length);
- buffer[length] = _u('\0');
- return buffer;
- }
- /*static*/ __ecount(length+1) char16* JavascriptString::AllocateAndCopySz(__in ArenaAllocator* arena, __in_ecount(length) const char16* content, charcount_t length)
- {
- // Note: Intentionally not using SafeSzSize nor hoisting common
- // sub-expression "length + 1" into a local variable otherwise
- // Prefast gets confused and cannot track buffer's length.
- // JavascriptString::MaxCharLength is valid; however, we are incrementing below by 1 and want to make sure we aren't overflowing
- // Nor going outside of valid range.
- if (length >= JavascriptString::MaxCharLength)
- {
- Throw::OutOfMemory();
- }
- // Allocate arena memory to store the string plus a terminating NUL
- char16* buffer = AnewArray(arena, char16, length + 1);
- js_wmemcpy_s(buffer, length + 1, content, length);
- buffer[length] = _u('\0');
- return buffer;
- }
- RecyclableObject * JavascriptString::CloneToScriptContext(ScriptContext* requestContext)
- {
- return JavascriptString::NewWithBuffer(this->GetSz(), this->GetLength(), requestContext);
- }
- charcount_t JavascriptString::ConvertToIndex(Var varIndex, ScriptContext *scriptContext)
- {
- if (TaggedInt::Is(varIndex))
- {
- return TaggedInt::ToInt32(varIndex);
- }
- return NumberUtilities::LwFromDblNearest(JavascriptConversion::ToInteger(varIndex, scriptContext));
- }
- char16* JavascriptString::GetNormalizedString(_NORM_FORM form, ArenaAllocator* tempAllocator, charcount_t& sizeOfNormalizedStringWithoutNullTerminator)
- {
- ScriptContext* scriptContext = this->GetScriptContext();
- if (this->GetLength() == 0)
- {
- sizeOfNormalizedStringWithoutNullTerminator = 0;
- return nullptr;
- }
- // IMPORTANT: Implementation Notes
- // Normalize string estimates the required size of the buffer based on averages and other data.
- // It is very hard to get a precise size from an input string without expanding/contracting it on the buffer.
- // 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.
- // The approach taken is based on the simple example in the MSDN article.
- // - Loop until the return value is either an error (apart from insufficient buffer size), or success.
- // - Each time recreate a temporary buffer based on the last guess.
- // - When creating the JS string, use the positive return value and copy the buffer across.
- // Design choice for "guesses" comes from data Windows collected; and in most cases the loop will not iterate more than 2 times.
- Assert(!IsNormalizedString(form, this->GetSz(), this->GetLength()));
- //Get the first size estimate
- int sizeEstimate = NormalizeString(form, this->GetSz(), this->GetLength() + 1, nullptr, 0);
- char16 *tmpBuffer = nullptr;
- DWORD lastError = ERROR_INSUFFICIENT_BUFFER;
- //Loop while the size estimate is bigger than 0
- while (lastError == ERROR_INSUFFICIENT_BUFFER)
- {
- tmpBuffer = AnewArray(tempAllocator, char16, sizeEstimate);
- sizeEstimate = NormalizeString(form, this->GetSz(), this->GetLength() + 1, tmpBuffer, sizeEstimate);
- // Success, sizeEstimate is the exact size including the null terminator
- if (sizeEstimate > 0)
- {
- sizeOfNormalizedStringWithoutNullTerminator = sizeEstimate - 1;
- return tmpBuffer;
- }
- //Anything less than 0, we have an error, flip sizeEstimate now. As both times we need to use it, we need positive anyways.
- lastError = GetLastError();
- sizeEstimate *= -1;
- }
- switch ((long)lastError)
- {
- case ERROR_INVALID_PARAMETER:
- //some invalid parameter, coding error
- AssertMsg(false, "ERROR_INVALID_PARAMETER check pointers passed to NormalizeString");
- JavascriptError::ThrowRangeError(scriptContext, JSERR_FailedToNormalize);
- break;
- case ERROR_NO_UNICODE_TRANSLATION:
- //the value returned is the negative index of an invalid unicode character
- JavascriptError::ThrowRangeErrorVar(scriptContext, JSERR_InvalidUnicodeCharacter, sizeEstimate);
- break;
- case ERROR_SUCCESS:
- //The actual size of the output string is zero.
- //Theoretically only empty input string should produce this, which is handled above, thus the code path should not be hit.
- 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)");
- sizeOfNormalizedStringWithoutNullTerminator = 0;
- return nullptr; // scriptContext->GetLibrary()->GetEmptyString();
- break;
- default:
- AssertMsg(false, "Unknown error. MSDN documentation didn't specify any additional errors.");
- JavascriptError::ThrowRangeError(scriptContext, JSERR_FailedToNormalize);
- break;
- }
- }
- void JavascriptString::InstantiateForceInlinedMembers()
- {
- // Force-inlined functions defined in a translation unit need a reference from an extern non-force-inlined function in
- // the same translation unit to force an instantiation of the force-inlined function. Otherwise, if the force-inlined
- // function is not referenced in the same translation unit, it will not be generated and the linker is not able to find
- // the definition to inline the function in other translation units.
- Assert(false);
- JavascriptString *const s = nullptr;
- s->ConcatDestructive(nullptr);
- }
- JavascriptString *
- JavascriptString::Concat3(JavascriptString * pstLeft, JavascriptString * pstCenter, JavascriptString * pstRight)
- {
- ConcatStringMulti * concatString = ConcatStringMulti::New(3, pstLeft, pstCenter, pstLeft->GetScriptContext());
- concatString->SetItem(2, pstRight);
- return concatString;
- }
- BOOL JavascriptString::HasProperty(PropertyId propertyId)
- {
- if (propertyId == PropertyIds::length)
- {
- return true;
- }
- ScriptContext* scriptContext = GetScriptContext();
- charcount_t index;
- if (scriptContext->IsNumericPropertyId(propertyId, &index))
- {
- if (index < this->GetLength())
- {
- return true;
- }
- }
- return false;
- }
- BOOL JavascriptString::IsEnumerable(PropertyId propertyId)
- {
- ScriptContext* scriptContext = GetScriptContext();
- charcount_t index;
- if (scriptContext->IsNumericPropertyId(propertyId, &index))
- {
- if (index < this->GetLength())
- {
- return true;
- }
- }
- return false;
- }
- BOOL JavascriptString::GetProperty(Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
- {
- return GetPropertyBuiltIns(propertyId, value);
- }
- BOOL JavascriptString::GetProperty(Var originalInstance, JavascriptString* propertyNameString, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
- {
- PropertyRecord const* propertyRecord;
- this->GetScriptContext()->FindPropertyRecord(propertyNameString, &propertyRecord);
- if (propertyRecord != nullptr && GetPropertyBuiltIns(propertyRecord->GetPropertyId(), value))
- {
- return true;
- }
- return false;
- }
- bool JavascriptString::GetPropertyBuiltIns(PropertyId propertyId, Var* value)
- {
- if (propertyId == PropertyIds::length)
- {
- *value = JavascriptNumber::ToVar(this->GetLength(), this->GetScriptContext());
- return true;
- }
- return false;
- }
- BOOL JavascriptString::GetPropertyReference(Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
- {
- return JavascriptString::GetProperty(originalInstance, propertyId, value, info, requestContext);
- }
- BOOL JavascriptString::SetItem(uint32 index, Var value, PropertyOperationFlags propertyOperationFlags)
- {
- if (this->HasItemAt(index))
- {
- JavascriptError::ThrowCantAssignIfStrictMode(propertyOperationFlags, this->GetScriptContext());
- return FALSE;
- }
- return __super::SetItem(index, value, propertyOperationFlags);
- }
- BOOL JavascriptString::DeleteItem(uint32 index, PropertyOperationFlags propertyOperationFlags)
- {
- if (this->HasItemAt(index))
- {
- JavascriptError::ThrowCantDeleteIfStrictMode(propertyOperationFlags, this->GetScriptContext(), TaggedInt::ToString(index, this->GetScriptContext())->GetString());
- return FALSE;
- }
- return __super::DeleteItem(index, propertyOperationFlags);
- }
- BOOL JavascriptString::HasItem(uint32 index)
- {
- return this->HasItemAt(index);
- }
- BOOL JavascriptString::GetItem(Var originalInstance, uint32 index, Var* value, ScriptContext* requestContext)
- {
- // String should always be marshalled to the current context
- Assert(requestContext == this->GetScriptContext());
- return this->GetItemAt(index, value);
- }
- BOOL JavascriptString::GetItemReference(Var originalInstance, uint32 index, Var* value, ScriptContext* requestContext)
- {
- // String should always be marshalled to the current context
- return this->GetItemAt(index, value);
- }
- BOOL JavascriptString::GetEnumerator(BOOL enumNonEnumerable, Var* enumerator, ScriptContext * requestContext, bool preferSnapshotSemantics, bool enumSymbols)
- {
- *enumerator = RecyclerNew(GetScriptContext()->GetRecycler(), JavascriptStringEnumerator, this, requestContext);
- return true;
- }
- BOOL JavascriptString::DeleteProperty(PropertyId propertyId, PropertyOperationFlags propertyOperationFlags)
- {
- if (propertyId == PropertyIds::length)
- {
- JavascriptError::ThrowCantDeleteIfStrictMode(propertyOperationFlags, this->GetScriptContext(), this->GetScriptContext()->GetPropertyName(propertyId)->GetBuffer());
- return FALSE;
- }
- return __super::DeleteProperty(propertyId, propertyOperationFlags);
- }
- BOOL JavascriptString::GetDiagValueString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
- {
- stringBuilder->Append(_u('"'));
- stringBuilder->Append(this->GetString(), this->GetLength());
- stringBuilder->Append(_u('"'));
- return TRUE;
- }
- BOOL JavascriptString::GetDiagTypeString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
- {
- stringBuilder->AppendCppLiteral(_u("String"));
- return TRUE;
- }
- RecyclableObject* JavascriptString::ToObject(ScriptContext * requestContext)
- {
- return requestContext->GetLibrary()->CreateStringObject(this);
- }
- Var JavascriptString::GetTypeOfString(ScriptContext * requestContext)
- {
- return requestContext->GetLibrary()->GetStringTypeDisplayString();
- }
- }
|