[JSC] Module namespace object behaves like immutable prototype exotic object
authorutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 10 Dec 2016 11:56:30 +0000 (11:56 +0000)
committerutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 10 Dec 2016 11:56:30 +0000 (11:56 +0000)
https://bugs.webkit.org/show_bug.cgi?id=165598

Reviewed by Mark Lam.

JSTests:

* modules/namespace-prototype-assignment.js: Added.
(else):
(reportError):
(shouldEqual):
(shouldThrow):
(stringify):
(makeTestID):
(doInternalSetPrototypeOf):
(ordinarySetPrototypeOf):
(setImmutablePrototype):
(windowProxySetPrototypeOf):
(initSetterExpectation):
(throwIfNoExceptionPending):
(objectSetPrototypeOf):
(setUnderscoreProto):
(reflectSetPrototypeOf):
(setPrototypeOf):
(newObjectProto.toString):
(Symbol):
(test):
(runTests):
* modules/namespace-set-prototype-of.js: Added.
(shouldThrow):
(TypeError.Cannot.set prototype):

Source/JavaScriptCore:

In the latest ECMA262 draft, the module namespace object behaves like immutable prototype exotic object.
https://tc39.github.io/ecma262/#sec-module-namespace-exotic-objects-setprototypeof-v

* runtime/JSModuleNamespaceObject.h:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@209662 268f45cc-cd09-0410-ab3c-d52691b4dbfc

JSTests/ChangeLog
JSTests/modules/namespace-prototype-assignment.js [new file with mode: 0644]
JSTests/modules/namespace-set-prototype-of.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/runtime/JSModuleNamespaceObject.h

index dfbec59..8e646b8 100644 (file)
@@ -1,3 +1,35 @@
+2016-12-10  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        [JSC] Module namespace object behaves like immutable prototype exotic object
+        https://bugs.webkit.org/show_bug.cgi?id=165598
+
+        Reviewed by Mark Lam.
+
+        * modules/namespace-prototype-assignment.js: Added.
+        (else):
+        (reportError):
+        (shouldEqual):
+        (shouldThrow):
+        (stringify):
+        (makeTestID):
+        (doInternalSetPrototypeOf):
+        (ordinarySetPrototypeOf):
+        (setImmutablePrototype):
+        (windowProxySetPrototypeOf):
+        (initSetterExpectation):
+        (throwIfNoExceptionPending):
+        (objectSetPrototypeOf):
+        (setUnderscoreProto):
+        (reflectSetPrototypeOf):
+        (setPrototypeOf):
+        (newObjectProto.toString):
+        (Symbol):
+        (test):
+        (runTests):
+        * modules/namespace-set-prototype-of.js: Added.
+        (shouldThrow):
+        (TypeError.Cannot.set prototype):
+
 2016-12-09  Michael Saboff  <msaboff@apple.com>
 
         JSVALUE64: Pass arguments in platform argument registers when making JavaScript calls
