Browse Source

Fixes #254. Implements Object.getOwnPropertyDescriptors and includes -ESObjectGetOwnPropertyDescriptors to toggle its availability.

Jordon Wing 9 năm trước cách đây
mục cha
commit
2aa2c1f290

+ 4 - 1
lib/Common/ConfigFlagsList.h

@@ -575,7 +575,7 @@ PHASE(All)
 #define DEFAULT_CONFIG_ES7ExponentionOperator  (true)
 #define DEFAULT_CONFIG_ES7TrailingComma        (true)
 #define DEFAULT_CONFIG_ES7ValuesEntries        (true)
-
+#define DEFAULT_CONFIG_ESObjectGetOwnPropertyDescriptors (true)
 #define DEFAULT_CONFIG_ES6Verbose              (false)
 #define DEFAULT_CONFIG_ES6All                  (false)
 // ES6 DEFAULT BEHAVIOR
@@ -1012,6 +1012,9 @@ FLAGPR           (Boolean, ES6, ES6Verbose             , "Enable ES6 verbose tra
     #define COMPILE_DISABLE_ArrayBufferTransfer 0
 #endif
 FLAGPR_REGOVR_EXP(Boolean, ES6, ArrayBufferTransfer    , "Enable ArrayBuffer.transfer"                              , DEFAULT_CONFIG_ArrayBufferTransfer)
+
+FLAGPR           (Boolean, ES6, ESObjectGetOwnPropertyDescriptors, "Enable Object.getOwnPropertyDescriptors"              , DEFAULT_CONFIG_ESObjectGetOwnPropertyDescriptors)
+
 // /ES6 (BLUE+1) features/flags
 
 #ifdef ENABLE_PROJECTION

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

@@ -48,6 +48,7 @@ FLAG_RELEASE(IsES6HasInstanceEnabled, ES6HasInstance)
 FLAG_RELEASE(SkipSplitOnNoResult, SkipSplitOnNoResult)
 FLAG_RELEASE(IsES7AsyncAndAwaitEnabled, ES7AsyncAwait)
 FLAG_RELEASE(IsArrayBufferTransferEnabled, ArrayBufferTransfer)
+FLAG_RELEASE(IsESObjectGetOwnPropertyDescriptorsEnabled, ESObjectGetOwnPropertyDescriptors)
 #ifdef ENABLE_PROJECTION
 FLAG(AreWinRTDelegatesInterfaces, WinRTDelegateInterfaces)
 FLAG_RELEASE(IsWinRTAdaptiveAppsEnabled, WinRTAdaptiveApps)

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

@@ -169,6 +169,7 @@ BUILTIN(JavascriptObject, DefineProperty, EntryDefineProperty, FunctionInfo::Err
 BUILTIN(JavascriptObject, DefineProperties, EntryDefineProperties, FunctionInfo::ErrorOnNew)
 BUILTIN(JavascriptObject, Create, EntryCreate, FunctionInfo::ErrorOnNew)
 BUILTIN(JavascriptObject, GetOwnPropertyDescriptor, EntryGetOwnPropertyDescriptor, FunctionInfo::ErrorOnNew)
+BUILTIN(JavascriptObject, GetOwnPropertyDescriptors, EntryGetOwnPropertyDescriptors, FunctionInfo::ErrorOnNew)
 BUILTIN(JavascriptObject, GetPrototypeOf, EntryGetPrototypeOf, FunctionInfo::ErrorOnNew)
 BUILTIN(JavascriptObject, SetPrototypeOf, EntrySetPrototypeOf, FunctionInfo::ErrorOnNew)
 BUILTIN(JavascriptObject, Keys, EntryKeys, FunctionInfo::ErrorOnNew)

+ 14 - 0
lib/Runtime/Library/JavascriptLibrary.cpp

@@ -3781,6 +3781,11 @@ namespace Js
             propertyCount += 2;
         }
 
+        if (scriptContext->GetConfig()->IsESObjectGetOwnPropertyDescriptorsEnabled())
+        {
+            propertyCount++;
+        }
+
         typeHandler->Convert(objectConstructor, mode, propertyCount);
 
         library->AddMember(objectConstructor, PropertyIds::length, TaggedInt::ToVarUnchecked(1), PropertyNone);
@@ -3794,6 +3799,11 @@ namespace Js
             library->AddFunctionToLibraryObject(objectConstructor, PropertyIds::defineProperty, &JavascriptObject::EntryInfo::DefineProperty, 3));
         scriptContext->SetBuiltInLibraryFunction(JavascriptObject::EntryInfo::GetOwnPropertyDescriptor.GetOriginalEntryPoint(),
             library->AddFunctionToLibraryObject(objectConstructor, PropertyIds::getOwnPropertyDescriptor, &JavascriptObject::EntryInfo::GetOwnPropertyDescriptor, 2));
