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 | |
44 | QT_USE_NAMESPACE |
45 | using namespace QV4; |
46 | using namespace QV4::Compiler; |
47 | using namespace QQmlJS::AST; |
48 | using namespace QQmlJS; |
49 | |
50 | QT_BEGIN_NAMESPACE |
51 | |
52 | Context *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 | |
75 | bool 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 | |
89 | bool 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 | |
126 | Context::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 | |
201 | void Context::(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 | |
313 | void Context::(Codegen *codegen) |
314 | { |
315 | using Instruction = Moth::Instruction; |
316 | Moth::BytecodeGenerator *bytecodeGenerator = codegen->generator(); |
317 | |
318 | if (!requiresExecutionContext) |
319 | return; |
320 | |
321 | QT_WARNING_PUSH |
322 | QT_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()); |
327 | QT_WARNING_POP |
328 | } |
329 | |
330 | void 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, ®istersInTDZ](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 | |
414 | QT_END_NAMESPACE |
415 | |