"this" should be in TDZ until super is called in the constructor of a derived class
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 13 Mar 2015 01:11:15 +0000 (01:11 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 13 Mar 2015 01:11:15 +0000 (01:11 +0000)
https://bugs.webkit.org/show_bug.cgi?id=142527

Reviewed by Mark Hahnenberg.

DFG and FTL implementations co-authored by Filip Pizlo.

In ES6 class syntax, "this" register must be in the "temporal dead zone" (TDZ) and throw ReferenceError until
super() is called inside the constructor of a derived class.

Added op_check_tdz, a new OP code, which throws a reference error when the first operand is an empty value
to all tiers of JIT and LLint. The op code throws in the slow path on the basis that a TDZ error should be
a programming error and not a part of the programs' normal control flow. In DFG, this op code is represented
by a no-op must-generate node CheckNotEmpty modeled after CheckCell.

Also made the constructor of a derived class assign the empty value to "this" register rather than undefined
so that ThisNode can emit the op_check_tdz to check the initialized-ness of "this" in such a constructor.

* bytecode/BytecodeList.json: Added op_check_tdz.
* bytecode/BytecodeUseDef.h:
(JSC::computeUsesForBytecodeOffset): Ditto.
(JSC::computeDefsForBytecodeOffset): Ditto.
* bytecode/CodeBlock.cpp:
(JSC::CodeBlock::dumpBytecode): Ditto.
* bytecode/ExitKind.cpp:
(JSC::exitKindToString): Added TDZFailure.
* bytecode/ExitKind.h: Ditto.
* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::BytecodeGenerator): Assign the empty value to "this" register to indicate it's in TDZ.
(JSC::BytecodeGenerator::emitTDZCheck): Added.
(JSC::BytecodeGenerator::emitReturn): Emit the TDZ check since "this" can still be in TDZ if super() was never
called. e.g. class B extends A { constructor() { } }
* bytecompiler/BytecodeGenerator.h:
* bytecompiler/NodesCodegen.cpp:
(JSC::ThisNode::emitBytecode): Always emit the TDZ check if we're inside the constructor of a derived class.
We can't omit this check even if the result was ignored per spec.
* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects): Previously, empty value could never appear
in a local variable. This is no longer true so generalize this code. Also added the support for CheckNotEmpty.
Like CheckCell, we phantomize this DFG node in the constant folding phase if the type of the operand is already
found to be not empty. Otherwise filter out SpecEmpty.
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::parseBlock): Added op_check_tdz.
* dfg/DFGCapabilities.cpp:
(JSC::DFG::capabilityLevel): op_check_tdz can be compiled and inlined.
* dfg/DFGClobberize.h:
(JSC::DFG::clobberize): CheckNotEmpty doesn't read or write values.
* dfg/DFGConstantFoldingPhase.cpp:
(JSC::DFG::ConstantFoldingPhase::foldConstants): Convert CheckNotEmpty to a phantom if non-emptiness had already
been proven for the operand prior to this node.
* dfg/DFGDoesGC.cpp:
(JSC::DFG::doesGC): CheckNotEmpty does not trigger GC.
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode): CheckNotEmpty is a no-op in the fixup phase.
* dfg/DFGNodeType.h: CheckNotEmpty cannot be removed even if the result was ignored. See ThisNode::emitBytecode.
* dfg/DFGPredictionPropagationPhase.cpp:
(JSC::DFG::PredictionPropagationPhase::propagate): CheckNotEmpty doesn't return any value.
* dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute): CheckNotEmpty doesn't load from heap so it's safe.
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile): Speculative the operand to be not empty. OSR exit if the speculation fails.
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile): Ditto.
* ftl/FTLCapabilities.cpp:
(JSC::FTL::canCompile): CheckNotEmpty can be compiled in FTL.
* ftl/FTLLowerDFGToLLVM.cpp:
(JSC::FTL::LowerDFGToLLVM::compileNode): Calls compileCheckNotEmpty for CheckNotEmpty.
(JSC::FTL::LowerDFGToLLVM::compileCheckNotEmpty): OSR exit with "TDZFailure" if the operand is not empty.
* jit/JIT.cpp:
(JSC::JIT::privateCompileMainPass): Added op_check_tdz.
(JSC::JIT::privateCompileSlowCases): Ditto.
* jit/JIT.h:
* jit/JITOpcodes.cpp:
(JSC::JIT::emit_op_check_tdz): Implements op_check_tdz in Baseline JIT.
(JSC::JIT::emitSlow_op_check_tdz): Ditto.
* jit/JITOpcodes32_64.cpp:
(JSC::JIT::emit_op_check_tdz): Ditto.
(JSC::JIT::emitSlow_op_check_tdz): Ditto.
* llint/LowLevelInterpreter32_64.asm: Implements op_check_tdz in LLint.
* llint/LowLevelInterpreter64.asm: Ditto.
* runtime/CommonSlowPaths.cpp:
(JSC::SLOW_PATH_DECL): Throws a reference error for op_check_tdz. Shared by LLint and Baseline JIT.
* runtime/CommonSlowPaths.h:
* tests/stress/class-syntax-no-loop-tdz.js: Added.
* tests/stress/class-syntax-no-tdz-in-catch.js: Added.
* tests/stress/class-syntax-no-tdz-in-conditional.js: Added.
* tests/stress/class-syntax-no-tdz-in-loop-no-inline-super.js: Added.
* tests/stress/class-syntax-no-tdz-in-loop.js: Added.
* tests/stress/class-syntax-no-tdz.js: Added.
* tests/stress/class-syntax-tdz-in-catch.js: Added.
* tests/stress/class-syntax-tdz-in-conditional.js: Added.
* tests/stress/class-syntax-tdz-in-loop.js: Added.
* tests/stress/class-syntax-tdz.js: Added.

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

