"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.
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@181466 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index 37a9189..f55a200 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -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
diff --git a/Source/JavaScriptCore/bytecode/BytecodeList.json b/Source/JavaScriptCore/bytecode/BytecodeList.json
index 5549119..954267f 100644
--- a/Source/JavaScriptCore/bytecode/BytecodeList.json
+++ b/Source/JavaScriptCore/bytecode/BytecodeList.json
@@ -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 },
diff --git a/Source/JavaScriptCore/bytecode/BytecodeUseDef.h b/Source/JavaScriptCore/bytecode/BytecodeUseDef.h
index 8d21ceb..21ece95 100644
--- a/Source/JavaScriptCore/bytecode/BytecodeUseDef.h
+++ b/Source/JavaScriptCore/bytecode/BytecodeUseDef.h
@@ -56,6 +56,7 @@
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 @@
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:
diff --git a/Source/JavaScriptCore/bytecode/CodeBlock.cpp b/Source/JavaScriptCore/bytecode/CodeBlock.cpp
index 209ea73..16fcaaa 100644
--- a/Source/JavaScriptCore/bytecode/CodeBlock.cpp
+++ b/Source/JavaScriptCore/bytecode/CodeBlock.cpp
@@ -789,6 +789,11 @@
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;
diff --git a/Source/JavaScriptCore/bytecode/ExitKind.cpp b/Source/JavaScriptCore/bytecode/ExitKind.cpp
index 1577b57..4f79f2c 100644
--- a/Source/JavaScriptCore/bytecode/ExitKind.cpp
+++ b/Source/JavaScriptCore/bytecode/ExitKind.cpp
@@ -70,6 +70,8 @@
return "NotStringObject";
case VarargsOverflow:
return "VarargsOverflow";
+ case TDZFailure:
+ return "TDZFailure";
case Uncountable:
return "Uncountable";
case UncountableInvalidation:
diff --git a/Source/JavaScriptCore/bytecode/ExitKind.h b/Source/JavaScriptCore/bytecode/ExitKind.h
index 90ac08a..59cbbf5 100644
--- a/Source/JavaScriptCore/bytecode/ExitKind.h
+++ b/Source/JavaScriptCore/bytecode/ExitKind.h
@@ -47,6 +47,7 @@
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.
diff --git a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
index 2db25cc..6cfe830 100644
--- a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
+++ b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
@@ -404,7 +404,7 @@
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 @@
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 @@
}
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
diff --git a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h
index c9b8875..dd994c0 100644
--- a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h
+++ b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h
@@ -458,6 +458,7 @@
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
diff --git a/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp b/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
index 5581d15..2e41ae6 100644
--- a/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
+++ b/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
@@ -144,6 +144,9 @@
RegisterID* ThisNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
{
+ if (generator.constructorKindIsDerived())
+ generator.emitTDZCheck(generator.thisRegister());
+
if (dst == generator.ignoredResult())
return 0;
diff --git a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
index c015ad0..52b0d05 100644
--- a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
+++ b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
@@ -143,16 +143,7 @@
}
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 @@
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();
diff --git a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
index 2874cc3..4b6fcc6 100644
--- a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
+++ b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
@@ -2822,6 +2822,12 @@
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);
diff --git a/Source/JavaScriptCore/dfg/DFGCapabilities.cpp b/Source/JavaScriptCore/dfg/DFGCapabilities.cpp
index a19c306..a476572 100644
--- a/Source/JavaScriptCore/dfg/DFGCapabilities.cpp
+++ b/Source/JavaScriptCore/dfg/DFGCapabilities.cpp
@@ -99,6 +99,7 @@
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:
diff --git a/Source/JavaScriptCore/dfg/DFGClobberize.h b/Source/JavaScriptCore/dfg/DFGClobberize.h
index 58b7fbc..22cab17 100644
--- a/Source/JavaScriptCore/dfg/DFGClobberize.h
+++ b/Source/JavaScriptCore/dfg/DFGClobberize.h
@@ -254,7 +254,11 @@
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;
diff --git a/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp b/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
index fa22ba7..050ec60 100644
--- a/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
@@ -185,7 +185,15 @@
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();
diff --git a/Source/JavaScriptCore/dfg/DFGDoesGC.cpp b/Source/JavaScriptCore/dfg/DFGDoesGC.cpp
index 61204df..866ecb5 100644
--- a/Source/JavaScriptCore/dfg/DFGDoesGC.cpp
+++ b/Source/JavaScriptCore/dfg/DFGDoesGC.cpp
@@ -105,6 +105,7 @@
case PutGlobalVar:
case VarInjectionWatchpoint:
case CheckCell:
+ case CheckNotEmpty:
case AllocationProfileWatchpoint:
case RegExpExec:
case RegExpTest:
diff --git a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
index 838670c..c12754b 100644
--- a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
@@ -1269,6 +1269,7 @@
case CountExecution:
case ForceOSRExit:
case CheckBadCell:
+ case CheckNotEmpty:
case CheckWatchdogTimer:
case Unreachable:
case ExtractOSREntryLocal:
diff --git a/Source/JavaScriptCore/dfg/DFGNodeType.h b/Source/JavaScriptCore/dfg/DFGNodeType.h
index bda8184..cc44c82 100644
--- a/Source/JavaScriptCore/dfg/DFGNodeType.h
+++ b/Source/JavaScriptCore/dfg/DFGNodeType.h
@@ -191,6 +191,7 @@
macro(NotifyWrite, NodeMustGenerate) \
macro(VarInjectionWatchpoint, NodeMustGenerate) \
macro(CheckCell, NodeMustGenerate) \
+ macro(CheckNotEmpty, NodeMustGenerate) \
macro(CheckBadCell, NodeMustGenerate) \
macro(AllocationProfileWatchpoint, NodeMustGenerate) \
macro(CheckInBounds, NodeMustGenerate) \
diff --git a/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp b/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
index 8401f5d..947251c 100644
--- a/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
@@ -626,6 +626,7 @@
case SetArgument:
case CheckStructure:
case CheckCell:
+ case CheckNotEmpty:
case CheckBadCell:
case PutStructure:
case TearOffArguments:
diff --git a/Source/JavaScriptCore/dfg/DFGSafeToExecute.h b/Source/JavaScriptCore/dfg/DFGSafeToExecute.h
index 5199ee2..6e62b73 100644
--- a/Source/JavaScriptCore/dfg/DFGSafeToExecute.h
+++ b/Source/JavaScriptCore/dfg/DFGSafeToExecute.h
@@ -178,6 +178,7 @@
case VarInjectionWatchpoint:
case CheckCell:
case CheckBadCell:
+ case CheckNotEmpty:
case AllocationProfileWatchpoint:
case RegExpExec:
case RegExpTest:
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
index e97dd43..fa3bd0d 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
@@ -3758,6 +3758,14 @@
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);
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
index 1d1bccb..8c859af 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
@@ -3842,7 +3842,15 @@
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);
diff --git a/Source/JavaScriptCore/ftl/FTLCapabilities.cpp b/Source/JavaScriptCore/ftl/FTLCapabilities.cpp
index 77d098f..5e36767 100644
--- a/Source/JavaScriptCore/ftl/FTLCapabilities.cpp
+++ b/Source/JavaScriptCore/ftl/FTLCapabilities.cpp
@@ -110,6 +110,7 @@
case StringCharAt:
case CheckCell:
case CheckBadCell:
+ case CheckNotEmpty:
case StringCharCodeAt:
case AllocatePropertyStorage:
case ReallocatePropertyStorage:
diff --git a/Source/JavaScriptCore/ftl/FTLLowerDFGToLLVM.cpp b/Source/JavaScriptCore/ftl/FTLLowerDFGToLLVM.cpp
index 1a290c5..e0c5a34 100644
--- a/Source/JavaScriptCore/ftl/FTLLowerDFGToLLVM.cpp
+++ b/Source/JavaScriptCore/ftl/FTLLowerDFGToLLVM.cpp
@@ -546,6 +546,9 @@
case CheckCell:
compileCheckCell();
break;
+ case CheckNotEmpty:
+ compileCheckNotEmpty();
+ break;
case CheckBadCell:
compileCheckBadCell();
break;
@@ -1862,7 +1865,12 @@
{
terminate(BadCell);
}
-
+
+ void compileCheckNotEmpty()
+ {
+ speculate(TDZFailure, noValue(), nullptr, m_out.isZero64(lowJSValue(m_node->child1())));
+ }
+
void compileGetExecutable()
{
LValue cell = lowCell(m_node->child1());
diff --git a/Source/JavaScriptCore/jit/JIT.cpp b/Source/JavaScriptCore/jit/JIT.cpp
index 2c81267..2a91b39 100644
--- a/Source/JavaScriptCore/jit/JIT.cpp
+++ b/Source/JavaScriptCore/jit/JIT.cpp
@@ -201,6 +201,7 @@
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 @@
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)
diff --git a/Source/JavaScriptCore/jit/JIT.h b/Source/JavaScriptCore/jit/JIT.h
index 161b847..0fd77a6 100644
--- a/Source/JavaScriptCore/jit/JIT.h
+++ b/Source/JavaScriptCore/jit/JIT.h
@@ -468,6 +468,7 @@
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 @@
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&);
diff --git a/Source/JavaScriptCore/jit/JITOpcodes.cpp b/Source/JavaScriptCore/jit/JITOpcodes.cpp
index 6c1ae4b8..d02d436 100644
--- a/Source/JavaScriptCore/jit/JITOpcodes.cpp
+++ b/Source/JavaScriptCore/jit/JITOpcodes.cpp
@@ -756,6 +756,19 @@
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()));
diff --git a/Source/JavaScriptCore/jit/JITOpcodes32_64.cpp b/Source/JavaScriptCore/jit/JITOpcodes32_64.cpp
index 8987d65..b57f8b1 100644
--- a/Source/JavaScriptCore/jit/JITOpcodes32_64.cpp
+++ b/Source/JavaScriptCore/jit/JITOpcodes32_64.cpp
@@ -997,6 +997,19 @@
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);
diff --git a/Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm b/Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm
index 5c1c047..5bcd89f 100644
--- a/Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm
+++ b/Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm
@@ -803,6 +803,16 @@
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
diff --git a/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm b/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm
index 62939708..dcb9883 100644
--- a/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm
+++ b/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm
@@ -687,6 +687,17 @@
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)
diff --git a/Source/JavaScriptCore/runtime/CommonSlowPaths.cpp b/Source/JavaScriptCore/runtime/CommonSlowPaths.cpp
index f33f0c6..c0972ca 100644
--- a/Source/JavaScriptCore/runtime/CommonSlowPaths.cpp
+++ b/Source/JavaScriptCore/runtime/CommonSlowPaths.cpp
@@ -257,6 +257,12 @@
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();
diff --git a/Source/JavaScriptCore/runtime/CommonSlowPaths.h b/Source/JavaScriptCore/runtime/CommonSlowPaths.h
index 70aad20..7e2980d 100644
--- a/Source/JavaScriptCore/runtime/CommonSlowPaths.h
+++ b/Source/JavaScriptCore/runtime/CommonSlowPaths.h
@@ -187,6 +187,7 @@
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
index 0000000..e663919
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/class-syntax-no-loop-tdz.js
@@ -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
index 0000000..f5c3826
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/class-syntax-no-tdz-in-catch.js
@@ -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
index 0000000..a68cdd6
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/class-syntax-no-tdz-in-conditional.js
@@ -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
index 0000000..5cdd1dd
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/class-syntax-no-tdz-in-loop-no-inline-super.js
@@ -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
index 0000000..c04094e
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/class-syntax-no-tdz-in-loop.js
@@ -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
index 0000000..f66c7c1
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/class-syntax-no-tdz.js
@@ -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
index 0000000..4aacc6a8
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/class-syntax-tdz-in-catch.js
@@ -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
index 0000000..3d5464b
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/class-syntax-tdz-in-conditional.js
@@ -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
index 0000000..b9838cc
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/class-syntax-tdz-in-loop.js
@@ -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
index 0000000..c4fe553
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/class-syntax-tdz.js
@@ -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;
+}