diff --git a/JSTests/modules/namespace-prototype-assignment.js b/JSTests/modules/namespace-prototype-assignment.js
new file mode 100644 (file)
index 0000000..d938133
--- /dev/null
@@ -0,0 +1,389 @@
+// This test suite compares the behavior of setting the prototype on various values
+// (using Object.setPrototypeOf(), obj.__proto__ assignment, and Reflect.setPrototypeOf())
+// against what is specified in the ES spec.  The expected behavior specified according
+// to the spec is expressed in expectationsForObjectSetPrototypeOf,
+// expectationsForSetUnderscoreProto, and expectationsForReflectSetPrototypeOf.
+
+import * as namespace from "./namespace-prototype-assignment.js"
+
+var inBrowser = (typeof window != "undefined");
+
+// Test configuration options:
+var verbose = false;
+var maxIterations = 1;
+var throwOnEachError = true;
+
+var testUndefined = true;
+var testNull = true;
+var testTrue = true;
+var testFalse = true;
+var testNumbers = true;
+var testString = true;
+var testSymbol = true;
+var testObject = true;
+var testGlobal = true;
+var testWindowProtos = inBrowser;
+
+var engine;
+
+//====================================================================================
+// Error messages:
+
+if (inBrowser) {
+    let userAgent = navigator.userAgent;
+    if (userAgent.match(/ Chrome\/[0-9]+/)) engine = "chrome";
+    else if (userAgent.match(/ Firefox\/[0-9]+/)) engine = "default";
+    else engine = "safari";
+} else
+    engine = "jsc";
+
+// Set default error messages and then override with engine specific ones below.
+var DefaultTypeError = "TypeError: ";
+var CannotSetPrototypeOfImmutablePrototypeObject = DefaultTypeError;
+var CannotSetPrototypeOfThisObject = DefaultTypeError;
+var CannotSetPrototypeOfUndefinedOrNull = DefaultTypeError;
+var CannotSetPrototypeOfNonObject = DefaultTypeError;
+var PrototypeValueCanOnlyBeAnObjectOrNull = DefaultTypeError;
+var ObjectProtoCalledOnNullOrUndefinedError = DefaultTypeError;
+var ReflectSetPrototypeOfRequiresTheFirstArgumentBeAnObject = DefaultTypeError;
+var ReflectSetPrototypeOfRequiresTheSecondArgumentBeAnObjectOrNull = DefaultTypeError;
+
+if (engine == "jsc" || engine === "safari") {
+    CannotSetPrototypeOfImmutablePrototypeObject = "TypeError: Cannot set prototype of immutable prototype object";
+    CannotSetPrototypeOfThisObject = "TypeError: Cannot set prototype of this object";
+    CannotSetPrototypeOfUndefinedOrNull = "TypeError: Cannot set prototype of undefined or null";
+    CannotSetPrototypeOfNonObject = "TypeError: Cannot set prototype of non-object";
+    PrototypeValueCanOnlyBeAnObjectOrNull = "TypeError: Prototype value can only be an object or null";
+    ObjectProtoCalledOnNullOrUndefinedError = "TypeError: Object.prototype.__proto__ called on null or undefined";
+    ReflectSetPrototypeOfRequiresTheFirstArgumentBeAnObject = "TypeError: Reflect.setPrototypeOf requires the first argument be an object";
+    ReflectSetPrototypeOfRequiresTheSecondArgumentBeAnObjectOrNull = "TypeError: Reflect.setPrototypeOf requires the second argument be either an object or null";
+} else if (engine === "chrome") {
+    CannotSetPrototypeOfImmutablePrototypeObject = "TypeError: Immutable prototype object ";
+}
+
+//====================================================================================
+// Utility functions:
+
+if (inBrowser)
+    print = console.log;
+
+function reportError(errorMessage) {
+    if (throwOnEachError)
+        throw errorMessage;
+    else
+        print(errorMessage);
+}
+
+function shouldEqual(testID, resultType, actual, expected) {
+    if (actual != expected)
+        reportError("ERROR in " + resultType
+            + ": expect " + stringify(expected) + ", actual " + stringify(actual)
+            + " in test: " + testID.signature + " on iteration " + testID.iteration);
+}
+
+function shouldThrow(testID, resultType, actual, expected) {
+    let actualStr = "" + actual;
+    if (!actualStr.startsWith(expected))
+        reportError("ERROR in " + resultType
+            + ": expect " + expected + ", actual " + actual
+            + " in test: " + testID.signature + " on iteration " + testID.iteration);
+}
+
+function stringify(value) {
+    if (typeof value == "string") return '"' + value + '"';
+    if (typeof value == "symbol") return value.toString();
+
+    if (value === origGlobalProto) return "origGlobalProto";
+    if (value === origObjectProto) return "origObjectProto";
+    if (value === newObjectProto) return "newObjectProto";
+    if (value === proxyObject) return "proxyObject";
+
+    if (value === null) return "null";
+    if (typeof value == "object") return "object";
+    return "" + value;
+}
+
+function makeTestID(index, iteration, targetName, protoToSet, protoSetter, expected) {
+    let testID = {};
+    testID.signature = "[" + index + "] "
+        + protoSetter.actionName + "|"
+        + targetName + "|"
+        + stringify(protoToSet) + "|"
+        + stringify(expected.result) + "|"
+        + stringify(expected.proto) + "|"
+        + stringify(expected.exception);
+    testID.iteration = iteration;
+    return testID;
+}
+
+//====================================================================================
+// Functions to express the expectations of the ES specification:
+
+function doInternalSetPrototypeOf(result, target, origProto, newProto) {
+    if (!target.setPrototypeOf) {
+        result.success = true;
+        result.exception = undefined;
+        return;
+    }
+    target.setPrototypeOf(result, origProto, newProto);
+}
+
+// 9.1.2.1 OrdinarySetPrototypeOf ( O, V )
+// https://tc39.github.io/ecma262/#sec-ordinarysetprototypeof
+function ordinarySetPrototypeOf(result, currentProto, newProto) {
+    // 9.1.2.1-4 If SameValue(V, current) is true, return true.
+    if (newProto === currentProto) {
+        result.success = true;
+        return;
+    }
+    // 9.1.2.1-5 [extensibility check not tested here]
+    // 9.1.2.1-8 [cycle check not tested here]
+    result.success = true;
+}
+
+// 9.4.7.2 SetImmutablePrototype ( O, V )
+// https://tc39.github.io/ecma262/#sec-set-immutable-prototype
+function setImmutablePrototype(result, currentProto, newProto) {
+    if (newProto === currentProto) {
+        result.success = true;
+        return;
+    }
+    result.success = false;
+    result.exception = CannotSetPrototypeOfImmutablePrototypeObject;
+}
+
+// HTML spec: 7.4.2 [[SetPrototypeOf]] ( V )
+// https://html.spec.whatwg.org/#windowproxy-setprototypeof
+function windowProxySetPrototypeOf(result, currentProto, newProto) {
+    result.success = false;
+    result.exception = CannotSetPrototypeOfThisObject;
+}
+
+
+var count = 0;
+function initSetterExpectation(target, newProto) {
+    var targetValue = target.value();
+    var origProto = undefined;
+    if (targetValue != null && targetValue != undefined)
+        origProto = targetValue.__proto__; // Default to old proto.
+
+    var expected = {};
+    expected.targetValue = targetValue;
+    expected.origProto = origProto;
+    expected.exception = undefined;
+    expected.proto = origProto;
+    expected.result = undefined;
+
+    return expected;
+}
+
+// 19.1.2.21 Object.setPrototypeOf ( O, proto )
+// https://tc39.github.io/ecma262/#sec-object.setprototypeof
+function objectSetPrototypeOf(target, newProto) {
+    let expected = initSetterExpectation(target, newProto);
+    var targetValue = expected.targetValue;
+    var origProto = expected.origProto;
+
+    function throwIfNoExceptionPending(e) {
+        if (!expected.exception)
+            expected.exception = e;
+    }
+
+    // 19.1.2.21-1 Let O be ? RequireObjectCoercible(O).
+    if (targetValue == undefined || targetValue == null)
+        throwIfNoExceptionPending(CannotSetPrototypeOfUndefinedOrNull);
+
+    // 19.1.2.21-2 If Type(proto) is neither Object nor Null, throw a TypeError exception.
+    if (typeof newProto != "object")
+        throwIfNoExceptionPending(PrototypeValueCanOnlyBeAnObjectOrNull);
+
+    // 19.1.2.21-3 If Type(O) is not Object, return O.
+    else if (typeof targetValue != "object")
+        expected.result = targetValue;
+
+    // 19.1.2.21-4 Let status be ? O.[[SetPrototypeOf]](proto).
+    else {
+        // 19.1.2.21-5 If status is false, throw a TypeError exception.
+        let result = {};
+        doInternalSetPrototypeOf(result, target, origProto, newProto);
+        if (result.success)
+            expected.proto = newProto;
+        else
+            throwIfNoExceptionPending(result.exception);
+
+        // 19.1.2.21-6 Return O.
+        expected.result = targetValue;
+    }
+
+    return expected;
+}
+objectSetPrototypeOf.action = (obj, newProto) => Object.setPrototypeOf(obj, newProto);
+objectSetPrototypeOf.actionName = "Object.setPrototypeOf";
+
+
+// B.2.2.1.2 set Object.prototype.__proto__
+// https://tc39.github.io/ecma262/#sec-set-object.prototype.__proto__
+function setUnderscoreProto(target, newProto) {
+    let expected = initSetterExpectation(target, newProto);
+    var targetValue = expected.targetValue;
+    var origProto = expected.origProto;
+
+    function throwIfNoExceptionPending(e) {
+        if (!expected.exception)
+            expected.exception = e;
+    }
+
+    // B.2.2.1.2-1 Let O be ? RequireObjectCoercible(this value).
+    if (targetValue == undefined || targetValue == null)
+        throwIfNoExceptionPending(DefaultTypeError);
+
+    // B.2.2.1.2-2 If Type(proto) is neither Object nor Null, return undefined.
+    if (typeof newProto != "object")
+        expected.result = undefined;
+
+    // B.2.2.1.2-3 If Type(O) is not Object, return undefined.
+    else if (typeof targetValue != "object")
+        expected.result = undefined;
+
+    // B.2.2.1.2-4 Let status be ? O.[[SetPrototypeOf]](proto).
+    else {
+        // B.2.2.1.2-5 If status is false, throw a TypeError exception.
+        let result = {};
+        doInternalSetPrototypeOf(result, target, origProto, newProto);
+        if (result.success)
+            expected.proto = newProto;
+        else
+            throwIfNoExceptionPending(result.exception);
+
+        // B.2.2.1.2-6 Return undefined.
+        expected.result = undefined;
+    }
+
+    // Override the result to be the newProto value because the statement obj.__proto__ = value
+    // will produce the rhs value, not the result of the obj.__proto__ setter.
+    expected.result = newProto;
+    return expected;
+}
+setUnderscoreProto.action = (obj, newProto) => (obj.__proto__ = newProto);
+setUnderscoreProto.actionName = "obj.__proto__";
+
+
+// 26.1.13 Reflect.setPrototypeOf ( target, proto )
+// https://tc39.github.io/ecma262/#sec-reflect.setprototypeof
+// function expectationsForReflectSetPrototypeOf(target, newProto, targetExpectation) {
+function reflectSetPrototypeOf(target, newProto) {
+    let expected = initSetterExpectation(target, newProto);
+    var targetValue = expected.targetValue;
+    var origProto = expected.origProto;
+
+    function throwIfNoExceptionPending(e) {
+        if (!expected.exception)
+            expected.exception = e;
+    }
+
+    // 26.1.13-1 If Type(target) is not Object, throw a TypeError exception.
+    if (targetValue === null || typeof targetValue != "object")
+        throwIfNoExceptionPending(ReflectSetPrototypeOfRequiresTheFirstArgumentBeAnObject);
+
+    // 26.1.13-2 If Type(proto) is not Object and proto is not null, throw a TypeError exception.
+    if (typeof newProto != "object")
+        throwIfNoExceptionPending(ReflectSetPrototypeOfRequiresTheSecondArgumentBeAnObjectOrNull);
+
+    // 26.1.13-3 Return ? target.[[SetPrototypeOf]](proto).
+    let result = {};
+    doInternalSetPrototypeOf(result, target, origProto, newProto);
+    expected.result = result.success;
+    if (result.success)
+        expected.proto = newProto;
+
+    return expected;
+}
+reflectSetPrototypeOf.action = (obj, newProto) => Reflect.setPrototypeOf(obj, newProto);
+reflectSetPrototypeOf.actionName = "Reflect.setPrototypeOf";
+
+
+//====================================================================================
+// Test Data:
+
+var global = new Function('return this')();
+var origGlobalProto = global.__proto__;
+var origObjectProto = {}.__proto__;
+var proxyObject = new Proxy({ }, {
+    setPrototypeOf(target, value) {
+        throw "Thrown from proxy";
+    }
+});
+var newObjectProto = { toString() { return "newObjectProto"; } };
+var origNamespaceProto = namespace.__proto__;
+
+var targets = [];
+
+targets.push({
+  name: "namespace",
+  value: () => origNamespaceProto,
+  setPrototypeOf: setImmutablePrototype
+});
+
+var newProtos = [
+    undefined,
+    null,
+    true,
+    false,
+    0,
+    11,
+    123.456,
+    "doh",
+    Symbol("doh"),
+    {},
+    origObjectProto,
+    origGlobalProto,
+    newObjectProto
+];
+
+var protoSetters = [
+    objectSetPrototypeOf,
+    setUnderscoreProto,
+    reflectSetPrototypeOf,
+];
+
+
+//====================================================================================
+// Test driver functions:
+
+function test(testID, targetValue, newProto, setterAction, expected) {
+    let exception = undefined;
+    let result = undefined;
+    try {
+        result = setterAction(targetValue, newProto);
+    } catch (e) {
+        exception = e;
+    }
+    shouldThrow(testID, "exception", exception, expected.exception);
+    if (!expected.exception) {
+        shouldEqual(testID, "__proto__", targetValue.__proto__, expected.proto);
+        shouldEqual(testID, "result", result, expected.result);
+    }
+}
+
+function runTests() {
+    let testIndex = 0;
+    for (let protoSetter of protoSetters) {
+        for (let target of targets) {
+            for (let newProto of newProtos) {
+                let currentTestIndex = testIndex++;
+                for (var i = 0; i < maxIterations; i++) {
+                    let expected = protoSetter(target, newProto);
+                    let targetValue = expected.targetValue;
+
+                    let testID = makeTestID(currentTestIndex, i, target.name, newProto, protoSetter, expected);
+                    if (verbose && i == 0)
+                        print("test: " + testID.signature);
+
+                    test(testID, targetValue, newProto, protoSetter.action, expected);
+                }
+            }
+        }
+    }
+}
+
+runTests();
diff --git a/JSTests/modules/namespace-set-prototype-of.js b/JSTests/modules/namespace-set-prototype-of.js
new file mode 100644 (file)
index 0000000..2c1ece3
--- /dev/null
@@ -0,0 +1,10 @@
+import * as namespace from "./namespace-set-prototype-of.js"
+import { shouldBe, shouldThrow } from "./resources/assert.js";
+
+shouldThrow(() => {
+    Object.setPrototypeOf(namespace, {});
+}, `TypeError: Cannot set prototype of immutable prototype object`);
+
+shouldBe(Reflect.setPrototypeOf(namespace, {}), false);
+shouldBe(Reflect.setPrototypeOf(namespace, null), true);
+shouldBe(Object.setPrototypeOf(namespace, null), namespace);
index f2a31c0..859024c 100644 (file)
@@ -1,5 +1,17 @@
 2016-12-10  Yusuke Suzuki  <utatane.tea@gmail.com>
 
