Title: [256846] trunk
Revision
256846
Author
ross.kirsl...@sony.com
Date
2020-02-18 12:01:20 -0800 (Tue, 18 Feb 2020)

Log Message

[JSC] Computed function properties compute their keys twice
https://bugs.webkit.org/show_bug.cgi?id=207297

Reviewed by Keith Miller.

JSTests:

* stress/computed-property-key-side-effects.js: Added.
* test262/expectations.yaml: Mark 6 test cases as passing.

Source/_javascript_Core:

If a pseudo-String is used as the key of a computed function property,
any side effects from resolving the string value occur in duplicate.

The cause has two parts:
  - We aren't ensuring that the string value is resolved before doing SetFunctionName and PutByVal.
  - Our implementation of SetFunctionName (https://tc39.es/ecma262/#sec-setfunctionname)
    calls toString on a non-symbol argument, instead of assuming the type is a string.

* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::shouldSetFunctionName): Added.
(JSC::BytecodeGenerator::emitSetFunctionName): Added.
(JSC::BytecodeGenerator::emitSetFunctionNameIfNeededImpl): Deleted.
(JSC::BytecodeGenerator::emitSetFunctionNameIfNeeded): Deleted.
* bytecompiler/BytecodeGenerator.h:
Split the "if needed" logic out into its own function.

* bytecompiler/NodesCodegen.cpp:
(JSC::PropertyListNode::emitBytecode):
(JSC::PropertyListNode::emitPutConstantProperty):
(JSC::DefineFieldNode::emitBytecode):
Never emit OpSetFunctionName for a name of unknown type.
(But also, don't perform a needless ToPropertyKey for non-function computed property keys.)

* runtime/JSFunction.cpp:
(JSC::JSFunction::setFunctionName):
Don't call toString, assert isString.

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (256845 => 256846)


--- trunk/JSTests/ChangeLog	2020-02-18 19:55:39 UTC (rev 256845)
+++ trunk/JSTests/ChangeLog	2020-02-18 20:01:20 UTC (rev 256846)
@@ -1,3 +1,13 @@
+2020-02-18  Ross Kirsling  <ross.kirsl...@sony.com>
+
+        [JSC] Computed function properties compute their keys twice
+        https://bugs.webkit.org/show_bug.cgi?id=207297
+
+        Reviewed by Keith Miller.
+
+        * stress/computed-property-key-side-effects.js: Added.
+        * test262/expectations.yaml: Mark 6 test cases as passing.
+
 2020-02-17  Yusuke Suzuki  <ysuz...@apple.com>
 
         [JSC] JITThunk should be HashSet<Weak<NativeExecutable>> with appropriate GC weakness handling

Added: trunk/JSTests/stress/computed-property-key-side-effects.js (0 => 256846)


--- trunk/JSTests/stress/computed-property-key-side-effects.js	                        (rev 0)
+++ trunk/JSTests/stress/computed-property-key-side-effects.js	2020-02-18 20:01:20 UTC (rev 256846)
@@ -0,0 +1,38 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error(`expected ${expected} but got ${actual}`);
+}
+
+let count;
+const key1 = { toString() { count++; return 'foo'; } };
+const key2 = { toString: null, valueOf() { count++; return 'foo'; } };
+
+function test() {
+  count = 0;
+
+  ({ [key1]() { return 'bar'; } });
+  shouldBe(count, 1);
+  ({ [key1]: function () { return 'bar'; } });
+  shouldBe(count, 2);
+  ({ [key1]: () => 'bar' });
+  shouldBe(count, 3);
+  ({ get [key1]() { return 'bar'; } });
+  shouldBe(count, 4);
+  ({ set [key1](_) {} });
+  shouldBe(count, 5);
+
+  ({ [key2]() { return 'bar'; } });
+  shouldBe(count, 6);
+  ({ [key2]: function () { return 'bar'; } });
+  shouldBe(count, 7);
+  ({ [key2]: () => 'bar' });
+  shouldBe(count, 8);
+  ({ get [key2]() { return 'bar'; } });
+  shouldBe(count, 9);
+  ({ set [key2](_) {} });
+  shouldBe(count, 10);
+}
+noInline(test);
+
+for (let i = 0; i < 1e5; i++)
+  test();

