Browse Source

Move RegExp properties to the prototype

This change moves all instance properties except "lastIndex" to the
prototype as accessor properties.
Gorkem Yakin 10 năm trước cách đây
mục cha
commit
72dc28306c

+ 1 - 0
lib/Runtime/Base/ThreadConfigFlagsList.h

@@ -46,6 +46,7 @@ FLAG_RELEASE(IsES6UnicodeExtensionsEnabled, ES6Unicode)
 FLAG_RELEASE(IsES6UnscopablesEnabled, ES6Unscopables)
 FLAG_RELEASE(IsES6WeakSetEnabled, ES6WeakSet)
 FLAG_RELEASE(IsES6RegExStickyEnabled, ES6RegExSticky)
+FLAG_RELEASE(IsES6RegExPrototypePropertiesEnabled, ES6RegExPrototypeProperties)
 FLAG_RELEASE(IsES6HasInstanceEnabled, ES6HasInstance)
 FLAG_RELEASE(SkipSplitOnNoResult, SkipSplitOnNoResult)
 FLAG_RELEASE(IsES7AsyncAndAwaitEnabled, ES7AsyncAwait)

+ 7 - 0
lib/Runtime/Library/JavascriptBuiltInFunctionList.h

@@ -184,6 +184,13 @@ BUILTIN(JavascriptRegExp, Test, EntryTest, FunctionInfo::ErrorOnNew)
 BUILTIN(JavascriptRegExp, ToString, EntryToString, FunctionInfo::ErrorOnNew | FunctionInfo::HasNoSideEffect)
 BUILTIN(JavascriptRegExp, GetterSymbolSpecies, EntryGetterSymbolSpecies, FunctionInfo::ErrorOnNew)
 BUILTIN(JavascriptRegExp, Compile, EntryCompile, FunctionInfo::None)
+BUILTIN(JavascriptRegExp, GetterGlobal, EntryGetterGlobal, FunctionInfo::ErrorOnNew | FunctionInfo::HasNoSideEffect)
+BUILTIN(JavascriptRegExp, GetterIgnoreCase, EntryGetterIgnoreCase, FunctionInfo::ErrorOnNew | FunctionInfo::HasNoSideEffect)
+BUILTIN(JavascriptRegExp, GetterMultiline, EntryGetterMultiline, FunctionInfo::ErrorOnNew | FunctionInfo::HasNoSideEffect)
+BUILTIN(JavascriptRegExp, GetterOptions, EntryGetterOptions, FunctionInfo::ErrorOnNew | FunctionInfo::HasNoSideEffect)
+BUILTIN(JavascriptRegExp, GetterSource, EntryGetterSource, FunctionInfo::ErrorOnNew | FunctionInfo::HasNoSideEffect)
+BUILTIN(JavascriptRegExp, GetterSticky, EntryGetterSticky, FunctionInfo::ErrorOnNew | FunctionInfo::HasNoSideEffect)
+BUILTIN(JavascriptRegExp, GetterUnicode, EntryGetterUnicode, FunctionInfo::ErrorOnNew | FunctionInfo::HasNoSideEffect)
 BUILTIN(JavascriptString, NewInstance, NewInstance, FunctionInfo::SkipDefaultNewObject)
 BUILTIN(JavascriptString, CharAt, EntryCharAt, FunctionInfo::ErrorOnNew)
 BUILTIN(JavascriptString, CharCodeAt, EntryCharCodeAt, FunctionInfo::ErrorOnNew)

+ 31 - 5
lib/Runtime/Library/JavascriptLibrary.cpp

@@ -2534,24 +2534,30 @@ namespace Js
 
         Recycler *const recycler = GetRecycler();
 
+        const ScriptConfiguration *scriptConfig = scriptContext->GetConfig();
+
         // Creating the regex prototype object requires compiling an empty regex, which may require error types to be
         // initialized first (such as when a stack probe fails). So, the regex prototype and other things that depend on it are
         // initialized here, which will be after the dependency types are initialized.
         //
         // In ES6, RegExp.prototype is not a RegExp object itself so we do not need to wait and create an empty RegExp.
         // Instead, we just create an ordinary object prototype for RegExp.prototype in InitializePrototypes.
-        if (!scriptContext->GetConfig()->IsES6PrototypeChain() && regexPrototype == nullptr)
+        if (!scriptConfig->IsES6PrototypeChain() && regexPrototype == nullptr)
         {
             regexPrototype = RecyclerNew(recycler, JavascriptRegExp, emptyRegexPattern,
                 DynamicType::New(scriptContext, TypeIds_RegEx, objectPrototype, nullptr,
                 DeferredTypeHandler<InitializeRegexPrototype>::GetDefaultInstance()));
         }
 
