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

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