Parcourir la source

Rewritten and expanded tests for seal/freeze.
Fixed sealed typed arrays to report themselves frozen only if empty.

Irina Yatsenko il y a 7 ans
Parent
commit
66ddb87b38

+ 9 - 0
lib/Runtime/Library/TypedArray.cpp

@@ -1114,6 +1114,15 @@ namespace Js
         return SetItem(index, value);
     }
 
+    BOOL TypedArrayBase::IsObjectArrayFrozen()
+    {
+        if (GetLength() > 0)
+        {
+            return false; // the backing buffer is always modifiable
+        }
+        return IsSealed();
+    }
+
     BOOL TypedArrayBase::Is(Var aValue)
     {
         TypeId typeId = JavascriptOperators::GetTypeId(aValue);

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

@@ -179,6 +179,7 @@ namespace Js
 
         // objectArray support
         virtual BOOL SetItemWithAttributes(uint32 index, Var value, PropertyAttributes attributes) override;
+        virtual BOOL IsObjectArrayFrozen() override;
 
         Var FindMinOrMax(Js::ScriptContext * scriptContext, TypeId typeId, bool findMax);
         template<typename T, bool checkNaNAndNegZero> Var FindMinOrMax(Js::ScriptContext * scriptContext, bool findMax);

+ 9 - 0
lib/Runtime/Types/SimpleDictionaryTypeHandler.cpp

@@ -2288,6 +2288,15 @@ namespace Js
             return false;
         }
 
+        if (DynamicObject::IsAnyTypedArray(instance))
+        {
+            auto typedArray = static_cast<TypedArrayBase*>(instance);
+            if (!typedArray->IsObjectArrayFrozen())
+            {
+                return false;
+            }
+        }
+
         // Since we've determined that the object was frozen, set the flag to avoid further checks into all properties
         // (once frozen there is no way to go back to un-frozen).
         this->SetFlags(IsSealedOnceFlag | IsFrozenOnceFlag);

+ 0 - 14
test/es5/freeze.baseline

@@ -1,14 +0,0 @@
-TestCase1
-1
-false
-TestCase2 - freeze & add a property
-x,y
-true
-TestCase3 - freeze & delete a property
-x,y
-true
-20
-TestCase4 - freeze & modify a property
-x,y
-true
-20

+ 110 - 40
test/es5/freeze.js

@@ -3,47 +3,117 @@
 // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
 //-------------------------------------------------------------------------------------------------------
 