-        regexType = DynamicType::New(scriptContext, TypeIds_RegEx, regexPrototype, nullptr,
-            SimplePathTypeHandler::New(scriptContext, scriptContext->GetRootPath(), 0, 0, 0, true, true), true, true);
+        SimplePathTypeHandler *typeHandler =
+            SimplePathTypeHandler::New(scriptContext, scriptContext->GetRootPath(), 0, 0, 0, true, true);
+        // See JavascriptRegExp::IsWritable for property writability
+        if (!scriptConfig->IsES6RegExPrototypePropertiesEnabled())
+        {
+            typeHandler->ClearHasOnlyWritableDataProperties();
+        }
 
-        // See JavascriptRegExp::IsWritable for special non-writable properties
-        regexType->GetTypeHandler()->ClearHasOnlyWritableDataProperties();
+        regexType = DynamicType::New(scriptContext, TypeIds_RegEx, regexPrototype, nullptr, typeHandler, true, true);
     }
 
     void JavascriptLibrary::InitializeMathObject(DynamicObject* mathObject, DeferredTypeHandlerBase * typeHandler, DeferredInitializeMode mode)
@@ -3631,6 +3637,26 @@ namespace Js
         // This is deprecated. Should be guarded with appropriate version flag.
         library->AddFunctionToLibraryObject(regexPrototype, PropertyIds::compile, &JavascriptRegExp::EntryInfo::Compile, 2);
 
+        const ScriptConfiguration* scriptConfig = regexPrototype->GetScriptContext()->GetConfig();
+        if (scriptConfig->IsES6RegExPrototypePropertiesEnabled())
+        {
+            library->AddAccessorsToLibraryObject(regexPrototype, PropertyIds::global, &JavascriptRegExp::EntryInfo::GetterGlobal, nullptr);
+            library->AddAccessorsToLibraryObject(regexPrototype, PropertyIds::ignoreCase, &JavascriptRegExp::EntryInfo::GetterIgnoreCase, nullptr);
+            library->AddAccessorsToLibraryObject(regexPrototype, PropertyIds::multiline, &JavascriptRegExp::EntryInfo::GetterMultiline, nullptr);
+            library->AddAccessorsToLibraryObject(regexPrototype, PropertyIds::options, &JavascriptRegExp::EntryInfo::GetterOptions, nullptr);
+            library->AddAccessorsToLibraryObject(regexPrototype, PropertyIds::source, &JavascriptRegExp::EntryInfo::GetterSource, nullptr);
+
+            if (scriptConfig->IsES6RegExStickyEnabled())
+            {
+                library->AddAccessorsToLibraryObject(regexPrototype, PropertyIds::sticky, &JavascriptRegExp::EntryInfo::GetterSticky, nullptr);
+            }
+
+            if (scriptConfig->IsES6UnicodeExtensionsEnabled())
+            {
+                library->AddAccessorsToLibraryObject(regexPrototype, PropertyIds::unicode, &JavascriptRegExp::EntryInfo::GetterUnicode, nullptr);
+            }
+        }
+
         DebugOnly(CheckRegisteredBuiltIns(builtinFuncs, library->GetScriptContext()));
 
         regexPrototype->SetHasNoEnumerableProperties(true);

+ 196 - 167
lib/Runtime/Library/JavascriptRegularExpression.cpp

@@ -18,10 +18,6 @@ namespace Js
         lastIndexOrFlag(0)
     {
         Assert(type->GetTypeId() == TypeIds_RegEx);
-
-        // See JavascriptRegExp::IsWritable for special non-writable properties
-        // The JavascriptLibrary should have cleared the bits already
-        Assert(!this->GetTypeHandler()->GetHasOnlyWritableDataProperties());
         Assert(!this->GetType()->AreThisAndPrototypesEnsuredToHaveOnlyWritableDataProperties());
 
 #if ENABLE_REGEX_CONFIG_OPTIONS
@@ -508,12 +504,6 @@ namespace Js
 
         Assert(!(callInfo.Flags & CallFlags_New));
 
-        // enforce 'this' arg generic
-        if (args.Info.Count == 0)
-        {
-            JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedRegExp, L"RegExp.prototype.exec");
-        }
-
         JavascriptRegExp * pRegEx = GetJavascriptRegExp(args, L"RegExp.prototype.exec", scriptContext);
 
         JavascriptString * pStr;
