Title: [201122] trunk/Source/_javascript_Core
Revision
201122
Author
[email protected]
Date
2016-05-18 18:27:49 -0700 (Wed, 18 May 2016)

Log Message

Function with default parameter values that are arrow functions that capture this isn't working
https://bugs.webkit.org/show_bug.cgi?id=157786
<rdar://problem/26327329>

Reviewed by Geoffrey Garen.

To make the scopes ordered properly, I needed to initialize the arrow
function lexical environment before initializing default parameter values.
I also made the code easier to reason about by never reusing the function's
var lexical environment for the arrow function lexical environment. The
reason for this is that that code was wrong, and we just didn't have code to
that properly tested it. It was easy for that code to be wrong because
sometimes the function's lexical environment isn't the top-most scope
(namely, when a function's parameter list is non-simple) and sometimes
it is (when the function's parameter list is simple).

Also, because a function's default parameter values may capture the
'arguments' variable inside an arrow function, I needed to take care
to initialize the 'arguments' variable as part of whichever scope
is the top-most scope. It's either the function's var environment
if the parameter list is simple, or it's the function's parameter
environment if the parameter list is non-simple.

* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::BytecodeGenerator):
(JSC::BytecodeGenerator::initializeDefaultParameterValuesAndSetupFunctionScopeStack):
(JSC::BytecodeGenerator::initializeArrowFunctionContextScopeIfNeeded):
(JSC::BytecodeGenerator::initializeParameters):
(JSC::BytecodeGenerator::initializeVarLexicalEnvironment):
(JSC::BytecodeGenerator::visibleNameForParameter):
* bytecompiler/BytecodeGenerator.h:
* tests/stress/arrow-functions-as-default-parameter-values.js: Added.
(assert):
(test):
(test.foo):
* tests/stress/op-push-name-scope-crashes-profiler.js:
(test):

Modified Paths

Added Paths

Diff

Modified: trunk/Source/_javascript_Core/ChangeLog (201121 => 201122)


--- trunk/Source/_javascript_Core/ChangeLog	2016-05-19 01:01:21 UTC (rev 201121)
+++ trunk/Source/_javascript_Core/ChangeLog	2016-05-19 01:27:49 UTC (rev 201122)
@@ -1,3 +1,43 @@
+2016-05-18  Saam barati  <[email protected]>
+
+        Function with default parameter values that are arrow functions that capture this isn't working
+        https://bugs.webkit.org/show_bug.cgi?id=157786
+        <rdar://problem/26327329>
+
+        Reviewed by Geoffrey Garen.
+
+        To make the scopes ordered properly, I needed to initialize the arrow 
+        function lexical environment before initializing default parameter values.
+        I also made the code easier to reason about by never reusing the function's
+        var lexical environment for the arrow function lexical environment. The
+        reason for this is that that code was wrong, and we just didn't have code to
+        that properly tested it. It was easy for that code to be wrong because
+        sometimes the function's lexical environment isn't the top-most scope
+        (namely, when a function's parameter list is non-simple) and sometimes
+        it is (when the function's parameter list is simple).
+
+        Also, because a function's default parameter values may capture the
+        'arguments' variable inside an arrow function, I needed to take care
+        to initialize the 'arguments' variable as part of whichever scope
+        is the top-most scope. It's either the function's var environment
+        if the parameter list is simple, or it's the function's parameter
+        environment if the parameter list is non-simple.
+
+        * bytecompiler/BytecodeGenerator.cpp:
+        (JSC::BytecodeGenerator::BytecodeGenerator):
+        (JSC::BytecodeGenerator::initializeDefaultParameterValuesAndSetupFunctionScopeStack):
+        (JSC::BytecodeGenerator::initializeArrowFunctionContextScopeIfNeeded):
+        (JSC::BytecodeGenerator::initializeParameters):
+        (JSC::BytecodeGenerator::initializeVarLexicalEnvironment):
+        (JSC::BytecodeGenerator::visibleNameForParameter):
+        * bytecompiler/BytecodeGenerator.h:
+        * tests/stress/arrow-functions-as-default-parameter-values.js: Added.
+        (assert):
+        (test):
+        (test.foo):
+        * tests/stress/op-push-name-scope-crashes-profiler.js:
+        (test):
+
 2016-05-18  Michael Saboff  <[email protected]>
 
         r199812 broke test262

Modified: trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.cpp (201121 => 201122)


--- trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.cpp	2016-05-19 01:01:21 UTC (rev 201121)
+++ trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.cpp	2016-05-19 01:27:49 UTC (rev 201122)
@@ -321,15 +321,15 @@
         emitPushFunctionNameScope(functionNode->ident(), &m_calleeRegister, markAsCaptured);
     }
 
-    if (shouldCaptureSomeOfTheThings) {
+    if (shouldCaptureSomeOfTheThings)
         m_lexicalEnvironmentRegister = addVar();
-        // We can allocate the "var" environment if we don't have default parameter expressions. If we have
-        // default parameter expressions, we have to hold off on allocating the "var" environment because
-        // the parent scope of the "var" environment is the parameter environment.
-        if (isSimpleParameterList)
-            initializeVarLexicalEnvironment(symbolTableConstantIndex);
-    }
 
+    // We can allocate the "var" environment if we don't have default parameter expressions. If we have
+    // default parameter expressions, we have to hold off on allocating the "var" environment because
+    // the parent scope of the "var" environment is the parameter environment.
+    if (isSimpleParameterList)
+        initializeVarLexicalEnvironment(symbolTableConstantIndex, functionSymbolTable, shouldCaptureSomeOfTheThings);
+
     // Figure out some interesting facts about our arguments.
     bool capturesAnyArgumentByName = false;
     if (functionNode->hasCapturedVariables()) {
@@ -452,19 +452,6 @@
         instructions().append(m_argumentsRegister->index());
     }
     
-    for (FunctionMetadataNode* function : functionNode->functionStack()) {
-        const Identifier& ident = function->ident();
-        createVariable(ident, varKind(ident.impl()), functionSymbolTable);
-        m_functionsToInitialize.append(std::make_pair(function, NormalFunctionVariable));
-    }
-    for (auto& entry : functionNode->varDeclarations()) {
-        ASSERT(!entry.value.isLet() && !entry.value.isConst());
-        if (!entry.value.isVar()) // This is either a parameter or callee.
-            continue;
-        // Variables named "arguments" are never const.
-        createVariable(Identifier::fromUid(m_vm, entry.key.get()), varKind(entry.key.get()), functionSymbolTable, IgnoreExisting);
-    }
-
     // There are some variables that need to be preinitialized to something other than Undefined:
     //
     // - "arguments": unless it's used as a function or parameter, this should refer to the
@@ -486,6 +473,7 @@
     
     // This is our final act of weirdness. "arguments" is overridden by everything except the
     // callee. We add it to the symbol table if it's not already there and it's not an argument.
+    bool shouldCreateArgumentsVariableInParameterScope = false;
     if (needsArguments) {
         // If "arguments" is overridden by a function or destructuring parameter name, then it's
         // OK for us to call createVariable() because it won't change anything. It's also OK for
@@ -504,8 +492,10 @@
             }
         }
 
+        bool shouldCreateArgumensVariable = !haveParameterNamedArguments && !m_codeBlock->isArrowFunction();
+        shouldCreateArgumentsVariableInParameterScope = shouldCreateArgumensVariable && !isSimpleParameterList;
         // Do not create arguments variable in case of Arrow function. Value will be loaded from parent scope
-        if (!haveParameterNamedArguments && !m_codeBlock->isArrowFunction()) {
+        if (shouldCreateArgumensVariable && !shouldCreateArgumentsVariableInParameterScope) {
             createVariable(
                 propertyNames().arguments, varKind(propertyNames().arguments.impl()), functionSymbolTable);
 
@@ -513,6 +503,21 @@
         }
     }
 
+    for (FunctionMetadataNode* function : functionNode->functionStack()) {
+        const Identifier& ident = function->ident();
+        createVariable(ident, varKind(ident.impl()), functionSymbolTable);
+        m_functionsToInitialize.append(std::make_pair(function, NormalFunctionVariable));
+    }
+    for (auto& entry : functionNode->varDeclarations()) {
+        ASSERT(!entry.value.isLet() && !entry.value.isConst());
+        if (!entry.value.isVar()) // This is either a parameter or callee.
+            continue;
+        if (shouldCreateArgumentsVariableInParameterScope && entry.key.get() == propertyNames().arguments.impl())
+            continue;
+        createVariable(Identifier::fromUid(m_vm, entry.key.get()), varKind(entry.key.get()), functionSymbolTable, IgnoreExisting);
+    }
+
+
     m_newTargetRegister = addVar();
     switch (parseMode) {
     case SourceParseMode::GeneratorWrapperFunctionMode: {
@@ -569,10 +574,17 @@
             emitLoadNewTargetFromArrowFunctionLexicalEnvironment();
     }
 
+    if (needsToUpdateArrowFunctionContext() && !codeBlock->isArrowFunction()) {
+        initializeArrowFunctionContextScopeIfNeeded();
+        emitPutThisToArrowFunctionContextScope();
+        emitPutNewTargetToArrowFunctionContextScope();
+        emitPutDerivedConstructorToArrowFunctionContextScope();
+    }
+
     // All "addVar()"s needs to happen before "initializeDefaultParameterValuesAndSetupFunctionScopeStack()" is called
     // because a function's default parameter ExpressionNodes will use temporary registers.
     pushTDZVariables(*parentScopeTDZVariables, TDZCheckOptimization::DoNotOptimize);
-    initializeDefaultParameterValuesAndSetupFunctionScopeStack(parameters, isSimpleParameterList, functionNode, functionSymbolTable, symbolTableConstantIndex, captures);
+    initializeDefaultParameterValuesAndSetupFunctionScopeStack(parameters, isSimpleParameterList, functionNode, functionSymbolTable, symbolTableConstantIndex, captures, shouldCreateArgumentsVariableInParameterScope);
     
     // If we don't have  default parameter _expression_, then loading |this| inside an arrow function must be done
     // after initializeDefaultParameterValuesAndSetupFunctionScopeStack() because that function sets up the
@@ -585,13 +597,6 @@
             emitLoadNewTargetFromArrowFunctionLexicalEnvironment();
     }
     
-    if (needsToUpdateArrowFunctionContext() && !codeBlock->isArrowFunction()) {
-        initializeArrowFunctionContextScopeIfNeeded(functionSymbolTable);
-        emitPutThisToArrowFunctionContextScope();
-        emitPutNewTargetToArrowFunctionContextScope();
-        emitPutDerivedConstructorToArrowFunctionContextScope();
-    }
-
     bool shouldInitializeBlockScopedFunctions = false; // We generate top-level function declarations in ::generate().
     pushLexicalScope(m_scopeNode, TDZCheckOptimization::Optimize, NestedScopeType::IsNotNested, nullptr, shouldInitializeBlockScopedFunctions);
 }