Modified: trunk/JSTests/test262/expectations.yaml (256845 => 256846)


--- trunk/JSTests/test262/expectations.yaml	2020-02-18 19:55:39 UTC (rev 256845)
+++ trunk/JSTests/test262/expectations.yaml	2020-02-18 20:01:20 UTC (rev 256846)
@@ -2203,15 +2203,6 @@
   default: 'Test262: This statement should not be evaluated.'
 test/language/block-scope/syntax/redeclaration/var-redeclaration-attempt-after-generator.js:
   default: 'Test262: This statement should not be evaluated.'
-test/language/computed-property-names/to-name-side-effects/class.js:
-  default: 'Test262Error: Expected [0, 1] and [0] to have the same contents. order set for key1'
-  strict mode: 'Test262Error: Expected [0, 1] and [0] to have the same contents. order set for key1'
-test/language/computed-property-names/to-name-side-effects/numbers-class.js:
-  default: 'Test262Error: Expected [0, 1] and [0] to have the same contents. order set for key1'
-  strict mode: 'Test262Error: Expected [0, 1] and [0] to have the same contents. order set for key1'
-test/language/computed-property-names/to-name-side-effects/object.js:
-  default: 'Test262Error: The result of `counter++` is `0` Expected SameValue(«1», «0») to be true'
-  strict mode: 'Test262Error: The result of `counter++` is `0` Expected SameValue(«1», «0») to be true'
 test/language/eval-code/direct/new.target-arrow.js:
   default: 'Test262Error: Expected SameValue(«function ReferenceError() {'
   strict mode: 'Test262Error: Expected SameValue(«function ReferenceError() {'

Modified: trunk/Source/_javascript_Core/ChangeLog (256845 => 256846)