41 files changed:
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecode/BytecodeList.json
Source/JavaScriptCore/bytecode/BytecodeUseDef.h
Source/JavaScriptCore/bytecode/CodeBlock.cpp
Source/JavaScriptCore/bytecode/ExitKind.cpp
Source/JavaScriptCore/bytecode/ExitKind.h
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h
Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
Source/JavaScriptCore/dfg/DFGCapabilities.cpp
Source/JavaScriptCore/dfg/DFGClobberize.h
Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
Source/JavaScriptCore/dfg/DFGDoesGC.cpp
Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
Source/JavaScriptCore/dfg/DFGNodeType.h
Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
Source/JavaScriptCore/dfg/DFGSafeToExecute.h
Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
Source/JavaScriptCore/ftl/FTLCapabilities.cpp
Source/JavaScriptCore/ftl/FTLLowerDFGToLLVM.cpp
Source/JavaScriptCore/jit/JIT.cpp
Source/JavaScriptCore/jit/JIT.h
Source/JavaScriptCore/jit/JITOpcodes.cpp
Source/JavaScriptCore/jit/JITOpcodes32_64.cpp
Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm
Source/JavaScriptCore/llint/LowLevelInterpreter64.asm
Source/JavaScriptCore/runtime/CommonSlowPaths.cpp
Source/JavaScriptCore/runtime/CommonSlowPaths.h
Source/JavaScriptCore/tests/stress/class-syntax-no-loop-tdz.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/class-syntax-no-tdz-in-catch.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/class-syntax-no-tdz-in-conditional.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/class-syntax-no-tdz-in-loop-no-inline-super.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/class-syntax-no-tdz-in-loop.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/class-syntax-no-tdz.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/class-syntax-tdz-in-catch.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/class-syntax-tdz-in-conditional.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/class-syntax-tdz-in-loop.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/class-syntax-tdz.js [new file with mode: 0644]

index 37a9189fed815aebb432b61f37b78a7a27d01835..f55a2005c06847f782142583531d5a7be28a3dcf 100644 (file)
@@ -1,3 +1,99 @@
+2015-03-12  Ryosuke Niwa  <rniwa@webkit.org>
+
+        "this" should be in TDZ until super is called in the constructor of a derived class
+        https://bugs.webkit.org/show_bug.cgi?id=142527
+
+        Reviewed by Mark Hahnenberg.
+
+        DFG and FTL implementations co-authored by Filip Pizlo.
+
+        In ES6 class syntax, "this" register must be in the "temporal dead zone" (TDZ) and throw ReferenceError until
+        super() is called inside the constructor of a derived class.
+
+        Added op_check_tdz, a new OP code, which throws a reference error when the first operand is an empty value
+        to all tiers of JIT and LLint. The op code throws in the slow path on the basis that a TDZ error should be
+        a programming error and not a part of the programs' normal control flow. In DFG, this op code is represented
+        by a no-op must-generate node CheckNotEmpty modeled after CheckCell.
+
+        Also made the constructor of a derived class assign the empty value to "this" register rather than undefined
+        so that ThisNode can emit the op_check_tdz to check the initialized-ness of "this" in such a constructor.
+
+        * bytecode/BytecodeList.json: Added op_check_tdz.
+        * bytecode/BytecodeUseDef.h:
+        (JSC::computeUsesForBytecodeOffset): Ditto.
+        (JSC::computeDefsForBytecodeOffset): Ditto.
+        * bytecode/CodeBlock.cpp:
+        (JSC::CodeBlock::dumpBytecode): Ditto.
+        * bytecode/ExitKind.cpp:
+        (JSC::exitKindToString): Added TDZFailure.
+        * bytecode/ExitKind.h: Ditto.
+        * bytecompiler/BytecodeGenerator.cpp:
+        (JSC::BytecodeGenerator::BytecodeGenerator): Assign the empty value to "this" register to indicate it's in TDZ.
+        (JSC::BytecodeGenerator::emitTDZCheck): Added.
+        (JSC::BytecodeGenerator::emitReturn): Emit the TDZ check since "this" can still be in TDZ if super() was never
+        called. e.g. class B extends A { constructor() { } }
+        * bytecompiler/BytecodeGenerator.h:
+        * bytecompiler/NodesCodegen.cpp:
+        (JSC::ThisNode::emitBytecode): Always emit the TDZ check if we're inside the constructor of a derived class.
+        We can't omit this check even if the result was ignored per spec.
+        * dfg/DFGAbstractInterpreterInlines.h:
+        (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects): Previously, empty value could never appear
+        in a local variable. This is no longer true so generalize this code. Also added the support for CheckNotEmpty.
+        Like CheckCell, we phantomize this DFG node in the constant folding phase if the type of the operand is already
+        found to be not empty. Otherwise filter out SpecEmpty.
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::parseBlock): Added op_check_tdz.
+        * dfg/DFGCapabilities.cpp:
+        (JSC::DFG::capabilityLevel): op_check_tdz can be compiled and inlined.
+        * dfg/DFGClobberize.h:
+        (JSC::DFG::clobberize): CheckNotEmpty doesn't read or write values.
+        * dfg/DFGConstantFoldingPhase.cpp:
+        (JSC::DFG::ConstantFoldingPhase::foldConstants): Convert CheckNotEmpty to a phantom if non-emptiness had already
+        been proven for the operand prior to this node.
+        * dfg/DFGDoesGC.cpp:
+        (JSC::DFG::doesGC): CheckNotEmpty does not trigger GC.
+        * dfg/DFGFixupPhase.cpp:
+        (JSC::DFG::FixupPhase::fixupNode): CheckNotEmpty is a no-op in the fixup phase.
+        * dfg/DFGNodeType.h: CheckNotEmpty cannot be removed even if the result was ignored. See ThisNode::emitBytecode.
+        * dfg/DFGPredictionPropagationPhase.cpp:
+        (JSC::DFG::PredictionPropagationPhase::propagate): CheckNotEmpty doesn't return any value.
+        * dfg/DFGSafeToExecute.h:
+        (JSC::DFG::safeToExecute): CheckNotEmpty doesn't load from heap so it's safe.
+        * dfg/DFGSpeculativeJIT32_64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile): Speculative the operand to be not empty. OSR exit if the speculation fails.
+        * dfg/DFGSpeculativeJIT64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile): Ditto.
+        * ftl/FTLCapabilities.cpp:
+        (JSC::FTL::canCompile): CheckNotEmpty can be compiled in FTL.
+        * ftl/FTLLowerDFGToLLVM.cpp:
+        (JSC::FTL::LowerDFGToLLVM::compileNode): Calls compileCheckNotEmpty for CheckNotEmpty.
+        (JSC::FTL::LowerDFGToLLVM::compileCheckNotEmpty): OSR exit with "TDZFailure" if the operand is not empty.
+        * jit/JIT.cpp:
+        (JSC::JIT::privateCompileMainPass): Added op_check_tdz.
+        (JSC::JIT::privateCompileSlowCases): Ditto.
+        * jit/JIT.h:
+        * jit/JITOpcodes.cpp:
+        (JSC::JIT::emit_op_check_tdz): Implements op_check_tdz in Baseline JIT.
+        (JSC::JIT::emitSlow_op_check_tdz): Ditto.
+        * jit/JITOpcodes32_64.cpp:
+        (JSC::JIT::emit_op_check_tdz): Ditto.
+        (JSC::JIT::emitSlow_op_check_tdz): Ditto.
+        * llint/LowLevelInterpreter32_64.asm: Implements op_check_tdz in LLint.
+        * llint/LowLevelInterpreter64.asm: Ditto.
+        * runtime/CommonSlowPaths.cpp:
+        (JSC::SLOW_PATH_DECL): Throws a reference error for op_check_tdz. Shared by LLint and Baseline JIT.
+        * runtime/CommonSlowPaths.h:
+        * tests/stress/class-syntax-no-loop-tdz.js: Added.
+        * tests/stress/class-syntax-no-tdz-in-catch.js: Added.
+        * tests/stress/class-syntax-no-tdz-in-conditional.js: Added.
+        * tests/stress/class-syntax-no-tdz-in-loop-no-inline-super.js: Added.
+        * tests/stress/class-syntax-no-tdz-in-loop.js: Added.
+        * tests/stress/class-syntax-no-tdz.js: Added.
+        * tests/stress/class-syntax-tdz-in-catch.js: Added.
+        * tests/stress/class-syntax-tdz-in-conditional.js: Added.
+        * tests/stress/class-syntax-tdz-in-loop.js: Added.
+        * tests/stress/class-syntax-tdz.js: Added.
+
 2015-03-12  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         Integrate MapData into JSMap and JSSet
