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