--- trunk/Source/_javascript_Core/ChangeLog	2020-02-18 19:55:39 UTC (rev 256845)
+++ trunk/Source/_javascript_Core/ChangeLog	2020-02-18 20:01:20 UTC (rev 256846)
@@ -1,3 +1,37 @@
+2020-02-18  Ross Kirsling  <ross.kirsl...@sony.com>
+
+        [JSC] Computed function properties compute their keys twice
+        https://bugs.webkit.org/show_bug.cgi?id=207297
+
+        Reviewed by Keith Miller.
+
+        If a pseudo-String is used as the key of a computed function property,
+        any side effects from resolving the string value occur in duplicate.
+
+        The cause has two parts:
+          - We aren't ensuring that the string value is resolved before doing SetFunctionName and PutByVal.
+          - Our implementation of SetFunctionName (https://tc39.es/ecma262/#sec-setfunctionname)
+            calls toString on a non-symbol argument, instead of assuming the type is a string.
+
+        * bytecompiler/BytecodeGenerator.cpp:
+        (JSC::BytecodeGenerator::shouldSetFunctionName): Added.
+        (JSC::BytecodeGenerator::emitSetFunctionName): Added.
+        (JSC::BytecodeGenerator::emitSetFunctionNameIfNeededImpl): Deleted.
+        (JSC::BytecodeGenerator::emitSetFunctionNameIfNeeded): Deleted.
+        * bytecompiler/BytecodeGenerator.h:
+        Split the "if needed" logic out into its own function.
+
+        * bytecompiler/NodesCodegen.cpp:
+        (JSC::PropertyListNode::emitBytecode):
+        (JSC::PropertyListNode::emitPutConstantProperty):
+        (JSC::DefineFieldNode::emitBytecode):
+        Never emit OpSetFunctionName for a name of unknown type.
+        (But also, don't perform a needless ToPropertyKey for non-function computed property keys.)
+
+        * runtime/JSFunction.cpp:
+        (JSC::JSFunction::setFunctionName):
+        Don't call toString, assert isString.
+
 2020-02-17  Yusuke Suzuki  <ysuz...@apple.com>
 
         [JSC] JITThunk should be HashSet<Weak<NativeExecutable>> with appropriate GC weakness handling

Modified: trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.cpp (256845 => 256846)


--- trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.cpp	2020-02-18 19:55:39 UTC (rev 256845)
+++ trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.cpp	2020-02-18 20:01:20 UTC (rev 256846)
@@ -3076,39 +3076,40 @@
     return dst;
 }
 
-template<typename LazyNameRegisterFn>
-void BytecodeGenerator::emitSetFunctionNameIfNeededImpl(ExpressionNode* valueNode, RegisterID* value, const LazyNameRegisterFn& lazyNameFn)
+bool BytecodeGenerator::shouldSetFunctionName(ExpressionNode* node)
 {
-    if (valueNode->isBaseFuncExprNode()) {
-        FunctionMetadataNode* metadata = static_cast<BaseFuncExprNode*>(valueNode)->metadata();
+    if (node->isBaseFuncExprNode()) {
+        FunctionMetadataNode* metadata = static_cast<BaseFuncExprNode*>(node)->metadata();
         if (!metadata->ecmaName().isNull())
-            return;
-    } else if (valueNode->isClassExprNode()) {
-        ClassExprNode* classExprNode = static_cast<ClassExprNode*>(valueNode);
+            return false;
+    } else if (node->isClassExprNode()) {
+        ClassExprNode* classExprNode = static_cast<ClassExprNode*>(node);
         if (!classExprNode->ecmaName().isNull())
-            return;
+            return false;
         if (classExprNode->hasStaticProperty(m_vm.propertyNames->name))
-            return;
+            return false;
     } else
-        return;
+        return false;
 
-    RegisterID* name = lazyNameFn();
+    return true;
+}
 
+void BytecodeGenerator::emitSetFunctionName(RegisterID* value, const Identifier& ident)
+{
+    RefPtr<RegisterID> name = emitLoad(newTemporary(), ident);
+
     // FIXME: We should use an op_call to an internal function here instead.
     // https://bugs.webkit.org/show_bug.cgi?id=155547
     OpSetFunctionName::emit(this, value, name);
 }
 
-void BytecodeGenerator::emitSetFunctionNameIfNeeded(ExpressionNode* valueNode, RegisterID* value, const Identifier& ident)
+void BytecodeGenerator::emitSetFunctionName(RegisterID* value, RegisterID* name)
 {
-    emitSetFunctionNameIfNeededImpl(valueNode, value, [=]() { return emitLoad(newTemporary(), ident); });
+    // FIXME: We should use an op_call to an internal function here instead.
+    // https://bugs.webkit.org/show_bug.cgi?id=155547
+    OpSetFunctionName::emit(this, value, name);
 }
 
-void BytecodeGenerator::emitSetFunctionNameIfNeeded(ExpressionNode* valueNode, RegisterID* value, RegisterID* name)
-{
-    emitSetFunctionNameIfNeededImpl(valueNode, value, [=]() { return name; });
-}
-
 RegisterID* BytecodeGenerator::emitCall(RegisterID* dst, RegisterID* func, ExpectedFunction expectedFunction, CallArguments& callArguments, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd, DebuggableCall debuggableCall)
 {
     return emitCall<OpCall>(dst, func, expectedFunction, callArguments, divot, divotStart, divotEnd, debuggableCall);

Modified: trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.h (256845 => 256846)


--- trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.h	2020-02-18 19:55:39 UTC (rev 256845)
+++ trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.h	2020-02-18 20:01:20 UTC (rev 256846)
@@ -753,8 +753,9 @@
         RegisterID* emitNewMethodDefinition(RegisterID* dst, MethodDefinitionNode*);
         RegisterID* emitNewRegExp(RegisterID* dst, RegExp*);
 
-        void emitSetFunctionNameIfNeeded(ExpressionNode* valueNode, RegisterID* value, RegisterID* name);
-        void emitSetFunctionNameIfNeeded(ExpressionNode* valueNode, RegisterID* value, const Identifier&);
+        bool shouldSetFunctionName(ExpressionNode*);
+        void emitSetFunctionName(RegisterID* value, RegisterID* name);
+        void emitSetFunctionName(RegisterID* value, const Identifier&);
 
         RegisterID* moveLinkTimeConstant(RegisterID* dst, LinkTimeConstant);
         RegisterID* moveEmptyValue(RegisterID* dst);
@@ -979,9 +980,6 @@
         bool emitReturnViaFinallyIfNeeded(RegisterID* returnRegister);
         void emitFinallyCompletion(FinallyContext&, Label& normalCompletionLabel);
 
-        template<typename LazyNameRegisterFn>
-        void emitSetFunctionNameIfNeededImpl(ExpressionNode*, RegisterID*, const LazyNameRegisterFn&);
-
     public:
         void pushFinallyControlFlowScope(FinallyContext&);
         void popFinallyControlFlowScope();

Modified: trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp (256845 => 256846)


--- trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp	2020-02-18 19:55:39 UTC (rev 256845)
+++ trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp	2020-02-18 20:01:20 UTC (rev 256846)
@@ -644,7 +644,10 @@
                 // Computed accessors.
                 if (node->m_type & PropertyNode::Computed) {
                     RefPtr<RegisterID> propertyName = generator.emitNode(node->m_expression);
-                    generator.emitSetFunctionNameIfNeeded(node->m_assign, value.get(), propertyName.get());
+                    if (generator.shouldSetFunctionName(node->m_assign)) {
+                        propertyName = generator.emitToPropertyKey(generator.newTemporary(), propertyName.get());
+                        generator.emitSetFunctionName(value.get(), propertyName.get());
+                    }
                     if (node->m_type & PropertyNode::Getter)
                         generator.emitPutGetterByVal(dst, propertyName.get(), attributes, value.get());
                     else
@@ -722,7 +725,10 @@
         else
             propertyNameRegister = generator.emitNode(node.m_expression);
 
-        generator.emitSetFunctionNameIfNeeded(node.m_assign, value.get(), propertyNameRegister.get());
+        if (generator.shouldSetFunctionName(node.m_assign)) {
+            propertyNameRegister = generator.emitToPropertyKey(generator.newTemporary(), propertyNameRegister.get());
+            generator.emitSetFunctionName(value.get(), propertyNameRegister.get());
+        }
         generator.emitCallDefineProperty(newObj, propertyNameRegister.get(), value.get(), nullptr, nullptr, BytecodeGenerator::PropertyConfigurable | BytecodeGenerator::PropertyWritable, m_position);
         return;
     }
@@ -738,7 +744,10 @@
         return;
     }
     RefPtr<RegisterID> propertyName = generator.emitNode(node.m_expression);
-    generator.emitSetFunctionNameIfNeeded(node.m_assign, value.get(), propertyName.get());
+    if (generator.shouldSetFunctionName(node.m_assign)) {
+        propertyName = generator.emitToPropertyKey(generator.newTemporary(), propertyName.get());
+        generator.emitSetFunctionName(value.get(), propertyName.get());
+    }
     generator.emitDirectPutByVal(newObj, propertyName.get(), value.get());
 }
 
@@ -4325,8 +4334,8 @@
         generator.emitLoad(value.get(), jsUndefined());
     else {
         generator.emitNode(value.get(), m_assign);
-        if (m_ident)
-            generator.emitSetFunctionNameIfNeeded(m_assign, value.get(), *m_ident);
+        if (m_ident && generator.shouldSetFunctionName(m_assign))
+            generator.emitSetFunctionName(value.get(), *m_ident);
     }
 
     switch (m_type) {

Modified: trunk/Source/_javascript_Core/runtime/JSFunction.cpp (256845 => 256846)


--- trunk/Source/_javascript_Core/runtime/JSFunction.cpp	2020-02-18 19:55:39 UTC (rev 256845)
+++ trunk/Source/_javascript_Core/runtime/JSFunction.cpp	2020-02-18 20:01:20 UTC (rev 256846)
@@ -766,10 +766,9 @@
         else
             name = makeString('[', String(&uid), ']');
     } else {
-        JSString* jsStr = value.toString(globalObject);
+        ASSERT(value.isString());
+        name = asString(value)->value(globalObject);
         RETURN_IF_EXCEPTION(scope, void());
-        name = jsStr->value(globalObject);
-        RETURN_IF_EXCEPTION(scope, void());
     }
     reifyName(vm, globalObject, name);
 }
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to