+        if (scriptContext->GetConfig()->IsESObjectGetOwnPropertyDescriptorsEnabled())
+        {
+            scriptContext->SetBuiltInLibraryFunction(JavascriptObject::EntryInfo::GetOwnPropertyDescriptors.GetOriginalEntryPoint(),
+                library->AddFunctionToLibraryObject(objectConstructor, PropertyIds::getOwnPropertyDescriptors, &JavascriptObject::EntryInfo::GetOwnPropertyDescriptors, 1));
+        }
         scriptContext->SetBuiltInLibraryFunction(JavascriptObject::EntryInfo::DefineProperties.GetOriginalEntryPoint(),
             library->AddFunctionToLibraryObject(objectConstructor, PropertyIds::defineProperties, &JavascriptObject::EntryInfo::DefineProperties, 2));
         library->AddFunctionToLibraryObject(objectConstructor, PropertyIds::create, &JavascriptObject::EntryInfo::Create, 2);
@@ -6810,6 +6820,10 @@ namespace Js
 
         REG_OBJECTS_LIB_FUNC(defineProperty, JavascriptObject::EntryDefineProperty);
         REG_OBJECTS_LIB_FUNC(getOwnPropertyDescriptor, JavascriptObject::EntryGetOwnPropertyDescriptor);
+        if (scriptContext->GetConfig()->IsESObjectGetOwnPropertyDescriptorsEnabled())
+        {
+            REG_OBJECTS_LIB_FUNC(getOwnPropertyDescriptors, JavascriptObject::EntryGetOwnPropertyDescriptors);
+        }
 
         REG_OBJECTS_LIB_FUNC(defineProperties, JavascriptObject::EntryDefineProperties);
         REG_OBJECTS_LIB_FUNC(create, JavascriptObject::EntryCreate);

+ 67 - 0
lib/Runtime/Library/JavascriptObject.cpp

@@ -837,6 +837,73 @@ namespace Js
         return isPropertyDescriptorDefined;
     }
 
