aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/compiler
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 /src/qml/compiler
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>
Diffstat (limited to 'src/qml/compiler')
-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
9 files changed, 106 insertions, 18 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)