+        [JSC] Module namespace object behaves like immutable prototype exotic object
+        https://bugs.webkit.org/show_bug.cgi?id=165598
+
+        Reviewed by Mark Lam.
+
+        In the latest ECMA262 draft, the module namespace object behaves like immutable prototype exotic object.
+        https://tc39.github.io/ecma262/#sec-module-namespace-exotic-objects-setprototypeof-v
+
+        * runtime/JSModuleNamespaceObject.h:
+
+2016-12-10  Yusuke Suzuki  <utatane.tea@gmail.com>
+
         REGRESSION(r208791): Assertion in testb3
         https://bugs.webkit.org/show_bug.cgi?id=165651
 
index 4060b39..b72f543 100644 (file)
@@ -35,7 +35,7 @@ class AbstractModuleRecord;
 class JSModuleNamespaceObject : public JSDestructibleObject {
 public:
     typedef JSDestructibleObject Base;
-    static const unsigned StructureFlags = Base::StructureFlags | OverridesGetOwnPropertySlot | InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero | OverridesGetPropertyNames | GetOwnPropertySlotIsImpureForPropertyAbsence;
+    static const unsigned StructureFlags = Base::StructureFlags | OverridesGetOwnPropertySlot | InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero | OverridesGetPropertyNames | GetOwnPropertySlotIsImpureForPropertyAbsence | IsImmutablePrototypeExoticObject;
 
     static JSModuleNamespaceObject* create(ExecState* exec, JSGlobalObject* globalObject, Structure* structure, AbstractModuleRecord* moduleRecord, const IdentifierSet& exports)
     {