Function.prototype.toString is incorrect for ArrowFunction
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 25 Aug 2015 19:10:29 +0000 (19:10 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 25 Aug 2015 19:10:29 +0000 (19:10 +0000)
https://bugs.webkit.org/show_bug.cgi?id=148148

Source/JavaScriptCore:

Patch by Aleksandr Skachkov <gskachkov@gmail.com> on 2015-08-25
Reviewed by Saam Barati.

Added correct support of toString() method for arrow function.

* parser/ASTBuilder.h:
(JSC::ASTBuilder::createFunctionMetadata):
(JSC::ASTBuilder::createArrowFunctionExpr):
* parser/Nodes.cpp:
(JSC::FunctionMetadataNode::FunctionMetadataNode):
* parser/Nodes.h:
* parser/Parser.cpp:
(JSC::Parser<LexerType>::parseFunctionBody):
(JSC::Parser<LexerType>::parseFunctionInfo):
* parser/SyntaxChecker.h:
(JSC::SyntaxChecker::createFunctionMetadata):
* runtime/FunctionPrototype.cpp:
(JSC::functionProtoFuncToString):
* tests/stress/arrowfunction-tostring.js: Added.

LayoutTests:

Patch by Skachkov Oleksandr <gskachkov@gmail.com> on 2015-08-25
Reviewed by Saam Barati.

Added test of toString() method.

* js/arrowfunction-tostring-expected.txt: Added.
* js/arrowfunction-tostring.html: Added.
* js/script-tests/arrowfunction-tostring.js: Added.

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

12 files changed:
LayoutTests/ChangeLog
LayoutTests/js/arrowfunction-tostring-expected.txt [new file with mode: 0644]
LayoutTests/js/arrowfunction-tostring.html [new file with mode: 0644]
LayoutTests/js/script-tests/arrowfunction-tostring.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/parser/ASTBuilder.h
Source/JavaScriptCore/parser/Nodes.cpp
Source/JavaScriptCore/parser/Nodes.h
Source/JavaScriptCore/parser/Parser.cpp
Source/JavaScriptCore/parser/SyntaxChecker.h
Source/JavaScriptCore/runtime/FunctionPrototype.cpp
Source/JavaScriptCore/tests/stress/arrowfunction-tostring.js [new file with mode: 0644]

index 5563ac5..9915640 100644 (file)
@@ -1,3 +1,16 @@
+2015-08-25  Skachkov Oleksandr  <gskachkov@gmail.com>
+
+        Function.prototype.toString is incorrect for ArrowFunction
+        https://bugs.webkit.org/show_bug.cgi?id=148148
+
+        Reviewed by Saam Barati.
+
+        Added test of toString() method.
+
+        * js/arrowfunction-tostring-expected.txt: Added.
+        * js/arrowfunction-tostring.html: Added.
+        * js/script-tests/arrowfunction-tostring.js: Added.
+
 2015-08-25  Myles C. Maxfield  <mmaxfield@apple.com>
 
         Test gardening
diff --git a/LayoutTests/js/arrowfunction-tostring-expected.txt b/LayoutTests/js/arrowfunction-tostring-expected.txt
new file mode 100644 (file)
index 0000000..fe9a4d5
--- /dev/null
@@ -0,0 +1,32 @@
+Tests for ES6 arrow function toString() method
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+var simpleArrowFunction = () => {}
+PASS simpleArrowFunction.toString() is '() => {}'
+PASS ((x) => { x + 1 }).toString() is '(x) => { x + 1 }'
+PASS (x => x + 1).toString() is 'x => x + 1'
+var f0 = x => x
+PASS f0.toString() is 'x => x'
+var f1 = () => this
+PASS f1.toString() is '() => this'
+var f2 = x => { return x; };
+PASS f2.toString() is '(x) => { return x; }'
+var f3 = (x, y) => { return x + y; };
+PASS f3.toString() is '(x, y) => { return x + y; }'
+function foo(x) { return x.toString()};
+PASS foo((x)=>x) is '(x)=>x'
+var a = z => z*2, b = () => ({});
+PASS a.toString() is 'z => z*2'
+PASS b.toString() is '() => ({})'
+var arrExpr = [y=>y + 1, x=>x];
+PASS arrExpr[0].toString() is 'y=>y + 1'
+PASS arrExpr[1].toString() is 'x=>x'
+var arrBody  = [y=>{ y + 1 }, x=>{ x }];
+PASS arrBody[0].toString() is 'y=>{ y + 1 }'
+PASS arrBody[1].toString() is 'x=>{ x }'
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/js/arrowfunction-tostring.html b/LayoutTests/js/arrowfunction-tostring.html
new file mode 100644 (file)
index 0000000..49f8b0c
--- /dev/null
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src="../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script src="script-tests/arrowfunction-tostring.js"></script>
+<script src="../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/js/script-tests/arrowfunction-tostring.js b/LayoutTests/js/script-tests/arrowfunction-tostring.js
new file mode 100644 (file)
index 0000000..b89ce40
--- /dev/null
@@ -0,0 +1,45 @@
+// Inspired by mozilla tests
+description('Tests for ES6 arrow function toString() method');
+
+debug('var simpleArrowFunction = () => {}');
+var simpleArrowFunction = () => {};
+shouldBe("simpleArrowFunction.toString()", "'() => {}'");
+shouldBe("((x) => { x + 1 }).toString()", "'(x) => { x + 1 }'");
+shouldBe("(x => x + 1).toString()", "'x => x + 1'");
+
+debug('var f0 = x => x');
+var f0 = x => x;
+shouldBe("f0.toString()", "'x => x'");
+
+debug('var f1 = () => this');
+var f1 = () => this;
+shouldBe("f1.toString()", "'() => this'");
+
+debug('var f2 = x => { return x; };');
+var f2 = (x) => { return x; };
+shouldBe("f2.toString()", "'(x) => { return x; }'");
+
+debug('var f3 = (x, y) => { return x + y; };');
+var f3 = (x, y) => { return x + y; };
+shouldBe("f3.toString()", "'(x, y) => { return x + y; }'");
+
+function foo(x) { return x.toString()};
+debug('function foo(x) { return x.toString()};');
+shouldBe("foo((x)=>x)", "'(x)=>x'");
+
+var a = z => z*2, b = () => ({});
+debug('var a = z => z*2, b = () => ({});');
+shouldBe("a.toString()", "'z => z*2'");
+shouldBe("b.toString()", "'() => ({})'");
+
+var arrExpr = [y=>y + 1, x=>x];
+debug('var arrExpr = [y=>y + 1, x=>x];');
+shouldBe("arrExpr[0].toString()", "'y=>y + 1'");
+shouldBe("arrExpr[1].toString()", "'x=>x'");
+
+var arrBody  = [y=>{ y + 1 }, x=>{ x }];
+debug('var arrBody  = [y=>{ y + 1 }, x=>{ x }];');
+shouldBe("arrBody[0].toString()", "'y=>{ y + 1 }'");
+shouldBe("arrBody[1].toString()", "'x=>{ x }'");
+
+var successfullyParsed = true;
index 3623a49..75a294e 100644 (file)
@@ -1,3 +1,27 @@
+2015-08-25 Aleksandr Skachkov   <gskachkov@gmail.com>
+
+        Function.prototype.toString is incorrect for ArrowFunction
+        https://bugs.webkit.org/show_bug.cgi?id=148148
+
+        Reviewed by Saam Barati.
+        
+        Added correct support of toString() method for arrow function.
+
+        * parser/ASTBuilder.h:
+        (JSC::ASTBuilder::createFunctionMetadata):
+        (JSC::ASTBuilder::createArrowFunctionExpr):
+        * parser/Nodes.cpp:
+        (JSC::FunctionMetadataNode::FunctionMetadataNode):
+        * parser/Nodes.h:
+        * parser/Parser.cpp:
+        (JSC::Parser<LexerType>::parseFunctionBody):
+        (JSC::Parser<LexerType>::parseFunctionInfo):
+        * parser/SyntaxChecker.h:
+        (JSC::SyntaxChecker::createFunctionMetadata):
+        * runtime/FunctionPrototype.cpp:
+        (JSC::functionProtoFuncToString):
+        * tests/stress/arrowfunction-tostring.js: Added.
+
 2015-08-25  Saam barati  <sbarati@apple.com>
 
         Callee can be incorrectly overridden when it's captured
index 3853839..5b099ec 100644 (file)
@@ -361,18 +361,18 @@ public:
         const JSTokenLocation& startLocation, const JSTokenLocation& endLocation, 
         unsigned startColumn, unsigned endColumn, int functionKeywordStart, 
         int functionNameStart, int parametersStart, bool inStrictContext, 
-        ConstructorKind constructorKind, unsigned parameterCount, SourceParseMode mode, bool isArrowFunction)
+        ConstructorKind constructorKind, unsigned parameterCount, SourceParseMode mode, bool isArrowFunction, bool isArrowFunctionBodyExpression)
     {
         return new (m_parserArena) FunctionMetadataNode(
             m_parserArena, startLocation, endLocation, startColumn, endColumn, 
             functionKeywordStart, functionNameStart, parametersStart, 
-            inStrictContext, constructorKind, parameterCount, mode, isArrowFunction);
+            inStrictContext, constructorKind, parameterCount, mode, isArrowFunction, isArrowFunctionBodyExpression);
     }
 
     ExpressionNode* createArrowFunctionExpr(const JSTokenLocation& location, const ParserFunctionInfo<ASTBuilder>& functionInfo)
     {
         usesThis();
-        SourceCode source = m_sourceCode->subExpression(functionInfo.startOffset, functionInfo.endOffset, functionInfo.startLine, functionInfo.bodyStartColumn);
+        SourceCode source = m_sourceCode->subExpression(functionInfo.startOffset, functionInfo.body->isArrowFunctionBodyExpression() ? functionInfo.endOffset - 1 : functionInfo.endOffset, functionInfo.startLine, functionInfo.bodyStartColumn);
         ArrowFuncExprNode* result = new (m_parserArena) ArrowFuncExprNode(location, *functionInfo.name, functionInfo.body, source);
         functionInfo.body->setLoc(functionInfo.startLine, functionInfo.endLine, location.startOffset, location.lineStartOffset);
         return result;
index 18f8455..ea37639 100644 (file)
@@ -151,7 +151,7 @@ FunctionMetadataNode::FunctionMetadataNode(
     ParserArena&, const JSTokenLocation& startLocation, 
     const JSTokenLocation& endLocation, unsigned startColumn, unsigned endColumn, 
     int functionKeywordStart, int functionNameStart, int parametersStart, bool isInStrictContext, 
-    ConstructorKind constructorKind, unsigned parameterCount, SourceParseMode mode, bool isArrowFunction)
+    ConstructorKind constructorKind, unsigned parameterCount, SourceParseMode mode, bool isArrowFunction, bool isArrowFunctionBodyExpression)
         : Node(endLocation)
         , m_startColumn(startColumn)
         , m_endColumn(endColumn)
