blob: e5c5452de4dede63e0359dc45d5da960a87c49e9 [file]
//-------------------------------------------------------------------------------------------------------
// 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());
#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) :
DynamicObject(instance),
pattern(instance->GetPattern()),
splitPattern(instance->GetSplitPattern()),
lastIndexVar(instance->lastIndexVar),
lastIndexOrFlag(instance->lastIndexOrFlag)
{
// For boxing stack instance
Assert(ThreadContext::IsOnStack(instance));
}
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)
{
AssertMsg(Is(aValue), "Ensure var is actually a 'JavascriptRegExp'");
return static_cast<JavascriptRegExp *>(RecyclableObject::FromVar(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<PropertyOperationFlags>(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)
{
if (JavascriptRegExp::Is(var))
{
return JavascriptRegExp::FromVar(var);
}
if (JavascriptOperators::GetTypeId(var) == TypeIds_HostDispatch)
{
TypeId remoteTypeId;
RecyclableObject* reclObj = RecyclableObject::FromVar(var);
reclObj->GetRemoteTypeId(&remoteTypeId);
if (remoteTypeId == TypeIds_RegEx)
{
return static_cast<JavascriptRegExp *>(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 if (JavascriptString::Is(args[1]))
{
return JavascriptString::FromVar(args[1]);
}
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 = callInfo.Flags & CallFlags_NewTarget ? args.Values[args.Info.Count] : function;
bool isCtorSuperCall = (callInfo.Flags & CallFlags_New) && newTarget != nullptr && !JavascriptOperators::IsUndefined(newTarget);
Assert(isCtorSuperCall || !(callInfo.Flags & CallFlags_New) || args[0] == nullptr
|| JavascriptOperators::GetTypeId(args[0]) == TypeIds_HostDispatch);
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], scriptContext)) &&
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], scriptContext))
{
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<UnifiedRegex::RegexFlags>(source->GetPattern()->GetFlags() & ~UnifiedRegex::StickyRegexFlag);
UnifiedRegex::RegexFlags newSplitFlags =
static_cast<UnifiedRegex::RegexFlags>(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, scriptContext))
{
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 (\<lineTerminator>), 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. \<lineTerminator> 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], scriptContext))
{
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_DATACOUNT(ES6_RegexSymbolMatch);
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_DATACOUNT(ES6_RegexSymbolReplace);
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_DATACOUNT(ES6_RegexSymbolSearch);
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_DATACOUNT(ES6_RegexSymbolSplit);
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], scriptContext))
? 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::FromVar(exec);
Var result = 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<UnifiedRegex::RegexFlags>(flags | flag)
: static_cast<UnifiedRegex::RegexFlags>(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<ArenaAllocator> 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<ArenaAllocator>* 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<ArenaAllocator> 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)
{
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);
*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)
{
const ScriptConfiguration* scriptConfig = this->GetScriptContext()->GetConfig();
#define HAS_PROPERTY(ownProperty) \
return (ownProperty ? Property_Found : DynamicObject::HasPropertyQuery(propertyId));
switch (propertyId)
{
case PropertyIds::lastIndex:
return 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);
}
#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();
JsUtil::CharacterBuffer<WCHAR> propertyName(propertyNameString->GetString(), propertyNameString->GetLength());
#define DELETE_PROPERTY(ownProperty) \
if (ownProperty) \
{ \
JavascriptError::ThrowCantDeleteIfStrictMode(flags, this->GetScriptContext(), propertyNameString->GetString()); \
return false; \
} \
return DynamicObject::DeleteProperty(propertyNameString, flags);
if (BuiltInPropertyRecords::lastIndex.Equals(propertyName))
{
DELETE_PROPERTY(true);
}
else if (BuiltInPropertyRecords::global.Equals(propertyName)
|| BuiltInPropertyRecords::multiline.Equals(propertyName)
|| BuiltInPropertyRecords::ignoreCase.Equals(propertyName)
|| BuiltInPropertyRecords::source.Equals(propertyName)
|| BuiltInPropertyRecords::options.Equals(propertyName))
{
DELETE_PROPERTY(!scriptConfig->IsES6RegExPrototypePropertiesEnabled());
}
else if (BuiltInPropertyRecords::unicode.Equals(propertyName))
{
DELETE_PROPERTY(scriptConfig->IsES6UnicodeExtensionsEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled());
}
else if (BuiltInPropertyRecords::sticky.Equals(propertyName))
{
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<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
{
Js::InternalString str = pattern->GetSource();
stringBuilder->Append(str.GetBuffer(), str.GetLength());
return TRUE;
}
BOOL JavascriptRegExp::GetDiagTypeString(StringBuilder<ArenaAllocator>* 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<TTD::NSSnapObjects::SnapRegexInfo>();
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<TTD::NSSnapObjects::SnapRegexInfo*, TTD::NSSnapObjects::SnapObjectType::SnapRegexObject>(objData, sri);
}
void JavascriptRegExp::SetLastIndexInfo_TTD(CharCount lastIndex, Js::Var lastVar)
{
this->lastIndexOrFlag = lastIndex;
this->lastIndexVar = lastVar;
}
#endif
} // namespace Js