/* * Copyright (c) 2021-2024 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_regexp.h" #include "ecmascript/builtins/builtins_regexp-inl.h" #include #include "ecmascript/ecma_string-inl.h" #include "ecmascript/interpreter/interpreter.h" #include "ecmascript/js_regexp.h" #include "ecmascript/js_regexp_iterator.h" #include "ecmascript/js_tagged_value-inl.h" #include "ecmascript/object_fast_operator-inl.h" #include "ecmascript/property_detector-inl.h" #include "ecmascript/regexp/regexp_executor.h" #include "ecmascript/regexp/regexp_parser_cache.h" #include "ecmascript/tagged_array-inl.h" namespace panda::ecmascript::builtins { // 21.2.3.1 JSTaggedValue BuiltinsRegExp::RegExpConstructor(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), RegExp, Constructor); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle newTargetTemp = GetNewTarget(argv); JSHandle pattern = GetCallArg(argv, 0); JSHandle flags = GetCallArg(argv, 1); // 1. Let patternIsRegExp be IsRegExp(pattern). bool patternIsRegExp = JSObject::IsRegExp(thread, pattern); // 2. ReturnIfAbrupt(patternIsRegExp). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 3. If NewTarget is not undefined, let newTarget be NewTarget. JSHandle newTarget; const GlobalEnvConstants *globalConst = thread->GlobalConstants(); if (!newTargetTemp->IsUndefined()) { newTarget = newTargetTemp; } else { auto ecmaVm = thread->GetEcmaVM(); JSHandle env = ecmaVm->GetGlobalEnv(); // disable gc [[maybe_unused]] DisallowGarbageCollection noGc; // 4.a Let newTarget be the active function object. newTarget = env->GetRegExpFunction(); JSHandle constructorString = globalConst->GetHandledConstructorString(); // 4.b If patternIsRegExp is true and flags is undefined if (patternIsRegExp && flags->IsUndefined()) { // 4.b.i Let patternConstructor be Get(pattern, "constructor"). JSTaggedValue patternConstructor = ObjectFastOperator::FastGetPropertyByValue( thread, pattern.GetTaggedValue(), constructorString.GetTaggedValue()); // 4.b.ii ReturnIfAbrupt(patternConstructor). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 4.b.iii If SameValue(newTarget, patternConstructor) is true, return pattern. if (JSTaggedValue::SameValue(newTarget.GetTaggedValue(), patternConstructor)) { return pattern.GetTaggedValue(); } } } // 5. If Type(pattern) is Object and pattern has a [[RegExpMatcher]] internal slot bool isJsReg = false; if (pattern->IsECMAObject()) { JSHandle patternObj = JSHandle::Cast(pattern); isJsReg = patternObj->IsJSRegExp(); } JSHandle patternTemp; JSHandle flagsTemp; if (isJsReg) { JSHandle patternReg(thread, JSRegExp::Cast(pattern->GetTaggedObject())); // 5.a Let P be the value of pattern’s [[OriginalSource]] internal slot. patternTemp = JSHandle(thread, patternReg->GetOriginalSource()); if (flags->IsUndefined()) { // 5.b If flags is undefined, let F be the value of pattern’s [[OriginalFlags]] internal slot. flagsTemp = JSHandle(thread, patternReg->GetOriginalFlags()); } else { // 5.c Else, let F be flags. flagsTemp = JSHandle(thread, *JSTaggedValue::ToString(thread, flags)); } RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 6. Else if patternIsRegExp is true } else if (patternIsRegExp) { JSHandle sourceString(globalConst->GetHandledSourceString()); JSHandle flagsString(globalConst->GetHandledFlagsString()); // disable gc [[maybe_unused]] DisallowGarbageCollection noGc; // 6.a Let P be Get(pattern, "source"). patternTemp = JSObject::GetProperty(thread, pattern, sourceString).GetValue(); // 6.b ReturnIfAbrupt(P). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 6.c If flags is undefined if (flags->IsUndefined()) { // 6.c.i Let F be Get(pattern, "flags"). flagsTemp = JSObject::GetProperty(thread, pattern, flagsString).GetValue(); // 6.c.ii ReturnIfAbrupt(F). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } else { // 6.d Else, let F be flags. flagsTemp = JSHandle(thread, *JSTaggedValue::ToString(thread, flags)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } } else { // 7.a Let P be pattern. patternTemp = pattern; // 7.b Let F be flags. if (flags->IsUndefined()) { flagsTemp = flags; } else { flagsTemp = JSHandle(thread, *JSTaggedValue::ToString(thread, flags)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } } // 8. Let O be RegExpAlloc(newTarget). JSHandle object(thread, RegExpAlloc(thread, newTarget)); // 9. ReturnIfAbrupt(O). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 10. Return RegExpInitialize(O, P, F). JSTaggedValue result = RegExpInitialize(thread, object, patternTemp, flagsTemp); return JSTaggedValue(result); } // prototype // 20.2.5.2 JSTaggedValue BuiltinsRegExp::Exec(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), RegExp, Exec); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); // 1. Let R be the this value. JSHandle thisObj = GetThis(argv); // 4. Let S be ToString(string). JSHandle inputStr = GetCallArg(argv, 0); JSHandle stringHandle = JSTaggedValue::ToString(thread, inputStr); // 5. ReturnIfAbrupt(S). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle string = JSHandle::Cast(stringHandle); // 2. If Type(R) is not Object, throw a TypeError exception. if (!thisObj->IsECMAObject()) { // throw a TypeError exception. THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception()); } // 3. If R does not have a [[RegExpMatcher]] internal slot, throw a TypeError exception. if (!thisObj->IsJSRegExp()) { // throw a TypeError exception. THROW_TYPE_ERROR_AND_RETURN(thread, "this does not have [[RegExpMatcher]]", JSTaggedValue::Exception()); } bool useCache = true; bool isFastPath = IsFastRegExp(thread, thisObj); JSHandle cacheTable(thread->GetCurrentEcmaContext()->GetRegExpCache()); if (!isFastPath || cacheTable->GetLargeStrCount() == 0 || cacheTable->GetConflictCount() == 0) { useCache = false; } // 6. Return RegExpBuiltinExec(R, S). return RegExpBuiltinExec(thread, thisObj, string, isFastPath, useCache); } // 20.2.5.13 JSTaggedValue BuiltinsRegExp::Test(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), RegExp, Test); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); // 1. Let R be the this value. JSHandle thisObj = GetThis(argv); JSHandle inputStr = GetCallArg(argv, 0); // 2. If Type(R) is not Object, throw a TypeError exception. if (!thisObj->IsECMAObject()) { // throw a TypeError exception. THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception()); } // 3. Let string be ToString(S). // 4. ReturnIfAbrupt(string). JSHandle stringHandle = JSTaggedValue::ToString(thread, inputStr); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle string = JSHandle::Cast(stringHandle); // test fast path if (IsFastRegExp(thread, thisObj)) { return RegExpTestFast(thread, thisObj, string, true); } // 5. Let match be RegExpExec(R, string). JSTaggedValue matchResult = RegExpExec(thread, thisObj, string, false); // 6. ReturnIfAbrupt(match). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 7. If match is not null, return true; else return false. return GetTaggedBoolean(!matchResult.IsNull()); } bool BuiltinsRegExp::IsFastRegExp(JSThread *thread, JSHandle regexp, RegExpSymbol symbolTag) { JSHandle env = thread->GetEcmaVM()->GetGlobalEnv(); const GlobalEnvConstants *globalConst = thread->GlobalConstants(); JSHClass *hclass = JSHandle::Cast(regexp)->GetJSHClass(); JSHClass *originHClass = JSHClass::Cast(globalConst->GetJSRegExpClass().GetTaggedObject()); // regexp instance hclass if (hclass != originHClass) { return false; } // lastIndex type is Int JSTaggedValue lastIndex = JSHandle::Cast(regexp)->GetPropertyInlinedProps(LAST_INDEX_OFFSET); if (!lastIndex.IsInt() || lastIndex.GetInt() < 0) { return false; } // RegExp.prototype hclass JSTaggedValue proto = hclass->GetPrototype(); JSHClass *regexpHclass = proto.GetTaggedObject()->GetClass(); JSHandle originRegexpClassValue = env->GetRegExpPrototypeClass(); JSHClass *originRegexpHclass = JSHClass::Cast(originRegexpClassValue.GetTaggedValue().GetTaggedObject()); if (regexpHclass != originRegexpHclass) { return false; } JSObject* protoObj = JSObject::Cast(proto); // RegExp.prototype.exec JSTaggedValue execVal = protoObj->GetPropertyInlinedProps(JSRegExp::EXEC_INLINE_PROPERTY_INDEX); if (execVal != env->GetTaggedRegExpExecFunction()) { return false; } JSTaggedValue symbolFunc = JSTaggedValue::Hole(); switch (symbolTag) { case RegExpSymbol::UNKNOWN: break; #define V(UpperCase, Camel) \ case RegExpSymbol::UpperCase: \ symbolFunc = protoObj->GetPropertyInlinedProps( \ JSRegExp::UpperCase##_INLINE_PROPERTY_INDEX); \ if (symbolFunc != env->GetTaggedRegExp##Camel##Function()) { \ return false; \ } \ break; REGEXP_SYMBOL_FUNCTION_LIST(V) #undef V } if (!PropertyDetector::IsRegExpFlagsDetectorValid(env)) { return false; } return true; } JSTaggedValue BuiltinsRegExp::RegExpTestFast(JSThread *thread, JSHandle regexp, const JSHandle inputStr, bool useCache) { // 1. Assert: Type(S) is String. ASSERT(inputStr->IsString()); uint32_t lastIndex = static_cast(GetLastIndex(thread, regexp, true)); // 2. Search RegExpExecResult cache JSHandle cacheTable(thread->GetCurrentEcmaContext()->GetRegExpCache()); JSHandle undefined = thread->GlobalConstants()->GetHandledUndefined(); if (useCache) { JSTaggedValue cacheResult = cacheTable->FindCachedResult(thread, inputStr, RegExpExecResultCache::TEST_TYPE, regexp, JSTaggedValue(lastIndex), undefined); if (!cacheResult.IsUndefined()) { return cacheResult; } } uint32_t length = EcmaStringAccessor(inputStr->GetTaggedObject()).GetLength(); if (lastIndex > length) { SetLastIndex(thread, regexp, JSTaggedValue(0), true); return JSTaggedValue::False(); } JSHandle inputString = JSHandle::Cast(inputStr); bool matchResult = RegExpExecInternal(thread, regexp, inputString, lastIndex); // 2. Check whether the regexp is global or sticky, which determines whether we update last index later on. bool global = GetOriginalFlag(thread, regexp, RegExpParser::FLAG_GLOBAL); bool sticky = GetOriginalFlag(thread, regexp, RegExpParser::FLAG_STICKY); bool ifUpdateLastIndex = global || sticky; if (!matchResult) { if (ifUpdateLastIndex) { SetLastIndex(thread, regexp, JSTaggedValue(0), true); } if (useCache) { RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, inputStr, JSHandle(thread, JSTaggedValue(matchResult)), RegExpExecResultCache::TEST_TYPE, lastIndex, 0, undefined); // 0: match fail so lastIndex is 0 } return JSTaggedValue::False(); } JSHandle globalTable(thread->GetCurrentEcmaContext()->GetRegExpGlobalResult()); JSTaggedValue endIndex = globalTable->GetEndIndex(); uint32_t newLastIndex = lastIndex; if (ifUpdateLastIndex) { newLastIndex = static_cast(endIndex.GetInt()); SetLastIndex(thread, regexp, endIndex, true); } if (useCache) { RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, inputStr, JSHandle(thread, JSTaggedValue(matchResult)), RegExpExecResultCache::TEST_TYPE, lastIndex, newLastIndex, undefined); } return GetTaggedBoolean(matchResult); } // 20.2.5.14 JSTaggedValue BuiltinsRegExp::ToString(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), RegExp, ToString); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); // 1. Let R be the this value. JSHandle thisObj = GetThis(argv); auto ecmaVm = thread->GetEcmaVM(); // 2. If Type(R) is not Object, throw a TypeError exception. if (!thisObj->IsECMAObject()) { THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception()); } const GlobalEnvConstants *globalConstants = thread->GlobalConstants(); JSMutableHandle getSource(thread, JSTaggedValue::Undefined()); JSMutableHandle getFlags(thread, JSTaggedValue::Undefined()); JSHandle sourceStrHandle; JSHandle flagsStrHandle; if (IsFastRegExp(thread, thisObj)) { JSHandle regexp(thread, JSRegExp::Cast(thisObj->GetTaggedObject())); // 3. Let pattern be ToString(Get(R, "source")). getSource.Update(regexp->GetOriginalSource()); sourceStrHandle = JSTaggedValue::ToString(thread, getSource); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); uint8_t flagsBits = static_cast(regexp->GetOriginalFlags().GetInt()); getFlags.Update(FlagsBitsToString(thread, flagsBits)); flagsStrHandle = JSTaggedValue::ToString(thread, getFlags); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } else { JSHandle sourceString(globalConstants->GetHandledSourceString()); JSHandle flagsString(globalConstants->GetHandledFlagsString()); // 3. Let pattern be ToString(Get(R, "source")). getSource.Update(JSObject::GetProperty(thread, thisObj, sourceString).GetValue()); sourceStrHandle = JSTaggedValue::ToString(thread, getSource); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); getFlags.Update(JSObject::GetProperty(thread, thisObj, flagsString).GetValue()); flagsStrHandle = JSTaggedValue::ToString(thread, getFlags); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } JSHandle slashStr = JSHandle::Cast(globalConstants->GetHandledBackslashString()); // 7. Let result be the String value formed by concatenating "/", pattern, and "/", and flags. ObjectFactory *factory = ecmaVm->GetFactory(); JSHandle tempStr = factory->ConcatFromString(slashStr, sourceStrHandle); JSHandle resultTemp = factory->ConcatFromString(tempStr, slashStr); return factory->ConcatFromString(resultTemp, flagsStrHandle).GetTaggedValue(); } // 20.2.5.3 JSTaggedValue BuiltinsRegExp::GetFlags(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), RegExp, GetFlags); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); // 1. Let R be the this value. JSHandle thisObj = GetThis(argv); // 2. If Type(R) is not Object, throw a TypeError exception. if (!thisObj->IsECMAObject()) { THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception()); } // 3. Let result be the empty String. // 4. ~ 19. if (!IsFastRegExp(thread, thisObj)) { return GetAllFlagsInternal(thread, thisObj); } uint8_t flagsBits = static_cast(JSRegExp::Cast(thisObj->GetTaggedObject())->GetOriginalFlags().GetInt()); return FlagsBitsToString(thread, flagsBits); } JSTaggedValue BuiltinsRegExp::GetAllFlagsInternal(JSThread *thread, JSHandle &thisObj) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); const GlobalEnvConstants *globalConstants = thread->GlobalConstants(); uint8_t *flagsStr = new uint8_t[RegExpParser::FLAG_NUM + 1]; // FLAG_NUM flags + '\0' if (flagsStr == nullptr) { LOG_ECMA(FATAL) << "BuiltinsRegExp::GetAllFlagsInternal:flagsStr is nullptr"; } size_t flagsLen = 0; JSHandle emptyString = factory->GetEmptyString(); JSHandle hasIndicesKey(factory->NewFromASCII("hasIndices")); JSHandle hasIndicesResult = JSObject::GetProperty(thread, thisObj, hasIndicesKey).GetValue(); RETURN_VALUE_IF_ABRUPT_COMPLETION_WITH_DATA_DELETE(thread, emptyString.GetTaggedValue(), flagsStr); if (hasIndicesResult->ToBoolean()) { flagsStr[flagsLen] = 'd'; flagsLen++; } JSHandle globalKey(globalConstants->GetHandledGlobalString()); JSHandle globalResult = JSObject::GetProperty(thread, thisObj, globalKey).GetValue(); RETURN_VALUE_IF_ABRUPT_COMPLETION_WITH_DATA_DELETE(thread, emptyString.GetTaggedValue(), flagsStr); if (globalResult->ToBoolean()) { flagsStr[flagsLen] = 'g'; flagsLen++; } JSHandle ignoreCaseKey(factory->NewFromASCII("ignoreCase")); JSHandle ignoreCaseResult = JSObject::GetProperty(thread, thisObj, ignoreCaseKey).GetValue(); RETURN_VALUE_IF_ABRUPT_COMPLETION_WITH_DATA_DELETE(thread, emptyString.GetTaggedValue(), flagsStr); if (ignoreCaseResult->ToBoolean()) { flagsStr[flagsLen] = 'i'; flagsLen++; } JSHandle multilineKey(factory->NewFromASCII("multiline")); JSHandle multilineResult = JSObject::GetProperty(thread, thisObj, multilineKey).GetValue(); RETURN_VALUE_IF_ABRUPT_COMPLETION_WITH_DATA_DELETE(thread, emptyString.GetTaggedValue(), flagsStr); if (multilineResult->ToBoolean()) { flagsStr[flagsLen] = 'm'; flagsLen++; } JSHandle dotAllKey(factory->NewFromASCII("dotAll")); JSHandle dotAllResult = JSObject::GetProperty(thread, thisObj, dotAllKey).GetValue(); RETURN_VALUE_IF_ABRUPT_COMPLETION_WITH_DATA_DELETE(thread, emptyString.GetTaggedValue(), flagsStr); if (dotAllResult->ToBoolean()) { flagsStr[flagsLen] = 's'; flagsLen++; } JSHandle unicodeKey(globalConstants->GetHandledUnicodeString()); JSHandle unicodeResult = JSObject::GetProperty(thread, thisObj, unicodeKey).GetValue(); RETURN_VALUE_IF_ABRUPT_COMPLETION_WITH_DATA_DELETE(thread, emptyString.GetTaggedValue(), flagsStr); if (unicodeResult->ToBoolean()) { flagsStr[flagsLen] = 'u'; flagsLen++; } JSHandle stickyKey(globalConstants->GetHandledStickyString()); JSHandle stickyResult = JSObject::GetProperty(thread, thisObj, stickyKey).GetValue(); RETURN_VALUE_IF_ABRUPT_COMPLETION_WITH_DATA_DELETE(thread, emptyString.GetTaggedValue(), flagsStr); if (stickyResult->ToBoolean()) { flagsStr[flagsLen] = 'y'; flagsLen++; } flagsStr[flagsLen] = '\0'; JSHandle flagsString = factory->NewFromUtf8(flagsStr, flagsLen); delete[] flagsStr; return flagsString.GetTaggedValue(); } // 20.2.5.4 JSTaggedValue BuiltinsRegExp::GetGlobal(EcmaRuntimeCallInfo *argv) { ASSERT(argv); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, RegExp, GetGlobal); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle thisObj = GetThis(argv); JSHandle constructor = GetConstructor(argv); return GetFlagsInternal(thread, thisObj, constructor, RegExpParser::FLAG_GLOBAL); } // 22.2.6.6 JSTaggedValue BuiltinsRegExp::GetHasIndices(EcmaRuntimeCallInfo *argv) { ASSERT(argv); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, RegExp, GetHasIndices); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle thisObj = GetThis(argv); JSHandle constructor = GetConstructor(argv); return GetFlagsInternal(thread, thisObj, constructor, RegExpParser::FLAG_HASINDICES); } // 20.2.5.5 JSTaggedValue BuiltinsRegExp::GetIgnoreCase(EcmaRuntimeCallInfo *argv) { ASSERT(argv); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, RegExp, GetIgnoreCase); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle thisObj = GetThis(argv); JSHandle constructor = GetConstructor(argv); return GetFlagsInternal(thread, thisObj, constructor, RegExpParser::FLAG_IGNORECASE); } // 20.2.5.7 JSTaggedValue BuiltinsRegExp::GetMultiline(EcmaRuntimeCallInfo *argv) { ASSERT(argv); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, RegExp, GetMultiline); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle thisObj = GetThis(argv); JSHandle constructor = GetConstructor(argv); return GetFlagsInternal(thread, thisObj, constructor, RegExpParser::FLAG_MULTILINE); } JSTaggedValue BuiltinsRegExp::GetDotAll(EcmaRuntimeCallInfo *argv) { ASSERT(argv); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, RegExp, GetDotAll); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle thisObj = GetThis(argv); JSHandle constructor = GetConstructor(argv); return GetFlagsInternal(thread, thisObj, constructor, RegExpParser::FLAG_DOTALL); } // 20.2.5.10 JSTaggedValue BuiltinsRegExp::GetSource(EcmaRuntimeCallInfo *argv) { ASSERT(argv); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, RegExp, GetSource); [[maybe_unused]] EcmaHandleScope handleScope(thread); // 1. Let R be the this value. JSHandle thisObj = GetThis(argv); // 2. If Type(R) is not Object, throw a TypeError exception. // 3. If R does not have an [[OriginalSource]] internal slot, throw a TypeError exception. // 4. If R does not have an [[OriginalFlags]] internal slot, throw a TypeError exception. if (!thisObj->IsECMAObject()) { // throw a TypeError exception. THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception()); } if (!thisObj->IsJSRegExp()) { // a. If SameValue(R, %RegExp.prototype%) is true, return "(?:)". const GlobalEnvConstants *globalConst = thread->GlobalConstants(); JSHandle constructorKey = globalConst->GetHandledConstructorString(); JSHandle objConstructor = JSTaggedValue::GetProperty(thread, thisObj, constructorKey).GetValue(); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue(false)); JSHandle constructor = GetConstructor(argv); if (objConstructor->IsJSFunction() && constructor->IsJSFunction()) { JSHandle objRealm = JSObject::GetFunctionRealm(thread, objConstructor); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle ctorRealm = JSObject::GetFunctionRealm(thread, constructor); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); if (objRealm->GetRegExpPrototype() == thisObj && *objRealm == *ctorRealm) { JSHandle result = thread->GetEcmaVM()->GetFactory()->NewFromASCII("(?:)"); return result.GetTaggedValue(); } } // b. throw a TypeError exception. THROW_TYPE_ERROR_AND_RETURN(thread, "this does not have [[OriginalSource]]", JSTaggedValue::Exception()); } // 5. Let src be the value of R’s [[OriginalSource]] internal slot. JSHandle regexpObj(thread, JSRegExp::Cast(thisObj->GetTaggedObject())); JSHandle source(thread, regexpObj->GetOriginalSource()); // 6. Let flags be the value of R’s [[OriginalFlags]] internal slot. uint8_t flagsBits = static_cast(regexpObj->GetOriginalFlags().GetInt()); JSHandle flags(thread, FlagsBitsToString(thread, flagsBits)); // 7. Return EscapeRegExpPattern(src, flags). return JSTaggedValue(EscapeRegExpPattern(thread, source, flags)); } // 20.2.5.12 JSTaggedValue BuiltinsRegExp::GetSticky(EcmaRuntimeCallInfo *argv) { ASSERT(argv); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, RegExp, GetSticky); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle thisObj = GetThis(argv); JSHandle constructor = GetConstructor(argv); return GetFlagsInternal(thread, thisObj, constructor, RegExpParser::FLAG_STICKY); } // 20.2.5.15 JSTaggedValue BuiltinsRegExp::GetUnicode(EcmaRuntimeCallInfo *argv) { ASSERT(argv); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, RegExp, GetUnicode); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle thisObj = GetThis(argv); JSHandle constructor = GetConstructor(argv); return GetFlagsInternal(thread, thisObj, constructor, RegExpParser::FLAG_UTF16); } // 21.2.4.2 JSTaggedValue BuiltinsRegExp::GetSpecies(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), RegExp, GetSpecies); return GetThis(argv).GetTaggedValue(); } // 21.2.5.6 JSTaggedValue BuiltinsRegExp::Match(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), RegExp, Match); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); // 1. Let rx be the this value. JSHandle thisObj = GetThis(argv); // 3. Let S be ToString(string) JSHandle inputString = GetCallArg(argv, 0); JSHandle stringHandle = JSTaggedValue::ToString(thread, inputString); // 4. ReturnIfAbrupt(string). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle string = JSHandle::Cast(stringHandle); if (!thisObj->IsECMAObject()) { // 2. If Type(rx) is not Object, throw a TypeError exception. THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception()); } bool isFastPath = IsFastRegExp(thread, thisObj); return RegExpMatch(thread, thisObj, string, isFastPath); } JSTaggedValue BuiltinsRegExp::RegExpMatch(JSThread *thread, const JSHandle regexp, const JSHandle string, bool isFastPath) { bool useCache = true; JSHandle undefined = thread->GlobalConstants()->GetHandledUndefined(); JSHandle cacheTable(thread->GetCurrentEcmaContext()->GetRegExpCache()); if (!isFastPath || cacheTable->GetLargeStrCount() == 0 || cacheTable->GetConflictCount() == 0) { useCache = false; } bool isGlobal = GetFlag(thread, regexp, RegExpParser::FLAG_GLOBAL, isFastPath); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 7. If global is false, then if (!isGlobal) { // a. Return RegExpExec(rx, S). if (isFastPath) { return RegExpBuiltinExec(thread, regexp, string, isFastPath, useCache); } else { return RegExpExec(thread, regexp, string, useCache); } } if (useCache) { uint32_t lastIndex = static_cast(GetLastIndex(thread, regexp, isFastPath)); JSTaggedValue cacheResult = cacheTable->FindCachedResult(thread, string, RegExpExecResultCache::MATCH_TYPE, regexp, JSTaggedValue(lastIndex), undefined); if (!cacheResult.IsUndefined()) { return cacheResult; } } bool fullUnicode = GetFlag(thread, regexp, RegExpParser::FLAG_UTF16, isFastPath); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // b. Let setStatus be Set(rx, "lastIndex", 0, true). SetLastIndex(thread, regexp, JSTaggedValue(0), isFastPath); // c. ReturnIfAbrupt(setStatus). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // d. Let A be ArrayCreate(0). JSHandle array(JSArray::ArrayCreate(thread, JSTaggedNumber(0), ArrayMode::LITERAL)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); TaggedArray *srcElements = TaggedArray::Cast(array->GetElements().GetTaggedObject()); JSMutableHandle elements(thread, srcElements); // e. Let n be 0. uint32_t resultNum = 0; uint32_t arrLen = 1; JSMutableHandle result(thread, JSTaggedValue::Undefined()); JSMutableHandle matchString(thread, JSTaggedValue::Undefined()); // f. Repeat, while (true) { if (isFastPath) { uint32_t lastIndex = static_cast(GetLastIndex(thread, regexp, isFastPath)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); result.Update(RegExpBuiltinExecWithoutResult(thread, regexp, string, isFastPath, lastIndex, false)); JSHandle globalTable(thread->GetCurrentEcmaContext()->GetRegExpGlobalResult()); uint32_t endIndex = static_cast(globalTable->GetEndOfCaptureIndex(0).GetInt()); if (result->IsNull()) { // 1. If n=0, return null. lastIndex = static_cast(GetLastIndex(thread, regexp, isFastPath)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); if (resultNum == 0) { RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, string, JSHandle(thread, JSTaggedValue::Null()), RegExpExecResultCache::MATCH_TYPE, 0, 0, undefined); return JSTaggedValue::Null(); } RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, string, JSHandle::Cast(array), RegExpExecResultCache::MATCH_TYPE, 0, 0, undefined); // 2. Else, return A. return array.GetTaggedValue(); } uint32_t startIndex = static_cast(globalTable->GetStartOfCaptureIndex(0).GetInt()); uint32_t len = endIndex - startIndex; matchString.Update(JSTaggedValue(EcmaStringAccessor::FastSubString( thread->GetEcmaVM(), JSHandle::Cast(string), startIndex, len))); } else { // i. Let result be RegExpExec(rx, S). result.Update(RegExpExec(thread, regexp, string, useCache)); // ii. ReturnIfAbrupt(result). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // iii. If result is null, then if (result->IsNull()) { // 1. If n=0, return null. if (resultNum == 0) { return JSTaggedValue::Null(); } // 2. Else, return A. return array.GetTaggedValue(); } // iv. Else result is not null, // 1. Let matchStr be ToString(Get(result, "0")). JSHandle zeroString = thread->GlobalConstants()->GetHandledZeroString(); JSTaggedValue matchVal = ObjectFastOperator::FastGetPropertyByValue( thread, result.GetTaggedValue(), zeroString.GetTaggedValue()); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle matchStr(thread, matchVal); matchString.Update(JSTaggedValue::ToString(thread, matchStr)); // 2. ReturnIfAbrupt(matchStr). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } JSHandle matchValue = JSHandle::Cast(matchString); // 3. Let status be CreateDataProperty(A, ToString(n), matchStr). if (arrLen > elements->GetLength()) { elements.Update(JSObject::GrowElementsCapacity(thread, JSHandle::Cast(array), elements->GetLength(), true)); } elements->Set(thread, arrLen - 1, matchValue); array->SetArrayLength(thread, arrLen); arrLen++; // 5. If matchStr is the empty String, then if (EcmaStringAccessor(matchString).GetLength() == 0) { int64_t lastIndex = GetLastIndex(thread, regexp, isFastPath); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // c. Let nextIndex be AdvanceStringIndex(S, thisIndex, fullUnicode). // d. Let setStatus be Set(rx, "lastIndex", nextIndex, true). JSTaggedValue nextIndex = JSTaggedValue(AdvanceStringIndex(string, lastIndex, fullUnicode)); SetLastIndex(thread, regexp, nextIndex, isFastPath); // e. ReturnIfAbrupt(setStatus). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } // 6. Increase n. resultNum++; } } JSTaggedValue BuiltinsRegExp::MatchAll(EcmaRuntimeCallInfo *argv) { ASSERT(argv); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, RegExp, MatchAll); [[maybe_unused]] EcmaHandleScope handleScope(thread); // 1. Let R be the this value. // 2. If Type(R) is not Object, throw a TypeError exception. JSHandle thisObj = GetThis(argv); if (!thisObj->IsECMAObject()) { THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception()); } // 3. Let S be ? ToString(string). JSHandle inputString = GetCallArg(argv, 0); JSHandle stringHandle = JSTaggedValue::ToString(thread, inputString); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); bool isFastPath = IsFastRegExp(thread, thisObj); return RegExpMatchAll(thread, thisObj, stringHandle, isFastPath); } JSTaggedValue BuiltinsRegExp::RegExpMatchAll(JSThread *thread, const JSHandle regexp, const JSHandle string, bool isFastPath) { JSMutableHandle matcher(thread, JSTaggedValue::Undefined()); bool global = false; bool fullUnicode = false; if (isFastPath) { JSHandle jsRegExp = JSHandle::Cast(regexp); JSHandle pattern(thread, jsRegExp->GetOriginalSource()); JSHandle flags(thread, jsRegExp->GetOriginalFlags()); matcher.Update(BuiltinsRegExp::RegExpCreate(thread, pattern, flags)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); SetLastIndex(thread, matcher, JSHandle::Cast(jsRegExp)->GetPropertyInlinedProps(LAST_INDEX_OFFSET), isFastPath); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); global = GetOriginalFlag(thread, matcher, RegExpParser::FLAG_GLOBAL); fullUnicode = GetOriginalFlag(thread, matcher, RegExpParser::FLAG_UTF16); } else { auto ecmaVm = thread->GetEcmaVM(); // 4. Let C be ? SpeciesConstructor(R, %RegExp%). JSHandle defaultConstructor = ecmaVm->GetGlobalEnv()->GetRegExpFunction(); JSHandle objHandle(regexp); JSHandle constructor = JSObject::SpeciesConstructor(thread, objHandle, defaultConstructor); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); const GlobalEnvConstants *globalConstants = thread->GlobalConstants(); // 5. Let flags be ? ToString(? Get(R, "flags")). JSHandle flagsString(globalConstants->GetHandledFlagsString()); JSHandle getFlags(JSObject::GetProperty(thread, regexp, flagsString).GetValue()); JSHandle flagsStrHandle = JSTaggedValue::ToString(thread, getFlags); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 6. Let matcher be ? Construct(C, « R, flags »). JSHandle undefined = globalConstants->GetHandledUndefined(); EcmaRuntimeCallInfo *runtimeInfo = EcmaInterpreter::NewRuntimeCallInfo(thread, constructor, undefined, undefined, 2); // 2: two args RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); runtimeInfo->SetCallArg(regexp.GetTaggedValue(), flagsStrHandle.GetTaggedValue()); JSTaggedValue taggedMatcher = JSFunction::Construct(runtimeInfo); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); matcher.Update(taggedMatcher); // 7. Let lastIndex be ? ToLength(? Get(R, "lastIndex")). JSHandle lastIndexString(globalConstants->GetHandledLastIndexString()); JSHandle getLastIndex(JSObject::GetProperty(thread, regexp, lastIndexString).GetValue()); JSTaggedNumber thisLastIndex = JSTaggedValue::ToLength(thread, getLastIndex); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 8. Perform ? Set(matcher, "lastIndex", lastIndex, true). ObjectFastOperator::FastSetPropertyByValue(thread, matcher.GetTaggedValue(), lastIndexString.GetTaggedValue(), thisLastIndex); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 9. If flags contains "g", let global be true. // 10. Else, let global be false. JSHandle gString(globalConstants->GetHandledGString()); if (EcmaStringAccessor::IndexOf(ecmaVm, flagsStrHandle, gString) != -1) { global = true; } // 11. If flags contains "u", let fullUnicode be true. // 12. Else, let fullUnicode be false. JSHandle uString(globalConstants->GetHandledUString()); if (EcmaStringAccessor::IndexOf(ecmaVm, flagsStrHandle, uString) != -1) { fullUnicode = true; } } // 13. Return ! CreateRegExpStringIterator(matcher, S, global, fullUnicode). return JSRegExpIterator::CreateRegExpStringIterator(thread, matcher, string, global, fullUnicode).GetTaggedValue(); } JSTaggedValue BuiltinsRegExp::RegExpReplaceFast(JSThread *thread, JSHandle regexp, JSHandle inputString, uint32_t inputLength) { ASSERT(regexp->IsJSRegExp()); BUILTINS_API_TRACE(thread, RegExp, RegExpReplaceFast); JSHandle regexpHandle(regexp); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); // get bytecode JSTaggedValue bufferData = JSRegExp::Cast(regexp->GetTaggedObject())->GetByteCodeBuffer(); void *dynBuf = JSNativePointer::Cast(bufferData.GetTaggedObject())->GetExternalPointer(); // get flags auto bytecodeBuffer = reinterpret_cast(dynBuf); uint32_t flags = *reinterpret_cast(bytecodeBuffer + RegExpParser::FLAGS_OFFSET); JSHandle cacheTable(thread->GetCurrentEcmaContext()->GetRegExpCache()); JSHandle tagInputString = JSHandle::Cast(inputString); bool useCache = false; uint32_t lastIndex = 0; GetLastIndex(thread, regexp, lastIndex); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); auto globalConst = thread->GlobalConstants(); JSHandle pattern(thread, regexpHandle->GetOriginalSource()); JSHandle flagsBits(thread, regexpHandle->GetOriginalFlags()); useCache = ShouldUseCache(thread, inputString); uint32_t lastIndexInput = lastIndex; JSHandle emptyString(thread, globalConst->GetEmptyString()); if (useCache) { JSTaggedValue cacheResult = cacheTable->FindCachedResult(thread, tagInputString, RegExpExecResultCache::REPLACE_TYPE, regexp, JSTaggedValue(lastIndexInput), emptyString); if (!cacheResult.IsUndefined()) { return cacheResult; } } std::string resultString; MatchAndReplace(thread, regexp, inputString, flags, lastIndex, inputLength, resultString); auto resultValue = factory->NewFromStdString(resultString); if (useCache) { RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, tagInputString, JSHandle(resultValue), RegExpExecResultCache::REPLACE_TYPE, lastIndexInput, lastIndex, emptyString); } return resultValue.GetTaggedValue(); } JSTaggedValue BuiltinsRegExp::GetLastIndex(JSThread *thread, JSHandle regexp, uint32_t &lastIndex) { JSTaggedValue bufferData = JSRegExp::Cast(regexp->GetTaggedObject())->GetByteCodeBuffer(); void *dynBuf = JSNativePointer::Cast(bufferData.GetTaggedObject())->GetExternalPointer(); auto bytecodeBuffer = reinterpret_cast(dynBuf); uint32_t flags = *reinterpret_cast(bytecodeBuffer + RegExpParser::FLAGS_OFFSET); JSHandle lastIndexHandle(thread->GlobalConstants()->GetHandledLastIndexString()); if ((flags & (RegExpParser::FLAG_STICKY | RegExpParser::FLAG_GLOBAL)) == 0) { lastIndex = 0; } else { JSTaggedValue thisIndex = ObjectFastOperator::FastGetPropertyByValue(thread, regexp.GetTaggedValue(), lastIndexHandle.GetTaggedValue()); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); if (thisIndex.IsInt()) { lastIndex = static_cast(thisIndex.GetInt()); } else { JSHandle thisIndexHandle(thread, thisIndex); auto lengthValue = JSTaggedValue::ToLength(thread, thisIndexHandle); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); lastIndex = lengthValue.GetNumber(); } } return JSTaggedValue::Undefined(); } bool BuiltinsRegExp::ShouldUseCache(JSThread *thread, JSHandle inputString) { JSHandle cacheTable(thread->GetCurrentEcmaContext()->GetRegExpCache()); uint32_t length = EcmaStringAccessor(inputString).GetLength(); uint32_t largeStrCount = cacheTable->GetLargeStrCount(); if (largeStrCount != 0) { if (length > MIN_REPLACE_STRING_LENGTH) { cacheTable->SetLargeStrCount(thread, --largeStrCount); } } else { cacheTable->SetStrLenThreshold(thread, MIN_REPLACE_STRING_LENGTH); } return length > cacheTable->GetStrLenThreshold(); } JSTaggedValue BuiltinsRegExp::MatchAndReplace(JSThread *thread, JSHandle regexp, JSHandle inputString, uint32_t &flags, uint32_t lastIndex, uint32_t inputLength, std::string &resultString) { uint32_t nextPosition = 0; JSHandle globalTable(thread->GetCurrentEcmaContext()->GetRegExpGlobalResult()); JSHandle lastIndexHandle(thread->GlobalConstants()->GetHandledLastIndexString()); JSHandle tagInputString = JSHandle::Cast(inputString); // 12. Let done be false. // 13. Repeat, while done is false for (;;) { if (lastIndex > inputLength) { break; } bool matchResult = RegExpExecInternal(thread, regexp, inputString, lastIndex); if (!matchResult) { if (flags & (RegExpParser::FLAG_STICKY | RegExpParser::FLAG_GLOBAL)) { lastIndex = 0; ObjectFastOperator::FastSetPropertyByValue(thread, regexp.GetTaggedValue(), lastIndexHandle.GetTaggedValue(), JSTaggedValue(0)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } break; } uint32_t startIndex = static_cast(globalTable->GetStartOfCaptureIndex(0).GetInt()); uint32_t endIndex = static_cast(globalTable->GetEndIndex().GetInt()); lastIndex = endIndex; if (nextPosition < startIndex) { auto substr = EcmaStringAccessor::FastSubString( thread->GetEcmaVM(), inputString, nextPosition, startIndex - nextPosition); resultString += EcmaStringAccessor(substr).ToStdString(StringConvertedUsage::LOGICOPERATION); } nextPosition = endIndex; if (!(flags & RegExpParser::FLAG_GLOBAL)) { // a. Let setStatus be Set(R, "lastIndex", e, true). ObjectFastOperator::FastSetPropertyByValue(thread, regexp.GetTaggedValue(), lastIndexHandle.GetTaggedValue(), JSTaggedValue(lastIndex)); // b. ReturnIfAbrupt(setStatus). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); break; } if (endIndex == startIndex) { bool unicode = EcmaStringAccessor(inputString).IsUtf16() && (flags & RegExpParser::FLAG_UTF16); endIndex = static_cast(AdvanceStringIndex(tagInputString, endIndex, unicode)); } lastIndex = endIndex; } auto substr = EcmaStringAccessor::FastSubString( thread->GetEcmaVM(), inputString, nextPosition, inputLength - nextPosition); resultString += EcmaStringAccessor(substr).ToStdString(StringConvertedUsage::LOGICOPERATION); return JSTaggedValue::Undefined(); } // 21.2.5.8 // NOLINTNEXTLINE(readability-function-size) JSTaggedValue BuiltinsRegExp::Replace(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), RegExp, Replace); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); // 1. Let rx be the this value. JSHandle thisObj = GetThis(argv); if (!thisObj->IsECMAObject()) { // 2. If Type(rx) is not Object, throw a TypeError exception. THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception()); } // 3. Let S be ToString(string). JSHandle string = GetCallArg(argv, 0); JSHandle inputReplaceValue = GetCallArg(argv, 1); return ReplaceInternal(thread, thisObj, string, inputReplaceValue); } JSTaggedValue BuiltinsRegExp::ReplaceInternal(JSThread *thread, JSHandle thisObj, JSHandle string, JSHandle inputReplaceValue) { JSHandle srcString = JSTaggedValue::ToString(thread, string); const GlobalEnvConstants *globalConst = thread->GlobalConstants(); // 4. ReturnIfAbrupt(S). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle inputStr = JSHandle::Cast(srcString); // 5. Let lengthS be the number of code unit elements in S. uint32_t length = EcmaStringAccessor(srcString).GetLength(); // 6. Let functionalReplace be IsCallable(replaceValue). bool functionalReplace = inputReplaceValue->IsCallable(); JSHandle replaceValueHandle; // Add cache for regexp replace bool useCache = true; if (!functionalReplace) { replaceValueHandle = JSTaggedValue::ToString(thread, inputReplaceValue); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } else { useCache = false; } // 8. Let global be ToBoolean(Get(rx, "global")). ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); bool isGlobal = false; bool fullUnicode = false; bool isFastPath = IsFastRegExp(thread, thisObj); if (isFastPath) { isGlobal = GetOriginalFlag(thread, thisObj, RegExpParser::FLAG_GLOBAL); fullUnicode = GetOriginalFlag(thread, thisObj, RegExpParser::FLAG_UTF16); if (isGlobal) { SetLastIndex(thread, thisObj, JSTaggedValue(0), isFastPath); } } else { // 9. ReturnIfAbrupt(global). useCache = false; isGlobal = GetFlag(thread, thisObj, RegExpParser::FLAG_GLOBAL, isFastPath); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 10. If global is true, then if (isGlobal) { // a. Let fullUnicode be ToBoolean(Get(rx, "unicode")). fullUnicode = GetFlag(thread, thisObj, RegExpParser::FLAG_UTF16, isFastPath); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // b. Let setStatus be Set(rx, "lastIndex", 0, true). SetLastIndex(thread, thisObj, JSTaggedValue(0), isFastPath); // c. ReturnIfAbrupt(setStatus). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } } // Add cache for the intermediate result of replace bool useIntermediateCache = false; JSHandle undefined = thread->GlobalConstants()->GetHandledUndefined(); JSHandle cacheTable(thread->GetCurrentEcmaContext()->GetRegExpCache()); if (isFastPath) { if (isGlobal && !functionalReplace && EcmaStringAccessor(replaceValueHandle).GetLength() == 0) { return RegExpReplaceFast(thread, thisObj, srcString, length); } JSHandle regexpHandle(thisObj); useIntermediateCache = true; if (!functionalReplace) { uint32_t strLength = EcmaStringAccessor(replaceValueHandle).GetLength(); uint32_t largeStrCount = cacheTable->GetLargeStrCount(); if (largeStrCount != 0) { if (strLength > MIN_REPLACE_STRING_LENGTH) { cacheTable->SetLargeStrCount(thread, --largeStrCount); } } else { cacheTable->SetStrLenThreshold(thread, MIN_REPLACE_STRING_LENGTH); } if (strLength > cacheTable->GetStrLenThreshold()) { uint32_t lastIndexInput = static_cast(GetLastIndex(thread, thisObj, isFastPath)); JSTaggedValue cacheResult = cacheTable->FindCachedResult(thread, string, RegExpExecResultCache::REPLACE_TYPE, thisObj, JSTaggedValue(lastIndexInput), inputReplaceValue); if (!cacheResult.IsUndefined()) { return cacheResult; } } } } // 11. Let results be a new empty List. JSMutableHandle resultsList(thread, JSArray::ArrayCreate(thread, JSTaggedNumber(0))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); int resultsIndex = 0; JSMutableHandle nextIndexHandle(thread, JSTaggedValue(0)); JSMutableHandle execResult(thread, JSTaggedValue(0)); // Add cache for the intermediate result of replace JSTaggedValue cachedResultsList(JSTaggedValue::VALUE_UNDEFINED); if (useIntermediateCache) { uint32_t lastIndexInput = static_cast(GetLastIndex(thread, thisObj, isFastPath)); cachedResultsList = cacheTable->FindCachedResult(thread, string, RegExpExecResultCache::INTERMEDIATE_REPLACE_TYPE, thisObj, JSTaggedValue(lastIndexInput), undefined, true); } if (!cachedResultsList.IsUndefined()) { resultsList.Update(cachedResultsList); resultsIndex = static_cast(JSArray::Cast(resultsList.GetTaggedValue())->GetArrayLength()); } else { // 12. Let done be false. // 13. Repeat, while done is false for (;;) { // a. Let result be RegExpExec(rx, S). execResult.Update(RegExpExec(thread, thisObj, inputStr, useCache, true)); // b. ReturnIfAbrupt(result). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // c. If result is null, set done to true. if (execResult->IsNull()) { break; } // d. Else result is not null, i. Append result to the end of results. TaggedArray *srcElements = TaggedArray::Cast(resultsList->GetElements().GetTaggedObject()); JSMutableHandle elements(thread, srcElements); if (resultsIndex >= static_cast(elements->GetLength())) { elements.Update(JSObject::GrowElementsCapacity(thread, resultsList, elements->GetLength(), true)); } elements->Set(thread, resultsIndex, execResult); JSArray::Cast(*resultsList)->SetArrayLength(thread, resultsIndex + 1); resultsIndex++; // ii. If global is false, set done to true. if (!isGlobal) { break; } // iii. Else, 1. Let matchStr be ToString(Get(result, "0")). JSHandle matchedStr = globalConst->GetHandledZeroString(); JSTaggedValue getMatchVal = ObjectFastOperator::FastGetPropertyByValue( thread, execResult.GetTaggedValue(), matchedStr.GetTaggedValue()); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle getMatch(thread, getMatchVal); JSHandle matchString = JSTaggedValue::ToString(thread, getMatch); // 2. ReturnIfAbrupt(matchStr). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 3. If matchStr is the empty String, then if (EcmaStringAccessor(matchString).GetLength() == 0) { // a. Let thisIndex be ToLength(Get(rx, "lastIndex")). uint32_t thisIndex = static_cast(GetLastIndex(thread, thisObj, isFastPath)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // c. Let nextIndex be AdvanceStringIndex(S, thisIndex, fullUnicode). uint32_t nextIndex = static_cast(AdvanceStringIndex(inputStr, thisIndex, fullUnicode)); nextIndexHandle.Update(JSTaggedValue(nextIndex)); // d. Let setStatus be Set(rx, "lastIndex", nextIndex, true). SetLastIndex(thread, thisObj, nextIndexHandle.GetTaggedValue(), isFastPath); // e. ReturnIfAbrupt(setStatus). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } } if (useIntermediateCache) { RegExpExecResultCache::AddResultInCache(thread, cacheTable, thisObj, string, JSHandle(resultsList), RegExpExecResultCache::INTERMEDIATE_REPLACE_TYPE, 0, 0, undefined, true); } } // 14. Let accumulatedResult be the empty String value. bool isUtf8 = true; uint32_t resultStrLength = 0; uint32_t resultArrayLength = (static_cast(resultsIndex) + 1) * 2; JSHandle resultArray = factory->NewTaggedArray(resultArrayLength); std::vector resultLengthArray(resultArrayLength); // 15. Let nextSourcePosition be 0. uint32_t nextSourcePosition = 0; JSMutableHandle getMatchString(thread, JSTaggedValue::Undefined()); JSMutableHandle resultValues(thread, JSTaggedValue(0)); JSMutableHandle ncapturesHandle(thread, JSTaggedValue(0)); JSMutableHandle capN(thread, JSTaggedValue(0)); // 16. Repeat, for each result in results, for (int i = 0; i < resultsIndex; i++) { resultValues.Update(ElementAccessor::Get(resultsList, i)); // a. Let nCaptures be ToLength(Get(result, "length")). uint32_t ncaptures; if (isFastPath) { ncaptures = static_cast(JSArray::Cast(resultValues.GetTaggedValue())->GetArrayLength()); } else { JSHandle lengthHandle = globalConst->GetHandledLengthString(); ncapturesHandle.Update(ObjectFastOperator::FastGetPropertyByValue( thread, resultValues.GetTaggedValue(), lengthHandle.GetTaggedValue())); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); ncaptures = JSTaggedValue::ToUint32(thread, ncapturesHandle); } // b. ReturnIfAbrupt(nCaptures). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // c. Let nCaptures be max(nCaptures − 1, 0). ncaptures = (ncaptures == 0) ? 0 : ncaptures - 1; // d. Let matched be ToString(Get(result, "0")). JSTaggedValue value = ObjectFastOperator::GetPropertyByIndex(thread, resultValues.GetTaggedValue(), 0); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); getMatchString.Update(value); JSHandle matchString = JSTaggedValue::ToString(thread, getMatchString); // e. ReturnIfAbrupt(matched). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // f. Let matchLength be the number of code units in matched. uint32_t matchLength = EcmaStringAccessor(matchString).GetLength(); // g. Let position be ToInteger(Get(result, "index")). JSTaggedValue positionTag = GetExecResultIndex(thread, resultValues, isFastPath); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle positionHandle(thread, positionTag); uint32_t position = 0; if (positionHandle->IsInt()) { position = static_cast(positionHandle->GetInt()); } else { position = JSTaggedValue::ToUint32(thread, positionHandle); // h. ReturnIfAbrupt(position). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } // i. Let position be max(min(position, lengthS), 0). position = std::max(std::min(position, length), 0); // j. Let n be 1. uint32_t index = 1; // k. Let captures be an empty List. JSHandle capturesList = factory->NewTaggedArray(ncaptures); // l. Repeat while n ≤ nCaptures while (index <= ncaptures) { // i. Let capN be Get(result, ToString(n)). capN.Update(ObjectFastOperator::FastGetPropertyByIndex(thread, resultValues.GetTaggedValue(), index)); // ii. ReturnIfAbrupt(capN). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // iii. If capN is not undefined, then if (!capN->IsUndefined()) { // 1. Let capN be ToString(capN). JSHandle capNStr = JSTaggedValue::ToString(thread, capN); // 2. ReturnIfAbrupt(capN). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle capnStr = JSHandle::Cast(capNStr); capturesList->Set(thread, index - 1, capnStr); } else { // iv. Append capN as the last element of captures. capturesList->Set(thread, index - 1, capN); } // v. Let n be n+1 ++index; } // j. Let namedCaptures be ? Get(result, "groups"). JSTaggedValue named = GetExecResultGroups(thread, resultValues, isFastPath); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle namedCaptures(thread, named); // m. If functionalReplace is true, then JSMutableHandle replacementString(thread, factory->GetEmptyString()); int emptyArrLength = 0; if (namedCaptures->IsUndefined()) { emptyArrLength = 3; // 3: «matched, pos, and string» } else { emptyArrLength = 4; // 4: «matched, pos, string, and groups» } JSHandle replacerArgs = factory->NewTaggedArray(emptyArrLength + capturesList->GetLength()); if (functionalReplace) { // i. Let replacerArgs be «matched». replacerArgs->Set(thread, 0, getMatchString.GetTaggedValue()); // ii. Append in list order the elements of captures to the end of the List replacerArgs. // iii. Append position and S as the last two elements of replacerArgs. index = 0; while (index < capturesList->GetLength()) { replacerArgs->Set(thread, index + 1, capturesList->Get(index)); ++index; } replacerArgs->Set(thread, index + 1, JSTaggedValue(position)); replacerArgs->Set(thread, index + 2, inputStr.GetTaggedValue()); // 2: position of string if (!namedCaptures->IsUndefined()) { replacerArgs->Set(thread, index + 3, namedCaptures.GetTaggedValue()); // 3: position of groups } // iv. Let replValue be Call(replaceValue, undefined, replacerArgs). const uint32_t argsLength = replacerArgs->GetLength(); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, inputReplaceValue, undefined, undefined, argsLength); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); info->SetCallArg(argsLength, replacerArgs); JSTaggedValue replaceResult = JSFunction::Call(info); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle replValue(thread, replaceResult); // v. Let replacement be ToString(replValue). replacementString.Update(JSTaggedValue::ToString(thread, replValue)); // o. ReturnIfAbrupt(replacement). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } else { // n. Else, if (!namedCaptures->IsUndefined()) { JSHandle namedCapturesObj = JSTaggedValue::ToObject(thread, namedCaptures); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); namedCaptures = JSHandle::Cast(namedCapturesObj); } replacementString.Update(BuiltinsString::GetSubstitution(thread, matchString, srcString, position, capturesList, namedCaptures, replaceValueHandle)); } // p. If position ≥ nextSourcePosition, then if (position >= nextSourcePosition) { // ii. Let accumulatedResult be the String formed by concatenating the code units of the current value // of accumulatedResult with the substring of S consisting of the code units from nextSourcePosition // (inclusive) up to position (exclusive) and with the code units of replacement. // store undefined in resultArray resultArray->Set(thread, REPLACE_RESULT_VAL * i, JSTaggedValue::Undefined()); uint64_t bits = 0; bits |= ReplaceLengthField::Encode(position - nextSourcePosition); bits |= ReplacePositionField::Encode(nextSourcePosition); // store position and length bits in resultLengthArray resultLengthArray[REPLACE_RESULT_VAL * i] = bits; resultStrLength += (position - nextSourcePosition); auto subString = EcmaStringAccessor::FastSubString( thread->GetEcmaVM(), srcString, nextSourcePosition, position - nextSourcePosition); isUtf8 &= EcmaStringAccessor(subString).IsUtf8(); // store replacement string in resultArray resultArray->Set(thread, REPLACE_RESULT_VAL * i + 1, replacementString.GetTaggedValue()); uint32_t replacementLength = EcmaStringAccessor(replacementString).GetLength(); // store length of replacement string in resultLengthArray resultLengthArray[REPLACE_RESULT_VAL * i + 1] = static_cast(replacementLength); resultStrLength += replacementLength; isUtf8 &= EcmaStringAccessor(replacementString).IsUtf8(); // iii. Let nextSourcePosition be position + matchLength. nextSourcePosition = position + matchLength; } } // 17. If nextSourcePosition ≥ lengthS, return accumulatedResult. if (nextSourcePosition < length) { // store undefined in resultArray resultArray->Set(thread, REPLACE_RESULT_VAL * resultsIndex, JSTaggedValue::Undefined()); uint64_t bits = 0; bits |= ReplaceLengthField::Encode(length - nextSourcePosition); bits |= ReplacePositionField::Encode(nextSourcePosition); auto subStringEnd = EcmaStringAccessor::FastSubString( thread->GetEcmaVM(), srcString, nextSourcePosition, length - nextSourcePosition); isUtf8 &= EcmaStringAccessor(subStringEnd).IsUtf8(); // store position and length bits in resultLengthArray resultLengthArray[REPLACE_RESULT_VAL * resultsIndex] = bits; resultStrLength += (length - nextSourcePosition); } JSHandle result = CreateStringFromResultArray(thread, resultArray, resultLengthArray, srcString, resultStrLength, isUtf8); // 18. Return the String formed by concatenating the code units of accumulatedResult with the substring of S // consisting of the code units from nextSourcePosition (inclusive) up through the final code unit of S(inclusive). if (useCache) { RegExpExecResultCache::AddResultInCache(thread, cacheTable, thisObj, string, JSHandle(result), RegExpExecResultCache::REPLACE_TYPE, 0, nextIndexHandle->GetInt(), inputReplaceValue); } return result.GetTaggedValue(); } // 21.2.5.9 JSTaggedValue BuiltinsRegExp::Search(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), RegExp, Search); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); // 1. Let rx be the this value. JSHandle thisObj = GetThis(argv); // 3. Let S be ToString(string). JSHandle inputStr = GetCallArg(argv, 0); JSHandle stringHandle = JSTaggedValue::ToString(thread, inputStr); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle string = JSHandle::Cast(stringHandle); if (!thisObj->IsECMAObject()) { // 2. If Type(rx) is not Object, throw a TypeError exception. THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception()); } return RegExpSearch(thread, thisObj, string); } JSTaggedValue BuiltinsRegExp::RegExpSearch(JSThread *thread, const JSHandle regexp, const JSHandle string) { bool isFastPath = IsFastRegExp(thread, regexp); if (isFastPath) { return RegExpSearchFast(thread, regexp, string); } // 4. Let previousLastIndex be ? Get(rx, "lastIndex"). JSHandle lastIndexString(thread->GlobalConstants()->GetHandledLastIndexString()); JSHandle previousLastIndex = JSObject::GetProperty(thread, regexp, lastIndexString).GetValue(); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 5. If SameValue(previousLastIndex, 0) is false, then // Perform ? Set(rx, "lastIndex", 0, true). if (!JSTaggedValue::SameValue(previousLastIndex.GetTaggedValue(), JSTaggedValue(0))) { JSHandle value(thread, JSTaggedValue(0)); JSObject::SetProperty(thread, regexp, lastIndexString, value, true); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } // 6. Let result be ? RegExpExec(rx, S). JSHandle result(thread, RegExpExec(thread, regexp, string, false)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 7. Let currentLastIndex be ? Get(rx, "lastIndex"). JSHandle currentLastIndex = JSObject::GetProperty(thread, regexp, lastIndexString).GetValue(); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 8. If SameValue(currentLastIndex, previousLastIndex) is false, then // Perform ? Set(rx, "lastIndex", previousLastIndex, true). if (!JSTaggedValue::SameValue(previousLastIndex.GetTaggedValue(), currentLastIndex.GetTaggedValue())) { JSObject::SetProperty(thread, regexp, lastIndexString, previousLastIndex, true); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } // 9. If result is null, return -1. if (result->IsNull()) { return JSTaggedValue(-1); } // 10. Return ? Get(result, "index"). JSHandle index(thread->GlobalConstants()->GetHandledIndexString()); return JSObject::GetProperty(thread, result, index).GetValue().GetTaggedValue(); } JSTaggedValue BuiltinsRegExp::RegExpSearchFast(JSThread *thread, const JSHandle regexp, const JSHandle string) { JSHandle cacheTable(thread->GetCurrentEcmaContext()->GetRegExpCache()); JSHandle undefined = thread->GlobalConstants()->GetHandledUndefined(); JSTaggedValue cacheResult = cacheTable->FindCachedResult(thread, string, RegExpExecResultCache::SEARCH_TYPE, regexp, JSTaggedValue(0), undefined); if (!cacheResult.IsUndefined()) { return cacheResult; } JSHandle stringHandle = JSHandle::Cast(string); bool matchResult = RegExpExecInternal(thread, regexp, stringHandle, 0); if (!matchResult) { RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, string, JSHandle(thread, JSTaggedValue(-1)), RegExpExecResultCache::SEARCH_TYPE, 0, 0, undefined); return JSTaggedValue(-1); } JSHandle globalTable(thread->GetCurrentEcmaContext()->GetRegExpGlobalResult()); JSTaggedValue result = globalTable->GetStartOfCaptureIndex(0); RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, string, JSHandle(thread, JSTaggedValue(result)), RegExpExecResultCache::SEARCH_TYPE, 0, 0, undefined); return result; } JSTaggedValue BuiltinsRegExp::RegExpSplit(JSThread *thread, const JSHandle regexp, JSHandle jsString, JSHandle limit, bool isFastPath) { bool useCache = false; if (isFastPath) { if (limit->IsUndefined()) { useCache = true; return RegExpSplitFast(thread, regexp, jsString, MAX_SPLIT_LIMIT, useCache); } else if (limit->IsInt()) { int64_t lim = limit->GetInt(); if (lim >= 0) { return RegExpSplitFast(thread, regexp, jsString, static_cast(lim), useCache); } } } auto ecmaVm = thread->GetEcmaVM(); // 5. Let C be SpeciesConstructor(rx, %RegExp%). JSHandle defaultConstructor = ecmaVm->GetGlobalEnv()->GetRegExpFunction(); JSHandle objHandle(regexp); JSHandle constructor = JSObject::SpeciesConstructor(thread, objHandle, defaultConstructor); // 6. ReturnIfAbrupt(C). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 7. Let flags be ToString(Get(rx, "flags")). ObjectFactory *factory = ecmaVm->GetFactory(); const GlobalEnvConstants *globalConstants = thread->GlobalConstants(); JSHandle flagsString(globalConstants->GetHandledFlagsString()); JSHandle taggedFlags = JSObject::GetProperty(thread, regexp, flagsString).GetValue(); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle flags; if (taggedFlags->IsUndefined()) { flags = factory->GetEmptyString(); } else { flags = JSTaggedValue::ToString(thread, taggedFlags); } // 8. ReturnIfAbrupt(flags). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 9. If flags contains "u", let unicodeMatching be true. // 10. Else, let unicodeMatching be false. JSHandle uStringHandle(globalConstants->GetHandledUString()); bool unicodeMatching = (EcmaStringAccessor::IndexOf(ecmaVm, flags, uStringHandle) != -1); // 11. If flags contains "y", let newFlags be flags. JSHandle newFlagsHandle; JSHandle yStringHandle = JSHandle::Cast(globalConstants->GetHandledYString()); if (EcmaStringAccessor::IndexOf(ecmaVm, flags, yStringHandle) != -1) { newFlagsHandle = flags; } else { // 12. Else, let newFlags be the string that is the concatenation of flags and "y". JSHandle yStr = JSHandle::Cast(globalConstants->GetHandledYString()); newFlagsHandle = factory->ConcatFromString(flags, yStr); } // 13. Let splitter be Construct(C, «rx, newFlags»). JSHandle globalObject(thread, thread->GetEcmaVM()->GetGlobalEnv()->GetGlobalObject()); JSHandle undefined = globalConstants->GetHandledUndefined(); EcmaRuntimeCallInfo *runtimeInfo = EcmaInterpreter::NewRuntimeCallInfo(thread, constructor, undefined, undefined, 2); // 2: two args RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); runtimeInfo->SetCallArg(regexp.GetTaggedValue(), newFlagsHandle.GetTaggedValue()); JSTaggedValue taggedSplitter = JSFunction::Construct(runtimeInfo); // 14. ReturnIfAbrupt(splitter). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle splitter(thread, taggedSplitter); // 15. Let A be ArrayCreate(0). JSHandle array(JSArray::ArrayCreate(thread, JSTaggedNumber(0))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 16. Let lengthA be 0. uint32_t aLength = 0; // 17. If limit is undefined, let lim be 2^32–1; else let lim be ToUint32(limit). uint32_t lim; if (limit->IsUndefined()) { lim = MAX_SPLIT_LIMIT; } else { lim = JSTaggedValue::ToUint32(thread, limit); // 18. ReturnIfAbrupt(lim). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } // 19. Let size be the number of elements in S. uint32_t size = EcmaStringAccessor(jsString->GetTaggedObject()).GetLength(); // 20. Let p be 0. uint32_t startIndex = 0; // 21. If lim = 0, return A. if (lim == 0) { return JSTaggedValue(static_cast(array.GetTaggedValue().GetTaggedObject())); } // 22. If size = 0, then if (size == 0) { // a. Let z be RegExpExec(splitter, S). JSHandle execResult(thread, RegExpExec(thread, splitter, jsString, useCache)); // b. ReturnIfAbrupt(z). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // c. If z is not null, return A. if (!execResult->IsNull()) { return JSTaggedValue(static_cast(array.GetTaggedValue().GetTaggedObject())); } // d. Assert: The following call will never result in an abrupt completion. // e. Perform CreateDataProperty(A, "0", S). JSObject::CreateDataProperty(thread, array, 0, jsString); // f. Return A. return JSTaggedValue(static_cast(array.GetTaggedValue().GetTaggedObject())); } // 23. Let q be p. uint32_t endIndex = startIndex; JSMutableHandle lastIndexvalue(thread, JSTaggedValue(endIndex)); // 24. Repeat, while q < size JSHandle lastIndexString = globalConstants->GetHandledLastIndexString(); while (endIndex < size) { // a. Let setStatus be Set(splitter, "lastIndex", q, true). lastIndexvalue.Update(JSTaggedValue(endIndex)); JSObject::SetProperty(thread, splitter, lastIndexString, lastIndexvalue, true); // b. ReturnIfAbrupt(setStatus). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle execResult(thread, RegExpExec(thread, splitter, jsString, useCache)); // d. ReturnIfAbrupt(z). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // e. If z is null, let q be AdvanceStringIndex(S, q, unicodeMatching). if (execResult->IsNull()) { endIndex = static_cast(AdvanceStringIndex(jsString, endIndex, unicodeMatching)); } else { // f. Else z is not null, // i. Let e be ToLength(Get(splitter, "lastIndex")). JSHandle lastIndexHandle = JSObject::GetProperty(thread, splitter, lastIndexString).GetValue(); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSTaggedNumber lastIndexNumber = JSTaggedValue::ToLength(thread, lastIndexHandle); // ii. ReturnIfAbrupt(e). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); uint32_t lastIndex = lastIndexNumber.GetNumber(); // iii. If e = p, let q be AdvanceStringIndex(S, q, unicodeMatching). if (lastIndex == startIndex) { endIndex = static_cast(AdvanceStringIndex(jsString, endIndex, unicodeMatching)); } else { // iv. Else e != p, // 1. Let T be a String value equal to the substring of S consisting of the elements at indices p // (inclusive) through q (exclusive). auto substr = EcmaStringAccessor::FastSubString(thread->GetEcmaVM(), JSHandle::Cast(jsString), startIndex, endIndex - startIndex); std::string stdStrT = EcmaStringAccessor(substr).ToStdString(StringConvertedUsage::LOGICOPERATION); // 2. Assert: The following call will never result in an abrupt completion. // 3. Perform CreateDataProperty(A, ToString(lengthA), T). JSHandle tValue(factory->NewFromStdString(stdStrT)); JSObject::CreateDataProperty(thread, array, aLength, tValue); // 4. Let lengthA be lengthA +1. ++aLength; // 5. If lengthA = lim, return A. if (aLength == lim) { return array.GetTaggedValue(); } // 6. Let p be e. startIndex = lastIndex; // 7. Let numberOfCaptures be ToLength(Get(z, "length")). JSHandle lengthString(thread->GlobalConstants()->GetHandledLengthString()); JSHandle capturesHandle = JSObject::GetProperty(thread, execResult, lengthString).GetValue(); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSTaggedNumber numberOfCapturesNumber = JSTaggedValue::ToLength(thread, capturesHandle); // 8. ReturnIfAbrupt(numberOfCaptures). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); uint32_t numberOfCaptures = numberOfCapturesNumber.GetNumber(); // 9. Let numberOfCaptures be max(numberOfCaptures-1, 0). numberOfCaptures = (numberOfCaptures == 0) ? 0 : numberOfCaptures - 1; // 10. Let i be 1. uint32_t i = 1; // 11. Repeat, while i ≤ numberOfCaptures. while (i <= numberOfCaptures) { // a. Let nextCapture be Get(z, ToString(i)). JSHandle nextCapture = JSObject::GetProperty(thread, execResult, i).GetValue(); // b. ReturnIfAbrupt(nextCapture). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // c. Perform CreateDataProperty(A, ToString(lengthA), nextCapture). JSObject::CreateDataProperty(thread, array, aLength, nextCapture); // d. Let i be i + 1. ++i; // e. Let lengthA be lengthA +1. ++aLength; // f. If lengthA = lim, return A. if (aLength == lim) { return array.GetTaggedValue(); } } // 12. Let q be p. endIndex = startIndex; } } } // 25. Let T be a String value equal to the substring of S consisting of the elements at indices p (inclusive) // through size (exclusive). auto substr = EcmaStringAccessor::FastSubString(thread->GetEcmaVM(), JSHandle::Cast(jsString), startIndex, size - startIndex); std::string stdStrT = EcmaStringAccessor(substr).ToStdString(StringConvertedUsage::LOGICOPERATION); // 26. Assert: The following call will never result in an abrupt completion. // 27. Perform CreateDataProperty(A, ToString(lengthA), t). JSHandle tValue(factory->NewFromStdString(stdStrT)); JSObject::CreateDataProperty(thread, array, aLength, tValue); // 28. Return A. return array.GetTaggedValue(); } // 21.2.5.11 // NOLINTNEXTLINE(readability-function-size) JSTaggedValue BuiltinsRegExp::Split(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), RegExp, Split); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); // 1. Let rx be the this value. JSHandle thisObj = GetThis(argv); // 3. Let S be ToString(string). JSHandle inputString = GetCallArg(argv, 0); JSHandle limit = GetCallArg(argv, 1); JSHandle stringHandle = JSTaggedValue::ToString(thread, inputString); // 4. ReturnIfAbrupt(string). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle jsString = JSHandle::Cast(stringHandle); if (!thisObj->IsECMAObject()) { // 2. If Type(rx) is not Object, throw a TypeError exception. THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception()); } bool isFastPath = IsFastRegExp(thread, thisObj); return RegExpSplit(thread, thisObj, jsString, limit, isFastPath); } JSTaggedValue BuiltinsRegExp::RegExpSplitFast(JSThread *thread, const JSHandle regexp, JSHandle jsString, uint32_t limit, bool useCache) { if (limit == 0) { return JSArray::ArrayCreate(thread, JSTaggedNumber(0), ArrayMode::LITERAL).GetTaggedValue(); } JSHandle undefined = thread->GlobalConstants()->GetHandledUndefined(); JSHandle cacheTable(thread->GetCurrentEcmaContext()->GetRegExpCache()); if (useCache) { JSTaggedValue cacheResult = cacheTable->FindCachedResult(thread, jsString, RegExpExecResultCache::SPLIT_TYPE, regexp, JSTaggedValue(0), undefined); if (!cacheResult.IsUndefined()) { return cacheResult; } } uint32_t size = EcmaStringAccessor(jsString->GetTaggedObject()).GetLength(); JSHandle string = JSHandle::Cast(jsString); if (size == 0) { bool matchResult = RegExpExecInternal(thread, regexp, string, 0); // 0: lastIndex if (matchResult) { JSHandle res = JSArray::ArrayCreate(thread, JSTaggedNumber(0), ArrayMode::LITERAL); if (useCache) { RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, jsString, res, RegExpExecResultCache::SPLIT_TYPE, 0, 0, undefined); } return res.GetTaggedValue(); } JSHandle element = thread->GetEcmaVM()->GetFactory()->NewTaggedArray(1); // 1: length element->Set(thread, 0, jsString); JSHandle res = JSHandle::Cast(JSArray::CreateArrayFromList(thread, element)); if (useCache) { RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, jsString, res, RegExpExecResultCache::SPLIT_TYPE, 0, 0, undefined); } return res.GetTaggedValue(); } bool isUnicode = GetOriginalFlag(thread, regexp, RegExpParser::FLAG_UTF16); bool isSticky = GetOriginalFlag(thread, regexp, RegExpParser::FLAG_STICKY); uint32_t nextMatchFrom = 0; uint32_t lastMatchEnd = 0; uint32_t arrLen = 1; // at least one result string JSHandle splitArray(JSArray::ArrayCreate(thread, JSTaggedNumber(1), ArrayMode::LITERAL)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); TaggedArray *srcElements = TaggedArray::Cast(splitArray->GetElements().GetTaggedObject()); JSMutableHandle elements(thread, srcElements); JSMutableHandle matchValue(thread, JSTaggedValue::Undefined()); while (nextMatchFrom < size) { bool matchResult = RegExpExecInternal(thread, regexp, string, nextMatchFrom); if (!matchResult) { if (!isSticky) { // done match break; } nextMatchFrom = static_cast(AdvanceStringIndex(jsString, nextMatchFrom, isUnicode)); continue; } // find match result JSHandle matchResultInfo(thread->GetCurrentEcmaContext()->GetRegExpGlobalResult()); uint32_t matchStartIndex = static_cast(matchResultInfo->GetStartOfCaptureIndex(0).GetInt()); uint32_t matchEndIndex = static_cast(matchResultInfo->GetEndOfCaptureIndex(0).GetInt()); if (matchEndIndex == lastMatchEnd && matchEndIndex == nextMatchFrom) { // advance index and continue if match result is empty. nextMatchFrom = static_cast(AdvanceStringIndex(jsString, nextMatchFrom, isUnicode)); } else { matchValue.Update(JSTaggedValue(EcmaStringAccessor::FastSubString(thread->GetEcmaVM(), string, lastMatchEnd, matchStartIndex - lastMatchEnd))); if (arrLen > elements->GetLength()) { elements.Update(JSObject::GrowElementsCapacity(thread, JSHandle::Cast(splitArray), elements->GetLength(), true)); } elements->Set(thread, arrLen - 1, matchValue); splitArray->SetArrayLength(thread, arrLen); if (arrLen == limit) { if (useCache) { RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, jsString, JSHandle(splitArray), RegExpExecResultCache::SPLIT_TYPE, 0, 0, undefined); } return JSTaggedValue(splitArray.GetTaggedValue().GetTaggedObject()); } arrLen++; uint32_t capturesSize = static_cast(matchResultInfo->GetTotalCaptureCounts().GetInt()); uint32_t captureIndex = 1; while (captureIndex < capturesSize) { uint32_t captureStartIndex = static_cast( matchResultInfo->GetStartOfCaptureIndex(captureIndex).GetInt()); uint32_t captureEndIndex = static_cast( matchResultInfo->GetEndOfCaptureIndex(captureIndex).GetInt()); int32_t subStrLen = static_cast(captureEndIndex - captureStartIndex); if (subStrLen < 0) { matchValue.Update(JSTaggedValue::Undefined()); } else { matchValue.Update(JSTaggedValue(EcmaStringAccessor::FastSubString(thread->GetEcmaVM(), string, captureStartIndex, subStrLen))); } if (arrLen > elements->GetLength()) { elements.Update(JSObject::GrowElementsCapacity(thread, JSHandle::Cast(splitArray), arrLen, true)); } elements->Set(thread, arrLen - 1, matchValue); splitArray->SetArrayLength(thread, arrLen); if (arrLen == limit) { if (useCache) { RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, jsString, JSHandle(splitArray), RegExpExecResultCache::SPLIT_TYPE, 0, 0, undefined); } return JSTaggedValue(splitArray.GetTaggedValue().GetTaggedObject()); } arrLen++; captureIndex++; } lastMatchEnd = matchEndIndex; nextMatchFrom = matchEndIndex; } } matchValue.Update(JSTaggedValue(EcmaStringAccessor::FastSubString(thread->GetEcmaVM(), JSHandle::Cast(jsString), lastMatchEnd, size - lastMatchEnd))); if (arrLen > elements->GetLength()) { elements.Update(JSObject::GrowElementsCapacity(thread, JSHandle::Cast(splitArray), arrLen, true)); } elements->Set(thread, arrLen - 1, matchValue); splitArray->SetArrayLength(thread, arrLen); if (limit == MAX_SPLIT_LIMIT) { RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, jsString, JSHandle(splitArray), RegExpExecResultCache::SPLIT_TYPE, 0, 0, undefined); } return JSTaggedValue(splitArray.GetTaggedValue().GetTaggedObject()); } bool BuiltinsRegExp::RegExpExecInternal(JSThread *thread, const JSHandle regexp, JSHandle inputString, int32_t lastIndex) { size_t stringLength = EcmaStringAccessor(inputString).GetLength(); bool isUtf16 = EcmaStringAccessor(inputString).IsUtf16(); FlatStringInfo flatStrInfo = EcmaStringAccessor::FlattenAllString(thread->GetEcmaVM(), inputString); if (EcmaStringAccessor(inputString).IsTreeString()) { // use flattenedString as srcString inputString = JSHandle(thread, flatStrInfo.GetString()); } const uint8_t *strBuffer; if (isUtf16) { strBuffer = reinterpret_cast(flatStrInfo.GetDataUtf16()); } else { strBuffer = flatStrInfo.GetDataUtf8(); } bool isSuccess = Matcher(thread, regexp, strBuffer, stringLength, lastIndex, isUtf16); if (isSuccess) { JSHandle globalTable(thread->GetCurrentEcmaContext()->GetRegExpGlobalResult()); globalTable->ResetDollar(thread); globalTable->SetInputString(thread, inputString.GetTaggedValue()); } return isSuccess; } // NOLINTNEXTLINE(readability-non-const-parameter) bool BuiltinsRegExp::Matcher(JSThread *thread, const JSHandle regexp, const uint8_t *buffer, size_t length, int32_t lastIndex, bool isUtf16) { BUILTINS_API_TRACE(thread, RegExp, Matcher); // get bytecode JSTaggedValue bufferData = JSRegExp::Cast(regexp->GetTaggedObject())->GetByteCodeBuffer(); void *dynBuf = JSNativePointer::Cast(bufferData.GetTaggedObject())->GetExternalPointer(); auto bytecodeBuffer = reinterpret_cast(dynBuf); // execute RegExpCachedChunk chunk(thread); RegExpExecutor executor(&chunk); if (lastIndex < 0) { lastIndex = 0; } bool ret = executor.Execute(buffer, lastIndex, static_cast(length), bytecodeBuffer, isUtf16); if (ret) { executor.GetResult(thread); } return ret; } int64_t BuiltinsRegExp::AdvanceStringIndex(const JSHandle &inputStr, int64_t index, bool unicode) { // 1. Assert: Type(S) is String. ASSERT(inputStr->IsString()); // 2. Assert: index is an integer such that 0≤index≤2^53 - 1 ASSERT(0 <= index && index <= pow(2, 53) - 1); // 3. Assert: Type(unicode) is Boolean. // 4. If unicode is false, return index+1. if (!unicode) { return index + 1; } // 5. Let length be the number of code units in S. uint32_t length = EcmaStringAccessor(inputStr->GetTaggedObject()).GetLength(); // 6. If index+1 ≥ length, return index+1. if (index + 1 >= length) { return index + 1; } // 7. Let first be the code unit value at index index in S. uint16_t first = EcmaStringAccessor(inputStr->GetTaggedObject()).Get(index); // 8. If first < 0xD800 or first > 0xDFFF, return index+1. if (first < 0xD800 || first > 0xDFFF) { // NOLINT(readability-magic-numbers) return index + 1; } // 9. Let second be the code unit value at index index+1 in S. uint16_t second = EcmaStringAccessor(inputStr->GetTaggedObject()).Get(index + 1); // 10. If second < 0xDC00 or second > 0xDFFF, return index+1. if (second < 0xDC00 || second > 0xDFFF) { // NOLINT(readability-magic-numbers) return index + 1; } // 11. Return index + 2. return index + 2; } JSTaggedValue BuiltinsRegExp::GetFlagsInternal(JSThread *thread, const JSHandle &obj, const JSHandle &constructor, const uint8_t mask) { BUILTINS_API_TRACE(thread, RegExp, GetFlagsInternal); // 1. Let R be the this value. // 2. If Type(R) is not Object, throw a TypeError exception. if (!obj->IsECMAObject()) { // throw a TypeError exception. THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue(false)); } // 3. If R does not have an [[OriginalFlags]] internal slot, throw a TypeError exception. JSHandle patternObj = JSHandle::Cast(obj); if (!patternObj->IsJSRegExp()) { // a. If SameValue(R, %RegExp.prototype%) is true, return undefined. const GlobalEnvConstants *globalConst = thread->GlobalConstants(); JSHandle constructorKey = globalConst->GetHandledConstructorString(); JSHandle objConstructor = JSTaggedValue::GetProperty(thread, obj, constructorKey).GetValue(); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue(false)); if (objConstructor->IsJSFunction() && constructor->IsJSFunction()) { JSHandle objRealm = JSObject::GetFunctionRealm(thread, objConstructor); JSHandle ctorRealm = JSObject::GetFunctionRealm(thread, constructor); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); if (objRealm->GetRegExpPrototype() == obj && *objRealm == *ctorRealm) { return JSTaggedValue::Undefined(); } } // b. throw a TypeError exception. THROW_TYPE_ERROR_AND_RETURN(thread, "this does not have [[OriginalFlags]]", JSTaggedValue(false)); } // 4. Let flags be the value of R’s [[OriginalFlags]] internal slot. JSHandle regexpObj(thread, JSRegExp::Cast(obj->GetTaggedObject())); // 5. If flags contains the code unit "[flag]", return true. // 6. Return false. uint8_t flags = static_cast(regexpObj->GetOriginalFlags().GetInt()); return GetTaggedBoolean(flags & mask); } // 22.2.7.8 JSHandle BuiltinsRegExp::MakeMatchIndicesIndexPairArray(JSThread *thread, const std::vector>& indices, const std::vector>& groupNames, bool hasGroups) { // 1. Let n be the number of elements in indices. uint32_t n = indices.size(); // Assert: groupNames has n - 1 elements. ASSERT(groupNames.size() == n - 1); // 5. Let A be ! ArrayCreate(n). JSHandle results(JSArray::ArrayCreate(thread, JSTaggedNumber(n))); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); // 6. If hasGroups is true, then // a. Let groups be OrdinaryObjectCreate(null). // 7. Else, // a. Let groups be undefined. JSMutableHandle groups(thread, JSTaggedValue::Undefined()); if (hasGroups) { JSHandle nullHandle(thread, JSTaggedValue::Null()); JSHandle nullObj = factory->OrdinaryNewJSObjectCreate(nullHandle); groups.Update(nullObj.GetTaggedValue()); } // 8. Perform ! CreateDataPropertyOrThrow(A, "groups", groups). const GlobalEnvConstants *globalConst = thread->GlobalConstants(); JSHandle groupsKey = globalConst->GetHandledGroupsString(); JSObject::CreateDataProperty(thread, results, groupsKey, groups); // 9. For each integer i such that 0 ≤ i < n, in ascending order, do // a. Let matchIndices be indices[i]. // b. If matchIndices is not undefined, then // i. Let matchIndexPair be GetMatchIndexPair(S, matchIndices). // c. Else, // i. Let matchIndexPair be undefined. // d. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(i)), matchIndexPair). // e. If i > 0 and groupNames[i - 1] is not undefined, then // i. Assert: groups is not undefined. // ii. Perform ! CreateDataPropertyOrThrow(groups, groupNames[i - 1], matchIndexPair). JSMutableHandle matchIndexPair(thread, JSTaggedValue::Undefined()); for (uint32_t i = 0; i < n; i++) { std::pair matchIndices = indices[i]; if (!matchIndices.first.IsUndefined()) { JSHandle match = factory->NewTaggedArray(2); // 2 means the length of array match->Set(thread, 0, matchIndices.first); match->Set(thread, 1, matchIndices.second); JSHandle pair(JSArray::CreateArrayFromList(thread, JSHandle::Cast(match))); matchIndexPair.Update(pair.GetTaggedValue()); } else { matchIndexPair.Update(JSTaggedValue::Undefined()); } JSObject::CreateDataProperty(thread, results, i, matchIndexPair); if (i > 0) { JSHandle groupName = groupNames[i - 1]; if (!groupName->IsUndefined()) { JSHandle groupObject = JSHandle::Cast(groups); JSObject::CreateDataProperty(thread, groupObject, groupName, matchIndexPair); } } } // 10. Return A. return JSHandle::Cast(results); } // 21.2.5.2.2 JSTaggedValue BuiltinsRegExp::RegExpBuiltinExec(JSThread *thread, const JSHandle regexp, const JSHandle inputStr, bool isFastPath, bool useCache, bool isIntermediateResult) { ASSERT(regexp->IsJSRegExp()); ASSERT(inputStr->IsString()); BUILTINS_API_TRACE(thread, RegExp, RegExpBuiltinExec); uint32_t lastIndex = static_cast(GetLastIndex(thread, regexp, isFastPath)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle undefined = thread->GlobalConstants()->GetHandledUndefined(); JSHandle cacheTable(thread->GetCurrentEcmaContext()->GetRegExpCache()); if (useCache) { JSTaggedValue cacheResult = cacheTable->FindCachedResult(thread, inputStr, RegExpExecResultCache::EXEC_TYPE, regexp, JSTaggedValue(lastIndex), undefined); if (!cacheResult.IsUndefined()) { return cacheResult; } } JSTaggedValue result = RegExpBuiltinExecWithoutResult(thread, regexp, inputStr, isFastPath, lastIndex, useCache); if (result.IsNull()) { return result; } JSHandle inputString = JSHandle::Cast(inputStr); JSHandle globalTable(thread->GetCurrentEcmaContext()->GetRegExpGlobalResult()); uint32_t capturesSize = static_cast(globalTable->GetTotalCaptureCounts().GetInt()); JSHandle results(JSArray::ArrayCreate(thread, JSTaggedNumber(capturesSize))); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); const GlobalEnvConstants *globalConst = thread->GlobalConstants(); JSHandle indexValue(thread, globalTable->GetStartOfCaptureIndex(0)); if (isIntermediateResult) { // inlined intermediate result results->SetPropertyInlinedPropsWithRep(thread, EXEC_RESULT_INDEX_OFFSET, indexValue.GetTaggedValue()); results->SetPropertyInlinedPropsWithRep(thread, EXEC_RESULT_INPUT_OFFSET, inputStr.GetTaggedValue()); } else { // 24. Perform CreateDataProperty(A, "index", matchIndex). JSHandle indexKey = globalConst->GetHandledIndexString(); JSObject::CreateDataProperty(thread, results, indexKey, indexValue); // 25. Perform CreateDataProperty(A, "input", S). JSHandle inputKey = globalConst->GetHandledInputString(); JSObject::CreateDataProperty(thread, results, inputKey, inputStr); } // 27. Perform CreateDataProperty(A, "0", matched_substr). uint32_t startIndex = static_cast(globalTable->GetStartOfCaptureIndex(0).GetInt()); uint32_t len = static_cast(globalTable->GetEndOfCaptureIndex(0).GetInt()) - startIndex; JSHandle zeroValue(thread, JSTaggedValue(EcmaStringAccessor::FastSubString( thread->GetEcmaVM(), inputString, startIndex, len))); TaggedArray *srcElements = TaggedArray::Cast(results->GetElements().GetTaggedObject()); JSHandle resultElements(thread, srcElements); resultElements->Set(thread, 0, zeroValue); // Let indices be a new empty List. // Let groupNames be a new empty List. // Append match to indices. uint32_t endIndex = globalTable->GetEndIndex().GetInt(); std::vector> indices; std::vector> groupNames; indices.emplace_back(std::make_pair(globalTable->GetStartOfCaptureIndex(0), JSTaggedValue(endIndex))); // If R contains any GroupName, then // a. Let groups be OrdinaryObjectCreate(null). // b. Let hasGroups be true. // Else, // a. Let groups be undefined. // b. Let hasGroups be false. JSHandle regexpObj(regexp); JSHandle groupName(thread, regexpObj->GetGroupName()); JSMutableHandle groups(thread, JSTaggedValue::Undefined()); bool hasGroups = false; if (!groupName->IsUndefined()) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle nullHandle(thread, JSTaggedValue::Null()); JSHandle nullObj = factory->OrdinaryNewJSObjectCreate(nullHandle); groups.Update(nullObj.GetTaggedValue()); hasGroups = true; } if (isIntermediateResult) { // inlined intermediate result results->SetPropertyInlinedPropsWithRep(thread, EXEC_RESULT_GROUPS_OFFSET, groups.GetTaggedValue()); } else { // Perform ! CreateDataPropertyOrThrow(A, "groups", groups). JSHandle groupsKey = globalConst->GetHandledGroupsString(); JSObject::CreateDataProperty(thread, results, groupsKey, groups); } // Create a new RegExp on global uint32_t captureIndex = 1; JSMutableHandle iValue(thread, JSTaggedValue::Undefined()); // 28. For each integer i such that i > 0 and i <= n for (; captureIndex < capturesSize; captureIndex++) { // a. Let capture_i be ith element of r's captures List int32_t captureStartIndex = globalTable->GetStartOfCaptureIndex(captureIndex).GetInt(); int32_t captureEndIndex = globalTable->GetEndOfCaptureIndex(captureIndex).GetInt(); int32_t subStrLen = captureEndIndex - captureStartIndex; if (subStrLen < 0) { iValue.Update(JSTaggedValue::Undefined()); indices.emplace_back(std::make_pair(JSTaggedValue::Undefined(), JSTaggedValue::Undefined())); } else { iValue.Update(JSTaggedValue(EcmaStringAccessor::FastSubString( thread->GetEcmaVM(), inputString, captureStartIndex, subStrLen))); indices.emplace_back(std::make_pair(captureStartIndex, captureEndIndex)); } // add to RegExp.$i and i must <= 9 if (captureIndex <= REGEXP_GLOBAL_ARRAY_SIZE) { globalTable->SetCapture(thread, captureIndex, iValue.GetTaggedValue()); } resultElements->Set(thread, captureIndex, iValue); if (!groupName->IsUndefined()) { JSHandle groupObject = JSHandle::Cast(groups); TaggedArray *groupArray = TaggedArray::Cast(regexpObj->GetGroupName().GetTaggedObject()); if (groupArray->GetLength() > captureIndex - 1) { JSHandle skey(thread, groupArray->Get(captureIndex - 1)); JSObject::CreateDataProperty(thread, groupObject, skey, iValue); groupNames.emplace_back(skey); } else { groupNames.emplace_back(undefined); } } else { groupNames.emplace_back(undefined); } } // If hasIndices is true, then // a. Let indicesArray be MakeMatchIndicesIndexPairArray(S, indices, groupNames, hasGroups). // b. Perform ! CreateDataPropertyOrThrow(A, "indices", indicesArray). bool hasIndices = GetOriginalFlag(thread, regexp, RegExpParser::FLAG_HASINDICES); if (hasIndices) { auto indicesArray = MakeMatchIndicesIndexPairArray(thread, indices, groupNames, hasGroups); JSHandle indicesKey = globalConst->GetHandledIndicesString(); JSObject::CreateDataProperty(thread, results, indicesKey, indicesArray); } JSHandle emptyString = thread->GlobalConstants()->GetHandledEmptyString(); while (captureIndex <= REGEXP_GLOBAL_ARRAY_SIZE) { globalTable->SetCapture(thread, captureIndex, emptyString.GetTaggedValue()); ++captureIndex; } if (useCache) { uint32_t newLastIndex = lastIndex; bool global = GetOriginalFlag(thread, regexp, RegExpParser::FLAG_GLOBAL); bool sticky = GetOriginalFlag(thread, regexp, RegExpParser::FLAG_STICKY); if (global || sticky) { newLastIndex = static_cast(globalTable->GetEndIndex().GetInt()); } RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, inputStr, JSHandle(results), RegExpExecResultCache::EXEC_TYPE, lastIndex, newLastIndex, undefined); } // 29. Return A. return results.GetTaggedValue(); } JSTaggedValue BuiltinsRegExp::RegExpBuiltinExecWithoutResult(JSThread *thread, const JSHandle regexp, const JSHandle inputStr, bool isFastPath, uint32_t lastIndex, bool useCache) { // check global and sticky flag to determine whether need to update lastIndex bool global = GetOriginalFlag(thread, regexp, RegExpParser::FLAG_GLOBAL); bool sticky = GetOriginalFlag(thread, regexp, RegExpParser::FLAG_STICKY); bool ifUpdateLastIndex = global || sticky; if (ifUpdateLastIndex) { uint32_t length = EcmaStringAccessor(inputStr->GetTaggedObject()).GetLength(); if (lastIndex > length) { SetLastIndex(thread, regexp, JSTaggedValue(0), isFastPath); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); return JSTaggedValue::Null(); } } else { lastIndex = 0; } JSHandle undefined = thread->GlobalConstants()->GetHandledUndefined(); JSHandle inputString = JSHandle::Cast(inputStr); bool matchResult = RegExpExecInternal(thread, regexp, inputString, lastIndex); if (!matchResult) { uint32_t endIndex = lastIndex; if (ifUpdateLastIndex) { endIndex = 0; SetLastIndex(thread, regexp, JSTaggedValue(0), isFastPath); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } if (useCache) { JSHandle cacheTable(thread->GetCurrentEcmaContext()->GetRegExpCache()); RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, inputStr, JSHandle(thread, JSTaggedValue::Null()), RegExpExecResultCache::EXEC_TYPE, lastIndex, endIndex, undefined); } return JSTaggedValue::Null(); } JSHandle globalTable(thread->GetCurrentEcmaContext()->GetRegExpGlobalResult()); JSTaggedValue endIndex = globalTable->GetEndIndex(); if (ifUpdateLastIndex) { SetLastIndex(thread, regexp, endIndex, isFastPath); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } return JSTaggedValue::True(); } // 21.2.5.2.1 JSTaggedValue BuiltinsRegExp::RegExpExec(JSThread *thread, const JSHandle ®exp, const JSHandle &inputString, bool useCache, bool isIntermediateResult) { BUILTINS_API_TRACE(thread, RegExp, RegExpExec); // 1. Assert: Type(R) is Object. ASSERT(regexp->IsECMAObject()); // 2. Assert: Type(S) is String. ASSERT(inputString->IsString()); // 3. Let exec be Get(R, "exec"). JSHandle inputStr = JSTaggedValue::ToString(thread, inputString); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); const GlobalEnvConstants *globalConst = thread->GlobalConstants(); JSHandle execHandle = globalConst->GetHandledExecString(); JSTaggedValue execVal = ObjectFastOperator::FastGetPropertyByValue(thread, regexp.GetTaggedValue(), execHandle.GetTaggedValue()); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle exec(thread, execVal); // 4. ReturnIfAbrupt(exec). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 5. If IsCallable(exec) is true, then if (exec->IsCallable()) { JSHandle undefined = globalConst->GetHandledUndefined(); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, exec, regexp, undefined, 1); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); info->SetCallArg(inputStr.GetTaggedValue()); JSTaggedValue result = JSFunction::Call(info); // b. ReturnIfAbrupt(result). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); if (!result.IsECMAObject() && !result.IsNull()) { // throw a TypeError exception. THROW_TYPE_ERROR_AND_RETURN(thread, "exec result is null or is not Object", JSTaggedValue::Exception()); } return result; } // 6. If R does not have a [[RegExpMatcher]] internal slot, throw a TypeError exception. if (!regexp->IsJSRegExp()) { // throw a TypeError exception. THROW_TYPE_ERROR_AND_RETURN(thread, "this does not have a [[RegExpMatcher]]", JSTaggedValue::Exception()); } // 7. Return RegExpBuiltinExec(R, S). return RegExpBuiltinExec(thread, regexp, inputString, false, useCache, isIntermediateResult); } // 21.2.3.2.1 JSTaggedValue BuiltinsRegExp::RegExpAlloc(JSThread *thread, const JSHandle &newTarget) { BUILTINS_API_TRACE(thread, RegExp, RegExpAlloc); /** * 1. Let obj be OrdinaryCreateFromConstructor(newTarget, "%RegExpPrototype%", * «[[RegExpMatcher]],[[OriginalSource]], [[OriginalFlags]]»). * */ ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle env = thread->GetEcmaVM()->GetGlobalEnv(); JSHandle func = env->GetRegExpFunction(); JSHandle obj(factory->NewJSObjectByConstructor(JSHandle(func), newTarget)); // 2. ReturnIfAbrupt(obj). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 5. Return obj. return obj.GetTaggedValue(); } uint32_t BuiltinsRegExp::UpdateExpressionFlags(JSThread *thread, const CString &checkStr) { uint32_t flagsBits = 0; uint32_t flagsBitsTemp = 0; for (char i : checkStr) { switch (i) { case 'g': flagsBitsTemp = RegExpParser::FLAG_GLOBAL; break; case 'i': flagsBitsTemp = RegExpParser::FLAG_IGNORECASE; break; case 'm': flagsBitsTemp = RegExpParser::FLAG_MULTILINE; break; case 's': flagsBitsTemp = RegExpParser::FLAG_DOTALL; break; case 'u': flagsBitsTemp = RegExpParser::FLAG_UTF16; break; case 'y': flagsBitsTemp = RegExpParser::FLAG_STICKY; break; case 'd': flagsBitsTemp = RegExpParser::FLAG_HASINDICES; break; default: { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle syntaxError = factory->GetJSError(base::ErrorType::SYNTAX_ERROR, "invalid regular expression flags", StackCheck::NO); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, syntaxError.GetTaggedValue(), 0); } } if ((flagsBits & flagsBitsTemp) != 0) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle syntaxError = factory->GetJSError(base::ErrorType::SYNTAX_ERROR, "invalid regular expression flags", StackCheck::NO); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, syntaxError.GetTaggedValue(), 0); } flagsBits |= flagsBitsTemp; } return flagsBits; } JSTaggedValue BuiltinsRegExp::FlagsBitsToString(JSThread *thread, uint8_t flags) { ASSERT((flags & 0x80) == 0); // 0x80: first bit of flags must be 0 BUILTINS_API_TRACE(thread, RegExp, FlagsBitsToString); uint8_t *flagsStr = new uint8_t[RegExpParser::FLAG_NUM + 1]; // FLAG_NUM flags + '\0' size_t flagsLen = 0; if (flags & RegExpParser::FLAG_HASINDICES) { flagsStr[flagsLen] = 'd'; flagsLen++; } if (flags & RegExpParser::FLAG_GLOBAL) { flagsStr[flagsLen] = 'g'; flagsLen++; } if (flags & RegExpParser::FLAG_IGNORECASE) { flagsStr[flagsLen] = 'i'; flagsLen++; } if (flags & RegExpParser::FLAG_MULTILINE) { flagsStr[flagsLen] = 'm'; flagsLen++; } if (flags & RegExpParser::FLAG_DOTALL) { flagsStr[flagsLen] = 's'; flagsLen++; } if (flags & RegExpParser::FLAG_UTF16) { flagsStr[flagsLen] = 'u'; flagsLen++; } if (flags & RegExpParser::FLAG_STICKY) { flagsStr[flagsLen] = 'y'; flagsLen++; } flagsStr[flagsLen] = '\0'; JSHandle flagsString = thread->GetEcmaVM()->GetFactory()->NewFromUtf8(flagsStr, flagsLen); delete[] flagsStr; return flagsString.GetTaggedValue(); } // 21.2.3.2.2 JSTaggedValue BuiltinsRegExp::RegExpInitialize(JSThread *thread, const JSHandle &obj, const JSHandle &pattern, const JSHandle &flags) { BUILTINS_API_TRACE(thread, RegExp, RegExpInitialize); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle patternStrHandle; uint8_t flagsBits = 0; // 1. If pattern is undefined, let P be the empty String. if (pattern->IsUndefined()) { patternStrHandle = factory->GetEmptyString(); } else { // 2. Else, let P be ToString(pattern). patternStrHandle = JSTaggedValue::ToString(thread, pattern); // 3. ReturnIfAbrupt(P). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } // 4. If flags is undefined, let F be the empty String. if (flags->IsUndefined()) { flagsBits = 0; } else if (flags->IsInt()) { flagsBits = static_cast(flags->GetInt()); } else { // 5. Else, let F be ToString(flags). JSHandle flagsStrHandle = JSTaggedValue::ToString(thread, flags); // 6. ReturnIfAbrupt(F). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); /** * 7. If F contains any code unit other than "d", "g", "i", "m", "u", or "y" or if it contains the same code * unit more than once, throw a SyntaxError exception. **/ CString checkStr = ConvertToString(*flagsStrHandle, StringConvertedUsage::LOGICOPERATION); flagsBits = static_cast(UpdateExpressionFlags(thread, checkStr)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } // 9. 10. Chunk chunk(thread->GetNativeAreaAllocator()); RegExpParser parser = RegExpParser(thread, &chunk); RegExpParserCache *regExpParserCache = thread->GetCurrentEcmaContext()->GetRegExpParserCache(); CVector groupName; auto getCache = regExpParserCache->GetCache(*patternStrHandle, flagsBits, groupName); if (getCache.first.IsHole()) { // String -> CString bool cesu8 = !(RegExpParser::FLAG_UTF16 & flagsBits); CString patternStdStr = ConvertToString(*patternStrHandle, StringConvertedUsage::LOGICOPERATION, cesu8); parser.Init(const_cast(reinterpret_cast(patternStdStr.c_str())), patternStdStr.size(), flagsBits); parser.Parse(); if (parser.IsError()) { JSHandle syntaxError = factory->GetJSError(base::ErrorType::SYNTAX_ERROR, parser.GetErrorMsg().c_str(), StackCheck::NO); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, syntaxError.GetTaggedValue(), JSTaggedValue::Exception()); } groupName = parser.GetGroupNames(); } JSHandle regexp(thread, JSRegExp::Cast(obj->GetTaggedObject())); // 11. Set the value of obj’s [[OriginalSource]] internal slot to P. regexp->SetOriginalSource(thread, patternStrHandle.GetTaggedValue()); // 12. Set the value of obj’s [[OriginalFlags]] internal slot to F. regexp->SetOriginalFlags(thread, JSTaggedValue(flagsBits)); if (!groupName.empty()) { JSHandle taggedArray = factory->NewTaggedArray(groupName.size()); for (size_t i = 0; i < groupName.size(); ++i) { JSHandle flagsKey(factory->NewFromStdString(groupName[i].c_str())); taggedArray->Set(thread, i, flagsKey); } regexp->SetGroupName(thread, taggedArray); } // 13. Set obj’s [[RegExpMatcher]] internal slot. if (getCache.first.IsHole()) { auto bufferSize = parser.GetOriginBufferSize(); auto buffer = parser.GetOriginBuffer(); factory->NewJSRegExpByteCodeData(regexp, buffer, bufferSize); regExpParserCache->SetCache(*patternStrHandle, flagsBits, regexp->GetByteCodeBuffer(), bufferSize, groupName); } else { regexp->SetByteCodeBuffer(thread, getCache.first); regexp->SetLength(static_cast(getCache.second)); } // 14. Let setStatus be Set(obj, "lastIndex", 0, true). SetLastIndex(thread, obj, JSTaggedValue(0), true); // 16. Return obj. return obj.GetTaggedValue(); } JSTaggedValue BuiltinsRegExp::RegExpCreate(JSThread *thread, const JSHandle &pattern, const JSHandle &flags) { BUILTINS_API_TRACE(thread, RegExp, Create); auto ecmaVm = thread->GetEcmaVM(); JSHandle env = ecmaVm->GetGlobalEnv(); JSHandle newTarget = env->GetRegExpFunction(); // 1. Let obj be RegExpAlloc(%RegExp%). JSHandle object(thread, RegExpAlloc(thread, newTarget)); // 2. ReturnIfAbrupt(obj). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 3. Return RegExpInitialize(obj, P, F). return RegExpInitialize(thread, object, pattern, flags); } // 21.2.3.2.4 EcmaString *BuiltinsRegExp::EscapeRegExpPattern(JSThread *thread, const JSHandle &src, const JSHandle &flags) { BUILTINS_API_TRACE(thread, RegExp, EscapeRegExpPattern); // String -> CString JSHandle srcStr(thread, static_cast(src->GetTaggedObject())); JSHandle flagsStr(thread, static_cast(flags->GetTaggedObject())); CString srcStdStr = ConvertToString(*srcStr, StringConvertedUsage::LOGICOPERATION); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); // "" -> (?:) if (srcStdStr.empty()) { srcStdStr = "(?:)"; } // "/" -> "\/" srcStdStr = base::StringHelper::ReplaceAll(srcStdStr, "/", "\\/"); // "\\" -> "\" srcStdStr = base::StringHelper::ReplaceAll(srcStdStr, "\\", "\\"); return *factory->NewFromUtf8(srcStdStr); } // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define SET_GET_CAPTURE_IMPL(index) \ JSTaggedValue BuiltinsRegExp::GetCapture##index(JSThread *thread, [[maybe_unused]] const JSHandle &obj) \ { \ return RegExpGlobalResult::GetCapture(thread); \ } \ bool BuiltinsRegExp::SetCapture##index([[maybe_unused]] JSThread *thread, \ [[maybe_unused]] const JSHandle &obj, \ [[maybe_unused]] const JSHandle &value, \ [[maybe_unused]] bool mayThrow) \ { \ return true; \ } SET_GET_CAPTURE_IMPL(1) SET_GET_CAPTURE_IMPL(2) SET_GET_CAPTURE_IMPL(3) SET_GET_CAPTURE_IMPL(4) SET_GET_CAPTURE_IMPL(5) SET_GET_CAPTURE_IMPL(6) SET_GET_CAPTURE_IMPL(7) SET_GET_CAPTURE_IMPL(8) SET_GET_CAPTURE_IMPL(9) #undef SET_GET_CAPTURE_IMPL JSTaggedValue RegExpExecResultCache::CreateCacheTable(JSThread *thread) { int length = CACHE_TABLE_HEADER_SIZE + INITIAL_CACHE_NUMBER * ENTRY_SIZE; auto table = static_cast( *thread->GetEcmaVM()->GetFactory()->NewTaggedArray(length, JSTaggedValue::Undefined())); table->SetLargeStrCount(thread, DEFAULT_LARGE_STRING_COUNT); table->SetConflictCount(thread, DEFAULT_CONFLICT_COUNT); table->SetStrLenThreshold(thread, 0); table->SetHitCount(thread, 0); table->SetCacheCount(thread, 0); table->SetCacheLength(thread, INITIAL_CACHE_NUMBER); return JSTaggedValue(table); } JSTaggedValue RegExpExecResultCache::FindCachedResult(JSThread *thread, const JSHandle input, CacheType type, const JSHandle regexp, JSTaggedValue lastIndexInput, JSHandle extend, bool isIntermediateResult) { JSHandle regexpObj(regexp); JSTaggedValue pattern = regexpObj->GetOriginalSource(); JSTaggedValue flags = regexpObj->GetOriginalFlags(); JSTaggedValue inputValue = input.GetTaggedValue(); JSTaggedValue extendValue = extend.GetTaggedValue(); if (!pattern.IsString() || !flags.IsInt() || !input->IsString() || !lastIndexInput.IsInt()) { return JSTaggedValue::Undefined(); } uint32_t hash = pattern.GetKeyHashCode() + static_cast(flags.GetInt()) + input->GetKeyHashCode() + static_cast(lastIndexInput.GetInt()); uint32_t entry = hash & static_cast(GetCacheLength() - 1); if (!Match(entry, pattern, flags, inputValue, lastIndexInput, extendValue, type)) { uint32_t entry2 = (entry + 1) & static_cast(GetCacheLength() - 1); if (!Match(entry2, pattern, flags, inputValue, lastIndexInput, extendValue, type)) { return JSTaggedValue::Undefined(); } entry = entry2; } ASSERT((static_cast(CACHE_TABLE_HEADER_SIZE) + static_cast(entry) * static_cast(ENTRY_SIZE)) <= static_cast(UINT32_MAX)); uint32_t index = CACHE_TABLE_HEADER_SIZE + entry * ENTRY_SIZE; // update cached value if input value is changed JSTaggedValue cachedStr = Get(index + INPUT_STRING_INDEX); if (!cachedStr.IsUndefined() && cachedStr != inputValue) { Set(thread, index + INPUT_STRING_INDEX, inputValue); } JSTaggedValue result; switch (type) { case REPLACE_TYPE: result = Get(index + RESULT_REPLACE_INDEX); break; case SPLIT_TYPE: result = Get(index + RESULT_SPLIT_INDEX); break; case MATCH_TYPE: result = Get(index + RESULT_MATCH_INDEX); break; case EXEC_TYPE: result = Get(index + RESULT_EXEC_INDEX); break; case INTERMEDIATE_REPLACE_TYPE: result = Get(index + RESULT_INTERMEDIATE_REPLACE_INDEX); break; case TEST_TYPE: result = Get(index + RESULT_TEST_INDEX); break; case SEARCH_TYPE: result = Get(index + RESULT_SEARCH_INDEX); break; default: LOG_ECMA(FATAL) << "this branch is unreachable"; UNREACHABLE(); break; } JSHandle globalTable(thread->GetCurrentEcmaContext()->GetRegExpGlobalResult()); JSTaggedValue cachedTable = Get(index + CAPTURE_SIZE); thread->GetCurrentEcmaContext()->SetRegExpGlobalResult(cachedTable); SetHitCount(thread, GetHitCount() + 1); if (type != SEARCH_TYPE && type != SPLIT_TYPE) { BuiltinsRegExp::SetLastIndex(thread, regexp, Get(index + LAST_INDEX_INDEX), true); } if (!isIntermediateResult && result.IsJSArray()) { JSHandle resultHandle(thread, JSArray::Cast(result)); JSHandle copyArray = thread->GetEcmaVM()->GetFactory()->CloneArrayLiteral(resultHandle); return copyArray.GetTaggedValue(); } return result; } void RegExpExecResultCache::AddResultInCache(JSThread *thread, JSHandle cache, const JSHandle regexp, const JSHandle input, const JSHandle resultArray, CacheType type, uint32_t lastIndexInput, uint32_t lastIndex, JSHandle extend, bool isIntermediateResult) { JSHandle regexpObj(regexp); JSHandle pattern(thread, regexpObj->GetOriginalSource()); JSHandle flags(thread, regexpObj->GetOriginalFlags()); if (!pattern->IsString() || !flags->IsInt() || !input->IsString()) { return; } JSHandle resultArrayCopy; if (!isIntermediateResult && resultArray->IsJSArray()) { JSHandle copyArray = thread->GetEcmaVM()->GetFactory() ->CloneArrayLiteral(JSHandle(resultArray)); resultArrayCopy = JSHandle(copyArray); } else { resultArrayCopy = JSHandle(resultArray); } JSHandle globalTable(thread->GetCurrentEcmaContext()->GetRegExpGlobalResult()); JSHandle taggedArray = JSHandle::Cast(globalTable); auto factory = thread->GetEcmaVM()->GetFactory(); uint32_t arrayLength = globalTable->GetLength(); JSHandle resTableArray = factory->NewAndCopyTaggedArray(taggedArray, arrayLength, arrayLength); JSTaggedValue patternValue = pattern.GetTaggedValue(); JSTaggedValue flagsValue = flags.GetTaggedValue(); JSTaggedValue inputValue = input.GetTaggedValue(); JSTaggedValue extendValue = extend.GetTaggedValue(); JSTaggedValue lastIndexInputValue(lastIndexInput); JSTaggedValue lastIndexValue(lastIndex); JSTaggedValue resTableArrayValue = resTableArray.GetTaggedValue(); uint32_t hash = patternValue.GetKeyHashCode() + static_cast(flagsValue.GetInt()) + inputValue.GetKeyHashCode() + lastIndexInput; uint32_t entry = hash & static_cast(cache->GetCacheLength() - 1); ASSERT((static_cast(CACHE_TABLE_HEADER_SIZE) + static_cast(entry) * static_cast(ENTRY_SIZE)) <= static_cast(UINT32_MAX)); uint32_t index = CACHE_TABLE_HEADER_SIZE + entry * ENTRY_SIZE; if (cache->Get(index).IsUndefined()) { cache->SetCacheCount(thread, cache->GetCacheCount() + 1); cache->SetEntry(thread, entry, patternValue, flagsValue, inputValue, lastIndexInputValue, lastIndexValue, extendValue, resTableArrayValue); cache->UpdateResultArray(thread, entry, resultArrayCopy.GetTaggedValue(), type); } else if (cache->Match(entry, patternValue, flagsValue, inputValue, lastIndexInputValue, extendValue, type)) { cache->UpdateResultArray(thread, entry, resultArrayCopy.GetTaggedValue(), type); } else { uint32_t entry2 = (entry + 1) & static_cast(cache->GetCacheLength() - 1); ASSERT((static_cast(CACHE_TABLE_HEADER_SIZE) + static_cast(entry2) * static_cast(ENTRY_SIZE)) <= static_cast(UINT32_MAX)); uint32_t index2 = CACHE_TABLE_HEADER_SIZE + entry2 * ENTRY_SIZE; if (cache->GetCacheLength() < DEFAULT_CACHE_NUMBER) { GrowRegexpCache(thread, cache); // update value after gc. patternValue = pattern.GetTaggedValue(); flagsValue = flags.GetTaggedValue(); inputValue = input.GetTaggedValue(); cache->SetCacheLength(thread, DEFAULT_CACHE_NUMBER); entry2 = hash & static_cast(cache->GetCacheLength() - 1); index2 = CACHE_TABLE_HEADER_SIZE + entry2 * ENTRY_SIZE; } extendValue = extend.GetTaggedValue(); resTableArrayValue = resTableArray.GetTaggedValue(); if (cache->Get(index2).IsUndefined()) { cache->SetCacheCount(thread, cache->GetCacheCount() + 1); cache->SetEntry(thread, entry2, patternValue, flagsValue, inputValue, lastIndexInputValue, lastIndexValue, extendValue, resTableArrayValue); cache->UpdateResultArray(thread, entry2, resultArrayCopy.GetTaggedValue(), type); } else if (cache->Match(entry2, patternValue, flagsValue, inputValue, lastIndexInputValue, extendValue, type)) { cache->UpdateResultArray(thread, entry2, resultArrayCopy.GetTaggedValue(), type); } else { cache->SetConflictCount(thread, cache->GetConflictCount() > 1 ? (cache->GetConflictCount() - 1) : 0); cache->SetCacheCount(thread, cache->GetCacheCount() - 1); cache->ClearEntry(thread, entry2); cache->SetEntry(thread, entry2, patternValue, flagsValue, inputValue, lastIndexInputValue, lastIndexValue, extendValue, resTableArrayValue); cache->UpdateResultArray(thread, entry2, resultArrayCopy.GetTaggedValue(), type); } } } void RegExpExecResultCache::GrowRegexpCache(JSThread *thread, JSHandle cache) { int length = CACHE_TABLE_HEADER_SIZE + DEFAULT_CACHE_NUMBER * ENTRY_SIZE; auto factory = thread->GetEcmaVM()->GetFactory(); auto newCache = factory->ExtendArray(JSHandle(cache), length, JSTaggedValue::Undefined()); thread->GetCurrentEcmaContext()->SetRegExpCache(newCache.GetTaggedValue()); } void RegExpExecResultCache::SetEntry(JSThread *thread, int entry, JSTaggedValue &pattern, JSTaggedValue &flags, JSTaggedValue &input, JSTaggedValue &lastIndexInputValue, JSTaggedValue &lastIndexValue, JSTaggedValue &extendValue, JSTaggedValue &resTableArray) { ASSERT((static_cast(CACHE_TABLE_HEADER_SIZE) + static_cast(entry) * static_cast(ENTRY_SIZE)) <= static_cast(INT_MAX)); int index = CACHE_TABLE_HEADER_SIZE + entry * ENTRY_SIZE; Set(thread, index + PATTERN_INDEX, pattern); Set(thread, index + FLAG_INDEX, flags); Set(thread, index + INPUT_STRING_INDEX, input); Set(thread, index + LAST_INDEX_INPUT_INDEX, lastIndexInputValue); Set(thread, index + LAST_INDEX_INDEX, lastIndexValue); Set(thread, index + EXTEND_INDEX, extendValue); Set(thread, index + CAPTURE_SIZE, resTableArray); } void RegExpExecResultCache::UpdateResultArray(JSThread *thread, int entry, JSTaggedValue resultArray, CacheType type) { ASSERT((static_cast(CACHE_TABLE_HEADER_SIZE) + static_cast(entry) * static_cast(ENTRY_SIZE)) <= static_cast(INT_MAX)); int index = CACHE_TABLE_HEADER_SIZE + entry * ENTRY_SIZE; switch (type) { case REPLACE_TYPE: Set(thread, index + RESULT_REPLACE_INDEX, resultArray); break; case SPLIT_TYPE: Set(thread, index + RESULT_SPLIT_INDEX, resultArray); break; case MATCH_TYPE: Set(thread, index + RESULT_MATCH_INDEX, resultArray); break; case EXEC_TYPE: Set(thread, index + RESULT_EXEC_INDEX, resultArray); break; case INTERMEDIATE_REPLACE_TYPE: Set(thread, index + RESULT_INTERMEDIATE_REPLACE_INDEX, resultArray); break; case TEST_TYPE: Set(thread, index + RESULT_TEST_INDEX, resultArray); break; case SEARCH_TYPE: Set(thread, index + RESULT_SEARCH_INDEX, resultArray); break; default: LOG_ECMA(FATAL) << "this branch is unreachable"; UNREACHABLE(); break; } } void RegExpExecResultCache::ClearEntry(JSThread *thread, int entry) { ASSERT((static_cast(CACHE_TABLE_HEADER_SIZE) + static_cast(entry) * static_cast(ENTRY_SIZE)) <= static_cast(INT_MAX)); int index = CACHE_TABLE_HEADER_SIZE + entry * ENTRY_SIZE; JSTaggedValue undefined = JSTaggedValue::Undefined(); for (int i = 0; i < ENTRY_SIZE; i++) { Set(thread, index + i, undefined); } } bool RegExpExecResultCache::Match(int entry, JSTaggedValue &pattern, JSTaggedValue &flags, JSTaggedValue &input, JSTaggedValue &lastIndexInputValue, JSTaggedValue &extend, CacheType type) { ASSERT((static_cast(CACHE_TABLE_HEADER_SIZE) + static_cast(entry) * static_cast(ENTRY_SIZE)) <= static_cast(INT_MAX)); int index = CACHE_TABLE_HEADER_SIZE + entry * ENTRY_SIZE; JSTaggedValue typeKey = Get(index + RESULT_REPLACE_INDEX + type); if (typeKey.IsUndefined()) { return false; } JSTaggedValue keyPattern = Get(index + PATTERN_INDEX); if (keyPattern.IsUndefined()) { return false; } uint8_t flagsBits = static_cast(flags.GetInt()); JSTaggedValue keyFlags = Get(index + FLAG_INDEX); uint8_t keyFlagsBits = static_cast(keyFlags.GetInt()); if (flagsBits != keyFlagsBits) { return false; } uint32_t lastIndexInputInt = static_cast(lastIndexInputValue.GetInt()); JSTaggedValue keyLastIndexInput = Get(index + LAST_INDEX_INPUT_INDEX); uint32_t keyLastIndexInputInt = static_cast(keyLastIndexInput.GetInt()); if (lastIndexInputInt != keyLastIndexInputInt) { return false; } JSTaggedValue keyInput = Get(index + INPUT_STRING_INDEX); JSTaggedValue keyExtend = Get(index + EXTEND_INDEX); EcmaString *patternStr = EcmaString::Cast(pattern.GetTaggedObject()); EcmaString *inputStr = EcmaString::Cast(input.GetTaggedObject()); EcmaString *keyPatternStr = EcmaString::Cast(keyPattern.GetTaggedObject()); EcmaString *keyInputStr = EcmaString::Cast(keyInput.GetTaggedObject()); bool extendEqual = false; if (extend.IsString() && keyExtend.IsString()) { EcmaString *extendStr = EcmaString::Cast(extend.GetTaggedObject()); EcmaString *keyExtendStr = EcmaString::Cast(keyExtend.GetTaggedObject()); extendEqual = EcmaStringAccessor::StringsAreEqual(extendStr, keyExtendStr); } else if (extend.IsUndefined() && keyExtend.IsUndefined()) { extendEqual = true; } else { return false; } return extendEqual && EcmaStringAccessor::StringsAreEqual(patternStr, keyPatternStr) && EcmaStringAccessor::StringsAreEqual(inputStr, keyInputStr); } JSTaggedValue RegExpGlobalResult::CreateGlobalResultTable(JSThread *thread) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); uint32_t initialLength = GLOBAL_TABLE_SIZE + INITIAL_CAPTURE_INDICES; auto table = static_cast( *factory->NewTaggedArray(initialLength, JSTaggedValue::Undefined())); // initialize dollars with empty string JSTaggedValue emptyString = factory->GetEmptyString().GetTaggedValue(); for (uint32_t i = 1; i <= DOLLAR_NUMBER; i++) { table->SetCapture(thread, CAPTURE_START_INDEX + i, emptyString); } // initialize match info table->SetTotalCaptureCounts(thread, JSTaggedValue(0)); table->SetInputString(thread, emptyString); for (uint32_t i = 0; i < INITIAL_CAPTURE_INDICES / 2; i++) { // 2: capture pair table->SetStartOfCaptureIndex(thread, i, JSTaggedValue(0)); table->SetEndOfCaptureIndex(thread, i, JSTaggedValue(0)); } return JSTaggedValue(table); } JSHandle RegExpGlobalResult::GrowCapturesCapacity(JSThread *thread, JSHandleresult, uint32_t length) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle newResult = factory->ExtendArray( JSHandle(result), length, JSTaggedValue(0)); thread->GetCurrentEcmaContext()->SetRegExpGlobalResult(newResult.GetTaggedValue()); return JSHandle(newResult); } bool BuiltinsRegExp::GetFlag(JSThread *thread, const JSHandle regexp, uint32_t flag, bool isFastPath) { if (isFastPath) { uint8_t flagsBits = static_cast(JSHandle::Cast(regexp)->GetOriginalFlags().GetInt()); return (flagsBits & flag) != 0; } else { JSMutableHandle flagStr(thread, JSTaggedValue::Undefined()); switch (flag) { case RegExpParser::FLAG_GLOBAL: flagStr.Update(thread->GlobalConstants()->GetHandledGlobalString()); break; case RegExpParser::FLAG_UTF16: flagStr.Update(thread->GlobalConstants()->GetHandledUnicodeString()); break; case RegExpParser::FLAG_STICKY: flagStr.Update(thread->GlobalConstants()->GetHandledStickyString()); break; case RegExpParser::FLAG_MULTILINE: flagStr.Update(thread->GlobalConstants()->GetHandledMultilineString()); break; case RegExpParser::FLAG_IGNORECASE: flagStr.Update(thread->GlobalConstants()->GetHandledIgnoreCaseString()); break; case RegExpParser::FLAG_HASINDICES: flagStr.Update(thread->GlobalConstants()->GetHandledHasIndicesString()); break; case RegExpParser::FLAG_DOTALL: UNREACHABLE(); default: break; } JSTaggedValue globalValue = ObjectFastOperator::FastGetPropertyByValue(thread, regexp.GetTaggedValue(), flagStr.GetTaggedValue()); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false); return globalValue.ToBoolean(); } } bool BuiltinsRegExp::GetOriginalFlag(JSThread *thread, const JSHandle regexp, uint32_t flag) { return GetFlag(thread, regexp, flag, true); } void BuiltinsRegExp::SetLastIndex(JSThread *thread, const JSHandle regexp, JSTaggedValue lastIndex, bool isFastPath) { if (isFastPath) { JSHandle::Cast(regexp)->SetPropertyInlinedPropsWithRep(thread, LAST_INDEX_OFFSET, lastIndex); return; } ObjectFastOperator::FastSetPropertyByValue(thread, regexp.GetTaggedValue(), thread->GlobalConstants()->GetHandledLastIndexString().GetTaggedValue(), lastIndex); } int64_t BuiltinsRegExp::GetLastIndex(JSThread *thread, const JSHandle regexp, bool isFastPath) { if (isFastPath) { return JSHandle::Cast(regexp)->GetPropertyInlinedProps(LAST_INDEX_OFFSET).GetInt(); } JSHandle lastIndexHandle(thread, ObjectFastOperator::FastGetPropertyByValue( thread, regexp.GetTaggedValue(), thread->GlobalConstants()->GetHandledLastIndexString().GetTaggedValue())); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false); JSTaggedNumber thisIndex = JSTaggedValue::ToLength(thread, lastIndexHandle); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false); return thisIndex.GetNumber(); } JSTaggedValue BuiltinsRegExp::GetExecResultIndex(JSThread *thread, const JSHandle &execResults, bool isFastPath) { if (isFastPath) { return JSHandle::Cast(execResults)->GetPropertyInlinedProps(EXEC_RESULT_INDEX_OFFSET); } JSHandle resultIndex = thread->GlobalConstants()->GetHandledIndexString(); JSTaggedValue index = ObjectFastOperator::FastGetPropertyByValue( thread, execResults.GetTaggedValue(), resultIndex.GetTaggedValue()); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); return index; } JSTaggedValue BuiltinsRegExp::GetExecResultGroups(JSThread *thread, const JSHandle &execResults, bool isFastPath) { if (isFastPath) { return JSHandle::Cast(execResults)->GetPropertyInlinedProps(EXEC_RESULT_GROUPS_OFFSET); } JSHandle groupKey = thread->GlobalConstants()->GetHandledGroupsString(); JSTaggedValue groups = ObjectFastOperator::FastGetPropertyByValue( thread, execResults.GetTaggedValue(), groupKey.GetTaggedValue()); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); return groups; } JSHandle BuiltinsRegExp::CreateStringFromResultArray(JSThread *thread, const JSHandle resultArray, const std::vector &resultLengthArray, JSHandle srcString, uint32_t resultStrLength, bool isUtf8) { JSHandle result = JSHandle(thread, EcmaStringAccessor::CreateLineString(thread->GetEcmaVM(), resultStrLength, isUtf8)); FlatStringInfo resultInfo = FlatStringInfo(*result, 0, resultStrLength); FlatStringInfo flatStrInfo = EcmaStringAccessor::FlattenAllString(thread->GetEcmaVM(), srcString); if (EcmaStringAccessor(srcString).IsTreeString()) { // use flattenedString as srcString srcString = JSHandle(thread, flatStrInfo.GetString()); } uint32_t nextPos = 0; uint32_t resultArrayLength = resultArray->GetLength(); for (int i = 0; i < static_cast(resultArrayLength); i++) { JSTaggedValue substrValue = resultArray->Get(thread, i); if (substrValue.IsHole()) { continue; } resultInfo.SetStartIndex(nextPos); if (substrValue.IsUndefined()) { uint64_t bits = resultLengthArray[i]; uint32_t subLength = ReplaceLengthField::Decode(bits); uint32_t subPosition = ReplacePositionField::Decode(bits); if (isUtf8) { EcmaStringAccessor::WriteToFlatWithPos(*srcString, resultInfo.GetDataUtf8Writable(), subLength, subPosition); } else { EcmaStringAccessor::WriteToFlatWithPos(*srcString, resultInfo.GetDataUtf16Writable(), subLength, subPosition); } nextPos += subLength; } else { EcmaString *replacementStr = EcmaString::Cast(substrValue.GetTaggedObject()); uint32_t replaceLength = static_cast(resultLengthArray[i]); if (isUtf8) { EcmaStringAccessor::WriteToFlat(replacementStr, resultInfo.GetDataUtf8Writable(), replaceLength); } else { EcmaStringAccessor::WriteToFlat(replacementStr, resultInfo.GetDataUtf16Writable(), replaceLength); } nextPos += replaceLength; } } return result; } } // namespace panda::ecmascript::builtins