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