//------------------------------------------------------------------------------------------------------- // Copyright (C) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- #include "RuntimeLibraryPch.h" // Parser Includes #include "DebugWriter.h" #include "RegexPattern.h" namespace Js { JavascriptRegExp::JavascriptRegExp(UnifiedRegex::RegexPattern* pattern, DynamicType* type) : DynamicObject(type), pattern(pattern), splitPattern(nullptr), lastIndexVar(nullptr), lastIndexOrFlag(0) { Assert(type->GetTypeId() == TypeIds_RegEx); Assert(!this->GetType()->AreThisAndPrototypesEnsuredToHaveOnlyWritableDataProperties()); Assert(!this->GetType()->ThisAndPrototypesHaveNoSpecialProperties()); #if ENABLE_REGEX_CONFIG_OPTIONS if (REGEX_CONFIG_FLAG(RegexTracing)) { UnifiedRegex::DebugWriter* w = type->GetScriptContext()->GetRegexDebugWriter(); if (pattern == 0) w->PrintEOL(_u("// REGEX CREATE")); else { w->Print(_u("// REGEX CREATE ")); pattern->Print(w); w->EOL(); } } #endif } JavascriptRegExp::JavascriptRegExp(DynamicType * type) : DynamicObject(type), pattern(nullptr), splitPattern(nullptr), lastIndexVar(nullptr), lastIndexOrFlag(0) { Assert(type->GetTypeId() == TypeIds_RegEx); #if DBG if (REGEX_CONFIG_FLAG(RegexTracing)) { UnifiedRegex::DebugWriter* w = type->GetScriptContext()->GetRegexDebugWriter(); w->PrintEOL(_u("REGEX CREATE")); } #endif } JavascriptRegExp::JavascriptRegExp(JavascriptRegExp * instance, bool deepCopy) : DynamicObject(instance, deepCopy), pattern(instance->GetPattern()), splitPattern(instance->GetSplitPattern()), lastIndexVar(instance->lastIndexVar), lastIndexOrFlag(instance->lastIndexOrFlag) { // For boxing stack instance Assert(ThreadContext::IsOnStack(instance)); // These members should never be on the stack and thus never need to be deep copied Assert(!ThreadContext::IsOnStack(instance->GetPattern())); Assert(!ThreadContext::IsOnStack(instance->GetSplitPattern())); Assert(!ThreadContext::IsOnStack(instance->lastIndexVar)); } bool JavascriptRegExp::Is(Var aValue) { return JavascriptOperators::GetTypeId(aValue) == TypeIds_RegEx; } // IsRegExp in the spec. bool JavascriptRegExp::IsRegExpLike(Var aValue, ScriptContext* scriptContext) { if (scriptContext->GetConfig()->IsES6RegExSymbolsEnabled()) { if (!JavascriptOperators::IsObject(aValue)) { return false; } Var symbolMatchProperty = JavascriptOperators::GetProperty( RecyclableObject::FromVar(aValue), PropertyIds::_symbolMatch, scriptContext); if (!JavascriptOperators::IsUndefined(symbolMatchProperty)) { return JavascriptConversion::ToBool(symbolMatchProperty, scriptContext); } } return JavascriptRegExp::Is(aValue); } JavascriptRegExp* JavascriptRegExp::FromVar(Var aValue) { AssertOrFailFastMsg(Is(aValue), "Ensure var is actually a 'JavascriptRegExp'"); return static_cast(aValue); } JavascriptRegExp* JavascriptRegExp::UnsafeFromVar(Var aValue) { AssertMsg(Is(aValue), "Ensure var is actually a 'JavascriptRegExp'"); return static_cast(aValue); } CharCount JavascriptRegExp::GetLastIndexProperty(RecyclableObject* instance, ScriptContext* scriptContext) { int64 lastIndex = JavascriptConversion::ToLength( JavascriptOperators::GetProperty(instance, PropertyIds::lastIndex, scriptContext), scriptContext); return GetIndexOrMax(lastIndex); } void JavascriptRegExp::SetLastIndexProperty(Var instance, CharCount lastIndex, ScriptContext* scriptContext) { SetLastIndexProperty( instance, JavascriptNumber::ToVar(lastIndex, scriptContext), scriptContext); } void JavascriptRegExp::SetLastIndexProperty(Var instance, Var lastIndex, ScriptContext* scriptContext) { JavascriptOperators::SetProperty( instance, RecyclableObject::FromVar(instance), PropertyIds::lastIndex, lastIndex, scriptContext, static_cast(PropertyOperation_ThrowIfNotExtensible | PropertyOperation_ThrowIfNonWritable)); } bool JavascriptRegExp::GetGlobalProperty(RecyclableObject* instance, ScriptContext* scriptContext) { return JavascriptConversion::ToBool( JavascriptOperators::GetProperty(instance, PropertyIds::global, scriptContext), scriptContext); } bool JavascriptRegExp::GetUnicodeProperty(RecyclableObject* instance, ScriptContext* scriptContext) { return JavascriptConversion::ToBool( JavascriptOperators::GetProperty(instance, PropertyIds::unicode, scriptContext), scriptContext); } CharCount JavascriptRegExp::AddIndex(CharCount base, CharCount offset) { return (base + offset < base) // Overflow? ? MaxCharCount : base + offset; } CharCount JavascriptRegExp::GetIndexOrMax(int64 index) { return (index > SIZE_MAX || IsValidCharCount((size_t) index)) ? (CharCount) index : MaxCharCount; } InternalString JavascriptRegExp::GetSource() const { return GetPattern()->GetSource(); } UnifiedRegex::RegexFlags JavascriptRegExp::GetFlags() const { return GetPattern()->GetFlags(); } JavascriptRegExp* JavascriptRegExp::GetJavascriptRegExp(Arguments& args, PCWSTR propertyName, ScriptContext* scriptContext) { if (args.Info.Count == 0) { JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedRegExp, propertyName); } return ToRegExp(args[0], propertyName, scriptContext); } JavascriptRegExp* JavascriptRegExp::ToRegExp(Var var, PCWSTR varName, ScriptContext* scriptContext) { JavascriptRegExp * regExp = JavascriptOperators::TryFromVar(var); if (regExp) { return regExp; } if (JavascriptOperators::GetTypeId(var) == TypeIds_HostDispatch) { TypeId remoteTypeId = TypeIds_Limit; RecyclableObject* reclObj = RecyclableObject::UnsafeFromVar(var); if (reclObj->GetRemoteTypeId(&remoteTypeId) && remoteTypeId == TypeIds_RegEx) { return static_cast(reclObj->GetRemoteObject()); } } JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedRegExp, varName); } RecyclableObject* JavascriptRegExp::GetThisObject(Arguments& args, PCWSTR varName, ScriptContext* scriptContext) { if (args.Info.Count == 0 || !JavascriptOperators::IsObject(args[0])) { JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedObject, varName); } return RecyclableObject::FromVar(args[0]); } JavascriptString* JavascriptRegExp::GetFirstStringArg(Arguments& args, ScriptContext* scriptContext) { if (args.Info.Count == 1) { return scriptContext->GetLibrary()->GetUndefinedDisplayString(); } else { JavascriptString *jsString = JavascriptOperators::TryFromVar(args[1]); if (jsString) { return jsString; } else { return JavascriptConversion::ToString(args[1], scriptContext); } } } bool JavascriptRegExp::ShouldApplyPrototypeWebWorkaround(Arguments& args, ScriptContext* scriptContext) { return scriptContext->GetConfig()->IsES6PrototypeChain() && \ args.Info.Count >= 1 && args[0] == scriptContext->GetLibrary()->GetRegExpPrototype(); } Var JavascriptRegExp::NewInstance(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); ScriptContext* scriptContext = function->GetScriptContext(); AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'"); // SkipDefaultNewObject function flag should have prevented the default object from // being created, except when call true a host dispatch. Var newTarget = args.HasNewTarget() ? args.Values[args.Info.Count] : function; bool isCtorSuperCall = JavascriptOperators::GetAndAssertIsConstructorSuperCall(args); UnifiedRegex::RegexPattern* pattern = nullptr; UnifiedRegex::RegexPattern* splitPattern = nullptr; JavascriptRegExp* regex = nullptr; if (callInfo.Count < 2) { pattern = scriptContext->GetLibrary()->GetEmptyRegexPattern(); } else if (JavascriptRegExp::IsRegExpLike(args[1], scriptContext)) { // JavascriptRegExp::IsRegExpLike() makes sure that args[1] is an Object. RecyclableObject* regexLikeObj = RecyclableObject::FromVar(args[1]); if (!(callInfo.Flags & CallFlags_New) && (callInfo.Count == 2 || JavascriptOperators::IsUndefinedObject(args[2])) && newTarget == JavascriptOperators::GetProperty(regexLikeObj, PropertyIds::constructor, scriptContext)) { // ES5 15.10.3.1 Called as a function: If pattern R is a regexp object and flags is undefined, then return R unchanged. // As per ES6 21.2.3.1: We should only return pattern when the this argument is not an uninitialized RegExp object. // If regex is null, we can be sure the this argument is not initialized. return regexLikeObj; } if (JavascriptRegExp::Is(regexLikeObj)) { JavascriptRegExp* source = JavascriptRegExp::FromVar(regexLikeObj); if (callInfo.Count > 2) { // As per ES 2015 21.2.3.1: If 1st argument is RegExp and 2nd argument is flag then return regexp with same pattern as 1st // argument and flags supplied by the 2nd argument. if (!JavascriptOperators::IsUndefinedObject(args[2])) { InternalString str = source->GetSource(); pattern = CreatePattern(JavascriptString::NewCopyBuffer(str.GetBuffer(), str.GetLength(), scriptContext), args[2], scriptContext); // "splitPattern" is a version of "pattern" without the sticky flag. If other flags are the same, we can safely // reuse "splitPattern". UnifiedRegex::RegexFlags currentSplitFlags = static_cast(source->GetPattern()->GetFlags() & ~UnifiedRegex::StickyRegexFlag); UnifiedRegex::RegexFlags newSplitFlags = static_cast(pattern->GetFlags() & ~UnifiedRegex::StickyRegexFlag); if (newSplitFlags == currentSplitFlags) { splitPattern = source->GetSplitPattern(); } } } if (!pattern) { pattern = source->GetPattern(); splitPattern = source->GetSplitPattern(); } } else // RegExp-like { Var source = JavascriptOperators::GetProperty(regexLikeObj, PropertyIds::source, scriptContext); Var flags = args.Info.Count < 3 || JavascriptOperators::IsUndefinedObject(args[2]) ? JavascriptOperators::GetProperty(regexLikeObj, PropertyIds::flags, scriptContext) : args[2]; pattern = CreatePattern(source, flags, scriptContext); } } else { pattern = CreatePattern(args[1], (callInfo.Count > 2) ? args[2] : nullptr, scriptContext); } if (regex == nullptr) { regex = scriptContext->GetLibrary()->CreateRegExp(nullptr); } regex->SetPattern(pattern); regex->SetSplitPattern(splitPattern); return isCtorSuperCall ? JavascriptOperators::OrdinaryCreateFromConstructor(RecyclableObject::FromVar(newTarget), regex, nullptr, scriptContext) : regex; } UnifiedRegex::RegexPattern* JavascriptRegExp::CreatePattern(Var aValue, Var options, ScriptContext *scriptContext) { JavascriptString * strBody; if (JavascriptString::Is(aValue)) { strBody = JavascriptString::FromVar(aValue); } else if (JavascriptOperators::GetTypeId(aValue) == TypeIds_Undefined) { strBody = scriptContext->GetLibrary()->GetEmptyString(); } else { strBody = JavascriptConversion::ToString(aValue, scriptContext); // must be null terminated! } int cBody = strBody->GetLength(); const char16 *szRegex = strBody->GetSz(); int cOpts = 0; const char16 *szOptions = nullptr; JavascriptString * strOptions = nullptr; if (options != nullptr && !JavascriptOperators::IsUndefinedObject(options)) { if (JavascriptString::Is(options)) { strOptions = JavascriptString::FromVar(options); } else { strOptions = JavascriptConversion::ToString(options, scriptContext); } szOptions = strOptions->GetSz(); // must be null terminated! cOpts = strOptions->GetLength(); } UnifiedRegex::RegexPattern* pattern = RegexHelper::CompileDynamic(scriptContext, szRegex, cBody, szOptions, cOpts, false); return pattern; } JavascriptRegExp* JavascriptRegExp::CreateRegEx(const char16* pSource, CharCount sourceLen, UnifiedRegex::RegexFlags flags, ScriptContext *scriptContext) { UnifiedRegex::RegexPattern* pattern = RegexHelper::CompileDynamic(scriptContext, pSource, sourceLen, flags, false); return scriptContext->GetLibrary()->CreateRegExp(pattern); } JavascriptRegExp* JavascriptRegExp::CreateRegEx(Var aValue, Var options, ScriptContext *scriptContext) { // This is called as helper from OpCode::CoerseRegEx. If aValue is regex pattern /a/, CreatePattern converts // it to pattern "/a/" instead of "a". So if we know that aValue is regex, then just return the same object if (JavascriptRegExp::Is(aValue)) { return JavascriptRegExp::FromVar(aValue); } else { return CreateRegExNoCoerce(aValue, options, scriptContext); } } JavascriptRegExp* JavascriptRegExp::CreateRegExNoCoerce(Var aValue, Var options, ScriptContext *scriptContext) { UnifiedRegex::RegexPattern* pattern = CreatePattern(aValue, options, scriptContext); return scriptContext->GetLibrary()->CreateRegExp(pattern); } void JavascriptRegExp::CacheLastIndex() { if (lastIndexVar == nullptr) lastIndexOrFlag = 0; else { // Does ToInteger(lastIndex) yield an integer in [0, MaxCharCount]? double v = JavascriptConversion::ToInteger(lastIndexVar, GetScriptContext()); if (JavascriptNumber::IsNan(v)) lastIndexOrFlag = 0; else if (JavascriptNumber::IsPosInf(v) || JavascriptNumber::IsNegInf(v) || v < 0.0 || v > (double)MaxCharCount) lastIndexOrFlag = InvalidValue; else lastIndexOrFlag = (CharCount)v; } } JavascriptString *JavascriptRegExp::ToString(bool sourceOnly) { Js::InternalString str = pattern->GetSource(); CompoundString *const builder = CompoundString::NewWithCharCapacity(str.GetLength() + 5, GetLibrary()); if (!sourceOnly) { builder->AppendChars(_u('/')); } if (pattern->IsLiteral()) { builder->AppendChars(str.GetBuffer(), str.GetLength()); } else { // Need to ensure that the resulting static regex is functionally equivalent (as written) to 'this' regex. This // involves the following: // - Empty regex should result in /(?:)/ rather than //, which is a comment // - Unescaped '/' needs to be escaped so that it doesn't end the static regex prematurely // - Line terminators need to be escaped since they're not allowed in a static regex if (str.GetLength() == 0) { builder->AppendChars(_u("(?:)")); } else { bool escape = false; for (charcount_t i = 0; i < str.GetLength(); ++i) { const char16 c = str.GetBuffer()[i]; if(!escape) { switch(c) { case _u('/'): case _u('\n'): case _u('\r'): case _u('\x2028'): case _u('\x2029'): // Unescaped '/' or line terminator needs to be escaped break; case _u('\\'): // Escape sequence; the next character is escaped and shouldn't be escaped further escape = true; Assert(i + 1 < str.GetLength()); // cannot end in a '\' // '\' is appended on the next iteration as 'escape' is true. This handles the case where we // have an escaped line terminator (\), where \\n has a different meaning and we // need to use \n instead. continue; default: builder->AppendChars(c); continue; } } else { escape = false; } builder->AppendChars(_u('\\')); switch(c) { // Line terminators need to be escaped. \ is a special case, where \\n doesn't work // since that means a '\' followed by an 'n'. We need to use \n instead. case _u('\n'): builder->AppendChars(_u('n')); break; case _u('\r'): builder->AppendChars(_u('r')); break; case _u('\x2028'): builder->AppendChars(_u("u2028")); break; case _u('\x2029'): builder->AppendChars(_u("u2029")); break; default: builder->AppendChars(c); } } } } if (!sourceOnly) { builder->AppendChars(_u('/')); // Cross-browser compatibility - flags are listed in alphabetical order in the spec and by other browsers // If you change the order of the flags, don't forget to change it in EntryGetterFlags() and GetOptions() too. if (pattern->IsGlobal()) { builder->AppendChars(_u('g')); } if (pattern->IsIgnoreCase()) { builder->AppendChars(_u('i')); } if (pattern->IsMultiline()) { builder->AppendChars(_u('m')); } if (pattern->IsUnicode()) { builder->AppendChars(_u('u')); } if (pattern->IsSticky()) { builder->AppendChars(_u('y')); } } return builder; } Var JavascriptRegExp::EntryCompile(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); ScriptContext* scriptContext = function->GetScriptContext(); JavascriptRegExp* thisRegularExpression = GetJavascriptRegExp(args, _u("RegExp.prototype.compile"), scriptContext); UnifiedRegex::RegexPattern* pattern; UnifiedRegex::RegexPattern* splitPattern = nullptr; if (callInfo.Count == 1 ) { pattern = scriptContext->GetLibrary()->GetEmptyRegexPattern(); } else if (JavascriptRegExp::Is(args[1])) { JavascriptRegExp* source = JavascriptRegExp::FromVar(args[1]); //compile with a regular expression pattern = source->GetPattern(); splitPattern = source->GetSplitPattern(); // second arg must be undefined if a reg expression is passed if(callInfo.Count > 2 && JavascriptOperators::GetTypeId(args[2]) != TypeIds_Undefined) { JavascriptError::ThrowSyntaxError(scriptContext, JSERR_RegExpSyntax); } } else { //compile with a string JavascriptString * strBody; if (JavascriptString::Is(args[1])) { strBody = JavascriptString::FromVar(args[1]); } else if(JavascriptOperators::GetTypeId(args[1]) == TypeIds_Undefined) { strBody = scriptContext->GetLibrary()->GetEmptyString(); } else { strBody = JavascriptConversion::ToString(args[1], scriptContext); } int cBody = strBody->GetLength(); const char16 *szRegex = strBody->GetSz(); // must be null terminated! int cOpts = 0; const char16 *szOptions = nullptr; JavascriptString * strOptions = nullptr; if (callInfo.Count > 2 && !JavascriptOperators::IsUndefinedObject(args[2])) { if (JavascriptString::Is(args[2])) { strOptions = JavascriptString::FromVar(args[2]); } else { strOptions = JavascriptConversion::ToString(args[2], scriptContext); } szOptions = strOptions->GetSz(); // must be null terminated! cOpts = strOptions->GetLength(); } pattern = RegexHelper::CompileDynamic(scriptContext, szRegex, cBody, szOptions, cOpts, false); } thisRegularExpression->SetPattern(pattern); thisRegularExpression->SetSplitPattern(splitPattern); thisRegularExpression->SetLastIndex(0); return thisRegularExpression; } Var JavascriptRegExp::OP_NewRegEx(Var aCompiledRegex, ScriptContext* scriptContext) { JavascriptRegExp * pNewInstance = RecyclerNew(scriptContext->GetRecycler(),JavascriptRegExp,((UnifiedRegex::RegexPattern*)aCompiledRegex), scriptContext->GetLibrary()->GetRegexType()); return pNewInstance; } Var JavascriptRegExp::EntryExec(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); ScriptContext* scriptContext = function->GetScriptContext(); Assert(!(callInfo.Flags & CallFlags_New)); JavascriptRegExp * pRegEx = GetJavascriptRegExp(args, _u("RegExp.prototype.exec"), scriptContext); JavascriptString * pStr = GetFirstStringArg(args, scriptContext); return RegexHelper::RegexExec(scriptContext, pRegEx, pStr, RegexHelper::IsResultNotUsed(callInfo.Flags)); } Var JavascriptRegExp::EntryTest(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); ScriptContext* scriptContext = function->GetScriptContext(); Assert(!(callInfo.Flags & CallFlags_New)); RecyclableObject *thisObj = GetThisObject(args, _u("RegExp.prototype.test"), scriptContext); JavascriptString* string = GetFirstStringArg(args, scriptContext); return RegexHelper::RegexTest(scriptContext, thisObj, string); } Var JavascriptRegExp::EntryToString(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); ScriptContext* scriptContext = function->GetScriptContext(); Assert(!(callInfo.Flags & CallFlags_New)); Assert(args.Info.Count > 0); PCWSTR const varName = _u("RegExp.prototype.toString"); const ScriptConfiguration* scriptConfig = scriptContext->GetConfig(); if (scriptConfig->IsES6RegExPrototypePropertiesEnabled()) { RecyclableObject *thisObj = GetThisObject(args, varName, scriptContext); JavascriptString* source = JavascriptConversion::ToString( JavascriptOperators::GetProperty(thisObj, PropertyIds::source, scriptContext), scriptContext); JavascriptString* flags = JavascriptConversion::ToString( JavascriptOperators::GetProperty(thisObj, PropertyIds::flags, scriptContext), scriptContext); CharCount length = source->GetLength() + flags->GetLength() + 2; // 2 for the two '/'s CompoundString *const builder = CompoundString::NewWithCharCapacity(length, scriptContext->GetLibrary()); builder->Append(_u('/')); builder->Append(source); builder->Append(_u('/')); builder->Append(flags); return builder; } else { JavascriptRegExp* obj = GetJavascriptRegExp(args, varName, scriptContext); bool sourceOnly = false; return obj->ToString(sourceOnly); } } Var JavascriptRegExp::EntrySymbolMatch(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); Assert(!(callInfo.Flags & CallFlags_New)); ScriptContext* scriptContext = function->GetScriptContext(); CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, RegexSymbolMatch, scriptContext); PCWSTR const varName = _u("RegExp.prototype[Symbol.match]"); RecyclableObject *thisObj = GetThisObject(args, varName, scriptContext); JavascriptString* string = GetFirstStringArg(args, scriptContext); return RegexHelper::RegexMatch( scriptContext, thisObj, string, RegexHelper::IsResultNotUsed(callInfo.Flags)); } bool JavascriptRegExp::HasOriginalRegExType(RecyclableObject* instance) { JavascriptLibrary* library = instance->GetLibrary(); if (instance->GetType() != library->GetRegexType()) { return false; } DynamicObject* regexPrototype = library->GetRegExpPrototype(); return JavascriptOperators::GetPrototype(instance) == regexPrototype && regexPrototype->GetType() == library->GetRegexPrototypeType(); } bool JavascriptRegExp::HasObservableConstructor(DynamicObject* regexPrototype) { JavascriptLibrary* library = regexPrototype->GetLibrary(); return regexPrototype->GetSlot(library->GetRegexConstructorSlotIndex()) != library->GetRegExpConstructor(); } bool JavascriptRegExp::HasObservableExec(DynamicObject* regexPrototype) { JavascriptLibrary* library = regexPrototype->GetLibrary(); return regexPrototype->GetSlot(library->GetRegexExecSlotIndex()) != library->GetRegexExecFunction(); } bool JavascriptRegExp::HasObservableFlags(DynamicObject* regexPrototype) { JavascriptLibrary* library = regexPrototype->GetLibrary(); return regexPrototype->GetScriptContext()->GetConfig()->IsES6RegExPrototypePropertiesEnabled() && regexPrototype->GetSlot(library->GetRegexFlagsGetterSlotIndex()) != library->GetRegexFlagsGetterFunction(); } bool JavascriptRegExp::HasObservableGlobalFlag(DynamicObject* regexPrototype) { JavascriptLibrary* library = regexPrototype->GetLibrary(); return regexPrototype->GetScriptContext()->GetConfig()->IsES6RegExPrototypePropertiesEnabled() && regexPrototype->GetSlot(library->GetRegexGlobalGetterSlotIndex()) != library->GetRegexGlobalGetterFunction(); } bool JavascriptRegExp::HasObservableUnicodeFlag(DynamicObject* regexPrototype) { const ScriptConfiguration* scriptConfig = regexPrototype->GetScriptContext()->GetConfig(); JavascriptLibrary* library = regexPrototype->GetLibrary(); return scriptConfig->IsES6UnicodeExtensionsEnabled() && scriptConfig->IsES6RegExPrototypePropertiesEnabled() && regexPrototype->GetSlot(library->GetRegexUnicodeGetterSlotIndex()) != library->GetRegexUnicodeGetterFunction(); } Var JavascriptRegExp::EntrySymbolReplace(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); Assert(!(callInfo.Flags & CallFlags_New)); ScriptContext* scriptContext = function->GetScriptContext(); CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, RegexSymbolReplace, scriptContext); PCWSTR varName = _u("RegExp.prototype[Symbol.replace]"); RecyclableObject* thisObj = GetThisObject(args, varName, scriptContext); JavascriptString* string = GetFirstStringArg(args, scriptContext); Var replaceValue = (args.Info.Count > 2) ? args[2] : scriptContext->GetLibrary()->GetUndefined(); if (JavascriptFunction::Is(replaceValue)) { JavascriptFunction* replaceFunction = JavascriptFunction::FromVar(replaceValue); return RegexHelper::RegexReplaceFunction(scriptContext, thisObj, string, replaceFunction); } else { JavascriptString* replaceString = JavascriptConversion::ToString(replaceValue, scriptContext); return RegexHelper::RegexReplace( scriptContext, thisObj, string, replaceString, RegexHelper::IsResultNotUsed(callInfo.Flags)); } } Var JavascriptRegExp::EntrySymbolSearch(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); Assert(!(callInfo.Flags & CallFlags_New)); ScriptContext* scriptContext = function->GetScriptContext(); CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, RegexSymbolSearch, scriptContext); PCWSTR const varName = _u("RegExp.prototype[Symbol.search]"); RecyclableObject *thisObj = GetThisObject(args, varName, scriptContext); Var regEx = args[0]; JavascriptString* string = GetFirstStringArg(args, scriptContext); Var previousLastIndex = JavascriptOperators::GetProperty(thisObj, PropertyIds::lastIndex, scriptContext); SetLastIndexProperty(regEx, TaggedInt::ToVarUnchecked(0), scriptContext); Var result = CallExec(thisObj, string, varName, scriptContext); SetLastIndexProperty(regEx, previousLastIndex, scriptContext); return JavascriptOperators::IsNull(result) ? TaggedInt::ToVarUnchecked(-1) : JavascriptOperators::GetProperty(RecyclableObject::FromVar(result), PropertyIds::index, scriptContext); } Var JavascriptRegExp::EntrySymbolSplit(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); Assert(!(callInfo.Flags & CallFlags_New)); ScriptContext* scriptContext = function->GetScriptContext(); CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, RegexSymbolSplit, scriptContext); RecyclableObject *thisObj = GetThisObject(args, _u("RegExp.prototype[Symbol.match]"), scriptContext); JavascriptString* string = GetFirstStringArg(args, scriptContext); // TODO: SPEC DEVIATION // // In RegexHelper::RegexSplit, we check if RegExp properties are overridden in order to determine // if the algorithm is observable. If it is, we go through the new ES6 algorithm, but otherwise, we // run the faster ES5 version. // // According to the spec, we're supposed to process "limit" after we use some of the RegExp properties. // However, there doesn't seem to be any reason why "limit" processing can't be pulled above the rest // in the spec. Therefore, we should see if such a spec update is OK. If not, this would have to be // moved to its correct place in the code. uint32 limit = (args.Info.Count < 3 || JavascriptOperators::IsUndefinedObject(args[2])) ? UINT_MAX : JavascriptConversion::ToUInt32(args[2], scriptContext); return RegexHelper::RegexSplit( scriptContext, thisObj, string, limit, RegexHelper::IsResultNotUsed(callInfo.Flags)); } Var JavascriptRegExp::CallExec(RecyclableObject* thisObj, JavascriptString* string, PCWSTR varName, ScriptContext* scriptContext) { Var exec = JavascriptOperators::GetProperty(thisObj, PropertyIds::exec, scriptContext); if (JavascriptConversion::IsCallable(exec)) { RecyclableObject* execFn = RecyclableObject::UnsafeFromVar(exec); ThreadContext * threadContext = scriptContext->GetThreadContext(); Var result = threadContext->ExecuteImplicitCall(execFn, ImplicitCall_Accessor, [=]()->Js::Var { return CALL_FUNCTION(scriptContext->GetThreadContext(), execFn, CallInfo(CallFlags_Value, 2), thisObj, string); }); if (!JavascriptOperators::IsObjectOrNull(result)) { JavascriptError::ThrowTypeError(scriptContext, JSERR_RegExpExecInvalidReturnType, varName); } return result; } JavascriptRegExp* regExObj = ToRegExp(thisObj, varName, scriptContext); return RegexHelper::RegexExec(scriptContext, regExObj, string, false); } UnifiedRegex::RegexFlags JavascriptRegExp::SetRegexFlag( PropertyId propertyId, UnifiedRegex::RegexFlags flags, UnifiedRegex::RegexFlags flag, ScriptContext* scriptContext) { bool isEnabled = JavascriptConversion::ToBool( JavascriptOperators::GetProperty(this, propertyId, scriptContext), scriptContext); return isEnabled ? static_cast(flags | flag) : static_cast(flags & ~flag); } Var JavascriptRegExp::EntryGetterSymbolSpecies(RecyclableObject* function, CallInfo callInfo, ...) { ARGUMENTS(args, callInfo); Assert(args.Info.Count > 0); return args[0]; } Var JavascriptRegExp::EntryGetterFlags(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); Assert(!(callInfo.Flags & CallFlags_New)); ScriptContext* scriptContext = function->GetScriptContext(); RecyclableObject *thisObj = GetThisObject(args, _u("RegExp.prototype.flags"), scriptContext); Var flags; BEGIN_TEMP_ALLOCATOR(tempAlloc, scriptContext, _u("JavascriptRegExp")) { StringBuilder bs(tempAlloc, 5); #define APPEND_FLAG(propertyId, flag) AppendFlagForFlagsProperty(&bs, thisObj, propertyId, flag, scriptContext) // If you change the order of the flags, don't forget to change it in GetOptions() and ToString() too. APPEND_FLAG(PropertyIds::global, _u('g')); APPEND_FLAG(PropertyIds::ignoreCase, _u('i')); APPEND_FLAG(PropertyIds::multiline, _u('m')); ScriptConfiguration const * scriptConfig = scriptContext->GetConfig(); if (scriptConfig->IsES6UnicodeExtensionsEnabled()) { APPEND_FLAG(PropertyIds::unicode, _u('u')); } if (scriptConfig->IsES6RegExStickyEnabled()) { APPEND_FLAG(PropertyIds::sticky, _u('y')); } #undef APPEND_FLAG flags = Js::JavascriptString::NewCopyBuffer(bs.Detach(), bs.Count(), scriptContext); } END_TEMP_ALLOCATOR(tempAlloc, scriptContext); return flags; } void JavascriptRegExp::AppendFlagForFlagsProperty( StringBuilder* builder, RecyclableObject* thisObj, PropertyId propertyId, char16 flag, ScriptContext* scriptContext) { Var propertyValue = JavascriptOperators::GetProperty(thisObj, propertyId, scriptContext); if (JavascriptConversion::ToBoolean(propertyValue, scriptContext)) { builder->Append(flag); } } Var JavascriptRegExp::EntryGetterOptions(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); Assert(!(callInfo.Flags & CallFlags_New)); ScriptContext* scriptContext = function->GetScriptContext(); if (ShouldApplyPrototypeWebWorkaround(args, scriptContext)) { return scriptContext->GetLibrary()->GetUndefined(); } return GetJavascriptRegExp(args, _u("RegExp.prototype.options"), scriptContext)->GetOptions(); } Var JavascriptRegExp::GetOptions() { Var options; ScriptContext* scriptContext = this->GetLibrary()->GetScriptContext(); BEGIN_TEMP_ALLOCATOR(tempAlloc, scriptContext, _u("JavascriptRegExp")) { StringBuilder bs(tempAlloc, 4); // If you change the order of the flags, don't forget to change it in EntryGetterFlags() and ToString() too. if(GetPattern()->IsGlobal()) { bs.Append(_u('g')); } if(GetPattern()->IsIgnoreCase()) { bs.Append(_u('i')); } if(GetPattern()->IsMultiline()) { bs.Append(_u('m')); } if (GetPattern()->IsUnicode()) { bs.Append(_u('u')); } if (GetPattern()->IsSticky()) { bs.Append(_u('y')); } options = Js::JavascriptString::NewCopyBuffer(bs.Detach(), bs.Count(), scriptContext); } END_TEMP_ALLOCATOR(tempAlloc, scriptContext); return options; } Var JavascriptRegExp::EntryGetterSource(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); Assert(!(callInfo.Flags & CallFlags_New)); ScriptContext* scriptContext = function->GetScriptContext(); if (ShouldApplyPrototypeWebWorkaround(args, scriptContext)) { return JavascriptString::NewCopyBuffer(_u("(?:)"), 4, scriptContext); } return GetJavascriptRegExp(args, _u("RegExp.prototype.source"), scriptContext)->ToString(true); } #define DEFINE_FLAG_GETTER(methodName, propertyName, patternMethodName) \ Var JavascriptRegExp::##methodName##(RecyclableObject* function, CallInfo callInfo, ...) \ { \ PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); \ ARGUMENTS(args, callInfo); \ Assert(!(callInfo.Flags & CallFlags_New)); \ \ ScriptContext* scriptContext = function->GetScriptContext(); \ if (ShouldApplyPrototypeWebWorkaround(args, scriptContext)) \ {\ return scriptContext->GetLibrary()->GetUndefined(); \ }\ \ JavascriptRegExp* pRegEx = GetJavascriptRegExp(args, _u("RegExp.prototype.") _u(#propertyName), scriptContext); \ return pRegEx->GetLibrary()->CreateBoolean(pRegEx->GetPattern()->##patternMethodName##()); \ } DEFINE_FLAG_GETTER(EntryGetterGlobal, global, IsGlobal) DEFINE_FLAG_GETTER(EntryGetterIgnoreCase, ignoreCase, IsIgnoreCase) DEFINE_FLAG_GETTER(EntryGetterMultiline, multiline, IsMultiline) DEFINE_FLAG_GETTER(EntryGetterSticky, sticky, IsSticky) DEFINE_FLAG_GETTER(EntryGetterUnicode, unicode, IsUnicode) JavascriptRegExp * JavascriptRegExp::BoxStackInstance(JavascriptRegExp * instance, bool deepCopy) { Assert(ThreadContext::IsOnStack(instance)); // On the stack, the we reserved a pointer before the object as to store the boxed value JavascriptRegExp ** boxedInstanceRef = ((JavascriptRegExp **)instance) - 1; JavascriptRegExp * boxedInstance = *boxedInstanceRef; if (boxedInstance) { return boxedInstance; } Assert(instance->GetTypeHandler()->GetInlineSlotsSize() == 0); boxedInstance = RecyclerNew(instance->GetRecycler(), JavascriptRegExp, instance, deepCopy); *boxedInstanceRef = boxedInstance; return boxedInstance; } // Both 'unicode' and 'sticky' special properties could be controlled via config // flags. Below, 'sticky' is listed after 'unicode' (for no special reason). // When the 'unicode' config flag is disabled, we want 'unicode' to be excluded // from the list, but we still want 'sticky' to be included depending on its // config flag. That's the reason why we have two lists for the special property // IDs. #define DEFAULT_SPECIAL_PROPERTY_IDS \ PropertyIds::lastIndex, \ PropertyIds::global, \ PropertyIds::multiline, \ PropertyIds::ignoreCase, \ PropertyIds::source, \ PropertyIds::options PropertyId const JavascriptRegExp::specialPropertyIdsAll[] = { DEFAULT_SPECIAL_PROPERTY_IDS, PropertyIds::unicode, PropertyIds::sticky }; PropertyId const JavascriptRegExp::specialPropertyIdsWithoutUnicode[] = { DEFAULT_SPECIAL_PROPERTY_IDS, PropertyIds::sticky }; PropertyQueryFlags JavascriptRegExp::HasPropertyQuery(PropertyId propertyId, _Inout_opt_ PropertyValueInfo* info) { const ScriptConfiguration* scriptConfig = this->GetScriptContext()->GetConfig(); #define HAS_PROPERTY(ownProperty) \ return (ownProperty ? PropertyQueryFlags::Property_Found : DynamicObject::HasPropertyQuery(propertyId, info)); switch (propertyId) { case PropertyIds::lastIndex: return PropertyQueryFlags::Property_Found; case PropertyIds::global: case PropertyIds::multiline: case PropertyIds::ignoreCase: case PropertyIds::source: case PropertyIds::options: HAS_PROPERTY(!scriptConfig->IsES6RegExPrototypePropertiesEnabled()); case PropertyIds::unicode: HAS_PROPERTY(scriptConfig->IsES6UnicodeExtensionsEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled()) case PropertyIds::sticky: HAS_PROPERTY(scriptConfig->IsES6RegExStickyEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled()) default: return DynamicObject::HasPropertyQuery(propertyId, info); } #undef HAS_PROPERTY } PropertyQueryFlags JavascriptRegExp::GetPropertyReferenceQuery(Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext) { return JavascriptRegExp::GetPropertyQuery(originalInstance, propertyId, value, info, requestContext); } PropertyQueryFlags JavascriptRegExp::GetPropertyQuery(Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext) { BOOL result; if (GetPropertyBuiltIns(propertyId, value, &result)) { return JavascriptConversion::BooleanToPropertyQueryFlags(result); } return DynamicObject::GetPropertyQuery(originalInstance, propertyId, value, info, requestContext); } PropertyQueryFlags JavascriptRegExp::GetPropertyQuery(Var originalInstance, JavascriptString* propertyNameString, Var* value, PropertyValueInfo* info, ScriptContext* requestContext) { BOOL result; PropertyRecord const* propertyRecord; this->GetScriptContext()->FindPropertyRecord(propertyNameString, &propertyRecord); if (propertyRecord != nullptr && GetPropertyBuiltIns(propertyRecord->GetPropertyId(), value, &result)) { return JavascriptConversion::BooleanToPropertyQueryFlags(result); } return DynamicObject::GetPropertyQuery(originalInstance, propertyNameString, value, info, requestContext); } bool JavascriptRegExp::GetPropertyBuiltIns(PropertyId propertyId, Var* value, BOOL* result) { const ScriptConfiguration* scriptConfig = this->GetScriptContext()->GetConfig(); #define GET_FLAG(patternMethod) \ if (!scriptConfig->IsES6RegExPrototypePropertiesEnabled()) \ { \ *value = this->GetLibrary()->CreateBoolean(this->GetPattern()->##patternMethod##()); \ *result = true; \ return true; \ } \ else \ { \ return false; \ } switch (propertyId) { case PropertyIds::lastIndex: if (this->lastIndexVar == nullptr) { Assert(lastIndexOrFlag <= MaxCharCount); this->lastIndexVar = JavascriptNumber::ToVar(lastIndexOrFlag, GetScriptContext()); } *value = this->lastIndexVar; *result = true; return true; case PropertyIds::global: GET_FLAG(IsGlobal) case PropertyIds::multiline: GET_FLAG(IsMultiline) case PropertyIds::ignoreCase: GET_FLAG(IsIgnoreCase) case PropertyIds::unicode: GET_FLAG(IsUnicode) case PropertyIds::sticky: GET_FLAG(IsSticky) case PropertyIds::source: if (!scriptConfig->IsES6RegExPrototypePropertiesEnabled()) { *value = this->ToString(true); *result = true; return true; } else { return false; } case PropertyIds::options: if (!scriptConfig->IsES6RegExPrototypePropertiesEnabled()) { *value = GetOptions(); *result = true; return true; } else { return false; } default: return false; } #undef GET_FLAG } BOOL JavascriptRegExp::SetProperty(PropertyId propertyId, Var value, PropertyOperationFlags flags, PropertyValueInfo* info) { BOOL result; if (SetPropertyBuiltIns(propertyId, value, flags, &result)) { return result; } return DynamicObject::SetProperty(propertyId, value, flags, info); } BOOL JavascriptRegExp::SetProperty(JavascriptString* propertyNameString, Var value, PropertyOperationFlags flags, PropertyValueInfo* info) { BOOL result; PropertyRecord const * propertyRecord; this->GetScriptContext()->FindPropertyRecord(propertyNameString, &propertyRecord); if (propertyRecord != nullptr && SetPropertyBuiltIns(propertyRecord->GetPropertyId(), value, flags, &result)) { return result; } return DynamicObject::SetProperty(propertyNameString, value, flags, info); } bool JavascriptRegExp::SetPropertyBuiltIns(PropertyId propertyId, Var value, PropertyOperationFlags flags, BOOL* result) { const ScriptConfiguration* scriptConfig = this->GetScriptContext()->GetConfig(); #define SET_PROPERTY(ownProperty) \ if (ownProperty) \ { \ JavascriptError::ThrowCantAssignIfStrictMode(flags, this->GetScriptContext()); \ *result = false; \ return true; \ } \ return false; switch (propertyId) { case PropertyIds::lastIndex: this->lastIndexVar = value; lastIndexOrFlag = NotCachedValue; *result = true; return true; case PropertyIds::global: case PropertyIds::multiline: case PropertyIds::ignoreCase: case PropertyIds::source: case PropertyIds::options: SET_PROPERTY(!scriptConfig->IsES6RegExPrototypePropertiesEnabled()); case PropertyIds::unicode: SET_PROPERTY(scriptConfig->IsES6UnicodeExtensionsEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled()); case PropertyIds::sticky: SET_PROPERTY(scriptConfig->IsES6RegExStickyEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled()); default: return false; } #undef SET_PROPERTY } BOOL JavascriptRegExp::InitProperty(PropertyId propertyId, Var value, PropertyOperationFlags flags, PropertyValueInfo* info) { return SetProperty(propertyId, value, flags, info); } BOOL JavascriptRegExp::DeleteProperty(PropertyId propertyId, PropertyOperationFlags propertyOperationFlags) { const ScriptConfiguration* scriptConfig = this->GetScriptContext()->GetConfig(); #define DELETE_PROPERTY(ownProperty) \ if (ownProperty) \ { \ JavascriptError::ThrowCantDeleteIfStrictMode(propertyOperationFlags, this->GetScriptContext(), this->GetScriptContext()->GetPropertyName(propertyId)->GetBuffer()); \ return false; \ } \ return DynamicObject::DeleteProperty(propertyId, propertyOperationFlags); switch (propertyId) { case PropertyIds::lastIndex: DELETE_PROPERTY(true); case PropertyIds::global: case PropertyIds::multiline: case PropertyIds::ignoreCase: case PropertyIds::source: case PropertyIds::options: DELETE_PROPERTY(!scriptConfig->IsES6RegExPrototypePropertiesEnabled()); case PropertyIds::unicode: DELETE_PROPERTY(scriptConfig->IsES6UnicodeExtensionsEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled()); case PropertyIds::sticky: DELETE_PROPERTY(scriptConfig->IsES6RegExStickyEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled()); default: return DynamicObject::DeleteProperty(propertyId, propertyOperationFlags); } #undef DELETE_PROPERTY } BOOL JavascriptRegExp::DeleteProperty(JavascriptString *propertyNameString, PropertyOperationFlags flags) { const ScriptConfiguration* scriptConfig = this->GetScriptContext()->GetConfig(); #define DELETE_PROPERTY(ownProperty) \ if (ownProperty) \ { \ JavascriptError::ThrowCantDeleteIfStrictMode(flags, this->GetScriptContext(), propertyNameString->GetString()); \ return false; \ } \ return DynamicObject::DeleteProperty(propertyNameString, flags); if (BuiltInPropertyRecords::lastIndex.Equals(propertyNameString)) { DELETE_PROPERTY(true); } else if (BuiltInPropertyRecords::global.Equals(propertyNameString) || BuiltInPropertyRecords::multiline.Equals(propertyNameString) || BuiltInPropertyRecords::ignoreCase.Equals(propertyNameString) || BuiltInPropertyRecords::source.Equals(propertyNameString) || BuiltInPropertyRecords::options.Equals(propertyNameString)) { DELETE_PROPERTY(!scriptConfig->IsES6RegExPrototypePropertiesEnabled()); } else if (BuiltInPropertyRecords::unicode.Equals(propertyNameString)) { DELETE_PROPERTY(scriptConfig->IsES6UnicodeExtensionsEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled()); } else if (BuiltInPropertyRecords::sticky.Equals(propertyNameString)) { DELETE_PROPERTY(scriptConfig->IsES6RegExStickyEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled()); } else { return DynamicObject::DeleteProperty(propertyNameString, flags); } #undef DELETE_PROPERTY } DescriptorFlags JavascriptRegExp::GetSetter(PropertyId propertyId, Var* setterValue, PropertyValueInfo* info, ScriptContext* requestContext) { DescriptorFlags result; if (GetSetterBuiltIns(propertyId, info, &result)) { return result; } return DynamicObject::GetSetter(propertyId, setterValue, info, requestContext); } DescriptorFlags JavascriptRegExp::GetSetter(JavascriptString* propertyNameString, Var* setterValue, PropertyValueInfo* info, ScriptContext* requestContext) { DescriptorFlags result; PropertyRecord const * propertyRecord; this->GetScriptContext()->FindPropertyRecord(propertyNameString, &propertyRecord); if (propertyRecord != nullptr && GetSetterBuiltIns(propertyRecord->GetPropertyId(), info, &result)) { return result; } return DynamicObject::GetSetter(propertyNameString, setterValue, info, requestContext); } bool JavascriptRegExp::GetSetterBuiltIns(PropertyId propertyId, PropertyValueInfo* info, DescriptorFlags* result) { const ScriptConfiguration* scriptConfig = this->GetScriptContext()->GetConfig(); #define GET_SETTER(ownProperty) \ if (ownProperty) \ { \ PropertyValueInfo::SetNoCache(info, this); \ *result = JavascriptRegExp::IsWritable(propertyId) ? WritableData : Data; \ return true; \ } \ return false; switch (propertyId) { case PropertyIds::lastIndex: GET_SETTER(true); case PropertyIds::global: case PropertyIds::multiline: case PropertyIds::ignoreCase: case PropertyIds::source: case PropertyIds::options: GET_SETTER(!scriptConfig->IsES6RegExPrototypePropertiesEnabled()); case PropertyIds::unicode: GET_SETTER(scriptConfig->IsES6UnicodeExtensionsEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled()); case PropertyIds::sticky: GET_SETTER(scriptConfig->IsES6RegExStickyEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled()); default: return false; } #undef GET_SETTER } BOOL JavascriptRegExp::GetDiagValueString(StringBuilder* stringBuilder, ScriptContext* requestContext) { Js::InternalString str = pattern->GetSource(); stringBuilder->Append(str.GetBuffer(), str.GetLength()); return TRUE; } BOOL JavascriptRegExp::GetDiagTypeString(StringBuilder* stringBuilder, ScriptContext* requestContext) { stringBuilder->AppendCppLiteral(JS_DIAG_TYPE_JavascriptRegExp); return TRUE; } BOOL JavascriptRegExp::IsEnumerable(PropertyId propertyId) { const ScriptConfiguration* scriptConfig = this->GetScriptContext()->GetConfig(); #define IS_ENUMERABLE(ownProperty) \ return (ownProperty ? false : DynamicObject::IsEnumerable(propertyId)); switch (propertyId) { case PropertyIds::lastIndex: return false; case PropertyIds::global: case PropertyIds::multiline: case PropertyIds::ignoreCase: case PropertyIds::source: case PropertyIds::options: IS_ENUMERABLE(!scriptConfig->IsES6RegExPrototypePropertiesEnabled()); case PropertyIds::unicode: IS_ENUMERABLE(scriptConfig->IsES6UnicodeExtensionsEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled()); case PropertyIds::sticky: IS_ENUMERABLE(scriptConfig->IsES6RegExStickyEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled()); default: return DynamicObject::IsEnumerable(propertyId); } #undef IS_ENUMERABLE } BOOL JavascriptRegExp::IsConfigurable(PropertyId propertyId) { const ScriptConfiguration* scriptConfig = this->GetScriptContext()->GetConfig(); #define IS_CONFIGURABLE(ownProperty) \ return (ownProperty ? false : DynamicObject::IsConfigurable(propertyId)); switch (propertyId) { case PropertyIds::lastIndex: return false; case PropertyIds::global: case PropertyIds::multiline: case PropertyIds::ignoreCase: case PropertyIds::source: case PropertyIds::options: IS_CONFIGURABLE(!scriptConfig->IsES6RegExPrototypePropertiesEnabled()); case PropertyIds::unicode: IS_CONFIGURABLE(scriptConfig->IsES6UnicodeExtensionsEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled()); case PropertyIds::sticky: IS_CONFIGURABLE(scriptConfig->IsES6RegExStickyEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled()); default: return DynamicObject::IsConfigurable(propertyId); } #undef IS_CONFIGURABLE } BOOL JavascriptRegExp::IsWritable(PropertyId propertyId) { const ScriptConfiguration* scriptConfig = this->GetScriptContext()->GetConfig(); #define IS_WRITABLE(ownProperty) \ return (ownProperty ? false : DynamicObject::IsWritable(propertyId)); switch (propertyId) { case PropertyIds::lastIndex: return true; case PropertyIds::global: case PropertyIds::multiline: case PropertyIds::ignoreCase: case PropertyIds::source: case PropertyIds::options: IS_WRITABLE(!scriptConfig->IsES6RegExPrototypePropertiesEnabled()) case PropertyIds::unicode: IS_WRITABLE(scriptConfig->IsES6UnicodeExtensionsEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled()); case PropertyIds::sticky: IS_WRITABLE(scriptConfig->IsES6RegExStickyEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled()); default: return DynamicObject::IsWritable(propertyId); } #undef IS_WRITABLE } BOOL JavascriptRegExp::GetSpecialPropertyName(uint32 index, JavascriptString ** propertyName, ScriptContext * requestContext) { uint length = GetSpecialPropertyCount(); if (index < length) { *propertyName = requestContext->GetPropertyString(GetSpecialPropertyIdsInlined()[index]); return true; } return false; } // Returns the number of special non-enumerable properties this type has. uint JavascriptRegExp::GetSpecialPropertyCount() const { if (GetScriptContext()->GetConfig()->IsES6RegExPrototypePropertiesEnabled()) { return 1; // lastIndex } uint specialPropertyCount = defaultSpecialPropertyIdsCount; if (GetScriptContext()->GetConfig()->IsES6UnicodeExtensionsEnabled()) { specialPropertyCount += 1; } if (GetScriptContext()->GetConfig()->IsES6RegExStickyEnabled()) { specialPropertyCount += 1; } return specialPropertyCount; } // Returns the list of special non-enumerable properties for the type. PropertyId const * JavascriptRegExp::GetSpecialPropertyIds() const { return GetSpecialPropertyIdsInlined(); } inline PropertyId const * JavascriptRegExp::GetSpecialPropertyIdsInlined() const { return GetScriptContext()->GetConfig()->IsES6UnicodeExtensionsEnabled() ? specialPropertyIdsAll : specialPropertyIdsWithoutUnicode; } #if ENABLE_TTD TTD::NSSnapObjects::SnapObjectType JavascriptRegExp::GetSnapTag_TTD() const { return TTD::NSSnapObjects::SnapObjectType::SnapRegexObject; } void JavascriptRegExp::ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc) { TTD::NSSnapObjects::SnapRegexInfo* sri = alloc.SlabAllocateStruct(); UnifiedRegex::RegexPattern* pattern = this->pattern; alloc.CopyStringIntoWLength(pattern->GetSource().GetBuffer(), pattern->GetSource().GetLength(), sri->RegexStr); //split regex should be automatically generated from regex string and flags so no need to exttract it as well sri->Flags = this->GetFlags(); sri->LastIndexVar = TTD_CONVERT_JSVAR_TO_TTDVAR(this->lastIndexVar); sri->LastIndexOrFlag = this->lastIndexOrFlag; TTD::NSSnapObjects::StdExtractSetKindSpecificInfo(objData, sri); } void JavascriptRegExp::SetLastIndexInfo_TTD(CharCount lastIndex, Js::Var lastVar) { this->lastIndexOrFlag = lastIndex; this->lastIndexVar = lastVar; } #endif } // namespace Js