@@ -541,12 +531,6 @@ namespace Js
         ScriptContext* scriptContext = function->GetScriptContext();
         Assert(!(callInfo.Flags & CallFlags_New));
 
-        // enforce 'this' arg generic
-        if (args.Info.Count == 0)
-        {
-            JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedRegExp, L"RegExp.prototype.test");
-        }
-
         JavascriptRegExp* pRegEx = GetJavascriptRegExp(args, L"RegExp.prototype.test", scriptContext);
         JavascriptString * pStr;
 
@@ -577,12 +561,6 @@ namespace Js
 
         Assert(!(callInfo.Flags & CallFlags_New));
 
-        // enforce 'this' arg generic
-        if (args.Info.Count == 0)
-        {
-            JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedRegExp, L"RegExp.prototype.toString");
-        }
-
         JavascriptRegExp* obj = GetJavascriptRegExp(args, L"RegExp.prototype.toString", scriptContext);
 
         return obj->ToString();
@@ -597,6 +575,77 @@ namespace Js
         return args[0];
     }
 
+    Var JavascriptRegExp::EntryGetterOptions(RecyclableObject* function, CallInfo callInfo, ...)
+    {
+        PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
+        ARGUMENTS(args, callInfo);
+        Assert(!(callInfo.Flags & CallFlags_New));
+
+        return GetJavascriptRegExp(args, L"RegExp.prototype.options", function->GetScriptContext())->GetOptions();
+    }
+
+    Var JavascriptRegExp::GetOptions()
+    {
+        Var options;
+
+        ScriptContext* scriptContext = this->GetLibrary()->GetScriptContext();
+        BEGIN_TEMP_ALLOCATOR(tempAlloc, scriptContext, L"JavascriptRegExp")
+        {
+            StringBuilder<ArenaAllocator> bs(tempAlloc, 4);
+
+            if(GetPattern()->IsGlobal())
+            {
+                bs.Append(L'g');
+            }
+            if(GetPattern()->IsIgnoreCase())
+            {
+                bs.Append(L'i');
+            }
+            if(GetPattern()->IsMultiline())
+            {
+                bs.Append(L'm');
+            }
+            if (GetPattern()->IsUnicode())
+            {
+                bs.Append(L'u');
+            }
+            if (GetPattern()->IsSticky())
+            {
+                bs.Append(L'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));
+
+        return GetJavascriptRegExp(args, L"RegExp.prototype.source", function->GetScriptContext())->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)); \
+        \
+        JavascriptRegExp* pRegEx = GetJavascriptRegExp(args, L"RegExp.prototype." L#propertyName, function->GetScriptContext()); \
+        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));
@@ -640,30 +689,30 @@ namespace Js
 
     BOOL JavascriptRegExp::HasProperty(PropertyId propertyId)
     {
+        const ScriptConfiguration* scriptConfig = this->GetScriptContext()->GetConfig();
+
+#define HAS_PROPERTY(ownProperty) \
+        return (ownProperty ? true : DynamicObject::HasProperty(propertyId));
+
         switch (propertyId)
         {
         case PropertyIds::lastIndex:
+            return true;
         case PropertyIds::global:
         case PropertyIds::multiline:
         case PropertyIds::ignoreCase:
         case PropertyIds::source:
         case PropertyIds::options:
-            return true;
+            HAS_PROPERTY(!scriptConfig->IsES6RegExPrototypePropertiesEnabled());
         case PropertyIds::unicode:
-            if (this->GetScriptContext()->GetConfig()->IsES6UnicodeExtensionsEnabled())
-            {
-                return true;
-            }
-            return DynamicObject::HasProperty(propertyId);
+            HAS_PROPERTY(scriptConfig->IsES6UnicodeExtensionsEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled())
         case PropertyIds::sticky:
-            if (this->GetScriptContext()->GetConfig()->IsES6RegExStickyEnabled())
-            {
-                return true;
-            }
-            return DynamicObject::HasProperty(propertyId);
+            HAS_PROPERTY(scriptConfig->IsES6RegExStickyEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled())
         default:
             return DynamicObject::HasProperty(propertyId);
         }
+
+#undef HAS_PROPERTY
     }
 
     BOOL JavascriptRegExp::GetPropertyReference(Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
@@ -698,6 +747,20 @@ namespace Js
 
     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:
@@ -710,21 +773,19 @@ namespace Js
             *result = true;
             return true;
         case PropertyIds::global:
-            *value = this->GetLibrary()->CreateBoolean(this->GetPattern()->IsGlobal());
-            *result = true;
-            return true;
+            GET_FLAG(IsGlobal)
         case PropertyIds::multiline:
-            *value = this->GetLibrary()->CreateBoolean(this->GetPattern()->IsMultiline());
-            *result = true;
-            return true;
+            GET_FLAG(IsMultiline)
         case PropertyIds::ignoreCase:
-            *value = this->GetLibrary()->CreateBoolean(this->GetPattern()->IsIgnoreCase());
-            *result = true;
-            return true;
+            GET_FLAG(IsIgnoreCase)
         case PropertyIds::unicode:
-            if (GetScriptContext()->GetConfig()->IsES6UnicodeExtensionsEnabled())
+            GET_FLAG(IsUnicode)
+        case PropertyIds::sticky:
+            GET_FLAG(IsSticky)
+        case PropertyIds::source:
+            if (!scriptConfig->IsES6RegExPrototypePropertiesEnabled())
             {
-                *value = this->GetLibrary()->CreateBoolean(this->GetPattern()->IsUnicode());
+                *value = this->ToString(true);
                 *result = true;
                 return true;
             }
@@ -732,10 +793,10 @@ namespace Js
             {
                 return false;
             }
-        case PropertyIds::sticky:
-            if (GetScriptContext()->GetConfig()->IsES6RegExStickyEnabled())
+        case PropertyIds::options:
+            if (!scriptConfig->IsES6RegExPrototypePropertiesEnabled())
             {
-                *value = this->GetLibrary()->CreateBoolean(this->GetPattern()->IsSticky());
+                *value = GetOptions();
                 *result = true;
                 return true;
             }
@@ -743,48 +804,11 @@ namespace Js
             {
                 return false;
             }
-        case PropertyIds::source:
-            {
-                *value = this->ToString(true);
-                *result = true;
-                return true;
-            }
-        case PropertyIds::options:
-        {
-            ScriptContext* scriptContext = this->GetLibrary()->GetScriptContext();
-            BEGIN_TEMP_ALLOCATOR(tempAlloc, scriptContext, L"JavascriptRegExp")
-            {
-                StringBuilder<ArenaAllocator> bs(tempAlloc, 4);
-
-                if(GetPattern()->IsGlobal())
-                {
-                    bs.Append(L'g');
-                }
-                if(GetPattern()->IsIgnoreCase())
-                {
-                    bs.Append(L'i');
-                }
-                if(GetPattern()->IsMultiline())
-                {
-                    bs.Append(L'm');
-                }
-                if (GetPattern()->IsUnicode())
-                {
-                    bs.Append(L'u');
-                }
-                if (GetPattern()->IsSticky())
-                {
-                    bs.Append(L'y');
-                }
-                *value = Js::JavascriptString::NewCopyBuffer(bs.Detach(), bs.Count(), scriptContext);
-            }
-            END_TEMP_ALLOCATOR(tempAlloc, scriptContext);
-            *result = true;
-            return true;
-        }
         default:
             return false;
         }
+
+#undef GET_FLAG
     }
 
     BOOL JavascriptRegExp::SetProperty(PropertyId propertyId, Var value, PropertyOperationFlags flags, PropertyValueInfo* info)