index 5549119b400d1edb193e37cc91544594f77825f6..954267fbf5ba8ad26bade23f137553c5e495f77f 100644 (file)
@@ -11,6 +11,7 @@
             { "name" : "op_create_arguments", "length" : 3 },
             { "name" : "op_create_this", "length" : 4 },
             { "name" : "op_to_this", "length" : 4 },
+            { "name" : "op_check_tdz", "length" : 2 },
             { "name" : "op_new_object", "length" : 4 },
             { "name" : "op_new_array", "length" : 5 },
             { "name" : "op_new_array_with_size", "length" : 4 },
index 8d21ceba682c820b1fc5f4ddeea426f5f6a27513..21ece9563eeefbe1214ae5a4f1e5e7867ccf528c 100644 (file)
@@ -56,6 +56,7 @@ void computeUsesForBytecodeOffset(
         return;
     case op_get_scope:
     case op_to_this:
+    case op_check_tdz:
     case op_pop_scope:
     case op_profile_will_call:
     case op_profile_did_call:
@@ -364,6 +365,7 @@ void computeDefsForBytecodeOffset(CodeBlock* codeBlock, unsigned bytecodeOffset,
     case op_mov:
     case op_new_object:
     case op_to_this:
+    case op_check_tdz:
     case op_init_lazy_reg:
     case op_get_scope:
     case op_create_arguments:
index 209ea73a4c14c80c52beb0ea39fb89cd09dc4e9b..16fcaaa7386282eac7f6b61f3e20f03e389d9409 100644 (file)
@@ -789,6 +789,11 @@ void CodeBlock::dumpBytecode(
             out.print(" ", (++it)->u.toThisStatus);
             break;
         }
+        case op_check_tdz: {
+            int r0 = (++it)->u.operand;
+            printLocationOpAndRegisterOperand(out, exec, location, it, "op_check_tdz", r0);
+            break;
+        }
         case op_new_object: {
             int r0 = (++it)->u.operand;
             unsigned inferredInlineCapacity = (++it)->u.operand;
index 1577b57f25ad9ea392c3aa129eb3f4094a4723af..4f79f2c1c5e4e7fdbe043a049525100ad50ff47b 100644 (file)
@@ -70,6 +70,8 @@ const char* exitKindToString(ExitKind kind)
         return "NotStringObject";
     case VarargsOverflow:
         return "VarargsOverflow";
+    case TDZFailure:
+        return "TDZFailure";
     case Uncountable:
         return "Uncountable";
     case UncountableInvalidation:
index 90ac08a8ef72a90040d4f15a58dd8bb2c5f7160a..59cbbf5a2b387797e3ee2ba81f36e1c61c03b923 100644 (file)
@@ -47,6 +47,7 @@ enum ExitKind : uint8_t {
     ExoticObjectMode, // We exited because some exotic object that we were accessing was in an exotic mode (like Arguments with slow arguments).
     NotStringObject, // We exited because we shouldn't have attempted to optimize string object access.
     VarargsOverflow, // We exited because a varargs call passed more arguments than we expected.
+    TDZFailure, // We exited because we were in the TDZ and accessed the variable.
     Uncountable, // We exited for none of the above reasons, and we should not count it. Most uses of this should be viewed as a FIXME.
     UncountableInvalidation, // We exited because the code block was invalidated; this means that we've already counted the reasons why the code block was invalidated.
     WatchdogTimerFired, // We exited because we need to service the watchdog timer.
index 2db25ccebd8dcd5b0ab4135a32f3ca9cdd5cb2a5..6cfe830d0346160e88c5f2ede4272b36b331b27a 100644 (file)
@@ -404,7 +404,7 @@ BytecodeGenerator::BytecodeGenerator(VM& vm, FunctionNode* functionNode, Unlinke
         if (constructorKindIsDerived()) {
             m_newTargetRegister = addVar();
             emitMove(m_newTargetRegister, &m_thisRegister);
-            emitLoad(&m_thisRegister, jsNull());
+            emitMove(&m_thisRegister, addConstantEmptyValue());
         } else
             emitCreateThis(&m_thisRegister);
     } else if (functionNode->usesThis() || codeBlock->usesEval()) {
@@ -1556,6 +1556,12 @@ RegisterID* BytecodeGenerator::emitCreateThis(RegisterID* dst)
     return dst;
 }
 
+void BytecodeGenerator::emitTDZCheck(RegisterID* target)
+{
+    emitOpcode(op_check_tdz);
+    instructions().append(target->index());
+}
+
 RegisterID* BytecodeGenerator::emitNewObject(RegisterID* dst)
 {
     size_t begin = instructions().size();
@@ -1907,12 +1913,16 @@ RegisterID* BytecodeGenerator::emitReturn(RegisterID* src)
     }
 
     bool thisMightBeUninitialized = constructorKindIsDerived();
-    if (isConstructor() && (src->index() != m_thisRegister.index() || thisMightBeUninitialized)) {
+    bool srcIsThis = src->index() == m_thisRegister.index();
+    if (isConstructor() && (!srcIsThis || thisMightBeUninitialized)) {
         RefPtr<Label> isObjectOrUndefinedLabel = newLabel();
 
+        if (srcIsThis && thisMightBeUninitialized)
+            emitTDZCheck(src);
+
         emitJumpIfTrue(emitIsObject(newTemporary(), src), isObjectOrUndefinedLabel.get());
 
-        if (constructorKindIsDerived()) {
+        if (thisMightBeUninitialized) {
             emitJumpIfTrue(emitIsUndefined(newTemporary(), src), isObjectOrUndefinedLabel.get());
             emitThrowTypeError("Cannot return a non-object type in the constructor of a derived class.");
         } else
index c9b88755c92d6a9798afb6b6ad8a6df1db349b79..dd994c0aa9a7ca0f350d853959fb9a1b30df4543 100644 (file)
@@ -458,6 +458,7 @@ namespace JSC {
         RegisterID* emitUnaryNoDstOp(OpcodeID, RegisterID* src);
 
         RegisterID* emitCreateThis(RegisterID* dst);
+        void emitTDZCheck(RegisterID* target);
         RegisterID* emitNewObject(RegisterID* dst);
         RegisterID* emitNewArray(RegisterID* dst, ElementNode*, unsigned length); // stops at first elision
 
index 5581d15af46e29bcb84348321eb8ac19daa3b1f2..2e41ae650a964620246ce83eb8e2b87707a85238 100644 (file)
@@ -144,6 +144,9 @@ RegisterID* RegExpNode::emitBytecode(BytecodeGenerator& generator, RegisterID* d
 
 RegisterID* ThisNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
 {
+    if (generator.constructorKindIsDerived())
+        generator.emitTDZCheck(generator.thisRegister());
+
     if (dst == generator.ignoredResult())
         return 0;
 
index c015ad03667b4684a8d436f9b2bfac58e52f9981..52b0d05723b959185654e819bf25fc426559e756 100644 (file)
@@ -143,16 +143,7 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
     }
         
     case ExtractOSREntryLocal: {
-        if (!(node->unlinkedLocal().isArgument())
-            && m_graph.m_lazyVars.get(node->unlinkedLocal().toLocal())) {
-            // This is kind of pessimistic - we could know in some cases that the
-            // DFG code at the point of the OSR had already initialized the lazy
-            // variable. But maybe this is fine, since we're inserting OSR
-            // entrypoints very early in the pipeline - so any lazy initializations
-            // ought to be hoisted out anyway.
-            forNode(node).makeBytecodeTop();
-        } else
-            forNode(node).makeHeapTop();
+        forNode(node).makeBytecodeTop();
         break;
     }
             
@@ -1865,11 +1856,21 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
             ASSERT(value);
             break;
         }
-        
         filterByValue(node->child1(), *node->cellOperand());
         break;
     }
+
+    case CheckNotEmpty: {
+        AbstractValue& value = forNode(node->child1());
+        if (!(value.m_type & SpecEmpty)) {
+            m_state.setFoundConstants(true);
+            break;
+        }
         
+        filter(value, ~SpecEmpty);
+        break;
+    }
+
     case CheckInBounds: {
         JSValue left = forNode(node->child1()).value();
         JSValue right = forNode(node->child2()).value();
index 2874cc37aa1eed89f38b3c894e8414916db134ce..4b6fcc67b512784108215852445e3a125227aa16 100644 (file)
@@ -2822,6 +2822,12 @@ bool ByteCodeParser::parseBlock(unsigned limit)
             NEXT_OPCODE(op_mov);
         }
 
+        case op_check_tdz: {
+            Node* op = get(VirtualRegister(currentInstruction[1].u.operand));
+            addToGraph(CheckNotEmpty, op);
+            NEXT_OPCODE(op_check_tdz);
+        }
+
         case op_check_has_instance:
             addToGraph(CheckHasInstance, get(VirtualRegister(currentInstruction[3].u.operand)));
             NEXT_OPCODE(op_check_has_instance);
index a19c30686053f629488f2e586cab9e95db053535..a4765726a2f7c90b3de95d191b5cb826c9b454b7 100644 (file)
@@ -99,6 +99,7 @@ CapabilityLevel capabilityLevel(OpcodeID opcodeID, CodeBlock* codeBlock, Instruc
     case op_enter:
     case op_touch_entry:
     case op_to_this:
+    case op_check_tdz:
     case op_create_this:
     case op_bitand:
     case op_bitor:
index 58b7fbcec374896a49f5ca03c2ff2e7f20b17059..22cab1757dccaa36f7f252e92e248f6dd70cd2aa 100644 (file)
@@ -254,7 +254,11 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
     case CheckCell:
         def(PureValue(CheckCell, AdjacencyList(AdjacencyList::Fixed, node->child1()), node->cellOperand()));
         return;
-        
+
+    case CheckNotEmpty:
+        def(PureValue(CheckNotEmpty, AdjacencyList(AdjacencyList::Fixed, node->child1())));
+        return;
+
     case ConstantStoragePointer:
         def(PureValue(node, node->storagePointer()));
         return;
index fa22ba7fd0c96ae79cb9a48c9e32dcb265ce8217..050ec60f871013ba86eeeb6f846c9fdfdf0121cc 100644 (file)
@@ -185,7 +185,15 @@ private:
                 eliminated = true;
                 break;
             }
-                
+
+            case CheckNotEmpty: {
+                if (m_state.forNode(node->child1()).m_type & SpecEmpty)
+                    break;
+                node->convertToPhantom();
+                eliminated = true;
+                break;
+            }
+
             case CheckInBounds: {
                 JSValue left = m_state.forNode(node->child1()).value();
                 JSValue right = m_state.forNode(node->child2()).value();
index 61204dfd7c1d86b6fd8702b76e7458cef24debfe..866ecb5adb7e05a2ef0c89b63c344f9fa48bf975 100644 (file)
@@ -105,6 +105,7 @@ bool doesGC(Graph& graph, Node* node)
     case PutGlobalVar:
     case VarInjectionWatchpoint:
     case CheckCell:
+    case CheckNotEmpty:
     case AllocationProfileWatchpoint:
     case RegExpExec:
     case RegExpTest:
index 838670c93c0ca279b66b3a89e278802d077966d1..c12754b5987e6504d11dc1a34be8b44c1a36c92d 100644 (file)
@@ -1269,6 +1269,7 @@ private:
         case CountExecution:
         case ForceOSRExit:
         case CheckBadCell:
+        case CheckNotEmpty:
         case CheckWatchdogTimer:
         case Unreachable:
         case ExtractOSREntryLocal:
index bda8184149efe75c2e164f891dab15fe36bd0b2e..cc44c82ce66e5c0e26966dbbb6b1415c98440407 100644 (file)
@@ -191,6 +191,7 @@ namespace JSC { namespace DFG {
     macro(NotifyWrite, NodeMustGenerate) \
     macro(VarInjectionWatchpoint, NodeMustGenerate) \
     macro(CheckCell, NodeMustGenerate) \
+    macro(CheckNotEmpty, NodeMustGenerate) \
     macro(CheckBadCell, NodeMustGenerate) \
     macro(AllocationProfileWatchpoint, NodeMustGenerate) \
     macro(CheckInBounds, NodeMustGenerate) \
index 8401f5d7b2aee90685fe5c1a47222c51a746dc7d..947251cbcaa5c688a609a69ff14153f4a0ff03f2 100644 (file)
@@ -626,6 +626,7 @@ private:
         case SetArgument:
         case CheckStructure:
         case CheckCell:
+        case CheckNotEmpty:
         case CheckBadCell:
         case PutStructure:
         case TearOffArguments:
index 5199ee273f323aa04c96446bdf4b7be0f1557767..6e62b733c4722fac6fb77256842620a034569a1e 100644 (file)
@@ -178,6 +178,7 @@ bool safeToExecute(AbstractStateType& state, Graph& graph, Node* node)
     case VarInjectionWatchpoint:
     case CheckCell:
     case CheckBadCell:
+    case CheckNotEmpty:
     case AllocationProfileWatchpoint:
     case RegExpExec:
     case RegExpTest:
index e97dd43b182712d46c54ab85ab0eebc5e96a5cbf..fa3bd0d80ac8247777960b3172597dc71905070c 100644 (file)
@@ -3758,6 +3758,14 @@ void SpeculativeJIT::compile(Node* node)
         break;
     }
 
+    case CheckNotEmpty: {
+        JSValueOperand operand(this, node->child1());
+        GPRReg tagGPR = operand.tagGPR();
+        speculationCheck(TDZFailure, JSValueSource(), nullptr, m_jit.branch32(JITCompiler::Equal, tagGPR, TrustedImm32(JSValue::EmptyValueTag)));
+        noResult(node);
+        break;
+    }
+
     case GetExecutable: {
         SpeculateCellOperand function(this, node->child1());
         GPRTemporary result(this, Reuse, function);
index 1d1bccb74cc70fac72c1dbd62514afb17fb7992a..8c859af9b2f817ab0cccd1cdf71546a2b785ae4d 100644 (file)
@@ -3842,7 +3842,15 @@ void SpeculativeJIT::compile(Node* node)
         noResult(node);
         break;
     }
-        
+
+    case CheckNotEmpty: {
+        JSValueOperand operand(this, node->child1());
+        GPRReg gpr = operand.gpr();
+        speculationCheck(TDZFailure, JSValueSource(), nullptr, m_jit.branchTest64(JITCompiler::Zero, gpr));
+        noResult(node);
+        break;
+    }
+
     case GetExecutable: {
         SpeculateCellOperand function(this, node->child1());
         GPRTemporary result(this, Reuse, function);
index 77d098f29bc96e3ce3a46c65ed5b73e21fb0bdc2..5e36767208f2e011926ccb03f469f35c94fd84ff 100644 (file)
@@ -110,6 +110,7 @@ inline CapabilityLevel canCompile(Node* node)
     case StringCharAt:
     case CheckCell:
     case CheckBadCell:
+    case CheckNotEmpty:
     case StringCharCodeAt:
     case AllocatePropertyStorage:
     case ReallocatePropertyStorage:
index 1a290c5f8fc18ed5ce4eecd53ffff4cb6511ee0e..e0c5a34cb3ef5bf45336c5ea7a376696337ee634 100644 (file)
@@ -546,6 +546,9 @@ private:
         case CheckCell:
             compileCheckCell();
             break;
+        case CheckNotEmpty:
+            compileCheckNotEmpty();
+            break;
         case CheckBadCell:
             compileCheckBadCell();
             break;
@@ -1862,7 +1865,12 @@ private:
     {
         terminate(BadCell);
     }
-    
+
+    void compileCheckNotEmpty()
+    {
+        speculate(TDZFailure, noValue(), nullptr, m_out.isZero64(lowJSValue(m_node->child1())));
+    }
+
     void compileGetExecutable()
     {
         LValue cell = lowCell(m_node->child1());
index 2c8126772d8740179b1dc5e3b7875c5dab8cb678..2a91b39c3d72bda48e1b133f4e67457eff7c7e0d 100644 (file)
@@ -201,6 +201,7 @@ void JIT::privateCompileMainPass()
         DEFINE_OP(op_construct)
         DEFINE_OP(op_create_this)
         DEFINE_OP(op_to_this)
+        DEFINE_OP(op_check_tdz)
         DEFINE_OP(op_init_lazy_reg)
         DEFINE_OP(op_create_arguments)
         DEFINE_OP(op_debug)
@@ -375,6 +376,7 @@ void JIT::privateCompileSlowCases()
         DEFINE_SLOWCASE_OP(op_construct_varargs)
         DEFINE_SLOWCASE_OP(op_construct)
         DEFINE_SLOWCASE_OP(op_to_this)
+        DEFINE_SLOWCASE_OP(op_check_tdz)
         DEFINE_SLOWCASE_OP(op_create_this)
         DEFINE_SLOWCASE_OP(op_div)
         DEFINE_SLOWCASE_OP(op_eq)
index 161b8476284f2de182635da36106fedf05ea64da..0fd77a65b83e0c484e08fabbf9c2ed39685eff0a 100644 (file)
@@ -468,6 +468,7 @@ namespace JSC {
         void emit_op_construct(Instruction*);
         void emit_op_create_this(Instruction*);
         void emit_op_to_this(Instruction*);
+        void emit_op_check_tdz(Instruction*);
         void emit_op_create_arguments(Instruction*);
         void emit_op_debug(Instruction*);
         void emit_op_del_by_id(Instruction*);
@@ -572,6 +573,7 @@ namespace JSC {
         void emitSlow_op_construct(Instruction*, Vector<SlowCaseEntry>::iterator&);
         void emitSlow_op_to_this(Instruction*, Vector<SlowCaseEntry>::iterator&);
         void emitSlow_op_create_this(Instruction*, Vector<SlowCaseEntry>::iterator&);
+        void emitSlow_op_check_tdz(Instruction*, Vector<SlowCaseEntry>::iterator&);
         void emitSlow_op_div(Instruction*, Vector<SlowCaseEntry>::iterator&);
         void emitSlow_op_eq(Instruction*, Vector<SlowCaseEntry>::iterator&);
         void emitSlow_op_get_callee(Instruction*, Vector<SlowCaseEntry>::iterator&);
index 6c1ae4b86fa7ad6445d05bf8c06a04289fc9bb6c..d02d436981b1aefa5a58e3d632b0e9841beba651 100644 (file)
@@ -756,6 +756,19 @@ void JIT::emitSlow_op_create_this(Instruction* currentInstruction, Vector<SlowCa
     slowPathCall.call();
 }
 
+void JIT::emit_op_check_tdz(Instruction* currentInstruction)
+{
+    emitGetVirtualRegister(currentInstruction[1].u.operand, regT0);
+    addSlowCase(branchTest64(Zero, regT0));
+}
+
+void JIT::emitSlow_op_check_tdz(Instruction* currentInstruction, Vector<SlowCaseEntry>::iterator& iter)
+{
+    linkSlowCase(iter);
+    JITSlowPathCall slowPathCall(this, currentInstruction, slow_path_throw_tdz_error);
+    slowPathCall.call();
+}
+
 void JIT::emit_op_profile_will_call(Instruction* currentInstruction)
 {
     Jump profilerDone = branchTestPtr(Zero, AbsoluteAddress(m_vm->enabledProfilerAddress()));
index 8987d65a22ad4570989e9fce501c4c389a946174..b57f8b1b1aa7aafb75f1d382d715874af654b1ec 100644 (file)
@@ -997,6 +997,19 @@ void JIT::emitSlow_op_to_this(Instruction* currentInstruction, Vector<SlowCaseEn
     slowPathCall.call();
 }
 
+void JIT::emit_op_check_tdz(Instruction* currentInstruction)
+{
+    emitLoadTag(currentInstruction[1].u.operand, regT0);
+    addSlowCase(branch32(Equal, regT0, TrustedImm32(JSValue::EmptyValueTag)));
+}
+
+void JIT::emitSlow_op_check_tdz(Instruction* currentInstruction, Vector<SlowCaseEntry>::iterator& iter)
+{
+    linkSlowCase(iter);
+    JITSlowPathCall slowPathCall(this, currentInstruction, slow_path_throw_tdz_error);
+    slowPathCall.call();
+}
+
 void JIT::emit_op_profile_will_call(Instruction* currentInstruction)
 {
     load32(m_vm->enabledProfilerAddress(), regT0);
index 5c1c047c3133b89cdafcbdeabe22fedd7d6f22d8..5bcd89f56c2634d89a54c8a05460ee00ad70333a 100644 (file)
@@ -803,6 +803,16 @@ _llint_op_new_object:
     dispatch(4)
 
 
+_llint_op_check_tdz:
+    traceExecution()
+    loadpFromInstruction(1, t0)
+    bineq TagOffset[cfr, t0, 8], EmptyValueTag, .opNotTDZ
+    callSlowPath(_slow_path_throw_tdz_error)
+
+.opNotTDZ:
+    dispatch(2)
+
+
 _llint_op_mov:
     traceExecution()
     loadi 8[PC], t1
index 62939708cafce3ef1b1ca43974b6ed538ddb35f0..dcb98835ec8e99d7c0807a2cd8f5a1ad43d05be3 100644 (file)
@@ -687,6 +687,17 @@ _llint_op_new_object:
     dispatch(4)
 
 
+_llint_op_check_tdz:
+    traceExecution()
+    loadpFromInstruction(1, t0)
+    loadq [cfr, t0, 8], t0
+    bqneq t0, ValueEmpty, .opNotTDZ
+    callSlowPath(_slow_path_throw_tdz_error)
+
+.opNotTDZ:
+    dispatch(2)
+
+
 _llint_op_mov:
     traceExecution()
     loadisFromInstruction(2, t1)
index f33f0c60905b9ebae2c776226ba5201466ddd02c..c0972ca4059e3380d0d289ffdef1a791b433b0e3 100644 (file)
@@ -257,6 +257,12 @@ SLOW_PATH_DECL(slow_path_to_this)
     RETURN(v1.toThis(exec, exec->codeBlock()->isStrictMode() ? StrictMode : NotStrictMode));
 }
 
+SLOW_PATH_DECL(slow_path_throw_tdz_error)
+{
+    BEGIN();
+    THROW(createReferenceError(exec, "Cannot access uninitialized variable."));
+}
+
 SLOW_PATH_DECL(slow_path_not)
 {
     BEGIN();
index 70aad201806fd877ee7354b14d66ef11b2aa0305..7e2980d6145b051cf8a096df183c7d15589d3f46 100644 (file)
@@ -187,6 +187,7 @@ SLOW_PATH_HIDDEN_DECL(slow_path_create_this);
 SLOW_PATH_HIDDEN_DECL(slow_path_enter);
 SLOW_PATH_HIDDEN_DECL(slow_path_get_callee);
 SLOW_PATH_HIDDEN_DECL(slow_path_to_this);
+SLOW_PATH_HIDDEN_DECL(slow_path_throw_tdz_error);
 SLOW_PATH_HIDDEN_DECL(slow_path_not);
 SLOW_PATH_HIDDEN_DECL(slow_path_eq);
 SLOW_PATH_HIDDEN_DECL(slow_path_neq);
diff --git a/Source/JavaScriptCore/tests/stress/class-syntax-no-loop-tdz.js b/Source/JavaScriptCore/tests/stress/class-syntax-no-loop-tdz.js
new file mode 100644 (file)
index 0000000..e663919
--- /dev/null
@@ -0,0 +1,21 @@
+//@ skip
+
+class A {
+    constructor() { }
+}
+
+class B extends A {
+    constructor() {
+        for (var j = 0; j < 10; j++) {
+            if (!j)
+                super();
+            else
+                this;
+        }
+    }
+}
+
+noInline(B);
+
+for (var i = 0; i < 100000; ++i)
+    new B();
diff --git a/Source/JavaScriptCore/tests/stress/class-syntax-no-tdz-in-catch.js b/Source/JavaScriptCore/tests/stress/class-syntax-no-tdz-in-catch.js
new file mode 100644 (file)
index 0000000..f5c3826
--- /dev/null
@@ -0,0 +1,20 @@
+//@ skip
+
+class A {
+    constructor() { }
+}
+
+class B extends A {
+    constructor() {
+        try {
+            this;
+        } catch (e) {
+            super();
+        }
+    }
+}
+
+noInline(B);
+
+for (var i = 0; i < 100000; ++i)
+    new B();
diff --git a/Source/JavaScriptCore/tests/stress/class-syntax-no-tdz-in-conditional.js b/Source/JavaScriptCore/tests/stress/class-syntax-no-tdz-in-conditional.js
new file mode 100644 (file)
index 0000000..a68cdd6
--- /dev/null
@@ -0,0 +1,19 @@
+//@ skip
+
+class A {
+    constructor() { }
+}
+
+class B extends A {
+    constructor(accessThisBeforeSuper) {
+        if (accessThisBeforeSuper)
+            this;
+        else
+            super();
+    }
+}
+
+noInline(B);
+
+for (var i = 0; i < 100000; ++i)
+    new B(false);
diff --git a/Source/JavaScriptCore/tests/stress/class-syntax-no-tdz-in-loop-no-inline-super.js b/Source/JavaScriptCore/tests/stress/class-syntax-no-tdz-in-loop-no-inline-super.js
new file mode 100644 (file)
index 0000000..5cdd1dd
--- /dev/null
@@ -0,0 +1,26 @@
+//@ skip
+
+class A {
+    constructor() { }
+}
+
+noInline(A);
+
+class B extends A {
+    constructor() {
+        var values = [];
+        for (var j = 0; j < 100; j++) {
+            if (j == 1)
+                super();
+            else if (j > 2)
+                this;
+            else
+                values.push(i);
+        }
+    }
+}
+
+noInline(B);
+
+for (var i = 0; i < 100000; ++i)
+    new B();
diff --git a/Source/JavaScriptCore/tests/stress/class-syntax-no-tdz-in-loop.js b/Source/JavaScriptCore/tests/stress/class-syntax-no-tdz-in-loop.js
new file mode 100644 (file)
index 0000000..c04094e
--- /dev/null
@@ -0,0 +1,24 @@
+//@ skip
+
+class A {
+    constructor() { }
+}
+
+class B extends A {
+    constructor() {
+        var values = [];
+        for (var j = 0; j < 100; j++) {
+            if (j == 1)
+                super();
+            else if (j > 2)
+                this;
+            else
+                values.push(i);
+        }
+    }
+}
+
+noInline(B);
+
+for (var i = 0; i < 100000; ++i)
+    new B();
diff --git a/Source/JavaScriptCore/tests/stress/class-syntax-no-tdz.js b/Source/JavaScriptCore/tests/stress/class-syntax-no-tdz.js
new file mode 100644 (file)
index 0000000..f66c7c1
--- /dev/null
@@ -0,0 +1,17 @@
+//@ skip
+
+class A {
+    constructor() { }
+}
+
+class B extends A {
+    constructor() {
+        super();
+        this;
+    }
+}
+
+noInline(B);
+
+for (var i = 0; i < 100000; ++i)
+    new B();
diff --git a/Source/JavaScriptCore/tests/stress/class-syntax-tdz-in-catch.js b/Source/JavaScriptCore/tests/stress/class-syntax-tdz-in-catch.js
new file mode 100644 (file)
index 0000000..4aacc6a
--- /dev/null
@@ -0,0 +1,31 @@
+//@ skip
+
+class A {
+    constructor() { }
+}
+
+class B extends A {
+    constructor() {
+        try {
+            this;
+        } catch (e) {
+            this;
+            super();
+        }
+    }
+}
+
+noInline(B);
+
+for (var i = 0; i < 100000; ++i) {
+    var exception = null;
+    try {
+         new B(false);
+    } catch (e) {
+        exception = e;
+        if (!(e instanceof ReferenceError))
+            throw "Exception thrown in iteration " + i + " was not a reference error";
+    }
+    if (!exception)
+        throw "Exception not thrown for an unitialized this at iteration " + i;
+}
diff --git a/Source/JavaScriptCore/tests/stress/class-syntax-tdz-in-conditional.js b/Source/JavaScriptCore/tests/stress/class-syntax-tdz-in-conditional.js
new file mode 100644 (file)
index 0000000..3d5464b
--- /dev/null
@@ -0,0 +1,29 @@
+//@ skip
+
+class A {
+    constructor() { }
+}
+
+class B extends A {
+    constructor(accessThisBeforeSuper) {
+        if (accessThisBeforeSuper)
+            this;
+        else {
+            this;
+            super();
+        }
+    }
+}
+
+noInline(B);
+
+for (var i = 0; i < 100000; ++i) {
+    var exception = null;
+    try {
+         new B(false);
+    } catch (e) {
+        exception = e;
+    }
+    if (!exception)
+        throw "Exception not thrown for an unitialized this at iteration " + i;
+}
diff --git a/Source/JavaScriptCore/tests/stress/class-syntax-tdz-in-loop.js b/Source/JavaScriptCore/tests/stress/class-syntax-tdz-in-loop.js
new file mode 100644 (file)
index 0000000..b9838cc
--- /dev/null
@@ -0,0 +1,31 @@
+//@ skip
+
+class A {
+    constructor() { }
+}
+
+class B extends A {
+    constructor() {
+        for (var j = 0; j < 100; j++) {
+            if (j)
+                super();
+            else
+                this;
+        }
+    }
+}
+
+noInline(B);
+
+for (var i = 0; i < 100000; ++i) {
+    var exception = null;
+    try {
+        new B();
+    } catch (e) {
+        exception = e;
+        if (!(e instanceof ReferenceError))
+            throw "Exception thrown in iteration " + i + " was not a reference error";
+    }
+    if (!exception)
+        throw "Exception not thrown for an unitialized this at iteration " + i;
+}
diff --git a/Source/JavaScriptCore/tests/stress/class-syntax-tdz.js b/Source/JavaScriptCore/tests/stress/class-syntax-tdz.js
new file mode 100644 (file)
index 0000000..c4fe553
--- /dev/null
@@ -0,0 +1,27 @@
+//@ skip
+
+class A {
+    constructor() { }
+}
+
+class B extends A {
+    constructor() {
+        this;
+        super();
+    }
+}
+
+noInline(B);
+
+for (var i = 0; i < 100000; ++i) {
+    var exception;
+    try {
+        new B();
+    } catch (e) {
+        exception = e;
+        if (!(e instanceof ReferenceError))
+            throw "Exception thrown in iteration " + i + " was not a reference error";
+    }
+    if (!exception)
+        throw "Exception not thrown for an unitialized this at iteration " + i;
+}