1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQml module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qv4compilercontext_p.h"
41#include "qv4compilercontrolflow_p.h"
42#include "qv4bytecodegenerator_p.h"
43
44QT_USE_NAMESPACE
45using namespace QV4;
46using namespace QV4::Compiler;
47using namespace QQmlJS::AST;
48using namespace QQmlJS;
49
50QT_BEGIN_NAMESPACE
51
52Context *Module::newContext(Node *node, Context *parent, ContextType contextType)
53{
54 Q_ASSERT(!contextMap.contains(node));
55
56 Context *c = new Context(parent, contextType);
57 if (node) {
58 SourceLocation loc = node->firstSourceLocation();
59 c->line = loc.startLine;
60 c->column = loc.startColumn;
61 }
62
63 contextMap.insert(akey: node, avalue: c);
64
65 if (!parent)
66 rootContext = c;
67 else {
68 parent->nestedContexts.append(t: c);
69 c->isStrict = parent->isStrict;
70 }
71
72 return c;
73}
74
75bool Context::Member::requiresTDZCheck(const SourceLocation &accessLocation, bool accessAcrossContextBoundaries) const
76{
77 if (!isLexicallyScoped())
78 return false;
79
80 if (accessAcrossContextBoundaries)
81 return true;
82
83 if (!accessLocation.isValid() || !endOfInitializerLocation.isValid())
84 return true;
85
86 return accessLocation.begin() < endOfInitializerLocation.end();
87}
88
89bool Context::addLocalVar(const QString &name, Context::MemberType type, VariableScope scope, FunctionExpression *function,
90 const QQmlJS::SourceLocation &endOfInitializer)
91{
92 // ### can this happen?
93 if (name.isEmpty())
94 return true;
95
96 if (type != FunctionDefinition) {
97 if (formals && formals->containsName(name))
98 return (scope == VariableScope::Var);
99 }
100 if (!isCatchBlock || name != caughtVariable) {
101 MemberMap::iterator it = members.find(akey: name);
102 if (it != members.end()) {
103 if (scope != VariableScope::Var || (*it).scope != VariableScope::Var)
104 return false;
105 if ((*it).type <= type) {
106 (*it).type = type;
107 (*it).function = function;
108 }
109 return true;
110 }
111 }
112
113 // hoist var declarations to the function level
114 if (contextType == ContextType::Block && (scope == VariableScope::Var && type != MemberType::FunctionDefinition))
115 return parent->addLocalVar(name, type, scope, function, endOfInitializer);
116
117 Member m;
118 m.type = type;
119 m.function = function;
120 m.scope = scope;
121 m.endOfInitializerLocation = endOfInitializer;
122 members.insert(akey: name, avalue: m);
123 return true;
124}
125
126Context::ResolvedName Context::resolveName(const QString &name, const QQmlJS::SourceLocation &accessLocation)
127{
128 int scope = 0;
129 Context *c = this;
130
131 ResolvedName result;
132
133 while (c) {
134 if (c->isWithBlock)
135 return result;
136
137 Context::Member m = c->findMember(name);
138 if (!c->parent && m.index < 0)
139 break;
140
141 if (m.type != Context::UndefinedMember) {
142 result.type = m.canEscape ? ResolvedName::Local : ResolvedName::Stack;
143 result.scope = scope;
144 result.index = m.index;
145 result.isConst = (m.scope == VariableScope::Const);
146 result.requiresTDZCheck = m.requiresTDZCheck(accessLocation, accessAcrossContextBoundaries: c != this);
147 if (c->isStrict && (name == QLatin1String("arguments") || name == QLatin1String("eval")))
148 result.isArgOrEval = true;
149 return result;
150 }
151 const int argIdx = c->findArgument(name);
152 if (argIdx != -1) {
153 if (c->argumentsCanEscape) {
154 result.index = argIdx + c->locals.size();
155 result.scope = scope;
156 result.type = ResolvedName::Local;
157 result.isConst = false;
158 return result;
159 } else {
160 result.index = argIdx + sizeof(CallData) / sizeof(StaticValue) - 1;
161 result.scope = 0;
162 result.type = ResolvedName::Stack;
163 result.isConst = false;
164 return result;
165 }
166 }
167 if (c->hasDirectEval) {
168 Q_ASSERT(!c->isStrict && c->contextType != ContextType::Block);
169 return result;
170 }
171
172 if (c->requiresExecutionContext)
173 ++scope;
174 c = c->parent;
175 }
176
177 if (c && c->contextType == ContextType::ESModule) {
178 for (int i = 0; i < c->importEntries.count(); ++i) {
179 if (c->importEntries.at(i).localName == name) {
180 result.index = i;
181 result.type = ResolvedName::Import;
182 result.isConst = true;
183 // We don't know at compile time whether the imported value is let/const or not.
184 result.requiresTDZCheck = true;
185 return result;
186 }
187 }
188 }
189
190 // ### can we relax the restrictions here?
191 if (c->contextType == ContextType::Eval)
192 return result;
193
194 if (c->contextType == ContextType::Binding || c->contextType == ContextType::ScriptImportedByQML)
195 result.type = ResolvedName::QmlGlobal;
196 else
197 result.type = ResolvedName::Global;
198 return result;
199}
200
201void Context::emitBlockHeader(Codegen *codegen)
202{
203 using Instruction = Moth::Instruction;
204 Moth::BytecodeGenerator *bytecodeGenerator = codegen->generator();
205
206 setupFunctionIndices(bytecodeGenerator);
207
208 if (requiresExecutionContext) {
209 if (blockIndex < 0) {
210 codegen->module()->blocks.append(t: this);
211 blockIndex = codegen->module()->blocks.count() - 1;
212 }
213
214 if (contextType == ContextType::Global) {
215 Instruction::PushScriptContext scriptContext;
216 scriptContext.index = blockIndex;
217 bytecodeGenerator->addInstruction(data: scriptContext);
218 } else if (contextType == ContextType::Block || (contextType == ContextType::Eval && !isStrict)) {
219 if (isCatchBlock) {
220 Instruction::PushCatchContext catchContext;
221 catchContext.index = blockIndex;
222 catchContext.name = codegen->registerString(name: caughtVariable);
223 bytecodeGenerator->addInstruction(data: catchContext);
224 } else {
225 Instruction::PushBlockContext blockContext;
226 blockContext.index = blockIndex;
227 bytecodeGenerator->addInstruction(data: blockContext);
228 }
229 } else if (contextType != ContextType::ESModule && contextType != ContextType::ScriptImportedByQML) {
230 Instruction::CreateCallContext createContext;
231 bytecodeGenerator->addInstruction(data: createContext);
232 }
233 }
234
235 if (contextType == ContextType::Block && sizeOfRegisterTemporalDeadZone > 0) {
236 Instruction::InitializeBlockDeadTemporalZone tdzInit;
237 tdzInit.firstReg = registerOffset + nRegisters - sizeOfRegisterTemporalDeadZone;
238 tdzInit.count = sizeOfRegisterTemporalDeadZone;
239 bytecodeGenerator->addInstruction(data: tdzInit);
240 }
241
242 if (usesThis) {
243 Q_ASSERT(!isStrict);
244 // make sure we convert this to an object
245 Instruction::ConvertThisToObject convert;
246 bytecodeGenerator->addInstruction(data: convert);
247 }
248 if (innerFunctionAccessesThis) {
249 Instruction::LoadReg load;
250 load.reg = CallData::This;
251 bytecodeGenerator->addInstruction(data: load);
252 Codegen::Reference r = codegen->referenceForName(QStringLiteral("this"), lhs: true);
253 r.storeConsumeAccumulator();
254 }
255 if (innerFunctionAccessesNewTarget) {
256 Instruction::LoadReg load;
257 load.reg = CallData::NewTarget;
258 bytecodeGenerator->addInstruction(data: load);
259 Codegen::Reference r = codegen->referenceForName(QStringLiteral("new.target"), lhs: true);
260 r.storeConsumeAccumulator();
261 }
262
263 if (contextType == ContextType::Global || contextType == ContextType::ScriptImportedByQML || (contextType == ContextType::Eval && !isStrict)) {
264 // variables in global code are properties of the global context object, not locals as with other functions.
265 for (Context::MemberMap::const_iterator it = members.constBegin(), cend = members.constEnd(); it != cend; ++it) {
266 if (it->isLexicallyScoped())
267 continue;
268 const QString &local = it.key();
269
270 Instruction::DeclareVar declareVar;
271 declareVar.isDeletable = (contextType == ContextType::Eval);
272 declareVar.varName = codegen->registerString(name: local);
273 bytecodeGenerator->addInstruction(data: declareVar);
274 }
275 }
276
277 if (contextType == ContextType::Function || contextType == ContextType::Binding || contextType == ContextType::ESModule) {
278 for (Context::MemberMap::iterator it = members.begin(), end = members.end(); it != end; ++it) {
279 if (it->canEscape && it->type == Context::ThisFunctionName) {
280 // move the function from the stack to the call context
281 Instruction::LoadReg load;
282 load.reg = CallData::Function;
283 bytecodeGenerator->addInstruction(data: load);
284 Instruction::StoreLocal store;
285 store.index = it->index;
286 bytecodeGenerator->addInstruction(data: store);
287 }
288 }
289 }
290
291 if (usesArgumentsObject == Context::ArgumentsObjectUsed) {
292 Q_ASSERT(contextType != ContextType::Block);
293 if (isStrict || (formals && !formals->isSimpleParameterList())) {
294 Instruction::CreateUnmappedArgumentsObject setup;
295 bytecodeGenerator->addInstruction(data: setup);
296 } else {
297 Instruction::CreateMappedArgumentsObject setup;
298 bytecodeGenerator->addInstruction(data: setup);
299 }
300 codegen->referenceForName(QStringLiteral("arguments"), lhs: false).storeConsumeAccumulator();
301 }
302
303 for (const Context::Member &member : qAsConst(t&: members)) {
304 if (member.function) {
305 const int function = codegen->defineFunction(name: member.function->name.toString(), ast: member.function, formals: member.function->formals, body: member.function->body);
306 codegen->loadClosure(index: function);
307 Codegen::Reference r = codegen->referenceForName(name: member.function->name.toString(), lhs: true);
308 r.storeConsumeAccumulator();
309 }
310 }
311}
312
313void Context::emitBlockFooter(Codegen *codegen)
314{
315 using Instruction = Moth::Instruction;
316 Moth::BytecodeGenerator *bytecodeGenerator = codegen->generator();
317
318 if (!requiresExecutionContext)
319 return;
320
321QT_WARNING_PUSH
322QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // the loads below are empty structs.
323 if (contextType == ContextType::Global)
324 bytecodeGenerator->addInstruction(data: Instruction::PopScriptContext());
325 else if (contextType != ContextType::ESModule && contextType != ContextType::ScriptImportedByQML)
326 bytecodeGenerator->addInstruction(data: Instruction::PopContext());
327QT_WARNING_POP
328}
329
330void Context::setupFunctionIndices(Moth::BytecodeGenerator *bytecodeGenerator)
331{
332 if (registerOffset != -1) {
333 // already computed, check for consistency
334 Q_ASSERT(registerOffset == bytecodeGenerator->currentRegister());
335 bytecodeGenerator->newRegisterArray(n: nRegisters);
336 return;
337 }
338 Q_ASSERT(locals.size() == 0);
339 Q_ASSERT(nRegisters == 0);
340 registerOffset = bytecodeGenerator->currentRegister();
341
342 QVector<Context::MemberMap::Iterator> localsInTDZ;
343 const auto registerLocal = [this, &localsInTDZ](Context::MemberMap::iterator member) {
344 if (member->isLexicallyScoped()) {
345 localsInTDZ << member;
346 } else {
347 member->index = locals.size();
348 locals.append(t: member.key());
349 }
350 };
351
352 QVector<Context::MemberMap::Iterator> registersInTDZ;
353 const auto allocateRegister = [bytecodeGenerator, &registersInTDZ](Context::MemberMap::iterator member) {
354 if (member->isLexicallyScoped())
355 registersInTDZ << member;
356 else
357 member->index = bytecodeGenerator->newRegister();
358 };
359
360 switch (contextType) {
361 case ContextType::ESModule:
362 case ContextType::Block:
363 case ContextType::Function:
364 case ContextType::Binding: {
365 for (Context::MemberMap::iterator it = members.begin(), end = members.end(); it != end; ++it) {
366 if (it->canEscape) {
367 registerLocal(it);
368 } else {
369 if (it->type == Context::ThisFunctionName)
370 it->index = CallData::Function;
371 else
372 allocateRegister(it);
373 }
374 }
375 break;
376 }
377 case ContextType::Global:
378 case ContextType::ScriptImportedByQML:
379 case ContextType::Eval:
380 for (Context::MemberMap::iterator it = members.begin(), end = members.end(); it != end; ++it) {
381 if (!it->isLexicallyScoped() && (contextType == ContextType::Global || contextType == ContextType::ScriptImportedByQML || !isStrict))
382 continue;
383 if (it->canEscape)
384 registerLocal(it);
385 else
386 allocateRegister(it);
387 }
388 break;
389 }
390
391 sizeOfLocalTemporalDeadZone = localsInTDZ.count();
392 for (auto &member: qAsConst(t&: localsInTDZ)) {
393 member->index = locals.size();
394 locals.append(t: member.key());
395 }
396
397 if (contextType == ContextType::ESModule && !localNameForDefaultExport.isEmpty()) {
398 if (!members.contains(akey: localNameForDefaultExport)) {
399 // allocate a local slot for the default export, to be used in
400 // CodeGen::visit(ExportDeclaration*).
401 locals.append(t: localNameForDefaultExport);
402 ++sizeOfLocalTemporalDeadZone;
403 }
404 }
405
406 sizeOfRegisterTemporalDeadZone = registersInTDZ.count();
407 firstTemporalDeadZoneRegister = bytecodeGenerator->currentRegister();
408 for (auto &member: qAsConst(t&: registersInTDZ))
409 member->index = bytecodeGenerator->newRegister();
410
411 nRegisters = bytecodeGenerator->currentRegister() - registerOffset;
412}
413
414QT_END_NAMESPACE
415

source code of qtdeclarative/src/qml/compiler/qv4compilercontext.cpp