-function write(args)
-{
- WScript.Echo(args);
+if (this.WScript && this.WScript.LoadScriptFile) { // Check for running in ch
+  this.WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
 }
 
-write("TestCase1");
-write(Object.freeze.length);
-write(Object.isFrozen({}));
-
-write("TestCase2 - freeze & add a property");
-var a = {x:20, y:30};
-Object.freeze(a);
-SafeCall(function() { a.z = 50; });
-write(Object.getOwnPropertyNames(a));
-write(Object.isFrozen(a));
-
-write("TestCase3 - freeze & delete a property");
-var a = {x:20, y:30};
-Object.freeze(a);
-SafeCall(function() { delete a.x; });
-write(Object.getOwnPropertyNames(a));
-write(Object.isFrozen(a));
-write(a.x);
-
-write("TestCase4 - freeze & modify a property");
-var a = {x:20, y:30};
-Object.freeze(a);
-SafeCall(function() { a.x = 40; });
-SafeCall(function() { a.y = 60; });
-write(Object.getOwnPropertyNames(a));
-write(Object.isFrozen(a));
-write(a.x);
-
-function SafeCall(f)
-{
-  try
+var tests = [
   {
-    f();
-  }
-  catch (e)
+      name: "Add, delete, modify properties after freezing",
+      body: function () {
+          let a = {x: 42};
+          
+          Object.freeze(a);
+          assert.isFalse(Object.isExtensible(a));
+          assert.isTrue(Object.isSealed(a));
+          assert.isTrue(Object.isFrozen(a));
+
+          // cannot add new properties
+          a.y = 17;
+          assert.isFalse(a.hasOwnProperty('y'));
+          assert.throws(function () { 'use strict'; a.y = 17; }, TypeError, 
+            "Should throw on creating a new property in frozen object in strict mode", 
+            "Cannot create property for a non-extensible object");
+
+          // cannot delete properties
+          assert.isFalse(delete a.x);
+          assert.isTrue(a.hasOwnProperty('x'));
+          assert.throws(function () { 'use strict'; delete a.x; }, TypeError, 
+            "Should throw on creating a new property in frozen object in strict mode", 
+            "Calling delete on 'x' is not allowed in strict mode");
+
+          // cannot change prototype
+          let b = {};
+          assert.throws(function () { 'use strict'; Object.setPrototypeOf(a, b); }, TypeError, 
+            "Should throw on creating a new property in sealed object in strict mode", 
+            "Cannot create property for a non-extensible object");
+
+          // existing properties should be set to non-writable and non-configurable
+          let descr = Object.getOwnPropertyDescriptor(a, 'x');
+          assert.isFalse(descr.configurable);
+          assert.isFalse(descr.writable);
+      }
+  },
+  {
+    name: "Add, delete, modify indexed elements of an array after freezing",
+    body: function () {
+        let a = [42];
+        a[2] = 43;
+        
+        Object.freeze(a);
+        assert.isFalse(Object.isExtensible(a));
+        assert.isTrue(Object.isSealed(a));
+        assert.isTrue(Object.isFrozen(a));
+
+        // the array cannot be extended
+        a[3] = 17;
+        assert.areEqual(3, a.length);
+        assert.isFalse(a.hasOwnProperty('3'))
+        assert.throws(function () { 'use strict'; a[3] = 17; }, TypeError, 
+          "Should throw on creating a new property in frozen object in strict mode", 
+          "Cannot create property for a non-extensible object");
+
+        // a hole cannot be filled
+        a[1] = 17;
+        assert.areEqual(3, a.length);
+        assert.isFalse(a.hasOwnProperty('1'))
+        assert.throws(function () { 'use strict'; a[1] = 17; }, TypeError, 
+          "Should throw on creating a new property in frozen object in strict mode", 
+          "Cannot create property for a non-extensible object");
+
+        // existing elements cannot be deleted
+        assert.isFalse(delete a[0]);
+        assert.isTrue(a.hasOwnProperty('0'));
+        assert.throws(function () { 'use strict'; delete a[0]; }, TypeError, 
+          "Should throw on creating a new property in frozen object in strict mode", 
+          "Calling delete on '0' is not allowed in strict mode");
+
+        // existing elements cannot be modified
+        let descr = Object.getOwnPropertyDescriptor(a, '0');
+        assert.isFalse(descr.configurable);
+        assert.isFalse(descr.writable);
+        a[0] = 17;
+        assert.areEqual(42, a[0]);
+        assert.throws(function () { 'use strict'; a[0] = 17; }, TypeError, 
+          "Should throw on creating a new property in frozen object in strict mode", 
+          "Assignment to read-only properties is not allowed in strict mode");
+
+        // the special 'length' property also cannot be modified
+        let descr_len = Object.getOwnPropertyDescriptor(a, 'length');
+        assert.isFalse(descr_len.configurable);
+        assert.isFalse(descr_len.writable);
+    }
+  },
   {
-    write("Exception: " + e.name);
-  }
-}
+    // what is the spec??? 
+    // v8 doesn't allow freezing typed arrays at all
+    name: "Add, delete, modify indexed elements of a typed array after freezing",
+    body: function () {
+        let a = new Int8Array(1);
+        a[0] = 42;
+
+        Object.freeze(a); // should it throw?
+
+        assert.isFalse(Object.isExtensible(a));
+        assert.isTrue(Object.isSealed(a));
+        assert.isTrue(Object.isFrozen(a)); // should it return false?
+
+        // current behavior:
+        // even though the array is frozen it's ok to modify existing elements
+        a[0] = 17;
+        assert.areEqual(17, a[0]);
+    }
+  },
+];
+
+testRunner.runTests(tests, { verbose: false /*so no need to provide baseline*/ });

+ 0 - 2
test/es5/rlexe.xml

