Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlirbuilder.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 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// Qt-Security score:significant
4
6
7#include <private/qv4staticvalue_p.h>
8#include <private/qv4compileddata_p.h>
9#include <private/qqmljsparser_p.h>
10#include <private/qqmljslexer_p.h>
11#include <private/qv4compilerscanfunctions_p.h>
12#include <QCoreApplication>
13#include <QCryptographicHash>
14#include <QtCore/qtyperevision.h>
15
16#include <cmath>
17#include <iterator>
18
19QT_USE_NAMESPACE
20
21using namespace Qt::StringLiterals;
22
23static const quint32 emptyStringIndex = 0;
24using namespace QmlIR;
25using namespace QQmlJS;
26
27#define COMPILE_EXCEPTION(location, desc)
28 {
29 recordError(location, desc);
30 return false;
31 }
32
33void Object::simplifyRequiredProperties() {
34 // if a property of the current object was marked as required
35 // do not store that information in the ExtraData
36 // but rather mark the property as required
37 QSet<int> required;
38 for (auto it = this->requiredPropertyExtraDataBegin(); it != this->requiredPropertyExtraDataEnd(); ++it)
39 required.insert(it->nameIndex);
40 if (required.isEmpty())
41 return;
42 for (auto it = this->propertiesBegin(); it != this->propertiesEnd(); ++it) {
43 auto requiredIt = required.find(it->nameIndex());
44 if (requiredIt != required.end()) {
45 it->setIsRequired(true);
46 required.erase(requiredIt);
47 }
48 }
49 QmlIR::RequiredPropertyExtraData *prev = nullptr;
50 auto current = this->requiredPropertyExtraDatas->first;
51 while (current) {
52 if (required.contains(current->nameIndex))
53 prev = current;
54 else
55 requiredPropertyExtraDatas->unlink(prev, current);
56 current = current->next;
57 }
58}
59
60/*!
61 \internal
62
63 Reorders the alias data within the linked list of this object so that
64 aliases targeting other aliases on the same object come after their
65 targets. This ensures that dependencies are resolved and appended to
66 the property cache first, which is necessary because the runtime
67 assumes that the property cache position matches the alias table index.
68
69 Only considers same-object dependencies (where the alias's idIndex
70 resolves to idNameIndex). Cross-object dependencies are handled by
71 the multi-pass resolution in QQmlCOmponentAndAliasResolver.
72*/
73void Object::sortAliasDependencies(const Document *doc, QList<QQmlJS::DiagnosticMessage> *errors)
74{
75 using AliasArray = QVarLengthArray<Alias *, 8>;
76
77 AliasArray ordered;
78 ordered.reserve(aliasCount());
79
80 // if the default property is an alias, we need to later update the default property index
81 Alias *defaultPropertyAlias = nullptr;
82 qsizetype aliasCounter = 0;
83
84 // Collect aliases as nodes in a graph. Non-local ones are already ordered.
85 AliasArray nodes;
86 for (Alias *a = firstAlias(); a; ++aliasCounter, a = a->next) {
87 if (defaultPropertyIsAlias && aliasCounter == indexOfDefaultPropertyOrAlias) {
88 defaultPropertyAlias = a;
89 }
90 if (a->idIndex() == idNameIndex && idNameIndex != 0)
91 nodes.append(a);
92 else
93 ordered.append(a);
94 }
95
96 // Nothing to sort here.
97 if (nodes.isEmpty())
98 return;
99
100 // Collect dependencies as edges between nodes
101 QVarLengthArray<qsizetype, 8> edges(nodes.size(), -1);
102 for (qsizetype i = 0, end = nodes.size(); i < end; ++i) {
103 const QStringView propValue = doc->stringAt(nodes[i]->propertyNameIndex());
104 const int dotIdx = propValue.indexOf(QLatin1Char('.'));
105 const QStringView targetName = dotIdx != -1 ? propValue.left(dotIdx) : propValue;
106 if (targetName.isEmpty())
107 continue;
108
109 for (qsizetype j = 0; j < end; ++j) {
110 if (j != i && doc->stringAt(nodes[j]->nameIndex()) == targetName) {
111 edges[i] = j;
112 break;
113 }
114 }
115 }
116
117 // Simple DFS-based topological sort
118 for (qsizetype i = 0, end = nodes.size(); i < end; ++i) {
119 // Skip already inserted nodes
120 if (nodes[i] == nullptr)
121 continue;
122
123 // Follow the dependency chain to find the root.
124 QVarLengthArray<qsizetype, 8> chain;
125 for (qsizetype j = edges[i]; j != -1 && nodes[j]; j = edges[j]) {
126 if (!chain.contains(j)) {
127 chain.append(j);
128 continue;
129 }
130
131 const QV4::CompiledData::Location &location = nodes[j]->location();
132 QQmlJS::DiagnosticMessage error;
133 error.loc.startLine = location.line();
134 error.loc.startColumn = location.column();
135 error.loc.offset = QQmlJS::SourceLocation::offsetFrom(
136 doc->code, location.line(), location.column());
137 error.message = QCoreApplication::translate("QQmlParser", "Cyclic alias");
138 errors->append(std::move(error));
139 return;
140 }
141
142 // Emit in reverse (dependency first)
143 for (qsizetype k = chain.size() - 1; k >= 0; --k)
144 ordered.append(std::exchange(nodes[chain[k]], nullptr));
145 ordered.append(std::exchange(nodes[i], nullptr));
146 }
147
148 // Apply the sorted order to the alias list.
149 setFirstAlias(ordered[0]);
150
151 for (qsizetype i = 0, end = ordered.size() - 1; i < end; ++i)
152 ordered[i]->next = ordered[i + 1];
153 ordered.last()->next = nullptr;
154 if (defaultPropertyAlias) {
155 auto it = std::find(ordered.constBegin(), ordered.constEnd(), defaultPropertyAlias);
156 indexOfDefaultPropertyOrAlias = std::distance(ordered.constBegin(), it);
157 }
158}
159
160bool Parameter::initType(
161 QV4::CompiledData::ParameterType *paramType,
162 const QString &typeName, int typeNameIndex,
163 QV4::CompiledData::ParameterType::Flag listFlag)
164{
165 auto builtinType = stringToBuiltinType(typeName);
166 if (builtinType == QV4::CompiledData::CommonType::Invalid) {
167 if (typeName.isEmpty()) {
168 paramType->set(listFlag, 0);
169 return false;
170 }
171 Q_ASSERT(quint32(typeNameIndex) < (1u << 31));
172 paramType->set(listFlag, typeNameIndex);
173 } else {
174 Q_ASSERT(quint32(builtinType) < (1u << 31));
175 paramType->set(listFlag | QV4::CompiledData::ParameterType::Common,
176 static_cast<quint32>(builtinType));
177 }
178 return true;
179}
180
181QV4::CompiledData::CommonType Parameter::stringToBuiltinType(const QString &typeName)
182{
183 static const struct TypeNameToType {
184 const char *name;
185 size_t nameLength;
186 QV4::CompiledData::CommonType type;
187 } propTypeNameToTypes[] = {
188 { "void", strlen("void"), QV4::CompiledData::CommonType::Void },
189 { "int", strlen("int"), QV4::CompiledData::CommonType::Int },
190 { "bool", strlen("bool"), QV4::CompiledData::CommonType::Bool },
191 { "double", strlen("double"), QV4::CompiledData::CommonType::Real },
192 { "real", strlen("real"), QV4::CompiledData::CommonType::Real },
193 { "string", strlen("string"), QV4::CompiledData::CommonType::String },
194 { "url", strlen("url"), QV4::CompiledData::CommonType::Url },
195 { "date", strlen("date"), QV4::CompiledData::CommonType::DateTime },
196 { "regexp", strlen("regexp"), QV4::CompiledData::CommonType::RegExp },
197 { "rect", strlen("rect"), QV4::CompiledData::CommonType::Rect },
198 { "point", strlen("point"), QV4::CompiledData::CommonType::Point },
199 { "size", strlen("size"), QV4::CompiledData::CommonType::Size },
200 { "variant", strlen("variant"), QV4::CompiledData::CommonType::Var },
201 { "var", strlen("var"), QV4::CompiledData::CommonType::Var }
202 };
203 static const int propTypeNameToTypesCount = sizeof(propTypeNameToTypes) /
204 sizeof(propTypeNameToTypes[0]);
205
206 for (int typeIndex = 0; typeIndex < propTypeNameToTypesCount; ++typeIndex) {
207 const TypeNameToType *t = propTypeNameToTypes + typeIndex;
208 if (typeName == QLatin1String(t->name, static_cast<int>(t->nameLength))) {
209 return t->type;
210 }
211 }
212 return QV4::CompiledData::CommonType::Invalid;
213}
214
215void Object::init(QQmlJS::MemoryPool *pool, int typeNameIndex, int idIndex,
216 const QV4::CompiledData::Location &loc)
217{
218 Q_ASSERT(loc.line() > 0 && loc.column() > 0);
219 inheritedTypeNameIndex = typeNameIndex;
220 location = loc;
221 idNameIndex = idIndex;
222 id = -1;
223 indexOfDefaultPropertyOrAlias = -1;
224 defaultPropertyIsAlias = false;
225 flags = QV4::CompiledData::Object::NoFlag;
226 properties = pool->New<PoolList<Property> >();
227 aliases = pool->New<PoolList<Alias> >();
228 qmlEnums = pool->New<PoolList<Enum>>();
229 qmlSignals = pool->New<PoolList<Signal> >();
230 bindings = pool->New<PoolList<Binding> >();
231 functions = pool->New<PoolList<Function> >();
232 functionsAndExpressions = pool->New<PoolList<CompiledFunctionOrExpression> >();
233 inlineComponents = pool->New<PoolList<InlineComponent>>();
234 requiredPropertyExtraDatas = pool->New<PoolList<RequiredPropertyExtraData>>();
235 declarationsOverride = nullptr;
236}
237
238QString IRBuilder::sanityCheckFunctionNames(Object *obj, QQmlJS::SourceLocation *errorLocation)
239{
240 QSet<int> functionNames;
241 for (auto functionit = obj->functionsBegin(); functionit != obj->functionsEnd(); ++functionit) {
242 Function *f = functionit.ptr;
243 errorLocation->startLine = f->location.line();
244 errorLocation->startColumn = f->location.column();
245 if (f->isQmlFunction) {
246 if (functionNames.contains(f->nameIndex))
247 return tr("Duplicate method name");
248 functionNames.insert(f->nameIndex);
249 }
250
251 for (auto signalit = obj->signalsBegin(); signalit != obj->signalsEnd(); ++signalit) {
252 QmlIR::Signal *s = signalit.ptr;
253 if (s->nameIndex == f->nameIndex)
254 return tr("Duplicate method name");
255 }
256
257 const QString name = stringAt(f->nameIndex);
258 Q_ASSERT(!f->isQmlFunction || !name.isEmpty());
259 if (!name.isEmpty() && name.at(0).isUpper())
260 return tr("Method names cannot begin with an upper case letter");
261 if (QV4::Compiler::Codegen::isNameGlobal(name))
262 return tr("Illegal method name");
263 }
264 return QString(); // no error
265}
266
267QString Object::appendEnum(Enum *enumeration)
268{
269 Object *target = declarationsOverride;
270 if (!target)
271 target = this;
272
273 for (Enum *e = qmlEnums->first; e; e = e->next) {
274 if (e->nameIndex == enumeration->nameIndex)
275 return tr("Duplicate scoped enum name");
276 }
277
278 target->qmlEnums->append(enumeration);
279 return QString(); // no error
280}
281
282QString Object::appendSignal(Signal *signal)
283{
284 Object *target = declarationsOverride;
285 if (!target)
286 target = this;
287
288 for (Signal *s = qmlSignals->first; s; s = s->next) {
289 if (s->nameIndex == signal->nameIndex)
290 return tr("Duplicate signal name");
291 }
292
293 target->qmlSignals->append(signal);
294 return QString(); // no error
295}
296
297QString Object::appendProperty(Property *prop, const QString &propertyName, bool isDefaultProperty, const QQmlJS::SourceLocation &defaultToken, QQmlJS::SourceLocation *errorLocation)
298{
299 Object *target = declarationsOverride;
300 if (!target)
301 target = this;
302
303 for (Property *p = target->properties->first; p; p = p->next)
304 if (p->nameIndex() == prop->nameIndex())
305 return tr("Duplicate property name");
306
307 for (Alias *a = target->aliases->first; a; a = a->next)
308 if (a->nameIndex() == prop->nameIndex())
309 return tr("Property duplicates alias name");
310
311 if (propertyName.constData()->isUpper())
312 return tr("Property names cannot begin with an upper case letter");
313
314 const int index = target->properties->append(prop);
315 if (isDefaultProperty) {
316 if (target->indexOfDefaultPropertyOrAlias != -1) {
317 *errorLocation = defaultToken;
318 return tr("Duplicate default property");
319 }
320 target->indexOfDefaultPropertyOrAlias = index;
321 }
322 return QString(); // no error
323}
324
325QString Object::appendAlias(Alias *alias, const QString &aliasName, bool isDefaultProperty, const QQmlJS::SourceLocation &defaultToken, QQmlJS::SourceLocation *errorLocation)
326{
327 Object *target = declarationsOverride;
328 if (!target)
329 target = this;
330
331 const auto aliasWithSameName = std::find_if(target->aliases->begin(), target->aliases->end(), [&alias](const Alias &targetAlias){
332 return targetAlias.nameIndex() == alias->nameIndex();
333 });
334 if (aliasWithSameName != target->aliases->end())
335 return tr("Duplicate alias name");
336
337 const auto aliasSameAsProperty = std::find_if(target->properties->begin(), target->properties->end(), [&alias](const Property &targetProp){
338 return targetProp.nameIndex() == alias->nameIndex();
339 });
340
341 if (aliasSameAsProperty != target->properties->end())
342 return tr("Alias has same name as existing property");
343
344 if (aliasName.constData()->isUpper())
345 return tr("Alias names cannot begin with an upper case letter");
346
347 const int index = target->aliases->append(alias);
348
349 if (isDefaultProperty) {
350 if (target->indexOfDefaultPropertyOrAlias != -1) {
351 *errorLocation = defaultToken;
352 return tr("Duplicate default property");
353 }
354 target->indexOfDefaultPropertyOrAlias = index;
355 target->defaultPropertyIsAlias = true;
356 }
357
358 return QString(); // no error
359}
360
361void Object::appendFunction(QmlIR::Function *f)
362{
363 // Unlike properties, a function definition inside a grouped property does not go into
364 // the surrounding object. It's been broken since the Qt 5 era, and the semantics
365 // seems super confusing, so it wouldn't make sense to support that.
366 Q_ASSERT(!declarationsOverride);
367 functions->append(f);
368}
369
370void Object::appendInlineComponent(InlineComponent *ic)
371{
372 inlineComponents->append(ic);
373}
374
375void Object::appendRequiredPropertyExtraData(RequiredPropertyExtraData *extraData)
376{
377 requiredPropertyExtraDatas->append(extraData);
378}
379
380QString Object::appendBinding(Binding *b, bool isListBinding)
381{
382 const bool bindingToDefaultProperty = (b->propertyNameIndex == quint32(0));
383 if (!isListBinding
384 && !bindingToDefaultProperty
385 && b->type() != QV4::CompiledData::Binding::Type_GroupProperty
386 && b->type() != QV4::CompiledData::Binding::Type_AttachedProperty
387 && !b->hasFlag(QV4::CompiledData::Binding::IsOnAssignment)) {
388 Binding *existing = findBinding(b->propertyNameIndex);
389 if (existing
390 && existing->isValueBinding() == b->isValueBinding()
391 && !existing->hasFlag(QV4::CompiledData::Binding::IsOnAssignment)) {
392 return tr("Property value set multiple times");
393 }
394 }
395 if (bindingToDefaultProperty)
396 bindings->append(b);
397 else
398 bindings->prepend(b);
399 return QString(); // no error
400}
401
402Binding *Object::findBinding(quint32 nameIndex) const
403{
404 for (Binding *b = bindings->first; b; b = b->next)
405 if (b->propertyNameIndex == nameIndex)
406 return b;
407 return nullptr;
408}
409
410QString Object::bindingAsString(Document *doc, int scriptIndex) const
411{
412 CompiledFunctionOrExpression *foe = functionsAndExpressions->slowAt(scriptIndex);
413 QQmlJS::AST::Node *node = foe->node;
414 if (QQmlJS::AST::ExpressionStatement *exprStmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(node))
415 node = exprStmt->expression;
416 QQmlJS::SourceLocation start = node->firstSourceLocation();
417 QQmlJS::SourceLocation end = node->lastSourceLocation();
418 return doc->code.mid(start.offset, end.offset + end.length - start.offset);
419}
420
421QStringList Signal::parameterStringList(const QV4::Compiler::StringTableGenerator *stringPool) const
422{
423 QStringList result;
424 result.reserve(parameters->count);
425 for (Parameter *param = parameters->first; param; param = param->next)
426 result << stringPool->stringForIndex(param->nameIndex);
427 return result;
428}
429
430Document::Document(const QString &fileName, const QString &finalUrl, bool debugMode)
431 : jsModule(fileName, finalUrl, debugMode)
432 , program(nullptr)
433 , jsGenerator(&jsModule)
434{
435}
436
437ScriptDirectivesCollector::ScriptDirectivesCollector(Document *doc)
438 : document(doc)
439 , engine(&doc->jsParserEngine)
440 , jsGenerator(&doc->jsGenerator)
441{
442}
443
444void ScriptDirectivesCollector::pragmaLibrary()
445{
446 document->jsModule.unitFlags |= QV4::CompiledData::Unit::IsSharedLibrary;
447}
448
449void ScriptDirectivesCollector::importFile(const QString &jsfile, const QString &module, int lineNumber, int column)
450{
451 QV4::CompiledData::Import *import = engine->pool()->New<QV4::CompiledData::Import>();
452 import->type = QV4::CompiledData::Import::ImportScript;
453 import->uriIndex = jsGenerator->registerString(jsfile);
454 import->qualifierIndex = jsGenerator->registerString(module);
455 import->location.set(lineNumber, column);
456 document->imports << import;
457}
458
459void ScriptDirectivesCollector::importModule(const QString &uri, const QString &version, const QString &module, int lineNumber, int column)
460{
461 QV4::CompiledData::Import *import = engine->pool()->New<QV4::CompiledData::Import>();
462 import->type = QV4::CompiledData::Import::ImportLibrary;
463 import->uriIndex = jsGenerator->registerString(uri);
464 import->version = IRBuilder::extractVersion(version);
465 import->qualifierIndex = jsGenerator->registerString(module);
466 import->location.set(lineNumber, column);
467 document->imports << import;
468}
469
470IRBuilder::IRBuilder()
471 : _object(nullptr)
472 , _propertyDeclaration(nullptr)
473 , pool(nullptr)
474 , jsGenerator(nullptr)
475{
476}
477
478bool IRBuilder::generateFromQml(const QString &code, const QString &url, Document *output)
479{
480 QQmlJS::AST::UiProgram *program = nullptr;
481 {
482 QQmlJS::Lexer lexer(&output->jsParserEngine);
483 lexer.setCode(code, /*line = */ 1);
484
485 QQmlJS::Parser parser(&output->jsParserEngine);
486
487 const bool parseResult = parser.parse();
488 const auto diagnosticMessages = parser.diagnosticMessages();
489 if (!parseResult || !diagnosticMessages.isEmpty()) {
490 // Extract errors from the parser
491 for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) {
492 if (m.isWarning()) {
493 qWarning("%s:%d : %s", qPrintable(url), m.loc.startLine, qPrintable(m.message));
494 continue;
495 }
496
497 errors << m;
498 }
499
500 if (!errors.isEmpty() || !parseResult)
501 return false;
502 }
503 program = parser.ast();
504 Q_ASSERT(program);
505 }
506
507 output->code = code;
508 output->program = program;
509
510 qSwap(_imports, output->imports);
511 qSwap(_pragmas, output->pragmas);
512 qSwap(_objects, output->objects);
513 this->pool = output->jsParserEngine.pool();
514 this->jsGenerator = &output->jsGenerator;
515
516 Q_ASSERT(registerString(QString()) == emptyStringIndex);
518 sourceCode = code;
520 accept(program->headers);
522 if (program->members->next) {
523 QQmlJS::SourceLocation loc = program->members->next->firstSourceLocation();
524 recordError(loc, QCoreApplication::translate("QQmlParser", "Unexpected object definition"));
525 return false;
527
528 QQmlJS::AST::UiObjectDefinition *rootObject = QQmlJS::AST::cast<QQmlJS::AST::UiObjectDefinition*>(program->members->member);
529 Q_ASSERT(rootObject);
530 int rootObjectIndex = -1;
531 if (defineQMLObject(&rootObjectIndex, rootObject)) {
532 Q_ASSERT(rootObjectIndex == 0);
533 }
534
535 qSwap(_imports, output->imports);
536 qSwap(_pragmas, output->pragmas);
537 qSwap(_objects, output->objects);
538
539 for (Object *object: std::as_const(output->objects)) {
540 object->simplifyRequiredProperties();
541
542 // Reorder aliases so that same-object dependencies come before their
543 // dependents. This later ensures the property cache ordering matches
544 // the alias table, which the runtime relies on.
545 object->sortAliasDependencies(output, &errors);
546 }
547
548 return errors.isEmpty();
549}
550
551bool IRBuilder::visit(QQmlJS::AST::UiProgram *)
552{
553 Q_ASSERT(!"should not happen");
554 return false;
555}
556
557bool IRBuilder::visit(QQmlJS::AST::UiObjectDefinition *node)
558{
559 // The grammar can't distinguish between two different definitions here:
560 // Item { ... }
561 // versus
562 // font { ... }
563 // The former is a new binding with no property name and "Item" as type name,
564 // and the latter is a binding to the font property with no type name but
565 // only initializer.
566
567 QQmlJS::AST::UiQualifiedId *lastId = node->qualifiedTypeNameId;
568 while (lastId->next)
569 lastId = lastId->next;
570 bool isType = lastId->name.data()->isUpper();
571 if (isType) {
572 int idx = 0;
573 if (!defineQMLObject(&idx, node))
574 return false;
575 const QQmlJS::SourceLocation nameLocation = node->qualifiedTypeNameId->identifierToken;
576 appendBinding(nameLocation, nameLocation, emptyStringIndex, idx);
577 } else {
578 int idx = 0;
579 const QQmlJS::SourceLocation location = node->qualifiedTypeNameId->firstSourceLocation();
580 if (!defineQMLObject(
581 &idx, /*qualfied type name id*/nullptr,
582 { location.startLine, location.startColumn }, node->initializer,
583 /*declarations should go here*/_object)) {
584 return false;
585 }
586 appendBinding(node->qualifiedTypeNameId, idx);
587 }
588 return false;
589}
590
591bool IRBuilder::visit(QQmlJS::AST::UiInlineComponent *ast)
592{
593 int idx = -1;
594 if (insideInlineComponent) {
595 recordError(ast->firstSourceLocation(), QLatin1String("Nested inline components are not supported"));
596 return false;
597 }
598 if (inlineComponentsNames.contains(ast->name.toString())) {
599 recordError(ast->firstSourceLocation(), QLatin1String("Inline component names must be unique per file"));
600 return false;
601 } else {
602 inlineComponentsNames.insert(ast->name.toString());
603 }
604 {
605 QScopedValueRollback<bool> rollBack {insideInlineComponent, true};
606 if (!defineQMLObject(&idx, ast->component))
607 return false;
608 }
609 Q_ASSERT(idx > 0);
610 Object* definedObject = _objects.at(idx);
611 definedObject->flags |= QV4::CompiledData::Object::IsInlineComponentRoot;
612 definedObject->flags |= QV4::CompiledData::Object::IsPartOfInlineComponent;
613 auto inlineComponent = New<InlineComponent>();
614 inlineComponent->nameIndex = registerString(ast->name.toString());
615 inlineComponent->objectIndex = idx;
616 auto location = ast->firstSourceLocation();
617 inlineComponent->location.set(location.startLine, location.startColumn);
618 _object->appendInlineComponent(inlineComponent);
619 return false;
620}
621
622bool IRBuilder::visit(QQmlJS::AST::UiObjectBinding *node)
623{
624 int idx = 0;
625 const QQmlJS::SourceLocation location = node->qualifiedTypeNameId->firstSourceLocation();
626 if (!defineQMLObject(&idx, node->qualifiedTypeNameId,
627 { location.startLine, location.startColumn }, node->initializer)) {
628 return false;
629 }
630 appendBinding(node->qualifiedId, idx, node->hasOnToken);
631 return false;
632}
633
634bool IRBuilder::visit(QQmlJS::AST::UiScriptBinding *node)
635{
636 appendBinding(node->qualifiedId, node->statement, node);
637 return false;
638}
639
640bool IRBuilder::visit(QQmlJS::AST::UiArrayBinding *node)
641{
642 const QQmlJS::SourceLocation qualifiedNameLocation = node->qualifiedId->identifierToken;
643 Object *object = nullptr;
644 QQmlJS::AST::UiQualifiedId *name = node->qualifiedId;
645 if (!resolveQualifiedId(&name, &object))
646 return false;
647
648 qSwap(_object, object);
649
650 const int propertyNameIndex = registerString(name->name.toString());
651
652 if (bindingsTarget()->findBinding(propertyNameIndex) != nullptr) {
653 recordError(name->identifierToken, tr("Property value set multiple times"));
654 return false;
655 }
656
657 QVarLengthArray<QQmlJS::AST::UiArrayMemberList *, 16> memberList;
658 QQmlJS::AST::UiArrayMemberList *member = node->members;
659 while (member) {
660 memberList.append(member);
661 member = member->next;
662 }
663 for (int i = memberList.size() - 1; i >= 0; --i) {
664 member = memberList.at(i);
665 QQmlJS::AST::UiObjectDefinition *def = QQmlJS::AST::cast<QQmlJS::AST::UiObjectDefinition*>(member->member);
666
667 int idx = 0;
668 if (!defineQMLObject(&idx, def))
669 return false;
670 appendBinding(qualifiedNameLocation, name->identifierToken, propertyNameIndex, idx, /*isListItem*/ true);
671 }
672
673 qSwap(_object, object);
674 return false;
675}
676
677void IRBuilder::accept(QQmlJS::AST::Node *node)
678{
679 QQmlJS::AST::Node::accept(node, this);
680}
681
682bool IRBuilder::defineQMLObject(
683 int *objectIndex, QQmlJS::AST::UiQualifiedId *qualifiedTypeNameId,
684 const QV4::CompiledData::Location &location, QQmlJS::AST::UiObjectInitializer *initializer,
685 Object *declarationsOverride)
686{
687 if (QQmlJS::AST::UiQualifiedId *lastName = qualifiedTypeNameId) {
688 while (lastName->next)
689 lastName = lastName->next;
690 if (!lastName->name.constData()->isUpper()) {
691 recordError(lastName->identifierToken, tr("Expected type name"));
692 return false;
693 }
694 }
695
696 Object *obj = New<Object>();
697
698 _objects.append(obj);
699 *objectIndex = _objects.size() - 1;
700 qSwap(_object, obj);
701
702 _object->init(pool, registerString(asString(qualifiedTypeNameId)), emptyStringIndex, location);
703 _object->declarationsOverride = declarationsOverride;
704 if (insideInlineComponent) {
705 _object->flags |= QV4::CompiledData::Object::IsPartOfInlineComponent;
706 }
707
708 // A new object is also a boundary for property declarations.
709 Property *declaration = nullptr;
710 qSwap(_propertyDeclaration, declaration);
711
712 accept(initializer);
713
714 qSwap(_propertyDeclaration, declaration);
715
716 qSwap(_object, obj);
717
718 if (!errors.isEmpty())
719 return false;
720
721 QQmlJS::SourceLocation loc;
722 QString error = sanityCheckFunctionNames(obj, &loc);
723 if (!error.isEmpty()) {
724 recordError(loc, error);
725 return false;
726 }
727
728 return true;
729}
730
731bool IRBuilder::visit(QQmlJS::AST::UiImport *node)
732{
733 QString uri;
734 QV4::CompiledData::Import *import = New<QV4::CompiledData::Import>();
735
736 if (!node->fileName.isNull()) {
737 uri = node->fileName.toString();
738
739 if (uri.endsWith(QLatin1String(".js")) || uri.endsWith(QLatin1String(".mjs"))) {
740 import->type = QV4::CompiledData::Import::ImportScript;
741 } else {
742 import->type = QV4::CompiledData::Import::ImportFile;
743 }
744 } else {
745 import->type = QV4::CompiledData::Import::ImportLibrary;
746 uri = asString(node->importUri);
747 }
748
749 import->qualifierIndex = emptyStringIndex;
750
751 // Qualifier
752 if (!node->importId.isNull()) {
753 QString qualifier = node->importId.toString();
754 if (!qualifier.at(0).isUpper()) {
755 recordError(node->importIdToken, QCoreApplication::translate("QQmlParser","Invalid import qualifier '%1': must start with an uppercase letter").arg(qualifier));
756 return false;
757 }
758 if (qualifier == QLatin1String("Qt")) {
759 recordError(node->importIdToken, QCoreApplication::translate("QQmlParser","Reserved name \"Qt\" cannot be used as an qualifier"));
760 return false;
761 }
762 import->qualifierIndex = registerString(qualifier);
763
764 // Check for script qualifier clashes
765 bool isScript = import->type == QV4::CompiledData::Import::ImportScript;
766 for (int ii = 0; ii < _imports.size(); ++ii) {
767 const QV4::CompiledData::Import *other = _imports.at(ii);
768 bool otherIsScript = other->type == QV4::CompiledData::Import::ImportScript;
769
770 if ((isScript || otherIsScript) && qualifier == jsGenerator->stringForIndex(other->qualifierIndex)) {
771 recordError(node->importIdToken, QCoreApplication::translate("QQmlParser","Script import qualifiers must be unique."));
772 return false;
773 }
774 }
775
776 } else if (import->type == QV4::CompiledData::Import::ImportScript) {
777 recordError(node->fileNameToken, QCoreApplication::translate("QQmlParser","Script import requires a qualifier"));
778 return false;
779 }
780
781 if (node->version) {
782 import->version = node->version->version;
783 } else {
784 // Otherwise initialize the major and minor version to invalid to signal "latest".
785 import->version = QTypeRevision();
786 }
787
788 import->location.set(node->importToken.startLine, node->importToken.startColumn);
789
790 import->uriIndex = registerString(uri);
791
792 _imports.append(import);
793
794 return false;
795}
796
797
798template<typename Argument>
800{
801 static bool run(IRBuilder *builder, QQmlJS::AST::UiPragma *node, Pragma *pragma)
802 {
803 Q_ASSERT(builder);
804 Q_ASSERT(node);
805 Q_ASSERT(pragma);
806
807 if (!isUnique(builder)) {
808 builder->recordError(
809 node->pragmaToken, QCoreApplication::translate(
810 "QQmlParser", "Multiple %1 pragmas found").arg(name()));
811 return false;
812 }
813
814 pragma->type = type();
815
816 if (QQmlJS::AST::UiPragmaValueList *bad = assign(pragma, node->values)) {
817 builder->recordError(
818 node->pragmaToken, QCoreApplication::translate(
819 "QQmlParser", "Unknown %1 '%2' in pragma").arg(name(), bad->value));
820 return false;
821 }
822
823 return true;
824 }
825
826private:
827 static constexpr Pragma::PragmaType type()
828 {
835 } else if constexpr (std::is_same_v<Argument, Pragma::NativeMethodBehaviorValue>) {
837 } else if constexpr (std::is_same_v<Argument, Pragma::ValueTypeBehaviorValue>) {
839 }
840
842 }
843
844 template<typename F>
847 {
848 for (QQmlJS::AST::UiPragmaValueList *i = input; i; i = i->next) {
849 if (!process(i->value))
850 return i;
851 }
852 return nullptr;
853 }
854
857 {
858 // We could use QMetaEnum here to make the code more compact,
859 // but it's probably more expensive.
860
863 if (value == "Unbound"_L1) {
865 return true;
866 }
867 if (value == "Bound"_L1) {
869 return true;
870 }
871 return false;
872 });
875 if (value == "Append"_L1) {
877 return true;
878 }
879 if (value == "Replace"_L1) {
881 return true;
882 }
883 if (value == "ReplaceIfNotDefault"_L1) {
885 return true;
886 }
887 return false;
888 });
891 if (value == "Ignored"_L1) {
893 return true;
894 }
895 if (value == "Enforced"_L1) {
897 return true;
898 }
899 return false;
900 });
901 } else if constexpr (std::is_same_v<Argument, Pragma::NativeMethodBehaviorValue>) {
903 if (value == "AcceptThisObject"_L1) {
905 return true;
906 }
907 if (value == "RejectThisObject"_L1) {
909 return true;
910 }
911 return false;
912 });
913 } else if constexpr (std::is_same_v<Argument, Pragma::ValueTypeBehaviorValue>) {
916 const auto setFlag = [pragma](Pragma::ValueTypeBehaviorValue flag, bool value) {
919 .setFlag(flag, value).toInt();
920 };
921
922 if (value == "Reference"_L1) {
923 setFlag(Pragma::Copy, false);
924 return true;
925 }
926 if (value == "Copy"_L1) {
927 setFlag(Pragma::Copy, true);
928 return true;
929 }
930
931 if (value == "Inaddressable"_L1) {
932 setFlag(Pragma::Addressable, false);
933 return true;
934 }
935 if (value == "Addressable"_L1) {
936 setFlag(Pragma::Addressable, true);
937 return true;
938 }
939
940 if (value == "Inassertable"_L1) {
941 setFlag(Pragma::Assertable, false);
942 return true;
943 }
944 if (value == "Assertable"_L1) {
945 setFlag(Pragma::Assertable, true);
946 return true;
947 }
948
949 return false;
950 });
951 }
952
953 Q_UNREACHABLE_RETURN(nullptr);
954 }
955
956 static bool isUnique(const IRBuilder *builder)
957 {
958 for (const Pragma *prev : builder->_pragmas) {
959 if (prev->type == type())
960 return false;
961 }
962 return true;
963 };
964
965 static QLatin1StringView name()
966 {
967 switch (type()) {
968 case Pragma::ListPropertyAssignBehavior:
969 return "list property assign behavior"_L1;
970 case Pragma::ComponentBehavior:
971 return "component behavior"_L1;
972 case Pragma::FunctionSignatureBehavior:
973 return "function signature behavior"_L1;
974 case Pragma::NativeMethodBehavior:
975 return "native method behavior"_L1;
976 case Pragma::ValueTypeBehavior:
977 return "value type behavior"_L1;
978 default:
979 break;
980 }
981 Q_UNREACHABLE_RETURN(QLatin1StringView());
982 }
983};
984
985bool IRBuilder::visit(QQmlJS::AST::UiPragma *node)
986{
987 Pragma *pragma = New<Pragma>();
988
989 if (!node->name.isNull()) {
990 if (node->name == "Singleton"_L1) {
991 pragma->type = Pragma::Singleton;
992 } else if (node->name == "Strict"_L1) {
993 pragma->type = Pragma::Strict;
994 } else if (node->name == "ComponentBehavior"_L1) {
995 if (!PragmaParser<Pragma::ComponentBehaviorValue>::run(this, node, pragma))
996 return false;
997 } else if (node->name == "ListPropertyAssignBehavior"_L1) {
998 if (!PragmaParser<Pragma::ListPropertyAssignBehaviorValue>::run(this, node, pragma))
999 return false;
1000 } else if (node->name == "FunctionSignatureBehavior"_L1) {
1001 if (!PragmaParser<Pragma::FunctionSignatureBehaviorValue>::run(this, node, pragma))
1002 return false;
1003 } else if (node->name == "NativeMethodBehavior"_L1) {
1004 if (!PragmaParser<Pragma::NativeMethodBehaviorValue>::run(this, node, pragma))
1005 return false;
1006 } else if (node->name == "ValueTypeBehavior"_L1) {
1007 if (!PragmaParser<Pragma::ValueTypeBehaviorValue>::run(this, node, pragma))
1008 return false;
1009 } else if (node->name == "Translator"_L1) {
1010 pragma->type = Pragma::Translator;
1011 pragma->translationContextIndex = registerString(node->values->value.toString());
1012
1013 } else {
1014 recordError(node->pragmaToken, QCoreApplication::translate(
1015 "QQmlParser", "Unknown pragma '%1'").arg(node->name));
1016 return false;
1017 }
1018 } else {
1019 recordError(node->pragmaToken, QCoreApplication::translate(
1020 "QQmlParser", "Empty pragma found"));
1021 return false;
1022 }
1023
1024 pragma->location.set(node->pragmaToken.startLine, node->pragmaToken.startColumn);
1025 _pragmas.append(pragma);
1026
1027 return false;
1028}
1029
1030static QStringList astNodeToStringList(QQmlJS::AST::Node *node)
1031{
1032 if (node->kind == QQmlJS::AST::Node::Kind_IdentifierExpression) {
1033 QString name =
1034 static_cast<QQmlJS::AST::IdentifierExpression *>(node)->name.toString();
1035 return QStringList() << name;
1036 } else if (node->kind == QQmlJS::AST::Node::Kind_FieldMemberExpression) {
1037 QQmlJS::AST::FieldMemberExpression *expr = static_cast<QQmlJS::AST::FieldMemberExpression *>(node);
1038
1039 QStringList rv = astNodeToStringList(expr->base);
1040 if (rv.isEmpty())
1041 return rv;
1042 rv.append(expr->name.toString());
1043 return rv;
1044 }
1045 return QStringList();
1046}
1047
1048bool IRBuilder::visit(QQmlJS::AST::UiEnumDeclaration *node)
1049{
1050 Enum *enumeration = New<Enum>();
1051 QString enumName = node->name.toString();
1052 enumeration->nameIndex = registerString(enumName);
1053
1054 if (enumName.at(0).isLower())
1055 COMPILE_EXCEPTION(node->enumToken, tr("Scoped enum names must begin with an upper case letter"));
1056
1057 enumeration->location.set(node->enumToken.startLine, node->enumToken.startColumn);
1058
1059 enumeration->enumValues = New<PoolList<EnumValue>>();
1060
1061 QQmlJS::AST::UiEnumMemberList *e = node->members;
1062 while (e) {
1063 EnumValue *enumValue = New<EnumValue>();
1064 QString member = e->member.toString();
1065 enumValue->nameIndex = registerString(member);
1066 if (member.at(0).isLower())
1067 COMPILE_EXCEPTION(e->memberToken, tr("Enum names must begin with an upper case letter"));
1068
1069 double part;
1070 if (std::modf(e->value, &part) != 0.0)
1071 COMPILE_EXCEPTION(e->valueToken, tr("Enum value must be an integer"));
1072 if (e->value > std::numeric_limits<qint32>::max() || e->value < std::numeric_limits<qint32>::min())
1073 COMPILE_EXCEPTION(e->valueToken, tr("Enum value out of range"));
1074 enumValue->value = e->value;
1075 enumeration->enumValues->append(enumValue);
1076
1077 e = e->next;
1078 }
1079
1080 QString error = _object->appendEnum(enumeration);
1081 if (!error.isEmpty()) {
1082 recordError(node->enumToken, error);
1083 return false;
1084 }
1085
1086 return false;
1087}
1088
1089
1090bool IRBuilder::visit(QQmlJS::AST::UiPublicMember *node)
1091{
1092 if (node->type == QQmlJS::AST::UiPublicMember::Signal) {
1093 Signal *signal = New<Signal>();
1094 const QString signalName = node->name.toString();
1095 signal->nameIndex = registerString(signalName);
1096
1097 QQmlJS::SourceLocation loc = node->typeToken;
1098 signal->location.set(loc.startLine, loc.startColumn);
1099
1100 signal->parameters = New<PoolList<Parameter> >();
1101
1102 QQmlJS::AST::UiParameterList *p = node->parameters;
1103 while (p) {
1104 if (!p->type) {
1105 recordError(node->typeToken, QCoreApplication::translate("QQmlParser","Expected parameter type"));
1106 return false;
1107 }
1108
1109 Parameter *param = New<Parameter>();
1110 param->nameIndex = registerString(p->name.toString());
1111 if (!Parameter::initType(
1112 &param->type, [this](const QString &str) { return registerString(str); },
1113 p->type)) {
1114 QString errStr = QCoreApplication::translate("QQmlParser","Invalid signal parameter type: ");
1115 errStr.append(p->type->toString());
1116 recordError(node->typeToken, errStr);
1117 return false;
1118 }
1119 signal->parameters->append(param);
1120 p = p->next;
1121 }
1122
1123 for (const QChar &ch : signalName) {
1124 if (ch.isLower())
1125 break;
1126 if (ch.isUpper()) {
1127 COMPILE_EXCEPTION(node->identifierToken,
1128 tr("Signal names cannot begin with an upper case letter"));
1129 }
1130 }
1131
1132 if (QV4::Compiler::Codegen::isNameGlobal(signalName))
1133 COMPILE_EXCEPTION(node->identifierToken, tr("Illegal signal name"));
1134
1135 QString error = _object->appendSignal(signal);
1136 if (!error.isEmpty()) {
1137 recordError(node->identifierToken, error);
1138 return false;
1139 }
1140 } else {
1141 QString memberType = asString(node->memberType);
1142 if (memberType == QLatin1String("alias")) {
1143 return appendAlias(node);
1144 } else {
1145 QStringView name = node->name;
1146
1147 Property *property = New<Property>();
1148 property->setIsReadOnly(node->isReadonly());
1149 property->setIsRequired(node->isRequired());
1150 property->setIsVirtual(node->isVirtual());
1151 property->setIsOverride(node->isOverride());
1152 property->setIsFinal(node->isFinal());
1153
1154 const QV4::CompiledData::CommonType builtinPropertyType
1155 = Parameter::stringToBuiltinType(memberType);
1156 if (builtinPropertyType != QV4::CompiledData::CommonType::Invalid)
1157 property->setCommonType(builtinPropertyType);
1158 else
1159 property->setTypeNameIndex(registerString(memberType));
1160
1161 QStringView typeModifier = node->typeModifier;
1162 if (typeModifier == QLatin1String("list")) {
1163 property->setIsList(true);
1164 } else if (!typeModifier.isEmpty()) {
1165 recordError(node->typeModifierToken, QCoreApplication::translate("QQmlParser","Invalid property type modifier"));
1166 return false;
1167 }
1168
1169 const QString propName = name.toString();
1170 property->setNameIndex(registerString(propName));
1171
1172 QQmlJS::SourceLocation loc = node->firstSourceLocation();
1173 property->location.set(loc.startLine, loc.startColumn);
1174
1175 QQmlJS::SourceLocation errorLocation;
1176 QString error;
1177
1178 if (QV4::Compiler::Codegen::isNameGlobal(propName))
1179 error = tr("Illegal property name");
1180 else
1181 error = _object->appendProperty(property, propName, node->isDefaultMember(), node->defaultToken(), &errorLocation);
1182
1183 if (!error.isEmpty()) {
1184 if (errorLocation.startLine == 0)
1185 errorLocation = node->identifierToken;
1186
1187 recordError(errorLocation, error);
1188 return false;
1189 }
1190
1191 qSwap(_propertyDeclaration, property);
1192 if (node->binding) {
1193 // process QML-like initializers (e.g. property Object o: Object {})
1194 QQmlJS::AST::Node::accept(node->binding, this);
1195 } else if (node->statement) {
1196 if (!isRedundantNullInitializerForPropertyDeclaration(_propertyDeclaration, node->statement))
1197 appendBinding(node->identifierToken, node->identifierToken, _propertyDeclaration->nameIndex(), node->statement, node);
1198 }
1199 qSwap(_propertyDeclaration, property);
1200 }
1201 }
1202
1203 return false;
1204}
1205
1206void IRBuilder::registerFunctionExpr(QQmlJS::AST::FunctionExpression *fexp, IsQmlFunction isQmlFunction)
1207{
1208 CompiledFunctionOrExpression *foe = New<CompiledFunctionOrExpression>();
1209 foe->node = fexp;
1210 foe->parentNode = fexp;
1211 foe->nameIndex = registerString(fexp->name.toString());
1212 const int index = _object->functionsAndExpressions->append(foe);
1213
1214 Function *f = New<Function>();
1215 QQmlJS::SourceLocation loc = fexp->identifierToken;
1216 f->location.set(loc.startLine, loc.startColumn);
1217 f->index = index;
1218 f->nameIndex = registerString(fexp->name.toString());
1219 f->isQmlFunction = isQmlFunction == IsQmlFunction::Yes;
1220
1221 const auto idGenerator = [this](const QString &str) { return registerString(str); };
1222
1223 Parameter::initType(
1224 &f->returnType, idGenerator,
1225 fexp->typeAnnotation ? fexp->typeAnnotation->type : nullptr);
1226
1227 const QQmlJS::AST::BoundNames formals = fexp->formals ? fexp->formals->formals()
1228 : QQmlJS::AST::BoundNames();
1229 int formalsCount = formals.size();
1230 f->formals.allocate(pool, formalsCount);
1231
1232 int i = 0;
1233 for (const auto &arg : formals) {
1234 Parameter *functionParameter = &f->formals[i];
1235 functionParameter->nameIndex = registerString(arg.id);
1236 Parameter::initType(
1237 &functionParameter->type, idGenerator,
1238 arg.typeAnnotation.isNull() ? nullptr : arg.typeAnnotation->type);
1239 ++i;
1240 }
1241
1242 _object->appendFunction(f);
1243}
1244
1245bool IRBuilder::visit(QQmlJS::AST::UiSourceElement *node)
1246{
1247 if (QQmlJS::AST::FunctionExpression *funDecl = node->sourceElement->asFunctionDefinition()) {
1248 if (_object->declarationsOverride) {
1249 // See Object::appendFunction() for why.
1250 recordError(node->firstSourceLocation(),
1251 QCoreApplication::translate(
1252 "QQmlParser", "Function declaration inside grouped property"));
1253 return false;
1254 }
1255 registerFunctionExpr(funDecl, IsQmlFunction::Yes);
1256 } else {
1257 recordError(node->firstSourceLocation(), QCoreApplication::translate("QQmlParser","JavaScript declaration outside Script element"));
1258 }
1259 return false;
1260}
1261
1262bool IRBuilder::visit(AST::UiRequired *ast)
1263{
1264 auto extraData = New<RequiredPropertyExtraData>();
1265 extraData->nameIndex = registerString(ast->name.toString());
1266 _object->appendRequiredPropertyExtraData(extraData);
1267 return false;
1268}
1269
1270QString IRBuilder::asString(QQmlJS::AST::UiQualifiedId *node)
1271{
1272 QString s;
1273
1274 for (QQmlJS::AST::UiQualifiedId *it = node; it; it = it->next) {
1275 s.append(it->name);
1276
1277 if (it->next)
1278 s.append(QLatin1Char('.'));
1279 }
1280
1281 return s;
1282}
1283
1284QStringView IRBuilder::asStringRef(QQmlJS::AST::Node *node)
1285{
1286 if (!node)
1287 return QStringView();
1288
1289 return textRefAt(node->firstSourceLocation(), node->lastSourceLocation());
1290}
1291
1292QTypeRevision IRBuilder::extractVersion(QStringView string)
1293{
1294 if (string.isEmpty())
1295 return QTypeRevision();
1296
1297 const int dot = string.indexOf(QLatin1Char('.'));
1298 return (dot < 0)
1299 ? QTypeRevision::fromMajorVersion(string.toInt())
1300 : QTypeRevision::fromVersion(string.left(dot).toInt(), string.mid(dot + 1).toInt());
1301}
1302
1303QStringView IRBuilder::textRefAt(const QQmlJS::SourceLocation &first, const QQmlJS::SourceLocation &last) const
1304{
1305 return QStringView(sourceCode).mid(first.offset, last.offset + last.length - first.offset);
1306}
1307
1308void IRBuilder::setBindingValue(QV4::CompiledData::Binding *binding, QQmlJS::AST::Statement *statement, QQmlJS::AST::Node *parentNode)
1309{
1310 QQmlJS::SourceLocation loc = statement->firstSourceLocation();
1311 binding->valueLocation.set(loc.startLine, loc.startColumn);
1312 binding->setType(QV4::CompiledData::Binding::Type_Invalid);
1313 if (_propertyDeclaration && _propertyDeclaration->isReadOnly())
1314 binding->setFlag(QV4::CompiledData::Binding::InitializerForReadOnlyDeclaration);
1315
1316 QQmlJS::AST::ExpressionStatement *exprStmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(statement);
1317 if (exprStmt) {
1318 QQmlJS::AST::ExpressionNode * const expr = exprStmt->expression;
1319 if (QQmlJS::AST::StringLiteral *lit = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(expr)) {
1320 binding->setType(QV4::CompiledData::Binding::Type_String);
1321 binding->stringIndex = registerString(lit->value.toString());
1322 } else if (QQmlJS::AST::TemplateLiteral *templateLit = QQmlJS::AST::cast<QQmlJS::AST::TemplateLiteral *>(expr);
1323 templateLit && templateLit->hasNoSubstitution) {
1324 // A template literal without substitution is just a string.
1325 // With substitution, it could however be an arbitrarily complex expression
1326 binding->setType(QV4::CompiledData::Binding::Type_String);
1327 binding->stringIndex = registerString(templateLit->value.toString());
1328 } else if (expr->kind == QQmlJS::AST::Node::Kind_TrueLiteral) {
1329 binding->setType(QV4::CompiledData::Binding::Type_Boolean);
1330 binding->value.b = true;
1331 } else if (expr->kind == QQmlJS::AST::Node::Kind_FalseLiteral) {
1332 binding->setType(QV4::CompiledData::Binding::Type_Boolean);
1333 binding->value.b = false;
1334 } else if (QQmlJS::AST::NumericLiteral *lit = QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(expr)) {
1335 binding->setType(QV4::CompiledData::Binding::Type_Number);
1336 binding->value.constantValueIndex = jsGenerator->registerConstant(QV4::Encode(lit->value));
1337 } else if (QQmlJS::AST::CallExpression *call = QQmlJS::AST::cast<QQmlJS::AST::CallExpression *>(expr)) {
1338 if (QQmlJS::AST::IdentifierExpression *base = QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression *>(call->base)) {
1339 tryGeneratingTranslationBinding(base->name, call->arguments, binding);
1340 // If it wasn't a translation binding, a normal script binding will be generated
1341 // below.
1342 }
1343 } else if (QQmlJS::AST::cast<QQmlJS::AST::FunctionExpression *>(expr)) {
1344 binding->setFlag(QV4::CompiledData::Binding::IsFunctionExpression);
1345 } else if (QQmlJS::AST::UnaryMinusExpression *unaryMinus = QQmlJS::AST::cast<QQmlJS::AST::UnaryMinusExpression *>(expr)) {
1346 if (QQmlJS::AST::NumericLiteral *lit = QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(unaryMinus->expression)) {
1347 binding->setType(QV4::CompiledData::Binding::Type_Number);
1348 binding->value.constantValueIndex = jsGenerator->registerConstant(QV4::Encode(-lit->value));
1349 }
1350 } else if (QQmlJS::AST::cast<QQmlJS::AST::NullExpression *>(expr)) {
1351 binding->setType(QV4::CompiledData::Binding::Type_Null);
1352 binding->value.nullMarker = 0;
1353 }
1354 }
1355
1356 // Do binding instead
1357 if (binding->type() == QV4::CompiledData::Binding::Type_Invalid) {
1358 binding->setType(QV4::CompiledData::Binding::Type_Script);
1359
1360 CompiledFunctionOrExpression *expr = New<CompiledFunctionOrExpression>();
1361 expr->node = statement;
1362 expr->parentNode = parentNode;
1363 expr->nameIndex = registerString(QLatin1String("expression for ")
1364 + stringAt(binding->propertyNameIndex));
1365 const int index = bindingsTarget()->functionsAndExpressions->append(expr);
1366 binding->value.compiledScriptIndex = index;
1367 // We don't need to store the binding script as string, except for script strings
1368 // and types with custom parsers. Those will be added later in the compilation phase.
1369 // Except that we cannot recover the string when cachegen runs; we need to therefore retain
1370 // "undefined" and "[]" (for QQmlListModel). Any other "special" strings (for the various
1371 // literals) are already handled above
1372 QQmlJS::AST::Node *nodeForString = statement;
1373 if (exprStmt)
1374 nodeForString = exprStmt->expression;
1375 const QStringView source = asStringRef(nodeForString);
1376 if (source == u"undefined") {
1377 binding->stringIndex = registerString(u"undefined"_s);
1378 } else if (qualifiedEnumDot(source) != -1) {
1379 binding->stringIndex = registerString(source.toString());
1380 } else if (const auto *arrayPattern =
1381 QQmlJS::AST::cast<QQmlJS::AST::ArrayPattern *>(nodeForString);
1382 arrayPattern && !arrayPattern->elements) {
1383 binding->stringIndex = registerString(u"[]"_s);
1384 } else {
1385 binding->stringIndex = emptyStringIndex;
1386 }
1387 }
1388}
1389
1390void IRBuilder::tryGeneratingTranslationBinding(QStringView base, AST::ArgumentList *args, QV4::CompiledData::Binding *binding)
1391{
1392 const auto registerString = [&](QStringView string) {
1393 return jsGenerator->registerString(string.toString()) ;
1394 };
1395
1396 const auto finalizeTranslationData = [&](
1397 QV4::CompiledData::Binding::Type type,
1398 QV4::CompiledData::TranslationData translationData) {
1399 binding->setType(type);
1400 if (type == QV4::CompiledData::Binding::Type_Translation
1401 || type == QV4::CompiledData::Binding::Type_TranslationById) {
1402 binding->value.translationDataIndex = jsGenerator->registerTranslation(translationData);
1403 } else if (type == QV4::CompiledData::Binding::Type_String) {
1404 binding->stringIndex = translationData.number;
1405 }
1406 };
1407
1408 tryGeneratingTranslationBindingBase(
1409 base, args,
1410 registerString, registerString, registerString, finalizeTranslationData);
1411}
1412
1413void IRBuilder::appendBinding(QQmlJS::AST::UiQualifiedId *name, QQmlJS::AST::Statement *value, QQmlJS::AST::Node *parentNode)
1414{
1415 const QQmlJS::SourceLocation qualifiedNameLocation = name->identifierToken;
1416 Object *object = nullptr;
1417 if (!resolveQualifiedId(&name, &object))
1418 return;
1419 if (_object == object && name->name == QLatin1String("id")) {
1420 setId(name->identifierToken, value);
1421 return;
1422 }
1423 qSwap(_object, object);
1424 appendBinding(qualifiedNameLocation, name->identifierToken, registerString(name->name.toString()), value, parentNode);
1425 qSwap(_object, object);
1426}
1427
1428void IRBuilder::appendBinding(QQmlJS::AST::UiQualifiedId *name, int objectIndex, bool isOnAssignment)
1429{
1430 const QQmlJS::SourceLocation qualifiedNameLocation = name->identifierToken;
1431 Object *object = nullptr;
1432 if (!resolveQualifiedId(&name, &object, isOnAssignment))
1433 return;
1434 qSwap(_object, object);
1435 appendBinding(qualifiedNameLocation, name->identifierToken, registerString(name->name.toString()), objectIndex, /*isListItem*/false, isOnAssignment);
1436 qSwap(_object, object);
1437}
1438
1439void IRBuilder::appendBinding(const QQmlJS::SourceLocation &qualifiedNameLocation, const QQmlJS::SourceLocation &nameLocation, quint32 propertyNameIndex,
1440 QQmlJS::AST::Statement *value, QQmlJS::AST::Node *parentNode)
1441{
1442 Binding *binding = New<Binding>();
1443 binding->propertyNameIndex = propertyNameIndex;
1444 binding->offset = nameLocation.offset;
1445 binding->location.set(nameLocation.startLine, nameLocation.startColumn);
1446 binding->clearFlags();
1447 setBindingValue(binding, value, parentNode);
1448 QString error = bindingsTarget()->appendBinding(binding, /*isListBinding*/false);
1449 if (!error.isEmpty()) {
1450 recordError(qualifiedNameLocation, error);
1451 }
1452}
1453
1454void IRBuilder::appendBinding(const QQmlJS::SourceLocation &qualifiedNameLocation, const QQmlJS::SourceLocation &nameLocation, quint32 propertyNameIndex, int objectIndex, bool isListItem, bool isOnAssignment)
1455{
1456 if (stringAt(propertyNameIndex) == QLatin1String("id")) {
1457 recordError(nameLocation, tr("Invalid component id specification"));
1458 return;
1459 }
1460
1461 Binding *binding = New<Binding>();
1462 binding->propertyNameIndex = propertyNameIndex;
1463 binding->offset = nameLocation.offset;
1464 binding->location.set(nameLocation.startLine, nameLocation.startColumn);
1465
1466 const Object *obj = _objects.at(objectIndex);
1467 binding->valueLocation = obj->location;
1468
1469 binding->clearFlags();
1470
1471 if (_propertyDeclaration && _propertyDeclaration->isReadOnly())
1472 binding->setFlag(Binding::InitializerForReadOnlyDeclaration);
1473
1474 // No type name on the initializer means it must be a group property
1475 if (_objects.at(objectIndex)->inheritedTypeNameIndex == emptyStringIndex)
1476 binding->setType(Binding::Type_GroupProperty);
1477 else
1478 binding->setType(Binding::Type_Object);
1479
1480 if (isOnAssignment)
1481 binding->setFlag(Binding::IsOnAssignment);
1482 if (isListItem)
1483 binding->setFlag(Binding::IsListItem);
1484
1485 binding->value.objectIndex = objectIndex;
1486 QString error = bindingsTarget()->appendBinding(binding, isListItem);
1487 if (!error.isEmpty()) {
1488 recordError(qualifiedNameLocation, error);
1489 }
1490}
1491
1492bool IRBuilder::appendAlias(QQmlJS::AST::UiPublicMember *node)
1493{
1494 Alias *alias = New<Alias>();
1495 alias->setIsReadOnly(node->isReadonly());
1496
1497 const QString propName = node->name.toString();
1498 alias->setNameIndex(registerString(propName));
1499
1500 QQmlJS::SourceLocation loc = node->firstSourceLocation();
1501 alias->setLocation({loc.startLine, loc.startColumn});
1502
1503 alias->setPropertyNameIndex(emptyStringIndex);
1504
1505 if (!node->statement && !node->binding)
1506 COMPILE_EXCEPTION(loc, tr("No property alias location"));
1507
1508 QQmlJS::SourceLocation rhsLoc;
1509 if (node->binding)
1510 rhsLoc = node->binding->firstSourceLocation();
1511 else if (node->statement)
1512 rhsLoc = node->statement->firstSourceLocation();
1513 else
1514 rhsLoc = node->semicolonToken;
1515 alias->setReferenceLocation({rhsLoc.startLine, rhsLoc.startColumn});
1516
1517 QStringList aliasReference;
1518
1519 if (QQmlJS::AST::ExpressionStatement *stmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement*>(node->statement)) {
1520 aliasReference = astNodeToStringList(stmt->expression);
1521 if (aliasReference.isEmpty()) {
1522 if (isStatementNodeScript(node->statement)) {
1523 COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias reference. An alias reference must be specified as <id>, <id>.<property> or <id>.<value property>.<property>"));
1524 } else {
1525 COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias location"));
1526 }
1527 }
1528 } else {
1529 COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias reference. An alias reference must be specified as <id>, <id>.<property> or <id>.<value property>.<property>"));
1530 }
1531
1532 if (aliasReference.size() < 1 || aliasReference.size() > 3)
1533 COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias reference. An alias reference must be specified as <id>, <id>.<property> or <id>.<value property>.<property>"));
1534
1535 alias->setIdIndex(registerString(aliasReference.first()));
1536
1537 QString propertyValue = aliasReference.value(1);
1538 if (aliasReference.size() == 3)
1539 propertyValue += QLatin1Char('.') + aliasReference.at(2);
1540 alias->setPropertyNameIndex(registerString(propertyValue));
1541
1542 QQmlJS::SourceLocation errorLocation;
1543 QString error;
1544
1545 if (QV4::Compiler::Codegen::isNameGlobal(propName))
1546 error = tr("Illegal property name");
1547 else
1548 error = _object->appendAlias(alias, propName, node->isDefaultMember(), node->defaultToken(), &errorLocation);
1549
1550 if (!error.isEmpty()) {
1551 if (errorLocation.startLine == 0)
1552 errorLocation = node->identifierToken;
1553
1554 recordError(errorLocation, error);
1555 return false;
1556 }
1557
1558 return false;
1559}
1560
1561Object *IRBuilder::bindingsTarget() const
1562{
1563 if (_propertyDeclaration && _object->declarationsOverride)
1564 return _object->declarationsOverride;
1565 return _object;
1566}
1567
1568bool IRBuilder::setId(const QQmlJS::SourceLocation &idLocation, QQmlJS::AST::Statement *value)
1569{
1570 QQmlJS::SourceLocation loc = value->firstSourceLocation();
1571 QStringView str;
1572
1573 QQmlJS::AST::Node *node = value;
1574 if (QQmlJS::AST::ExpressionStatement *stmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(node)) {
1575 if (QQmlJS::AST::StringLiteral *lit = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(stmt->expression)) {
1576 str = lit->value;
1577 node = nullptr;
1578 } else
1579 node = stmt->expression;
1580 }
1581
1582 if (node && str.isEmpty())
1583 str = asStringRef(node);
1584
1585 if (str.isEmpty())
1586 COMPILE_EXCEPTION(loc, tr( "Invalid empty ID"));
1587
1588 QChar ch = str.at(0);
1589 if (ch.isLetter() && !ch.isLower())
1590 COMPILE_EXCEPTION(loc, tr( "IDs cannot start with an uppercase letter"));
1591
1592 QChar u(QLatin1Char('_'));
1593 if (!ch.isLetter() && ch != u)
1594 COMPILE_EXCEPTION(loc, tr( "IDs must start with a letter or underscore"));
1595
1596 for (int ii = 1; ii < str.size(); ++ii) {
1597 ch = str.at(ii);
1598 if (!ch.isLetterOrNumber() && ch != u)
1599 COMPILE_EXCEPTION(loc, tr( "IDs must contain only letters, numbers, and underscores"));
1600 }
1601
1602 QString idQString(str.toString());
1603 if (QV4::Compiler::Codegen::isNameGlobal(idQString))
1604 COMPILE_EXCEPTION(loc, tr( "ID illegally masks global JavaScript property"));
1605
1606 if (_object->idNameIndex != emptyStringIndex)
1607 COMPILE_EXCEPTION(idLocation, tr("Property value set multiple times"));
1608
1609 _object->idNameIndex = registerString(idQString);
1610 _object->locationOfIdProperty.set(idLocation.startLine, idLocation.startColumn);
1611
1612 return true;
1613}
1614
1615bool IRBuilder::resolveQualifiedId(QQmlJS::AST::UiQualifiedId **nameToResolve, Object **object, bool onAssignment)
1616{
1617 QQmlJS::AST::UiQualifiedId *qualifiedIdElement = *nameToResolve;
1618
1619 if (qualifiedIdElement->name == QLatin1String("id") && qualifiedIdElement->next)
1620 COMPILE_EXCEPTION(qualifiedIdElement->identifierToken, tr( "Invalid use of id property"));
1621
1622 // If it's a namespace, prepend the qualifier and we'll resolve it later to the correct type.
1623 QString currentName = qualifiedIdElement->name.toString();
1624 if (qualifiedIdElement->next) {
1625 for (const QV4::CompiledData::Import* import : std::as_const(_imports))
1626 if (import->qualifierIndex != emptyStringIndex
1627 && stringAt(import->qualifierIndex) == currentName) {
1628 qualifiedIdElement = qualifiedIdElement->next;
1629 currentName += QLatin1Char('.') + qualifiedIdElement->name;
1630
1631 if (!qualifiedIdElement->name.data()->isUpper())
1632 COMPILE_EXCEPTION(qualifiedIdElement->firstSourceLocation(), tr("Expected type name"));
1633
1634 break;
1635 }
1636 }
1637
1638 *object = _object;
1639 while (qualifiedIdElement->next) {
1640 const quint32 propertyNameIndex = registerString(currentName);
1641 const bool isAttachedProperty = qualifiedIdElement->name.data()->isUpper();
1642
1643 Binding *binding = (*object)->findBinding(propertyNameIndex);
1644 if (binding) {
1645 if (isAttachedProperty) {
1646 if (!binding->isAttachedProperty())
1647 binding = nullptr;
1648 } else if (!binding->isGroupProperty()) {
1649 binding = nullptr;
1650 }
1651 }
1652 if (!binding) {
1653 binding = New<Binding>();
1654 binding->propertyNameIndex = propertyNameIndex;
1655 binding->offset = qualifiedIdElement->identifierToken.offset;
1656 binding->location.set(qualifiedIdElement->identifierToken.startLine,
1657 qualifiedIdElement->identifierToken.startColumn);
1658 binding->valueLocation.set(qualifiedIdElement->next->identifierToken.startLine,
1659 qualifiedIdElement->next->identifierToken.startColumn);
1660 binding->clearFlags();
1661
1662 if (onAssignment)
1663 binding->setFlag(QV4::CompiledData::Binding::IsOnAssignment);
1664
1665 if (isAttachedProperty)
1666 binding->setType(QV4::CompiledData::Binding::Type_AttachedProperty);
1667 else
1668 binding->setType(QV4::CompiledData::Binding::Type_GroupProperty);
1669
1670 int objIndex = 0;
1671 if (!defineQMLObject(&objIndex, nullptr, binding->location, nullptr, nullptr))
1672 return false;
1673 binding->value.objectIndex = objIndex;
1674
1675 QString error = (*object)->appendBinding(binding, /*isListBinding*/false);
1676 if (!error.isEmpty()) {
1677 recordError(qualifiedIdElement->identifierToken, error);
1678 return false;
1679 }
1680 *object = _objects.at(objIndex);
1681 } else {
1682 Q_ASSERT(binding->isAttachedProperty() || binding->isGroupProperty());
1683 *object = _objects.at(binding->value.objectIndex);
1684 }
1685
1686 qualifiedIdElement = qualifiedIdElement->next;
1687 if (qualifiedIdElement)
1688 currentName = qualifiedIdElement->name.toString();
1689 }
1690 *nameToResolve = qualifiedIdElement;
1691 return true;
1692}
1693
1694void IRBuilder::recordError(const QQmlJS::SourceLocation &location, const QString &description)
1695{
1696 QQmlJS::DiagnosticMessage error;
1697 error.loc = location;
1698 error.message = description;
1699 errors << error;
1700}
1701
1702bool IRBuilder::isStatementNodeScript(QQmlJS::AST::Statement *statement)
1703{
1704 if (QQmlJS::AST::ExpressionStatement *stmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(statement)) {
1705 QQmlJS::AST::ExpressionNode *expr = stmt->expression;
1706 if (QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(expr))
1707 return false;
1708 else if (expr->kind == QQmlJS::AST::Node::Kind_TrueLiteral)
1709 return false;
1710 else if (expr->kind == QQmlJS::AST::Node::Kind_FalseLiteral)
1711 return false;
1712 else if (QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(expr))
1713 return false;
1714 else {
1715
1716 if (QQmlJS::AST::UnaryMinusExpression *unaryMinus = QQmlJS::AST::cast<QQmlJS::AST::UnaryMinusExpression *>(expr)) {
1717 if (QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(unaryMinus->expression)) {
1718 return false;
1719 }
1720 }
1721 }
1722 }
1723
1724 return true;
1725}
1726
1727bool IRBuilder::isRedundantNullInitializerForPropertyDeclaration(Property *property, QQmlJS::AST::Statement *statement)
1728{
1729 if (property->isCommonType() || property->isList())
1730 return false;
1731 QQmlJS::AST::ExpressionStatement *exprStmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(statement);
1732 if (!exprStmt)
1733 return false;
1734 QQmlJS::AST::ExpressionNode * const expr = exprStmt->expression;
1735 return QQmlJS::AST::cast<QQmlJS::AST::NullExpression *>(expr);
1736}
1737
1738void QmlUnitGenerator::generate(Document &output, const QV4::CompiledData::DependentTypesHasher &dependencyHasher)
1739{
1740 using namespace QV4::CompiledData;
1741
1742 output.jsGenerator.stringTable.registerString(output.jsModule.fileName);
1743 output.jsGenerator.stringTable.registerString(output.jsModule.finalUrl);
1744
1745 Unit *jsUnit = nullptr;
1746
1747 if (!output.javaScriptCompilationUnit)
1748 output.javaScriptCompilationUnit.adopt(new QV4::CompiledData::CompilationUnit);
1749
1750 // We may already have unit data if we're loading an ahead-of-time generated cache file.
1751 if (output.javaScriptCompilationUnit->unitData()) {
1752 jsUnit = const_cast<Unit *>(output.javaScriptCompilationUnit->unitData());
1753 output.javaScriptCompilationUnit->dynamicStrings
1754 = output.jsGenerator.stringTable.allStrings();
1755 } else {
1756 Unit *createdUnit;
1757 jsUnit = createdUnit = output.jsGenerator.generateUnit();
1758
1759 // enable flag if we encountered pragma Singleton
1760 for (Pragma *p : std::as_const(output.pragmas)) {
1761 switch (p->type) {
1762 case Pragma::Singleton:
1763 createdUnit->flags |= Unit::IsSingleton;
1764 break;
1765 case Pragma::Strict:
1766 createdUnit->flags |= Unit::IsStrict;
1767 break;
1768 case Pragma::ComponentBehavior:
1769 // ### Qt7: Change the default to Bound by reverting the meaning of the flag.
1770 switch (p->componentBehavior) {
1771 case Pragma::Bound:
1772 createdUnit->flags |= Unit::ComponentsBound;
1773 break;
1774 case Pragma::Unbound:
1775 // this is the default
1776 break;
1777 }
1778 break;
1779 case Pragma::ListPropertyAssignBehavior:
1780 switch (p->listPropertyAssignBehavior) {
1781 case Pragma::Replace:
1782 createdUnit->flags |= Unit::ListPropertyAssignReplace;
1783 break;
1784 case Pragma::ReplaceIfNotDefault:
1785 createdUnit->flags |= Unit::ListPropertyAssignReplaceIfNotDefault;
1786 break;
1787 case Pragma::Append:
1788 // this is the default
1789 break;
1790 }
1791 break;
1792 case Pragma::FunctionSignatureBehavior:
1793 switch (p->functionSignatureBehavior) {
1794 case Pragma::Enforced:
1795 break;
1796 case Pragma::Ignored:
1797 createdUnit->flags |= Unit::FunctionSignaturesIgnored;
1798 break;
1799 }
1800 break;
1801 case Pragma::NativeMethodBehavior:
1802 switch (p->nativeMethodBehavior) {
1803 case Pragma::AcceptThisObject:
1804 createdUnit->flags |= Unit::NativeMethodsAcceptThisObject;
1805 break;
1806 case Pragma::RejectThisObject:
1807 // this is the default;
1808 break;
1809 }
1810 break;
1811 case Pragma::ValueTypeBehavior:
1812 if (Pragma::ValueTypeBehaviorValues(p->valueTypeBehavior)
1813 .testFlag(Pragma::Copy)) {
1814 createdUnit->flags |= Unit::ValueTypesCopied;
1815 }
1816 if (Pragma::ValueTypeBehaviorValues(p->valueTypeBehavior)
1817 .testFlag(Pragma::Addressable)) {
1818 createdUnit->flags |= Unit::ValueTypesAddressable;
1819 }
1820 if (Pragma::ValueTypeBehaviorValues(p->valueTypeBehavior)
1821 .testFlag(Pragma::Assertable)) {
1822 createdUnit->flags |= Unit::ValueTypesAssertable;
1823 }
1824 break;
1825 case Pragma::Translator:
1826 if (createdUnit->translationTableSize)
1827 if (quint32_le *index = createdUnit->translationContextIndex())
1828 *index = p->translationContextIndex;
1829 break;
1830 }
1831 }
1832
1833 if (dependencyHasher) {
1834 const QByteArray checksum = dependencyHasher();
1835 if (checksum.size() == sizeof(createdUnit->dependencyMD5Checksum)) {
1836 memcpy(createdUnit->dependencyMD5Checksum, checksum.constData(),
1837 sizeof(createdUnit->dependencyMD5Checksum));
1838 }
1839 }
1840
1841 createdUnit->sourceFileIndex = output.jsGenerator.stringTable.getStringId(output.jsModule.fileName);
1842 createdUnit->finalUrlIndex = output.jsGenerator.stringTable.getStringId(output.jsModule.finalUrl);
1843 }
1844
1845 // No more new strings after this point, we're calculating offsets.
1846 output.jsGenerator.stringTable.freeze();
1847
1848 const uint importSize = uint(sizeof(QV4::CompiledData::Import)) * output.imports.size();
1849 const uint objectOffsetTableSize = output.objects.size() * uint(sizeof(quint32));
1850
1851 QHash<const Object*, quint32> objectOffsets;
1852
1853 const unsigned int objectOffset = sizeof(QV4::CompiledData::QmlUnit) + importSize;
1854 uint nextOffset = objectOffset + objectOffsetTableSize;
1855 for (Object *o : std::as_const(output.objects)) {
1856 objectOffsets.insert(o, nextOffset);
1857 nextOffset += QV4::CompiledData::Object::calculateSizeExcludingSignalsAndEnums(o->functionCount(), o->propertyCount(), o->aliasCount(), o->enumCount(), o->signalCount(), o->bindingCount(), o->namedObjectsInComponent.size(), o->inlineComponentCount(), o->requiredPropertyExtraDataCount());
1858
1859 int signalTableSize = 0;
1860 for (const Signal *s = o->firstSignal(); s; s = s->next)
1861 signalTableSize += QV4::CompiledData::Signal::calculateSize(s->parameters->count);
1862
1863 nextOffset += signalTableSize;
1864
1865 int enumTableSize = 0;
1866 for (const Enum *e = o->firstEnum(); e; e = e->next)
1867 enumTableSize += QV4::CompiledData::Enum::calculateSize(e->enumValues->count);
1868
1869 nextOffset += enumTableSize;
1870 }
1871
1872 const uint totalSize = nextOffset;
1873 char *data = (char*)malloc(totalSize);
1874 memset(data, 0, totalSize);
1875 QV4::CompiledData::QmlUnit *qmlUnit = reinterpret_cast<QV4::CompiledData::QmlUnit *>(data);
1876 qmlUnit->offsetToImports = sizeof(*qmlUnit);
1877 qmlUnit->nImports = output.imports.size();
1878 qmlUnit->offsetToObjects = objectOffset;
1879 qmlUnit->nObjects = output.objects.size();
1880
1881 // write imports
1882 char *importPtr = data + qmlUnit->offsetToImports;
1883 for (const QV4::CompiledData::Import *imp : std::as_const(output.imports)) {
1884 QV4::CompiledData::Import *importToWrite = reinterpret_cast<QV4::CompiledData::Import*>(importPtr);
1885 *importToWrite = *imp;
1886 importPtr += sizeof(QV4::CompiledData::Import);
1887 }
1888
1889 // write objects
1890 quint32_le *objectTable = reinterpret_cast<quint32_le*>(data + qmlUnit->offsetToObjects);
1891 for (int i = 0; i < output.objects.size(); ++i) {
1892 const Object *o = output.objects.at(i);
1893 char * const objectPtr = data + objectOffsets.value(o);
1894 *objectTable++ = objectOffsets.value(o);
1895
1896 QV4::CompiledData::Object *objectToWrite = reinterpret_cast<QV4::CompiledData::Object*>(objectPtr);
1897 objectToWrite->inheritedTypeNameIndex = o->inheritedTypeNameIndex;
1898 objectToWrite->indexOfDefaultPropertyOrAlias = o->indexOfDefaultPropertyOrAlias;
1899 objectToWrite->setHasAliasAsDefaultProperty(o->defaultPropertyIsAlias);
1900 objectToWrite->setFlags(QV4::CompiledData::Object::Flags(o->flags));
1901 objectToWrite->idNameIndex = o->idNameIndex;
1902 objectToWrite->setObjectId(o->id);
1903 objectToWrite->location = o->location;
1904 objectToWrite->locationOfIdProperty = o->locationOfIdProperty;
1905
1906 quint32 nextOffset = sizeof(QV4::CompiledData::Object);
1907
1908 objectToWrite->nFunctions = o->functionCount();
1909 objectToWrite->offsetToFunctions = nextOffset;
1910 nextOffset += objectToWrite->nFunctions * sizeof(quint32);
1911
1912 objectToWrite->nProperties = o->propertyCount();
1913 objectToWrite->offsetToProperties = nextOffset;
1914 nextOffset += objectToWrite->nProperties * sizeof(QV4::CompiledData::Property);
1915
1916 objectToWrite->nAliases = o->aliasCount();
1917 objectToWrite->offsetToAliases = nextOffset;
1918 nextOffset += objectToWrite->nAliases * sizeof(QV4::CompiledData::Alias);
1919
1920 objectToWrite->nEnums = o->enumCount();
1921 objectToWrite->offsetToEnums = nextOffset;
1922 nextOffset += objectToWrite->nEnums * sizeof(quint32);
1923
1924 objectToWrite->nSignals = o->signalCount();
1925 objectToWrite->offsetToSignals = nextOffset;
1926 nextOffset += objectToWrite->nSignals * sizeof(quint32);
1927
1928 objectToWrite->nBindings = o->bindingCount();
1929 objectToWrite->offsetToBindings = nextOffset;
1930 nextOffset += objectToWrite->nBindings * sizeof(QV4::CompiledData::Binding);
1931
1932 objectToWrite->nNamedObjectsInComponent = o->namedObjectsInComponent.size();
1933 objectToWrite->offsetToNamedObjectsInComponent = nextOffset;
1934 nextOffset += objectToWrite->nNamedObjectsInComponent * sizeof(quint32);
1935
1936 objectToWrite->nInlineComponents = o->inlineComponentCount();
1937 objectToWrite->offsetToInlineComponents = nextOffset;
1938 nextOffset += objectToWrite->nInlineComponents * sizeof (QV4::CompiledData::InlineComponent);
1939
1940 objectToWrite->nRequiredPropertyExtraData = o->requiredPropertyExtraDataCount();
1941 objectToWrite->offsetToRequiredPropertyExtraData = nextOffset;
1942 nextOffset += objectToWrite->nRequiredPropertyExtraData * sizeof(QV4::CompiledData::RequiredPropertyExtraData);
1943
1944 quint32_le *functionsTable = reinterpret_cast<quint32_le *>(objectPtr + objectToWrite->offsetToFunctions);
1945 for (const Function *f = o->firstFunction(); f; f = f->next)
1946 *functionsTable++ = o->runtimeFunctionIndices.at(f->index);
1947
1948 char *propertiesPtr = objectPtr + objectToWrite->offsetToProperties;
1949 for (const Property *p = o->firstProperty(); p; p = p->next) {
1950 QV4::CompiledData::Property *propertyToWrite = reinterpret_cast<QV4::CompiledData::Property*>(propertiesPtr);
1951 *propertyToWrite = *p;
1952 propertiesPtr += sizeof(QV4::CompiledData::Property);
1953 }
1954
1955 char *aliasesPtr = objectPtr + objectToWrite->offsetToAliases;
1956 for (const Alias *a = o->firstAlias(); a; a = a->next) {
1957 QV4::CompiledData::Alias *aliasToWrite = reinterpret_cast<QV4::CompiledData::Alias*>(aliasesPtr);
1958 *aliasToWrite = *a;
1959 aliasesPtr += sizeof(QV4::CompiledData::Alias);
1960 }
1961
1962 char *bindingPtr = objectPtr + objectToWrite->offsetToBindings;
1963 bindingPtr = writeBindings(bindingPtr, o, &QV4::CompiledData::Binding::isValueBindingNoAlias);
1964 bindingPtr = writeBindings(bindingPtr, o, &QV4::CompiledData::Binding::isSignalHandler);
1965 bindingPtr = writeBindings(bindingPtr, o, &QV4::CompiledData::Binding::isAttachedProperty);
1966 bindingPtr = writeBindings(bindingPtr, o, &QV4::CompiledData::Binding::isGroupProperty);
1967 bindingPtr = writeBindings(bindingPtr, o, &QV4::CompiledData::Binding::isValueBindingToAlias);
1968 Q_ASSERT((bindingPtr - objectToWrite->offsetToBindings - objectPtr) / sizeof(QV4::CompiledData::Binding) == unsigned(o->bindingCount()));
1969
1970 quint32_le *signalOffsetTable = reinterpret_cast<quint32_le *>(objectPtr + objectToWrite->offsetToSignals);
1971 quint32 signalTableSize = 0;
1972 char *signalPtr = objectPtr + nextOffset;
1973 for (const Signal *s = o->firstSignal(); s; s = s->next) {
1974 *signalOffsetTable++ = signalPtr - objectPtr;
1975 QV4::CompiledData::Signal *signalToWrite = reinterpret_cast<QV4::CompiledData::Signal*>(signalPtr);
1976
1977 signalToWrite->nameIndex = s->nameIndex;
1978 signalToWrite->location = s->location;
1979 signalToWrite->nParameters = s->parameters->count;
1980
1981 QV4::CompiledData::Parameter *parameterToWrite = reinterpret_cast<QV4::CompiledData::Parameter*>(signalPtr + sizeof(*signalToWrite));
1982 for (Parameter *param = s->parameters->first; param; param = param->next, ++parameterToWrite)
1983 *parameterToWrite = *param;
1984
1985 int size = QV4::CompiledData::Signal::calculateSize(s->parameters->count);
1986 signalTableSize += size;
1987 signalPtr += size;
1988 }
1989 nextOffset += signalTableSize;
1990
1991 quint32_le *enumOffsetTable = reinterpret_cast<quint32_le*>(objectPtr + objectToWrite->offsetToEnums);
1992 char *enumPtr = objectPtr + nextOffset;
1993 for (const Enum *e = o->firstEnum(); e; e = e->next) {
1994 *enumOffsetTable++ = enumPtr - objectPtr;
1995 QV4::CompiledData::Enum *enumToWrite = reinterpret_cast<QV4::CompiledData::Enum*>(enumPtr);
1996
1997 enumToWrite->nameIndex = e->nameIndex;
1998 enumToWrite->location = e->location;
1999 enumToWrite->nEnumValues = e->enumValues->count;
2000
2001 QV4::CompiledData::EnumValue *enumValueToWrite = reinterpret_cast<QV4::CompiledData::EnumValue*>(enumPtr + sizeof(*enumToWrite));
2002 for (EnumValue *enumValue = e->enumValues->first; enumValue; enumValue = enumValue->next, ++enumValueToWrite)
2003 *enumValueToWrite = *enumValue;
2004
2005 int size = QV4::CompiledData::Enum::calculateSize(e->enumValues->count);
2006 enumPtr += size;
2007 }
2008
2009 quint32_le *namedObjectInComponentPtr = reinterpret_cast<quint32_le *>(objectPtr + objectToWrite->offsetToNamedObjectsInComponent);
2010 for (int i = 0; i < o->namedObjectsInComponent.size(); ++i) {
2011 *namedObjectInComponentPtr++ = o->namedObjectsInComponent.at(i);
2012 }
2013
2014 char *inlineComponentPtr = objectPtr + objectToWrite->offsetToInlineComponents;
2015 for (auto it = o->inlineComponentsBegin(); it != o->inlineComponentsEnd(); ++it) {
2016 const InlineComponent *ic = it.ptr;
2017 QV4::CompiledData::InlineComponent *icToWrite = reinterpret_cast<QV4::CompiledData::InlineComponent*>(inlineComponentPtr);
2018 *icToWrite = *ic;
2019 inlineComponentPtr += sizeof(QV4::CompiledData::InlineComponent);
2020 }
2021
2022 char *requiredPropertyExtraDataPtr = objectPtr + objectToWrite->offsetToRequiredPropertyExtraData;
2023 for (auto it = o->requiredPropertyExtraDataBegin(); it != o->requiredPropertyExtraDataEnd(); ++it) {
2024 const RequiredPropertyExtraData *extraData = it.ptr;
2025 QV4::CompiledData::RequiredPropertyExtraData *extraDataToWrite = reinterpret_cast<QV4::CompiledData::RequiredPropertyExtraData*>(requiredPropertyExtraDataPtr);
2026 *extraDataToWrite = *extraData;
2027 requiredPropertyExtraDataPtr += sizeof(QV4::CompiledData::RequiredPropertyExtraData);
2028 }
2029 }
2030
2031 if (!output.javaScriptCompilationUnit->unitData()) {
2032 // Combine the qml data into the general unit data.
2033 jsUnit = static_cast<QV4::CompiledData::Unit *>(realloc(jsUnit, jsUnit->unitSize + totalSize));
2034 jsUnit->offsetToQmlUnit = jsUnit->unitSize;
2035 jsUnit->unitSize += totalSize;
2036 memcpy(jsUnit->qmlUnit(), qmlUnit, totalSize);
2037 free(qmlUnit);
2038 QV4::Compiler::JSUnitGenerator::generateUnitChecksum(jsUnit);
2039 qmlUnit = jsUnit->qmlUnit();
2040 }
2041
2042 static const bool showStats = qEnvironmentVariableIsSet("QML_SHOW_UNIT_STATS");
2043 if (showStats) {
2044 qDebug() << "Generated QML unit that is" << totalSize << "bytes big contains:";
2045 qDebug() << " " << jsUnit->functionTableSize << "functions";
2046 qDebug() << " " << jsUnit->unitSize << "for JS unit";
2047 qDebug() << " " << importSize << "for imports";
2048 qDebug() << " " << nextOffset - objectOffset - objectOffsetTableSize << "for" << qmlUnit->nObjects << "objects";
2049 quint32 totalBindingCount = 0;
2050 for (quint32 i = 0; i < qmlUnit->nObjects; ++i)
2051 totalBindingCount += qmlUnit->objectAt(i)->nBindings;
2052 qDebug() << " " << totalBindingCount << "bindings";
2053 quint32 totalCodeSize = 0;
2054 for (quint32 i = 0; i < jsUnit->functionTableSize; ++i)
2055 totalCodeSize += jsUnit->functionAt(i)->codeSize;
2056 qDebug() << " " << totalCodeSize << "bytes total byte code";
2057 qDebug() << " " << jsUnit->stringTableSize << "strings";
2058 quint32 totalStringSize = 0;
2059 for (quint32 i = 0; i < jsUnit->stringTableSize; ++i)
2060 totalStringSize += QV4::CompiledData::String::calculateSize(jsUnit->stringAtInternal(i));
2061 qDebug() << " " << totalStringSize << "bytes total strings";
2062 }
2063
2064 output.javaScriptCompilationUnit->setUnitData(
2065 jsUnit, qmlUnit, output.jsModule.fileName, output.jsModule.finalUrl);
2066}
2067
2068char *QmlUnitGenerator::writeBindings(char *bindingPtr, const Object *o, BindingFilter filter) const
2069{
2070 for (const Binding *b = o->firstBinding(); b; b = b->next) {
2071 if (!(b->*(filter))())
2072 continue;
2073 QV4::CompiledData::Binding *bindingToWrite = reinterpret_cast<QV4::CompiledData::Binding*>(bindingPtr);
2074 *bindingToWrite = *b;
2075 if (b->type() == QV4::CompiledData::Binding::Type_Script)
2076 bindingToWrite->value.compiledScriptIndex = o->runtimeFunctionIndices.at(b->value.compiledScriptIndex);
2077 bindingPtr += sizeof(QV4::CompiledData::Binding);
2078 }
2079 return bindingPtr;
2080}
2081
2082JSCodeGen::JSCodeGen(
2083 Document *document, QV4::Compiler::CodegenWarningInterface *iface,
2084 bool storeSourceLocations)
2085 : QV4::Compiler::Codegen(&document->jsGenerator, /*strict mode*/ false, iface,
2086 storeSourceLocations),
2087 document(document)
2088{
2089 _module = &document->jsModule;
2090 _fileNameIsUrl = true;
2091}
2092
2093QList<int> JSCodeGen::generateJSCodeForFunctionsAndBindings(
2094 const QList<CompiledFunctionOrExpression> &functions)
2095{
2096 auto qmlName = [&](const CompiledFunctionOrExpression &c) {
2097 if (c.nameIndex != 0)
2098 return document->stringAt(c.nameIndex);
2099 else
2100 return QStringLiteral("%qml-expression-entry");
2101 };
2102 QList<int> runtimeFunctionIndices(functions.size());
2103
2104 QV4::Compiler::ScanFunctions scan(this, document->code, QV4::Compiler::ContextType::Global);
2105 scan.enterGlobalEnvironment(QV4::Compiler::ContextType::Binding);
2106 for (const CompiledFunctionOrExpression &f : functions) {
2107 Q_ASSERT(f.node != document->program);
2108 Q_ASSERT(f.parentNode && f.parentNode != document->program);
2109 auto function = f.node->asFunctionDefinition();
2110
2111 if (function) {
2112 scan.enterQmlFunction(function);
2113 } else {
2114 Q_ASSERT(f.node != f.parentNode);
2115 scan.enterEnvironment(f.parentNode, QV4::Compiler::ContextType::Binding, qmlName(f));
2116 }
2117
2118 /* We do not want to visit the whole function, as we already called enterQmlFunction
2119 However, there might be a function defined as a default argument of the function.
2120 That needs to be considered, too, so we call handleTopLevelFunctionFormals to
2121 deal with them.
2122 */
2123 scan.handleTopLevelFunctionFormals(function);
2124 scan(function ? function->body : f.node);
2125 scan.leaveEnvironment();
2126 }
2127 scan.leaveEnvironment();
2128
2129 if (hasError())
2130 return QList<int>();
2131
2132 _context = nullptr;
2133
2134 for (int i = 0; i < functions.size(); ++i) {
2135 const CompiledFunctionOrExpression &qmlFunction = functions.at(i);
2136 QQmlJS::AST::Node *node = qmlFunction.node;
2137 Q_ASSERT(node != document->program);
2138
2139 QQmlJS::AST::FunctionExpression *function = node->asFunctionDefinition();
2140
2141 QString name;
2142 if (function)
2143 name = function->name.toString();
2144 else
2145 name = qmlName(qmlFunction);
2146
2147 QQmlJS::AST::StatementList *body;
2148 if (function) {
2149 body = function->body;
2150 } else {
2151 // Synthesize source elements.
2152 QQmlJS::MemoryPool *pool = document->jsParserEngine.pool();
2153
2154 QQmlJS::AST::Statement *stmt = node->statementCast();
2155 if (!stmt) {
2156 Q_ASSERT(node->expressionCast());
2157 QQmlJS::AST::ExpressionNode *expr = node->expressionCast();
2158 stmt = new (pool) QQmlJS::AST::ExpressionStatement(expr);
2159 }
2160 body = new (pool) QQmlJS::AST::StatementList(stmt);
2161 body = body->finish();
2162 }
2163
2164 int idx = defineFunction(name, function ? function : qmlFunction.parentNode,
2165 function ? function->formals : nullptr, body);
2166 runtimeFunctionIndices[i] = idx;
2167 }
2168
2169 return runtimeFunctionIndices;
2170}
2171
2172bool JSCodeGen::generateRuntimeFunctions(QmlIR::Object *object)
2173{
2174 if (object->functionsAndExpressions->count == 0)
2175 return true;
2176
2177 QList<QmlIR::CompiledFunctionOrExpression> functionsToCompile;
2178 functionsToCompile.reserve(object->functionsAndExpressions->count);
2179 for (QmlIR::CompiledFunctionOrExpression *foe = object->functionsAndExpressions->first; foe;
2180 foe = foe->next) {
2181 functionsToCompile << *foe;
2182 }
2183
2184 const auto runtimeFunctionIndices = generateJSCodeForFunctionsAndBindings(functionsToCompile);
2185 if (hasError())
2186 return false;
2187
2188 object->runtimeFunctionIndices.allocate(document->jsParserEngine.pool(),
2189 runtimeFunctionIndices);
2190 return true;
2191}
static const quint32 emptyStringIndex
#define COMPILE_EXCEPTION(location, desc)
static QStringList astNodeToStringList(QQmlJS::AST::Node *node)
static bool run(IRBuilder *builder, QQmlJS::AST::UiPragma *node, Pragma *pragma)
PoolList< Parameter > * parameters
QStringList parameterStringList(const QV4::Compiler::StringTableGenerator *stringPool) const