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