+    Var JavascriptObject::EntryGetOwnPropertyDescriptors(RecyclableObject* function, CallInfo callInfo, ...)
+    {
+        PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
+
+        ARGUMENTS(args, callInfo);
+        ScriptContext* scriptContext = function->GetScriptContext();
+
+        Assert(!(callInfo.Flags & CallFlags_New));
+
+        RecyclableObject* obj = nullptr;
+
+        if (args.Info.Count < 2)
+        {
+            obj = RecyclableObject::FromVar(JavascriptOperators::ToObject(scriptContext->GetLibrary()->GetUndefined(), scriptContext));
+        }
+        else
+        {
+            // Convert the argument to object first
+            obj = RecyclableObject::FromVar(JavascriptOperators::ToObject(args[1], scriptContext));
+        }
+
+        // If the object is HostDispatch try to invoke the operation remotely
+        if (obj->GetTypeId() == TypeIds_HostDispatch)
+        {
+            Var result;
+            if (obj->InvokeBuiltInOperationRemotely(EntryGetOwnPropertyDescriptors, args, &result))
+            {
+                return result;
+            }
+        }
+
+        Var ownPropertyKeys = JavascriptOperators::GetOwnPropertyKeys(obj, scriptContext);
+        Assert(JavascriptArray::Is(ownPropertyKeys));
+
+        if (!JavascriptArray::Is(ownPropertyKeys))
+        {
+            ownPropertyKeys = scriptContext->GetLibrary()->CreateArray(0);
+        }
+
+
+        JavascriptArray *ownPropsArray = JavascriptArray::FromVar(ownPropertyKeys);
+
+        RecyclableObject* resultObj = scriptContext->GetLibrary()->CreateObject(true, (Js::PropertyIndex) ownPropsArray->GetLength());
+        
+        PropertyDescriptor propDesc;
+        Var propKey = nullptr;
+
+        for (uint i = 0; i < ownPropsArray->GetLength(); i++)
+        {
+            BOOL getPropResult = ownPropsArray->DirectGetItemAt(i, &propKey);
+            Assert(getPropResult);
+
+            if (!getPropResult)
+            {
+                continue;
+            }
+            
+            PropertyRecord const * propertyRecord;
+            JavascriptConversion::ToPropertyKey(propKey, scriptContext, &propertyRecord);
+
+            Var newDescriptor = JavascriptObject::GetOwnPropertyDescriptorHelper(obj, propKey, scriptContext);
+            resultObj->SetProperty(propertyRecord->GetPropertyId(), newDescriptor, PropertyOperation_None, nullptr);
+        }
+
+        return resultObj;
+    }
+
     Var JavascriptObject::EntryGetPrototypeOf(RecyclableObject* function, CallInfo callInfo, ...)
     {
         PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

+ 2 - 0
lib/Runtime/Library/JavascriptObject.h

@@ -30,6 +30,7 @@ namespace Js
             static FunctionInfo DefineProperties;
             static FunctionInfo Create;
             static FunctionInfo GetOwnPropertyDescriptor;
+            static FunctionInfo GetOwnPropertyDescriptors;
             static FunctionInfo GetPrototypeOf;
             static FunctionInfo SetPrototypeOf;
             static FunctionInfo Keys;
@@ -62,6 +63,7 @@ namespace Js
         static Var EntryDefineProperties(RecyclableObject* function, CallInfo callInfo, ...);
         static Var EntryCreate(RecyclableObject* function, CallInfo callInfo, ...);
         static Var EntryGetOwnPropertyDescriptor(RecyclableObject* function, CallInfo callInfo, ...);
+        static Var EntryGetOwnPropertyDescriptors(RecyclableObject* function, CallInfo callInfo, ...);
         static Var EntryGetPrototypeOf(RecyclableObject* function, CallInfo callInfo, ...);
         static Var EntrySetPrototypeOf(RecyclableObject* function, CallInfo callInfo, ...);
         static Var EntryKeys(RecyclableObject* function, CallInfo callInfo, ...);

+ 159 - 0
test/Object/getOwnPropertyDescriptors.js

@@ -0,0 +1,159 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+if (this.WScript && this.WScript.LoadScriptFile) { // Check for running in ch
+    this.WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
+}
+
+function verifyObjectDescriptors(descriptors, allTruePropName, allFalsePropName) {
+    var allProperties = Object.getOwnPropertyNames(descriptors).concat(Object.getOwnPropertySymbols(descriptors));
+
+    assert.areEqual([allTruePropName, allFalsePropName], allProperties, "Result should have one descriptor for each own property");
+
+    assert.isTrue(descriptors.hasOwnProperty(allTruePropName), "Result should contain all own properties");
+    assert.isTrue(descriptors.hasOwnProperty(allFalsePropName), "Result should contain all own properties");
+    assert.areEqual(descriptors[allTruePropName].value, "fooAllTrue", "Result value attribute should match the value set by defineProperties");
+    assert.areEqual(descriptors[allFalsePropName].value, "fooAllFalse", "Result value attribute should match the value set by defineProperties");
+
+    var expectedProps = ['configurable', 'writable', 'enumerable'];
+    for (var i in expectedProps) {
+        assert.isTrue(descriptors[allTruePropName][expectedProps[i]], "Result value attribute should match the value set by defineProperties");
+        assert.isFalse(descriptors[allFalsePropName][expectedProps[i]], "Result value attribute should match the value set by defineProperties");
+    }
+}
+
+var tests = [
+    {
+        name: "Object has getOwnPropertyDescriptors method",
+            body: function() {
+                assert.isTrue(Object.hasOwnProperty("getOwnPropertyDescriptors"), 'Object should have getOwnPropertyDescriptors method');
+
+                assert.isFalse(Object.hasOwnProperty({}, "getOwnPropertyDescriptors"), 'New objects should have a property getOwnPropertyDescriptors');
+                assert.isUndefined(Object.getOwnPropertyDescriptor({}, "getOwnPropertyDescriptors"), 'Object.getOwnPropertyDescriptor({}, "getOwnPropertyDescriptors") should be undefined');
+
+                for (var p in {}) {
+                    assert.isTrue(p != "getOwnPropertyDescriptors", "getOwnPropertyDescriptors should not be enumerable on new objects");
+                }
+
+                assert.areEqual(1, Object.getOwnPropertyDescriptors.length, "Object.getOwnPropertyDescriptors requires exactly one parameter.");
+            }
+    },
+    {
+        name: "Correctly handles bad parameters.",
+        body: function() {
+            assert.throws(function() {
+                Object.getOwnPropertyDescriptors();
+            }, TypeError, "Missing first parameter should cause a TypeError.", "Object expected");
+
+            assert.throws(function() {
+                Object.getOwnPropertyDescriptors(null);
+            }, TypeError, "Null first parameter should cause a TypeError", "Object expected");
+        }
+    },
+    {
+        name: "The resulting get and set are identical with the original get and set.",
+        body: function() {
+            // This test is adapted from https://github.com/tc39/proposal-object-getownpropertydescriptors/blob/master/test/built-ins/Object/getOwnPropertyDescriptors/has-accessors.js
+            var a = {
+                get a() {},
+                set a(value) {}
+            };
+            var b = Object.getOwnPropertyDescriptors(a);
+
+            assert.isTrue(b.a.get === Object.getOwnPropertyDescriptor(a, 'a').get);
+            assert.isTrue(b.a.set === Object.getOwnPropertyDescriptor(a, 'a').set);
+        }
+    },
+    {
+        name: "For properties with string names, the list of property descriptors includes all own properties with correct descriptors",
+        body: function() {
+            var foo = {}
+
+            Object.defineProperties(foo, {
+                "fooAllTrue": {
+                    configurable: true,
+                    enumerable: true,
+                    value: "fooAllTrue",
+                    writable: true
+                },
+                "fooAllFalse": {
+                    configurable: false,
+                    enumerable: false,
+                    value: "fooAllFalse",
+                    writable: false
+                }
+            });
+
+            var desc = Object.getOwnPropertyDescriptors(foo);
+            assert.isTrue(desc instanceof Object, "Result must be an object");
+
+            verifyObjectDescriptors(desc, "fooAllTrue", "fooAllFalse");
+        }
+    },
+    {
+        name: "For properties with number names, the list of property descriptors includes all own properties with correct descriptors",
+        body: function() {
+            var foo = {}
+
+            var allTrueNum = 1234;
+            var allFalseNum = 5678;
+
+            Object.defineProperties(foo, {
+                [allTrueNum]: {
+                    configurable: true,
+                    enumerable: true,
+                    value: "fooAllTrue",
+                    writable: true
+                },
+                [allFalseNum]: {
+                    configurable: false,
+                    enumerable: false,
+                    value: "fooAllFalse",
+                    writable: false
+                }
+            });
+
+            var desc = Object.getOwnPropertyDescriptors(foo);
+            assert.isTrue(desc instanceof Object, "Result must be an object");
+
+            verifyObjectDescriptors(desc, allTrueNum.toString(), allFalseNum.toString());
+
+            // Also verify that the properties are accessible as numbers
+            assert.areEqual(desc[allTrueNum].value, "fooAllTrue", "For properties with number names, resulting properties should be accessible with numeric names.")
+            assert.areEqual(desc[allFalseNum].value, "fooAllFalse", "For properties with number names, resulting properties should be accessible with numeric names.")
+        }
+    },
+    {
+        name: "For properties with symbol names, the list of property descriptors includes all own properties with correct descriptors",
+        body: function() {
+            var foo = {}
+
+            var allTrueSymbol = Symbol("allTrue");
+            var allFalseSymbol = Symbol("allFalse");
+
+            Object.defineProperties(foo, {
+                [allTrueSymbol]: {
+                    configurable: true,
+                    enumerable: true,
+                    value: "fooAllTrue",
+                    writable: true
+                },
+                [allFalseSymbol]: {
+                    configurable: false,
+                    enumerable: false,
+                    value: "fooAllFalse",
+                    writable: false
+                }
+            });
+
+            var desc = Object.getOwnPropertyDescriptors(foo);
+            assert.isTrue(desc instanceof Object, "Result must be an object");
+
+            verifyObjectDescriptors(desc, allTrueSymbol, allFalseSymbol);
+        }
+    }
+]
+
+testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });

+ 6 - 0
test/Object/rlexe.xml

@@ -200,6 +200,12 @@
       <baseline>getOwnPropertyDescriptor_v3.baseline</baseline>
     </default>
   </test>
+  <test>
+    <default>
+      <files>getOwnPropertyDescriptors.js</files>
+      <compile-flags>-args summary -endargs -ESObjectGetOwnPropertyDescriptors</compile-flags>
+    </default>
+  </test>
   <test>
     <default>
       <files>objectCreationOptimizations.js</files>

+ 2 - 0
test/es6/es6_all.baseline

@@ -81,5 +81,7 @@ FLAG ES6 = 1 - setting child flag ES6HasInstance = 1
 FLAG ES6HasInstance = 1
 FLAG ES6 = 1 - setting child flag ArrayBufferTransfer = 1
 FLAG ArrayBufferTransfer = 1
+FLAG ES6 = 1 - setting child flag ESObjectGetOwnPropertyDescriptors = 1
+FLAG ESObjectGetOwnPropertyDescriptors = 1
 FLAG WERExceptionSupport = 1
 default argument

+ 2 - 0
test/es6/es6_stable.baseline

@@ -83,6 +83,8 @@ FLAG ES6 = 1 - setting child flag ES6Verbose = 0
 FLAG ES6Verbose = 0
 FLAG ES6 = 1 - setting child flag ArrayBufferTransfer = 0
 FLAG ArrayBufferTransfer = 0
