Title: [194340] trunk
Revision
194340
Author
[email protected]
Date
2015-12-21 12:40:04 -0800 (Mon, 21 Dec 2015)

Log Message

Unexpected exception assigning to this._property inside arrow function
https://bugs.webkit.org/show_bug.cgi?id=152028

Patch by Skachkov Oleksandr <[email protected]> on 2015-12-21
Reviewed by Saam Barati.

Source/_javascript_Core:

The issue appeared in case if in arrow function created base-level lexical envioronment, and in this case
|this| value was loaded from wrong scope. The problem was that loading of the |this| happened too early when
compiling bytecode because the bytecode generators's scope stack wasn't in sync with runtime scope stack.
To fix issue loading of |this| was moved after initializeDefaultParameterValuesAndSetupFunctionScopeStack
in BytecodeGenerator.cpp

* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::BytecodeGenerator):
* tests/stress/arrowfunction-lexical-bind-this-2.js:

LayoutTests:

Added new test cases for arrow function, to force create lexical env in body of function.

* js/arrowfunction-lexical-bind-this-expected.txt:
* js/script-tests/arrowfunction-lexical-bind-this.js:

Modified Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (194339 => 194340)


--- trunk/LayoutTests/ChangeLog	2015-12-21 20:26:54 UTC (rev 194339)
+++ trunk/LayoutTests/ChangeLog	2015-12-21 20:40:04 UTC (rev 194340)
@@ -1,3 +1,15 @@
+2015-12-21  Skachkov Oleksandr  <[email protected]>
+
+        Unexpected exception assigning to this._property inside arrow function
+        https://bugs.webkit.org/show_bug.cgi?id=152028
+
+        Reviewed by Saam Barati.
+
+        Added new test cases for arrow function, to force create lexical env in body of function.
+
+        * js/arrowfunction-lexical-bind-this-expected.txt:
+        * js/script-tests/arrowfunction-lexical-bind-this.js:
+
 2015-12-21  Ryan Haddad  <[email protected]>
 
         Marking inspector/debugger/command-line-api-exception-nested-catch.html as a flaky timeout on mac-wk1

Modified: trunk/LayoutTests/js/arrowfunction-lexical-bind-this-expected.txt (194339 => 194340)


--- trunk/LayoutTests/js/arrowfunction-lexical-bind-this-expected.txt	2015-12-21 20:26:54 UTC (rev 194339)
+++ trunk/LayoutTests/js/arrowfunction-lexical-bind-this-expected.txt	2015-12-21 20:40:04 UTC (rev 194340)
@@ -16,6 +16,9 @@
 PASS real.borrow()() === real is true
 PASS arrowFunction('-this') is "right-this"
 PASS hostObj.func('-this') is "right-this"
+PASS arrowWithEval.func() is "new-value"
+PASS fooObject.arr() is internal_value_1
+PASS fooObject._id is internal_value_2
 PASS successfullyParsed is true
 
 TEST COMPLETE

Modified: trunk/LayoutTests/js/script-tests/arrowfunction-lexical-bind-this.js (194339 => 194340)


--- trunk/LayoutTests/js/script-tests/arrowfunction-lexical-bind-this.js	2015-12-21 20:26:54 UTC (rev 194339)
+++ trunk/LayoutTests/js/script-tests/arrowfunction-lexical-bind-this.js	2015-12-21 20:40:04 UTC (rev 194340)
@@ -1,10 +1,10 @@
 description('Tests for ES6 arrow function lexical bind of this');
 
 function Dog(name) {
-  this.name = name;
-  this.getName = () => eval("this.name");
-  this.getNameHard = () => eval("(() => this.name)()");
-  this.getNameNesting = () => () => () => this.name;
+    this.name = name;
+    this.getName = () => eval("this.name");
+    this.getNameHard = () => eval("(() => this.name)()");
+    this.getNameNesting = () => () => () => this.name;
 }
 
 var d = new Dog("Max");
@@ -13,10 +13,10 @@
 shouldBe('d.getNameNesting()()()', 'd.name');
 
 var obj = {
-  name   : 'objCode',
-  method : function () {
-    return (value) => this.name + "-name-" + value;
-  }
+    name   : 'objCode',
+    method : function () {
+        return (value) => this.name + "-name-" + value;
+    }
 };
 
 shouldBe("obj.method()('correct')", "'objCode-name-correct'");
