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