@@ -164,6 +164,7 @@ FunctionMetadataNode::FunctionMetadataNode(
         , m_isInStrictContext(isInStrictContext)
         , m_constructorKind(static_cast<unsigned>(constructorKind))
         , m_isArrowFunction(isArrowFunction)
+        , m_isArrowFunctionBodyExpression(isArrowFunctionBodyExpression)
 {
     ASSERT(m_constructorKind == static_cast<unsigned>(constructorKind));
 }
index 5d0b74e..ed300e7 100644 (file)
@@ -1808,7 +1808,7 @@ namespace JSC {
             ParserArena&, const JSTokenLocation& start, const JSTokenLocation& end, 
             unsigned startColumn, unsigned endColumn, int functionKeywordStart, 
             int functionNameStart, int parametersStart, bool isInStrictContext, 
-            ConstructorKind, unsigned, SourceParseMode, bool isArrowFunction);
+            ConstructorKind, unsigned, SourceParseMode, bool isArrowFunction, bool isArrowFunctionBodyExpression);
 
         void finishParsing(const SourceCode&, const Identifier&, FunctionMode);
         
@@ -1835,6 +1835,7 @@ namespace JSC {
         bool isInStrictContext() const { return m_isInStrictContext; }
         ConstructorKind constructorKind() { return static_cast<ConstructorKind>(m_constructorKind); }
         bool isArrowFunction() const { return m_isArrowFunction; }
+        bool isArrowFunctionBodyExpression() const { return m_isArrowFunctionBodyExpression; }
 
         void setLoc(unsigned firstLine, unsigned lastLine, int startOffset, int lineStartOffset)
         {
@@ -1861,6 +1862,7 @@ namespace JSC {
         unsigned m_isInStrictContext : 1;
         unsigned m_constructorKind : 2;
         unsigned m_isArrowFunction : 1;
+        unsigned m_isArrowFunctionBodyExpression : 1;
     };
 
     class FunctionNode final : public ScopeNode {
index 326bfd2..8a93615 100644 (file)
@@ -1547,11 +1547,12 @@ template <class TreeBuilder> TreeFunctionBody Parser<LexerType>::parseFunctionBo
     ConstructorKind constructorKind, FunctionBodyType bodyType, unsigned parameterCount, SourceParseMode parseMode)
 {
     bool isArrowFunction = FunctionBodyType::StandardFunctionBodyBlock != bodyType;
-    if (bodyType == StandardFunctionBodyBlock || bodyType == ArrowFunctionBodyBlock) {
+    bool isArrowFunctionBodyExpression = bodyType == ArrowFunctionBodyExpression;
+    if (!isArrowFunctionBodyExpression) {
         next();
         if (match(CLOSEBRACE)) {
             unsigned endColumn = tokenColumn();
-            return context.createFunctionMetadata(startLocation, tokenLocation(), startColumn, endColumn, functionKeywordStart, functionNameStart, parametersStart, strictMode(), constructorKind, parameterCount, parseMode, isArrowFunction);
+            return context.createFunctionMetadata(startLocation, tokenLocation(), startColumn, endColumn, functionKeywordStart, functionNameStart, parametersStart, strictMode(), constructorKind, parameterCount, parseMode, isArrowFunction, isArrowFunctionBodyExpression);
         }
     }
 
@@ -1563,7 +1564,7 @@ template <class TreeBuilder> TreeFunctionBody Parser<LexerType>::parseFunctionBo
     else
         failIfFalse(parseSourceElements(syntaxChecker, CheckForStrictMode), bodyType == StandardFunctionBodyBlock ? "Cannot parse body of this function" : "Cannot parse body of this arrow function");
     unsigned endColumn = tokenColumn();
-    return context.createFunctionMetadata(startLocation, tokenLocation(), startColumn, endColumn, functionKeywordStart, functionNameStart, parametersStart, strictMode(), constructorKind, parameterCount, parseMode, isArrowFunction);
+    return context.createFunctionMetadata(startLocation, tokenLocation(), startColumn, endColumn, functionKeywordStart, functionNameStart, parametersStart, strictMode(), constructorKind, parameterCount, parseMode, isArrowFunction, isArrowFunctionBodyExpression);
 }
 
 static const char* stringForFunctionMode(SourceParseMode mode)
@@ -1763,7 +1764,7 @@ template <class TreeBuilder> bool Parser<LexerType>::parseFunctionInfo(TreeBuild
         functionInfo.body = context.createFunctionMetadata(
             startLocation, endLocation, functionInfo.bodyStartColumn, bodyEndColumn, 
             functionKeywordStart, functionNameStart, parametersStart, 
-            cachedInfo->strictMode, constructorKind, cachedInfo->parameterCount, mode, isArrowFunction);
+            cachedInfo->strictMode, constructorKind, cachedInfo->parameterCount, mode, isArrowFunction,  functionBodyType == ArrowFunctionBodyExpression);
         
         functionScope->restoreFromSourceProviderCache(cachedInfo);
         popScope(functionScope, TreeBuilder::NeedsFreeVariableInfo);
index 7a85dd2..d469ab8 100644 (file)
@@ -186,7 +186,7 @@ public:
     ClassExpression createClassExpr(const JSTokenLocation&, const Identifier&, ExpressionType, ExpressionType, PropertyList, PropertyList) { return ClassExpr; }
 #endif
     ExpressionType createFunctionExpr(const JSTokenLocation&, const ParserFunctionInfo<SyntaxChecker>&) { return FunctionExpr; }
-    int createFunctionMetadata(const JSTokenLocation&, const JSTokenLocation&, int, int, bool, int, int, int, ConstructorKind, unsigned, SourceParseMode, bool) { return FunctionBodyResult; }
+    int createFunctionMetadata(const JSTokenLocation&, const JSTokenLocation&, int, int, bool, int, int, int, ConstructorKind, unsigned, SourceParseMode, bool, bool) { return FunctionBodyResult; }
     ExpressionType createArrowFunctionExpr(const JSTokenLocation&, const ParserFunctionInfo<SyntaxChecker>&) { return FunctionExpr; }
     void setFunctionNameStart(int, int) { }
     int createArguments() { return ArgumentsResult; }
index d082b12..6b3a094 100644 (file)
@@ -88,10 +88,13 @@ EncodedJSValue JSC_HOST_CALL functionProtoFuncToString(ExecState* exec)
             return JSValue::encode(jsMakeNontrivialString(exec, "function ", function->name(exec), "() {\n    [native code]\n}"));
 
         FunctionExecutable* executable = function->jsExecutable();
+        
+        String functionHeader = executable->isArrowFunction() ? "" : "function ";
+        
         String source = executable->source().provider()->getRange(
             executable->parametersStartOffset(),
             executable->typeProfilingEndOffset() + 1); // Type profiling end offset is the character before the '}'.
-        return JSValue::encode(jsMakeNontrivialString(exec, "function ", function->name(exec), source));
+        return JSValue::encode(jsMakeNontrivialString(exec, functionHeader, function->name(exec), source));
     }
 
     if (thisValue.inherits(InternalFunction::info())) {
diff --git a/Source/JavaScriptCore/tests/stress/arrowfunction-tostring.js b/Source/JavaScriptCore/tests/stress/arrowfunction-tostring.js
new file mode 100644 (file)
index 0000000..6e94984
--- /dev/null
@@ -0,0 +1,20 @@
+var testCase = function (actual, expected, message) {
+  if (actual !== expected) {
+    throw message + ". Expected '" + expected + "', but was '" + actual + "'";
+  }
+};
+
+var af1 = () => {};
+var af2 = (a) => { a + 1 };
+var af3 = x => x + 1;
+var af4 = (x, y) => x + y;
+
+noInline(af1);
+noInline(af2);
+
+for (var i = 0; i < 10000; ++i) {
+  testCase(af1.toString(), '() => {}', "Error: Not correct toString in arrow function #1");
+  testCase(af2.toString(), '(a) => { a + 1 }', "Error: Not correct toString in arrow function #2");
+  testCase(af3.toString(), 'x => x + 1', "Error: Not correct toString in arrow function #3");
+  testCase(af4.toString(), '(x, y) => x + y', "Error: Not correct toString in arrow function #4");
+}