@@ -805,9 +810,10 @@
 
 void BytecodeGenerator::initializeDefaultParameterValuesAndSetupFunctionScopeStack(
     FunctionParameters& parameters, bool isSimpleParameterList, FunctionNode* functionNode, SymbolTable* functionSymbolTable, 
-    int symbolTableConstantIndex, const std::function<bool (UniquedStringImpl*)>& captures)
+    int symbolTableConstantIndex, const std::function<bool (UniquedStringImpl*)>& captures, bool shouldCreateArgumentsVariableInParameterScope)
 {
     Vector<std::pair<Identifier, RefPtr<RegisterID>>> valuesToMoveIntoVars;
+    ASSERT(!(isSimpleParameterList && shouldCreateArgumentsVariableInParameterScope));
     if (!isSimpleParameterList) {
         // Refer to the ES6 spec section 9.2.12: http://www.ecma-international.org/ecma-262/6.0/index.html#sec-functiondeclarationinstantiation
         // This implements step 21.
@@ -815,6 +821,8 @@
         Vector<Identifier> allParameterNames; 
         for (unsigned i = 0; i < parameters.size(); i++)
             parameters.at(i).first->collectBoundIdentifiers(allParameterNames);
+        if (shouldCreateArgumentsVariableInParameterScope)
+            allParameterNames.append(propertyNames().arguments);
         IdentifierSet parameterSet;
         for (auto& ident : allParameterNames) {
             parameterSet.add(ident.impl());
@@ -823,10 +831,15 @@
             if (captures(ident.impl()))
                 addResult.iterator->value.setIsCaptured();
         }
-        
         // This implements step 25 of section 9.2.12.
         pushLexicalScopeInternal(environment, TDZCheckOptimization::Optimize, NestedScopeType::IsNotNested, nullptr, TDZRequirement::UnderTDZ, ScopeType::LetConstScope, ScopeRegisterType::Block);
 
+        if (shouldCreateArgumentsVariableInParameterScope) {
+            Variable argumentsVariable = variable(propertyNames().arguments); 
+            initializeVariable(argumentsVariable, m_argumentsRegister);
+            liftTDZCheckIfPossible(argumentsVariable);
+        }
+
         RefPtr<RegisterID> temp = newTemporary();
         for (unsigned i = 0; i < parameters.size(); i++) {
             std::pair<DestructuringPatternNode*, ExpressionNode*> parameter = parameters.at(i);
@@ -867,16 +880,10 @@
         // record for parameters and "var"s. The "var" environment record must have the
         // parameter environment record as its parent.
         // See step 28 of section 9.2.12.
-        if (m_lexicalEnvironmentRegister)
-            initializeVarLexicalEnvironment(symbolTableConstantIndex);
+        bool hasCapturedVariables = !!m_lexicalEnvironmentRegister; 
+        initializeVarLexicalEnvironment(symbolTableConstantIndex, functionSymbolTable, hasCapturedVariables);
     }
 
-    if (m_lexicalEnvironmentRegister)
-        pushScopedControlFlowContext();
-    m_symbolTableStack.append(SymbolTableStackEntry{ functionSymbolTable, m_lexicalEnvironmentRegister, false, symbolTableConstantIndex });
-
-    m_varScopeSymbolTableIndex = m_symbolTableStack.size() - 1;
-
     // This completes step 28 of section 9.2.12.
     for (unsigned i = 0; i < valuesToMoveIntoVars.size(); i++) {
         ASSERT(!isSimpleParameterList);
@@ -886,43 +893,16 @@
     }
 }
 
-void BytecodeGenerator::initializeArrowFunctionContextScopeIfNeeded(SymbolTable* symbolTable)
+void BytecodeGenerator::initializeArrowFunctionContextScopeIfNeeded()
 {
-    if (m_arrowFunctionContextLexicalEnvironmentRegister != nullptr)
-        return;
-    
-    if (m_lexicalEnvironmentRegister != nullptr) {
-        m_arrowFunctionContextLexicalEnvironmentRegister = m_lexicalEnvironmentRegister;
-        
-        if (!m_codeBlock->isArrowFunction()) {
-            ScopeOffset offset;
-            
-            ConcurrentJITLocker locker(ConcurrentJITLocker::NoLockingNecessary);
-            if (isThisUsedInInnerArrowFunction()) {
-                offset = symbolTable->takeNextScopeOffset(locker);
-                symbolTable->set(locker, propertyNames().thisIdentifier.impl(), SymbolTableEntry(VarOffset(offset)));
-            }
+    ASSERT(!m_arrowFunctionContextLexicalEnvironmentRegister);
 
-            if (m_codeType == FunctionCode && isNewTargetUsedInInnerArrowFunction()) {
-                offset = symbolTable->takeNextScopeOffset();
-                symbolTable->set(locker, propertyNames().newTargetLocalPrivateName.impl(), SymbolTableEntry(VarOffset(offset)));
-            }
-            
-            if (isConstructor() && constructorKind() == ConstructorKind::Derived && isSuperUsedInInnerArrowFunction()) {
-                offset = symbolTable->takeNextScopeOffset(locker);
-                symbolTable->set(locker, propertyNames().derivedConstructorPrivateName.impl(), SymbolTableEntry(VarOffset(offset)));
-            }
-        }
-
-        return;
-    }
-
     VariableEnvironment environment;
 
     if (isThisUsedInInnerArrowFunction()) {
         auto addResult = environment.add(propertyNames().thisIdentifier);
         addResult.iterator->value.setIsCaptured();
-        addResult.iterator->value.setIsConst();
+        addResult.iterator->value.setIsLet();
     }
     
     if (m_codeType == FunctionCode && isNewTargetUsedInInnerArrowFunction()) {
@@ -972,18 +952,25 @@
     }
 }
 
-void BytecodeGenerator::initializeVarLexicalEnvironment(int symbolTableConstantIndex)
+void BytecodeGenerator::initializeVarLexicalEnvironment(int symbolTableConstantIndex, SymbolTable* functionSymbolTable, bool hasCapturedVariables)
 {
-    RELEASE_ASSERT(m_lexicalEnvironmentRegister);
-    emitOpcode(op_create_lexical_environment);
-    instructions().append(m_lexicalEnvironmentRegister->index());
-    instructions().append(scopeRegister()->index());
-    instructions().append(symbolTableConstantIndex);
-    instructions().append(addConstantValue(jsUndefined())->index());
+    if (hasCapturedVariables) {
+        RELEASE_ASSERT(m_lexicalEnvironmentRegister);
+        emitOpcode(op_create_lexical_environment);
+        instructions().append(m_lexicalEnvironmentRegister->index());
+        instructions().append(scopeRegister()->index());
+        instructions().append(symbolTableConstantIndex);
+        instructions().append(addConstantValue(jsUndefined())->index());
 
-    emitOpcode(op_mov);
-    instructions().append(scopeRegister()->index());
-    instructions().append(m_lexicalEnvironmentRegister->index());
+        emitOpcode(op_mov);
+        instructions().append(scopeRegister()->index());
+        instructions().append(m_lexicalEnvironmentRegister->index());
+
+        pushScopedControlFlowContext();
+    }
+    bool isWithScope = false;
+    m_symbolTableStack.append(SymbolTableStackEntry{ functionSymbolTable, m_lexicalEnvironmentRegister, isWithScope, symbolTableConstantIndex });
+    m_varScopeSymbolTableIndex = m_symbolTableStack.size() - 1;
 }
 
 UniquedStringImpl* BytecodeGenerator::visibleNameForParameter(DestructuringPatternNode* pattern)

Modified: trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.h (201121 => 201122)


--- trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.h	2016-05-19 01:01:21 UTC (rev 201121)
+++ trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.h	2016-05-19 01:27:49 UTC (rev 201122)
@@ -856,9 +856,9 @@
         void emitLogShadowChickenTailIfNecessary();
 
         void initializeParameters(FunctionParameters&);
-        void initializeVarLexicalEnvironment(int symbolTableConstantIndex);
-        void initializeDefaultParameterValuesAndSetupFunctionScopeStack(FunctionParameters&, bool isSimpleParameterList, FunctionNode*, SymbolTable*, int symbolTableConstantIndex, const std::function<bool (UniquedStringImpl*)>& captures);
-        void initializeArrowFunctionContextScopeIfNeeded(SymbolTable* = nullptr);
+        void initializeVarLexicalEnvironment(int symbolTableConstantIndex, SymbolTable* functionSymbolTable, bool hasCapturedVariables);
+        void initializeDefaultParameterValuesAndSetupFunctionScopeStack(FunctionParameters&, bool isSimpleParameterList, FunctionNode*, SymbolTable*, int symbolTableConstantIndex, const std::function<bool (UniquedStringImpl*)>& captures, bool shouldCreateArgumentsVariableInParameterScope);
+        void initializeArrowFunctionContextScopeIfNeeded();
 
     public:
         JSString* addStringConstant(const Identifier&);

Added: trunk/Source/_javascript_Core/tests/stress/arrow-functions-as-default-parameter-values.js (0 => 201122)


--- trunk/Source/_javascript_Core/tests/stress/arrow-functions-as-default-parameter-values.js	                        (rev 0)
+++ trunk/Source/_javascript_Core/tests/stress/arrow-functions-as-default-parameter-values.js	2016-05-19 01:27:49 UTC (rev 201122)
@@ -0,0 +1,128 @@
+function assert(b) {
+    if (!b)
+        throw new Error("Bad assertion")
+}
+function test(f, n = 1000) {
+    for (let i = 0; i < n; i++)
+        f();
+}
+
+test(function() {
+    function foo(x = ()=>this) {
+        return x();
+    }
+    let o = {};
+    assert(foo.call(o) === o);
+})
+
+test(function() {
+    function foo(x = ()=>arguments) {
+        assert(x() === arguments);
+    }
+    foo();
+})
+
+test(function() {
+    function foo({x = ()=>arguments}) {
+        assert(x() === arguments);
+    }
+    foo({x:undefined});
+})
+
+test(function() {
+    function foo(x = ()=>arguments) {
+        let a = x();
+        assert(a.length === 3);
+        assert(a[0] === undefined);
+        assert(a[1] === 20);
+        assert(a[2] === 40);
+    }
+    foo(undefined, 20, 40);
+})
+
+test(function() {
+    function foo(x = ()=>new.target) {
+        assert(x() === foo);
+    }
+    new foo(undefined);
+})
+
+test(function() {
+    function foo({x = ()=>new.target}) {
+        assert(x() === foo);
+    }
+    new foo({});
+})
+
+test(function() {
+    function foo(x = ()=>arguments) {
+        var arguments;
+        assert(x() === arguments);
+    }
+    foo(undefined);
+});
+
+test(function() {
+    function foo(x = ()=>arguments) {
+        var arguments = 25;
+        assert(x() === arguments);
+    }
+    foo(undefined);
+});
+
+test(function() {
+    function foo(x = (y = ()=>arguments)=>y()) {
+        assert(x() === arguments);
+    }
+    foo(undefined);
+});
+
+test(function() {
+    function foo({x = (y = ()=>arguments)=>y()}) {
+        assert(x() === arguments);
+    }
+    foo({});
+});
+
+test(function() {
+    function foo(x = (y = ()=>this)=>y()) {
+        return x();
+    }
+    let o = {};
+    foo.call(o);
+});
+
+test(function() {
+    function foo(x = (y = ()=>new.target)=>y()) {
+        assert(x() === foo);
+    }
+    new foo();
+});
+
+test(function() {
+    function foo(x = (y = ()=>new.target)=>y()) {
+        assert(x() === undefined);
+    }
+    foo();
+});
+
+// FIXME: Our parser throws a syntax error here but it should not.
+// https://bugs.webkit.org/show_bug.cgi?id=157872
+/*
+test(function() {
+    class C {
+        constructor() { this._x = 45; }
+        get foo() { return this._x;}
+    }
+    class D extends C {
+        //constructor(x = ()=>super.foo) {
+        //    super();
+        //    assert(x() === 45);
+        //}
+        x(x = ()=>super.foo) {
+            return x();
+        }
+    }
+    //(new D).x();
+});
+*/

Modified: trunk/Source/_javascript_Core/tests/stress/op-push-name-scope-crashes-profiler.js (201121 => 201122)


--- trunk/Source/_javascript_Core/tests/stress/op-push-name-scope-crashes-profiler.js	2016-05-19 01:01:21 UTC (rev 201121)
+++ trunk/Source/_javascript_Core/tests/stress/op-push-name-scope-crashes-profiler.js	2016-05-19 01:27:49 UTC (rev 201122)
@@ -8,10 +8,10 @@
      })(arguments[0]);
 }
 
-for (var i = 0; i < 10000; ++i) {
+for (var i = 0; i < 1000; ++i) {
     counter = 0;
     test(100);
     if (counter !== 101) {
         throw "Oops, test(100) = " + test(100) + ", expected 101.";
     }
-}
\ No newline at end of file
+}
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to