* 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/base/config.h"
#include "ecmascript/builtins/builtins_regexp-inl.h"
#include "ecmascript/checkpoint/thread_state_transition.h"
#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/regexp/regexp_executor.h"
#include "ecmascript/regexp/regexp_parser_cache.h"
#include "ecmascript/tagged_array-inl.h"
namespace panda::ecmascript::builtins {
JSTaggedValue BuiltinsRegExp::RegExpConstructor(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), RegExp, Constructor);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> newTargetTemp = GetNewTarget(argv);
JSHandle<JSTaggedValue> pattern = GetCallArg(argv, 0);
JSHandle<JSTaggedValue> flags = GetCallArg(argv, 1);
bool patternIsRegExp = JSObject::IsRegExp(thread, pattern);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> newTarget;
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
if (!newTargetTemp->IsUndefined()) {
newTarget = newTargetTemp;
} else {
auto ecmaVm = thread->GetEcmaVM();
JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
newTarget = env->GetRegExpFunction();
JSHandle<JSTaggedValue> constructorString = globalConst->GetHandledConstructorString();
if (patternIsRegExp && flags->IsUndefined()) {
JSTaggedValue patternConstructor = ObjectFastOperator::FastGetPropertyByValue(
thread, pattern.GetTaggedValue(), constructorString.GetTaggedValue());
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (JSTaggedValue::SameValue(thread, newTarget.GetTaggedValue(), patternConstructor)) {
return pattern.GetTaggedValue();
}
}
}
bool isJsReg = false;
if (pattern->IsECMAObject()) {
JSHandle<JSObject> patternObj = JSHandle<JSObject>::Cast(pattern);
isJsReg = patternObj->IsJSRegExp();
}
JSHandle<JSTaggedValue> patternTemp;
JSHandle<JSTaggedValue> flagsTemp;
if (isJsReg) {
JSHandle<JSRegExp> patternReg(thread, JSRegExp::Cast(pattern->GetTaggedObject()));
patternTemp = JSHandle<JSTaggedValue>(thread, patternReg->GetOriginalSource(thread));
if (flags->IsUndefined()) {
flagsTemp = JSHandle<JSTaggedValue>(thread, patternReg->GetOriginalFlags(thread));
JSHandle<JSTaggedValue> object(thread, RegExpAlloc(thread, newTarget));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSTaggedValue result = RegExpInitialize<false>(thread, object, patternTemp, flagsTemp);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return JSTaggedValue(result);
} else {
flagsTemp = flags;
}
} else if (patternIsRegExp) {
JSHandle<JSTaggedValue> sourceString(globalConst->GetHandledSourceString());
JSHandle<JSTaggedValue> flagsString(globalConst->GetHandledFlagsString());
patternTemp = JSObject::GetProperty(thread, pattern, sourceString).GetValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (flags->IsUndefined()) {
flagsTemp = JSObject::GetProperty(thread, pattern, flagsString).GetValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
} else {
flagsTemp = flags;
}
} else {
patternTemp = pattern;
flagsTemp = flags;
}
JSHandle<JSTaggedValue> object(thread, RegExpAlloc(thread, newTarget));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSTaggedValue result = RegExpInitialize(thread, object, patternTemp, flagsTemp);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return JSTaggedValue(result);
}
JSTaggedValue BuiltinsRegExp::Exec(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), RegExp, Exec);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisObj = GetThis(argv);
JSHandle<JSTaggedValue> inputStr = GetCallArg(argv, 0);
JSHandle<EcmaString> stringHandle = JSTaggedValue::ToString(thread, inputStr);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> string = JSHandle<JSTaggedValue>::Cast(stringHandle);
if (!thisObj->IsECMAObject()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception());
}
if (!thisObj->IsJSRegExp()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "this does not have [[RegExpMatcher]]", JSTaggedValue::Exception());
}
bool useCache = true;
bool isFastPath = IsFastRegExp(thread, thisObj.GetTaggedValue());
JSHandle<RegExpExecResultCache> cacheTable(thread->GetGlobalEnv()->GetRegExpCache());
if (!isFastPath || cacheTable->GetLargeStrCount() == 0 || cacheTable->GetConflictCount() == 0) {
useCache = false;
}
return RegExpBuiltinExec(thread, thisObj, string, isFastPath, useCache);
}
JSTaggedValue BuiltinsRegExp::Test(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), RegExp, Test);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisObj = GetThis(argv);
JSHandle<JSTaggedValue> inputStr = GetCallArg(argv, 0);
if (!thisObj->IsECMAObject()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception());
}
JSHandle<EcmaString> stringHandle = JSTaggedValue::ToString(thread, inputStr);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> string = JSHandle<JSTaggedValue>::Cast(stringHandle);
JSHandle<RegExpExecResultCache> cacheTable(thread->GetGlobalEnv()->GetRegExpCache());
cacheTable->SetUseLastMatch(thread, false);
if (IsFastRegExp(thread, thisObj.GetTaggedValue())) {
return RegExpTestFast(thread, thisObj, string, true);
}
JSTaggedValue matchResult = RegExpExec(thread, thisObj, string, false);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return GetTaggedBoolean(!matchResult.IsNull());
}
bool BuiltinsRegExp::IsFastRegExp(JSThread *thread, JSTaggedValue regexp,
RegExpSymbol symbolTag)
{
DISALLOW_GARBAGE_COLLECTION;
JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
JSHClass *hclass = JSObject::Cast(regexp)->GetJSHClass();
JSHClass *originHClass = JSHClass::Cast(env->GetRegExpFuncInstanceClass().GetTaggedValue().GetTaggedObject());
if (hclass != originHClass) {
return false;
}
JSTaggedValue lastIndex = JSObject::Cast(regexp)->GetPropertyInlinedProps(thread, LAST_INDEX_OFFSET);
if (!lastIndex.IsInt() || lastIndex.GetInt() < 0) {
return false;
}
JSTaggedValue proto = hclass->GetPrototype(thread);
JSHClass *regexpHclass = proto.GetTaggedObject()->GetClass();
JSHandle<JSTaggedValue> originRegexpClassValue = env->GetRegExpPrototypeClass();
JSHClass *originRegexpHclass = JSHClass::Cast(originRegexpClassValue.GetTaggedValue().GetTaggedObject());
if (regexpHclass != originRegexpHclass) {
return false;
}
JSObject* protoObj = JSObject::Cast(proto);
JSTaggedValue execVal = protoObj->GetPropertyInlinedProps(thread, 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(thread, \
JSRegExp::UpperCase##_INLINE_PROPERTY_INDEX); \
if (symbolFunc != env->GetTaggedRegExp##Camel##Function()) { \
return false; \
} \
break;
REGEXP_SYMBOL_FUNCTION_LIST(V)
#undef V
}
if (env->GetRegExpFlagsDetector()) {
return false;
}
return true;
}
JSTaggedValue BuiltinsRegExp::RegExpTestFast(JSThread *thread, JSHandle<JSTaggedValue> regexp,
const JSHandle<JSTaggedValue> inputStr, bool useCache)
{
ASSERT(inputStr->IsString());
uint32_t lastIndex = static_cast<uint32_t>(GetLastIndex(thread, regexp, true));
JSHandle<RegExpExecResultCache> cacheTable(thread->GetGlobalEnv()->GetRegExpCache());
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
if (useCache) {
JSTaggedValue cacheResult;
if (UNLIKELY(thread->NeedReadBarrier())) {
cacheResult = cacheTable->FindCachedResult<RBMode::FAST_CMC_RB>(thread, inputStr,
RegExpExecResultCache::TEST_TYPE, regexp,
JSTaggedValue(lastIndex), undefined);
} else {
cacheResult = cacheTable->FindCachedResult<RBMode::FAST_NO_RB>(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<EcmaString> inputString = JSHandle<EcmaString>::Cast(inputStr);
bool matchResult = RegExpExecInternal(thread, regexp, inputString, lastIndex);
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<JSTaggedValue>(thread, JSTaggedValue(matchResult)),
RegExpExecResultCache::TEST_TYPE,
lastIndex, 0, undefined);
}
return JSTaggedValue::False();
}
JSHandle<RegExpGlobalResult> globalTable(thread->GetGlobalEnv()->GetRegExpGlobalResult());
JSTaggedValue endIndex = globalTable->GetEndIndex();
uint32_t newLastIndex = lastIndex;
if (ifUpdateLastIndex) {
newLastIndex = static_cast<uint32_t>(endIndex.GetInt());
SetLastIndex(thread, regexp, endIndex, true);
}
if (useCache) {
RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, inputStr,
JSHandle<JSTaggedValue>(thread, JSTaggedValue(matchResult)),
RegExpExecResultCache::TEST_TYPE,
lastIndex, newLastIndex, undefined);
}
return GetTaggedBoolean(matchResult);
}
JSTaggedValue BuiltinsRegExp::ToString(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), RegExp, ToString);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisObj = GetThis(argv);
auto ecmaVm = thread->GetEcmaVM();
if (!thisObj->IsECMAObject()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception());
}
const GlobalEnvConstants *globalConstants = thread->GlobalConstants();
JSMutableHandle<JSTaggedValue> getSource(thread, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> getFlags(thread, JSTaggedValue::Undefined());
JSHandle<EcmaString> sourceStrHandle;
JSHandle<EcmaString> flagsStrHandle;
if (IsFastRegExp(thread, thisObj.GetTaggedValue())) {
JSHandle<JSRegExp> regexp(thread, JSRegExp::Cast(thisObj->GetTaggedObject()));
getSource.Update(regexp->GetOriginalSource(thread));
sourceStrHandle = JSTaggedValue::ToString(thread, getSource);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
uint8_t flagsBits = static_cast<uint8_t>(regexp->GetOriginalFlags(thread).GetInt());
getFlags.Update(FlagsBitsToString(thread, flagsBits));
flagsStrHandle = JSTaggedValue::ToString(thread, getFlags);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
} else {
JSHandle<JSTaggedValue> sourceString(globalConstants->GetHandledSourceString());
JSHandle<JSTaggedValue> flagsString(globalConstants->GetHandledFlagsString());
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<EcmaString> slashStr = JSHandle<EcmaString>::Cast(globalConstants->GetHandledBackslashString());
ObjectFactory *factory = ecmaVm->GetFactory();
JSHandle<EcmaString> tempStr = factory->ConcatFromString(slashStr, sourceStrHandle);
JSHandle<EcmaString> resultTemp = factory->ConcatFromString(tempStr, slashStr);
return factory->ConcatFromString(resultTemp, flagsStrHandle).GetTaggedValue();
}
JSTaggedValue BuiltinsRegExp::GetFlags(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), RegExp, GetFlags);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisObj = GetThis(argv);
if (!thisObj->IsECMAObject()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception());
}
if (!IsFastRegExp(thread, thisObj.GetTaggedValue())) {
return GetAllFlagsInternal(thread, thisObj);
}
uint8_t flagsBits =
static_cast<uint8_t>(JSRegExp::Cast(thisObj->GetTaggedObject())->GetOriginalFlags(thread).GetInt());
return FlagsBitsToString(thread, flagsBits);
}
JSTaggedValue BuiltinsRegExp::GetAllFlagsInternal(JSThread *thread, JSHandle<JSTaggedValue> &thisObj)
{
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
const GlobalEnvConstants *globalConstants = thread->GlobalConstants();
uint8_t *flagsStr = new uint8_t[RegExpParser::FLAG_NUM + 1];
if (flagsStr == nullptr) {
LOG_ECMA(FATAL) << "BuiltinsRegExp::GetAllFlagsInternal:flagsStr is nullptr";
}
size_t flagsLen = 0;
JSHandle<EcmaString> emptyString = factory->GetEmptyString();
JSHandle<JSTaggedValue> hasIndicesKey(factory->NewFromASCII("hasIndices"));
JSHandle<JSTaggedValue> 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<JSTaggedValue> globalKey(globalConstants->GetHandledGlobalString());
JSHandle<JSTaggedValue> 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<JSTaggedValue> ignoreCaseKey(factory->NewFromASCII("ignoreCase"));
JSHandle<JSTaggedValue> 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<JSTaggedValue> multilineKey(factory->NewFromASCII("multiline"));
JSHandle<JSTaggedValue> 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<JSTaggedValue> dotAllKey(factory->NewFromASCII("dotAll"));
JSHandle<JSTaggedValue> 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<JSTaggedValue> unicodeKey(globalConstants->GetHandledUnicodeString());
JSHandle<JSTaggedValue> 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<JSTaggedValue> stickyKey(globalConstants->GetHandledStickyString());
JSHandle<JSTaggedValue> 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<EcmaString> flagsString = factory->NewFromUtf8(flagsStr, flagsLen);
delete[] flagsStr;
return flagsString.GetTaggedValue();
}
JSTaggedValue BuiltinsRegExp::GetGlobal(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
JSThread *thread = argv->GetThread();
BUILTINS_API_TRACE(thread, RegExp, GetGlobal);
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisObj = GetThis(argv);
JSHandle<JSTaggedValue> constructor = GetConstructor(argv);
return GetFlagsInternal(thread, thisObj, constructor, RegExpParser::FLAG_GLOBAL);
}
JSTaggedValue BuiltinsRegExp::GetHasIndices(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
JSThread *thread = argv->GetThread();
BUILTINS_API_TRACE(thread, RegExp, GetHasIndices);
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisObj = GetThis(argv);
JSHandle<JSTaggedValue> constructor = GetConstructor(argv);
return GetFlagsInternal(thread, thisObj, constructor, RegExpParser::FLAG_HASINDICES);
}
JSTaggedValue BuiltinsRegExp::GetIgnoreCase(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
JSThread *thread = argv->GetThread();
BUILTINS_API_TRACE(thread, RegExp, GetIgnoreCase);
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisObj = GetThis(argv);
JSHandle<JSTaggedValue> constructor = GetConstructor(argv);
return GetFlagsInternal(thread, thisObj, constructor, RegExpParser::FLAG_IGNORECASE);
}
JSTaggedValue BuiltinsRegExp::GetMultiline(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
JSThread *thread = argv->GetThread();
BUILTINS_API_TRACE(thread, RegExp, GetMultiline);
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisObj = GetThis(argv);
JSHandle<JSTaggedValue> 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<JSTaggedValue> thisObj = GetThis(argv);
JSHandle<JSTaggedValue> constructor = GetConstructor(argv);
return GetFlagsInternal(thread, thisObj, constructor, RegExpParser::FLAG_DOTALL);
}
JSTaggedValue BuiltinsRegExp::GetSource(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
JSThread *thread = argv->GetThread();
BUILTINS_API_TRACE(thread, RegExp, GetSource);
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisObj = GetThis(argv);
if (!thisObj->IsECMAObject()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception());
}
if (!thisObj->IsJSRegExp()) {
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
JSHandle<JSTaggedValue> constructorKey = globalConst->GetHandledConstructorString();
JSHandle<JSTaggedValue> objConstructor = JSTaggedValue::GetProperty(thread, thisObj, constructorKey).GetValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> constructor = GetConstructor(argv);
if (objConstructor->IsJSFunction() && constructor->IsJSFunction()) {
JSHandle<GlobalEnv> objRealm = JSObject::GetFunctionRealm(thread, objConstructor);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<GlobalEnv> ctorRealm = JSObject::GetFunctionRealm(thread, constructor);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (objRealm->GetRegExpPrototype() == thisObj && *objRealm == *ctorRealm) {
JSHandle<EcmaString> result = thread->GetEcmaVM()->GetFactory()->NewFromASCII("(?:)");
return result.GetTaggedValue();
}
}
THROW_TYPE_ERROR_AND_RETURN(thread, "this does not have [[OriginalSource]]", JSTaggedValue::Exception());
}
JSHandle<JSRegExp> regexpObj(thread, JSRegExp::Cast(thisObj->GetTaggedObject()));
JSHandle<JSTaggedValue> source(thread, regexpObj->GetOriginalSource(thread));
uint8_t flagsBits = static_cast<uint8_t>(regexpObj->GetOriginalFlags(thread).GetInt());
JSHandle<JSTaggedValue> flags(thread, FlagsBitsToString(thread, flagsBits));
return JSTaggedValue(EscapeRegExpPattern(thread, source, flags));
}
JSTaggedValue BuiltinsRegExp::GetSticky(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
JSThread *thread = argv->GetThread();
BUILTINS_API_TRACE(thread, RegExp, GetSticky);
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisObj = GetThis(argv);
JSHandle<JSTaggedValue> constructor = GetConstructor(argv);
return GetFlagsInternal(thread, thisObj, constructor, RegExpParser::FLAG_STICKY);
}
JSTaggedValue BuiltinsRegExp::GetUnicode(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
JSThread *thread = argv->GetThread();
BUILTINS_API_TRACE(thread, RegExp, GetUnicode);
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisObj = GetThis(argv);
JSHandle<JSTaggedValue> constructor = GetConstructor(argv);
return GetFlagsInternal(thread, thisObj, constructor, RegExpParser::FLAG_UTF16);
}
JSTaggedValue BuiltinsRegExp::GetSpecies(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), RegExp, GetSpecies);
return GetThis(argv).GetTaggedValue();
}
JSTaggedValue BuiltinsRegExp::Match(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), RegExp, Match);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisObj = GetThis(argv);
JSHandle<JSTaggedValue> inputString = GetCallArg(argv, 0);
JSHandle<EcmaString> stringHandle = JSTaggedValue::ToString(thread, inputString);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> string = JSHandle<JSTaggedValue>::Cast(stringHandle);
if (!thisObj->IsECMAObject()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception());
}
bool isFastPath = IsFastRegExp(thread, thisObj.GetTaggedValue(), RegExpSymbol::MATCH);
return RegExpMatch(thread, thisObj, string, isFastPath);
}
JSTaggedValue BuiltinsRegExp::RegExpMatch(JSThread *thread, const JSHandle<JSTaggedValue> regexp,
const JSHandle<JSTaggedValue> string, bool isFastPath)
{
bool useCache = true;
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
JSHandle<RegExpExecResultCache> cacheTable(thread->GetGlobalEnv()->GetRegExpCache());
cacheTable->SetUseLastMatch(thread, false);
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);
if (!isGlobal) {
if (isFastPath) {
return RegExpBuiltinExec(thread, regexp, string, isFastPath, useCache);
} else {
return RegExpExec(thread, regexp, string, useCache);
}
}
if (useCache) {
uint32_t lastIndex = static_cast<uint32_t>(GetLastIndex(thread, regexp, isFastPath));
JSTaggedValue cacheResult;
if (UNLIKELY(thread->NeedReadBarrier())) {
cacheResult = cacheTable->FindCachedResult<RBMode::FAST_CMC_RB>(thread, string,
RegExpExecResultCache::MATCH_TYPE, regexp,
JSTaggedValue(lastIndex), undefined);
} else {
cacheResult = cacheTable->FindCachedResult<RBMode::FAST_NO_RB>(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);
SetLastIndex(thread, regexp, JSTaggedValue(0), isFastPath);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSArray> array(JSArray::ArrayCreate(thread, JSTaggedNumber(0), ArrayMode::LITERAL));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
TaggedArray *srcElements = TaggedArray::Cast(array->GetElements(thread).GetTaggedObject());
JSMutableHandle<TaggedArray> elements(thread, srcElements);
uint32_t resultNum = 0;
uint32_t arrLen = 1;
JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
JSMutableHandle<EcmaString> matchString(thread, JSTaggedValue::Undefined());
while (true) {
if (isFastPath) {
uint32_t lastIndex = static_cast<uint32_t>(GetLastIndex(thread, regexp, isFastPath));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
result.Update(RegExpBuiltinExecWithoutResult(thread, regexp, string, isFastPath, lastIndex, false));
JSHandle<RegExpGlobalResult> globalTable(thread->GetGlobalEnv()->GetRegExpGlobalResult());
uint32_t endIndex = static_cast<uint32_t>(globalTable->GetEndOfCaptureIndex(0).GetInt());
if (result->IsNull()) {
lastIndex = static_cast<uint32_t>(GetLastIndex(thread, regexp, isFastPath));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (resultNum == 0) {
RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, string,
JSHandle<JSTaggedValue>(thread, JSTaggedValue::Null()),
RegExpExecResultCache::MATCH_TYPE,
0, 0, undefined);
return JSTaggedValue::Null();
}
RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, string,
JSHandle<JSTaggedValue>::Cast(array),
RegExpExecResultCache::MATCH_TYPE,
0, 0, undefined);
return array.GetTaggedValue();
}
uint32_t startIndex = static_cast<uint32_t>(globalTable->GetStartOfCaptureIndex(0).GetInt());
uint32_t len = endIndex - startIndex;
matchString.Update(JSTaggedValue(EcmaStringAccessor::FastSubString(
thread->GetEcmaVM(), JSHandle<EcmaString>::Cast(string), startIndex, len)));
} else {
result.Update(RegExpExec(thread, regexp, string, useCache));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (result->IsNull()) {
if (resultNum == 0) {
return JSTaggedValue::Null();
}
return array.GetTaggedValue();
}
JSHandle<JSTaggedValue> zeroString = thread->GlobalConstants()->GetHandledZeroString();
JSTaggedValue matchVal = ObjectFastOperator::FastGetPropertyByValue(
thread, result.GetTaggedValue(), zeroString.GetTaggedValue());
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> matchStr(thread, matchVal);
matchString.Update(JSTaggedValue::ToString(thread, matchStr));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
JSHandle<JSTaggedValue> matchValue = JSHandle<JSTaggedValue>::Cast(matchString);
if (arrLen > elements->GetLength()) {
elements.Update(JSObject::GrowElementsCapacity(thread,
JSHandle<JSObject>::Cast(array), elements->GetLength(), true));
}
elements->Set(thread, arrLen - 1, matchValue);
array->SetArrayLength(thread, arrLen);
arrLen++;
if (EcmaStringAccessor(matchString).GetLength() == 0) {
int64_t lastIndex = GetLastIndex(thread, regexp, isFastPath);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSTaggedValue nextIndex = JSTaggedValue(AdvanceStringIndex(thread, string, lastIndex, fullUnicode));
SetLastIndex(thread, regexp, nextIndex, isFastPath);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
resultNum++;
}
}
JSTaggedValue BuiltinsRegExp::MatchAll(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
JSThread *thread = argv->GetThread();
BUILTINS_API_TRACE(thread, RegExp, MatchAll);
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisObj = GetThis(argv);
if (!thisObj->IsECMAObject()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception());
}
JSHandle<JSTaggedValue> inputString = GetCallArg(argv, 0);
JSHandle<EcmaString> stringHandle = JSTaggedValue::ToString(thread, inputString);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
bool isFastPath = IsFastRegExp(thread, thisObj.GetTaggedValue(), RegExpSymbol::MATCHALL);
return RegExpMatchAll(thread, thisObj, stringHandle, isFastPath);
}
JSTaggedValue BuiltinsRegExp::RegExpMatchAll(JSThread *thread, const JSHandle<JSTaggedValue> regexp,
const JSHandle<EcmaString> string, bool isFastPath)
{
JSMutableHandle<JSTaggedValue> matcher(thread, JSTaggedValue::Undefined());
bool global = false;
bool fullUnicode = false;
if (isFastPath) {
JSHandle<JSRegExp> jsRegExp = JSHandle<JSRegExp>::Cast(regexp);
JSHandle<JSTaggedValue> pattern(thread, jsRegExp->GetOriginalSource(thread));
JSHandle<JSTaggedValue> flags(thread, jsRegExp->GetOriginalFlags(thread));
matcher.Update(BuiltinsRegExp::RegExpCreateWithRawFlags(thread, pattern, flags));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
SetLastIndex(thread, matcher,
JSHandle<JSObject>::Cast(jsRegExp)->GetPropertyInlinedProps(thread, 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();
JSHandle<JSTaggedValue> defaultConstructor = ecmaVm->GetGlobalEnv()->GetRegExpFunction();
JSHandle<JSObject> objHandle(regexp);
JSHandle<JSTaggedValue> constructor = JSObject::SpeciesConstructor(thread, objHandle, defaultConstructor);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
const GlobalEnvConstants *globalConstants = thread->GlobalConstants();
JSHandle<JSTaggedValue> flagsString(globalConstants->GetHandledFlagsString());
JSHandle<JSTaggedValue> getFlags(JSObject::GetProperty(thread, regexp, flagsString).GetValue());
JSHandle<EcmaString> flagsStrHandle = JSTaggedValue::ToString(thread, getFlags);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> undefined = globalConstants->GetHandledUndefined();
EcmaRuntimeCallInfo *runtimeInfo =
EcmaInterpreter::NewRuntimeCallInfo(thread, constructor, undefined, undefined, 2);
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);
JSHandle<JSTaggedValue> lastIndexString(globalConstants->GetHandledLastIndexString());
JSHandle<JSTaggedValue> getLastIndex(JSObject::GetProperty(thread, regexp, lastIndexString).GetValue());
JSTaggedNumber thisLastIndex = JSTaggedValue::ToLength(thread, getLastIndex);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
ObjectFastOperator::FastSetPropertyByValue(thread, matcher.GetTaggedValue(), lastIndexString.GetTaggedValue(),
thisLastIndex);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> gString(globalConstants->GetHandledGString());
if (EcmaStringAccessor::IndexOf(ecmaVm, flagsStrHandle, gString) != -1) {
global = true;
}
JSHandle<EcmaString> uString(globalConstants->GetHandledUString());
if (EcmaStringAccessor::IndexOf(ecmaVm, flagsStrHandle, uString) != -1) {
fullUnicode = true;
}
}
return JSRegExpIterator::CreateRegExpStringIterator(thread, matcher,
string, global, fullUnicode).GetTaggedValue();
}
JSTaggedValue BuiltinsRegExp::RegExpReplaceFast(JSThread *thread, JSHandle<JSTaggedValue> regexp,
JSHandle<EcmaString> inputString, uint32_t inputLength)
{
ASSERT(regexp->IsJSRegExp());
BUILTINS_API_TRACE(thread, RegExp, RegExpReplaceFast);
JSHandle<JSRegExp> regexpHandle(regexp);
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSTaggedValue bufferData = JSRegExp::Cast(regexp->GetTaggedObject())->GetByteCodeBuffer(thread);
void *dynBuf = JSNativePointer::Cast(bufferData.GetTaggedObject())->GetExternalPointer();
auto bytecodeBuffer = reinterpret_cast<uint8_t *>(dynBuf);
uint32_t flags = *reinterpret_cast<uint32_t *>(bytecodeBuffer + RegExpParser::FLAGS_OFFSET);
JSHandle<RegExpExecResultCache> cacheTable(thread->GetGlobalEnv()->GetRegExpCache());
JSHandle<JSTaggedValue> tagInputString = JSHandle<JSTaggedValue>::Cast(inputString);
bool useCache = false;
uint32_t lastIndex = 0;
GetLastIndex(thread, regexp, lastIndex);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
auto globalConst = thread->GlobalConstants();
JSHandle<JSTaggedValue> pattern(thread, regexpHandle->GetOriginalSource(thread));
JSHandle<JSTaggedValue> flagsBits(thread, regexpHandle->GetOriginalFlags(thread));
useCache = ShouldUseCache(thread, inputString);
uint32_t lastIndexInput = lastIndex;
JSHandle<JSTaggedValue> emptyString(thread, globalConst->GetEmptyString());
if (useCache) {
JSTaggedValue cacheResult;
if (UNLIKELY(thread->NeedReadBarrier())) {
cacheResult = cacheTable->FindCachedResult<RBMode::FAST_CMC_RB>(thread, tagInputString,
RegExpExecResultCache::REPLACE_TYPE, regexp,
JSTaggedValue(lastIndexInput),
emptyString);
} else {
cacheResult = cacheTable->FindCachedResult<RBMode::FAST_NO_RB>(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<JSTaggedValue>(resultValue),
RegExpExecResultCache::REPLACE_TYPE, lastIndexInput, lastIndex,
emptyString);
}
return resultValue.GetTaggedValue();
}
JSTaggedValue BuiltinsRegExp::GetLastIndex(JSThread *thread, JSHandle<JSTaggedValue> regexp,
uint32_t &lastIndex)
{
JSTaggedValue bufferData = JSRegExp::Cast(regexp->GetTaggedObject())->GetByteCodeBuffer(thread);
void *dynBuf = JSNativePointer::Cast(bufferData.GetTaggedObject())->GetExternalPointer();
auto bytecodeBuffer = reinterpret_cast<uint8_t *>(dynBuf);
uint32_t flags = *reinterpret_cast<uint32_t *>(bytecodeBuffer + RegExpParser::FLAGS_OFFSET);
JSHandle<JSTaggedValue> 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<uint32_t>(thisIndex.GetInt());
} else {
JSHandle<JSTaggedValue> 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<EcmaString> inputString)
{
JSHandle<RegExpExecResultCache> cacheTable(thread->GetGlobalEnv()->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<JSTaggedValue> regexp,
JSHandle<EcmaString> inputString, uint32_t &flags,
uint32_t lastIndex, uint32_t inputLength,
std::string &resultString)
{
uint32_t nextPosition = 0;
JSHandle<RegExpGlobalResult> globalTable(thread->GetGlobalEnv()->GetRegExpGlobalResult());
JSHandle<JSTaggedValue> lastIndexHandle(thread->GlobalConstants()->GetHandledLastIndexString());
JSHandle<JSTaggedValue> tagInputString = JSHandle<JSTaggedValue>::Cast(inputString);
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<uint32_t>(globalTable->GetStartOfCaptureIndex(0).GetInt());
uint32_t endIndex = static_cast<uint32_t>(globalTable->GetEndIndex().GetInt());
lastIndex = endIndex;
if (nextPosition < startIndex) {
auto substr = EcmaStringAccessor::FastSubString(
thread->GetEcmaVM(), inputString, nextPosition, startIndex - nextPosition);
resultString += EcmaStringAccessor(substr).ToStdString(thread, StringConvertedUsage::LOGICOPERATION);
}
nextPosition = endIndex;
if (!(flags & RegExpParser::FLAG_GLOBAL)) {
ObjectFastOperator::FastSetPropertyByValue(thread, regexp.GetTaggedValue(),
lastIndexHandle.GetTaggedValue(),
JSTaggedValue(lastIndex));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
break;
}
if (endIndex == startIndex) {
bool unicode = EcmaStringAccessor(inputString).IsUtf16() && (flags & RegExpParser::FLAG_UTF16);
endIndex = static_cast<uint32_t>(AdvanceStringIndex(thread, tagInputString, endIndex, unicode));
}
lastIndex = endIndex;
}
auto substr = EcmaStringAccessor::FastSubString(
thread->GetEcmaVM(), inputString, nextPosition, inputLength - nextPosition);
resultString += EcmaStringAccessor(substr).ToStdString(thread, StringConvertedUsage::LOGICOPERATION);
return JSTaggedValue::Undefined();
}
JSTaggedValue BuiltinsRegExp::Replace(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), RegExp, Replace);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisObj = GetThis(argv);
if (!thisObj->IsECMAObject()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception());
}
JSHandle<JSTaggedValue> string = GetCallArg(argv, 0);
JSHandle<JSTaggedValue> inputReplaceValue = GetCallArg(argv, 1);
return ReplaceInternal(thread, thisObj, string, inputReplaceValue);
}
JSTaggedValue BuiltinsRegExp::ReplaceInternal(JSThread *thread,
JSHandle<JSTaggedValue> thisObj,
JSHandle<JSTaggedValue> string,
JSHandle<JSTaggedValue> inputReplaceValue)
{
JSHandle<EcmaString> srcString = JSTaggedValue::ToString(thread, string);
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> inputStr = JSHandle<JSTaggedValue>::Cast(srcString);
uint32_t length = EcmaStringAccessor(srcString).GetLength();
bool functionalReplace = inputReplaceValue->IsCallable();
JSHandle<EcmaString> replaceValueHandle;
bool useCache = true;
if (!functionalReplace) {
replaceValueHandle = JSTaggedValue::ToString(thread, inputReplaceValue);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
} else {
useCache = false;
}
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
bool isGlobal = false;
bool fullUnicode = false;
bool isFastPath = IsFastRegExp(thread, thisObj.GetTaggedValue(), RegExpSymbol::REPLACE);
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 {
useCache = false;
isGlobal = GetFlag(thread, thisObj, RegExpParser::FLAG_GLOBAL, isFastPath);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (isGlobal) {
fullUnicode = GetFlag(thread, thisObj, RegExpParser::FLAG_UTF16, isFastPath);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
SetLastIndex(thread, thisObj, JSTaggedValue(0), isFastPath);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
}
bool useIntermediateCache = false;
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
JSHandle<RegExpExecResultCache> cacheTable(thread->GetGlobalEnv()->GetRegExpCache());
cacheTable->SetUseLastMatch(thread, false);
if (isFastPath) {
if (isGlobal && !functionalReplace && EcmaStringAccessor(replaceValueHandle).GetLength() == 0) {
return RegExpReplaceFast(thread, thisObj, srcString, length);
}
JSHandle<JSRegExp> 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<uint32_t>(GetLastIndex(thread, thisObj, isFastPath));
JSTaggedValue cacheResult;
if (UNLIKELY(thread->NeedReadBarrier())) {
cacheResult = cacheTable->FindCachedResult<RBMode::FAST_CMC_RB>(thread, string,
RegExpExecResultCache::REPLACE_TYPE, thisObj, JSTaggedValue(lastIndexInput),
inputReplaceValue);
} else {
cacheResult = cacheTable->FindCachedResult<RBMode::FAST_NO_RB>(thread, string,
RegExpExecResultCache::REPLACE_TYPE, thisObj, JSTaggedValue(lastIndexInput),
inputReplaceValue);
}
if (!cacheResult.IsUndefined()) {
return cacheResult;
}
}
}
}
JSMutableHandle<JSObject> resultsList(thread, JSArray::ArrayCreate(thread, JSTaggedNumber(0)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
int resultsIndex = 0;
JSMutableHandle<JSTaggedValue> nextIndexHandle(thread, JSTaggedValue(0));
JSMutableHandle<JSTaggedValue> execResult(thread, JSTaggedValue(0));
JSTaggedValue cachedResultsList(JSTaggedValue::VALUE_UNDEFINED);
if (useIntermediateCache) {
uint32_t lastIndexInput = static_cast<uint32_t>(GetLastIndex(thread, thisObj, isFastPath));
if (UNLIKELY(thread->NeedReadBarrier())) {
cachedResultsList = cacheTable->FindCachedResult<RBMode::FAST_CMC_RB>(thread, string,
RegExpExecResultCache::INTERMEDIATE_REPLACE_TYPE, thisObj,
JSTaggedValue(lastIndexInput), undefined, true);
} else {
cachedResultsList = cacheTable->FindCachedResult<RBMode::FAST_NO_RB>(thread, string,
RegExpExecResultCache::INTERMEDIATE_REPLACE_TYPE, thisObj,
JSTaggedValue(lastIndexInput), undefined, true);
}
}
if (!cachedResultsList.IsUndefined()) {
resultsList.Update(cachedResultsList);
resultsIndex = static_cast<int>(JSArray::Cast(resultsList.GetTaggedValue())->GetArrayLength());
} else {
for (;;) {
execResult.Update(RegExpExec(thread, thisObj, inputStr, useCache, true));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (execResult->IsNull()) {
break;
}
TaggedArray *srcElements = TaggedArray::Cast(resultsList->GetElements(thread).GetTaggedObject());
JSMutableHandle<TaggedArray> elements(thread, srcElements);
if (resultsIndex >= static_cast<int>(elements->GetLength())) {
elements.Update(JSObject::GrowElementsCapacity(thread, resultsList, elements->GetLength(), true));
}
elements->Set(thread, resultsIndex, execResult);
JSArray::Cast(*resultsList)->SetArrayLength(thread, resultsIndex + 1);
resultsIndex++;
if (!isGlobal) {
break;
}
JSHandle<JSTaggedValue> matchedStr = globalConst->GetHandledZeroString();
JSTaggedValue getMatchVal = ObjectFastOperator::FastGetPropertyByValue(
thread, execResult.GetTaggedValue(), matchedStr.GetTaggedValue());
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> getMatch(thread, getMatchVal);
JSHandle<EcmaString> matchString = JSTaggedValue::ToString(thread, getMatch);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (EcmaStringAccessor(matchString).GetLength() == 0) {
uint32_t thisIndex = static_cast<uint32_t>(GetLastIndex(thread, thisObj, isFastPath));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
uint32_t nextIndex =
static_cast<uint32_t>(AdvanceStringIndex(thread, inputStr, thisIndex, fullUnicode));
nextIndexHandle.Update(JSTaggedValue(nextIndex));
SetLastIndex(thread, thisObj, nextIndexHandle.GetTaggedValue(), isFastPath);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
}
if (useIntermediateCache) {
RegExpExecResultCache::AddResultInCache(thread, cacheTable, thisObj, string,
JSHandle<JSTaggedValue>(resultsList),
RegExpExecResultCache::INTERMEDIATE_REPLACE_TYPE, 0, 0,
undefined, true);
}
}
bool isUtf8 = true;
uint32_t resultStrLength = 0;
uint32_t resultArrayLength = (static_cast<uint32_t>(resultsIndex) + 1) * 2;
CVector<JSHandle<JSTaggedValue>> resultArray(resultArrayLength, globalConst->GetHandledHole());
std::vector<uint64_t> resultLengthArray(resultArrayLength);
CVector<JSHandle<JSTaggedValue>> capturesList;
uint32_t nextSourcePosition = 0;
JSMutableHandle<JSTaggedValue> getMatchString(thread, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> resultValues(thread, JSTaggedValue(0));
JSMutableHandle<JSTaggedValue> ncapturesHandle(thread, JSTaggedValue(0));
JSMutableHandle<JSTaggedValue> capN(thread, JSTaggedValue(0));
for (int i = 0; i < resultsIndex; i++) {
resultValues.Update(ElementAccessor::Get(thread, resultsList, i));
uint32_t ncaptures;
if (isFastPath) {
ncaptures = static_cast<uint32_t>(JSArray::Cast(resultValues.GetTaggedValue())->GetArrayLength());
} else {
JSHandle<JSTaggedValue> lengthHandle = globalConst->GetHandledLengthString();
ncapturesHandle.Update(ObjectFastOperator::FastGetPropertyByValue(
thread, resultValues.GetTaggedValue(), lengthHandle.GetTaggedValue()));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
ncaptures = JSTaggedValue::ToUint32(thread, ncapturesHandle);
}
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
ncaptures = (ncaptures == 0) ? 0 : ncaptures - 1;
JSTaggedValue value = ObjectFastOperator::GetPropertyByIndex(thread, resultValues.GetTaggedValue(), 0);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
getMatchString.Update(value);
JSHandle<EcmaString> matchString = JSTaggedValue::ToString(thread, getMatchString);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
uint32_t matchLength = EcmaStringAccessor(matchString).GetLength();
JSTaggedValue positionTag = GetExecResultIndex(thread, resultValues, isFastPath);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> positionHandle(thread, positionTag);
uint32_t position = 0;
if (positionHandle->IsInt()) {
position = static_cast<uint32_t>(positionHandle->GetInt());
} else {
position = JSTaggedValue::ToUint32(thread, positionHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
position = std::max<uint32_t>(std::min<uint32_t>(position, length), 0);
capturesList.resize(ncaptures);
for (uint32_t index = 0; index < ncaptures; index++) {
capN.Update(ObjectFastOperator::FastGetPropertyByIndex(thread, resultValues.GetTaggedValue(), index + 1));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (!capN->IsUndefined()) {
JSHandle<EcmaString> capNStr = JSTaggedValue::ToString(thread, capN);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
capturesList[index] = JSHandle<JSTaggedValue>(thread, capNStr.GetTaggedValue());
} else {
capturesList[index] = JSHandle<JSTaggedValue>(thread, capN.GetTaggedValue());
}
}
JSTaggedValue named = GetExecResultGroups(thread, resultValues, isFastPath);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> namedCaptures(thread, named);
JSMutableHandle<EcmaString> replacementString(thread, factory->GetEmptyString());
int emptyArrLength = 0;
if (namedCaptures->IsUndefined()) {
emptyArrLength = 3;
} else {
emptyArrLength = 4;
}
if (functionalReplace) {
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread,
inputReplaceValue, undefined, undefined, emptyArrLength + ncaptures);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(0, getMatchString.GetTaggedValue());
for (uint32_t index = 0; index < ncaptures; index++) {
info->SetCallArg(index + 1, capturesList[index].GetTaggedValue());
}
info->SetCallArg(ncaptures + EXEC_RESULT_INDEX_OFFSET, JSTaggedValue(position));
info->SetCallArg(ncaptures + EXEC_RESULT_INPUT_OFFSET, inputStr.GetTaggedValue());
if (!namedCaptures->IsUndefined()) {
info->SetCallArg(ncaptures + EXEC_RESULT_GROUPS_OFFSET, namedCaptures.GetTaggedValue());
}
JSTaggedValue replaceResult = JSFunction::Call(info);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> replValue(thread, replaceResult);
replacementString.Update(JSTaggedValue::ToString(thread, replValue));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
} else {
JSHandle<TaggedArray> capturesArray = factory->NewTaggedArray(ncaptures);
if (!namedCaptures->IsUndefined()) {
JSHandle<JSObject> namedCapturesObj = JSTaggedValue::ToObject(thread, namedCaptures);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
namedCaptures = JSHandle<JSTaggedValue>::Cast(namedCapturesObj);
}
for (uint32_t index = 0; index < ncaptures; index++) {
capturesArray->Set(thread, index, capturesList[index]);
}
replacementString.Update(BuiltinsString::GetSubstitution(thread, matchString, srcString,
position, capturesArray, namedCaptures, replaceValueHandle));
}
if (position >= nextSourcePosition) {
ASSERT(REPLACE_RESULT_VAL * i + 1 < resultArray.size());
resultArray[REPLACE_RESULT_VAL * i] = globalConst->GetHandledUndefined();
uint64_t bits = 0;
bits |= ReplaceLengthField::Encode(position - nextSourcePosition);
bits |= ReplacePositionField::Encode(nextSourcePosition);
resultLengthArray[REPLACE_RESULT_VAL * i] = bits;
resultStrLength += (position - nextSourcePosition);
isUtf8 &= EcmaStringAccessor::SubStringIsUtf8(
thread->GetEcmaVM(), srcString, nextSourcePosition, position - nextSourcePosition);
resultArray[REPLACE_RESULT_VAL * i + 1] =
JSHandle<JSTaggedValue>(thread, replacementString.GetTaggedValue());
uint32_t replacementLength = EcmaStringAccessor(replacementString).GetLength();
resultLengthArray[REPLACE_RESULT_VAL * i + 1] = static_cast<uint64_t>(replacementLength);
resultStrLength += replacementLength;
if (resultStrLength >= BaseString::MAX_STRING_LENGTH) {
THROW_RANGE_ERROR_AND_RETURN(thread, "Invalid string length", JSTaggedValue::Exception());
}
isUtf8 &= EcmaStringAccessor(replacementString).IsUtf8();
nextSourcePosition = position + matchLength;
}
}
if (nextSourcePosition < length) {
resultArray[REPLACE_RESULT_VAL * resultsIndex] = globalConst->GetHandledUndefined();
uint64_t bits = 0;
bits |= ReplaceLengthField::Encode(length - nextSourcePosition);
bits |= ReplacePositionField::Encode(nextSourcePosition);
isUtf8 &= EcmaStringAccessor::SubStringIsUtf8(
thread->GetEcmaVM(), srcString, nextSourcePosition, length - nextSourcePosition);
resultLengthArray[REPLACE_RESULT_VAL * resultsIndex] = bits;
resultStrLength += (length - nextSourcePosition);
}
JSHandle<EcmaString> result =
CreateStringFromResultArray(thread, resultArray, resultLengthArray, srcString, resultStrLength, isUtf8);
if (useCache) {
RegExpExecResultCache::AddResultInCache(thread, cacheTable, thisObj, string,
JSHandle<JSTaggedValue>(result),
RegExpExecResultCache::REPLACE_TYPE, 0, nextIndexHandle->GetInt(),
inputReplaceValue);
}
return result.GetTaggedValue();
}
JSTaggedValue BuiltinsRegExp::Search(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), RegExp, Search);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisObj = GetThis(argv);
JSHandle<JSTaggedValue> inputStr = GetCallArg(argv, 0);
JSHandle<EcmaString> stringHandle = JSTaggedValue::ToString(thread, inputStr);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> string = JSHandle<JSTaggedValue>::Cast(stringHandle);
if (!thisObj->IsECMAObject()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception());
}
return RegExpSearch(thread, thisObj, string);
}
JSTaggedValue BuiltinsRegExp::RegExpSearch(JSThread *thread,
const JSHandle<JSTaggedValue> regexp,
const JSHandle<JSTaggedValue> string)
{
JSHandle<RegExpExecResultCache> cacheTable(thread->GetGlobalEnv()->GetRegExpCache());
cacheTable->SetUseLastMatch(thread, false);
bool isFastPath = IsFastRegExp(thread, regexp.GetTaggedValue(), RegExpSymbol::SEARCH);
if (isFastPath) {
return RegExpSearchFast(thread, regexp, string);
}
JSHandle<JSTaggedValue> lastIndexString(thread->GlobalConstants()->GetHandledLastIndexString());
JSHandle<JSTaggedValue> previousLastIndex = JSObject::GetProperty(thread, regexp, lastIndexString).GetValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (!JSTaggedValue::SameValue(thread, previousLastIndex.GetTaggedValue(), JSTaggedValue(0))) {
JSHandle<JSTaggedValue> value(thread, JSTaggedValue(0));
JSObject::SetProperty(thread, regexp, lastIndexString, value, true);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
JSHandle<JSTaggedValue> result(thread, RegExpExec(thread, regexp, string, false));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> currentLastIndex = JSObject::GetProperty(thread, regexp, lastIndexString).GetValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (!JSTaggedValue::SameValue(thread, previousLastIndex.GetTaggedValue(), currentLastIndex.GetTaggedValue())) {
JSObject::SetProperty(thread, regexp, lastIndexString, previousLastIndex, true);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
if (result->IsNull()) {
return JSTaggedValue(-1);
}
JSHandle<JSTaggedValue> index(thread->GlobalConstants()->GetHandledIndexString());
return JSObject::GetProperty(thread, result, index).GetValue().GetTaggedValue();
}
JSTaggedValue BuiltinsRegExp::RegExpSearchFast(JSThread *thread,
const JSHandle<JSTaggedValue> regexp,
const JSHandle<JSTaggedValue> string)
{
JSHandle<RegExpExecResultCache> cacheTable(thread->GetGlobalEnv()->GetRegExpCache());
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
JSTaggedValue cacheResult;
if (UNLIKELY(thread->NeedReadBarrier())) {
cacheResult = cacheTable->FindCachedResult<RBMode::FAST_CMC_RB>(thread, string,
RegExpExecResultCache::SEARCH_TYPE, regexp,
JSTaggedValue(0), undefined);
} else {
cacheResult = cacheTable->FindCachedResult<RBMode::FAST_NO_RB>(thread, string,
RegExpExecResultCache::SEARCH_TYPE, regexp,
JSTaggedValue(0), undefined);
}
if (!cacheResult.IsUndefined()) {
return cacheResult;
}
JSHandle<EcmaString> stringHandle = JSHandle<EcmaString>::Cast(string);
bool matchResult = RegExpExecInternal(thread, regexp, stringHandle, 0);
if (!matchResult) {
RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, string,
JSHandle<JSTaggedValue>(thread, JSTaggedValue(-1)),
RegExpExecResultCache::SEARCH_TYPE,
0, 0, undefined);
return JSTaggedValue(-1);
}
JSHandle<RegExpGlobalResult> globalTable(thread->GetGlobalEnv()->GetRegExpGlobalResult());
JSTaggedValue result = globalTable->GetStartOfCaptureIndex(0);
RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, string,
JSHandle<JSTaggedValue>(thread, JSTaggedValue(result)),
RegExpExecResultCache::SEARCH_TYPE,
0, 0, undefined);
return result;
}
JSTaggedValue BuiltinsRegExp::RegExpSplit(JSThread *thread, const JSHandle<JSTaggedValue> regexp,
JSHandle<JSTaggedValue> jsString, JSHandle<JSTaggedValue> 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<uint32_t>(lim), useCache);
}
}
}
auto ecmaVm = thread->GetEcmaVM();
JSHandle<JSTaggedValue> defaultConstructor = ecmaVm->GetGlobalEnv()->GetRegExpFunction();
JSHandle<JSObject> objHandle(regexp);
JSHandle<JSTaggedValue> constructor = JSObject::SpeciesConstructor(thread, objHandle, defaultConstructor);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
ObjectFactory *factory = ecmaVm->GetFactory();
const GlobalEnvConstants *globalConstants = thread->GlobalConstants();
JSHandle<JSTaggedValue> flagsString(globalConstants->GetHandledFlagsString());
JSHandle<JSTaggedValue> taggedFlags = JSObject::GetProperty(thread, regexp, flagsString).GetValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> flags;
if (taggedFlags->IsUndefined()) {
flags = factory->GetEmptyString();
} else {
flags = JSTaggedValue::ToString(thread, taggedFlags);
}
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> uStringHandle(globalConstants->GetHandledUString());
bool unicodeMatching = (EcmaStringAccessor::IndexOf(ecmaVm, flags, uStringHandle) != -1);
JSHandle<EcmaString> newFlagsHandle;
JSHandle<EcmaString> yStringHandle = JSHandle<EcmaString>::Cast(globalConstants->GetHandledYString());
if (EcmaStringAccessor::IndexOf(ecmaVm, flags, yStringHandle) != -1) {
newFlagsHandle = flags;
} else {
JSHandle<EcmaString> yStr = JSHandle<EcmaString>::Cast(globalConstants->GetHandledYString());
newFlagsHandle = factory->ConcatFromString(flags, yStr);
}
JSHandle<JSObject> globalObject(thread, thread->GetEcmaVM()->GetGlobalEnv()->GetGlobalObject());
JSHandle<JSTaggedValue> undefined = globalConstants->GetHandledUndefined();
EcmaRuntimeCallInfo *runtimeInfo =
EcmaInterpreter::NewRuntimeCallInfo(thread, constructor, undefined, undefined, 2);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
runtimeInfo->SetCallArg(regexp.GetTaggedValue(), newFlagsHandle.GetTaggedValue());
JSTaggedValue taggedSplitter = JSFunction::Construct(runtimeInfo);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> splitter(thread, taggedSplitter);
JSHandle<JSObject> array(JSArray::ArrayCreate(thread, JSTaggedNumber(0)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
uint32_t aLength = 0;
uint32_t lim;
if (limit->IsUndefined()) {
lim = MAX_SPLIT_LIMIT;
} else {
lim = JSTaggedValue::ToUint32(thread, limit);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
uint32_t size = EcmaStringAccessor(jsString->GetTaggedObject()).GetLength();
uint32_t startIndex = 0;
if (lim == 0) {
return JSTaggedValue(static_cast<JSArray *>(array.GetTaggedValue().GetTaggedObject()));
}
if (size == 0) {
JSHandle<JSTaggedValue> execResult(thread, RegExpExec(thread, splitter, jsString, useCache));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (!execResult->IsNull()) {
return JSTaggedValue(static_cast<JSArray *>(array.GetTaggedValue().GetTaggedObject()));
}
JSObject::CreateDataProperty(thread, array, 0, jsString);
return JSTaggedValue(static_cast<JSArray *>(array.GetTaggedValue().GetTaggedObject()));
}
uint32_t endIndex = startIndex;
JSMutableHandle<JSTaggedValue> lastIndexvalue(thread, JSTaggedValue(endIndex));
JSHandle<JSTaggedValue> lastIndexString = globalConstants->GetHandledLastIndexString();
while (endIndex < size) {
lastIndexvalue.Update(JSTaggedValue(endIndex));
JSObject::SetProperty(thread, splitter, lastIndexString, lastIndexvalue, true);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> execResult(thread, RegExpExec(thread, splitter, jsString, useCache));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (execResult->IsNull()) {
endIndex = static_cast<uint32_t>(AdvanceStringIndex(thread, jsString, endIndex, unicodeMatching));
} else {
JSHandle<JSTaggedValue> lastIndexHandle =
JSObject::GetProperty(thread, splitter, lastIndexString).GetValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSTaggedNumber lastIndexNumber = JSTaggedValue::ToLength(thread, lastIndexHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
uint32_t lastIndex = static_cast<uint32_t>(std::min(
static_cast<uint64_t>(lastIndexNumber.GetNumber()), static_cast<uint64_t>(size)));
if (lastIndex == startIndex) {
endIndex = static_cast<uint32_t>(AdvanceStringIndex(thread, jsString, endIndex, unicodeMatching));
} else {
auto substr = EcmaStringAccessor::FastSubString(thread->GetEcmaVM(),
JSHandle<EcmaString>::Cast(jsString), startIndex, endIndex - startIndex);
std::string stdStrT =
EcmaStringAccessor(substr).ToStdString(thread, StringConvertedUsage::LOGICOPERATION);
JSHandle<JSTaggedValue> tValue(factory->NewFromStdString(stdStrT));
JSObject::CreateDataProperty(thread, array, aLength, tValue);
++aLength;
if (aLength == lim) {
return array.GetTaggedValue();
}
startIndex = lastIndex;
JSHandle<JSTaggedValue> lengthString(thread->GlobalConstants()->GetHandledLengthString());
JSHandle<JSTaggedValue> capturesHandle =
JSObject::GetProperty(thread, execResult, lengthString).GetValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSTaggedNumber numberOfCapturesNumber = JSTaggedValue::ToLength(thread, capturesHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
uint32_t numberOfCaptures = numberOfCapturesNumber.GetNumber();
numberOfCaptures = (numberOfCaptures == 0) ? 0 : numberOfCaptures - 1;
uint32_t i = 1;
while (i <= numberOfCaptures) {
JSHandle<JSTaggedValue> nextCapture = JSObject::GetProperty(thread, execResult, i).GetValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSObject::CreateDataProperty(thread, array, aLength, nextCapture);
++i;
++aLength;
if (aLength == lim) {
return array.GetTaggedValue();
}
}
endIndex = startIndex;
}
}
}
auto substr = EcmaStringAccessor::FastSubString(thread->GetEcmaVM(),
JSHandle<EcmaString>::Cast(jsString), startIndex, size - startIndex);
std::string stdStrT = EcmaStringAccessor(substr).ToStdString(thread, StringConvertedUsage::LOGICOPERATION);
JSHandle<JSTaggedValue> tValue(factory->NewFromStdString(stdStrT));
JSObject::CreateDataProperty(thread, array, aLength, tValue);
return array.GetTaggedValue();
}
JSTaggedValue BuiltinsRegExp::Split(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), RegExp, Split);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisObj = GetThis(argv);
JSHandle<JSTaggedValue> inputString = GetCallArg(argv, 0);
JSHandle<JSTaggedValue> limit = GetCallArg(argv, 1);
JSHandle<EcmaString> stringHandle = JSTaggedValue::ToString(thread, inputString);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> jsString = JSHandle<JSTaggedValue>::Cast(stringHandle);
if (!thisObj->IsECMAObject()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue::Exception());
}
JSHandle<RegExpExecResultCache> cacheTable(thread->GetGlobalEnv()->GetRegExpCache());
cacheTable->SetUseLastMatch(thread, false);
bool isFastPath = IsFastRegExp(thread, thisObj.GetTaggedValue(), RegExpSymbol::SPLIT);
return RegExpSplit(thread, thisObj, jsString, limit, isFastPath);
}
JSTaggedValue BuiltinsRegExp::RegExpSplitFast(JSThread *thread, const JSHandle<JSTaggedValue> regexp,
JSHandle<JSTaggedValue> jsString, uint32_t limit, bool useCache)
{
if (limit == 0) {
return JSArray::ArrayCreate(thread, JSTaggedNumber(0), ArrayMode::LITERAL).GetTaggedValue();
}
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
JSHandle<RegExpExecResultCache> cacheTable(thread->GetGlobalEnv()->GetRegExpCache());
RegExpExecResultCache::CacheGuardScope cacheGuardScope(cacheTable);
if (useCache) {
JSTaggedValue cacheResult;
if (UNLIKELY(thread->NeedReadBarrier())) {
cacheResult = cacheTable->FindCachedResult<RBMode::FAST_CMC_RB>(thread, jsString,
RegExpExecResultCache::SPLIT_TYPE, regexp,
JSTaggedValue(0), undefined);
} else {
cacheResult = cacheTable->FindCachedResult<RBMode::FAST_NO_RB>(thread, jsString,
RegExpExecResultCache::SPLIT_TYPE, regexp,
JSTaggedValue(0), undefined);
}
if (!cacheResult.IsUndefined()) {
return cacheResult;
}
}
uint32_t size = EcmaStringAccessor(jsString->GetTaggedObject()).GetLength();
JSHandle<EcmaString> string = JSHandle<EcmaString>::Cast(jsString);
if (size == 0) {
bool matchResult = RegExpExecInternal(thread, regexp, string, 0, RegExpParser::FLAG_STICKY);
if (matchResult) {
JSHandle<JSTaggedValue> 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<TaggedArray> element = thread->GetEcmaVM()->GetFactory()->NewTaggedArray(1);
element->Set(thread, 0, jsString);
JSHandle<JSTaggedValue> res = JSHandle<JSTaggedValue>::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);
uint32_t nextMatchFrom = 0;
uint32_t lastMatchEnd = 0;
uint32_t arrLen = 1;
JSHandle<JSArray> splitArray(JSArray::ArrayCreate(thread, JSTaggedNumber(1), ArrayMode::LITERAL));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
TaggedArray *srcElements = TaggedArray::Cast(splitArray->GetElements(thread).GetTaggedObject());
JSMutableHandle<TaggedArray> elements(thread, srcElements);
JSMutableHandle<JSTaggedValue> matchValue(thread, JSTaggedValue::Undefined());
while (nextMatchFrom < size) {
bool matchResult = RegExpExecInternal(thread, regexp, string, nextMatchFrom, RegExpParser::FLAG_STICKY);
if (!matchResult) {
nextMatchFrom = static_cast<uint32_t>(AdvanceStringIndex(thread, jsString, nextMatchFrom, isUnicode));
continue;
}
JSHandle<RegExpGlobalResult> matchResultInfo(thread->GetGlobalEnv()->GetRegExpGlobalResult());
uint32_t matchStartIndex = static_cast<uint32_t>(matchResultInfo->GetStartOfCaptureIndex(0).GetInt());
uint32_t matchEndIndex = static_cast<uint32_t>(matchResultInfo->GetEndOfCaptureIndex(0).GetInt());
if (matchEndIndex == lastMatchEnd && matchEndIndex == nextMatchFrom) {
nextMatchFrom = static_cast<uint32_t>(AdvanceStringIndex(thread, 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<JSObject>::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<JSTaggedValue>(splitArray),
RegExpExecResultCache::SPLIT_TYPE, 0, 0, undefined);
}
return JSTaggedValue(splitArray.GetTaggedValue().GetTaggedObject());
}
arrLen++;
uint32_t capturesSize = static_cast<uint32_t>(matchResultInfo->GetTotalCaptureCounts().GetInt());
uint32_t captureIndex = 1;
while (captureIndex < capturesSize) {
uint32_t captureStartIndex = static_cast<uint32_t>(
matchResultInfo->GetStartOfCaptureIndex(captureIndex).GetInt());
uint32_t captureEndIndex = static_cast<uint32_t>(
matchResultInfo->GetEndOfCaptureIndex(captureIndex).GetInt());
int32_t subStrLen = static_cast<int32_t>(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<JSObject>::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<JSTaggedValue>(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<EcmaString>::Cast(jsString), lastMatchEnd, size - lastMatchEnd)));
if (arrLen > elements->GetLength()) {
elements.Update(JSObject::GrowElementsCapacity(thread, JSHandle<JSObject>::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<JSTaggedValue>(splitArray), RegExpExecResultCache::SPLIT_TYPE,
0, 0, undefined);
}
return JSTaggedValue(splitArray.GetTaggedValue().GetTaggedObject());
}
bool BuiltinsRegExp::RegExpExecInternal(JSThread *thread, const JSHandle<JSTaggedValue> regexp,
JSHandle<EcmaString> inputString, int32_t lastIndex, uint32_t extraFlags)
{
size_t stringLength = EcmaStringAccessor(inputString).GetLength();
bool isUtf16 = EcmaStringAccessor(inputString).IsUtf16();
FlatStringInfo flatStrInfo = EcmaStringAccessor::FlattenAllString(thread->GetEcmaVM(), inputString);
if (EcmaStringAccessor(inputString).IsTreeString()) {
inputString = JSHandle<EcmaString>(thread, flatStrInfo.GetString());
}
const uint8_t *strBuffer;
if (isUtf16) {
strBuffer = reinterpret_cast<const uint8_t *>(flatStrInfo.GetDataUtf16());
} else {
strBuffer = flatStrInfo.GetDataUtf8();
}
bool isSuccess = false;
JSTaggedValue regexpSource = JSRegExp::Cast(regexp->GetTaggedObject())->GetOriginalSource(thread);
uint32_t regexpLength = EcmaStringAccessor(regexpSource).GetLength();
if (UNLIKELY(regexpLength > MIN_REGEXP_PATTERN_LENGTH_EXECUTE_WITH_OFFHEAP_STRING && stringLength > 0)) {
size_t utf8Len = LineEcmaString::DataSize(*inputString);
ASSERT(utf8Len > 0 &&
utf8Len == (isUtf16 ? (stringLength * 2) : stringLength));
uint8_t *offHeapString = new uint8_t[utf8Len];
if (memcpy_s(offHeapString, utf8Len, strBuffer, utf8Len) != EOK) {
LOG_FULL(FATAL) << "memcpy_s failed";
UNREACHABLE();
}
isSuccess = Matcher(thread, regexp, offHeapString, stringLength, lastIndex, isUtf16,
StringSource::OFFHEAP_STRING, extraFlags);
delete[] offHeapString;
} else {
isSuccess = Matcher(thread, regexp, strBuffer, stringLength, lastIndex, isUtf16,
StringSource::ONHEAP_STRING, extraFlags);
}
if (isSuccess) {
JSHandle<RegExpGlobalResult> globalTable(thread->GetGlobalEnv()->GetRegExpGlobalResult());
globalTable->ResetDollar(thread);
globalTable->SetInputString(thread, inputString.GetTaggedValue());
}
return isSuccess;
}
bool BuiltinsRegExp::Matcher(JSThread *thread, const JSHandle<JSTaggedValue> regexp,
const uint8_t *buffer, size_t length, int32_t lastIndex,
bool isUtf16, StringSource source, uint32_t extraFlags)
{
BUILTINS_API_TRACE(thread, RegExp, Matcher);
JSTaggedValue bufferData = JSRegExp::Cast(regexp->GetTaggedObject())->GetByteCodeBuffer(thread);
void *dynBuf = JSNativePointer::Cast(bufferData.GetTaggedObject())->GetExternalPointer();
auto bytecodeBuffer = reinterpret_cast<uint8_t *>(dynBuf);
RegExpCachedChunk chunk(thread);
RegExpExecutor executor(&chunk);
if (lastIndex < 0) {
lastIndex = 0;
}
bool ret = false;
if (UNLIKELY(source == StringSource::OFFHEAP_STRING)) {
#ifndef NDEBUG
if (!g_isEnableCMCGC) {
SharedHeap::GetInstance()->PostGCTaskForTest<TriggerGCType::SHARED_FULL_GC, GCReason::OTHER>(thread);
}
#endif
ThreadNativeScope scope(thread);
ret = executor.Execute(buffer, lastIndex, static_cast<uint32_t>(length), bytecodeBuffer, isUtf16, extraFlags);
} else {
ret = executor.Execute(buffer, lastIndex, static_cast<uint32_t>(length), bytecodeBuffer, isUtf16, extraFlags);
}
if (ret) {
executor.GetResult(thread);
}
return ret;
}
int64_t BuiltinsRegExp::AdvanceStringIndex(const JSThread *thread, const JSHandle<JSTaggedValue> &inputStr,
int64_t index, bool unicode)
{
ASSERT(inputStr->IsString());
ASSERT(0 <= index && index <= pow(2, 53) - 1);
if (!unicode) {
return index + 1;
}
uint32_t length = EcmaStringAccessor(inputStr->GetTaggedObject()).GetLength();
if (index + 1 >= length) {
return index + 1;
}
uint16_t first = EcmaStringAccessor(inputStr->GetTaggedObject()).Get(thread, index);
if (first < 0xD800 || first > 0xDFFF) {
return index + 1;
}
uint16_t second = EcmaStringAccessor(inputStr->GetTaggedObject()).Get(thread, index + 1);
if (second < 0xDC00 || second > 0xDFFF) {
return index + 1;
}
return index + 2;
}
JSTaggedValue BuiltinsRegExp::GetFlagsInternal(JSThread *thread, const JSHandle<JSTaggedValue> &obj,
const JSHandle<JSTaggedValue> &constructor, const uint8_t mask)
{
BUILTINS_API_TRACE(thread, RegExp, GetFlagsInternal);
if (!obj->IsECMAObject()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "this is not Object", JSTaggedValue(false));
}
JSHandle<JSObject> patternObj = JSHandle<JSObject>::Cast(obj);
if (!patternObj->IsJSRegExp()) {
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
JSHandle<JSTaggedValue> constructorKey = globalConst->GetHandledConstructorString();
JSHandle<JSTaggedValue> objConstructor = JSTaggedValue::GetProperty(thread, obj, constructorKey).GetValue();
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue(false));
if (objConstructor->IsJSFunction() && constructor->IsJSFunction()) {
JSHandle<GlobalEnv> objRealm = JSObject::GetFunctionRealm(thread, objConstructor);
JSHandle<GlobalEnv> ctorRealm = JSObject::GetFunctionRealm(thread, constructor);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (objRealm->GetRegExpPrototype() == obj && *objRealm == *ctorRealm) {
return JSTaggedValue::Undefined();
}
}
THROW_TYPE_ERROR_AND_RETURN(thread, "this does not have [[OriginalFlags]]", JSTaggedValue(false));
}
JSHandle<JSRegExp> regexpObj(thread, JSRegExp::Cast(obj->GetTaggedObject()));
uint8_t flags = static_cast<uint8_t>(regexpObj->GetOriginalFlags(thread).GetInt());
return GetTaggedBoolean(flags & mask);
}
JSHandle<JSTaggedValue> BuiltinsRegExp::MakeMatchIndicesIndexPairArray(JSThread *thread,
const std::vector<std::pair<JSTaggedValue, JSTaggedValue>>& indices,
const std::vector<JSHandle<JSTaggedValue>>& groupNames, bool hasGroups)
{
uint32_t n = indices.size();
ASSERT(groupNames.size() == n - 1);
JSHandle<JSObject> results(JSArray::ArrayCreate(thread, JSTaggedNumber(n)));
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSMutableHandle<JSTaggedValue> groups(thread, JSTaggedValue::Undefined());
if (hasGroups) {
JSHandle<JSTaggedValue> nullHandle(thread, JSTaggedValue::Null());
JSHandle<JSObject> nullObj = factory->OrdinaryNewJSObjectCreate(nullHandle);
groups.Update(nullObj.GetTaggedValue());
}
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
JSHandle<JSTaggedValue> groupsKey = globalConst->GetHandledGroupsString();
JSObject::CreateDataProperty(thread, results, groupsKey, groups);
JSMutableHandle<JSTaggedValue> matchIndexPair(thread, JSTaggedValue::Undefined());
for (uint32_t i = 0; i < n; i++) {
std::pair<JSTaggedValue, JSTaggedValue> matchIndices = indices[i];
if (!matchIndices.first.IsUndefined()) {
JSHandle<TaggedArray> match = factory->NewTaggedArray(2);
match->Set(thread, 0, matchIndices.first);
match->Set(thread, 1, matchIndices.second);
JSHandle<JSTaggedValue> pair(JSArray::CreateArrayFromList(thread, JSHandle<TaggedArray>::Cast(match)));
matchIndexPair.Update(pair.GetTaggedValue());
} else {
matchIndexPair.Update(JSTaggedValue::Undefined());
}
JSObject::CreateDataProperty(thread, results, i, matchIndexPair);
if (i > 0) {
JSHandle<JSTaggedValue> groupName = groupNames[i - 1];
if (!groupName->IsUndefined()) {
JSHandle<JSObject> groupObject = JSHandle<JSObject>::Cast(groups);
JSObject::CreateDataProperty(thread, groupObject, groupName, matchIndexPair);
}
}
}
return JSHandle<JSTaggedValue>::Cast(results);
}
JSTaggedValue BuiltinsRegExp::RegExpBuiltinExec(JSThread *thread, const JSHandle<JSTaggedValue> regexp,
const JSHandle<JSTaggedValue> inputStr,
bool isFastPath, bool useCache, bool isIntermediateResult)
{
ASSERT(regexp->IsJSRegExp());
ASSERT(inputStr->IsString());
BUILTINS_API_TRACE(thread, RegExp, RegExpBuiltinExec);
uint32_t lastIndex = static_cast<uint32_t>(GetLastIndex(thread, regexp, isFastPath));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
JSHandle<RegExpExecResultCache> cacheTable(thread->GetGlobalEnv()->GetRegExpCache());
RegExpExecResultCache::CacheGuardScope cacheGuardScope(cacheTable);
cacheTable->SetUseLastMatch(thread, false);
if (useCache) {
JSTaggedValue cacheResult;
if (UNLIKELY(thread->NeedReadBarrier())) {
cacheResult = cacheTable->FindCachedResult<RBMode::FAST_CMC_RB>(thread, inputStr,
RegExpExecResultCache::EXEC_TYPE, regexp,
JSTaggedValue(lastIndex), undefined);
} else {
cacheResult = cacheTable->FindCachedResult<RBMode::FAST_NO_RB>(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<EcmaString> inputString = JSHandle<EcmaString>::Cast(inputStr);
JSHandle<RegExpGlobalResult> globalTable(thread->GetGlobalEnv()->GetRegExpGlobalResult());
uint32_t capturesSize = static_cast<uint32_t>(globalTable->GetTotalCaptureCounts().GetInt());
#if ENABLE_MEMORY_OPTIMIZATION
JSHandle<JSFunction> arrayFunction = JSHandle<JSFunction>::Cast(thread->GetGlobalEnv()->GetArrayFunction());
JSHandle<JSTaggedValue> defaultArrayClass = thread->GetGlobalEnv()->GetDefaultArrayClass();
JSFunction::SetFunctionPrototypeOrInstanceHClass(thread, arrayFunction, defaultArrayClass.GetTaggedValue());
#endif
JSHandle<JSObject> results(JSArray::ArrayCreate(thread, JSTaggedNumber(capturesSize), ArrayMode::LITERAL));
#if ENABLE_MEMORY_OPTIMIZATION
JSHandle<JSTaggedValue> arrayClass = thread->GetGlobalEnv()->GetArrayClass();
JSFunction::SetFunctionPrototypeOrInstanceHClass(thread, arrayFunction, arrayClass.GetTaggedValue());
#endif
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
auto globalConst = thread->GlobalConstants();
JSHandle<JSTaggedValue> indexValue(thread, globalTable->GetStartOfCaptureIndex(0));
if (isIntermediateResult) {
results->SetPropertyInlinedPropsWithRep(thread, EXEC_RESULT_INDEX_OFFSET, indexValue.GetTaggedValue());
results->SetPropertyInlinedPropsWithRep(thread, EXEC_RESULT_INPUT_OFFSET, inputStr.GetTaggedValue());
} else {
JSHandle<JSTaggedValue> indexKey = globalConst->GetHandledIndexString();
JSObject::CreateDataProperty(thread, results, indexKey, indexValue);
JSHandle<JSTaggedValue> inputKey = globalConst->GetHandledInputString();
JSObject::CreateDataProperty(thread, results, inputKey, inputStr);
}
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<TaggedArray> resultElements = factory->NewTaggedArray(capturesSize);
uint32_t startIndex = static_cast<uint32_t>(globalTable->GetStartOfCaptureIndex(0).GetInt());
uint32_t len = static_cast<uint32_t>(globalTable->GetEndOfCaptureIndex(0).GetInt()) - startIndex;
JSHandle<JSTaggedValue> zeroValue(thread, JSTaggedValue(EcmaStringAccessor::FastSubString(
thread->GetEcmaVM(), inputString, startIndex, len)));
resultElements->Set(thread, 0, zeroValue);
JSHandle<JSTaggedValue> groupName(thread, JSHandle<JSRegExp>::Cast(regexp)->GetGroupName(thread));
JSMutableHandle<JSTaggedValue> groups(thread, JSTaggedValue::Undefined());
bool hasGroups = false;
if (!groupName->IsUndefined()) {
groups.Update(factory->CreateNullJSObject().GetTaggedValue());
hasGroups = true;
}
if (isIntermediateResult) {
results->SetPropertyInlinedPropsWithRep(thread, EXEC_RESULT_GROUPS_OFFSET, groups.GetTaggedValue());
} else {
JSHandle<JSTaggedValue> groupsKey = globalConst->GetHandledGroupsString();
JSObject::CreateDataProperty(thread, results, groupsKey, groups);
}
uint32_t endIndex = globalTable->GetEndIndex().GetInt();
std::vector<std::pair<JSTaggedValue, JSTaggedValue>> indices;
indices.reserve(capturesSize);
indices.emplace_back(globalTable->GetStartOfCaptureIndex(0), JSTaggedValue(endIndex));
std::vector<JSHandle<JSTaggedValue>> groupNames;
groupNames.reserve(capturesSize);
uint32_t captureIndex = 1;
JSMutableHandle<JSTaggedValue> iValue(thread, JSTaggedValue::Undefined());
for (; captureIndex < capturesSize; captureIndex++) {
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(JSTaggedValue::Undefined(), JSTaggedValue::Undefined());
} else {
iValue.Update(JSTaggedValue(EcmaStringAccessor::FastSubString(
thread->GetEcmaVM(), inputString, captureStartIndex, subStrLen)));
indices.emplace_back(JSTaggedValue(captureStartIndex), JSTaggedValue(captureEndIndex));
}
if (captureIndex <= REGEXP_GLOBAL_ARRAY_SIZE) {
globalTable->SetCapture(thread, captureIndex, iValue.GetTaggedValue());
}
resultElements->Set(thread, captureIndex, iValue);
if (!groupName->IsUndefined()) {
JSHandle<JSObject> groupObject = JSHandle<JSObject>::Cast(groups);
TaggedArray *groupArray = TaggedArray::Cast(groupName->GetTaggedObject());
if (groupArray->GetLength() > captureIndex - 1) {
JSHandle<JSTaggedValue> skey(thread, groupArray->Get(thread, captureIndex - 1));
JSObject::CreateDataProperty(thread, groupObject, skey, iValue);
groupNames.emplace_back(skey);
} else {
groupNames.emplace_back(undefined);
}
} else {
groupNames.emplace_back(undefined);
}
}
results->SetElements(thread, resultElements);
bool hasIndices = GetOriginalFlag(thread, regexp, RegExpParser::FLAG_HASINDICES);
if (hasIndices) {
auto indicesArray = MakeMatchIndicesIndexPairArray(thread, indices, groupNames, hasGroups);
JSHandle<JSTaggedValue> indicesKey = globalConst->GetHandledIndicesString();
JSObject::CreateDataProperty(thread, results, indicesKey, indicesArray);
}
JSHandle<JSTaggedValue> 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<uint32_t>(globalTable->GetEndIndex().GetInt());
}
RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, inputStr,
JSHandle<JSTaggedValue>(results), RegExpExecResultCache::EXEC_TYPE,
lastIndex, newLastIndex, undefined);
}
return results.GetTaggedValue();
}
JSTaggedValue BuiltinsRegExp::RegExpBuiltinExecWithoutResult(JSThread *thread, const JSHandle<JSTaggedValue> regexp,
const JSHandle<JSTaggedValue> inputStr,
bool isFastPath, uint32_t lastIndex, bool useCache)
{
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<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
JSHandle<EcmaString> inputString = JSHandle<EcmaString>::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<RegExpExecResultCache> cacheTable(thread->GetGlobalEnv()->GetRegExpCache());
RegExpExecResultCache::AddResultInCache(thread, cacheTable, regexp, inputStr,
JSHandle<JSTaggedValue>(thread, JSTaggedValue::Null()),
RegExpExecResultCache::EXEC_TYPE,
lastIndex, endIndex, undefined);
}
return JSTaggedValue::Null();
}
JSHandle<RegExpGlobalResult> globalTable(thread->GetGlobalEnv()->GetRegExpGlobalResult());
JSTaggedValue endIndex = globalTable->GetEndIndex();
if (ifUpdateLastIndex) {
SetLastIndex(thread, regexp, endIndex, isFastPath);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
return JSTaggedValue::True();
}
JSTaggedValue BuiltinsRegExp::RegExpExec(JSThread *thread, const JSHandle<JSTaggedValue> ®exp,
const JSHandle<JSTaggedValue> &inputString, bool useCache,
bool isIntermediateResult)
{
BUILTINS_API_TRACE(thread, RegExp, RegExpExec);
ASSERT(regexp->IsECMAObject());
ASSERT(inputString->IsString());
JSHandle<EcmaString> inputStr = JSTaggedValue::ToString(thread, inputString);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
JSHandle<JSTaggedValue> execHandle = globalConst->GetHandledExecString();
JSTaggedValue execVal = ObjectFastOperator::FastGetPropertyByValue(thread, regexp.GetTaggedValue(),
execHandle.GetTaggedValue());
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> exec(thread, execVal);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (exec->IsCallable()) {
JSHandle<JSTaggedValue> 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);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (!result.IsECMAObject() && !result.IsNull()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "exec result is null or is not Object", JSTaggedValue::Exception());
}
return result;
}
if (!regexp->IsJSRegExp()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "this does not have a [[RegExpMatcher]]", JSTaggedValue::Exception());
}
return RegExpBuiltinExec(thread, regexp, inputString, false, useCache, isIntermediateResult);
}
JSTaggedValue BuiltinsRegExp::RegExpAlloc(JSThread *thread, const JSHandle<JSTaggedValue> &newTarget)
{
BUILTINS_API_TRACE(thread, RegExp, RegExpAlloc);
* 1. Let obj be OrdinaryCreateFromConstructor(newTarget, "%RegExpPrototype%",
* «[[RegExpMatcher]],[[OriginalSource]], [[OriginalFlags]]»).
* */
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
JSHandle<JSTaggedValue> func = env->GetRegExpFunction();
JSHandle<JSTaggedValue> obj(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(func), newTarget));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
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<JSObject> 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<JSObject> 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 < (1 << RegExpParser::FLAG_NUM));
BUILTINS_API_TRACE(thread, RegExp, FlagsBitsToString);
uint8_t *flagsStr = new uint8_t[RegExpParser::FLAG_NUM + 1];
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<EcmaString> flagsString = thread->GetEcmaVM()->GetFactory()->NewFromUtf8(flagsStr, flagsLen);
delete[] flagsStr;
return flagsString.GetTaggedValue();
}
template <bool needFlagsTransition>
JSTaggedValue BuiltinsRegExp::RegExpInitialize(JSThread *thread, const JSHandle<JSTaggedValue> &obj,
const JSHandle<JSTaggedValue> &pattern,
const JSHandle<JSTaggedValue> &flags)
{
BUILTINS_API_TRACE(thread, RegExp, RegExpInitialize);
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<EcmaString> patternStrHandle;
if (pattern->IsUndefined()) {
patternStrHandle = factory->GetEmptyString();
} else {
patternStrHandle = JSTaggedValue::ToString(thread, pattern);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
uint8_t flagsBits = 0;
if constexpr (needFlagsTransition == false) {
ASSERT(flags->IsInt());
flagsBits = static_cast<uint8_t>(flags->GetInt());
} else {
if (flags->IsUndefined()) {
flagsBits = 0;
} else {
JSHandle<EcmaString> flagsStrHandle = JSTaggedValue::ToString(thread, flags);
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(thread, *flagsStrHandle, StringConvertedUsage::LOGICOPERATION);
flagsBits = static_cast<uint8_t>(UpdateExpressionFlags(thread, checkStr));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
}
Chunk chunk(thread->GetNativeAreaAllocator());
RegExpParser parser = RegExpParser(thread, &chunk);
RegExpParserCache *regExpParserCache = thread->GetEcmaVM()->GetRegExpParserCache();
CVector<CString> groupName;
auto getCache = regExpParserCache->GetCache(thread, *patternStrHandle, flagsBits, groupName);
if (getCache.first.IsHole()) {
bool cesu8 = !(RegExpParser::FLAG_UTF16 & flagsBits);
CString patternStdStr = ConvertToString(thread, *patternStrHandle, StringConvertedUsage::LOGICOPERATION,
cesu8);
parser.Init(const_cast<char *>(reinterpret_cast<const char *>(patternStdStr.c_str())), patternStdStr.size(),
flagsBits);
parser.Parse();
if (parser.IsError()) {
JSHandle<JSObject> 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<JSRegExp> regexp(thread, JSRegExp::Cast(obj->GetTaggedObject()));
regexp->SetOriginalSource(thread, patternStrHandle.GetTaggedValue());
regexp->SetOriginalFlags(thread, JSTaggedValue(flagsBits));
if (!groupName.empty()) {
JSHandle<TaggedArray> taggedArray = factory->NewTaggedArray(groupName.size());
for (size_t i = 0; i < groupName.size(); ++i) {
JSHandle<JSTaggedValue> flagsKey(factory->NewFromStdString(groupName[i].c_str()));
taggedArray->Set(thread, i, flagsKey);
}
regexp->SetGroupName(thread, taggedArray);
}
if (getCache.first.IsHole()) {
auto bufferSize = parser.GetOriginBufferSize();
auto buffer = parser.GetOriginBuffer();
factory->NewJSRegExpByteCodeData(regexp, buffer, bufferSize);
regExpParserCache->SetCache(thread, *patternStrHandle, flagsBits, regexp->GetByteCodeBuffer(thread),
bufferSize, groupName);
} else {
regexp->SetByteCodeBuffer(thread, getCache.first);
regexp->SetLength(static_cast<uint32_t>(getCache.second));
}
SetLastIndex(thread, obj, JSTaggedValue(0), true);
return obj.GetTaggedValue();
}
JSTaggedValue BuiltinsRegExp::RegExpCreate(JSThread *thread, const JSHandle<JSTaggedValue> &pattern,
const JSHandle<JSTaggedValue> &flags)
{
BUILTINS_API_TRACE(thread, RegExp, Create);
auto ecmaVm = thread->GetEcmaVM();
JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
JSHandle<JSTaggedValue> newTarget = env->GetRegExpFunction();
JSHandle<JSTaggedValue> object(thread, RegExpAlloc(thread, newTarget));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return RegExpInitialize(thread, object, pattern, flags);
}
JSTaggedValue BuiltinsRegExp::RegExpCreateWithRawFlags(JSThread *thread, const JSHandle<JSTaggedValue> &pattern,
const JSHandle<JSTaggedValue> &flags)
{
BUILTINS_API_TRACE(thread, RegExp, Create);
auto ecmaVm = thread->GetEcmaVM();
JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
JSHandle<JSTaggedValue> newTarget = env->GetRegExpFunction();
JSHandle<JSTaggedValue> object(thread, RegExpAlloc(thread, newTarget));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return RegExpInitialize<false>(thread, object, pattern, flags);
}
EcmaString *BuiltinsRegExp::EscapeRegExpPattern(JSThread *thread, const JSHandle<JSTaggedValue> &src,
const JSHandle<JSTaggedValue> &flags)
{
BUILTINS_API_TRACE(thread, RegExp, EscapeRegExpPattern);
JSHandle<EcmaString> srcStr(thread, static_cast<EcmaString *>(src->GetTaggedObject()));
JSHandle<EcmaString> flagsStr(thread, static_cast<EcmaString *>(flags->GetTaggedObject()));
CString srcStdStr = ConvertToString(thread, *srcStr, StringConvertedUsage::LOGICOPERATION);
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
if (srcStdStr.empty()) {
srcStdStr = "(?:)";
}
bool escapeChar = false;
for (size_t i = 0; i < srcStdStr.size(); i++) {
if (srcStdStr[i] == '\\') {
escapeChar = !escapeChar;
} else if (!escapeChar && srcStdStr[i]=='/') {
srcStdStr.insert(i, "\\");
i++;
} else {
escapeChar = false;
}
}
return *factory->NewFromUtf8(srcStdStr);
}
#define SET_GET_CAPTURE_IMPL(index) \
JSTaggedValue BuiltinsRegExp::GetCapture##index(JSThread *thread, [[maybe_unused]] const JSHandle<JSObject> &obj) \
{ \
return RegExpGlobalResult::GetCapture<index>(thread); \
} \
bool BuiltinsRegExp::SetCapture##index([[maybe_unused]] JSThread *thread, \
[[maybe_unused]] const JSHandle<JSObject> &obj, \
[[maybe_unused]] const JSHandle<JSTaggedValue> &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<RegExpExecResultCache *>(
*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);
table->SetLastMatchGlobalTableIndex(thread, DEFAULT_LAST_MATCH_INDEX);
table->SetUseLastMatch(thread, false);
table->SetNeedUpdateGlobal(thread, false);
return JSTaggedValue(table);
}
void RegExpExecResultCache::ShrinkCacheTable(JSThread *thread, JSHandle<RegExpExecResultCache> table)
{
ASSERT(table->GetCacheLength() == DEFAULT_CACHE_NUMBER);
int length = CACHE_TABLE_HEADER_SIZE + INITIAL_CACHE_NUMBER * ENTRY_SIZE;
table->Trim(thread, length);
table->SetCacheLength(thread, INITIAL_CACHE_NUMBER);
}
void RegExpExecResultCache::ClearCache(JSThread* thread, JSHandle<JSTaggedValue> cache)
{
if (cache->IsUndefined()) {
return;
}
JSHandle<RegExpExecResultCache> regexpCache(cache);
if (regexpCache->GetCacheCount() == 0 || regexpCache->CacheInGuard()) {
return;
}
if (regexpCache->GetHitCount() > 0) {
regexpCache->SetHitCount(thread, 0);
return;
}
if (regexpCache->GetNeedUpdateGlobal()) {
JSHandle<GlobalEnv> globalEnv = thread->GetGlobalEnv();
ASSERT(!globalEnv.GetTaggedValue().IsHole());
const int lastMatchIndex = regexpCache->GetLastMatchGlobalTableIndex();
ASSERT(lastMatchIndex != -1);
globalEnv->SetRegExpGlobalResult(thread, regexpCache->Get(thread, lastMatchIndex + CAPTURE_SIZE));
regexpCache->SetNeedUpdateGlobal(thread, false);
}
if (regexpCache->GetCacheLength() == DEFAULT_CACHE_NUMBER) {
ShrinkCacheTable(thread, regexpCache);
}
regexpCache->SetCacheCount(thread, 0);
regexpCache->SetLargeStrCount(thread, DEFAULT_LARGE_STRING_COUNT);
regexpCache->SetConflictCount(thread, DEFAULT_CONFLICT_COUNT);
regexpCache->SetStrLenThreshold(thread, 0);
regexpCache->SetLastMatchGlobalTableIndex(thread, DEFAULT_LAST_MATCH_INDEX);
regexpCache->SetUseLastMatch(thread, false);
std::fill_n(regexpCache->GetData() + CACHE_TABLE_HEADER_SIZE,
regexpCache->GetLength() - CACHE_TABLE_HEADER_SIZE,
JSTaggedValue::Undefined().GetRawData());
}
void RegExpExecResultCache::AddResultInCache(JSThread *thread, JSHandle<RegExpExecResultCache> cache,
const JSHandle<JSTaggedValue> regexp,
const JSHandle<JSTaggedValue> input,
const JSHandle<JSTaggedValue> resultArray, CacheType type,
uint32_t lastIndexInput, uint32_t lastIndex,
JSHandle<JSTaggedValue> extend, bool isIntermediateResult)
{
JSHandle<JSRegExp> regexpObj(regexp);
JSHandle<JSTaggedValue> pattern(thread, regexpObj->GetOriginalSource(thread));
JSHandle<JSTaggedValue> flags(thread, regexpObj->GetOriginalFlags(thread));
if (!pattern->IsString() || !flags->IsInt() || !input->IsString()) {
return;
}
JSHandle<JSTaggedValue> resultArrayCopy;
if (!isIntermediateResult && resultArray->IsJSArray()) {
JSHandle<JSArray> copyArray = thread->GetEcmaVM()->GetFactory()
->CloneArrayLiteral(JSHandle<JSArray>(resultArray));
resultArrayCopy = JSHandle<JSTaggedValue>(copyArray);
} else {
resultArrayCopy = JSHandle<JSTaggedValue>(resultArray);
}
JSHandle<RegExpGlobalResult> globalTable(thread->GetGlobalEnv()->GetRegExpGlobalResult());
JSHandle<TaggedArray> taggedArray = JSHandle<TaggedArray>::Cast(globalTable);
auto factory = thread->GetEcmaVM()->GetFactory();
uint32_t arrayLength = globalTable->GetLength();
JSHandle<TaggedArray> 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(thread) + static_cast<uint32_t>(flagsValue.GetInt()) +
inputValue.GetKeyHashCode(thread) + lastIndexInput;
uint32_t entry = hash & static_cast<uint32_t>(cache->GetCacheLength() - 1);
ASSERT((static_cast<size_t>(CACHE_TABLE_HEADER_SIZE) +
static_cast<size_t>(entry) * static_cast<size_t>(ENTRY_SIZE)) <= static_cast<size_t>(UINT32_MAX));
uint32_t index = CACHE_TABLE_HEADER_SIZE + entry * ENTRY_SIZE;
CacheGuardScope scope(cache);
cache->SetUseLastMatch(thread, true);
if (cache->Get(thread, 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);
cache->SetLastMatchGlobalTableIndex(thread, index);
} else if (cache->Match(thread, entry, patternValue, flagsValue, inputValue,
lastIndexInputValue, extendValue, type)) {
cache->UpdateResultArray(thread, entry, resultArrayCopy.GetTaggedValue(), type);
cache->SetLastMatchGlobalTableIndex(thread, index);
} else {
uint32_t entry2 = (entry + 1) & static_cast<uint32_t>(cache->GetCacheLength() - 1);
ASSERT((static_cast<size_t>(CACHE_TABLE_HEADER_SIZE) +
static_cast<size_t>(entry2) * static_cast<size_t>(ENTRY_SIZE)) <= static_cast<size_t>(UINT32_MAX));
uint32_t index2 = CACHE_TABLE_HEADER_SIZE + entry2 * ENTRY_SIZE;
if (cache->GetCacheLength() < DEFAULT_CACHE_NUMBER) {
GrowRegexpCache(thread, cache);
patternValue = pattern.GetTaggedValue();
flagsValue = flags.GetTaggedValue();
inputValue = input.GetTaggedValue();
entry2 = hash & static_cast<uint32_t>(cache->GetCacheLength() - 1);
index2 = CACHE_TABLE_HEADER_SIZE + entry2 * ENTRY_SIZE;
}
extendValue = extend.GetTaggedValue();
resTableArrayValue = resTableArray.GetTaggedValue();
cache->SetLastMatchGlobalTableIndex(thread, index2);
if (cache->Get(thread, 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(thread, 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->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<RegExpExecResultCache> cache)
{
int length = CACHE_TABLE_HEADER_SIZE + DEFAULT_CACHE_NUMBER * ENTRY_SIZE;
auto factory = thread->GetEcmaVM()->GetFactory();
auto newCache = factory->ExtendArray(JSHandle<TaggedArray>(cache), length, JSTaggedValue::Undefined());
JSHandle<RegExpExecResultCache>::Cast(newCache)->SetCacheLength(thread, DEFAULT_CACHE_NUMBER);
JSHandle<GlobalEnv> env = thread->GetGlobalEnv();
env->SetRegExpCache(thread, 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<size_t>(CACHE_TABLE_HEADER_SIZE) +
static_cast<size_t>(entry) * static_cast<size_t>(ENTRY_SIZE)) <= static_cast<size_t>(INT_MAX));
int index = CACHE_TABLE_HEADER_SIZE + entry * ENTRY_SIZE;
Set(thread, index + PATTERN_INDEX, pattern);
ASSERT(flags.IsInt());
Set<false>(thread, index + FLAG_INDEX, flags);
Set(thread, index + INPUT_STRING_INDEX, input);
ASSERT(lastIndexInputValue.IsInt());
Set<false>(thread, index + LAST_INDEX_INPUT_INDEX, lastIndexInputValue);
ASSERT(lastIndexValue.IsInt());
Set<false>(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<size_t>(CACHE_TABLE_HEADER_SIZE) +
static_cast<size_t>(entry) * static_cast<size_t>(ENTRY_SIZE)) <= static_cast<size_t>(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<size_t>(CACHE_TABLE_HEADER_SIZE) +
static_cast<size_t>(entry) * static_cast<size_t>(ENTRY_SIZE)) <= static_cast<size_t>(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);
}
}
JSTaggedValue RegExpExecResultCache::GetGlobalTable(JSThread *thread)
{
JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
JSHandle<RegExpExecResultCache> cacheTable(env->GetRegExpCache());
const int lastMatchIndex = cacheTable->GetLastMatchGlobalTableIndex();
if (lastMatchIndex == -1) {
return env->GetTaggedRegExpGlobalResult();
}
JSHandle<TaggedArray> lastMatchTable(thread, cacheTable->Get(thread, lastMatchIndex + CAPTURE_SIZE));
if (cacheTable->GetNeedUpdateGlobal()) {
auto factory = thread->GetEcmaVM()->GetFactory();
uint32_t arrayLength = lastMatchTable->GetLength();
JSHandle<TaggedArray> resTableArray = factory->NewAndCopyTaggedArray(lastMatchTable, arrayLength, arrayLength);
env->SetRegExpGlobalResult(thread, resTableArray.GetTaggedValue());
cacheTable->SetNeedUpdateGlobal(thread, false);
}
return cacheTable->GetUseLastMatch() ? lastMatchTable.GetTaggedValue() : env->GetTaggedRegExpGlobalResult();
}
JSTaggedValue RegExpGlobalResult::CreateGlobalResultTable(JSThread *thread)
{
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
uint32_t initialLength = GLOBAL_TABLE_SIZE + INITIAL_CAPTURE_INDICES;
auto table = static_cast<RegExpGlobalResult *>(
*factory->NewTaggedArray(initialLength, JSTaggedValue::Undefined()));
JSTaggedValue emptyString = factory->GetEmptyString().GetTaggedValue();
for (uint32_t i = 1; i <= DOLLAR_NUMBER; i++) {
table->SetCapture(thread, CAPTURE_START_INDEX + i, emptyString);
}
table->SetTotalCaptureCounts(thread, JSTaggedValue(0));
table->SetInputString(thread, emptyString);
for (uint32_t i = 0; i < INITIAL_CAPTURE_INDICES / 2; i++) {
table->SetStartOfCaptureIndex(thread, i, JSTaggedValue(0));
table->SetEndOfCaptureIndex(thread, i, JSTaggedValue(0));
}
return JSTaggedValue(table);
}
JSHandle<RegExpGlobalResult> RegExpGlobalResult::GrowCapturesCapacity(JSThread *thread,
JSHandle<RegExpGlobalResult>result, uint32_t length)
{
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<TaggedArray> newResult = factory->ExtendArray(
JSHandle<TaggedArray>(result), length, JSTaggedValue(0));
JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
env->SetRegExpGlobalResult(thread, newResult.GetTaggedValue());
return JSHandle<RegExpGlobalResult>(newResult);
}
bool BuiltinsRegExp::GetFlag(JSThread *thread, const JSHandle<JSTaggedValue> regexp, uint32_t flag, bool isFastPath)
{
if (isFastPath) {
uint8_t flagsBits = static_cast<uint8_t>(JSHandle<JSRegExp>::Cast(regexp)->GetOriginalFlags(thread).GetInt());
return (flagsBits & flag) != 0;
} else {
JSMutableHandle<JSTaggedValue> 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<JSTaggedValue> regexp, uint32_t flag)
{
return GetFlag(thread, regexp, flag, true);
}
void BuiltinsRegExp::SetLastIndex(JSThread *thread, const JSHandle<JSTaggedValue> regexp,
JSTaggedValue lastIndex, bool isFastPath)
{
if (isFastPath) {
JSHandle<JSObject>::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<JSTaggedValue> regexp, bool isFastPath)
{
if (isFastPath) {
return JSHandle<JSObject>::Cast(regexp)->GetPropertyInlinedProps(thread, LAST_INDEX_OFFSET).GetInt();
}
JSHandle<JSTaggedValue> 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<JSTaggedValue> &execResults,
bool isFastPath)
{
if (isFastPath) {
return JSHandle<JSObject>::Cast(execResults)->GetPropertyInlinedProps(thread, EXEC_RESULT_INDEX_OFFSET);
}
JSHandle<JSTaggedValue> 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<JSTaggedValue> &execResults,
bool isFastPath)
{
if (isFastPath) {
return JSHandle<JSObject>::Cast(execResults)->GetPropertyInlinedProps(thread, EXEC_RESULT_GROUPS_OFFSET);
}
JSHandle<JSTaggedValue> groupKey = thread->GlobalConstants()->GetHandledGroupsString();
JSTaggedValue groups = ObjectFastOperator::FastGetPropertyByValue(
thread, execResults.GetTaggedValue(), groupKey.GetTaggedValue());
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return groups;
}
JSHandle<EcmaString> BuiltinsRegExp::CreateStringFromResultArray(JSThread *thread,
const CVector<JSHandle<JSTaggedValue>> &resultArray,
const std::vector<uint64_t> &resultLengthArray,
JSHandle<EcmaString> srcString, uint32_t resultStrLength, bool isUtf8)
{
JSHandle<EcmaString> result = JSHandle<EcmaString>(thread,
EcmaStringAccessor::CreateLineString(thread->GetEcmaVM(), resultStrLength, isUtf8));
FlatStringInfo flatStrInfo = EcmaStringAccessor::FlattenAllString(thread->GetEcmaVM(), srcString);
if (EcmaStringAccessor(srcString).IsTreeString()) {
srcString = JSHandle<EcmaString>(thread, flatStrInfo.GetString());
}
FlatStringInfo resultInfo = FlatStringInfo(*result, 0, resultStrLength);
uint32_t nextPos = 0;
uint32_t resultArrayLength = resultArray.size();
for (uint32_t i = 0; i < resultArrayLength; i++) {
JSTaggedValue substrValue = resultArray[i].GetTaggedValue();
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<uint8_t>(thread, *srcString, resultInfo.GetDataUtf8Writable(),
subLength, subPosition);
} else {
EcmaStringAccessor::WriteToFlatWithPos<uint16_t>(thread, *srcString, resultInfo.GetDataUtf16Writable(),
subLength, subPosition);
}
nextPos += subLength;
} else {
EcmaString *replacementStr = EcmaString::Cast(substrValue.GetTaggedObject());
uint32_t replaceLength = static_cast<uint32_t>(resultLengthArray[i]);
if (isUtf8) {
EcmaStringAccessor::WriteToFlat(thread, replacementStr, resultInfo.GetDataUtf8Writable(),
replaceLength);
} else {
EcmaStringAccessor::WriteToFlat(thread, replacementStr, resultInfo.GetDataUtf16Writable(),
replaceLength);
}
nextPos += replaceLength;
}
}
return result;
}
}