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