lib/VM/JSLib/String.cpp (1,898 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ //===----------------------------------------------------------------------===// /// \file /// ES5.1 15.5 Initialize the String constructor. //===----------------------------------------------------------------------===// #include "JSLibInternal.h" #include "hermes/Platform/Unicode/PlatformUnicode.h" #include "hermes/VM/JSLib/RuntimeCommonStorage.h" #include "hermes/VM/Operations.h" #include "hermes/VM/PrimitiveBox.h" #include "hermes/VM/SmallXString.h" #include "hermes/VM/StringBuilder.h" #include "hermes/VM/StringView.h" #if defined(__ANDROID__) #include "hermes/Platform/Unicode/PlatformUnicode.h" #endif #ifdef __APPLE__ #include <CoreFoundation/CFString.h> #endif #include <locale> namespace hermes { namespace vm { //===----------------------------------------------------------------------===// /// String. Handle<JSObject> createStringConstructor(Runtime &runtime) { auto stringPrototype = Handle<JSString>::vmcast(&runtime.stringPrototype); auto cons = defineSystemConstructor<JSString>( runtime, Predefined::getSymbolID(Predefined::String), stringConstructor, stringPrototype, 1, CellKind::JSStringKind); // String.prototype.xxx methods. void *ctx = nullptr; defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::toString), ctx, stringPrototypeToString, 0); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::valueOf), ctx, stringPrototypeToString, 0); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::charCodeAt), ctx, stringPrototypeCharCodeAt, 1); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::codePointAt), ctx, stringPrototypeCodePointAt, 1); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::concat), ctx, stringPrototypeConcat, 1); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::substring), ctx, stringPrototypeSubstring, 2); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::toLowerCase), ctx, stringPrototypeToLowerCase, 0); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::toLocaleLowerCase), ctx, stringPrototypeToLocaleLowerCase, 0); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::toUpperCase), ctx, stringPrototypeToUpperCase, 0); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::toLocaleUpperCase), ctx, stringPrototypeToLocaleUpperCase, 0); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::substr), ctx, stringPrototypeSubstr, 2); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::trim), ctx, stringPrototypeTrim, 0); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::localeCompare), ctx, stringPrototypeLocaleCompare, 1); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::normalize), ctx, stringPrototypeNormalize, 0); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::repeat), ctx, stringPrototypeRepeat, 1); DefinePropertyFlags dpf = DefinePropertyFlags::getNewNonEnumerableFlags(); auto trimStartRes = runtime.makeHandle<Callable>(runtime.ignoreAllocationFailure(defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::trimStart), ctx, stringPrototypeTrimStart, 0, dpf))); auto trimEndRes = runtime.makeHandle<Callable>(runtime.ignoreAllocationFailure(defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::trimEnd), ctx, stringPrototypeTrimEnd, 0, dpf))); defineProperty( runtime, stringPrototype, Predefined::getSymbolID(Predefined::trimLeft), trimStartRes); defineProperty( runtime, stringPrototype, Predefined::getSymbolID(Predefined::trimRight), trimEndRes); (void)defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::SymbolIterator), Predefined::getSymbolID(Predefined::squareSymbolIterator), ctx, stringPrototypeSymbolIterator, 0, dpf); // String.xxx() methods. defineMethod( runtime, cons, Predefined::getSymbolID(Predefined::fromCharCode), ctx, stringFromCharCode, 1); defineMethod( runtime, cons, Predefined::getSymbolID(Predefined::fromCodePoint), ctx, stringFromCodePoint, 1); defineMethod( runtime, cons, Predefined::getSymbolID(Predefined::raw), ctx, stringRaw, 1); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::matchAll), ctx, stringPrototypeMatchAll, 1); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::replaceAll), ctx, stringPrototypeReplaceAll, 2); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::match), ctx, stringPrototypeMatch, 1); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::padEnd), (void *)false, stringPrototypePad, 1); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::padStart), (void *)true, stringPrototypePad, 1); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::replace), ctx, stringPrototypeReplace, 2); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::search), ctx, stringPrototypeSearch, 1); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::charAt), ctx, stringPrototypeCharAt, 1); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::endsWith), ctx, stringPrototypeEndsWith, 1); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::slice), ctx, stringPrototypeSlice, 2); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::split), ctx, stringPrototypeSplit, 2); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::includes), (void *)false, stringPrototypeIncludesOrStartsWith, 1); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::indexOf), ctx, stringPrototypeIndexOf, 1); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::lastIndexOf), ctx, stringPrototypeLastIndexOf, 1); defineMethod( runtime, stringPrototype, Predefined::getSymbolID(Predefined::startsWith), (void *)true, stringPrototypeIncludesOrStartsWith, 1); return cons; } CallResult<HermesValue> stringConstructor(void *, Runtime &runtime, NativeArgs args) { if (args.getArgCount() == 0) { return HermesValue::encodeStringValue( runtime.getPredefinedString(Predefined::emptyString)); } if (!args.isConstructorCall() && args.getArg(0).isSymbol()) { auto str = symbolDescriptiveString( runtime, Handle<SymbolID>::vmcast(args.getArgHandle(0))); if (LLVM_UNLIKELY(str == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return str->getHermesValue(); } auto sRes = toString_RJS(runtime, args.getArgHandle(0)); if (sRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } auto s = runtime.makeHandle(std::move(*sRes)); if (!args.isConstructorCall()) { // Not a constructor call, just return the string value. return s.getHermesValue(); } // Constructor call: initialize the JSString. auto self = args.vmcastThis<JSString>(); JSString::setPrimitiveString(self, runtime, s); return self.getHermesValue(); } CallResult<HermesValue> stringFromCharCode(void *, Runtime &runtime, NativeArgs args) { GCScope gcScope(runtime); uint32_t n = args.getArgCount(); if (LLVM_LIKELY(n == 1)) { // Fast path for when only one argument is provided. auto res = toUInt16(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } char16_t ch = res->getNumber(); return runtime.getCharacterString(ch).getHermesValue(); } auto builder = StringBuilder::createStringBuilder(runtime, SafeUInt32{n}); if (builder == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } for (unsigned i = 0; i < n; ++i) { // Call a function that may throw, let the runtime record it. auto res = toUInt16(runtime, args.getArgHandle(i)); if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } char16_t ch = res->getNumber(); builder->appendCharacter(ch); } return HermesValue::encodeStringValue(*builder->getStringPrimitive()); } CallResult<HermesValue> stringFromCodePoint(void *, Runtime &runtime, NativeArgs args) { GCScope gcScope{runtime}; // 1. Let codePoints be a List containing the arguments passed to this // function. // 2. Let length be the number of elements in codePoints. uint32_t length = args.getArgCount(); // 3. Let elements be a new List. llvh::SmallVector<char16_t, 32> elements{}; // 4. Let nextIndex be 0. uint32_t nextIndex = 0; MutableHandle<> next{runtime}; MutableHandle<> nextCP{runtime}; GCScopeMarkerRAII marker{gcScope}; // 5. Repeat while nextIndex < length for (; nextIndex < length; marker.flush()) { marker.flush(); // 5a. Let next be codePoints[nextIndex]. next = args.getArg(nextIndex); // 5b. Let nextCP be toNumber_RJS(next). auto nextCPRes = toNumber_RJS(runtime, next); if (LLVM_UNLIKELY(nextCPRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } nextCP = *nextCPRes; // 5d. If SameValue(nextCP, ToIntegerOrInfinity(nextCP)) is false, throw // a RangeError exception. auto nextCPInt = toIntegerOrInfinity(runtime, nextCP); if (LLVM_UNLIKELY(nextCPInt == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } if (!isSameValue(*nextCP, *nextCPInt)) { return runtime.raiseRangeError( TwineChar16("Code point must be an integer: ") + nextCP->getNumber()); } // 5e. If nextCP < 0 or nextCP > 0x10FFFF, throw a RangeError exception. if (nextCP->getNumber() < 0 || nextCP->getNumber() > 0x10FFFF) { return runtime.raiseRangeError( TwineChar16("Code point out of bounds: ") + nextCP->getNumber()); } // 5f. Append the elements of the UTF16Encoding (10.1.1) of nextCP to the // end of elements. // Safe to get as uint32_t because we've done int and bounds checking. utf16Encoding(nextCP->getNumberAs<uint32_t>(), elements); // 5g. Let nextIndex be nextIndex + 1. ++nextIndex; } // 6. Return the String value whose elements are, in order, the elements in // the List elements. If length is 0, the empty string is returned. return StringPrimitive::createEfficient(runtime, elements); } /// ES6.0 21.1.2.4 String.raw ( template , ...substitutions ) CallResult<HermesValue> stringRaw(void *, Runtime &runtime, NativeArgs args) { GCScope gcScope{runtime}; // 1. Let substitutions be a List consisting of all of the arguments passed to // this function, starting with the second argument. // If fewer than two arguments were passed, the List is empty. // 2. Let numberOfSubstitutions be the number of elements in substitutions. uint32_t numberOfSubstitutions = args.getArgCount() < 2 ? 0 : args.getArgCount() - 1; // 3. Let cooked be ToObject(template). auto cookedRes = toObject(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(cookedRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto cooked = runtime.makeHandle<JSObject>(*cookedRes); // 5. Let raw be ToObject(Get(cooked, "raw")). auto getRes = JSObject::getNamed_RJS( cooked, runtime, Predefined::getSymbolID(Predefined::raw)); if (LLVM_UNLIKELY(getRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto rawRes = toObject(runtime, runtime.makeHandle(std::move(*getRes))); if (LLVM_UNLIKELY(rawRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto raw = runtime.makeHandle<JSObject>(*rawRes); // 7. Let literalSegments be ToLength(Get(raw, "length")) auto lengthRes = JSObject::getNamed_RJS( raw, runtime, Predefined::getSymbolID(Predefined::length)); if (LLVM_UNLIKELY(lengthRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto literalSegmentsRes = toLength(runtime, runtime.makeHandle(std::move(*lengthRes))); if (LLVM_UNLIKELY(literalSegmentsRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } int64_t literalSegments = literalSegmentsRes->getNumberAs<int64_t>(); // 9. If literalSegments ≤ 0, return the empty string. if (literalSegments <= 0) { return HermesValue::encodeStringValue( runtime.getPredefinedString(Predefined::emptyString)); } // 10. Let stringElements be a new List. llvh::SmallVector<char16_t, 32> stringElements{}; // 11. Let nextIndex be 0. MutableHandle<> nextIndex{runtime, HermesValue::encodeNumberValue(0)}; MutableHandle<> tmpHandle{runtime}; MutableHandle<StringPrimitive> nextSeg{runtime}; MutableHandle<> next{runtime}; MutableHandle<StringPrimitive> nextSub{runtime}; // 12. Repeat GCScopeMarkerRAII marker{gcScope}; for (;; marker.flush()) { // 12. a. Let nextKey be ToString(nextIndex). // 12. b. Let nextSeg be ToString(Get(raw, nextKey)). auto nextSegPropRes = JSObject::getComputed_RJS(raw, runtime, nextIndex); if (LLVM_UNLIKELY(nextSegPropRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } tmpHandle = std::move(*nextSegPropRes); auto nextSegRes = toString_RJS(runtime, tmpHandle); if (LLVM_UNLIKELY(nextSegRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } nextSeg = nextSegRes->get(); // 12. d. Append in order the code unit elements of nextSeg to the end of // stringElements. nextSeg->appendUTF16String(stringElements); // 12. e. If nextIndex + 1 = literalSegments, then if (nextIndex->getNumberAs<int64_t>() + 1 == literalSegments) { // 12. i. Return the String value whose code units are, in order, the // elements in the List stringElements. If stringElements has no elements, // the empty string is returned. return StringPrimitive::createEfficient(runtime, stringElements); } if (nextIndex->getNumberAs<int64_t>() < numberOfSubstitutions) { // 12. f. If nextIndex < numberOfSubstitutions, let next be // substitutions[nextIndex]. // Add one to nextIndex to get index in substitutions. next = args.getArg(nextIndex->getNumberAs<int64_t>() + 1); // 12. h. Let nextSub be ToString(next). auto nextSubRes = toString_RJS(runtime, next); if (LLVM_UNLIKELY(nextSubRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } nextSub = nextSubRes->get(); // 12. j. Append in order the code unit elements of nextSub to the end of // stringElements. nextSub->appendUTF16String(stringElements); } // 12. g. Else, let next be the empty String. // Omitted because nothing happens. // 12. k. Let nextIndex be nextIndex + 1. nextIndex = HermesValue::encodeNumberValue(nextIndex->getNumberAs<int64_t>() + 1); } } //===----------------------------------------------------------------------===// /// String.prototype. CallResult<HermesValue> stringPrototypeToString(void *, Runtime &runtime, NativeArgs args) { if (args.getThisArg().isString()) { return args.getThisArg(); } // Not a String value, must be a string object. auto *strPtr = dyn_vmcast<JSString>(args.getThisArg()); if (strPtr) { // Only return the string if called on a String object. return HermesValue::encodeStringValue( JSString::getPrimitiveString(strPtr, runtime)); } return runtime.raiseTypeError( "String.prototype.toString() called on non-string object"); } CallResult<HermesValue> stringPrototypeCharCodeAt(void *, Runtime &runtime, NativeArgs args) { Handle<> thisValue{&args.getThisArg()}; // Call a function that may throw, let the runtime record it. if (LLVM_UNLIKELY( checkObjectCoercible(runtime, thisValue) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto strRes = toString_RJS(runtime, thisValue); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*strRes)); auto intRes = toIntegerOrInfinity(runtime, runtime.makeHandle(args.getArg(0))); if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto position = intRes->getNumber(); auto size = S->getStringLength(); if (position < 0 || position >= size) { return HermesValue::encodeNaNValue(); } return HermesValue::encodeDoubleValue( StringPrimitive::createStringView(runtime, S)[position]); } CallResult<HermesValue> stringPrototypeCodePointAt(void *, Runtime &runtime, NativeArgs args) { // 1. Let O be RequireObjectCoercible(this value). if (LLVM_UNLIKELY( checkObjectCoercible(runtime, args.getThisHandle()) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 2. Let S be ToString(O). auto strRes = toString_RJS(runtime, args.getThisHandle()); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*strRes)); // 4. Let position be ToIntegerOrInfinity(pos). auto positionRes = toIntegerOrInfinity(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(positionRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } double position = positionRes->getNumber(); // 6. Let size be the number of elements in S. double size = S->getStringLength(); // 7. If position < 0 or position ≥ size, return undefined. if (position < 0 || position >= size) { return HermesValue::encodeUndefinedValue(); } auto strView = StringPrimitive::createStringView(runtime, S); // 8. Let first be the code unit value of the element at index position in the // String S. char16_t first = strView[position]; // 9. If first < 0xD800 or first > 0xDBFF or position+1 = size, return first. if (first < 0xD800 || first > 0xDBFF || position + 1 == size) { return HermesValue::encodeNumberValue(first); } // 10. Let second be the code unit value of the element at index position+1 in // the String S. // Safe to access because we ensured that position + 1 < size. char16_t second = strView[position + 1]; // 11. If second < 0xDC00 or second > 0xDFFF, return first. if (second < 0xDC00 || second > 0xDFFF) { return HermesValue::encodeNumberValue(first); } // 12. Return UTF16Decode(first, second). return HermesValue::encodeNumberValue(utf16Decode(first, second)); } CallResult<HermesValue> stringPrototypeConcat(void *, Runtime &runtime, NativeArgs args) { GCScope gcScope(runtime); if (LLVM_UNLIKELY( checkObjectCoercible(runtime, args.getThisHandle()) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto strRes = toString_RJS(runtime, args.getThisHandle()); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*strRes)); // Track the total characters in the result. SafeUInt32 size(S->getStringLength()); uint32_t argCount = args.getArgCount(); // Store the results of toStrings and concat them at the end. auto arrRes = ArrayStorageSmall::create(runtime, argCount, argCount); if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto strings = runtime.makeHandle<ArrayStorageSmall>(*arrRes); // Run toString on the arguments to figure out the final size. auto marker = gcScope.createMarker(); for (uint32_t i = 0; i < argCount; ++i) { auto strRes = toString_RJS(runtime, args.getArgHandle(i)); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // Allocations can't be performed here, // and we know we're in bounds because we preallocated. strings->set( i, SmallHermesValue::encodeStringValue(strRes->get(), runtime), &runtime.getHeap()); uint32_t strLength = strRes->get()->getStringLength(); size.add(strLength); if (LLVM_UNLIKELY(size.isOverflowed())) { return runtime.raiseRangeError("resulting string length exceeds limit"); } gcScope.flushToMarker(marker); } // Allocate the complete result. auto builder = StringBuilder::createStringBuilder(runtime, size); if (builder == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // Copy 'this' argument first. builder->appendStringPrim(S); MutableHandle<StringPrimitive> element{runtime}; // Copy the rest of the strings. for (uint32_t i = 0; i < argCount; i++) { element = strings->at(i).getString(runtime); builder->appendStringPrim(element); } return builder->getStringPrimitive().getHermesValue(); } CallResult<HermesValue> stringPrototypeSubstring(void *, Runtime &runtime, NativeArgs args) { if (LLVM_UNLIKELY( checkObjectCoercible(runtime, args.getThisHandle()) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto strRes = toString_RJS(runtime, args.getThisHandle()); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*strRes)); double len = S->getStringLength(); auto intRes = toIntegerOrInfinity(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } double intStart = intRes->getNumber(); double intEnd; if (args.getArg(1).isUndefined()) { intEnd = len; } else { if (LLVM_UNLIKELY( (intRes = toIntegerOrInfinity(runtime, args.getArgHandle(1))) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } intEnd = intRes->getNumber(); } size_t finalStart = std::min(std::max(intStart, 0.0), len); size_t finalEnd = std::min(std::max(intEnd, 0.0), len); size_t from = std::min(finalStart, finalEnd); size_t to = std::max(finalStart, finalEnd); return StringPrimitive::slice(runtime, S, from, to > from ? to - from : 0); } static CallResult<HermesValue> convertCase( Runtime &runtime, Handle<StringPrimitive> S, const bool upperCase, const bool useCurrentLocale) { // Copying is unavoidable in this function, do it early on. SmallU16String<32> buff; // Must copy instead of just getting the reference, because later operations // may trigger GC and hence invalid pointers inside S. S->appendUTF16String(buff); UTF16Ref str = buff.arrayRef(); if (!useCurrentLocale) { // Try a fast path for ASCII strings. // First, bitwise-or all the characters to see if any one isn't ASCII. char16_t mask = 0; // Also, check if we have to do work or we can just return S directly. bool noop = true; if (upperCase) { for (const auto c : str) { mask |= c; // It's still a noop if the character isn't a lowercase ASCII. noop &= !('a' <= c && c <= 'z'); } } else { for (const auto c : str) { mask |= c; // It's still a noop if the character isn't an uppercase ASCII. noop &= !('A' <= c && c <= 'Z'); } } if (mask <= 127) { if (noop) { // We don't have to allocate anything. return S.getHermesValue(); } if (str.size() == 1) { // Use the Runtime stored representations of single-character strings. char16_t c = str[0]; if (upperCase) { char16_t isLower = 'a' <= c && c <= 'z'; return runtime.getCharacterString(c & ~(isLower << 5)) .getHermesValue(); } else { char16_t isUpper = 'A' <= c && c <= 'Z'; return runtime.getCharacterString(c | (isUpper << 5)) .getHermesValue(); } } SafeUInt32 len(S->getStringLength()); auto builder = StringBuilder::createStringBuilder(runtime, len); if (builder == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } char16_t ch; if (upperCase) { for (const char16_t c : str) { // If it is lower, then clear the 5th bit, else do nothing. char16_t isLower = 'a' <= c && c <= 'z'; ch = c & ~(isLower << 5); builder->appendCharacter(ch); } } else { for (const char16_t c : str) { // If it is upper, then set the 5th bit, else do nothing. char16_t isUpper = 'A' <= c && c <= 'Z'; ch = c | (isUpper << 5); builder->appendCharacter(ch); } } return HermesValue::encodeStringValue(*builder->getStringPrimitive()); } } platform_unicode::convertToCase( buff, upperCase ? platform_unicode::CaseConversion::ToUpper : platform_unicode::CaseConversion::ToLower, useCurrentLocale); return StringPrimitive::create(runtime, buff); } CallResult<HermesValue> stringPrototypeToLowerCase(void *, Runtime &runtime, NativeArgs args) { if (LLVM_UNLIKELY( checkObjectCoercible(runtime, args.getThisHandle()) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto res = toString_RJS(runtime, args.getThisHandle()); if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return convertCase( runtime, runtime.makeHandle(std::move(*res)), false, false); } CallResult<HermesValue> stringPrototypeToLocaleLowerCase(void *ctx, Runtime &runtime, NativeArgs args) { #ifdef HERMES_ENABLE_INTL return intlStringPrototypeToLocaleLowerCase(/* unused */ ctx, runtime, args); #else if (LLVM_UNLIKELY( checkObjectCoercible(runtime, args.getThisHandle()) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto res = toString_RJS(runtime, args.getThisHandle()); if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return convertCase(runtime, runtime.makeHandle(std::move(*res)), false, true); #endif } CallResult<HermesValue> stringPrototypeToUpperCase(void *, Runtime &runtime, NativeArgs args) { if (LLVM_UNLIKELY( checkObjectCoercible(runtime, args.getThisHandle()) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto res = toString_RJS(runtime, args.getThisHandle()); if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return convertCase(runtime, runtime.makeHandle(std::move(*res)), true, false); } CallResult<HermesValue> stringPrototypeToLocaleUpperCase(void *ctx, Runtime &runtime, NativeArgs args) { #ifdef HERMES_ENABLE_INTL return intlStringPrototypeToLocaleUpperCase(/* unused */ ctx, runtime, args); #else if (LLVM_UNLIKELY( checkObjectCoercible(runtime, args.getThisHandle()) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto res = toString_RJS(runtime, args.getThisHandle()); if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return convertCase(runtime, runtime.makeHandle(std::move(*res)), true, true); #endif } CallResult<HermesValue> stringPrototypeSubstr(void *, Runtime &runtime, NativeArgs args) { if (LLVM_UNLIKELY( checkObjectCoercible(runtime, args.getThisHandle()) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto strRes = toString_RJS(runtime, args.getThisHandle()); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*strRes)); double stringLen = S->getStringLength(); auto intRes = toIntegerOrInfinity(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } double start = intRes->getNumber(); double length; if (args.getArg(1).isUndefined()) { length = stringLen; } else { if (LLVM_UNLIKELY( (intRes = toIntegerOrInfinity(runtime, args.getArgHandle(1))) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } length = intRes->getNumber(); } if (start < 0) { start = std::max(stringLen + start, 0.0); } double adjustedLength = std::min(std::max(length, 0.0), stringLen - start); if (adjustedLength <= 0) { return HermesValue::encodeStringValue( runtime.getPredefinedString(Predefined::emptyString)); } else { return StringPrimitive::slice( runtime, S, static_cast<size_t>(start), static_cast<size_t>(adjustedLength)); } } /// \return the number of characters to trim from the begin iterator. static size_t trimStart( StringView::const_iterator begin, StringView::const_iterator end) { size_t toTrim = 0; while (begin != end && (isWhiteSpaceChar(*begin) || isLineTerminatorChar(*begin))) { ++begin; ++toTrim; } return toTrim; } /// \return the number of characters to trim from the end iterator. static size_t trimEnd( StringView::const_iterator begin, StringView::const_iterator end) { size_t toTrim = 0; while (begin != end && (isWhiteSpaceChar(*(end - 1)) || isLineTerminatorChar(*(end - 1)))) { --end; ++toTrim; } return toTrim; } CallResult<HermesValue> stringPrototypeTrim(void *, Runtime &runtime, NativeArgs args) { if (LLVM_UNLIKELY( checkObjectCoercible(runtime, args.getThisHandle()) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto res = toString_RJS(runtime, args.getThisHandle()); if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*res)); // Move begin and end to point to the first and last non-whitespace chars. size_t beginIdx = 0, endIdx = S->getStringLength(); { auto str = StringPrimitive::createStringView(runtime, S); auto begin = str.begin(); auto end = str.end(); beginIdx = trimStart(begin, end); begin += beginIdx; endIdx -= trimEnd(begin, end); } return StringPrimitive::slice(runtime, S, beginIdx, endIdx - beginIdx); } CallResult<HermesValue> stringPrototypeTrimStart(void *, Runtime &runtime, NativeArgs args) { if (LLVM_UNLIKELY( checkObjectCoercible(runtime, args.getThisHandle()) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto res = toString_RJS(runtime, args.getThisHandle()); if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*res)); // Move begin and end to point to the first and last non-whitespace chars. size_t beginIdx = 0; { auto str = StringPrimitive::createStringView(runtime, S); auto begin = str.begin(); auto end = str.end(); beginIdx = trimStart(begin, end); } return StringPrimitive::slice( runtime, S, beginIdx, S->getStringLength() - beginIdx); } CallResult<HermesValue> stringPrototypeTrimEnd(void *, Runtime &runtime, NativeArgs args) { if (LLVM_UNLIKELY( checkObjectCoercible(runtime, args.getThisHandle()) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto res = toString_RJS(runtime, args.getThisHandle()); if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*res)); // Move begin and end to point to the first and last non-whitespace chars. size_t endIdx = S->getStringLength(); { auto str = StringPrimitive::createStringView(runtime, S); auto begin = str.begin(); auto end = str.end(); endIdx -= trimEnd(begin, end); } return StringPrimitive::slice(runtime, S, 0, endIdx); } CallResult<HermesValue> stringPrototypeLocaleCompare(void *ctx, Runtime &runtime, NativeArgs args) { #ifdef HERMES_ENABLE_INTL return intlStringPrototypeLocaleCompare(/* unused */ ctx, runtime, args); #else auto thisValue = args.getThisHandle(); if (LLVM_UNLIKELY( checkObjectCoercible(runtime, thisValue) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto sRes = toString_RJS(runtime, thisValue); if (LLVM_UNLIKELY(sRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*sRes)); auto tRes = toString_RJS(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(tRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // "That" string. auto T = runtime.makeHandle(std::move(*tRes)); llvh::SmallVector<char16_t, 32> left; llvh::SmallVector<char16_t, 32> right; StringPrimitive::createStringView(runtime, S).appendUTF16String(left); StringPrimitive::createStringView(runtime, T).appendUTF16String(right); int comparisonResult = platform_unicode::localeCompare(left, right); assert(comparisonResult >= -1 && comparisonResult <= 1); return HermesValue::encodeNumberValue(comparisonResult); #endif } CallResult<HermesValue> stringPrototypeNormalize(void *, Runtime &runtime, NativeArgs args) { using platform_unicode::NormalizationForm; // 1. Let O be RequireObjectCoercible(this value). auto O = args.getThisHandle(); if (LLVM_UNLIKELY( checkObjectCoercible(runtime, O) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 2. Let S be ToString(O). auto sRes = toString_RJS(runtime, O); if (LLVM_UNLIKELY(sRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*sRes)); NormalizationForm form; // 4. If form is not provided or form is undefined, let form be "NFC". if (args.getArg(0).isUndefined()) { form = NormalizationForm::C; } else { // 5. Let f be ToString(form). auto fRes = toString_RJS(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(fRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto f = runtime.makeHandle(std::move(*fRes)); // 7. If f is not one of "NFC", "NFD", "NFKC", or "NFKD", throw a RangeError // exception. auto sv = StringPrimitive::createStringView(runtime, f); if (sv.equals(ASCIIRef{"NFC", 3})) { form = NormalizationForm::C; } else if (sv.equals(ASCIIRef{"NFD", 3})) { form = NormalizationForm::D; } else if (sv.equals(ASCIIRef{"NFKC", 4})) { form = NormalizationForm::KC; } else if (sv.equals(ASCIIRef{"NFKD", 4})) { form = NormalizationForm::KD; } else { return runtime.raiseRangeError( TwineChar16("Invalid normalization form: ") + *f); } } // 8. Let ns be the String value that is the result of normalizing S into the // normalization form named by f as specified in // http://www.unicode.org/reports/tr15/tr15-29.html. llvh::SmallVector<char16_t, 32> ns; S->appendUTF16String(ns); platform_unicode::normalize(ns, form); // 9. Return ns. return StringPrimitive::createEfficient(runtime, ns); } CallResult<HermesValue> stringPrototypeRepeat(void *, Runtime &runtime, NativeArgs args) { // 1. Let O be RequireObjectCoercible(this value). auto O = args.getThisHandle(); if (LLVM_UNLIKELY( checkObjectCoercible(runtime, O) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 2. Let S be ToString(O). // 3. ReturnIfAbrupt(S). auto sRes = toString_RJS(runtime, O); if (LLVM_UNLIKELY(sRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*sRes)); // 4. Let n be ToIntegerOrInfinity(count). // 5. ReturnIfAbrupt(n). auto nRes = toIntegerOrInfinity(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(nRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } double n = nRes->getNumber(); // 6. If n < 0, throw a RangeError exception. // 7. If n is +Infinity, throw a RangeError exception. if (n < 0 || n == std::numeric_limits<double>::infinity()) { return runtime.raiseRangeError( "String.prototype.repeat count must be finite and non-negative"); } // 8. Let T be a String value that is made from n copies of S appended // together. If n is 0, T is the empty String. double strLen = S->getStringLength(); if (n == 0 || strLen == 0) { return HermesValue::encodeStringValue( runtime.getPredefinedString(Predefined::emptyString)); } if (n > std::numeric_limits<uint32_t>::max() || S->getStringLength() > (double)StringPrimitive::MAX_STRING_LENGTH / n) { // Check for overflow. return runtime.raiseRangeError( "String.prototype.repeat result exceeds limit"); } // It's safe to multiply as the overflow check is done above. SafeUInt32 finalLen(strLen * n); auto builderRes = StringBuilder::createStringBuilder(runtime, finalLen); if (LLVM_UNLIKELY(builderRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // Using uint32_t for i is valid because we have bounds-checked n. for (uint32_t i = 0; i < n; ++i) { builderRes->appendStringPrim(S); } // 9. Return T. return builderRes->getStringPrimitive().getHermesValue(); } /// ES6.0 21.1.3.27 String.prototype [ @@iterator ]( ) CallResult<HermesValue> stringPrototypeSymbolIterator(void *, Runtime &runtime, NativeArgs args) { // 1. Let O be RequireObjectCoercible(this value). auto thisValue = args.getThisHandle(); if (LLVM_UNLIKELY( checkObjectCoercible(runtime, thisValue) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 2. Let S be ToString(O). // 3. ReturnIfAbrupt(S). auto strRes = toString_RJS(runtime, thisValue); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto string = runtime.makeHandle(std::move(*strRes)); // 4. Return CreateStringIterator(S). return JSStringIterator::create(runtime, string).getHermesValue(); } CallResult<HermesValue> stringPrototypeMatchAll(void *, Runtime &runtime, NativeArgs args) { // 1. Let O be ? RequireObjectCoercible(this value). auto O = args.getThisHandle(); if (LLVM_UNLIKELY( checkObjectCoercible(runtime, O) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 2. If regexp is neither undefined nor null, then auto regexp = args.getArgHandle(0); if (!regexp->isUndefined() && !regexp->isNull()) { // a. Let isRegExp be ? IsRegExp(regexp). auto isRegExpRes = isRegExp(runtime, regexp); if (LLVM_UNLIKELY(isRegExpRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // b. If isRegExp is true, then if (*isRegExpRes) { // Passing undefined and null checks imply regexp is an ObjectCoercible. Handle<JSObject> regexpObj = Handle<JSObject>::vmcast(regexp); bool isGlobal = false; // i. Let flags be ? Get(regexp, "flags"). auto flagsPropRes = JSObject::getNamed_RJS( regexpObj, runtime, Predefined::getSymbolID(Predefined::flags)); if (LLVM_UNLIKELY(flagsPropRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto flags = runtime.makeHandle(std::move(*flagsPropRes)); // ii. Perform ? RequireObjectCoercible(flags). if (LLVM_UNLIKELY( checkObjectCoercible(runtime, flags) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // iii. If ? ToString(flags) does not contain "g", throw a TypeError // exception. auto strRes = toString_RJS(runtime, flags); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto strView = StringPrimitive::createStringView( runtime, runtime.makeHandle(std::move(*strRes))); for (char16_t c : strView) if (c == u'g') isGlobal = true; if (!isGlobal) return runtime.raiseTypeError( "String.prototype.matchAll called with a non-global RegExp argument"); } // c. Let matcher be ? GetMethod(regexp, @@matchAll). auto matcherRes = getMethod( runtime, regexp, runtime.makeHandle( Predefined::getSymbolID(Predefined::SymbolMatchAll))); if (LLVM_UNLIKELY(matcherRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // d. If matcher is not undefined, then if (!matcherRes->getHermesValue().isUndefined()) { auto matcher = runtime.makeHandle<Callable>(std::move(*matcherRes)); // i. Return ? Call(matcher, regexp, «O»). return Callable::executeCall1( matcher, runtime, regexp, O.getHermesValue()) .toCallResultHermesValue(); } } // 3. Let S be ? ToString(O). auto strRes = toString_RJS(runtime, O); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*strRes)); // 4. Let rx be ? RegExpCreate(regexp, "g"). auto regRes = regExpCreate(runtime, regexp, runtime.getCharacterString('g')); if (regRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } Handle<JSRegExp> rx = regRes.getValue(); // 5. Return ? Invoke(rx, @@matchAll, «S»). auto propRes = JSObject::getNamed_RJS( rx, runtime, Predefined::getSymbolID(Predefined::SymbolMatchAll)); if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto func = Handle<Callable>::dyn_vmcast(runtime.makeHandle(std::move(*propRes))); if (LLVM_UNLIKELY(!func)) { return runtime.raiseTypeError( "RegExp.prototype[@@matchAll] must be callable."); } return Callable::executeCall1(func, runtime, rx, S.getHermesValue()) .toCallResultHermesValue(); } /// This provides a shared implementation of three operations in ES2021: /// 6.1.4.1 Runtime Semantics: StringIndexOf ( string, searchValue, fromIndex ) /// when clampPostion=false, /// 21.1.3.8 String.prototype.indexOf ( searchString [ , position ] ) /// when reverse=false, and /// 21.1.3.9 String.prototype.lastIndexOf ( searchString [ , position ] ) /// when reverse=true. /// /// Given a haystack ('string'), needle ('searchString'), and position, return /// the index of the first (reverse=false) or last (reverse=true) substring /// match of needle within haystack that is not smaller (normal) or larger /// (reverse) than position. /// /// \param runtime the runtime to use for argument coercions /// \param string represent the string searching from, i.e. "this" of /// indexOf / lastIndexOf or "string" of StringIndexOf. /// \param searchString represent the substring searching for, i.e. /// "searchString" of indexOf / lastIndexOf or "searchValue" of StringIndexOf. /// \param position represent the starting index of the search, i.e. /// "position" of indexOf / lastIndexOf or "fromIndex" of StringIndexOf. /// \param reverse whether we are running lastIndexOf (true) or indexOf (false) /// \param clampPosition whether the "position" is clamped to [0, length) /// (true if running indexOf / lastIndexOf) or not (running StringIndexOf). /// \returns Hermes-encoded index of the substring match, or -1 on failure static CallResult<HermesValue> stringDirectedIndexOf( Runtime &runtime, Handle<> string, Handle<> searchString, Handle<> position, bool reverse, bool clampPosition = true) { // 1. Let O be ? RequireObjectCoercible(this value). // Call a function that may throw, let the runtime record it. if (LLVM_UNLIKELY( checkObjectCoercible(runtime, string) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 2. Let S be ? ToString(O). auto strRes = toString_RJS(runtime, string); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*strRes)); // 3. Let searchStr be ? ToString(searchString). auto searchStrRes = toString_RJS(runtime, searchString); if (searchStrRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } auto searchStr = runtime.makeHandle(std::move(*searchStrRes)); double pos; if (reverse) { // lastIndexOf // 4. Let numPos be ? ToNumber(position). auto intRes = toNumber_RJS(runtime, position); if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } Handle<> numPos = runtime.makeHandle(intRes.getValue()); // 6. If numPos is NaN, let pos be +∞; otherwise, let pos be ! // ToIntegerOrInfinity(numPos). if (std::isnan(numPos->getNumber())) { pos = std::numeric_limits<double>::infinity(); } else { if (LLVM_UNLIKELY( (intRes = toIntegerOrInfinity(runtime, numPos)) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } pos = intRes->getNumber(); } } else { // indexOf // 4. Let pos be ? ToIntegerOrInfinity(position). auto intRes = toIntegerOrInfinity(runtime, position); if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } pos = intRes->getNumber(); } // Let len be the length of S. double len = S->getStringLength(); // When pos > len and "searchString" is an empty string, StringIndexOf behaves // differently than String.prototype.indexOf in terms of the "clampPosition": // 'aa'.indexOf('', 3) => 2 3 is clamped to 2. // StringIndexOf('aa', '', 3) => -1 3 is not clamped thus -1. // Also, when pos > len and "searchString" is non-empty, they both fail. // Therefore, it's safe to early return -1 for the case of StringIndexOf // (i.e. clampPosition=false) as soon as pos > len is observed. if (!clampPosition && pos > len) { return HermesValue::encodeNumberValue(-1); } // Let start be min(max(pos, 0), len). uint32_t start = static_cast<uint32_t>(std::min(std::max(pos, 0.), len)); // TODO: good candidate for Boyer-Moore on large needles/haystacks // TODO: good candidate for memchr on length-1 needles auto SView = StringPrimitive::createStringView(runtime, S); auto searchStrView = StringPrimitive::createStringView(runtime, searchStr); double ret = -1; if (reverse) { // lastIndexOf uint32_t lastPossibleMatchEnd = std::min(SView.length(), start + searchStrView.length()); auto foundIter = std::search( SView.rbegin() + (SView.length() - lastPossibleMatchEnd), SView.rend(), searchStrView.rbegin(), searchStrView.rend()); if (foundIter != SView.rend() || searchStrView.empty()) { ret = SView.rend() - foundIter - searchStrView.length(); } } else { // indexOf auto foundIter = std::search( SView.begin() + start, SView.end(), searchStrView.begin(), searchStrView.end()); if (foundIter != SView.end() || searchStrView.empty()) { ret = foundIter - SView.begin(); } } return HermesValue::encodeDoubleValue(ret); } /// ES12 6.1.4.1 Runtime Semantics: StringIndexOf ( string, searchValue, /// fromIndex ) /// This is currently implemented as a wrapper of stringDirectedIndexOf. /// Ideally, this can be implemented with less runtime checks and provide a fast /// path than stringDirectedIndexOf. TODO(T74338730) static CallResult<HermesValue> stringIndexOf( Runtime &runtime, Handle<StringPrimitive> string, Handle<StringPrimitive> searchValue, uint32_t fromIndex) { // 1. Assert: Type(string) is String. // 2. Assert: Type(searchValue) is String. // 3. Assert: ! IsNonNegativeInteger(fromIndex) is true. return stringDirectedIndexOf( runtime, string, searchValue, runtime.makeHandle(HermesValue::encodeDoubleValue(fromIndex)), false, false); } /// ES12 21.1.3.18 String.prototype.replaceAll ( searchValue, replaceValue ) CallResult<HermesValue> stringPrototypeReplaceAll(void *, Runtime &runtime, NativeArgs args) { GCScope gcScope{runtime}; // 1. Let O be ? RequireObjectCoercible(this value). auto O = args.getThisHandle(); if (LLVM_UNLIKELY( checkObjectCoercible(runtime, O) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto searchValue = args.getArgHandle(0); auto replaceValue = args.getArgHandle(1); // 2. If searchValue is neither undefined nor null, then if (!searchValue->isUndefined() && !searchValue->isNull()) { // a. Let isRegExp be ? IsRegExp(searchValue). auto isRegExpRes = isRegExp(runtime, searchValue); if (LLVM_UNLIKELY(isRegExpRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // b. If isRegExp is true, then if (*isRegExpRes) { Handle<JSObject> regexpObj = Handle<JSObject>::vmcast(searchValue); // i. Let flags be ? Get(searchValue, "flags"). auto flagsPropRes = JSObject::getNamed_RJS( regexpObj, runtime, Predefined::getSymbolID(Predefined::flags)); if (LLVM_UNLIKELY(flagsPropRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto flags = runtime.makeHandle(std::move(*flagsPropRes)); // ii. Perform ? RequireObjectCoercible(flags). if (LLVM_UNLIKELY( checkObjectCoercible(runtime, flags) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // iii. If ? ToString(flags) does not contain "g", throw a TypeError // exception. auto strRes = toString_RJS(runtime, flags); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto strView = StringPrimitive::createStringView( runtime, runtime.makeHandle(std::move(*strRes))); bool isGlobal = false; for (char16_t c : strView) if (c == u'g') isGlobal = true; if (!isGlobal) return runtime.raiseTypeError( "String.prototype.replaceAll called with a non-global RegExp argument"); } // c. Let replacer be ? GetMethod(searchValue, @@replace). auto replacerRes = getMethod( runtime, searchValue, runtime.makeHandle(Predefined::getSymbolID(Predefined::SymbolReplace))); if (LLVM_UNLIKELY(replacerRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // d. If replacer is not undefined, then if (!replacerRes->getHermesValue().isUndefined()) { auto replacer = runtime.makeHandle<Callable>(std::move(*replacerRes)); // i. Return ? Call(replacer, searchValue, « O, replaceValue ») return Callable::executeCall2( replacer, runtime, searchValue, O.getHermesValue(), replaceValue.getHermesValue()) .toCallResultHermesValue(); } } // 3. Let string be ? ToString(O). auto strRes = toString_RJS(runtime, O); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto string = runtime.makeHandle(std::move(*strRes)); // 4. Let searchString be ? ToString(searchValue). auto searchStrRes = toString_RJS(runtime, searchValue); if (LLVM_UNLIKELY(searchStrRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto searchString = runtime.makeHandle(std::move(*searchStrRes)); // 5. Let functionalReplace be IsCallable(replaceValue). auto replaceFn = Handle<Callable>::dyn_vmcast(replaceValue); bool functionalReplace = !!replaceFn; // It need to mutable since it's written here but read below. MutableHandle<StringPrimitive> replaceValueStr{runtime}; // 6. If functionalReplace is false, then if (!functionalReplace) { // a. Set replaceValue to ? ToString(replaceValue). auto replaceValueStrRes = toString_RJS(runtime, replaceValue); if (LLVM_UNLIKELY(replaceValueStrRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } replaceValueStr = std::move(*replaceValueStrRes); } // 7. Let searchLength be the length of searchString. uint32_t searchLength = searchString->getStringLength(); // 8. Let advanceBy be max(1, searchLength). uint32_t advanceBy = std::max(1u, searchLength); // 9. Let matchPositions be a new empty List. llvh::SmallVector<int32_t, 8> matchPositions{}; // 10. Let position be ! StringIndexOf(string, searchString, 0). auto positionRes = stringIndexOf(runtime, string, searchString, 0); int32_t position = positionRes->getNumberAs<int32_t>(); // 11. Repeat, while position is not -1, while (position != -1) { GCScopeMarkerRAII marker{runtime}; // a. Append position to the end of matchPositions. matchPositions.push_back(position); // b. Set position to ! StringIndexOf(string, searchString, position + // advanceBy). positionRes = stringIndexOf(runtime, string, searchString, position + advanceBy); assert( positionRes == ExecutionStatus::RETURNED && "StringIndexOf cannot fail"); position = positionRes->getNumberAs<int32_t>(); } // 12. Let endOfLastMatch be 0. uint32_t endOfLastMatch = 0; // 13. Let result be the empty String value. SmallU16String<32> result{}; // 14. For each position in matchPositions, do auto stringView = StringPrimitive::createStringView(runtime, string); MutableHandle<StringPrimitive> replacement{runtime}; MutableHandle<> replacementCallRes{runtime}; for (uint32_t i = 0, size = matchPositions.size(); i < size; ++i) { GCScopeMarkerRAII marker{runtime}; uint32_t position = matchPositions[i]; // a. Let preserved be the substring of string from endOfLastMatch to // position. // Noted that "substring" is from inclusiveStart to exclusiveEnd. auto preserved = stringView.slice(endOfLastMatch, position - endOfLastMatch); // b. If functionalReplace is true, then if (functionalReplace) { // i. Let replacement be ? ToString(? // Call(replaceValue, undefined, « searchString, position, string »)). auto callRes = Callable::executeCall3( replaceFn, runtime, Runtime::getUndefinedValue(), searchString.getHermesValue(), HermesValue::encodeNumberValue(position), string.getHermesValue()) .toCallResultHermesValue(); if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } replacementCallRes = *callRes; auto replacementStrRes = toString_RJS(runtime, replacementCallRes); if (LLVM_UNLIKELY(replacementStrRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } replacement = std::move(*replacementStrRes); } else { // c. Else, // i. Assert: Type(replaceValue) is String. // ii. Let captures be a new empty List. auto captures = Runtime::makeNullHandle<ArrayStorageSmall>(); // iii. Let replacement be ! GetSubstitution(searchString, string, // position, captures, undefined, replaceValue). auto callRes = getSubstitution( runtime, searchString, string, (double)position, captures, replaceValueStr); replacement = vmcast<StringPrimitive>(*callRes); } // d. Set result to the string-concatenation of result, preserved, and // replacement. preserved.appendUTF16String(result); StringPrimitive::createStringView(runtime, replacement) .appendUTF16String(result); // e. Set endOfLastMatch to position + searchLength. endOfLastMatch = position + searchLength; } // 15. If endOfLastMatch < the length of string, then if (endOfLastMatch < string->getStringLength()) { // a. Set result to the string-concatenation of result and the substring of // string from endOfLastMatch. stringView.slice(endOfLastMatch).appendUTF16String(result); } // 16. Return result. return StringPrimitive::create(runtime, result); } CallResult<HermesValue> stringPrototypeMatch(void *, Runtime &runtime, NativeArgs args) { // 1. Let O be RequireObjectCoercible(this value). // 2. ReturnIfAbrupt(O). auto O = args.getThisHandle(); if (LLVM_UNLIKELY( checkObjectCoercible(runtime, O) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 3. If regexp is neither undefined nor null, then auto regexp = args.getArgHandle(0); if (!regexp->isUndefined() && !regexp->isNull()) { // a. Let matcher be GetMethod(regexp, @@match). auto methodRes = getMethod( runtime, regexp, runtime.makeHandle(Predefined::getSymbolID(Predefined::SymbolMatch))); // b. ReturnIfAbrupt(matcher). if (LLVM_UNLIKELY(methodRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // c. If matcher is not undefined, then // i. Return Call(matcher, regexp, «‍O»). if (!methodRes->getHermesValue().isUndefined()) { auto matcher = runtime.makeHandle<Callable>(std::move(*methodRes)); return Callable::executeCall1( matcher, runtime, regexp, O.getHermesValue()) .toCallResultHermesValue(); } } // 4. Let S be ToString(O). auto strRes = toString_RJS(runtime, O); // 5. ReturnIfAbrupt(S). if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*strRes)); // 6. Let rx be RegExpCreate(regexp, undefined) (see 21.2.3.2.3). // 7. ReturnIfAbrupt(rx). auto regRes = regExpCreate(runtime, regexp, Runtime::getUndefinedValue()); if (regRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } Handle<JSRegExp> rx = regRes.getValue(); // 8. Return Invoke(rx, @@match, «‍S»). auto propRes = JSObject::getNamed_RJS( rx, runtime, Predefined::getSymbolID(Predefined::SymbolMatch)); if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto func = Handle<Callable>::dyn_vmcast(runtime.makeHandle(std::move(*propRes))); if (LLVM_UNLIKELY(!func)) { return runtime.raiseTypeError( "RegExp.prototype[@@match] must be callable."); } return Callable::executeCall1(func, runtime, rx, S.getHermesValue()) .toCallResultHermesValue(); } CallResult<HermesValue> stringPrototypePad(void *ctx, Runtime &runtime, NativeArgs args) { bool padStart = (bool)ctx; // 1. Let O be ? RequireObjectCoercible(this value). auto O = args.getThisHandle(); if (LLVM_UNLIKELY( checkObjectCoercible(runtime, O) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 2. Let S be ? ToString(O). auto sRes = toString_RJS(runtime, O); if (LLVM_UNLIKELY(sRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*sRes)); // 3. Let intMaxLength be ? ToLength(maxLength). auto intMaxLengthRes = toLength(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(intMaxLengthRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } const uint64_t intMaxLength = intMaxLengthRes->getNumberAs<int64_t>(); // 4. Let stringLength be the number of elements in S. const uint32_t stringLength = S->getStringLength(); SafeUInt32 size{stringLength}; // 5. If intMaxLength is not greater than stringLength, return S. if (intMaxLength <= stringLength) { return S.getHermesValue(); } MutableHandle<StringPrimitive> filler{runtime}; if (args.getArg(1).isUndefined()) { // 6. If fillString is undefined, let filler be a String consisting solely // of the code unit 0x0020 (SPACE). filler = runtime.getPredefinedString(Predefined::space); } else { // 7. Else, let filler be ? ToString(fillString). auto fillerRes = toString_RJS(runtime, args.getArgHandle(1)); if (LLVM_UNLIKELY(fillerRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } filler = fillerRes->get(); } // 8. If filler is the empty String, return S. if (filler->getStringLength() == 0) { return S.getHermesValue(); } // 9. Let fillLen be intMaxLength - stringLength. const uint64_t fillLen = intMaxLength - stringLength; // Check for overflow and strings that are too long, so we can ensure uint32_t // is a large enough type to use for math below. if (fillLen > StringPrimitive::MAX_STRING_LENGTH) { return runtime.raiseRangeError("String pad result exceeds limit"); } size.add((uint32_t)fillLen); if (size.isZero()) { return HermesValue::encodeStringValue( runtime.getPredefinedString(Predefined::emptyString)); } auto builderRes = StringBuilder::createStringBuilder(runtime, size); if (LLVM_UNLIKELY(builderRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } uint32_t resultLen = size.get(); // Repeatedly add filler to builder, taking up resultLen - stringLength // characters. auto addFiller = [&filler, resultLen, stringLength](StringBuilder &builder) { uint32_t remaining = resultLen - stringLength; const uint32_t fillerLen = filler->getStringLength(); while (remaining != 0) { uint32_t length = std::min(remaining, fillerLen); builder.appendStringPrim(filler, length); remaining -= length; } }; if (padStart) { // 10. Let truncatedStringFiller be a new String value consisting of // repeated concatenations of filler truncated to length fillLen. // 11. Return a new String value computed by the concatenation of // truncatedStringFiller and S. addFiller(*builderRes); builderRes->appendStringPrim(S); } else { // 10. Let truncatedStringFiller be a new String value consisting of // repeated concatenations of filler truncated to length fillLen. // 11. Return a new String value computed by the concatenation of S and // truncatedStringFiller. builderRes->appendStringPrim(S); addFiller(*builderRes); } return builderRes->getStringPrimitive().getHermesValue(); } CallResult<HermesValue> stringPrototypeReplace(void *, Runtime &runtime, NativeArgs args) { // 1. Let O be RequireObjectCoercible(this value). // 2. ReturnIfAbrupt(O). auto O = args.getThisHandle(); if (LLVM_UNLIKELY( checkObjectCoercible(runtime, O) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 3. If searchValue is neither undefined nor null, then auto searchValue = args.getArgHandle(0); auto replaceValue = args.getArgHandle(1); if (!searchValue->isUndefined() && !searchValue->isNull()) { // a. Let replacer be GetMethod(searchValue, @@replace). auto methodRes = getMethod( runtime, searchValue, runtime.makeHandle(Predefined::getSymbolID(Predefined::SymbolReplace))); // b. ReturnIfAbrupt(replacer). if (LLVM_UNLIKELY(methodRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // c. If replacer is not undefined, then // i. Return Call(replacer, searchValue, «‍O, replaceValue»). if (!methodRes->getHermesValue().isUndefined()) { // If methodRes is not Callable, step 3a would have thrown a TypeError. Handle<Callable> replacer = Handle<Callable>::vmcast(runtime, methodRes->getHermesValue()); return Callable::executeCall2( replacer, runtime, searchValue, O.getHermesValue(), replaceValue.getHermesValue()) .toCallResultHermesValue(); } } // 4. Let string be ToString(O). // 5. ReturnIfAbrupt(string). auto stringRes = toString_RJS(runtime, O); if (LLVM_UNLIKELY(stringRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto string = runtime.makeHandle(std::move(*stringRes)); // 6. Let searchString be ToString(searchValue). auto searchStringRes = toString_RJS(runtime, searchValue); // 7. ReturnIfAbrupt(searchString). if (LLVM_UNLIKELY(searchStringRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto searchString = runtime.makeHandle(std::move(*searchStringRes)); // 8. Let functionalReplace be IsCallable(replaceValue). auto replaceFn = Handle<Callable>::dyn_vmcast(replaceValue); MutableHandle<StringPrimitive> replaceValueStr{runtime}; bool functionalReplace = !!replaceFn; // 9. If functionalReplace is false, then if (!functionalReplace) { // a. Let replaceValue be ToString(replaceValue). // b. ReturnIfAbrupt(replaceValue). auto replaceValueStrRes = toString_RJS(runtime, replaceValue); if (LLVM_UNLIKELY(replaceValueStrRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } replaceValueStr = std::move(*replaceValueStrRes); } // 10. Search string for the first occurrence of searchString and let pos be // the index within string of the first code unit of the matched substring and // let matched be searchString. If no occurrences of searchString were found, // return string. // Special case: if they're both empty then the match is at position 0. uint32_t pos = 0; auto strView = StringPrimitive::createStringView(runtime, string); if (!strView.empty()) { auto searchView = StringPrimitive::createStringView(runtime, searchString); auto searchResult = std::search( strView.begin(), strView.end(), searchView.begin(), searchView.end()); if (searchResult != strView.end()) { pos = searchResult - strView.begin(); } else { return string.getHermesValue(); } } else if (searchString->getStringLength() != 0) { // If string is empty and search is not empty, there is no match. return string.getHermesValue(); } MutableHandle<StringPrimitive> replStr{runtime}; // 11. If functionalReplace is true, then if (functionalReplace) { // a. Let replValue be Call(replaceValue, undefined, «matched, pos, and // string»). auto callRes = Callable::executeCall3( replaceFn, runtime, Runtime::getUndefinedValue(), searchString.getHermesValue(), HermesValue::encodeNumberValue(pos), string.getHermesValue()); if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // b. Let replStr be ToString(replValue). auto replStrRes = toString_RJS(runtime, runtime.makeHandle(std::move(*callRes))); // c. ReturnIfAbrupt(replStr). if (LLVM_UNLIKELY(replStrRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } replStr = replStrRes->get(); } else { // 12. Else, // a. Let captures be an empty List. auto nullHandle = Runtime::makeNullHandle<ArrayStorageSmall>(); // b. Let replStr be GetSubstitution(matched, string, pos, captures, // replaceValue). auto callRes = getSubstitution( runtime, searchString, string, pos, nullHandle, replaceValueStr); if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } replStr = vmcast<StringPrimitive>(callRes.getValue()); } // 13. Let tailPos be pos + the number of code units in matched. uint32_t tailPos = pos + searchString->getStringLength(); // 14. Let newString be the String formed by concatenating the first pos code // units of string, replStr, and the trailing substring of string starting at // index tailPos. If pos is 0, the first element of the concatenation will be // the empty String. SmallU16String<32> newString{}; strView.slice(0, pos).appendUTF16String(newString); StringPrimitive::createStringView(runtime, replStr) .appendUTF16String(newString); strView.slice(tailPos).appendUTF16String(newString); // 15. Return newString. return StringPrimitive::create(runtime, newString); } CallResult<HermesValue> stringPrototypeSearch(void *, Runtime &runtime, NativeArgs args) { // 1. Let O be RequireObjectCoercible(this value). // 2. ReturnIfAbrupt(O). auto O = args.getThisHandle(); if (LLVM_UNLIKELY( checkObjectCoercible(runtime, O) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 3. If regexp is neither undefined nor null, then auto regexp = args.getArgHandle(0); if (!regexp->isUndefined() && !regexp->isNull()) { // a. Let searcher be GetMethod(regexp, @@search). auto methodRes = getMethod( runtime, regexp, runtime.makeHandle(Predefined::getSymbolID(Predefined::SymbolSearch))); // b. ReturnIfAbrupt(searcher). if (LLVM_UNLIKELY(methodRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // c. If searcher is not undefined, then // i. Return Call(searcher, regexp, «‍O»). if (!methodRes->getHermesValue().isUndefined()) { // If methodRes is not Callable, step 3a would have thrown a TypeError. Handle<Callable> searcher = Handle<Callable>::vmcast(runtime, methodRes->getHermesValue()); return Callable::executeCall1( searcher, runtime, regexp, O.getHermesValue()) .toCallResultHermesValue(); } } // 4. Let string be ToString(O). // 5. ReturnIfAbrupt(string). auto strRes = toString_RJS(runtime, O); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto string = runtime.makeHandle(std::move(*strRes)); // 6. Let rx be RegExpCreate(regexp, undefined) (see 21.2.3.2.3). // 7. ReturnIfAbrupt(rx). auto regRes = regExpCreate(runtime, regexp, Runtime::getUndefinedValue()); if (regRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } Handle<JSRegExp> rx = *regRes; // 8. Return Invoke(rx, @@search, «string»). auto propRes = JSObject::getNamed_RJS( rx, runtime, Predefined::getSymbolID(Predefined::SymbolSearch)); if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } if (LLVM_UNLIKELY(!vmisa<Callable>(propRes->get()))) { return runtime.raiseTypeError( "RegExp.prototype[@@search] must be callable."); } auto func = Handle<Callable>::vmcast(runtime.makeHandle(std::move(*propRes))); return Callable::executeCall1(func, runtime, rx, string.getHermesValue()) .toCallResultHermesValue(); } CallResult<HermesValue> stringPrototypeCharAt(void *, Runtime &runtime, NativeArgs args) { Handle<> thisValue{&args.getThisArg()}; // Call a function that may throw, let the runtime record it. if (LLVM_UNLIKELY( checkObjectCoercible(runtime, thisValue) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto strRes = toString_RJS(runtime, thisValue); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*strRes)); auto intRes = toIntegerOrInfinity(runtime, runtime.makeHandle(args.getArg(0))); if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto position = intRes->getNumber(); auto size = S->getStringLength(); if (position < 0 || position >= size) { return HermesValue::encodeStringValue( runtime.getPredefinedString(Predefined::emptyString)); } auto result = runtime.getCharacterString( StringPrimitive::createStringView(runtime, S)[position]); return HermesValue::encodeStringValue(result.get()); } CallResult<HermesValue> stringPrototypeSlice(void *, Runtime &runtime, NativeArgs args) { if (LLVM_UNLIKELY( checkObjectCoercible(runtime, args.getThisHandle()) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto strRes = toString_RJS(runtime, args.getThisHandle()); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*strRes)); double len = S->getStringLength(); auto intRes = toIntegerOrInfinity(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } double intStart = intRes->getNumber(); double intEnd; if (args.getArg(1).isUndefined()) { intEnd = len; } else { if (LLVM_UNLIKELY( (intRes = toIntegerOrInfinity(runtime, args.getArgHandle(1))) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } intEnd = intRes->getNumber(); } size_t from = intStart < 0 ? std::max(len + intStart, 0.0) : std::min(intStart, len); size_t to = intEnd < 0 ? std::max(len + intEnd, 0.0) : std::min(intEnd, len); size_t span = to > from ? to - from : 0; assert(from + span <= len && "invalid index computed in slice"); return StringPrimitive::slice(runtime, S, from, span); } CallResult<HermesValue> stringPrototypeEndsWith(void *, Runtime &runtime, NativeArgs args) { // 1. Let O be RequireObjectCoercible(this value). if (LLVM_UNLIKELY( checkObjectCoercible(runtime, args.getThisHandle()) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 2. Let S be ToString(O). auto strRes = toString_RJS(runtime, args.getThisHandle()); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*strRes)); // 4. Let isRegExp be IsRegExp(searchString). auto isRegExpRes = isRegExp(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(isRegExpRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 6. If isRegExp is true, throw a TypeError exception. if (LLVM_UNLIKELY(*isRegExpRes)) { return runtime.raiseTypeError( "First argument to endsWith must not be a RegExp"); } // 7. Let searchStr be ToString(searchString). auto searchStrRes = toString_RJS(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(searchStrRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto searchStr = runtime.makeHandle(std::move(*searchStrRes)); // 9. Let len be the number of elements in S. double len = S->getStringLength(); // 10. If endPosition is undefined, let pos be len, else let pos be // ToIntegerOrInfinity(endPosition). double pos; if (args.getArg(1).isUndefined()) { pos = len; } else { auto posRes = toIntegerOrInfinity(runtime, args.getArgHandle(1)); if (LLVM_UNLIKELY(posRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } pos = posRes->getNumberAs<double>(); } // 12. Let end be min(max(pos, 0), len). double end = std::min(std::max(pos, 0.0), len); // 13. Let searchLength be the number of elements in searchStr. double searchLength = searchStr->getStringLength(); // 14. Let start be end - searchLength. double start = end - searchLength; // 15. If start is less than 0, return false. if (start < 0) { return HermesValue::encodeBoolValue(false); } // 16. If the sequence of elements of S starting at start of length // searchLength is the same as the full element sequence of searchStr, return // true. // 17. Otherwise, return false. return HermesValue::encodeBoolValue( S->sliceEquals(start, searchLength, *searchStr)); } /// ES11 21.1.3.20.1 /// Works slightly differently from the given implementation in the spec. /// Given a string \p S and a starting point \p q, finds the first match of /// \p R such that it starts on or after index \p q in \p S. /// \param q starting point in S. Requires: q <= S->getStringLength(). /// \param R a String. /// \return Either None or an integer representing the start of the match. static OptValue<uint32_t> splitMatch( Runtime &runtime, Handle<StringPrimitive> S, uint32_t q, Handle<StringPrimitive> R) { // 2. Let r be the number of code units in R. auto r = R->getStringLength(); // 3. Let s be the number of code units in S. auto s = S->getStringLength(); // 4. If q+r > s, return false. if (q + r > s) { return llvh::None; } // Handle the case where the search starts at the end of the string and R is // the empty string. This path should only be triggered when S is itself the // empty string. if (q == s) { return q; } auto SStr = StringPrimitive::createStringView(runtime, S); auto RStr = StringPrimitive::createStringView(runtime, R); auto sliced = SStr.slice(q); auto searchResult = std::search(sliced.begin(), sliced.end(), RStr.begin(), RStr.end()); if (searchResult != sliced.end()) { return q + (searchResult - sliced.begin()) + r; } return llvh::None; } // ES11 21.1.3.20 String.prototype.split ( separator, limit ) CallResult<HermesValue> stringPrototypeSplit(void *, Runtime &runtime, NativeArgs args) { GCScope gcScope{runtime}; // 1. Let O be ? RequireObjectCoercible(this value). auto O = args.getThisHandle(); if (LLVM_UNLIKELY( checkObjectCoercible(runtime, O) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 2. If separator is neither undefined nor null, then auto separator = args.getArgHandle(0); if (!separator->isUndefined() && !separator->isNull()) { // a. Let splitter be ? GetMethod(separator, @@split). auto methodRes = getMethod( runtime, separator, runtime.makeHandle(Predefined::getSymbolID(Predefined::SymbolSplit))); if (LLVM_UNLIKELY(methodRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // b. If splitter is not undefined, then // i. Return ? Call(splitter, separator, «‍O, limit»). if (!methodRes->getHermesValue().isUndefined()) { Handle<Callable> splitter = Handle<Callable>::vmcast(runtime, methodRes->getHermesValue()); return Callable::executeCall2( splitter, runtime, separator, O.getHermesValue(), args.getArg(1)) .toCallResultHermesValue(); } } // 3. Let S be ? ToString(O). auto strRes = toString_RJS(runtime, args.getThisHandle()); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*strRes)); // 4. Let A be ! ArrayCreate(0). auto arrRes = JSArray::create(runtime, 0, 0); if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto A = *arrRes; // 5. Let lengthA be 0. uint32_t lengthA = 0; // 6. If limit is undefined, let lim be 232 - 1; else let lim be ? // ToUint32(limit). auto limit = args.getArgHandle(1); uint32_t lim; if (limit->isUndefined()) { // No limit supplied, make it max. lim = 0xffffffff; // 2 ^ 32 - 1 } else { auto intRes = toUInt32_RJS(runtime, limit); if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } lim = intRes->getNumber(); } // 7. Let s be the length of S. uint32_t s = S->getStringLength(); // 8. Let p be 0. // End of the last match. uint32_t p = 0; // 9. Let R be ? ToString(separator). // The pattern which we want to separate on. auto sepRes = toString_RJS(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(sepRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } Handle<StringPrimitive> R = runtime.makeHandle(std::move(sepRes.getValue())); // 10. If lim = 0, return A. if (lim == 0) { // Don't want any elements, so we're done. return A.getHermesValue(); } // 11. If separator is undefined, then if (LLVM_UNLIKELY(separator->isUndefined())) { // a. Perform ! CreateDataPropertyOrThrow(A, "0", S). (void)JSArray::setElementAt(A, runtime, 0, S); if (LLVM_UNLIKELY( JSArray::setLengthProperty(A, runtime, 1) == ExecutionStatus::EXCEPTION)) return ExecutionStatus::EXCEPTION; // b. Return A. return A.getHermesValue(); } // 12. If s = 0, then if (s == 0) { // S is the empty string. // a. Let z be SplitMatch(S, 0, R). auto matchResult = splitMatch(runtime, S, 0, R); // b. If z is not false, return A. if (matchResult) { return A.getHermesValue(); } // c. Perform ! CreateDataPropertyOrThrow(A, "0", S). (void)JSArray::setElementAt(A, runtime, 0, S); if (LLVM_UNLIKELY( JSArray::setLengthProperty(A, runtime, 1) == ExecutionStatus::EXCEPTION)) return ExecutionStatus::EXCEPTION; // e. Return A. return A.getHermesValue(); } // 17. Let q be p. // Place to attempt the start of the next match. uint32_t q = p; MutableHandle<> tmpHandle{runtime}; auto marker = gcScope.createMarker(); // 18. Repeat, while q ≠ s // Main loop: continue while we have space to find another match. while (q != s) { gcScope.flushToMarker(marker); // a. Let e be SplitMatch(S, q, R). // b. If e is false, let q = q+1. // Find the next valid match. We know that q < s. // ES6's SplitMatch only finds matches at q, but we find matches at or // after q, so if it fails, we know we're done. In effect, we are performing // steps (a) and (b) over and over again. auto matchResult = splitMatch(runtime, S, q, R); // If we did find a match, fast-forward q to the start of that match. if (matchResult) { q = *matchResult - R->getStringLength(); } if (!matchResult || q >= s) { // There's no matches between index q and the end of the string, so we're // done searching. Note: This behavior differs from the spec // implementation, because we check for matches at or after q. However, in // line with the spec, we only count matches that start before the end of // the string. break; } // c. Else, // Found a match, so go ahead and update e, such that the match is the range // [q,e). uint32_t e = *matchResult; // i. If e = p, set q to q + 1. if (e == p) { // The end of this match is the same as the end of the last match, // so we matched with the empty string. // We don't want to match the empty string at this location again, // so increment q in order to start the next search at the next position. q++; } // ii. Else, else { // Found a non-empty string match. // Add everything from the last match to the current one to A. // This has length q-p because q is the start of the current match, // and p was the end (exclusive) of the last match. // 1. Let T be the String value equal to the substring of S consisting of // the code units at indices p (inclusive) through q (exclusive). auto strRes = StringPrimitive::slice(runtime, S, p, q - p); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } tmpHandle = *strRes; // 2. Perform ! CreateDataPropertyOrThrow(A, ! ToString(lengthA), T) JSArray::setElementAt(A, runtime, lengthA, tmpHandle); // 3. Set lengthA to lengthA + 1. ++lengthA; // 4. If lengthA = lim, return A. if (lengthA == lim) { // Reached the limit, return early. if (LLVM_UNLIKELY( JSArray::setLengthProperty(A, runtime, lengthA) == ExecutionStatus::EXCEPTION)) return ExecutionStatus::EXCEPTION; return A.getHermesValue(); } // 5. Set p to e. // Update p to point to the end of this match, maintaining the // invariant that it points to the end of the last match encountered. p = e; // 6. Set q to p. // Start position of the next search is updated to the end of this match. q = p; } } // 15. Let T be the String value equal to the substring of S consisting of the // code units at indices p (inclusive) through s (exclusive). // Add the rest of the string (after the last match) to A. auto elementStrRes = StringPrimitive::slice(runtime, S, p, s - p); if (LLVM_UNLIKELY(elementStrRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } tmpHandle = *elementStrRes; // 16. Perform ! CreateDataPropertyOrThrow(A, ! ToString(lengthA), T). JSArray::setElementAt(A, runtime, lengthA, tmpHandle); ++lengthA; if (LLVM_UNLIKELY( JSArray::setLengthProperty(A, runtime, lengthA) == ExecutionStatus::EXCEPTION)) return ExecutionStatus::EXCEPTION; // 27. Return A. return A.getHermesValue(); } CallResult<HermesValue> stringPrototypeIncludesOrStartsWith( void *ctx, Runtime &runtime, NativeArgs args) { // If true, perform the startsWith operation. // Else, do the standard "includes" operation. bool startsWith = static_cast<bool>(ctx); // 1. Let O be RequireObjectCoercible(this value). if (LLVM_UNLIKELY( checkObjectCoercible(runtime, args.getThisHandle()) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 2. Let S be ToString(O). auto strRes = toString_RJS(runtime, args.getThisHandle()); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto S = runtime.makeHandle(std::move(*strRes)); // 4. Let isRegExp be IsRegExp(searchString). // 6. If isRegExp is true, throw a TypeError exception. auto isRegExpRes = isRegExp(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(isRegExpRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } if (LLVM_UNLIKELY(*isRegExpRes)) { return runtime.raiseTypeError( "First argument to startsWith and includes must not be a RegExp"); } // 7. Let searchStr be ToString(searchString). auto searchStrRes = toString_RJS(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(searchStrRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto searchStr = runtime.makeHandle(std::move(*searchStrRes)); // 9. Let pos be ToIntegerOrInfinity(position). // (If position is undefined, this step produces the value 0). auto posRes = toIntegerOrInfinity(runtime, args.getArgHandle(1)); if (LLVM_UNLIKELY(posRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } double pos = posRes->getNumber(); // 11. Let len be the number of elements in S. double len = S->getStringLength(); // 12. Let start be min(max(pos, 0), len). double start = std::min(std::max(pos, 0.0), len); // 13. Let searchLength be the number of elements in searchStr. double searchLength = searchStr->getStringLength(); if (startsWith) { // Perform the startsWith operation instead of includes. // 14. If searchLength+start is greater than len, return false. if (searchLength + start > len) { return HermesValue::encodeBoolValue(false); } // 15. If the sequence of elements of S starting at start of length // searchLength is the same as the full element sequence of searchStr, // return true. // 16. Otherwise, return false. return HermesValue::encodeBoolValue( S->sliceEquals(start, searchLength, *searchStr)); } // 14. If there exists any 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 code unit at index k+j of S is the same as the code // unit at index j of searchStr, return true; but if there is no such integer // k, return false. auto SView = StringPrimitive::createStringView(runtime, S); auto searchStrView = StringPrimitive::createStringView(runtime, searchStr); auto foundIter = std::search( SView.begin() + start, SView.end(), searchStrView.begin(), searchStrView.end()); // Note: searchStrView.empty check is needed in the special case that S is // empty, searchStr is empty, and start = 0 return HermesValue::encodeBoolValue( foundIter != SView.end() || searchStrView.empty()); } CallResult<HermesValue> stringPrototypeIndexOf(void *, Runtime &runtime, NativeArgs args) { auto searchString = args.getArgHandle(0); auto position = args.getArgHandle(1); return stringDirectedIndexOf( runtime, args.getThisHandle(), searchString, position, false); } CallResult<HermesValue> stringPrototypeLastIndexOf(void *, Runtime &runtime, NativeArgs args) { auto searchString = args.getArgHandle(0); auto position = args.getArgHandle(1); return stringDirectedIndexOf( runtime, args.getThisHandle(), searchString, position, true); } } // namespace vm } // namespace hermes