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