@@ -24,11 +24,11 @@
 shouldBe("obj.method()('correct')", "'newObjCode-name-correct'");
 
 var deepObj = {
-  name : 'wrongObjCode',
-  internalObject: {
-    name  :'internalObject',
-    method: function () { return (value) => this.name + "-name-" + value; }
-  }
+    name : 'wrongObjCode',
+    internalObject: {
+        name  :'internalObject',
+        method: function () { return (value) => this.name + "-name-" + value; }
+    }
 };
 shouldBe("deepObj.internalObject.method()('correct')", "'internalObject-name-correct'");
 
@@ -37,7 +37,7 @@
 shouldBe("deepObj.internalObject.method()('correct')", "'newInternalObject-name-correct'");
 
 var functionConstructor = function () {
-  this.func = () => this;
+    this.func = () => this;
 };
 
 var instance = new functionConstructor();
@@ -45,9 +45,9 @@
 shouldBe("instance.func() === instance", "true");
 
 var ownerObj = {
-  method : function () {
-    return () => this;
-  }
+    method : function () {
+        return () => this;
+    }
 };
 
 shouldBe("ownerObj.method()() === ownerObj", "true");
@@ -64,4 +64,38 @@
 shouldBe("arrowFunction('-this')", '"right-this"');
 shouldBe("hostObj.func('-this')", '"right-this"');
 
+var functionConstructorWithEval = function () {
+    this._id = 'old-value';
+    this.func = () => {
+        var f;
+        eval('10==10');
+        this._id = 'new-value';
+        return this._id;
+    };
+};
+
+var arrowWithEval = new functionConstructorWithEval();
+
+shouldBe("arrowWithEval.func()", '"new-value"');
+
+var internal_value_1 = 123;
+var internal_value_2 = '1234';
+
+function foo() {
+    let arr = () => {
+        var x = internal_value_1;
+        function bas() {
+            return x;
+        };
+        this._id = internal_value_2;
+        return bas();
+    };
+    this.arr = arr;
+};
+
+var fooObject = new foo();
+
+shouldBe("fooObject.arr()", 'internal_value_1');
+shouldBe("fooObject._id", 'internal_value_2');
+
 var successfullyParsed = true;

Modified: trunk/Source/_javascript_Core/ChangeLog (194339 => 194340)


--- trunk/Source/_javascript_Core/ChangeLog	2015-12-21 20:26:54 UTC (rev 194339)
+++ trunk/Source/_javascript_Core/ChangeLog	2015-12-21 20:40:04 UTC (rev 194340)
@@ -1,3 +1,20 @@
+2015-12-21  Skachkov Oleksandr  <[email protected]>
+
+        Unexpected exception assigning to this._property inside arrow function
+        https://bugs.webkit.org/show_bug.cgi?id=152028
+
+        Reviewed by Saam Barati.
+
+        The issue appeared in case if in arrow function created base-level lexical envioronment, and in this case 
+        |this| value was loaded from wrong scope. The problem was that loading of the |this| happened too early when
+        compiling bytecode because the bytecode generators's scope stack wasn't in sync with runtime scope stack.
+        To fix issue loading of |this| was moved after initializeDefaultParameterValuesAndSetupFunctionScopeStack 
+        in BytecodeGenerator.cpp   
+
+        * bytecompiler/BytecodeGenerator.cpp:
+        (JSC::BytecodeGenerator::BytecodeGenerator):
+        * tests/stress/arrowfunction-lexical-bind-this-2.js:
+
 2015-12-21  Filip Pizlo  <[email protected]>
 
         FTL B3 should do vararg calls

Modified: trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.cpp (194339 => 194340)


--- trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.cpp	2015-12-21 20:26:54 UTC (rev 194339)
+++ trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.cpp	2015-12-21 20:40:04 UTC (rev 194340)
@@ -521,12 +521,6 @@
 
     m_newTargetRegister = addVar();
     switch (parseMode) {
-    case SourceParseMode::ArrowFunctionMode: {
-        if (functionNode->usesThis())
-            emitLoadThisFromArrowFunctionLexicalEnvironment();
-        break;
-    }
-
     case SourceParseMode::GeneratorWrapperFunctionMode: {
         m_generatorRegister = addVar();
 
@@ -550,20 +544,22 @@
     }
 
     default: {
-        if (isConstructor()) {
-            emitMove(m_newTargetRegister, &m_thisRegister);
-            if (constructorKind() == ConstructorKind::Derived)
-                emitMoveEmptyValue(&m_thisRegister);
-            else
-                emitCreateThis(&m_thisRegister);
-        } else if (constructorKind() != ConstructorKind::None) {
-            emitThrowTypeError("Cannot call a class constructor");
-        } else if (functionNode->usesThis() || codeBlock->usesEval()) {
-            m_codeBlock->addPropertyAccessInstruction(instructions().size());
-            emitOpcode(op_to_this);
-            instructions().append(kill(&m_thisRegister));
-            instructions().append(0);
-            instructions().append(0);
+        if (SourceParseMode::ArrowFunctionMode != parseMode) {
+            if (isConstructor()) {
+                emitMove(m_newTargetRegister, &m_thisRegister);
+                if (constructorKind() == ConstructorKind::Derived)
+                    emitMoveEmptyValue(&m_thisRegister);
+                else
+                    emitCreateThis(&m_thisRegister);
+            } else if (constructorKind() != ConstructorKind::None) {
+                emitThrowTypeError("Cannot call a class constructor");
+            } else if (functionNode->usesThis() || codeBlock->usesEval()) {
+                m_codeBlock->addPropertyAccessInstruction(instructions().size());
+                emitOpcode(op_to_this);
+                instructions().append(kill(&m_thisRegister));
+                instructions().append(0);
+                instructions().append(0);
+            }
         }
         break;
     }
@@ -573,6 +569,12 @@
     // because a function's default parameter ExpressionNodes will use temporary registers.
     m_TDZStack.append(std::make_pair(*parentScopeTDZVariables, false));
     initializeDefaultParameterValuesAndSetupFunctionScopeStack(parameters, functionNode, functionSymbolTable, symbolTableConstantIndex, captures);
+    
+    // Loading |this| inside an arrow function must be done after initializeDefaultParameterValuesAndSetupFunctionScopeStack()
+    // because that function sets up the SymbolTable stack and emitLoadThisFromArrowFunctionLexicalEnvironment()
+    // consults the SymbolTable stack
+    if (parseMode == SourceParseMode::ArrowFunctionMode && functionNode->usesThis())
+        emitLoadThisFromArrowFunctionLexicalEnvironment();
 
     if (needsToUpdateArrowFunctionContext() && !codeBlock->isArrowFunction()) {
         initializeArrowFunctionContextScopeIfNeeded(functionSymbolTable);

Modified: trunk/Source/_javascript_Core/tests/stress/arrowfunction-lexical-bind-this-2.js (194339 => 194340)


--- trunk/Source/_javascript_Core/tests/stress/arrowfunction-lexical-bind-this-2.js	2015-12-21 20:26:54 UTC (rev 194339)
+++ trunk/Source/_javascript_Core/tests/stress/arrowfunction-lexical-bind-this-2.js	2015-12-21 20:40:04 UTC (rev 194340)
@@ -1,11 +1,11 @@
 var testCase = function (actual, expected, message) {
-  if (actual !== expected) {
-    throw message + ". Expected '" + expected + "', but was '" + actual + "'";
-  }
+    if (actual !== expected) {
+        throw message + ". Expected '" + expected + "', but was '" + actual + "'";
+    }
 };
 
 var functionConstructor = function () {
-  this.func = () => this;
+    this.func = () => this;
 };
 
 var instance = new functionConstructor();
@@ -13,9 +13,9 @@
 testCase(instance.func() === instance, true, "Error: this is not lexically binded inside of the arrow function #2");
 
 var obj = {
-  method: function () {
-    return () => this;
-  }
+    method: function () {
+        return () => this;
+    }
 };
 
 noInline(obj.method);
@@ -37,3 +37,40 @@
 for (var i=0; i < 10000; i++) {
     testCase(real.borrow()() === real, true, "Error: this is not lexically binded inside of the arrow function #5");
 }
+
+// Force create the lexical env inside of arrow function
+
+var functionConstructorWithEval = function () {
+    this._id = 'old-value';
+    this.func = () => {
+        var f;
+        eval('10==10');
+        this._id = 'new-value';
+        return this._id;
+    };
+};
+
+var arrowWithEval = new functionConstructorWithEval();
+
+for (var i=0; i < 10000; i++) {
+    testCase(arrowWithEval.func() === 'new-value', true, "Error: this is not lexically binded inside of the arrow function #6");
+}
+
+function foo() {
+    let arr = () => {
+        var x = 123;
+        function bas() {
+            return x;
+        };
+        this._id = '12345';
+        return bas();
+    };
+    this.arr = arr;
+};
+
+var fooObject = new foo();
+
+for (var i=0; i < 10000; i++) {
+    testCase(fooObject.arr() === 123, true, "Error: this is not lexically binded inside of the arrow function #7");
+    testCase(fooObject._id === '12345', true, "Error: this is not lexically binded inside of the arrow function #8");
+}
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to