@@ -105,13 +105,11 @@
   <test>
     <default>
       <files>seal.js</files>
-      <baseline>seal.baseline</baseline>
     </default>
   </test>
   <test>
     <default>
       <files>freeze.js</files>
-      <baseline>freeze.baseline</baseline>
     </default>
   </test>
   <test>

+ 0 - 11
test/es5/seal.baseline

@@ -1,11 +0,0 @@
-TestCase1
-1
-false
-TestCase2 - seal & add a property
-x,y
-true
-40
-TestCase3 - seal & delete a property
-x,y
-true
-40

+ 172 - 35
test/es5/seal.js

@@ -3,42 +3,179 @@
 // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
 //-------------------------------------------------------------------------------------------------------
 
-function write(args)
-{
- WScript.Echo(args);
+if (this.WScript && this.WScript.LoadScriptFile) { // Check for running in ch
+  this.WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
 }
 
-write("TestCase1");
-write(Object.seal.length);
-write(Object.isSealed({}));
-
-write("TestCase2 - seal & add a property");
-var a = {x:20, y:30};
-Object.seal(a);
-SafeCall(function() { a.x = 40; });
-SafeCall(function() { a.z = 50; });
-write(Object.getOwnPropertyNames(a));
-write(Object.isSealed(a));
-write(a.x);
-
-write("TestCase3 - seal & delete a property");
-var a = {x:20, y:30};
-Object.seal(a);
-SafeCall(function() { a.x = 40; });
-SafeCall(function() { delete a.x; });
-SafeCall(function() { a.z = 50; });
-write(Object.getOwnPropertyNames(a));
-write(Object.isSealed(a));
-write(a.x);
-
-function SafeCall(f)
-{
-  try
+var tests = [
   {
-    f();
-  }
-  catch (e)
+      name: "Add, delete, modify properties after sealing",
+      body: function () {
+          let a = {x: 42};
+          
+          Object.seal(a);
+          assert.isFalse(Object.isExtensible(a));
+          assert.isTrue(Object.isSealed(a));
+
+          // cannot add properties
+          a.y = 17;
+          assert.isFalse(a.hasOwnProperty('y'));
+          assert.throws(function () { 'use strict'; a.y = 17; }, TypeError, 
+            "Should throw on creating a new property in sealed object in strict mode", 
+            "Cannot create property for a non-extensible object");
+
+          // cannot delete properties
+          assert.isFalse(delete a.x);
+          assert.isTrue(a.hasOwnProperty('x'));
+          assert.throws(function () { 'use strict'; delete a.x; }, TypeError, 
+            "Should throw on creating a new property in sealed object in strict mode", 
+            "Calling delete on 'x' is not allowed in strict mode");
+
+          // cannot change prototype
+          let b = {};
+          assert.throws(function () { 'use strict'; Object.setPrototypeOf(a, b); }, TypeError, 
+            "Should throw on creating a new property in sealed object in strict mode", 
+            "Cannot create property for a non-extensible object");
+
+          // ok to modify the existing property
+          a.x = 17;
+          assert.areEqual(17, a.x);
+      }
+  },
   {
-    write("Exception: " + e.name);
-  }
-}
+    name: "Add, delete, modify indexed elements of an array after sealing",
+    body: function () {
+        let a = [42];
+        a[2] = 43;
+        
+        Object.seal(a);
+        assert.isFalse(Object.isExtensible(a));
+        assert.isTrue(Object.isSealed(a));
+
+        // the array cannot be extended
+        a[3] = 17;
+        assert.areEqual(3, a.length);
+        assert.isFalse(a.hasOwnProperty('3'))
+        assert.throws(function () { 'use strict'; a[3] = 17; }, TypeError, 
+          "Should throw on creating a new property in sealed object in strict mode", 
+          "Cannot create property for a non-extensible object");
+
+        // a hole cannot be filled
+        a[1] = 17;
+        assert.areEqual(3, a.length);
+        assert.isFalse(a.hasOwnProperty('1'))
+        assert.throws(function () { 'use strict'; a[1] = 17; }, TypeError, 
+          "Should throw on creating a new property in sealed object in strict mode", 
+          "Cannot create property for a non-extensible object");
+
+        // existing elements cannot be deleted
+        assert.isFalse(delete a[0]);
+        assert.isTrue(a.hasOwnProperty('0'));
+        assert.throws(function () { 'use strict'; delete a[0]; }, TypeError, 
+          "Should throw on creating a new property in sealed object in strict mode", 
+          "Calling delete on '0' is not allowed in strict mode");
+
+        // ok to modify an existing element
+        a[0] = 17;
+        assert.areEqual(17, a[0]);
+    }
+  },
+  {
+    name: "Add, delete, modify indexed elements of a typed array after sealing",
+    body: function () {
+        let a = new Int8Array(1);
+        a[0] = 42;
+
+        Object.seal(a);
+        assert.isFalse(Object.isExtensible(a));
+        assert.isTrue(Object.isSealed(a));
+
+        /* 
+        Typed arrays never allow adding or removing elements - should we test
+        that attempt to add in strict mode doesn't throw as for standard arrays?
+        (that's the current behavior of v8 and Chakra)
+        Not clear what the spec is. 
+
+        assert.throws(function () { 'use strict'; a[1] = 17; }, TypeError, 
+          "Should throw on creating a new property in sealed object in strict mode", 
+          "Cannot create property for a non-extensible object");
+        */
+
+        // ok to modify the existing element
+        a[0] = 17;
+        assert.areEqual(17, a[0]);
+    }
+  },
+  {
+    name: "Modify length of an array after sealing",
+    body: function () {
+        let a = [42, 17, 33];
+        a.length = 4;
+        Object.seal(a);
+
+        let descr_len = Object.getOwnPropertyDescriptor(a, 'length');
+        assert.isFalse(descr_len.configurable);
+        assert.isTrue(descr_len.writable);
+
+        // can increase length but cannot fill the tail
+        a.length = 5;
+        a[4] = "new!";
+        assert.areEqual(5, a.length);
+        assert.isFalse(a.hasOwnProperty('4'));
+
+        // cannot truncate by reducing the length below the last defined element
+        a.length = 1;
+        assert.areEqual(3, a.length);
+    }
+  },
+  {
+    name: "Sealed versus frozen",
+    body: function () {
+        let a = {x: 42};
+        Object.seal(a);
+        assert.isTrue(Object.isSealed(a));
+        assert.isFalse(Object.isFrozen(a));
+
+        // https://tc39.github.io/ecma262/#sec-testintegritylevel (7.3.15)
+        // empty objects are effectively frozen after being sealed
+        let empty_obj = {};
+        Object.seal(empty_obj);
+        assert.isTrue(Object.isSealed(empty_obj));
+        assert.isTrue(Object.isFrozen(empty_obj));
+
+        // similar to above, a sealed object with all properties individually 
+        // set to non-writable and non-configurable is frozen
+        let b = {};
+        Object.defineProperty(b, 'x', { value: 42, writable: false });
+        Object.seal(b);
+        assert.isTrue(Object.isSealed(b));
+        assert.isTrue(Object.isFrozen(b));
+
+        // standard arrays
+        let arr = [42];
+        Object.seal(arr);
+        assert.isTrue(Object.isSealed(arr));
+        assert.isFalse(Object.isFrozen(arr));
+
+        // typed arrays
+        let ta = new Int8Array(4);
+        Object.seal(ta);
+        assert.isTrue(Object.isSealed(ta));
+        assert.isFalse(Object.isFrozen(ta));
+
+        // empty typed arrays are effectively frozen after being sealed
+        let ta_empty = new Int8Array(0);
+        Object.seal(ta_empty);
+        assert.isTrue(Object.isSealed(ta_empty));
+        assert.isTrue(Object.isFrozen(ta_empty));
+
+        // frozen objects are sealed
+        let c = {x: 42};
+        Object.freeze(c);
+        assert.isTrue(Object.isFrozen(c));
+        assert.isTrue(Object.isSealed(c));
+    }
+  },
+];
+
+testRunner.runTests(tests, { verbose: false /*so no need to provide baseline*/ });