@@ -814,6 +838,17 @@ namespace Js
 
     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:
@@ -826,28 +861,16 @@ namespace Js
         case PropertyIds::ignoreCase:
         case PropertyIds::source:
         case PropertyIds::options:
-            JavascriptError::ThrowCantAssignIfStrictMode(flags, this->GetScriptContext());
-            *result = false;
-            return true;
+            SET_PROPERTY(!scriptConfig->IsES6RegExPrototypePropertiesEnabled());
         case PropertyIds::unicode:
-            if (this->GetScriptContext()->GetConfig()->IsES6UnicodeExtensionsEnabled())
-            {
-                JavascriptError::ThrowCantAssignIfStrictMode(flags, this->GetScriptContext());
-                *result = false;
-                return true;
-            }
-            return false;
+            SET_PROPERTY(scriptConfig->IsES6UnicodeExtensionsEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled());
         case PropertyIds::sticky:
-            if (this->GetScriptContext()->GetConfig()->IsES6RegExStickyEnabled())
-            {
-                JavascriptError::ThrowCantAssignIfStrictMode(flags, this->GetScriptContext());
-                *result = false;
-                return true;
-            }
-            return false;
+            SET_PROPERTY(scriptConfig->IsES6RegExStickyEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled());
         default:
             return false;
         }
