diff --git a/lib/Parser/rterrors.h b/lib/Parser/rterrors.h index 853178e168b..462a89702ed 100644 --- a/lib/Parser/rterrors.h +++ b/lib/Parser/rterrors.h @@ -275,6 +275,8 @@ RT_ERROR_MSG(JSERR_DeletePropertyWithSuper, 5146, "Unable to delete property '%s RT_ERROR_MSG(JSERR_DetachedTypedArray, 5147, "%s: The ArrayBuffer is detached.", "The ArrayBuffer is detached.", kjstTypeError, 0) RT_ERROR_MSG(JSERR_AsmJsCompileError, 5148, "%s: Compiling asm.js failed.", "Compiling asm.js failed.", kjstError, 0) +RT_ERROR_MSG(JSERR_ImmutablePrototypeSlot, 5149, "%s: Can't set the prototype of this object.", "Can't set the prototype of this object.", kjstTypeError, 0) + /* Error messages for misbehaved Async Operations for use in Promise.js */ RT_ERROR_MSG(ASYNCERR_NoErrorInErrorState, 5200, "", "Status is 'error', but getResults did not return an error", kjstError, 0) RT_ERROR_MSG(ASYNCERR_InvalidStatusArg, 5201, "", "Missing or invalid status parameter passed to completed handler", kjstError, 0) diff --git a/lib/Runtime/Library/JavascriptObject.cpp b/lib/Runtime/Library/JavascriptObject.cpp index 52b0981fe23..93a4d063148 100644 --- a/lib/Runtime/Library/JavascriptObject.cpp +++ b/lib/Runtime/Library/JavascriptObject.cpp @@ -176,6 +176,16 @@ namespace Js return FALSE; } + if (object->IsProtoImmutable()) + { + // ES2016 19.1.3: + // The Object prototype object is the intrinsic object %ObjectPrototype%. + // The Object prototype object is an immutable prototype exotic object. + // ES2016 9.4.7: + // An immutable prototype exotic object is an exotic object that has an immutable [[Prototype]] internal slot. + JavascriptError::ThrowTypeError(scriptContext, JSERR_ImmutablePrototypeSlot); + } + // 6. If V is not null, then // a. Let p be V. // b. Repeat, while p is not null diff --git a/lib/Runtime/Library/ObjectPrototypeObject.h b/lib/Runtime/Library/ObjectPrototypeObject.h index 8cf28d23d4a..bd9c1608035 100644 --- a/lib/Runtime/Library/ObjectPrototypeObject.h +++ b/lib/Runtime/Library/ObjectPrototypeObject.h @@ -36,6 +36,7 @@ namespace Js // Indicates if __proto__ is enabled currently (note that it can be disabled and re-enabled), // only useful for diagnostics to decide displaying __proto__ or [prototype]. bool is__proto__Enabled() const { return __proto__Enabled; } + BOOL IsProtoImmutable() const { return true; } void PostDefineOwnProperty__proto__(RecyclableObject* obj); }; diff --git a/lib/Runtime/Types/RecyclableObject.h b/lib/Runtime/Types/RecyclableObject.h index 3e133882105..cceda29eb9d 100644 --- a/lib/Runtime/Types/RecyclableObject.h +++ b/lib/Runtime/Types/RecyclableObject.h @@ -285,6 +285,7 @@ namespace Js { virtual BOOL IsConfigurable(PropertyId propertyId) { return false; } virtual BOOL IsEnumerable(PropertyId propertyId) { return false; } virtual BOOL IsExtensible() { return false; } + virtual BOOL IsProtoImmutable() const { return false; } virtual BOOL PreventExtensions() { return false; }; // Sets [[Extensible]] flag of instance to false virtual void ThrowIfCannotDefineProperty(PropertyId propId, PropertyDescriptor descriptor); virtual void ThrowIfCannotGetOwnPropertyDescriptor(PropertyId propId) {} diff --git a/test/UnitTestFramework/UnitTestFramework.js b/test/UnitTestFramework/UnitTestFramework.js index de4af3d9928..865ec507ff0 100644 --- a/test/UnitTestFramework/UnitTestFramework.js +++ b/test/UnitTestFramework/UnitTestFramework.js @@ -294,11 +294,15 @@ var assert = function assert() { /// /// expectedException: A specific exception type, e.g. TypeError. /// if undefined, this will pass if testFunction throws any exception.
- /// expectedErrorMessage: If specified, verifies the thrown Error has expected message.

+ /// message: Test-provided explanation of why this particular exception should be thrown.
+ /// expectedErrorMessage: If specified, verifies the thrown Error has expected message.
+ /// You only need to specify this if you are looking for a specific error message.
+ /// Does not require the prefix of e.g. "TypeError:" that would be printed by the host.

/// /// Example:
/// assert.throws(function() { eval("{"); }, SyntaxError, "expected SyntaxError")
/// assert.throws(function() { eval("{"); }) // -- use this when you don't care which exception is thrown.
+ /// assert.throws(function() { eval("{"); }, SyntaxError, "expected SyntaxError with message about expected semicolon.", "Expected ';'") /// var noException = {}; // Some unique object which will not be equal to anything else. var exception = noException; // Set default value. diff --git a/test/es6/proto_disable.baseline b/test/es6/proto_disable.baseline index a3f168a460d..33c7b6471d7 100644 --- a/test/es6/proto_disable.baseline +++ b/test/es6/proto_disable.baseline @@ -1,14 +1,11 @@ -*** Running test #1 (0): Change Object.prototype.__proto__ value -Object.prototype.__proto__ = null -PASSED -*** Running test #2 (1): seal/freeze Object.prototype +*** Running test #1 (0): seal/freeze Object.prototype Object.seal(Object.prototype) Object.freeze(Object.prototype) PASSED -*** Running test #3 (2): delete Object.prototype.__proto__ +*** Running test #2 (1): delete Object.prototype.__proto__ delete Object.prototype.__proto__ PASSED -*** Running test #4 (3): DefineOwnProperty with missing/different attribute set +*** Running test #3 (2): DefineOwnProperty with missing/different attribute set Object.defineProperty(Object.prototype, "__proto__", {}) Object.defineProperty(Object.prototype, "__proto__", {enumerable: false}) Object.defineProperty(Object.prototype, "__proto__", {configurable: true}) @@ -18,6 +15,6 @@ Object.defineProperty(Object.prototype, "__proto__", {enumerable: false, configu Object.defineProperty(Object.prototype, "__proto__", {value: 234, writable: true, enumerable: false, configurable: true}) Object.defineProperty(Object.prototype, "__proto__", {set: function () { return "custom setter" }, enumerable: false, configurable: true}) PASSED -*** Running test #5 (4): Change Object.prototype.__proto__ getter or setter +*** Running test #4 (3): Change Object.prototype.__proto__ getter or setter PASSED -Summary of tests: total executed: 5; passed: 5; failed: 0 +Summary of tests: total executed: 4; passed: 4; failed: 0 diff --git a/test/es6/proto_disable.js b/test/es6/proto_disable.js index 4f042b33df1..d2c67b7fafb 100644 --- a/test/es6/proto_disable.js +++ b/test/es6/proto_disable.js @@ -9,30 +9,6 @@ if (this.WScript && this.WScript.LoadScriptFile) { } var tests = [ - { - name: "Change Object.prototype.__proto__ value", - body: function () { - // This considered no-op: Object.prototype.__proto__ = null - verify_disable("Object.prototype.__proto__ = null", KEEP_ENABLED); - - // Set to these primitives will throw and make no change - [undefined, 0, 123, -12.3, NaN, Infinity, true, false, "str"].forEach( - function (newValue) { - Object.prototype.__proto__ = newValue; - verify__proto__enabled(); - }); - - // Set to any objects will throw and make no change - [new Boolean(), new Number(12), new String("string object"), {}, [], Object.prototype, Math.sin, assert.throws].forEach( - function (newValue) { - assert.throws__proto__Cyclic(function () { - Object.prototype.__proto__ = newValue; - }); - verify__proto__enabled(); - }); - } - }, - { name: "seal/freeze Object.prototype", body: function () { diff --git a/test/es7/immutable-prototype.js b/test/es7/immutable-prototype.js new file mode 100644 index 00000000000..05f9fbed26a --- /dev/null +++ b/test/es7/immutable-prototype.js @@ -0,0 +1,45 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +// ES7 Object Prototype object has an immutable [[Prototype]] internal slot +// See: 19.1.3 Properties of the Object Prototype Object +// See: 9.4.7 Immutable Prototype Exotic Objects + +WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); + +var tests = [ + { + name: "Not okay to set Object.prototype.[[Prototype]] using __proto__", + body: function () { + var objectPrototypeObject = Object.getPrototypeOf(Object.prototype) + var b = Object.create(null) + + assert.throws(function () { Object.prototype.__proto__ = b }, + TypeError, + "It should not be okay to set Object.prototype.[[Prototype]] using __proto__", + "Can't set the prototype of this object.") + + assert.areEqual(objectPrototypeObject, Object.prototype.__proto__, "Object.prototype.__proto__ is unchanged") + assert.areEqual(objectPrototypeObject, Object.getPrototypeOf(Object.prototype), "Object.getPrototypeOf(Object.prototype) is unchanged") + } + }, + { + name: "Not okay to set Object.prototype.[[Prototype]] using Object.setPrototypeOf", + body: function () { + var objectPrototypeObject = Object.getPrototypeOf(Object.prototype) + var b = Object.create(null) + + assert.throws(function () { Object.setPrototypeOf(Object.prototype, b) }, + TypeError, + "It should not be okay to set Object.prototype.[[Prototype]] using Object.setPrototypeOf", + "Can't set the prototype of this object.") + + assert.areEqual(objectPrototypeObject, Object.prototype.__proto__, "Object.prototype.__proto__ is unchanged") + assert.areEqual(objectPrototypeObject, Object.getPrototypeOf(Object.prototype), "Object.getPrototypeOf(Object.prototype) is unchanged") + } + }, +]; + +testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" }); diff --git a/test/es7/rlexe.xml b/test/es7/rlexe.xml index 9830430fee4..1826d5a8846 100644 --- a/test/es7/rlexe.xml +++ b/test/es7/rlexe.xml @@ -59,4 +59,10 @@ BugFix + + + immutable-prototype.js + -args summary -endargs + +