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 | |
9 | QT_USE_NAMESPACE |
10 | using namespace QV4; |
11 | using namespace QV4::Compiler; |
12 | using namespace QQmlJS::AST; |
13 | using namespace QQmlJS; |
14 | |
15 | QT_BEGIN_NAMESPACE |
16 | |
17 | Context *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 | |
40 | bool 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 | |
54 | bool 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 | |
94 | Context::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 | |
174 | void Context::(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 | |
286 | void Context::(Codegen *codegen) |
287 | { |
288 | using Instruction = Moth::Instruction; |
289 | Moth::BytecodeGenerator *bytecodeGenerator = codegen->generator(); |
290 | |
291 | if (!requiresExecutionContext) |
292 | return; |
293 | |
294 | QT_WARNING_PUSH |
295 | QT_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()); |
300 | QT_WARNING_POP |
301 | } |
302 | |
303 | void 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, ®istersInTDZ](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 | |
387 | QT_END_NAMESPACE |
388 | |