+
+#undef SET_PROPERTY
     }
 
     BOOL JavascriptRegExp::InitProperty(PropertyId propertyId, Var value, PropertyOperationFlags flags, PropertyValueInfo* info)
@@ -857,33 +880,35 @@ namespace Js
 
     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:
-            JavascriptError::ThrowCantDeleteIfStrictMode(propertyOperationFlags, this->GetScriptContext(), this->GetScriptContext()->GetPropertyName(propertyId)->GetBuffer());
-            return false;
+            DELETE_PROPERTY(!scriptConfig->IsES6RegExPrototypePropertiesEnabled());
         case PropertyIds::unicode:
-            if (this->GetScriptContext()->GetConfig()->IsES6UnicodeExtensionsEnabled())
-            {
-                JavascriptError::ThrowCantDeleteIfStrictMode(propertyOperationFlags, this->GetScriptContext(), this->GetScriptContext()->GetPropertyName(propertyId)->GetBuffer());
-                return false;
-            }
-            return DynamicObject::DeleteProperty(propertyId, propertyOperationFlags);
+            DELETE_PROPERTY(scriptConfig->IsES6UnicodeExtensionsEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled());
         case PropertyIds::sticky:
-            if (this->GetScriptContext()->GetConfig()->IsES6RegExStickyEnabled())
-            {
-                JavascriptError::ThrowCantDeleteIfStrictMode(propertyOperationFlags, this->GetScriptContext(), this->GetScriptContext()->GetPropertyName(propertyId)->GetBuffer());
-                return false;
-            }
-            return DynamicObject::DeleteProperty(propertyId, propertyOperationFlags);
+            DELETE_PROPERTY(scriptConfig->IsES6RegExStickyEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled());
         default:
             return DynamicObject::DeleteProperty(propertyId, propertyOperationFlags);
         }
+
+#undef DELETE_PROPERTY
     }
 
     DescriptorFlags JavascriptRegExp::GetSetter(PropertyId propertyId, Var* setterValue, PropertyValueInfo* info, ScriptContext* requestContext)
@@ -913,36 +938,36 @@ namespace Js
 
     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:
-            PropertyValueInfo::SetNoCache(info, this);
-            *result = JavascriptRegExp::IsWritable(propertyId) ? WritableData : Data;
-            return true;
+            GET_SETTER(!scriptConfig->IsES6RegExPrototypePropertiesEnabled());
         case PropertyIds::unicode:
-            if (this->GetScriptContext()->GetConfig()->IsES6UnicodeExtensionsEnabled())
-            {
-                PropertyValueInfo::SetNoCache(info, this);
-                *result = JavascriptRegExp::IsWritable(propertyId) ? WritableData : Data;
-                return true;
-            }
-            return false;
+            GET_SETTER(scriptConfig->IsES6UnicodeExtensionsEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled());
         case PropertyIds::sticky:
-            if (this->GetScriptContext()->GetConfig()->IsES6RegExStickyEnabled())
-            {
-                PropertyValueInfo::SetNoCache(info, this);
-                *result = JavascriptRegExp::IsWritable(propertyId) ? WritableData : Data;
-                return true;
-            }
-            return false;
+            GET_SETTER(scriptConfig->IsES6RegExStickyEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled());
         default:
             return false;
         }
+
+#undef GET_SETTER
     }
 
     BOOL JavascriptRegExp::GetDiagValueString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
@@ -960,62 +985,67 @@ namespace Js
 
     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:
-            return false;
+            IS_ENUMERABLE(!scriptConfig->IsES6RegExPrototypePropertiesEnabled());
         case PropertyIds::unicode:
-            if (this->GetScriptContext()->GetConfig()->IsES6UnicodeExtensionsEnabled())
-            {
-                return false;
-            }
-            return DynamicObject::IsEnumerable(propertyId);
+            IS_ENUMERABLE(scriptConfig->IsES6UnicodeExtensionsEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled());
         case PropertyIds::sticky:
-            if (this->GetScriptContext()->GetConfig()->IsES6RegExStickyEnabled())
-            {
-                return false;
-            }
-            return DynamicObject::IsEnumerable(propertyId);
+            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:
-            return false;
+            IS_CONFIGURABLE(!scriptConfig->IsES6RegExPrototypePropertiesEnabled());
         case PropertyIds::unicode:
