/* * Copyright (c) 2021 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ecmascript/builtins/builtins_string.h" #include <algorithm> #include <vector> #include <map> #include "ecmascript/intl/locale_helper.h" #include "ecmascript/base/number_helper.h" #include "ecmascript/base/string_helper.h" #include "ecmascript/builtins/builtins_json.h" #include "ecmascript/builtins/builtins_number.h" #include "ecmascript/builtins/builtins_regexp.h" #include "ecmascript/builtins/builtins_symbol.h" #include "ecmascript/ecma_runtime_call_info.h" #include "ecmascript/ecma_string-inl.h" #include "ecmascript/ecma_vm.h" #include "ecmascript/global_env.h" #include "ecmascript/interpreter/fast_runtime_stub-inl.h" #include "ecmascript/interpreter/interpreter.h" #include "ecmascript/js_array.h" #include "ecmascript/js_hclass.h" #include "ecmascript/js_object-inl.h" #include "ecmascript/js_primitive_ref.h" #include "ecmascript/js_regexp.h" #include "ecmascript/js_string_iterator.h" #include "ecmascript/js_tagged_value-inl.h" #include "ecmascript/mem/c_containers.h" #include "ecmascript/object_factory.h" #include "ecmascript/property_detector-inl.h" #include "ecmascript/tagged_array-inl.h" #include "ecmascript/tagged_array.h" #ifdef ARK_SUPPORT_INTL #include "ecmascript/js_collator.h" #include "ecmascript/js_locale.h" #else #ifndef ARK_NOT_SUPPORT_INTL_GLOBAL #include "ecmascript/intl/global_intl_helper.h" #endif #endif #include "unicode/normalizer2.h" #include "unicode/normlzr.h" #include "unicode/unistr.h" namespace panda::ecmascript::builtins { using ObjectFactory = ecmascript::ObjectFactory; using JSArray = ecmascript::JSArray; // 21.1.1.1 String(value) JSTaggedValue BuiltinsString::StringConstructor(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, Constructor); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle<JSTaggedValue> newTarget = GetNewTarget(argv); if (argv->GetArgsNumber() > 0) { JSHandle<JSTaggedValue> valTagNew = GetCallArg(argv, 0); if (newTarget->IsUndefined() && valTagNew->IsSymbol()) { return BuiltinsSymbol::SymbolDescriptiveString(thread, valTagNew.GetTaggedValue()); } JSHandle<EcmaString> str = JSTaggedValue::ToString(thread, valTagNew); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); if (newTarget->IsUndefined()) { return str.GetTaggedValue(); } JSHandle<JSTaggedValue> strTag(str); return JSPrimitiveRef::StringCreate(thread, strTag, newTarget).GetTaggedValue(); } JSHandle<EcmaString> val = factory->GetEmptyString(); JSHandle<JSTaggedValue> valTag(val); if (newTarget->IsUndefined()) { return factory->GetEmptyString().GetTaggedValue(); } return JSPrimitiveRef::StringCreate(thread, valTag, newTarget).GetTaggedValue(); } // 21.1.2.1 JSTaggedValue BuiltinsString::FromCharCode(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, FromCharCode); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); uint32_t argLength = argv->GetArgsNumber(); if (argLength == 0) { return factory->GetEmptyString().GetTaggedValue(); } if (argLength == 1) { JSHandle<JSTaggedValue> codePointTag = BuiltinsString::GetCallArg(argv, 0); uint16_t codePointValue = JSTaggedValue::ToUint16(thread, codePointTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); if (EcmaStringAccessor::CanBeCompressed(&codePointValue, 1)) { JSHandle<SingleCharTable> singleCharTable(thread, thread->GetSingleCharTable()); return singleCharTable->GetStringFromSingleCharTable(codePointValue); } JSHandle<EcmaString> strHandle = factory->NewFromUtf16Literal(&codePointValue, 1); return strHandle.GetTaggedValue(); } CVector<uint16_t> valueTable; valueTable.reserve(argLength); for (uint32_t i = 0; i < argLength; i++) { JSHandle<JSTaggedValue> nextCp = BuiltinsString::GetCallArg(argv, i); uint16_t nextCv = JSTaggedValue::ToUint16(thread, nextCp); valueTable.emplace_back(nextCv); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } return factory->NewFromUtf16Literal(valueTable.data(), valueTable.size()).GetTaggedValue(); } // 21.1.2.2 JSTaggedValue BuiltinsString::FromCodePoint(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, FromCodePoint); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); uint32_t argLength = argv->GetArgsNumber(); if (argLength == 0) { return factory->GetEmptyString().GetTaggedValue(); } std::u16string u16str; uint32_t u16strSize = argLength; for (uint32_t i = 0; i < argLength; i++) { JSHandle<JSTaggedValue> nextCpTag = BuiltinsString::GetCallArg(argv, i); JSTaggedNumber nextCpVal = JSTaggedValue::ToNumber(thread, nextCpTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); if (!nextCpVal.IsInteger()) { THROW_RANGE_ERROR_AND_RETURN(thread, "is not integer", JSTaggedValue::Exception()); } int32_t cp = nextCpVal.ToInt32(); if (cp < 0 || cp > ENCODE_MAX_UTF16) { THROW_RANGE_ERROR_AND_RETURN(thread, "CodePoint < 0 or CodePoint > 0x10FFFF", JSTaggedValue::Exception()); } if (cp == 0) { CVector<uint16_t> data {0x00}; return factory->NewFromUtf16Literal(data.data(), 1).GetTaggedValue(); } if (cp > UINT16_MAX) { uint16_t cu1 = std::floor((static_cast<uint32_t>(cp) - ENCODE_SECOND_FACTOR) / ENCODE_FIRST_FACTOR) + ENCODE_LEAD_LOW; uint16_t cu2 = ((static_cast<uint32_t>(cp) - ENCODE_SECOND_FACTOR) % ENCODE_FIRST_FACTOR) + ENCODE_TRAIL_LOW; std::u16string nextU16str1 = base::StringHelper::Utf16ToU16String(&cu1, 1); std::u16string nextU16str2 = base::StringHelper::Utf16ToU16String(&cu2, 1); base::StringHelper::InplaceAppend(u16str, nextU16str1); base::StringHelper::InplaceAppend(u16str, nextU16str2); u16strSize++; } else { auto u16tCp = static_cast<uint16_t>(cp); std::u16string nextU16str = base::StringHelper::Utf16ToU16String(&u16tCp, 1); base::StringHelper::InplaceAppend(u16str, nextU16str); } } const char16_t *constChar16tData = u16str.data(); auto *char16tData = const_cast<char16_t *>(constChar16tData); auto *uint16tData = reinterpret_cast<uint16_t *>(char16tData); return factory->NewFromUtf16Literal(uint16tData, u16strSize).GetTaggedValue(); } // 21.1.2.4 JSTaggedValue BuiltinsString::Raw(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, Raw); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); // Let cooked be ToObject(template). JSHandle<JSObject> cooked = JSTaggedValue::ToObject(thread, BuiltinsString::GetCallArg(argv, 0)); // ReturnIfAbrupt(cooked). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // Let raw be ToObject(Get(cooked, "raw")). JSHandle<JSTaggedValue> rawKey(factory->NewFromASCII("raw")); JSHandle<JSTaggedValue> rawTag = JSObject::GetProperty(thread, JSHandle<JSTaggedValue>::Cast(cooked), rawKey).GetValue(); JSHandle<JSObject> rawObj = JSTaggedValue::ToObject(thread, rawTag); // ReturnIfAbrupt(rawObj). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<JSTaggedValue> lengthKey = thread->GlobalConstants()->GetHandledLengthString(); JSHandle<JSTaggedValue> rawLen = JSObject::GetProperty(thread, JSHandle<JSTaggedValue>::Cast(rawObj), lengthKey).GetValue(); // ReturnIfAbrupt(rawLen). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSTaggedNumber lengthNumber = JSTaggedValue::ToLength(thread, rawLen); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); int length = static_cast<int>(lengthNumber.ToUint32()); if (length <= 0) { return factory->GetEmptyString().GetTaggedValue(); } std::u16string u16str; uint32_t argc = argv->GetArgsNumber() - 1; bool canBeCompress = true; for (uint32_t i = 0, argsI = 1; i < static_cast<uint32_t>(length); ++i, ++argsI) { // Let nextSeg be ToString(Get(raw, nextKey)). JSHandle<JSTaggedValue> elementString = JSObject::GetProperty(thread, JSHandle<JSTaggedValue>::Cast(rawObj), i).GetValue(); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); EcmaString *nextSeg = *JSTaggedValue::ToString(thread, elementString); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); u16str += EcmaStringAccessor(nextSeg).ToU16String(); if (EcmaStringAccessor(nextSeg).IsUtf16()) { canBeCompress = false; } if (i + 1 == static_cast<uint32_t>(length)) { break; } if (argsI <= argc) { EcmaString *nextSub = *JSTaggedValue::ToString(thread, GetCallArg(argv, argsI)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); u16str += EcmaStringAccessor(nextSub).ToU16String(); if (EcmaStringAccessor(nextSub).IsUtf16()) { canBeCompress = false; } } } // return the result string auto *uint16tData = reinterpret_cast<uint16_t *>(const_cast<char16_t *>(u16str.data())); return canBeCompress ? factory->NewFromUtf16LiteralCompress(uint16tData, u16str.size()).GetTaggedValue() : factory->NewFromUtf16LiteralNotCompress(uint16tData, u16str.size()).GetTaggedValue(); } // 21.1.3.1 JSTaggedValue BuiltinsString::CharAt(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, CharAt); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisFlat(thread, EcmaStringAccessor::Flatten(thread->GetEcmaVM(), thisHandle)); int32_t thisLen = static_cast<int32_t>(EcmaStringAccessor(thisFlat).GetLength()); JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 0); int32_t pos = 0; if (posTag->IsInt()) { pos = posTag->GetInt(); } else if (posTag->IsUndefined()) { pos = 0; } else { JSTaggedNumber posVal = JSTaggedValue::ToInteger(thread, posTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); double valueNumber = posVal.GetNumber(); if (!std::isfinite(valueNumber)) { return factory->GetEmptyString().GetTaggedValue(); } pos = posVal.ToInt32(); } if (pos < 0 || pos >= thisLen) { return factory->GetEmptyString().GetTaggedValue(); } uint16_t res = EcmaStringAccessor(thisFlat).Get<false>(pos); if (EcmaStringAccessor::CanBeCompressed(&res, 1)) { JSHandle<SingleCharTable> singleCharTable(thread, thread->GetSingleCharTable()); return singleCharTable->GetStringFromSingleCharTable(res); } return factory->NewFromUtf16Literal(&res, 1).GetTaggedValue(); } // 21.1.3.2 JSTaggedValue BuiltinsString::CharCodeAt(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, CharCodeAt); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisFlat(thread, EcmaStringAccessor::Flatten(thread->GetEcmaVM(), thisHandle)); int32_t thisLen = static_cast<int32_t>(EcmaStringAccessor(thisFlat).GetLength()); JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 0); int32_t pos = 0; if (posTag->IsInt()) { pos = posTag->GetInt(); } else if (posTag->IsUndefined()) { pos = 0; } else { JSTaggedNumber posVal = JSTaggedValue::ToInteger(thread, posTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); double valueNumber = posVal.GetNumber(); if (!std::isfinite(valueNumber)) { return GetTaggedDouble(base::NAN_VALUE); } pos = posVal.ToInt32(); } if (pos < 0 || pos >= thisLen) { return GetTaggedDouble(base::NAN_VALUE); } uint16_t ret = EcmaStringAccessor(thisFlat).Get<false>(pos); return GetTaggedInt(ret); } // 21.1.3.3 JSTaggedValue BuiltinsString::CodePointAt(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, CodePointAt); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisFlat(thread, EcmaStringAccessor::Flatten(thread->GetEcmaVM(), thisHandle)); JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 0); JSTaggedNumber posVal = JSTaggedValue::ToNumber(thread, posTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); int32_t pos = base::NumberHelper::DoubleInRangeInt32(posVal.GetNumber()); int32_t thisLen = static_cast<int32_t>(EcmaStringAccessor(thisFlat).GetLength()); if (pos < 0 || pos >= thisLen) { return JSTaggedValue::Undefined(); } uint16_t first = EcmaStringAccessor(thisFlat).Get<false>(pos); if (first < base::utf_helper::DECODE_LEAD_LOW || first > base::utf_helper::DECODE_LEAD_HIGH || pos + 1 == thisLen) { return GetTaggedInt(first); } uint16_t second = EcmaStringAccessor(thisFlat).Get<false>(pos + 1); if (second < base::utf_helper::DECODE_TRAIL_LOW || second > base::utf_helper::DECODE_TRAIL_HIGH) { return GetTaggedInt(first); } uint32_t res = base::utf_helper::UTF16Decode(first, second); return GetTaggedInt(res); } // 21.1.3.4 JSTaggedValue BuiltinsString::Concat(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, Concat); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); auto ecmaVm = thread->GetEcmaVM(); JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); uint32_t argLength = argv->GetArgsNumber(); if (argLength == 0) { return thisHandle.GetTaggedValue(); } for (uint32_t i = 0; i < argLength; i++) { JSHandle<JSTaggedValue> nextTag = BuiltinsString::GetCallArg(argv, i); JSHandle<EcmaString> nextHandle = JSTaggedValue::ToString(thread, nextTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); EcmaString *tempStr = EcmaStringAccessor::Concat(ecmaVm, thisHandle, nextHandle); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); thisHandle = JSHandle<EcmaString>(thread, tempStr); } return thisHandle.GetTaggedValue(); } // 21.1.3.5 String.prototype.constructor // 21.1.3.6 JSTaggedValue BuiltinsString::EndsWith(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, EndsWith); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); bool isRegexp = JSObject::IsRegExp(thread, searchTag); if (isRegexp) { THROW_TYPE_ERROR_AND_RETURN(thread, "is regexp", JSTaggedValue::Exception()); } JSHandle<EcmaString> searchHandle = JSTaggedValue::ToString(thread, searchTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); uint32_t thisLen = EcmaStringAccessor(thisHandle).GetLength(); uint32_t searchLen = EcmaStringAccessor(searchHandle).GetLength(); uint32_t pos = 0; JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 1); if (posTag->IsUndefined()) { pos = thisLen; } else { JSTaggedNumber posVal = JSTaggedValue::ToInteger(thread, posTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); if (posVal.GetNumber() == BuiltinsNumber::POSITIVE_INFINITY) { pos = thisLen; } else { pos = static_cast<uint32_t>(posVal.ToInt32()); } } uint32_t end = std::min(std::max(pos, 0U), thisLen); int32_t start = static_cast<int32_t>(end - searchLen); if (start < 0) { return BuiltinsString::GetTaggedBoolean(false); } int32_t idx = EcmaStringAccessor::IndexOf(thread->GetEcmaVM(), thisHandle, searchHandle, start); if (idx == start) { return BuiltinsString::GetTaggedBoolean(true); } return BuiltinsString::GetTaggedBoolean(false); } // 21.1.3.7 JSTaggedValue BuiltinsString::Includes(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, Includes); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0); JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); bool isRegexp = JSObject::IsRegExp(thread, searchTag); if (isRegexp) { THROW_TYPE_ERROR_AND_RETURN(thread, "is regexp", JSTaggedValue::Exception()); } JSHandle<EcmaString> searchHandle = JSTaggedValue::ToString(thread, searchTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); uint32_t thisLen = EcmaStringAccessor(thisHandle).GetLength(); int32_t pos = 0; JSHandle<JSTaggedValue> posTag = BuiltinsBase::GetCallArg(argv, 1); if (argv->GetArgsNumber() == 1) { pos = 0; } else { JSTaggedNumber posVal = JSTaggedValue::ToNumber(thread, posTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); pos = base::NumberHelper::DoubleInRangeInt32(posVal.GetNumber()); } int32_t start = std::min(std::max(pos, 0), static_cast<int32_t>(thisLen)); int32_t idx = EcmaStringAccessor::IndexOf(thread->GetEcmaVM(), thisHandle, searchHandle, start); if (idx < 0 || idx > static_cast<int32_t>(thisLen)) { return BuiltinsString::GetTaggedBoolean(false); } return BuiltinsString::GetTaggedBoolean(true); } // 21.1.3.8 JSTaggedValue BuiltinsString::IndexOf(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, IndexOf); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0); JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); uint32_t thisLen = EcmaStringAccessor(thisHandle).GetLength(); JSHandle<EcmaString> searchHandle = JSTaggedValue::ToString(thread, searchTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 1); int32_t pos = 0; if (posTag->IsInt()) { pos = posTag->GetInt(); } else if (posTag->IsUndefined()) { pos = 0; } else { JSTaggedNumber posVal = JSTaggedValue::ToInteger(thread, posTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); pos = posVal.ToInt32(); } pos = std::min(std::max(pos, 0), static_cast<int32_t>(thisLen)); int32_t res = EcmaStringAccessor::IndexOf(thread->GetEcmaVM(), thisHandle, searchHandle, pos); if (res >= 0 && res < static_cast<int32_t>(thisLen)) { return GetTaggedInt(res); } return GetTaggedInt(-1); } // 21.1.3.9 JSTaggedValue BuiltinsString::LastIndexOf(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, LastIndexOf); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0); JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); int32_t thisLen = static_cast<int32_t>(EcmaStringAccessor(thisHandle).GetLength()); JSHandle<EcmaString> searchHandle = JSTaggedValue::ToString(thread, searchTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); int32_t pos = 0; if (argv->GetArgsNumber() == 1) { pos = thisLen; } else { JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 1); JSTaggedNumber posVal = JSTaggedValue::ToInteger(thread, posTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); if (std::isnan(JSTaggedValue::ToNumber(thread, posTag).GetNumber())) { pos = thisLen; } else { pos = posVal.ToInt32(); } RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } pos = std::min(std::max(pos, 0), thisLen); int32_t res = EcmaStringAccessor::LastIndexOf(thread->GetEcmaVM(), thisHandle, searchHandle, pos); if (res >= 0 && res < thisLen) { return GetTaggedInt(res); } res = -1; return GetTaggedInt(static_cast<int32_t>(res)); } // 21.1.3.10 JSTaggedValue BuiltinsString::LocaleCompare(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, LocaleCompare); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> thatTag = BuiltinsString::GetCallArg(argv, 0); JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); [[maybe_unused]] JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); [[maybe_unused]] JSHandle<EcmaString> thatHandle = JSTaggedValue::ToString(thread, thatTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<JSTaggedValue> locales = GetCallArg(argv, 1); JSHandle<JSTaggedValue> options = GetCallArg(argv, 2); // 2: the second argument [[maybe_unused]] bool cacheable = (locales->IsUndefined() || locales->IsString()) && options->IsUndefined(); #ifdef ARK_SUPPORT_INTL if (cacheable) { auto collator = JSCollator::GetCachedIcuCollator(thread, locales); if (collator != nullptr) { JSTaggedValue result = JSCollator::CompareStrings(collator, thisHandle, thatHandle); return result; } } EcmaVM *ecmaVm = thread->GetEcmaVM(); ObjectFactory *factory = ecmaVm->GetFactory(); JSHandle<JSTaggedValue> ctor = ecmaVm->GetGlobalEnv()->GetCollatorFunction(); JSHandle<JSCollator> collator = JSHandle<JSCollator>::Cast(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(ctor))); JSHandle<JSCollator> initCollator = JSCollator::InitializeCollator(thread, collator, locales, options, cacheable); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); icu::Collator *icuCollator = nullptr; if (cacheable) { icuCollator = JSCollator::GetCachedIcuCollator(thread, locales); ASSERT(icuCollator != nullptr); } else { icuCollator = initCollator->GetIcuCollator(); } JSTaggedValue result = JSCollator::CompareStrings(icuCollator, thisHandle, thatHandle); return result; #else #ifdef ARK_NOT_SUPPORT_INTL_GLOBAL ARK_SUPPORT_INTL_RETURN_JSVALUE(thread, "LocaleCompare"); #else intl::GlobalIntlHelper gh(thread, intl::GlobalFormatterType::Collator); auto collator = gh.GetGlobalObject<intl::GlobalCollator>(thread, locales, options, intl::GlobalFormatterType::Collator, cacheable); if (collator == nullptr) { LOG_ECMA(ERROR) << "BuiltinsString::LocaleCompare:collator is nullptr"; } ASSERT(collator != nullptr); auto result = collator->Compare(EcmaStringAccessor(thisHandle).ToStdString(), EcmaStringAccessor(thatHandle).ToStdString()); return JSTaggedValue(result); #endif #endif } JSTaggedValue BuiltinsString::LocaleCompareGC(JSThread *thread, JSHandle<JSTaggedValue> locales, JSHandle<EcmaString> thisHandle, JSHandle<EcmaString> thatHandle, JSHandle<JSTaggedValue> options, bool cacheable) { EcmaVM *ecmaVm = thread->GetEcmaVM(); ObjectFactory *factory = ecmaVm->GetFactory(); JSHandle<JSTaggedValue> ctor = ecmaVm->GetGlobalEnv()->GetCollatorFunction(); JSHandle<JSCollator> collator = JSHandle<JSCollator>::Cast(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(ctor))); JSHandle<JSCollator> initCollator = JSCollator::InitializeCollator(thread, collator, locales, options, cacheable); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); icu::Collator *icuCollator = nullptr; if (cacheable) { icuCollator = JSCollator::GetCachedIcuCollator(thread, locales); ASSERT(icuCollator != nullptr); } else { icuCollator = initCollator->GetIcuCollator(); } JSTaggedValue result = JSCollator::CompareStrings(icuCollator, thisHandle, thatHandle); return result; } // 21.1.3.11 JSTaggedValue BuiltinsString::Match(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, Match); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); const GlobalEnvConstants *globalConst = thread->GlobalConstants(); JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<JSTaggedValue> regexp = BuiltinsString::GetCallArg(argv, 0); JSHandle<JSTaggedValue> matchTag = thread->GetEcmaVM()->GetGlobalEnv()->GetMatchSymbol(); JSHandle<JSTaggedValue> undefined = globalConst->GetHandledUndefined(); if (regexp->IsJSRegExp()) { JSHandle<RegExpExecResultCache> cacheTable(thread->GetCurrentEcmaContext()->GetRegExpCache()); JSHandle<JSRegExp> re(regexp); JSHandle<JSTaggedValue> pattern(thread, re->GetOriginalSource()); JSHandle<JSTaggedValue> flags(thread, re->GetOriginalFlags()); JSTaggedValue cacheResult = cacheTable->FindCachedResult(thread, pattern, flags, thisTag, RegExpExecResultCache::MATCH_TYPE, regexp, JSTaggedValue(0)); if (!cacheResult.IsUndefined()) { return cacheResult; } } if (!regexp->IsUndefined() && !regexp->IsNull()) { JSHandle<JSTaggedValue> matcher = JSObject::GetMethod(thread, regexp, matchTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); if (!matcher->IsUndefined()) { ASSERT(matcher->IsJSFunction()); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, matcher, regexp, undefined, 1); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); info->SetCallArg(thisTag.GetTaggedValue()); return JSFunction::Call(info); } } JSHandle<EcmaString> thisVal = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<JSTaggedValue> undifinedHandle = globalConst->GetHandledUndefined(); JSHandle<JSTaggedValue> rx(thread, BuiltinsRegExp::RegExpCreate(thread, regexp, undifinedHandle)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, undefined, rx, undefined, 1); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); info->SetCallArg(thisVal.GetTaggedValue()); return JSFunction::Invoke(info, matchTag); } JSTaggedValue BuiltinsString::MatchAll(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, MatchAll); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); const GlobalEnvConstants *globalConst = thread->GlobalConstants(); // 1. Let O be ? RequireObjectCoercible(this value). JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<JSTaggedValue> regexp = BuiltinsString::GetCallArg(argv, 0); JSHandle<JSTaggedValue> matchAllTag = thread->GetEcmaVM()->GetGlobalEnv()->GetMatchAllSymbol(); JSHandle<JSTaggedValue> undefined = globalConst->GetHandledUndefined(); auto ecmaVm = thread->GetEcmaVM(); ObjectFactory *factory = ecmaVm->GetFactory(); JSHandle<JSTaggedValue> gvalue(factory->NewFromASCII("g")); // 2. If regexp is neither undefined nor null, then if (!regexp->IsUndefined() && !regexp->IsNull()) { // a. Let isRegExp be ? IsRegExp(searchValue). bool isJSRegExp = JSObject::IsRegExp(thread, regexp); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // b. If isRegExp is true, then if (isJSRegExp) { // i. Let flags be ? Get(searchValue, "flags"). JSHandle<JSTaggedValue> flagsString(globalConst->GetHandledFlagsString()); JSHandle<JSTaggedValue> flags = JSObject::GetProperty(thread, regexp, flagsString).GetValue(); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // ii. Perform ? RequireObjectCoercible(flags). JSTaggedValue::RequireObjectCoercible(thread, flags); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // iii. If ? ToString(flags) does not contain "g", throw a TypeError exception. JSHandle<EcmaString> flagString = JSTaggedValue::ToString(thread, flags); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); int32_t pos = EcmaStringAccessor::IndexOf(ecmaVm, flagString, JSHandle<EcmaString>(gvalue)); if (pos == -1) { THROW_TYPE_ERROR_AND_RETURN(thread, "matchAll called with a non-global RegExp argument", JSTaggedValue::Exception()); } } // c. Let matcher be ? GetMethod(regexp, @@matchAll). // d. If matcher is not undefined, then JSHandle<JSTaggedValue> matcher = JSObject::GetMethod(thread, regexp, matchAllTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); if (!matcher->IsUndefined()) { // i. Return ? Call(matcher, regexp, « O »). EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, matcher, regexp, undefined, 1); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); info->SetCallArg(thisTag.GetTaggedValue()); return JSFunction::Call(info); } } // 3. Let S be ? ToString(O). JSHandle<EcmaString> thisVal = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 4. Let rx be ? RegExpCreate(regexp, "g"). JSHandle<JSTaggedValue> rx(thread, BuiltinsRegExp::RegExpCreate(thread, regexp, gvalue)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, undefined, rx, undefined, 1); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); info->SetCallArg(thisVal.GetTaggedValue()); return JSFunction::Invoke(info, matchAllTag); } // 21.1.3.12 JSTaggedValue BuiltinsString::Normalize(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, Normalize); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); auto vm = thread->GetEcmaVM(); ObjectFactory *factory = vm->GetFactory(); JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception()); JSHandle<EcmaString> formValue; if (argv->GetArgsNumber() == 0) { formValue = JSHandle<EcmaString>::Cast(thread->GlobalConstants()->GetHandledNfcString()); } else { JSHandle<JSTaggedValue> formTag = BuiltinsString::GetCallArg(argv, 0); if (formTag->IsUndefined()) { formValue = JSHandle<EcmaString>::Cast(thread->GlobalConstants()->GetHandledNfcString()); } else { formValue = JSTaggedValue::ToString(thread, formTag); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception()); } } JSHandle<EcmaString> nfc = JSHandle<EcmaString>::Cast(thread->GlobalConstants()->GetHandledNfcString()); JSHandle<EcmaString> nfd = factory->NewFromASCII("NFD"); JSHandle<EcmaString> nfkc = factory->NewFromASCII("NFKC"); JSHandle<EcmaString> nfkd = factory->NewFromASCII("NFKD"); UNormalizationMode uForm; if (EcmaStringAccessor::StringsAreEqual(vm, formValue, nfc)) { uForm = UNORM_NFC; } else if (EcmaStringAccessor::StringsAreEqual(vm, formValue, nfd)) { uForm = UNORM_NFD; } else if (EcmaStringAccessor::StringsAreEqual(vm, formValue, nfkc)) { uForm = UNORM_NFKC; } else if (EcmaStringAccessor::StringsAreEqual(vm, formValue, nfkd)) { uForm = UNORM_NFKD; } else { THROW_RANGE_ERROR_AND_RETURN(thread, "compare not equal", JSTaggedValue::Exception()); } std::u16string u16strThis = EcmaStringAccessor(thisHandle).ToU16String(); const char16_t *constChar16tData = u16strThis.data(); icu::UnicodeString src(constChar16tData, u16strThis.size()); icu::UnicodeString res; UErrorCode errorCode = U_ZERO_ERROR; int32_t option = 0; icu::Normalizer::normalize(src, uForm, option, res, errorCode); JSHandle<EcmaString> str = intl::LocaleHelper::UStringToString(thread, res); return JSTaggedValue(*str); } JSTaggedValue BuiltinsString::PadStart(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, PadStart); return BuiltinsString::Pad(argv, true); } JSTaggedValue BuiltinsString::PadEnd(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, PadEnd); return BuiltinsString::Pad(argv, false); } // 21.1.3.13 JSTaggedValue BuiltinsString::Repeat(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, Repeat); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); uint32_t thisLen = EcmaStringAccessor(thisHandle).GetLength(); JSHandle<JSTaggedValue> countTag = BuiltinsString::GetCallArg(argv, 0); int32_t count = 0; if (countTag->IsInt()) { count = countTag->GetInt(); } else { JSTaggedNumber num = JSTaggedValue::ToInteger(thread, countTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); double d = num.GetNumber(); if (d == base::POSITIVE_INFINITY) { THROW_RANGE_ERROR_AND_RETURN(thread, "is infinity", JSTaggedValue::Exception()); } count = base::NumberHelper::DoubleInRangeInt32(d); } if (count < 0) { THROW_RANGE_ERROR_AND_RETURN(thread, "less than 0", JSTaggedValue::Exception()); } if (count == 0) { auto emptyStr = thread->GetEcmaVM()->GetFactory()->GetEmptyString(); return emptyStr.GetTaggedValue(); } if (thisLen == 0) { return thisHandle.GetTaggedValue(); } if (static_cast<uint32_t>(count) >= static_cast<uint32_t>(EcmaString::MAX_STRING_LENGTH) / thisLen) { THROW_RANGE_ERROR_AND_RETURN(thread, "Invalid string length", JSTaggedValue::Exception()); } bool isUtf8 = EcmaStringAccessor(thisHandle).IsUtf8(); EcmaString *result = EcmaStringAccessor::CreateLineString(thread->GetEcmaVM(), thisLen * count, isUtf8); for (uint32_t index = 0; index < static_cast<uint32_t>(count); ++index) { EcmaStringAccessor::ReadData(result, *thisHandle, index * thisLen, (count - index) * thisLen, thisLen); } return JSTaggedValue(result); } // 21.1.3.14 JSTaggedValue BuiltinsString::Replace(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, Replace); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> thisTag = JSTaggedValue::RequireObjectCoercible(thread, BuiltinsString::GetThis(argv)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); auto ecmaVm = thread->GetEcmaVM(); JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv(); const GlobalEnvConstants *globalConst = thread->GlobalConstants(); JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0); JSHandle<JSTaggedValue> replaceTag = BuiltinsString::GetCallArg(argv, 1); ObjectFactory *factory = ecmaVm->GetFactory(); if (searchTag->IsJSRegExp() && replaceTag->IsString()) { if (BuiltinsRegExp::IsValidRegularExpression(thread, searchTag) == JSTaggedValue::False()) { THROW_SYNTAX_ERROR_AND_RETURN(thread, "Regular expression too large", JSTaggedValue::Exception()); } JSHandle<RegExpExecResultCache> cacheTable(thread->GetCurrentEcmaContext()->GetRegExpCache()); JSHandle<JSRegExp> re(searchTag); JSHandle<JSTaggedValue> pattern(thread, re->GetOriginalSource()); JSHandle<JSTaggedValue> flags(thread, re->GetOriginalFlags()); JSTaggedValue cacheResult = cacheTable->FindCachedResult(thread, pattern, flags, thisTag, RegExpExecResultCache::REPLACE_TYPE, searchTag, replaceTag.GetTaggedValue()); if (!cacheResult.IsUndefined()) { return cacheResult; } } if (searchTag->IsJSRegExp() && PropertyDetector::IsRegExpReplaceDetectorValid(env)) { JSTaggedValue proto = JSObject::GetPrototype(JSHandle<JSObject>(searchTag)); if (proto == env->GetTaggedRegExpPrototype()) { return BuiltinsRegExp::ReplaceInternal(thread, searchTag, thisTag, replaceTag); } } // If searchValue is neither undefined nor null, then if (!searchTag->IsUndefined() && !searchTag->IsNull()) { JSHandle<JSTaggedValue> replaceKey = env->GetReplaceSymbol(); // Let replacer be GetMethod(searchValue, @@replace). JSHandle<JSTaggedValue> replaceMethod = JSObject::GetMethod(thread, searchTag, replaceKey); // ReturnIfAbrupt(replacer). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // If replacer is not undefined, then if (!replaceMethod->IsUndefined()) { // Return Call(replacer, searchValue, «O, replaceValue»). const uint32_t argsLength = 2; JSHandle<JSTaggedValue> undefined = globalConst->GetHandledUndefined(); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, replaceMethod, searchTag, undefined, argsLength); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); info->SetCallArg(thisTag.GetTaggedValue(), replaceTag.GetTaggedValue()); return JSFunction::Call(info); } } // Let string be ToString(O). JSHandle<EcmaString> thisString = JSTaggedValue::ToString(thread, thisTag); // ReturnIfAbrupt(string). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // Let searchString be ToString(searchValue). JSHandle<EcmaString> searchString = JSTaggedValue::ToString(thread, searchTag); // ReturnIfAbrupt(searchString). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // Let functionalReplace be IsCallable(replaceValue). if (!replaceTag->IsCallable()) { // If functionalReplace is false, then // Let replaceValue be ToString(replaceValue). // ReturnIfAbrupt(replaceValue) replaceTag = JSHandle<JSTaggedValue>(JSTaggedValue::ToString(thread, replaceTag)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } // 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. int32_t pos = EcmaStringAccessor::IndexOf(ecmaVm, thisString, searchString); if (pos == -1) { return thisString.GetTaggedValue(); } JSHandle<JSTaggedValue> undefined = globalConst->GetHandledUndefined(); JSMutableHandle<JSTaggedValue> replHandle(thread, factory->GetEmptyString().GetTaggedValue()); // If functionalReplace is true, then if (replaceTag->IsCallable()) { // Let replValue be Call(replaceValue, undefined,«matched, pos, and string»). const uint32_t argsLength = 3; // 3: «matched, pos, and string» EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, replaceTag, undefined, undefined, argsLength); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); info->SetCallArg(searchString.GetTaggedValue(), JSTaggedValue(pos), thisString.GetTaggedValue()); JSTaggedValue replStrDeocodeValue = JSFunction::Call(info); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); replHandle.Update(replStrDeocodeValue); } else { // Let captures be an empty List. JSHandle<TaggedArray> capturesList = factory->EmptyArray(); ASSERT_PRINT(replaceTag->IsString(), "replace must be string"); JSHandle<EcmaString> replacement(thread, replaceTag->GetTaggedObject()); // Let replStr be GetSubstitution(matched, string, pos, captures, replaceValue) replHandle.Update(GetSubstitution(thread, searchString, thisString, pos, capturesList, undefined, replacement)); } JSHandle<EcmaString> realReplaceStr = JSTaggedValue::ToString(thread, replHandle); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // Let tailPos be pos + the number of code units in matched. int32_t tailPos = pos + static_cast<int32_t>(EcmaStringAccessor(searchString).GetLength()); // 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. // Return newString. JSHandle<EcmaString> prefixString(thread, EcmaStringAccessor::FastSubString(ecmaVm, thisString, 0, pos)); auto thisLen = EcmaStringAccessor(thisString).GetLength(); JSHandle<EcmaString> suffixString(thread, EcmaStringAccessor::FastSubString(ecmaVm, thisString, tailPos, thisLen - tailPos)); EcmaString *tempStr = EcmaStringAccessor::Concat(ecmaVm, prefixString, realReplaceStr); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> tempString(thread, tempStr); EcmaString *resultStr = EcmaStringAccessor::Concat(ecmaVm, tempString, suffixString); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); return JSTaggedValue(resultStr); } JSTaggedValue BuiltinsString::ReplaceAll(EcmaRuntimeCallInfo *argv) { ASSERT(argv); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, String, ReplaceAll); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> thisTag = JSTaggedValue::RequireObjectCoercible(thread, BuiltinsString::GetThis(argv)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); auto ecmaVm = thread->GetEcmaVM(); JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv(); const GlobalEnvConstants *globalConst = thread->GlobalConstants(); JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0); JSHandle<JSTaggedValue> replaceTag = BuiltinsString::GetCallArg(argv, 1); ObjectFactory *factory = ecmaVm->GetFactory(); if (!searchTag->IsUndefined() && !searchTag->IsNull()) { // a. Let isRegExp be ? IsRegExp(searchValue). bool isJSRegExp = JSObject::IsRegExp(thread, searchTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // b. If isRegExp is true, then if (isJSRegExp) { // i. Let flags be ? Get(searchValue, "flags"). JSHandle<JSTaggedValue> flagsString(globalConst->GetHandledFlagsString()); JSHandle<JSTaggedValue> flags = JSObject::GetProperty(thread, searchTag, flagsString).GetValue(); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // ii. Perform ? RequireObjectCoercible(flags). JSTaggedValue::RequireObjectCoercible(thread, flags); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // iii. If ? ToString(flags) does not contain "g", throw a TypeError exception. JSHandle<EcmaString> flagString = JSTaggedValue::ToString(thread, flags); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> gString(globalConst->GetHandledGString()); int32_t pos = EcmaStringAccessor::IndexOf(ecmaVm, flagString, gString); if (pos == -1) { THROW_TYPE_ERROR_AND_RETURN(thread, "string.prototype.replaceAll called with a non-global RegExp argument", JSTaggedValue::Exception()); } } // c. Let replacer be ? GetMethod(searchValue, @@replace). JSHandle<JSTaggedValue> replaceKey = env->GetReplaceSymbol(); JSHandle<JSTaggedValue> replaceMethod = JSObject::GetMethod(thread, searchTag, replaceKey); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // d. If replacer is not undefined, then if (!replaceMethod->IsUndefined()) { // i. Return ? Call(replacer, searchValue, «O, replaceValue»). const size_t argsLength = 2; JSHandle<JSTaggedValue> undefined = globalConst->GetHandledUndefined(); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, replaceMethod, searchTag, undefined, argsLength); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); info->SetCallArg(thisTag.GetTaggedValue(), replaceTag.GetTaggedValue()); return JSFunction::Call(info); } } // 3. Let string be ? ToString(O). JSHandle<EcmaString> thisString = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 4. Let searchString be ? ToString(searchValue). JSHandle<EcmaString> searchString = JSTaggedValue::ToString(thread, searchTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 5. Let functionalReplace be IsCallable(replaceValue). // 6. If functionalReplace is false, then if (!replaceTag->IsCallable()) { // a. Set replaceValue to ? ToString(replaceValue). replaceTag = JSHandle<JSTaggedValue>(JSTaggedValue::ToString(thread, replaceTag)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } // 7. Let searchLength be the length of searchString. // 8. Let advanceBy be max(1, searchLength). int32_t searchLength = static_cast<int32_t>(EcmaStringAccessor(searchString).GetLength()); int32_t advanceBy = std::max(1, searchLength); // 9. Let matchPositions be a new empty List. std::u16string stringBuilder; std::u16string stringPrefixString; std::u16string stringRealReplaceStr; std::u16string stringSuffixString; // 10. Let position be ! StringIndexOf(string, searchString, 0). int32_t pos = EcmaStringAccessor::IndexOf(ecmaVm, thisString, searchString); int32_t endOfLastMatch = 0; bool canBeCompress = true; JSHandle<JSTaggedValue> undefined = globalConst->GetHandledUndefined(); JSMutableHandle<JSTaggedValue> replHandle(thread, factory->GetEmptyString().GetTaggedValue()); while (pos != -1) { // If functionalReplace is true, then if (replaceTag->IsCallable()) { // Let replValue be Call(replaceValue, undefined,«matched, pos, and string»). const uint32_t argsLength = 3; // 3: «matched, pos, and string» EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, replaceTag, undefined, undefined, argsLength); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); info->SetCallArg(searchString.GetTaggedValue(), JSTaggedValue(pos), thisString.GetTaggedValue()); JSTaggedValue replStrDeocodeValue = JSFunction::Call(info); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); replHandle.Update(replStrDeocodeValue); } else { // Let captures be an empty List. JSHandle<TaggedArray> capturesList = factory->NewTaggedArray(0); ASSERT_PRINT(replaceTag->IsString(), "replace must be string"); JSHandle<EcmaString> replacement(thread, replaceTag->GetTaggedObject()); // Let replStr be GetSubstitution(matched, string, pos, captures, replaceValue) replHandle.Update(GetSubstitution(thread, searchString, thisString, pos, capturesList, undefined, replacement)); } JSHandle<EcmaString> realReplaceStr = JSTaggedValue::ToString(thread, replHandle); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // Let tailPos be pos + the number of code units in matched. // 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. // Return newString. JSHandle<EcmaString> prefixString(thread, EcmaStringAccessor::FastSubString(ecmaVm, thisString, endOfLastMatch, pos - endOfLastMatch)); stringPrefixString = EcmaStringAccessor(prefixString).ToU16String(); if (EcmaStringAccessor(prefixString).IsUtf16()) { canBeCompress = false; } stringRealReplaceStr = EcmaStringAccessor(realReplaceStr).ToU16String(); if (EcmaStringAccessor(realReplaceStr).IsUtf16()) { canBeCompress = false; } stringBuilder = stringBuilder + stringPrefixString + stringRealReplaceStr; endOfLastMatch = pos + searchLength; pos = EcmaStringAccessor::IndexOf(ecmaVm, thisString, searchString, pos + advanceBy); } if (endOfLastMatch < static_cast<int32_t>(EcmaStringAccessor(thisString).GetLength())) { auto thisLen = EcmaStringAccessor(thisString).GetLength(); JSHandle<EcmaString> suffixString(thread, EcmaStringAccessor::FastSubString(ecmaVm, thisString, endOfLastMatch, thisLen - endOfLastMatch)); stringSuffixString = EcmaStringAccessor(suffixString).ToU16String(); if (EcmaStringAccessor(suffixString).IsUtf16()) { canBeCompress = false; } stringBuilder = stringBuilder + stringSuffixString; } auto *char16tData = const_cast<char16_t *>(stringBuilder.c_str()); auto *uint16tData = reinterpret_cast<uint16_t *>(char16tData); return canBeCompress ? factory->NewFromUtf16LiteralCompress(uint16tData, stringBuilder.length()).GetTaggedValue() : factory->NewFromUtf16LiteralNotCompress(uint16tData, stringBuilder.length()).GetTaggedValue(); } JSTaggedValue BuiltinsString::GetSubstitution(JSThread *thread, const JSHandle<EcmaString> &matched, const JSHandle<EcmaString> &srcString, int position, const JSHandle<TaggedArray> &captureList, const JSHandle<JSTaggedValue> &namedCaptures, const JSHandle<EcmaString> &replacement) { BUILTINS_API_TRACE(thread, String, GetSubstitution); auto ecmaVm = thread->GetEcmaVM(); ObjectFactory *factory = ecmaVm->GetFactory(); JSHandle<EcmaString> dollarString = JSHandle<EcmaString>::Cast(thread->GlobalConstants()->GetHandledDollarString()); JSHandle<EcmaString> replacementFlat(thread, EcmaStringAccessor::Flatten(ecmaVm, replacement)); int32_t replaceLength = static_cast<int32_t>(EcmaStringAccessor(replacementFlat).GetLength()); int32_t tailPos = position + static_cast<int32_t>(EcmaStringAccessor(matched).GetLength()); int32_t nextDollarIndex = EcmaStringAccessor::IndexOf(ecmaVm, replacementFlat, dollarString); if (nextDollarIndex < 0) { return replacementFlat.GetTaggedValue(); } std::u16string stringBuilder; bool canBeCompress = true; if (nextDollarIndex > 0) { stringBuilder = EcmaStringAccessor(replacementFlat).ToU16String(nextDollarIndex); if (EcmaStringAccessor(replacementFlat).IsUtf16()) { canBeCompress = false; } } while (true) { int peekIndex = nextDollarIndex + 1; if (peekIndex >= replaceLength) { stringBuilder += '$'; auto *char16tData = const_cast<char16_t *>(stringBuilder.c_str()); auto *uint16tData = reinterpret_cast<uint16_t *>(char16tData); return canBeCompress ? factory->NewFromUtf16LiteralCompress(uint16tData, stringBuilder.length()).GetTaggedValue() : factory->NewFromUtf16LiteralNotCompress(uint16tData, stringBuilder.length()).GetTaggedValue(); } int continueFromIndex = -1; uint16_t peek = EcmaStringAccessor(replacementFlat).Get(peekIndex); switch (peek) { case '$': // $$ stringBuilder += '$'; continueFromIndex = peekIndex + 1; break; case '&': // $& - match stringBuilder += EcmaStringAccessor(matched).ToU16String(); if (EcmaStringAccessor(matched).IsUtf16()) { canBeCompress = false; } continueFromIndex = peekIndex + 1; break; case '`': // $` - prefix if (position > 0) { EcmaString *prefix = EcmaStringAccessor::FastSubString(ecmaVm, srcString, 0, position); stringBuilder += EcmaStringAccessor(prefix).ToU16String(); if (EcmaStringAccessor(prefix).IsUtf16()) { canBeCompress = false; } } continueFromIndex = peekIndex + 1; break; case '\'': { // $' - suffix int32_t srcLength = static_cast<int32_t>(EcmaStringAccessor(srcString).GetLength()); if (tailPos < srcLength) { EcmaString *sufffix = EcmaStringAccessor::FastSubString( ecmaVm, srcString, tailPos, srcLength - tailPos); stringBuilder += EcmaStringAccessor(sufffix).ToU16String(); if (EcmaStringAccessor(sufffix).IsUtf16()) { canBeCompress = false; } } continueFromIndex = peekIndex + 1; break; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { uint32_t capturesLength = captureList->GetLength(); // Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99 uint32_t scaledIndex = peek - '0'; int32_t advance = 1; if (peekIndex + 1 < replaceLength) { uint16_t nextPeek = EcmaStringAccessor(replacementFlat).Get(peekIndex + 1); if (nextPeek >= '0' && nextPeek <= '9') { constexpr uint32_t TEN_BASE = 10; uint32_t newScaledIndex = scaledIndex * TEN_BASE + (nextPeek - '0'); if (newScaledIndex <= capturesLength) { scaledIndex = newScaledIndex; advance = 2; // 2: 2 means from index needs to add two. } } } if (scaledIndex == 0 || scaledIndex > capturesLength) { stringBuilder += '$'; continueFromIndex = peekIndex; break; } JSTaggedValue capturesVal(captureList->Get(scaledIndex - 1)); if (!capturesVal.IsUndefined()) { EcmaString *captureString = EcmaString::Cast(capturesVal.GetTaggedObject()); stringBuilder += EcmaStringAccessor(captureString).ToU16String(); if (EcmaStringAccessor(captureString).IsUtf16()) { canBeCompress = false; } } continueFromIndex = peekIndex + advance; break; } case '<': { if (namedCaptures->IsUndefined()) { stringBuilder += '$'; continueFromIndex = peekIndex; break; } JSHandle<EcmaString> greaterSymString = factory->NewFromASCII(">"); int32_t pos = EcmaStringAccessor::IndexOf(ecmaVm, replacementFlat, greaterSymString, peekIndex); if (pos == -1) { stringBuilder += '$'; continueFromIndex = peekIndex; break; } JSHandle<EcmaString> groupName(thread, EcmaStringAccessor::FastSubString(ecmaVm, replacementFlat, peekIndex + 1, pos - peekIndex - 1)); JSHandle<JSTaggedValue> names(groupName); JSHandle<JSTaggedValue> capture = JSObject::GetProperty(thread, namedCaptures, names).GetValue(); if (capture->IsUndefined()) { continueFromIndex = pos + 1; break; } JSHandle<EcmaString> captureName = JSTaggedValue::ToString(thread, capture); stringBuilder += EcmaStringAccessor(captureName).ToU16String(); if (EcmaStringAccessor(captureName).IsUtf16()) { canBeCompress = false; } continueFromIndex = pos + 1; break; } default: stringBuilder += '$'; continueFromIndex = peekIndex; break; } // Go the the next $ in the replacement. nextDollarIndex = EcmaStringAccessor::IndexOf(ecmaVm, replacementFlat, dollarString, continueFromIndex); if (nextDollarIndex < 0) { if (continueFromIndex < replaceLength) { EcmaString *nextAppend = EcmaStringAccessor::FastSubString(ecmaVm, replacementFlat, continueFromIndex, replaceLength - continueFromIndex); stringBuilder += EcmaStringAccessor(nextAppend).ToU16String(); if (EcmaStringAccessor(nextAppend).IsUtf16()) { canBeCompress = false; } } auto *char16tData = const_cast<char16_t *>(stringBuilder.c_str()); auto *uint16tData = reinterpret_cast<uint16_t *>(char16tData); return canBeCompress ? factory->NewFromUtf16LiteralCompress(uint16tData, stringBuilder.length()).GetTaggedValue() : factory->NewFromUtf16LiteralNotCompress(uint16tData, stringBuilder.length()).GetTaggedValue(); } // Append substring between the previous and the next $ character. if (nextDollarIndex > continueFromIndex) { EcmaString *nextAppend = EcmaStringAccessor::FastSubString( ecmaVm, replacementFlat, continueFromIndex, nextDollarIndex - continueFromIndex); stringBuilder += EcmaStringAccessor(nextAppend).ToU16String(); if (EcmaStringAccessor(nextAppend).IsUtf16()) { canBeCompress = false; } } } LOG_ECMA(FATAL) << "this branch is unreachable"; UNREACHABLE(); } // 21.1.3.15 JSTaggedValue BuiltinsString::Search(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, Search); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); const GlobalEnvConstants *globalConst = thread->GlobalConstants(); JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<JSTaggedValue> regexp = BuiltinsString::GetCallArg(argv, 0); JSHandle<JSTaggedValue> searchTag = thread->GetEcmaVM()->GetGlobalEnv()->GetSearchSymbol(); JSHandle<JSTaggedValue> undefined = globalConst->GetHandledUndefined(); if (!regexp->IsUndefined() && !regexp->IsNull()) { JSHandle<JSTaggedValue> searcher = JSObject::GetMethod(thread, regexp, searchTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); if (!searcher->IsUndefined()) { ASSERT(searcher->IsJSFunction()); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, searcher, regexp, undefined, 1); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); info->SetCallArg(thisTag.GetTaggedValue()); return JSFunction::Call(info); } } JSHandle<EcmaString> thisVal = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<JSTaggedValue> rx(thread, BuiltinsRegExp::RegExpCreate(thread, regexp, undefined)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, undefined, rx, undefined, 1); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); info->SetCallArg(thisVal.GetTaggedValue()); return JSFunction::Invoke(info, searchTag); } // 21.1.3.16 JSTaggedValue BuiltinsString::Slice(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, Slice); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); int32_t thisLen = static_cast<int32_t>(EcmaStringAccessor(thisHandle).GetLength()); JSHandle<JSTaggedValue> startTag = BuiltinsString::GetCallArg(argv, 0); JSTaggedNumber startVal = JSTaggedValue::ToInteger(thread, startTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); int32_t start = ConvertDoubleToInt(startVal.GetNumber()); int32_t end = 0; JSHandle<JSTaggedValue> endTag = BuiltinsString::GetCallArg(argv, 1); if (endTag->IsUndefined()) { end = thisLen; } else { JSTaggedNumber endVal = JSTaggedValue::ToInteger(thread, endTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); end = ConvertDoubleToInt(endVal.GetNumber()); } int32_t from = 0; int32_t to = 0; if (start < 0) { from = std::max(start + thisLen, 0); } else { from = std::min(start, thisLen); } if (end < 0) { to = std::max(end + thisLen, 0); } else { to = std::min(end, thisLen); } int32_t len = std::max(to - from, 0); return JSTaggedValue(EcmaStringAccessor::FastSubString(thread->GetEcmaVM(), thisHandle, from, len)); } // 21.1.3.17 JSTaggedValue BuiltinsString::Split(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, Split); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); auto ecmaVm = thread->GetEcmaVM(); JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv(); // Let O be RequireObjectCoercible(this value). JSHandle<JSTaggedValue> thisTag = JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<JSTaggedValue> seperatorTag = BuiltinsString::GetCallArg(argv, 0); JSHandle<JSTaggedValue> limitTag = BuiltinsString::GetCallArg(argv, 1); if (thisTag->IsString() && seperatorTag->IsString()) { JSHandle<EcmaString> thisString(thisTag); JSHandle<EcmaString> seperatorString(seperatorTag); auto thisLength = EcmaStringAccessor(thisString).GetLength(); auto seperatorLength = EcmaStringAccessor(seperatorString).GetLength(); if (limitTag->IsUndefined() && thisLength != 0 && seperatorLength != 0) { return CreateArrayThisStringAndSeperatorStringAreNotEmpty( thread, ecmaVm, thisString, seperatorString, thisLength, seperatorLength); } uint32_t lim = UINT32_MAX - 1; if (!limitTag->IsUndefined()) { JSTaggedNumber limitIntValue = JSTaggedValue::ToInteger(thread, limitTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); lim = limitIntValue.ToUint32(); } // ReturnIfAbrupt(lim). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); if (lim == 0) { JSHandle<JSObject> resultArray(JSArray::ArrayCreate(thread, JSTaggedNumber(0))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); return resultArray.GetTaggedValue(); } return CreateArrayBySplitString(thread, ecmaVm, thisString, seperatorString, thisLength, seperatorLength, lim); } // If separator is neither undefined nor null, then if (!seperatorTag->IsUndefined() && !seperatorTag->IsNull()) { JSHandle<JSTaggedValue> splitKey = env->GetSplitSymbol(); // Let splitter be GetMethod(separator, @@split). JSHandle<JSTaggedValue> splitter = JSObject::GetMethod(thread, seperatorTag, splitKey); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); if (!splitter->IsUndefined()) { // Return Call(splitter, separator, «â€O, limit»). const uint32_t argsLength = 2; JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined(); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, splitter, seperatorTag, undefined, argsLength); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); info->SetCallArg(thisTag.GetTaggedValue(), limitTag.GetTaggedValue()); return JSFunction::Call(info); } } // Let S be ToString(O). JSHandle<EcmaString> thisString = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // If limit is undefined, let lim = 2^53–1; else let lim = ToLength(limit). uint32_t lim = UINT32_MAX - 1; if (!limitTag->IsUndefined()) { JSTaggedNumber limitIntValue = JSTaggedValue::ToInteger(thread, limitTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); lim = limitIntValue.ToUint32(); } // ReturnIfAbrupt(lim). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // Let s be the number of elements in S. auto thisLength = EcmaStringAccessor(thisString).GetLength(); JSHandle<EcmaString> seperatorString = JSTaggedValue::ToString(thread, seperatorTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // If lim = 0, return A. if (lim == 0) { JSHandle<JSObject> resultArray(JSArray::ArrayCreate(thread, JSTaggedNumber(0))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); return resultArray.GetTaggedValue(); } auto seperatorLength = EcmaStringAccessor(seperatorString).GetLength(); // If S is undefined or (this.length = 0 and S.length != 0), return array of size is 1 containing this string if (seperatorTag->IsUndefined()) { JSHandle<JSObject> resultArray(JSArray::ArrayCreate(thread, JSTaggedNumber(1))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // Perform CreateDataProperty(A, "0", S), CreateDataProperty's fast path JSObject::CreateDataProperty(thread, resultArray, 0, JSHandle<JSTaggedValue>(thisString)); ASSERT_PRINT(!thread->HasPendingException(), "CreateDataProperty(A, \"0\", S) can't throw exception"); return resultArray.GetTaggedValue(); } return CreateArrayBySplitString(thread, ecmaVm, thisString, seperatorString, thisLength, seperatorLength, lim); } JSTaggedValue BuiltinsString::CreateArrayFromString(JSThread *thread, EcmaVM *ecmaVm, const JSHandle<EcmaString> &thisString, uint32_t thisLength, uint32_t lim) { uint32_t actualLength = std::min(thisLength, lim); JSHandle<JSObject> resultArray(JSArray::ArrayCreate(thread, JSTaggedNumber(actualLength))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); for (uint32_t i = 0; i < actualLength; ++i) { EcmaString *elementString = EcmaStringAccessor::FastSubString(ecmaVm, thisString, i, 1); JSHandle<JSTaggedValue> elementTag(thread, elementString); // Perform CreateDataProperty(A, "0", S), CreateDataProperty's fast path JSObject::CreateDataProperty(thread, resultArray, i, elementTag); ASSERT_PRINT(!thread->HasPendingException(), "CreateDataProperty can't throw exception"); } return resultArray.GetTaggedValue(); } JSTaggedValue BuiltinsString::CreateArrayBySplitString(JSThread *thread, EcmaVM *ecmaVm, const JSHandle<EcmaString> &thisString, const JSHandle<EcmaString> &seperatorString, uint32_t thisLength, uint32_t seperatorLength, uint32_t lim) { if (thisLength != 0) { if (seperatorLength != 0) { return CreateArrayThisStringAndSeperatorStringAreNotEmpty( thread, ecmaVm, thisString, seperatorString, thisLength, seperatorLength, lim); } return CreateArrayFromString(thread, ecmaVm, thisString, thisLength, lim); } else { if (seperatorLength != 0) { JSHandle<JSObject> resultArray(JSArray::ArrayCreate(thread, JSTaggedNumber(1))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // Perform CreateDataProperty(A, "0", S), CreateDataProperty's fast path JSObject::CreateDataProperty(thread, resultArray, 0, JSHandle<JSTaggedValue>(thisString)); ASSERT_PRINT(!thread->HasPendingException(), "CreateDataProperty(A, \"0\", S) can't throw exception"); return resultArray.GetTaggedValue(); } JSHandle<JSObject> resultArray(JSArray::ArrayCreate(thread, JSTaggedNumber(0))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); return resultArray.GetTaggedValue(); } } JSTaggedValue BuiltinsString::CreateArrayThisStringAndSeperatorStringAreNotEmpty(JSThread *thread, EcmaVM *ecmaVm, const JSHandle<EcmaString> &thisString, const JSHandle<EcmaString> &seperatorString, uint32_t thisLength, uint32_t seperatorLength, uint32_t lim) { if (lim == UINT32_MAX - 1) { JSHandle<StringSplitResultCache> cacheTable(thread->GetCurrentEcmaContext()->GetStringSplitResultCache()); JSTaggedValue cacheResult = StringSplitResultCache::FindCachedResult(thread, cacheTable, thisString, seperatorString); if (cacheResult != JSTaggedValue::Undefined()) { JSHandle<JSTaggedValue> resultArray(JSArray::CreateArrayFromList(thread, JSHandle<TaggedArray>(thread, cacheResult))); return resultArray.GetTaggedValue(); } } uint32_t arrayLength = 0; std::vector<int32_t> posArray; int32_t index = 0; int32_t pos = EcmaStringAccessor::IndexOf(ecmaVm, thisString, seperatorString); while (pos != -1) { posArray.emplace_back(pos); ++arrayLength; if (arrayLength == lim) { break; } index = pos + static_cast<int32_t>(seperatorLength); pos = EcmaStringAccessor::IndexOf(ecmaVm, thisString, seperatorString, index); } uint32_t posArrLength = posArray.size(); arrayLength = lim > posArrLength ? posArrLength + 1 : posArrLength; return JSArray::ArrayCreateWithInit(thread, arrayLength, [thread, ecmaVm, &thisString, &seperatorString, &posArray, thisLength, seperatorLength, lim, posArrLength] (const JSHandle<TaggedArray> &newElements, [[maybe_unused]] uint32_t length) { int32_t index = 0; int32_t pos = 0; JSMutableHandle<JSTaggedValue> elementTag(thread, JSTaggedValue::Undefined()); for (uint32_t i = 0; i < posArrLength; i++) { pos = posArray[i]; EcmaString *elementString = EcmaStringAccessor::GetSubString(ecmaVm, thisString, index, pos - index); elementTag.Update(JSTaggedValue(elementString)); newElements->Set(thread, i, elementTag); index = pos + static_cast<int32_t>(seperatorLength); } if (lim > posArrLength) { EcmaString *elementString = EcmaStringAccessor::GetSubString(ecmaVm, thisString, index, thisLength - index); elementTag.Update(JSTaggedValue(elementString)); newElements->Set(thread, posArrLength, elementTag); } if (lim == UINT32_MAX - 1) { JSHandle<StringSplitResultCache> cacheTable(thread->GetCurrentEcmaContext()->GetStringSplitResultCache()); StringSplitResultCache::SetCachedResult(thread, cacheTable, thisString, seperatorString, newElements); } }); } // 21.1.3.18 JSTaggedValue BuiltinsString::StartsWith(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, StartsWith); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0); JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); bool isRegexp = JSObject::IsRegExp(thread, searchTag); if (isRegexp) { THROW_TYPE_ERROR_AND_RETURN(thread, "is regexp", JSTaggedValue::Exception()); } JSHandle<EcmaString> searchHandle = JSTaggedValue::ToString(thread, searchTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); uint32_t thisLen = EcmaStringAccessor(thisHandle).GetLength(); uint32_t searchLen = EcmaStringAccessor(searchHandle).GetLength(); int32_t pos = 0; JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 1); if (posTag->IsUndefined()) { pos = 0; } else if (posTag->IsInt()) { pos = posTag->GetInt(); } else { JSTaggedNumber posVal = JSTaggedValue::ToInteger(thread, posTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); if (posVal.GetNumber() == BuiltinsNumber::POSITIVE_INFINITY) { pos = thisLen; } else { pos = posVal.ToInt32(); } } pos = std::min(std::max(pos, 0), static_cast<int32_t>(thisLen)); if (static_cast<uint32_t>(pos) + searchLen > thisLen) { return BuiltinsString::GetTaggedBoolean(false); } int32_t res = EcmaStringAccessor::IndexOf(thread->GetEcmaVM(), thisHandle, searchHandle, pos); if (res == pos) { return BuiltinsString::GetTaggedBoolean(true); } return BuiltinsString::GetTaggedBoolean(false); } // 21.1.3.19 JSTaggedValue BuiltinsString::Substring(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, Substring); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); int32_t thisLen = static_cast<int32_t>(EcmaStringAccessor(thisHandle).GetLength()); JSHandle<JSTaggedValue> startTag = BuiltinsString::GetCallArg(argv, 0); JSTaggedNumber startVal = JSTaggedValue::ToInteger(thread, startTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); int32_t start = ConvertDoubleToInt(startVal.GetNumber()); int32_t end = 0; JSHandle<JSTaggedValue> endTag = BuiltinsString::GetCallArg(argv, 1); if (endTag->IsUndefined()) { end = thisLen; } else { JSTaggedNumber endVal = JSTaggedValue::ToInteger(thread, endTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); end = ConvertDoubleToInt(endVal.GetNumber()); } start = std::min(std::max(start, 0), thisLen); end = std::min(std::max(end, 0), thisLen); int32_t from = std::min(start, end); int32_t to = std::max(start, end); int32_t len = to - from; return JSTaggedValue(EcmaStringAccessor::GetSubString(thread->GetEcmaVM(), thisHandle, from, len)); } // 21.1.3.20 JSTaggedValue BuiltinsString::ToLocaleLowerCase(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, ToLocaleLowerCase); JSThread *thread = argv->GetThread(); EcmaVM *ecmaVm = thread->GetEcmaVM(); [[maybe_unused]] EcmaHandleScope handleScope(thread); // Let O be RequireObjectCoercible(this value). JSHandle<JSTaggedValue> obj(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // Let S be ? ToString(O). JSHandle<EcmaString> string = JSTaggedValue::ToString(thread, obj); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // Let requestedLocales be ? CanonicalizeLocaleList(locales). JSHandle<JSTaggedValue> locales = GetCallArg(argv, 0); // Fast path if (locales->IsUndefined() && EcmaStringAccessor(string).IsUtf8()) { EcmaString *result = EcmaStringAccessor::TryToLower(ecmaVm, string); return JSTaggedValue(result); } JSHandle<TaggedArray> requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // If requestedLocales is not an empty List, then Let requestedLocale be requestedLocales[0]. // Else, Let requestedLocale be DefaultLocale(). JSHandle<EcmaString> requestedLocale = intl::LocaleHelper::DefaultLocale(thread); if (requestedLocales->GetLength() != 0) { requestedLocale = JSHandle<EcmaString>(thread, requestedLocales->Get(0)); } // Let noExtensionsLocale be the String value that is requestedLocale with all Unicode locale extension sequences // removed. intl::LocaleHelper::ParsedLocale noExtensionsLocale = intl::LocaleHelper::HandleLocale(requestedLocale); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // Let availableLocales be a List with language tags that includes the languages for which the Unicode Character // Database contains language sensitive case mappings. Implementations may add additional language tags // if they support case mapping for additional locales. std::vector<std::string> availableLocales = intl::LocaleHelper::GetAvailableLocales(thread, nullptr, nullptr); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // Let locale be BestAvailableLocale(availableLocales, noExtensionsLocale). std::string locale = intl::LocaleHelper::BestAvailableLocale(availableLocales, noExtensionsLocale.base); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // If locale is undefined, let locale be "und". if (locale.empty()) { locale = "und"; } // Let uString be a List containing in order the code points of S as defined in ES2020, 6.1.4, // starting at the first element of S. // Transform those elements in uString to the to the Unicode Default Case Conversion algorithm icu::Locale icuLocale = icu::Locale::createFromName(locale.c_str()); EcmaString *result = EcmaStringAccessor::ToLocaleLower(ecmaVm, string, icuLocale); return JSTaggedValue(result); } // 21.1.3.21 JSTaggedValue BuiltinsString::ToLocaleUpperCase(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, ToLocaleLowerCase); JSThread *thread = argv->GetThread(); EcmaVM *ecmaVm = thread->GetEcmaVM(); [[maybe_unused]] EcmaHandleScope handleScope(thread); // Let O be RequireObjectCoercible(this value). JSHandle<JSTaggedValue> obj(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // Let S be ? ToString(O). JSHandle<EcmaString> string = JSTaggedValue::ToString(thread, obj); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // Let requestedLocales be ? CanonicalizeLocaleList(locales). JSHandle<JSTaggedValue> locales = GetCallArg(argv, 0); JSHandle<TaggedArray> requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // If requestedLocales is not an empty List, then Let requestedLocale be requestedLocales[0]. // Else, Let requestedLocale be DefaultLocale(). JSHandle<EcmaString> requestedLocale = intl::LocaleHelper::DefaultLocale(thread); if (requestedLocales->GetLength() != 0) { requestedLocale = JSHandle<EcmaString>(thread, requestedLocales->Get(0)); } // Let noExtensionsLocale be the String value that is requestedLocale with all Unicode locale extension sequences // removed. intl::LocaleHelper::ParsedLocale noExtensionsLocale = intl::LocaleHelper::HandleLocale(requestedLocale); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // Let availableLocales be a List with language tags that includes the languages for which the Unicode Character // Database contains language sensitive case mappings. Implementations may add additional language tags // if they support case mapping for additional locales. std::vector<std::string> availableLocales = intl::LocaleHelper::GetAvailableLocales(thread, nullptr, nullptr); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // Let locale be BestAvailableLocale(availableLocales, noExtensionsLocale). std::string locale = intl::LocaleHelper::BestAvailableLocale(availableLocales, noExtensionsLocale.base); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // If locale is undefined, let locale be "und". if (locale.empty()) { locale = "und"; } // Let uString be a List containing in order the code points of S as defined in ES2020, 6.1.4, // starting at the first element of S. // Transform those elements in uString to the to the Unicode Default Case Conversion algorithm icu::Locale icuLocale = icu::Locale::createFromName(locale.c_str()); EcmaString *result = EcmaStringAccessor::ToLocaleUpper(ecmaVm, string, icuLocale); return JSTaggedValue(result); } // 21.1.3.22 JSTaggedValue BuiltinsString::ToLowerCase(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, ToLowerCase); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); EcmaString *result = EcmaStringAccessor::ToLower(thread->GetEcmaVM(), thisHandle); return JSTaggedValue(result); } // 21.1.3.23 JSTaggedValue BuiltinsString::ToString(EcmaRuntimeCallInfo *argv) { ASSERT(argv); return ThisStringValue(argv->GetThread(), GetThis(argv).GetTaggedValue()); } // 21.1.3.24 JSTaggedValue BuiltinsString::ToUpperCase(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, ToUpperCase); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); EcmaString *result = EcmaStringAccessor::ToUpper(thread->GetEcmaVM(), thisHandle); return JSTaggedValue(result); } // 21.1.3.25 JSTaggedValue BuiltinsString::Trim(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, Trim); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); EcmaString *res = EcmaStringAccessor::Trim(thread, thisHandle, EcmaString::TrimMode::TRIM); return JSTaggedValue(res); } JSTaggedValue BuiltinsString::TrimStart(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, TrimStart); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> thisTag = JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); EcmaString *res = EcmaStringAccessor::Trim(thread, thisHandle, EcmaString::TrimMode::TRIM_START); return JSTaggedValue(res); } JSTaggedValue BuiltinsString::TrimEnd(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, TrimEnd); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> thisTag = JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); EcmaString *res = EcmaStringAccessor::Trim(thread, thisHandle, EcmaString::TrimMode::TRIM_END); return JSTaggedValue(res); } JSTaggedValue BuiltinsString::TrimLeft(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, TrimLeft); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> thisTag = JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); EcmaString *res = EcmaStringAccessor::Trim(thread, thisHandle, EcmaString::TrimMode::TRIM_START); return JSTaggedValue(res); } JSTaggedValue BuiltinsString::TrimRight(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, TrimRight); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> thisTag = JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); EcmaString *res = EcmaStringAccessor::Trim(thread, thisHandle, EcmaString::TrimMode::TRIM_END); return JSTaggedValue(res); } // 21.1.3.26 JSTaggedValue BuiltinsString::ValueOf(EcmaRuntimeCallInfo *argv) { ASSERT(argv); return ThisStringValue(argv->GetThread(), GetThis(argv).GetTaggedValue()); } // 21.1.3.27 JSTaggedValue BuiltinsString::GetStringIterator(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, GetStringIterator); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); // 1. Let O be RequireObjectCoercible(this value). JSHandle<JSTaggedValue> current(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // Let S be ToString(O). JSHandle<EcmaString> string = JSTaggedValue::ToString(thread, current); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(argv->GetThread()); // Return CreateStringIterator(S). return JSStringIterator::CreateStringIterator(thread, string).GetTaggedValue(); } // B.2.3.1 JSTaggedValue BuiltinsString::SubStr(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, SubStr); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); // 1. Let O be RequireObjectCoercible(this value). // 2. Let S be ToString(O). JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisString = JSTaggedValue::ToString(thread, thisTag); // 3. ReturnIfAbrupt(S). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<JSTaggedValue> intStart = GetCallArg(argv, 0); // 4. Let intStart be ToInteger(start). JSTaggedNumber numStart = JSTaggedValue::ToInteger(thread, intStart); // 5. ReturnIfAbrupt(intStart). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); int32_t start = numStart.ToInt32(); JSHandle<JSTaggedValue> lengthTag = GetCallArg(argv, 1); // 6. If length is undefined, let end be +ï‚¥; otherwise let end be ToInteger(length). int32_t end = 0; if (lengthTag->IsUndefined()) { end = INT_MAX; } else { JSTaggedNumber lengthNumber = JSTaggedValue::ToInteger(thread, lengthTag); // 7. ReturnIfAbrupt(end). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); end = lengthNumber.ToInt32(); } // 8. Let size be the number of code units in S. int32_t size = static_cast<int32_t>(EcmaStringAccessor(thisString).GetLength()); // 9. If intStart < 0, let intStart be max(size + intStart,0). if (start < 0) { start = std::max(size + start, 0); } // 10. Let resultLength be min(max(end,0), size – intStart). int32_t resultLength = std::min(std::max(end, 0), size - start); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); // 11. If resultLength ï‚£ 0, return the empty String "". if (resultLength <= 0) { return factory->GetEmptyString().GetTaggedValue(); } return JSTaggedValue(EcmaStringAccessor::FastSubString(thread->GetEcmaVM(), thisString, start, resultLength)); } // 22.1.3.1 JSTaggedValue BuiltinsString::At(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), String, Substring); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); // 1. Let O be RequireObjectCoercible(this value). // 2. Let S be ToString(O). JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 3. Let len be the length of S. int32_t thisLen = static_cast<int32_t>(EcmaStringAccessor(thisHandle).GetLength()); // 4. Let relativeIndex be ? ToIntegerOrInfinity(index). JSHandle<JSTaggedValue> indexTag = BuiltinsString::GetCallArg(argv, 0); JSTaggedNumber indexVal = JSTaggedValue::ToInteger(thread, indexTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); int32_t relativeIndex = ConvertDoubleToInt(indexVal.GetNumber()); // 5. If relativeIndex ≥ 0, then Let k be relativeIndex. 6. Else, Let k be len + relativeIndex. int32_t k = 0; if (relativeIndex >= 0) { k = relativeIndex; } else { k = thisLen + relativeIndex; } // 7. If k < 0 or k ≥ len, return undefined. if (k < 0 || k >= thisLen) { return JSTaggedValue::Undefined(); } // 8. Return the substring of S from k to k + 1. return JSTaggedValue(EcmaStringAccessor::FastSubString(thread->GetEcmaVM(), thisHandle, k, 1)); } JSTaggedValue BuiltinsString::GetLength(EcmaRuntimeCallInfo *argv) { ASSERT(argv); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, String, GetLength); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> thisHandle = GetThis(argv); JSHandle<EcmaString> thisString = JSTaggedValue::ToString(thread, thisHandle); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); return GetTaggedInt(EcmaStringAccessor(thisString).GetLength()); } // 21.1.3 JSTaggedValue BuiltinsString::ThisStringValue(JSThread *thread, JSTaggedValue value) { BUILTINS_API_TRACE(thread, String, ThisStringValue); if (value.IsString()) { return value; } if (value.IsECMAObject()) { auto jshclass = value.GetTaggedObject()->GetClass(); if (jshclass->GetObjectType() == JSType::JS_PRIMITIVE_REF) { JSTaggedValue primitive = JSPrimitiveRef::Cast(value.GetTaggedObject())->GetValue(); if (primitive.IsString()) { return primitive; } } } THROW_TYPE_ERROR_AND_RETURN(thread, "can not convert to String", JSTaggedValue::Exception()); } JSTaggedValue BuiltinsString::Pad(EcmaRuntimeCallInfo *argv, bool isStart) { JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, String, Pad); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle<JSTaggedValue> thisTag = JSTaggedValue::RequireObjectCoercible(thread, BuiltinsString::GetThis(argv)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<JSTaggedValue> lengthTag = GetCallArg(argv, 0); JSTaggedNumber number = JSTaggedValue::ToNumber(thread, lengthTag); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); int64_t intMaxLength = base::NumberHelper::DoubleToInt64(number.GetNumber()); int32_t stringLength = static_cast<int32_t>(EcmaStringAccessor(thisHandle).GetLength()); if (intMaxLength <= stringLength) { return thisHandle.GetTaggedValue(); } JSHandle<JSTaggedValue> fillString = GetCallArg(argv, 1); std::u16string stringBuilder; if (fillString->IsUndefined()) { stringBuilder = u" "; } else { JSHandle<EcmaString> filler = JSTaggedValue::ToString(thread, fillString); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); stringBuilder = EcmaStringAccessor(filler).ToU16String(); } if (stringBuilder.size() == 0) { return thisHandle.GetTaggedValue(); } std::u16string u16strSearch = EcmaStringAccessor(thisHandle).ToU16String(); int64_t fillLen = intMaxLength - stringLength; int64_t len = static_cast<int64_t>(stringBuilder.length()); if (static_cast<size_t>(intMaxLength) >= EcmaString::MAX_STRING_LENGTH) { THROW_RANGE_ERROR_AND_RETURN(thread, "Invalid string length", JSTaggedValue::Exception()); } std::u16string fiString; for (int32_t i = 0; i < fillLen; ++i) { fiString += stringBuilder[i % len]; } std::u16string resultString; if (isStart) { resultString = fiString + u16strSearch; } else { resultString = u16strSearch + fiString; } ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); return factory->NewFromUtf16Literal(reinterpret_cast<const uint16_t *>(resultString.c_str()), resultString.size()).GetTaggedValue(); } int32_t BuiltinsString::ConvertDoubleToInt(double d) { if (std::isnan(d) || d == -base::POSITIVE_INFINITY) { return 0; } if (d >= static_cast<double>(INT_MAX)) { return INT_MAX; } if (d <= static_cast<double>(INT_MIN)) { return INT_MIN; } return base::NumberHelper::DoubleToInt(d, base::INT32_BITS); } JSTaggedValue BuiltinsString::StringToList(JSThread *thread, JSHandle<EcmaString> &str) { JSHandle<StringToListResultCache> cacheTable(thread->GetCurrentEcmaContext()->GetStringToListResultCache()); JSTaggedValue cacheResult = StringToListResultCache::FindCachedResult(thread, cacheTable, str); if (cacheResult != JSTaggedValue::Undefined()) { JSHandle<JSTaggedValue> resultArray(JSArray::CreateArrayFromList(thread, JSHandle<TaggedArray>(thread, cacheResult))); return resultArray.GetTaggedValue(); } JSTaggedValue newArray = JSArray::ArrayCreate(thread, JSTaggedNumber(0)).GetTaggedValue(); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle<JSObject> newArrayHandle(thread, newArray); JSHandle<EcmaString> iteratedString(str); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle<TaggedArray> oldElements(thread, newArrayHandle->GetElements()); uint32_t totalElements = EcmaStringAccessor(iteratedString).GetLength(); JSHandle<TaggedArray> elements = (oldElements->GetLength() < totalElements) ? factory->ExtendArray(oldElements, totalElements) : oldElements; uint32_t index = 0; newArrayHandle->SetElements(thread, elements); while (index < totalElements) { uint16_t c = EcmaStringAccessor(iteratedString).Get(index); JSHandle<EcmaString> newStr = factory->NewFromUtf16Literal(&c, 1); ElementAccessor::Set(thread, newArrayHandle, index, newStr.GetTaggedValue(), true); index++; } JSHandle<JSArray>(newArrayHandle)->SetArrayLength(thread, totalElements); StringToListResultCache::SetCachedResult(thread, cacheTable, str, elements); return newArrayHandle.GetTaggedValue(); } JSTaggedValue StringSplitResultCache::CreateCacheTable(const JSThread *thread) { int length = CACHE_SIZE * ENTRY_SIZE; auto table = static_cast<StringSplitResultCache*>( *thread->GetEcmaVM()->GetFactory()->NewTaggedArray(length, JSTaggedValue::Undefined())); return JSTaggedValue(table); } JSTaggedValue StringSplitResultCache::FindCachedResult(const JSThread *thread, const JSHandle<StringSplitResultCache> &cache, const JSHandle<EcmaString> &thisString, const JSHandle<EcmaString> &pattern) { uint32_t hash = EcmaStringAccessor(thisString).GetHashcode(); uint32_t entry = hash & (CACHE_SIZE - 1); uint32_t index = entry * ENTRY_SIZE; JSTaggedValue cacheThis = cache->Get(index + STRING_INDEX); JSTaggedValue cachePattern = cache->Get(index + PATTERN_INDEX); if (!cacheThis.IsString() || !cachePattern.IsString()) { return JSTaggedValue::Undefined(); } JSHandle<EcmaString> cacheStringHandle(thread, cacheThis); JSHandle<EcmaString> cachePatternHandle(thread, cachePattern); if (EcmaStringAccessor::StringsAreEqual(thread->GetEcmaVM(), thisString, cacheStringHandle) && EcmaStringAccessor::StringsAreEqual(thread->GetEcmaVM(), pattern, cachePatternHandle)) { JSHandle<TaggedArray> cacheArray(thread, cache->Get(index + ARRAY_INDEX)); uint32_t arrayLength = cacheArray->GetLength(); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle<TaggedArray> copyArray = factory->NewAndCopyTaggedArray(cacheArray, arrayLength, arrayLength); return copyArray.GetTaggedValue(); } return JSTaggedValue::Undefined(); } void StringSplitResultCache::SetCachedResult(const JSThread *thread, const JSHandle<StringSplitResultCache> &cache, const JSHandle<EcmaString> &thisString, const JSHandle<EcmaString> &pattern, const JSHandle<TaggedArray> &resultArray) { // clone to cache array uint32_t arrayLength = resultArray->GetLength(); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle<TaggedArray> newElements(factory->NewTaggedArray(arrayLength)); for (uint32_t i = 0; i < arrayLength; i++) { newElements->Set(thread, i, resultArray->Get(i)); } uint32_t hash = EcmaStringAccessor(thisString).GetHashcode(); uint32_t entry = hash & (CACHE_SIZE - 1); uint32_t index = entry * ENTRY_SIZE; cache->Set(thread, index + STRING_INDEX, thisString); cache->Set(thread, index + PATTERN_INDEX, pattern); cache->Set(thread, index + ARRAY_INDEX, newElements); } JSTaggedValue StringToListResultCache::CreateCacheTable(const JSThread *thread) { int length = CACHE_SIZE * ENTRY_SIZE; auto table = static_cast<StringToListResultCache*>( *thread->GetEcmaVM()->GetFactory()->NewTaggedArray(length, JSTaggedValue::Undefined())); return JSTaggedValue(table); } JSTaggedValue StringToListResultCache::FindCachedResult(const JSThread *thread, const JSHandle<StringToListResultCache> &cache, const JSHandle<EcmaString> &thisString) { if (EcmaStringAccessor(thisString).GetLength() > MAX_STRING_LENGTH) { return JSTaggedValue::Undefined(); } uint32_t hash = EcmaStringAccessor(thisString).GetHashcode(); uint32_t entry = hash & (CACHE_SIZE - 1); uint32_t index = entry * ENTRY_SIZE; JSHandle<JSTaggedValue> cacheThis(thread, cache->Get(index + STRING_INDEX)); if (!cacheThis->IsString()) { return JSTaggedValue::Undefined(); } JSHandle<EcmaString> cacheStr(cacheThis); if (EcmaStringAccessor::StringsAreEqual(thread->GetEcmaVM(), thisString, cacheStr)) { return cache->Get(index + ARRAY_INDEX); } return JSTaggedValue::Undefined(); } void StringToListResultCache::SetCachedResult(const JSThread *thread, const JSHandle<StringToListResultCache> &cache, const JSHandle<EcmaString> &thisString, const JSHandle<TaggedArray> &resultArray) { if (EcmaStringAccessor(thisString).GetLength() > MAX_STRING_LENGTH || EcmaStringAccessor(thisString).GetLength() == 0) { return; } if (!EcmaStringAccessor(thisString).IsInternString()) { return; } // clone to cache array uint32_t arrayLength = resultArray->GetLength(); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle<TaggedArray> newElements(factory->NewCOWTaggedArray(arrayLength)); for (uint32_t i = 0; i < arrayLength; i++) { newElements->Set(thread, i, resultArray->Get(i)); } uint32_t hash = EcmaStringAccessor(thisString).GetHashcode(); uint32_t entry = hash & (CACHE_SIZE - 1); uint32_t index = entry * ENTRY_SIZE; cache->Set(thread, index + STRING_INDEX, thisString); cache->Set(thread, index + ARRAY_INDEX, newElements); } } // namespace panda::ecmascript::builtins