+FLAG ES6 = 1 - setting child flag ESObjectGetOwnPropertyDescriptors = 1
+FLAG ESObjectGetOwnPropertyDescriptors = 1
 FLAG ES6DefaultArgs = 1
 FLAG WERExceptionSupport = 1
 default argument

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

@@ -83,6 +83,8 @@ FLAG ES6 = 1 - setting child flag ES6Verbose = 0
 FLAG ES6Verbose = 0
 FLAG ES6 = 1 - setting child flag ArrayBufferTransfer = 0
 FLAG ArrayBufferTransfer = 0
+FLAG ES6 = 1 - setting child flag ESObjectGetOwnPropertyDescriptors = 1
+FLAG ESObjectGetOwnPropertyDescriptors = 1
 FLAG ES6 = 0
 FLAG ES6 = 0 - setting child flag Simdjs = 0
 FLAG Simdjs = 0
@@ -168,6 +170,8 @@ FLAG ES6 = 0 - setting child flag ES6Verbose = 0
 FLAG ES6Verbose = 0
 FLAG ES6 = 0 - setting child flag ArrayBufferTransfer = 0
 FLAG ArrayBufferTransfer = 0
+FLAG ES6 = 0 - setting child flag ESObjectGetOwnPropertyDescriptors = 0
+FLAG ESObjectGetOwnPropertyDescriptors = 0
 FLAG ES6DefaultArgs = 1
 FLAG WERExceptionSupport = 1
 default argument