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