-            if (this->GetScriptContext()->GetConfig()->IsES6UnicodeExtensionsEnabled())
-            {
-                return false;
-            }
-            return DynamicObject::IsConfigurable(propertyId);
+            IS_CONFIGURABLE(scriptConfig->IsES6UnicodeExtensionsEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled());
         case PropertyIds::sticky:
-            if (this->GetScriptContext()->GetConfig()->IsES6RegExStickyEnabled())
-            {
-                return false;
-            }
-            return DynamicObject::IsConfigurable(propertyId);
+            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:
@@ -1025,22 +1055,16 @@ namespace Js
         case PropertyIds::ignoreCase:
         case PropertyIds::source:
         case PropertyIds::options:
-            return false;
+            IS_WRITABLE(!scriptConfig->IsES6RegExPrototypePropertiesEnabled())
         case PropertyIds::unicode:
-            if (this->GetScriptContext()->GetConfig()->IsES6UnicodeExtensionsEnabled())
-            {
-                return false;
-            }
-            return DynamicObject::IsWritable(propertyId);
+            IS_WRITABLE(scriptConfig->IsES6UnicodeExtensionsEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled());
         case PropertyIds::sticky:
-            if (this->GetScriptContext()->GetConfig()->IsES6RegExStickyEnabled())
-            {
-                return false;
-            }
-            return DynamicObject::IsWritable(propertyId);
+            IS_WRITABLE(scriptConfig->IsES6RegExStickyEnabled() && !scriptConfig->IsES6RegExPrototypePropertiesEnabled());
         default:
             return DynamicObject::IsWritable(propertyId);
         }
+
+#undef IS_WRITABLE
     }
     BOOL JavascriptRegExp::GetSpecialPropertyName(uint32 index, Var *propertyName, ScriptContext * requestContext)
     {
@@ -1057,6 +1081,11 @@ namespace Js
     // 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())

+ 16 - 0
lib/Runtime/Library/JavascriptRegularExpression.h

@@ -46,6 +46,8 @@ namespace Js
         bool GetSetterBuiltIns(PropertyId propertyId, PropertyValueInfo* info, DescriptorFlags* result);
         inline PropertyId const * GetSpecialPropertyIdsInlined() const;
 
+        Var GetOptions();
+
         inline void SetPattern(UnifiedRegex::RegexPattern* pattern);
         inline void SetSplitPattern(UnifiedRegex::RegexPattern* splitPattern);
 
@@ -107,6 +109,13 @@ namespace Js
             static FunctionInfo Test;
             static FunctionInfo ToString;
             static FunctionInfo GetterSymbolSpecies;
+            static FunctionInfo GetterGlobal;
+            static FunctionInfo GetterIgnoreCase;
+            static FunctionInfo GetterMultiline;
+            static FunctionInfo GetterOptions;
+            static FunctionInfo GetterSource;
+            static FunctionInfo GetterSticky;
+            static FunctionInfo GetterUnicode;
             // v5.8 only
             static FunctionInfo Compile;
         };
@@ -116,6 +125,13 @@ namespace Js
         static Var EntryTest(RecyclableObject* function, CallInfo callInfo, ...);
         static Var EntryToString(RecyclableObject* function, CallInfo callInfo, ...);
         static Var EntryGetterSymbolSpecies(RecyclableObject* function, CallInfo callInfo, ...);
+        static Var EntryGetterGlobal(RecyclableObject* function, CallInfo callInfo, ...);
+        static Var EntryGetterIgnoreCase(RecyclableObject* function, CallInfo callInfo, ...);
+        static Var EntryGetterMultiline(RecyclableObject* function, CallInfo callInfo, ...);
+        static Var EntryGetterOptions(RecyclableObject* function, CallInfo callInfo, ...);
+        static Var EntryGetterSource(RecyclableObject* function, CallInfo callInfo, ...);
+        static Var EntryGetterSticky(RecyclableObject* function, CallInfo callInfo, ...);
+        static Var EntryGetterUnicode(RecyclableObject* function, CallInfo callInfo, ...);
         // v5.8 only
         static Var EntryCompile(RecyclableObject* function, CallInfo callInfo, ...);
 

+ 2 - 0
lib/common/ConfigFlagsList.h

@@ -516,6 +516,7 @@ PHASE(All)
 #define DEFAULT_CONFIG_ES6Unscopables          (true)
 #define DEFAULT_CONFIG_ES6WeakSet              (true)
 #define DEFAULT_CONFIG_ES6RegExSticky          (true)
