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