aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Hausmann <simon.hausmann@qt.io>2018-08-21 16:51:17 +0200
committerSimon Hausmann <simon.hausmann@qt.io>2018-08-28 17:50:41 +0000
commitbead103138c0d9dff3c9f927c9c4e2f44ee7db4c (patch)
tree32b23e94d07b5169758b426b1ad614e240dae9cd
parentec6996bcbed583177952f81f5bfaf1d67eb573ad (diff)
Implement the dead temporal zone
With const and let it is possible to access the declared member before initialization. This is expected to throw a type reference error at run-time. We initialize such variables with the empty value when entering their scope and check upon access for that. For locals we place the lexically scoped variables at the end. For register allocated lexical variables we group them into one batch and remember the index/size. Change-Id: Icb493ee0de0525bb682e1bc58981a4dfd33f750e Reviewed-by: Lars Knoll <lars.knoll@qt.io>
-rw-r--r--src/qml/compiler/qv4bytecodehandler.cpp6
-rw-r--r--src/qml/compiler/qv4codegen.cpp27
-rw-r--r--src/qml/compiler/qv4codegen_p.h3
-rw-r--r--src/qml/compiler/qv4compileddata_p.h11
-rw-r--r--src/qml/compiler/qv4compiler.cpp5
-rw-r--r--src/qml/compiler/qv4compilercontext.cpp56
-rw-r--r--src/qml/compiler/qv4compilercontext_p.h4
-rw-r--r--src/qml/compiler/qv4instr_moth.cpp8
-rw-r--r--src/qml/compiler/qv4instr_moth_p.h4
-rw-r--r--src/qml/jit/qv4baselineassembler.cpp24
-rw-r--r--src/qml/jit/qv4baselineassembler_p.h1
-rw-r--r--src/qml/jit/qv4baselinejit.cpp11
-rw-r--r--src/qml/jit/qv4baselinejit_p.h2
-rw-r--r--src/qml/jsruntime/qv4context.cpp4
-rw-r--r--src/qml/jsruntime/qv4context_p.h6
-rw-r--r--src/qml/jsruntime/qv4module.cpp5
-rw-r--r--src/qml/jsruntime/qv4runtime.cpp6
-rw-r--r--src/qml/jsruntime/qv4runtimeapi_p.h1
-rw-r--r--src/qml/jsruntime/qv4stackframe_p.h9
-rw-r--r--src/qml/jsruntime/qv4vme_moth.cpp15
-rw-r--r--tests/auto/qml/ecmascripttests/TestExpectations57
21 files changed, 189 insertions, 76 deletions
diff --git a/src/qml/compiler/qv4bytecodehandler.cpp b/src/qml/compiler/qv4bytecodehandler.cpp
index e2533a0505..181594010d 100644
--- a/src/qml/compiler/qv4bytecodehandler.cpp
+++ b/src/qml/compiler/qv4bytecodehandler.cpp
@@ -290,6 +290,9 @@ std::vector<int> ByteCodeHandler::collectLabelsInBytecode(const char *code, uint
addLabel(code - start + offset);
COLLECTOR_END_INSTR(UnwindToLabel)
+ COLLECTOR_BEGIN_INSTR(DeadTemporalZoneCheck)
+ COLLECTOR_END_INSTR(DeadTemporalZoneCheck)
+
COLLECTOR_BEGIN_INSTR(ThrowException)
COLLECTOR_END_INSTR(ThrowException)
@@ -519,6 +522,9 @@ std::vector<int> ByteCodeHandler::collectLabelsInBytecode(const char *code, uint
COLLECTOR_END_INSTR(Debug)
#endif // QT_NO_QML_DEBUGGER
+ COLLECTOR_BEGIN_INSTR(InitializeBlockDeadTemporalZone)
+ COLLECTOR_END_INSTR(InitializeBlockDeadTemporalZone)
+
COLLECTOR_BEGIN_INSTR(LoadQmlContext)
COLLECTOR_END_INSTR(LoadQmlContext)
diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp
index 734de853d1..5d16494c1b 100644
--- a/src/qml/compiler/qv4codegen.cpp
+++ b/src/qml/compiler/qv4codegen.cpp
@@ -47,6 +47,7 @@
#include <QtCore/QBitArray>
#include <QtCore/QLinkedList>
#include <QtCore/QStack>
+#include <QScopeGuard>
#include <private/qqmljsast_p.h>
#include <private/qv4string_p.h>
#include <private/qv4value_p.h>
@@ -501,8 +502,14 @@ void Codegen::variableDeclaration(PatternElement *ast)
{
RegisterScope scope(this);
- if (!ast->initializer)
+ if (!ast->initializer) {
+ if (ast->isLexicallyScoped()) {
+ Reference::fromConst(this, Encode::undefined()).loadInAccumulator();
+ Reference varToStore = targetForPatternElement(ast);
+ varToStore.storeConsumeAccumulator();
+ }
return;
+ }
initializeAndDestructureBindingElement(ast, Reference(), /*isDefinition*/ true);
}
@@ -2258,6 +2265,8 @@ Codegen::Reference Codegen::referenceForName(const QString &name, bool isLhs)
r.isVolatile = true;
r.isArgOrEval = resolved.isArgOrEval;
r.isReferenceToConst = resolved.isConst;
+ r.requiresTDZCheck = resolved.requiresTDZCheck;
+ r.name = name; // used to show correct name at run-time when TDZ check fails.
return r;
}
@@ -3818,7 +3827,7 @@ Codegen::Reference &Codegen::Reference::operator =(const Reference &other)
scope = other.scope;
break;
case Name:
- name = other.name;
+ // name is always copied
break;
case Member:
propertyBase = other.propertyBase;
@@ -3848,6 +3857,8 @@ Codegen::Reference &Codegen::Reference::operator =(const Reference &other)
codegen = other.codegen;
isReadonly = other.isReadonly;
isReferenceToConst = other.isReferenceToConst;
+ name = other.name;
+ requiresTDZCheck = other.requiresTDZCheck;
stackSlotIsLocalOrArgument = other.stackSlotIsLocalOrArgument;
isVolatile = other.isVolatile;
global = other.global;
@@ -3969,10 +3980,10 @@ void Codegen::Reference::storeOnStack(int slotIndex) const
Codegen::Reference Codegen::Reference::doStoreOnStack(int slotIndex) const
{
- if (isStackSlot() && slotIndex == -1 && !(stackSlotIsLocalOrArgument && isVolatile))
+ if (isStackSlot() && slotIndex == -1 && !(stackSlotIsLocalOrArgument && isVolatile) && !requiresTDZCheck)
return *this;
- if (isStackSlot()) { // temp-to-temp move
+ if (isStackSlot() && !requiresTDZCheck) { // temp-to-temp move
Reference dest = Reference::fromStackSlot(codegen, slotIndex);
Instruction::MoveReg move;
move.srcReg = stackSlot();
@@ -4130,6 +4141,14 @@ void Codegen::Reference::storeAccumulator() const
void Codegen::Reference::loadInAccumulator() const
{
+ auto tdzGuard = qScopeGuard([this](){
+ if (!requiresTDZCheck)
+ return;
+ Instruction::DeadTemporalZoneCheck check;
+ check.name = codegen->registerString(name);
+ codegen->bytecodeGenerator->addInstruction(check);
+ });
+
switch (type) {
case Accumulator:
return;
diff --git a/src/qml/compiler/qv4codegen_p.h b/src/qml/compiler/qv4codegen_p.h
index 516f506396..b7992bd8f2 100644
--- a/src/qml/compiler/qv4codegen_p.h
+++ b/src/qml/compiler/qv4codegen_p.h
@@ -219,7 +219,7 @@ public:
case Subscript:
return true;
default:
- return false;
+ return requiresTDZCheck;
}
}
bool isConstant() const { return type == Const; }
@@ -394,6 +394,7 @@ public:
mutable bool isArgOrEval = false;
bool isReadonly = false;
bool isReferenceToConst = false;
+ bool requiresTDZCheck = false;
bool stackSlotIsLocalOrArgument = false;
bool isVolatile = false;
bool global = false;
diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h
index dca38eae54..1f6c591ada 100644
--- a/src/qml/compiler/qv4compileddata_p.h
+++ b/src/qml/compiler/qv4compileddata_p.h
@@ -237,6 +237,8 @@ struct Block
{
quint32_le nLocals;
quint32_le localsOffset;
+ quint16_le sizeOfLocalTemporalDeadZone;
+ quint16_le padding;
const quint32_le *localsTable() const { return reinterpret_cast<const quint32_le *>(reinterpret_cast<const char *>(this) + localsOffset); }
@@ -251,7 +253,7 @@ struct Block
return (a + 7) & ~size_t(7);
}
};
-static_assert(sizeof(Block) == 8, "Block structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");
+static_assert(sizeof(Block) == 12, "Block structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");
// Function is aligned on an 8-byte boundary to make sure there are no bus errors or penalties
// for unaligned access. The ordering of the fields is also from largest to smallest.
@@ -276,6 +278,10 @@ struct Function
quint16_le nLineNumbers;
size_t lineNumberOffset() const { return localsOffset + nLocals * sizeof(quint32); }
quint32_le nestedFunctionIndex; // for functions that only return a single closure, used in signal handlers
+ quint16_le sizeOfLocalTemporalDeadZone;
+ quint16_le firstTemporalDeadZoneRegister;
+ quint16_le sizeOfRegisterTemporalDeadZone;
+ quint16_le nRegisters;
Location location;
// Qml Extensions Begin
@@ -293,7 +299,6 @@ struct Function
// Keep all unaligned data at the end
quint8 flags;
quint8 padding1;
- quint16_le nRegisters;
// quint32 formalsIndex[nFormals]
// quint32 localsIndex[nLocals]
@@ -326,7 +331,7 @@ struct Function
return (a + 7) & ~size_t(7);
}
};
-static_assert(sizeof(Function) == 48, "Function structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");
+static_assert(sizeof(Function) == 52, "Function structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");
struct Method {
enum Type {
diff --git a/src/qml/compiler/qv4compiler.cpp b/src/qml/compiler/qv4compiler.cpp
index 4a726c366d..b6e5504c24 100644
--- a/src/qml/compiler/qv4compiler.cpp
+++ b/src/qml/compiler/qv4compiler.cpp
@@ -398,6 +398,10 @@ void QV4::Compiler::JSUnitGenerator::writeFunction(char *f, QV4::Compiler::Conte
function->formalsOffset = currentOffset;
currentOffset += function->nFormals * sizeof(quint32);
+ function->sizeOfLocalTemporalDeadZone = irFunction->sizeOfLocalTemporalDeadZone;
+ function->sizeOfRegisterTemporalDeadZone = irFunction->sizeOfRegisterTemporalDeadZone;
+ function->firstTemporalDeadZoneRegister = irFunction->firstTemporalDeadZoneRegister;
+
function->nLocals = irFunction->locals.size();
function->localsOffset = currentOffset;
currentOffset += function->nLocals * sizeof(quint32);
@@ -532,6 +536,7 @@ void QV4::Compiler::JSUnitGenerator::writeBlock(char *b, QV4::Compiler::Context
quint32 currentOffset = sizeof(QV4::CompiledData::Block);
currentOffset = (currentOffset + 7) & ~quint32(0x7);
+ block->sizeOfLocalTemporalDeadZone = irBlock->sizeOfLocalTemporalDeadZone;
block->nLocals = irBlock->locals.size();
block->localsOffset = currentOffset;
currentOffset += block->nLocals * sizeof(quint32);
diff --git a/src/qml/compiler/qv4compilercontext.cpp b/src/qml/compiler/qv4compilercontext.cpp
index 09be19e427..ac0c2e0ecc 100644
--- a/src/qml/compiler/qv4compilercontext.cpp
+++ b/src/qml/compiler/qv4compilercontext.cpp
@@ -126,6 +126,7 @@ Context::ResolvedName Context::resolveName(const QString &name)
result.scope = scope;
result.index = m.index;
result.isConst = (m.scope == VariableScope::Const);
+ result.requiresTDZCheck = m.isLexicallyScoped();
if (c->isStrict && (name == QLatin1String("arguments") || name == QLatin1String("eval")))
result.isArgOrEval = true;
return result;
@@ -162,6 +163,8 @@ Context::ResolvedName Context::resolveName(const QString &name)
result.index = i;
result.type = ResolvedName::Import;
result.isConst = true;
+ // We don't know at compile time whether the imported value is let/const or not.
+ result.requiresTDZCheck = true;
return result;
}
}
@@ -209,6 +212,13 @@ void Context::emitBlockHeader(Codegen *codegen)
}
}
+ if (contextType == ContextType::Block && sizeOfRegisterTemporalDeadZone > 0) {
+ Instruction::InitializeBlockDeadTemporalZone tdzInit;
+ tdzInit.firstReg = registerOffset + nRegisters - sizeOfRegisterTemporalDeadZone;
+ tdzInit.count = sizeOfRegisterTemporalDeadZone;
+ bytecodeGenerator->addInstruction(tdzInit);
+ }
+
if (usesThis) {
Q_ASSERT(!isStrict);
// make sure we convert this to an object
@@ -303,21 +313,37 @@ void Context::setupFunctionIndices(Moth::BytecodeGenerator *bytecodeGenerator)
}
}
+ QVector<Context::MemberMap::Iterator> localsInTDZ;
+ const auto registerLocal = [this, &localsInTDZ](Context::MemberMap::iterator member) {
+ if (member->isLexicallyScoped()) {
+ localsInTDZ << member;
+ } else {
+ member->index = locals.size();
+ locals.append(member.key());
+ }
+ };
+
+ QVector<Context::MemberMap::Iterator> registersInTDZ;
+ const auto allocateRegister = [bytecodeGenerator, &registersInTDZ](Context::MemberMap::iterator member) {
+ if (member->isLexicallyScoped())
+ registersInTDZ << member;
+ else
+ member->index = bytecodeGenerator->newRegister();
+ };
+
switch (contextType) {
case ContextType::ESModule:
case ContextType::Block:
case ContextType::Function:
case ContextType::Binding: {
for (Context::MemberMap::iterator it = members.begin(), end = members.end(); it != end; ++it) {
- const QString &local = it.key();
if (it->canEscape) {
- it->index = locals.size();
- locals.append(local);
+ registerLocal(it);
} else {
if (it->type == Context::ThisFunctionName)
it->index = CallData::Function;
else
- it->index = bytecodeGenerator->newRegister();
+ allocateRegister(it);
}
}
break;
@@ -327,15 +353,25 @@ void Context::setupFunctionIndices(Moth::BytecodeGenerator *bytecodeGenerator)
for (Context::MemberMap::iterator it = members.begin(), end = members.end(); it != end; ++it) {
if (!it->isLexicallyScoped() && (contextType == ContextType::Global || !isStrict))
continue;
- if (it->canEscape) {
- it->index = locals.size();
- locals.append(it.key());
- } else {
- it->index = bytecodeGenerator->newRegister();
- }
+ if (it->canEscape)
+ registerLocal(it);
+ else
+ allocateRegister(it);
}
break;
}
+
+ sizeOfLocalTemporalDeadZone = localsInTDZ.count();
+ for (auto &member: qAsConst(localsInTDZ)) {
+ member->index = locals.size();
+ locals.append(member.key());
+ }
+
+ sizeOfRegisterTemporalDeadZone = registersInTDZ.count();
+ firstTemporalDeadZoneRegister = bytecodeGenerator->currentRegister();
+ for (auto &member: qAsConst(registersInTDZ))
+ member->index = bytecodeGenerator->newRegister();
+
nRegisters = bytecodeGenerator->currentRegister() - registerOffset;
}
diff --git a/src/qml/compiler/qv4compilercontext_p.h b/src/qml/compiler/qv4compilercontext_p.h
index bd0bd90a59..e7847e7072 100644
--- a/src/qml/compiler/qv4compilercontext_p.h
+++ b/src/qml/compiler/qv4compilercontext_p.h
@@ -189,6 +189,9 @@ struct Context {
int nRegisters = 0;
int registerOffset = -1;
+ int sizeOfLocalTemporalDeadZone = 0;
+ int firstTemporalDeadZoneRegister = 0;
+ int sizeOfRegisterTemporalDeadZone = 0;
bool hasDirectEval = false;
bool allVarsEscape = false;
bool hasNestedFunctions = false;
@@ -323,6 +326,7 @@ struct Context {
Type type = Unresolved;
bool isArgOrEval = false;
bool isConst = false;
+ bool requiresTDZCheck = false;
int scope = -1;
int index = -1;
bool isValid() const { return type != Unresolved; }
diff --git a/src/qml/compiler/qv4instr_moth.cpp b/src/qml/compiler/qv4instr_moth.cpp
index 514ec9ac9b..4653b7217d 100644
--- a/src/qml/compiler/qv4instr_moth.cpp
+++ b/src/qml/compiler/qv4instr_moth.cpp
@@ -415,6 +415,10 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st
d << "(" << level << ") " << ABSOLUTE_OFFSET();
MOTH_END_INSTR(UnwindToLabel)
+ MOTH_BEGIN_INSTR(DeadTemporalZoneCheck)
+ d << name;
+ MOTH_END_INSTR(DeadTemporalZoneCheck)
+
MOTH_BEGIN_INSTR(ThrowException)
MOTH_END_INSTR(ThrowException)
@@ -690,6 +694,10 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st
MOTH_BEGIN_INSTR(Debug)
MOTH_END_INSTR(Debug)
+ MOTH_BEGIN_INSTR(InitializeBlockDeadTemporalZone)
+ d << dumpRegister(firstReg, nFormals) << ", " << count;
+ MOTH_END_INSTR(InitializeBlockDeadTemporalZone)
+
MOTH_BEGIN_INSTR(LoadQmlContext)
d << dumpRegister(result, nFormals);
MOTH_END_INSTR(LoadQmlContext)
diff --git a/src/qml/compiler/qv4instr_moth_p.h b/src/qml/compiler/qv4instr_moth_p.h
index 25a07208c2..7c2cbf717e 100644
--- a/src/qml/compiler/qv4instr_moth_p.h
+++ b/src/qml/compiler/qv4instr_moth_p.h
@@ -118,6 +118,7 @@ QT_BEGIN_NAMESPACE
#define INSTR_SetUnwindHandler(op) INSTRUCTION(op, SetUnwindHandler, 1, offset)
#define INSTR_UnwindDispatch(op) INSTRUCTION(op, UnwindDispatch, 0)
#define INSTR_UnwindToLabel(op) INSTRUCTION(op, UnwindToLabel, 2, level, offset)
+#define INSTR_DeadTemporalZoneCheck(op) INSTRUCTION(op, DeadTemporalZoneCheck, 1, name)
#define INSTR_ThrowException(op) INSTRUCTION(op, ThrowException, 0)
#define INSTR_GetException(op) INSTRUCTION(op, GetException, 0)
#define INSTR_SetException(op) INSTRUCTION(op, SetException, 0)
@@ -192,6 +193,7 @@ QT_BEGIN_NAMESPACE
#define INSTR_Sub(op) INSTRUCTION(op, Sub, 1, lhs)
#define INSTR_LoadQmlContext(op) INSTRUCTION(op, LoadQmlContext, 1, result)
#define INSTR_LoadQmlImportedScripts(op) INSTRUCTION(op, LoadQmlImportedScripts, 1, result)
+#define INSTR_InitializeBlockDeadTemporalZone(op) INSTRUCTION(op, InitializeBlockDeadTemporalZone, 2, firstReg, count)
#define FOR_EACH_MOTH_INSTR_ALL(F) \
F(Nop) \
@@ -295,6 +297,7 @@ QT_BEGIN_NAMESPACE
F(SetUnwindHandler) \
F(UnwindDispatch) \
F(UnwindToLabel) \
+ F(DeadTemporalZoneCheck) \
F(ThrowException) \
F(GetException) \
F(SetException) \
@@ -326,6 +329,7 @@ QT_BEGIN_NAMESPACE
F(LoadSuperConstructor) \
F(PushScriptContext) \
F(PopScriptContext) \
+ F(InitializeBlockDeadTemporalZone) \
F(Debug) \
#define MOTH_NUM_INSTRUCTIONS() (static_cast<int>(Moth::Instr::Type::Debug_Wide) + 1)
diff --git a/src/qml/jit/qv4baselineassembler.cpp b/src/qml/jit/qv4baselineassembler.cpp
index c7d8f3c72c..5950901499 100644
--- a/src/qml/jit/qv4baselineassembler.cpp
+++ b/src/qml/jit/qv4baselineassembler.cpp
@@ -66,6 +66,7 @@ namespace JIT {
#define callHelper(x) PlatformAssemblerCommon::callRuntimeUnchecked(#x, reinterpret_cast<void *>(&x))
const QV4::Value::ValueTypeInternal IntegerTag = QV4::Value::ValueTypeInternal::Integer;
+const QV4::Value::ValueTypeInternal EmptyTag = QV4::Value::ValueTypeInternal::Empty;
static ReturnedValue toNumberHelper(ReturnedValue v)
{
@@ -172,6 +173,11 @@ public:
return branch64(Equal, AccumulatorRegister, TrustedImm64(Primitive::emptyValue().asReturnedValue()));
}
+ Jump jumpNotEmpty()
+ {
+ return branch64(NotEqual, AccumulatorRegister, TrustedImm64(Primitive::emptyValue().asReturnedValue()));
+ }
+
void toBoolean(std::function<void(RegisterID)> continuation)
{
urshift64(AccumulatorRegister, TrustedImm32(Value::IsIntegerConvertible_Shift), ScratchRegister);
@@ -634,6 +640,11 @@ public:
return branch32(Equal, AccumulatorRegisterTag, TrustedImm32(Primitive::emptyValue().asReturnedValue() >> 32));
}
+ Jump jumpNotEmpty()
+ {
+ return branch32(NotEqual, AccumulatorRegisterTag, TrustedImm32(Primitive::emptyValue().asReturnedValue() >> 32));
+ }
+
void toBoolean(std::function<void(RegisterID)> continuation)
{
urshift32(AccumulatorRegisterTag, TrustedImm32(Value::IsIntegerConvertible_Shift - 32),
@@ -1538,6 +1549,19 @@ void BaselineAssembler::popContext()
pasm()->storeHeapObject(PlatformAssembler::ScratchRegister, regAddr(CallData::Context));
}
+void BaselineAssembler::deadTemporalZoneCheck(int offsetForSavedIP, int variableName)
+{
+ auto valueIsAliveJump = pasm()->jumpNotEmpty();
+ storeInstructionPointer(offsetForSavedIP);
+ saveAccumulatorInFrame();
+ prepareCallWithArgCount(2);
+ passInt32AsArg(variableName, 1);
+ passEngineAsArg(0);
+ ASM_GENERATE_RUNTIME_CALL(Runtime::method_throwReferenceError, CallResultDestination::Ignore);
+ gotoCatchException();
+ valueIsAliveJump.link(pasm());
+}
+
void BaselineAssembler::ret()
{
pasm()->generateFunctionExit();
diff --git a/src/qml/jit/qv4baselineassembler_p.h b/src/qml/jit/qv4baselineassembler_p.h
index 4b64f91cd4..a2140ce47b 100644
--- a/src/qml/jit/qv4baselineassembler_p.h
+++ b/src/qml/jit/qv4baselineassembler_p.h
@@ -161,6 +161,7 @@ public:
void unwindToLabel(int level, int offset);
void pushCatchContext(int index, int name);
void popContext();
+ void deadTemporalZoneCheck(int offsetForSavedIP, int variableName);
// other stuff
void ret();
diff --git a/src/qml/jit/qv4baselinejit.cpp b/src/qml/jit/qv4baselinejit.cpp
index b729f21059..e9f85a5c6b 100644
--- a/src/qml/jit/qv4baselinejit.cpp
+++ b/src/qml/jit/qv4baselinejit.cpp
@@ -579,6 +579,10 @@ void BaselineJIT::generate_UnwindToLabel(int level, int offset)
as->unwindToLabel(level, absoluteOffsetForJump(offset));
}
+void BaselineJIT::generate_DeadTemporalZoneCheck(int name)
+{
+ as->deadTemporalZoneCheck(nextInstructionOffset(), name);
+}
void BaselineJIT::generate_ThrowException()
{
@@ -931,6 +935,13 @@ void BaselineJIT::generate_LoadQmlImportedScripts(int result)
as->storeReg(result);
}
+void BaselineJIT::generate_InitializeBlockDeadTemporalZone(int firstReg, int count)
+{
+ as->loadValue(Primitive::emptyValue().rawValue());
+ for (int i = firstReg, end = firstReg + count; i < end; ++i)
+ as->storeReg(i);
+}
+
void BaselineJIT::startInstruction(Instr::Type /*instr*/)
{
if (hasLabel())
diff --git a/src/qml/jit/qv4baselinejit_p.h b/src/qml/jit/qv4baselinejit_p.h
index 07a1853816..a3cb850fc1 100644
--- a/src/qml/jit/qv4baselinejit_p.h
+++ b/src/qml/jit/qv4baselinejit_p.h
@@ -134,6 +134,7 @@ public:
void generate_SetUnwindHandler(int offset) override;
void generate_UnwindDispatch() override;
void generate_UnwindToLabel(int level, int offset) override;
+ void generate_DeadTemporalZoneCheck(int name) override;
void generate_ThrowException() override;
void generate_GetException() override;
void generate_SetException() override;
@@ -208,6 +209,7 @@ public:
void generate_Sub(int lhs) override;
void generate_LoadQmlContext(int result) override;
void generate_LoadQmlImportedScripts(int result) override;
+ void generate_InitializeBlockDeadTemporalZone(int firstReg, int count) override;
void startInstruction(Moth::Instr::Type instr) override;
void endInstruction(Moth::Instr::Type instr) override;
diff --git a/src/qml/jsruntime/qv4context.cpp b/src/qml/jsruntime/qv4context.cpp
index 327e7b78c4..d22179173c 100644
--- a/src/qml/jsruntime/qv4context.cpp
+++ b/src/qml/jsruntime/qv4context.cpp
@@ -76,6 +76,8 @@ Heap::CallContext *ExecutionContext::newBlockContext(CppStackFrame *frame, int b
c->locals.size = nLocals;
c->locals.alloc = nLocals;
+ c->setupLocalTemporalDeadZone(function->compilationUnit->unitData()->blockAt(blockIndex));
+
return c;
}
@@ -115,6 +117,8 @@ Heap::CallContext *ExecutionContext::newCallContext(CppStackFrame *frame)
// memory allocated from the JS heap is 0 initialized, so check if empty is 0
Q_ASSERT(Primitive::undefinedValue().asReturnedValue() == 0);
+ c->setupLocalTemporalDeadZone(compiledFunction);
+
Value *args = c->locals.values + nLocals;
::memcpy(args, frame->originalArguments, frame->originalArgumentsCount * sizeof(Value));
c->nArgs = frame->originalArgumentsCount;
diff --git a/src/qml/jsruntime/qv4context_p.h b/src/qml/jsruntime/qv4context_p.h
index fbb4168e9b..5de11d80cb 100644
--- a/src/qml/jsruntime/qv4context_p.h
+++ b/src/qml/jsruntime/qv4context_p.h
@@ -118,6 +118,12 @@ DECLARE_HEAP_OBJECT(CallContext, ExecutionContext) {
return locals.data() + locals.size;
}
void setArg(uint index, Value v);
+
+ template <typename BlockOrFunction>
+ void setupLocalTemporalDeadZone(BlockOrFunction *bof) {
+ for (uint i = bof->nLocals - bof->sizeOfLocalTemporalDeadZone; i < bof->nLocals; ++i)
+ locals.values[i] = Primitive::emptyValue();
+ }
};
Q_STATIC_ASSERT(std::is_trivial< CallContext >::value);
Q_STATIC_ASSERT(std::is_standard_layout<CallContextData>::value);
diff --git a/src/qml/jsruntime/qv4module.cpp b/src/qml/jsruntime/qv4module.cpp
index 583d0c3756..c892749a11 100644
--- a/src/qml/jsruntime/qv4module.cpp
+++ b/src/qml/jsruntime/qv4module.cpp
@@ -70,6 +70,9 @@ void Heap::Module::init(ExecutionEngine *engine, CompiledData::CompilationUnit *
scope->locals.alloc = locals;
scope->nArgs = 0;
+ // Prepare the temporal dead zone
+ scope->setupLocalTemporalDeadZone(moduleFunction->compiledFunction);
+
Scope valueScope(engine);
// It's possible for example to re-export an import, for example:
@@ -106,7 +109,7 @@ ReturnedValue Module::virtualGet(const Managed *m, PropertyKey id, const Value *
const Value *v = module->d()->unit->resolveExport(expectedName);
if (hasProperty)
*hasProperty = v != nullptr;
- if (!v)
+ if (!v || v->isEmpty())
return Encode::undefined();
return v->asReturnedValue();
}
diff --git a/src/qml/jsruntime/qv4runtime.cpp b/src/qml/jsruntime/qv4runtime.cpp
index 9180108a0c..54898ca6a1 100644
--- a/src/qml/jsruntime/qv4runtime.cpp
+++ b/src/qml/jsruntime/qv4runtime.cpp
@@ -1470,6 +1470,12 @@ ReturnedValue Runtime::method_popScriptContext(ExecutionEngine *engine)
return root;
}
+void Runtime::method_throwReferenceError(ExecutionEngine *engine, int nameIndex)
+{
+ Scope scope(engine);
+ ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]);
+ engine->throwReferenceError(name);
+}
void Runtime::method_declareVar(ExecutionEngine *engine, bool deletable, int nameIndex)
{
diff --git a/src/qml/jsruntime/qv4runtimeapi_p.h b/src/qml/jsruntime/qv4runtimeapi_p.h
index 3f65e6c6d6..182cc393ea 100644
--- a/src/qml/jsruntime/qv4runtimeapi_p.h
+++ b/src/qml/jsruntime/qv4runtimeapi_p.h
@@ -132,6 +132,7 @@ struct ExceptionCheck<void (*)(QV4::NoThrowEngine *, A, B, C)> {
F(ReturnedValue, createScriptContext, (ExecutionEngine *engine, int index)) \
F(ReturnedValue, cloneBlockContext, (ExecutionContext *previous)) \
F(ReturnedValue, popScriptContext, (ExecutionEngine *engine)) \
+ F(void, throwReferenceError, (ExecutionEngine *engine, int nameIndex)) \
\
/* closures */ \
F(ReturnedValue, closure, (ExecutionEngine *engine, int functionId)) \
diff --git a/src/qml/jsruntime/qv4stackframe_p.h b/src/qml/jsruntime/qv4stackframe_p.h
index 878f9283e7..ca39a8f7bb 100644
--- a/src/qml/jsruntime/qv4stackframe_p.h
+++ b/src/qml/jsruntime/qv4stackframe_p.h
@@ -181,6 +181,15 @@ struct Q_QML_EXPORT CppStackFrame {
const Value *end = jsFrame->args + nRegisters;
for (Value *v = jsFrame->args + argc; v < end; ++v)
*v = Encode::undefined();
+
+ if (v4Function && v4Function->compiledFunction) {
+ const int firstDeadZoneRegister = v4Function->compiledFunction->firstTemporalDeadZoneRegister;
+ const int registerDeadZoneSize = v4Function->compiledFunction->sizeOfRegisterTemporalDeadZone;
+
+ const Value * tdzEnd = stackSpace + firstDeadZoneRegister + registerDeadZoneSize;
+ for (Value *v = stackSpace + firstDeadZoneRegister; v < tdzEnd; ++v)
+ *v = Primitive::emptyValue().asReturnedValue();
+ }
}
#endif
diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp
index aed3fce6b1..3570453525 100644
--- a/src/qml/jsruntime/qv4vme_moth.cpp
+++ b/src/qml/jsruntime/qv4vme_moth.cpp
@@ -784,6 +784,15 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine,
goto handleUnwind;
MOTH_END_INSTR(UnwindToLabel)
+ MOTH_BEGIN_INSTR(DeadTemporalZoneCheck)
+ if (ACC.isEmpty()) {
+ STORE_IP();
+ STORE_ACC();
+ Runtime::method_throwReferenceError(engine, name);
+ goto handleUnwind;
+ }
+ MOTH_END_INSTR(DeadTemporalZoneCheck)
+
MOTH_BEGIN_INSTR(ThrowException)
STORE_IP();
STORE_ACC();
@@ -1322,6 +1331,12 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine,
return acc;
MOTH_END_INSTR(Ret)
+ MOTH_BEGIN_INSTR(InitializeBlockDeadTemporalZone)
+ acc = Encode(Primitive::emptyValue());
+ for (int i = firstReg, end = firstReg + count; i < end; ++i)
+ STACK_VALUE(i) = acc;
+ MOTH_END_INSTR(InitializeBlockDeadTemporalZone)
+
MOTH_BEGIN_INSTR(Debug)
#if QT_CONFIG(qml_debug)
STORE_IP();
diff --git a/tests/auto/qml/ecmascripttests/TestExpectations b/tests/auto/qml/ecmascripttests/TestExpectations
index b303592e23..2848317683 100644
--- a/tests/auto/qml/ecmascripttests/TestExpectations
+++ b/tests/auto/qml/ecmascripttests/TestExpectations
@@ -827,9 +827,6 @@ built-ins/isNaN/toprimitive-not-callable-throws.js fails
language/computed-property-names/class/static/method-number.js fails
language/computed-property-names/class/static/method-string.js fails
language/computed-property-names/class/static/method-symbol.js fails
-language/eval-code/direct/lex-env-no-init-cls.js fails
-language/eval-code/direct/lex-env-no-init-const.js fails
-language/eval-code/direct/lex-env-no-init-let.js fails
language/eval-code/direct/new.target.js fails
language/eval-code/direct/new.target-arrow.js fails
language/eval-code/direct/new.target-fn.js fails
@@ -852,9 +849,6 @@ language/eval-code/direct/var-env-global-lex-non-strict.js sloppyFails
language/eval-code/direct/var-env-lower-lex-catch-non-strict.js sloppyFails
language/eval-code/direct/var-env-lower-lex-non-strict.js sloppyFails
language/eval-code/indirect/always-non-strict.js strictFails
-language/eval-code/indirect/lex-env-no-init-cls.js fails
-language/eval-code/indirect/lex-env-no-init-const.js fails
-language/eval-code/indirect/lex-env-no-init-let.js fails
language/eval-code/indirect/new.target.js fails
language/eval-code/indirect/non-definable-function-with-function.js sloppyFails
language/eval-code/indirect/non-definable-function-with-variable.js sloppyFails
@@ -904,7 +898,6 @@ language/expressions/assignment/S11.13.1_A7_T2.js fails
language/expressions/assignment/S11.13.1_A7_T3.js fails
language/expressions/assignment/destructuring/iterator-destructuring-property-reference-target-evaluation-order.js fails
language/expressions/assignment/destructuring/keyed-destructuring-property-reference-target-evaluation-order.js fails
-language/expressions/assignment/dstr-array-elem-init-let.js fails
language/expressions/assignment/dstr-array-elem-iter-rtrn-close-err.js fails
language/expressions/assignment/dstr-array-elem-iter-rtrn-close-null.js fails
language/expressions/assignment/dstr-array-elem-iter-rtrn-close.js fails
@@ -930,9 +923,7 @@ language/expressions/assignment/dstr-array-rest-lref-err.js fails
language/expressions/assignment/dstr-array-rest-put-let.js fails
language/expressions/assignment/dstr-obj-empty-null.js fails
language/expressions/assignment/dstr-obj-empty-undef.js fails
-language/expressions/assignment/dstr-obj-id-init-let.js fails
language/expressions/assignment/dstr-obj-id-put-let.js fails
-language/expressions/assignment/dstr-obj-prop-elem-init-let.js fails
language/expressions/assignment/dstr-obj-prop-put-let.js fails
language/expressions/assignment/fn-name-lhs-cover.js fails
language/expressions/assignment/fn-name-lhs-member.js fails
@@ -1256,22 +1247,9 @@ language/global-code/script-decl-var.js fails
language/identifiers/other_id_continue.js fails
language/identifiers/other_id_start-escaped.js fails
language/identifiers/other_id_start.js fails
-language/module-code/instn-iee-bndng-cls.js strictFails
-language/module-code/instn-iee-bndng-const.js strictFails
-language/module-code/instn-iee-bndng-let.js strictFails
-language/module-code/instn-local-bndng-cls.js strictFails
-language/module-code/instn-local-bndng-const.js strictFails
-language/module-code/instn-local-bndng-export-cls.js strictFails
-language/module-code/instn-local-bndng-export-const.js strictFails
-language/module-code/instn-local-bndng-export-let.js strictFails
-language/module-code/instn-local-bndng-let.js strictFails
-language/module-code/instn-named-bndng-cls.js strictFails
-language/module-code/instn-named-bndng-const.js strictFails
-language/module-code/instn-named-bndng-dflt-cls.js strictFails
language/module-code/instn-named-bndng-dflt-expr.js strictFails
language/module-code/instn-named-bndng-dflt-named.js strictFails
language/module-code/instn-named-bndng-dflt-star.js strictFails
-language/module-code/instn-named-bndng-let.js strictFails
language/module-code/namespace/internals/delete-exported-uninit.js strictFails
language/module-code/namespace/internals/enumerate-binding-uninit.js strictFails
language/module-code/namespace/internals/get-own-property-str-found-uninit.js strictFails
@@ -1315,9 +1293,6 @@ language/statements/class/meth-dflt-params-ref-later.js fails
language/statements/class/meth-dflt-params-ref-self.js fails
language/statements/class/meth-static-dflt-params-ref-later.js fails
language/statements/class/meth-static-dflt-params-ref-self.js fails
-language/statements/class/name-binding/in-extends-expression-assigned.js fails
-language/statements/class/name-binding/in-extends-expression-grouped.js fails
-language/statements/class/name-binding/in-extends-expression.js fails
language/statements/class/scope-gen-meth-paramsbody-var-open.js fails
language/statements/class/scope-meth-paramsbody-var-open.js fails
language/statements/class/scope-setter-paramsbody-var-open.js fails
@@ -1359,27 +1334,12 @@ language/statements/class/subclass/class-definition-null-proto-super.js fails
language/statements/class/subclass/class-definition-null-proto-this.js fails
language/statements/class/subclass/default-constructor-spread-override.js fails
language/statements/class/super/in-methods.js fails
-language/statements/const/block-local-closure-get-before-initialization.js fails
-language/statements/const/block-local-use-before-initialization-in-declaration-statement.js fails
-language/statements/const/block-local-use-before-initialization-in-prior-statement.js fails
-language/statements/const/function-local-closure-get-before-initialization.js fails
-language/statements/const/function-local-use-before-initialization-in-declaration-statement.js fails
-language/statements/const/function-local-use-before-initialization-in-prior-statement.js fails
-language/statements/const/global-closure-get-before-initialization.js fails
-language/statements/const/global-use-before-initialization-in-declaration-statement.js fails
-language/statements/const/global-use-before-initialization-in-prior-statement.js fails
language/statements/do-while/tco-body.js strictFails
-language/statements/for-in/head-const-bound-names-fordecl-tdz.js fails
-language/statements/for-in/head-let-bound-names-fordecl-tdz.js fails
language/statements/for-in/head-lhs-let.js sloppyFails
language/statements/for-in/head-var-bound-names-let.js sloppyFails
language/statements/for-in/identifier-let-allowed-as-lefthandside-expression-not-strict.js sloppyFails
-language/statements/for-in/scope-body-lex-open.js fails
-language/statements/for-in/scope-head-lex-close.js fails
-language/statements/for-in/scope-head-lex-open.js fails
language/statements/for-of/body-dstr-assign-error.js fails
language/statements/for-of/body-put-error.js fails
-language/statements/for-of/dstr-array-elem-init-let.js fails
language/statements/for-of/dstr-array-elem-iter-rtrn-close-err.js fails
language/statements/for-of/dstr-array-elem-iter-rtrn-close-null.js fails
language/statements/for-of/dstr-array-elem-iter-rtrn-close.js fails
@@ -1405,23 +1365,16 @@ language/statements/for-of/dstr-array-rest-lref-err.js fails
language/statements/for-of/dstr-array-rest-put-let.js fails
language/statements/for-of/dstr-obj-empty-null.js fails
language/statements/for-of/dstr-obj-empty-undef.js fails
-language/statements/for-of/dstr-obj-id-init-let.js fails
language/statements/for-of/dstr-obj-id-put-let.js fails
-language/statements/for-of/dstr-obj-prop-elem-init-let.js fails
language/statements/for-of/dstr-obj-prop-put-let.js fails
language/statements/for-of/generator-close-via-continue.js fails
language/statements/for-of/generator-close-via-return.js fails
language/statements/for-of/generator-close-via-throw.js fails
-language/statements/for-of/head-const-bound-names-fordecl-tdz.js fails
-language/statements/for-of/head-let-bound-names-fordecl-tdz.js fails
language/statements/for-of/head-var-bound-names-let.js sloppyFails
language/statements/for-of/iterator-close-via-continue.js fails
language/statements/for-of/iterator-close-via-return.js fails
language/statements/for-of/iterator-close-via-throw.js fails
language/statements/for-of/iterator-next-reference.js fails
-language/statements/for-of/scope-body-lex-open.js fails
-language/statements/for-of/scope-head-lex-close.js fails
-language/statements/for-of/scope-head-lex-open.js fails
language/statements/for-of/yield-star-from-catch.js fails
language/statements/for-of/yield-star-from-finally.js fails
language/statements/for-of/yield-star-from-try.js fails
@@ -1465,19 +1418,9 @@ language/statements/generators/yield-star-before-newline.js fails
language/statements/if/tco-else-body.js strictFails
language/statements/if/tco-if-body.js strictFails
language/statements/labeled/tco.js strictFails
-language/statements/let/block-local-closure-get-before-initialization.js fails
language/statements/let/block-local-closure-set-before-initialization.js fails
-language/statements/let/block-local-use-before-initialization-in-declaration-statement.js fails
-language/statements/let/block-local-use-before-initialization-in-prior-statement.js fails
-language/statements/let/function-local-closure-get-before-initialization.js fails
language/statements/let/function-local-closure-set-before-initialization.js fails
-language/statements/let/function-local-use-before-initialization-in-declaration-statement.js fails
-language/statements/let/function-local-use-before-initialization-in-prior-statement.js fails
-language/statements/let/global-closure-get-before-initialization.js fails
language/statements/let/global-closure-set-before-initialization.js fails
-language/statements/let/global-use-before-initialization-in-declaration-statement.js fails
-language/statements/let/global-use-before-initialization-in-prior-statement.js fails
-language/statements/let/syntax/let.js fails
language/statements/return/tco.js strictFails
language/statements/switch/scope-lex-open-case.js fails
language/statements/switch/scope-lex-open-dflt.js fails