+#define DEFAULT_CONFIG_ES6RegExPrototypeProperties (false)
 #define DEFAULT_CONFIG_ES6HasInstanceOf        (false)
 #define DEFAULT_CONFIG_ArrayBufferTransfer     (false)
 #define DEFAULT_CONFIG_ES7ExponentionOperator  (false)
@@ -912,6 +913,7 @@ FLAGPR           (Boolean, ES6, ES6UnicodeVerbose      , "Enable ES6 Unicode 6.0
 FLAGPR           (Boolean, ES6, ES6Unscopables         , "Enable ES6 With Statement Unscopables"                    , DEFAULT_CONFIG_ES6Unscopables)
 FLAGPR           (Boolean, ES6, ES6WeakSet             , "Enable ES6 WeakSet"                                       , DEFAULT_CONFIG_ES6WeakSet)
 FLAGPR           (Boolean, ES6, ES6RegExSticky         , "Enable ES6 RegEx sticky flag"                             , DEFAULT_CONFIG_ES6RegExSticky)
+FLAGPR_REGOVR_EXP(Boolean, ES6, ES6RegExPrototypeProperties, "Enable ES6 properties on the RegEx prototype"         , DEFAULT_CONFIG_ES6RegExPrototypeProperties)
 FLAGPR           (Boolean, ES6, ES6HasInstance         , "Enable ES6 @@hasInstance symbol"                          , DEFAULT_CONFIG_ES6HasInstanceOf)
 FLAGPR           (Boolean, ES6, ES6Verbose             , "Enable ES6 verbose trace"                                 , DEFAULT_CONFIG_ES6Verbose)
 FLAGPR_REGOVR_EXP(Boolean, ES6, ArrayBufferTransfer    , "Enable ArrayBuffer.transfer"                              , DEFAULT_CONFIG_ArrayBufferTransfer)

+ 1 - 0
test/Operators/rlexe.xml

@@ -199,6 +199,7 @@
     <default>
       <files>prototypeInheritance2.js</files>
       <baseline>prototypeInheritance2.baseline</baseline>
+      <compile-flags>-ES6RegExPrototypeProperties-</compile-flags>
     </default>
   </test>
   <test>

+ 2 - 0
test/Regex/rlexe.xml

@@ -34,6 +34,7 @@
     <default>
       <files>configurableTest.js</files>
       <baseline>configurableTest.baseline</baseline>
+      <compile-flags>-ES6RegExPrototypeProperties-</compile-flags>
     </default>
   </test>
   <test>
@@ -138,6 +139,7 @@
     <default>
       <files>properties.js</files>
       <baseline>properties.baseline</baseline>
+      <compile-flags>-ES6RegExPrototypeProperties-</compile-flags>
     </default>
   </test>
   <test>

+ 2 - 0
test/es6/es6_all.baseline

@@ -79,6 +79,8 @@ FLAG ES6 = 1 - setting child flag ES6WeakSet = 1
 FLAG ES6WeakSet = 1
 FLAG ES6 = 1 - setting child flag ES6RegExSticky = 1
 FLAG ES6RegExSticky = 1
+FLAG ES6 = 1 - setting child flag ES6RegExPrototypeProperties = 1
+FLAG ES6RegExPrototypeProperties = 1
 FLAG ES6 = 1 - setting child flag ES6HasInstance = 1
 FLAG ES6HasInstance = 1
 FLAG ES6 = 1 - setting child flag ArrayBufferTransfer = 1

+ 2 - 0
test/es6/es6_stable.baseline

@@ -79,6 +79,8 @@ FLAG ES6 = 1 - setting child flag ES6WeakSet = 1
 FLAG ES6WeakSet = 1
 FLAG ES6 = 1 - setting child flag ES6RegExSticky = 1
 FLAG ES6RegExSticky = 1
+FLAG ES6 = 1 - setting child flag ES6RegExPrototypeProperties = 0
+FLAG ES6RegExPrototypeProperties = 0
 FLAG ES6 = 1 - setting child flag ES6HasInstance = 0
 FLAG ES6HasInstance = 0
 FLAG ES6 = 1 - setting child flag ES6Verbose = 0

+ 4 - 0
test/es6/es6_stable.enable_disable.baseline

@@ -79,6 +79,8 @@ FLAG ES6 = 1 - setting child flag ES6WeakSet = 1
 FLAG ES6WeakSet = 1
 FLAG ES6 = 1 - setting child flag ES6RegExSticky = 1
 FLAG ES6RegExSticky = 1
+FLAG ES6 = 1 - setting child flag ES6RegExPrototypeProperties = 0
+FLAG ES6RegExPrototypeProperties = 0
 FLAG ES6 = 1 - setting child flag ES6HasInstance = 0
 FLAG ES6HasInstance = 0
 FLAG ES6 = 1 - setting child flag ES6Verbose = 0
@@ -166,6 +168,8 @@ FLAG ES6 = 0 - setting child flag ES6WeakSet = 0
 FLAG ES6WeakSet = 0
 FLAG ES6 = 0 - setting child flag ES6RegExSticky = 0
 FLAG ES6RegExSticky = 0
+FLAG ES6 = 0 - setting child flag ES6RegExPrototypeProperties = 0
+FLAG ES6RegExPrototypeProperties = 0
 FLAG ES6 = 0 - setting child flag ES6HasInstance = 0
 FLAG ES6HasInstance = 0
 FLAG ES6 = 0 - setting child flag ES6Verbose = 0

+ 80 - 0
test/es6/regex-prototype-properties.js

@@ -0,0 +1,80 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
+
+function flattenArray(array) {
+    return Array.prototype.concat.apply([], array);
+}
+
+var propertyNames = [
+    "global",
+    "ignoreCase",
+    "multiline",
+    "options",
+    "source",
+    "sticky",
+    "unicode",
+];
+
+var tests = flattenArray(propertyNames.map(function (name) {
+    // Values returned by the properties are tested in other files since they
+    // are independent of the config flag and work regardless of where the
+    // properties are.
+    return [
+        {
+            name: name + " exists on the prototype",
+            body: function () {
+                var descriptor = Object.getOwnPropertyDescriptor(RegExp.prototype, name);
+                assert.isNotUndefined(descriptor, "descriptor");
+
+                assert.isFalse(descriptor.enumerable, name + " is not enumerable");
+                assert.isTrue(descriptor.configurable, name + " is configurable");
+                assert.isUndefined(descriptor.value, name + " does not have a value");
+                assert.isUndefined(descriptor.set, name + " does not have a setter");
+
+                var getter = descriptor.get;
+                assert.isNotUndefined(getter, name + " has a getter");
+                assert.areEqual('function', typeof getter, "Getter for " + name + " is a function");
+                assert.areEqual("get " + name, descriptor.get.name, "Getter for " + name + " has the correct name");
+                assert.areEqual(0, descriptor.get.length, "Getter for " + name + " has the correct length");
+            }
+        },
+        {
+            name: name + " does not exist on the instance",
+            body: function () {
+                var descriptor = Object.getOwnPropertyDescriptor(/./, name);
+                assert.isUndefined(descriptor, name);
+            }
+        },
+        {
+            name: name + " getter can be called by RegExp subclasses",
+            body: function () {
+                class Subclass extends RegExp {}
+                var re = new Subclass();
+                assert.doesNotThrow(function () { re[name] });
+            }
+        },
+        {
+            name: name + " getter can not be called by non-RegExp objects",
+            body: function () {
+                var o = Object.create(/./);
+                var getter = Object.getOwnPropertyDescriptor(RegExp.prototype, name).get;
+                assert.throws(getter.bind(o));
+            }
+        },
+        {
+            name: name + " should be deletable",
+            body: function () {
+                var descriptor = Object.getOwnPropertyDescriptor(RegExp.prototype, name);
+                delete RegExp.prototype[name];
+                assert.isFalse(name in RegExp.prototype);
+                Object.defineProperty(RegExp.prototype, name, descriptor);
+            }
+        }
+    ];
+}));
+
+testRunner.runTests(tests, { verbose: WScript.Arguments[0] != 'summary' });

+ 6 - 0
test/es6/rlexe.xml

@@ -902,6 +902,12 @@
       <compile-flags>-args summary -endargs</compile-flags>
     </default>
   </test>
+  <test>
+    <default>
+      <files>regex-prototype-properties.js</files>
+      <compile-flags>-ES6RegExPrototypeProperties -args summary -endargs</compile-flags>
+    </default>
+  </test>
   <test>
     <default>
       <files>regexflags.js</files>

+ 1 - 0
test/strict/rlexe.xml

@@ -441,6 +441,7 @@
     <default>
       <files>13.delete.js</files>
       <baseline>13.delete.baseline</baseline>
+      <compile-flags>-ES6RegExPrototypeProperties-</compile-flags>
       <!-- TODO: Delete depends on flag on scriptContext. Add a new opcode for delete 13.delete_sm.js -->
     </default>
   </test>