diff options
Diffstat (limited to 'src/qml/compiler')
-rw-r--r-- | src/qml/compiler/compiler.pri | 6 | ||||
-rw-r--r-- | src/qml/compiler/qqmlcodegenerator.cpp | 544 | ||||
-rw-r--r-- | src/qml/compiler/qqmlcodegenerator_p.h | 143 | ||||
-rw-r--r-- | src/qml/compiler/qqmltypecompiler.cpp | 1908 | ||||
-rw-r--r-- | src/qml/compiler/qqmltypecompiler_p.h | 247 | ||||
-rw-r--r-- | src/qml/compiler/qv4compileddata.cpp | 72 | ||||
-rw-r--r-- | src/qml/compiler/qv4compileddata_p.h | 106 | ||||
-rw-r--r-- | src/qml/compiler/qv4compiler.cpp | 21 | ||||
-rw-r--r-- | src/qml/compiler/qv4compiler_p.h | 2 | ||||
-rw-r--r-- | src/qml/compiler/qv4instr_moth_p.h | 107 | ||||
-rw-r--r-- | src/qml/compiler/qv4isel_masm.cpp | 115 | ||||
-rw-r--r-- | src/qml/compiler/qv4isel_masm_p.h | 32 | ||||
-rw-r--r-- | src/qml/compiler/qv4isel_moth.cpp | 329 | ||||
-rw-r--r-- | src/qml/compiler/qv4isel_moth_p.h | 4 | ||||
-rw-r--r-- | src/qml/compiler/qv4isel_p.h | 2 | ||||
-rw-r--r-- | src/qml/compiler/qv4isel_util_p.h | 36 | ||||
-rw-r--r-- | src/qml/compiler/qv4jsir.cpp | 16 | ||||
-rw-r--r-- | src/qml/compiler/qv4regalloc.cpp | 5 | ||||
-rw-r--r-- | src/qml/compiler/qv4ssa.cpp | 18 | ||||
-rw-r--r-- | src/qml/compiler/qv4ssa_p.h | 2 |
20 files changed, 3239 insertions, 476 deletions
diff --git a/src/qml/compiler/compiler.pri b/src/qml/compiler/compiler.pri index df4f5e8dc3..b2569f9111 100644 --- a/src/qml/compiler/compiler.pri +++ b/src/qml/compiler/compiler.pri @@ -15,7 +15,8 @@ HEADERS += \ $$PWD/qv4ssa_p.h \ $$PWD/qv4regalloc_p.h \ $$PWD/qqmlcodegenerator_p.h \ - $$PWD/qv4isel_masm_p.h + $$PWD/qv4isel_masm_p.h \ + $$PWD/qqmltypecompiler_p.h SOURCES += \ $$PWD/qv4compileddata.cpp \ @@ -28,6 +29,7 @@ SOURCES += \ $$PWD/qv4ssa.cpp \ $$PWD/qv4regalloc.cpp \ $$PWD/qqmlcodegenerator.cpp \ - $$PWD/qv4isel_masm.cpp + $$PWD/qv4isel_masm.cpp \ + $$PWD/qqmltypecompiler.cpp include(../../3rdparty/masm/masm.pri) diff --git a/src/qml/compiler/qqmlcodegenerator.cpp b/src/qml/compiler/qqmlcodegenerator.cpp index 13b23fde68..f933465100 100644 --- a/src/qml/compiler/qqmlcodegenerator.cpp +++ b/src/qml/compiler/qqmlcodegenerator.cpp @@ -54,6 +54,8 @@ QT_USE_NAMESPACE +static const quint32 emptyStringIndex = 0; + DEFINE_BOOL_CONFIG_OPTION(lookupHints, QML_LOOKUP_HINTS); using namespace QtQml; @@ -64,6 +66,22 @@ using namespace QtQml; return false; \ } +void QmlObject::init(MemoryPool *pool, int typeNameIndex, int id, const AST::SourceLocation &loc) +{ + inheritedTypeNameIndex = typeNameIndex; + + location.line = loc.startLine; + location.column = loc.startColumn; + + idIndex = id; + indexOfDefaultProperty = -1; + properties = pool->New<PoolList<QmlProperty> >(); + qmlSignals = pool->New<PoolList<Signal> >(); + bindings = pool->New<PoolList<Binding> >(); + functions = pool->New<PoolList<Function> >(); + declarationsOverride = 0; +} + void QmlObject::dump(DebugStream &out) { out << inheritedTypeNameIndex << " {" << endl; @@ -73,6 +91,95 @@ void QmlObject::dump(DebugStream &out) out << "}" << endl; } +QString QmlObject::sanityCheckFunctionNames(const QList<CompiledFunctionOrExpression> &allFunctions, const QSet<QString> &illegalNames, AST::SourceLocation *errorLocation) +{ + QSet<int> functionNames; + for (Function *f = functions->first; f; f = f->next) { + AST::FunctionDeclaration *function = AST::cast<AST::FunctionDeclaration*>(allFunctions.at(f->index).node); + Q_ASSERT(function); + *errorLocation = function->identifierToken; + QString name = function->name.toString(); + if (functionNames.contains(f->nameIndex)) + return tr("Duplicate method name"); + functionNames.insert(f->nameIndex); + if (signalNames.contains(f->nameIndex)) + return tr("Duplicate method name"); + + if (name.at(0).isUpper()) + return tr("Method names cannot begin with an upper case letter"); + if (illegalNames.contains(name)) + return tr("Illegal method name"); + } + return QString(); // no error +} + +QString QmlObject::appendSignal(Signal *signal) +{ + QmlObject *target = declarationsOverride; + if (!target) + target = this; + if (target->signalNames.contains(signal->nameIndex)) + return tr("Duplicate signal name"); + target->signalNames.insert(signal->nameIndex); + target->qmlSignals->append(signal); + return QString(); // no error +} + +QString QmlObject::appendProperty(QmlProperty *prop, const QString &propertyName, bool isDefaultProperty, const AST::SourceLocation &defaultToken, AST::SourceLocation *errorLocation) +{ + QmlObject *target = declarationsOverride; + if (!target) + target = this; + + if (target->propertyNames.contains(prop->nameIndex)) + return tr("Duplicate property name"); + + if (propertyName.constData()->isUpper()) + return tr("Property names cannot begin with an upper case letter"); + + target->propertyNames.insert(prop->nameIndex); + + const int index = target->properties->append(prop); + if (isDefaultProperty) { + if (target->indexOfDefaultProperty != -1) { + *errorLocation = defaultToken; + return tr("Duplicate default property"); + } + target->indexOfDefaultProperty = index; + } + return QString(); // no error +} + +void QmlObject::appendFunction(Function *f) +{ + QmlObject *target = declarationsOverride; + if (!target) + target = this; + target->functions->append(f); +} + +QString QmlObject::appendBinding(Binding *b, bool isListBinding) +{ + const bool bindingToDefaultProperty = (b->propertyNameIndex == 0); + if (!isListBinding && !bindingToDefaultProperty + && b->type != QV4::CompiledData::Binding::Type_GroupProperty + && b->type != QV4::CompiledData::Binding::Type_AttachedProperty + && !(b->flags & QV4::CompiledData::Binding::IsOnAssignment)) { + if (bindingNames.contains(b->propertyNameIndex)) + return tr("Property value set multiple times"); + bindingNames.insert(b->propertyNameIndex); + } + if (isListBinding) { + bindings->append(b); + } else if (bindingToDefaultProperty) { + Binding *insertionPoint = bindings->findSortedInsertionPoint<QV4::CompiledData::Location, QV4::CompiledData::Binding, &QV4::CompiledData::Binding::location>(b); + bindings->insertAfter(insertionPoint, b); + } else { + bindings->prepend(b); + } + return QString(); // no error +} + QStringList Signal::parameterStringList(const QStringList &stringPool) const { QStringList result; @@ -82,8 +189,10 @@ QStringList Signal::parameterStringList(const QStringList &stringPool) const return result; } -QQmlCodeGenerator::QQmlCodeGenerator() - : _object(0) +QQmlCodeGenerator::QQmlCodeGenerator(const QSet<QString> &illegalNames) + : illegalNames(illegalNames) + , _object(0) + , _propertyDeclaration(0) , jsGenerator(0) { } @@ -132,7 +241,7 @@ bool QQmlCodeGenerator::generateFromQml(const QString &code, const QUrl &url, co this->pool = output->jsParserEngine.pool(); this->jsGenerator = &output->jsGenerator; - emptyStringIndex = registerString(QString()); + Q_ASSERT(registerString(QString()) == emptyStringIndex); sourceCode = code; @@ -205,7 +314,7 @@ bool QQmlCodeGenerator::visit(AST::UiObjectDefinition *node) int idx = defineQMLObject(node); appendBinding(node->qualifiedTypeNameId->identifierToken, emptyStringIndex, idx); } else { - int idx = defineQMLObject(/*qualfied type name id*/0, node->initializer); + int idx = defineQMLObject(/*qualfied type name id*/0, node->qualifiedTypeNameId->firstSourceLocation(), node->initializer, /*declarations should go here*/_object); appendBinding(node->qualifiedTypeNameId, idx); } return false; @@ -213,8 +322,8 @@ bool QQmlCodeGenerator::visit(AST::UiObjectDefinition *node) bool QQmlCodeGenerator::visit(AST::UiObjectBinding *node) { - int idx = defineQMLObject(node->qualifiedTypeNameId, node->initializer); - appendBinding(node->qualifiedId, idx); + int idx = defineQMLObject(node->qualifiedTypeNameId, node->qualifiedTypeNameId->firstSourceLocation(), node->initializer); + appendBinding(node->qualifiedId, idx, node->hasOnToken); return false; } @@ -277,63 +386,31 @@ void QQmlCodeGenerator::accept(AST::Node *node) AST::Node::acceptChild(node, this); } -bool QQmlCodeGenerator::sanityCheckFunctionNames() -{ - QSet<QString> functionNames; - for (Function *f = _object->functions->first; f; f = f->next) { - AST::FunctionDeclaration *function = AST::cast<AST::FunctionDeclaration*>(_functions.at(f->index).node); - Q_ASSERT(function); - QString name = function->name.toString(); - if (functionNames.contains(name)) - COMPILE_EXCEPTION(function->identifierToken, tr("Duplicate method name")); - functionNames.insert(name); - if (_signalNames.contains(name)) - COMPILE_EXCEPTION(function->identifierToken, tr("Duplicate method name")); - - if (name.at(0).isUpper()) - COMPILE_EXCEPTION(function->identifierToken, tr("Method names cannot begin with an upper case letter")); -#if 0 // ### - if (enginePrivate->v8engine()->illegalNames().contains(currSlot.name.toString())) - COMPILE_EXCEPTION(&currSlot, tr("Illegal method name")); -#endif - } - return true; -} - -int QQmlCodeGenerator::defineQMLObject(AST::UiQualifiedId *qualifiedTypeNameId, AST::UiObjectInitializer *initializer) +int QQmlCodeGenerator::defineQMLObject(AST::UiQualifiedId *qualifiedTypeNameId, const AST::SourceLocation &location, AST::UiObjectInitializer *initializer, QmlObject *declarationsOverride) { QmlObject *obj = New<QmlObject>(); _objects.append(obj); const int objectIndex = _objects.size() - 1; qSwap(_object, obj); - _object->inheritedTypeNameIndex = registerString(asString(qualifiedTypeNameId)); + _object->init(pool, registerString(asString(qualifiedTypeNameId)), emptyStringIndex, location); + _object->declarationsOverride = declarationsOverride; - AST::SourceLocation loc; - if (qualifiedTypeNameId) - loc = qualifiedTypeNameId->firstSourceLocation(); - _object->location.line = loc.startLine; - _object->location.column = loc.startColumn; - - _object->idIndex = emptyStringIndex; - _object->indexOfDefaultProperty = -1; - _object->properties = New<PoolList<QmlProperty> >(); - _object->qmlSignals = New<PoolList<Signal> >(); - _object->bindings = New<PoolList<Binding> >(); - _object->functions = New<PoolList<Function> >(); - - QSet<QString> propertyNames; - qSwap(_propertyNames, propertyNames); - QSet<QString> signalNames; - qSwap(_signalNames, signalNames); + // A new object is also a boundary for property declarations. + QmlProperty *declaration = 0; + qSwap(_propertyDeclaration, declaration); accept(initializer); - sanityCheckFunctionNames(); + qSwap(_propertyDeclaration, declaration); - qSwap(_propertyNames, propertyNames); - qSwap(_signalNames, signalNames); qSwap(_object, obj); + + AST::SourceLocation loc; + QString error = obj->sanityCheckFunctionNames(_functions, illegalNames, &loc); + if (!error.isEmpty()) + recordError(loc, error); + return objectIndex; } @@ -522,7 +599,7 @@ bool QQmlCodeGenerator::visit(AST::UiPublicMember *node) QString signalName = node->name.toString(); signal->nameIndex = registerString(signalName); - AST::SourceLocation loc = node->firstSourceLocation(); + AST::SourceLocation loc = node->typeToken; signal->location.line = loc.startLine; signal->location.column = loc.startColumn; @@ -582,19 +659,17 @@ bool QQmlCodeGenerator::visit(AST::UiPublicMember *node) p = p->next; } - if (_signalNames.contains(signalName)) - COMPILE_EXCEPTION(node->identifierToken, tr("Duplicate signal name")); - _signalNames.insert(signalName); - if (signalName.at(0).isUpper()) COMPILE_EXCEPTION(node->identifierToken, tr("Signal names cannot begin with an upper case letter")); -#if 0 // ### cannot access identifier table from separate thread - if (enginePrivate->v8engine()->illegalNames().contains(currSig.name.toString())) - COMPILE_EXCEPTION(&currSig, tr("Illegal signal name")); -#endif + if (illegalNames.contains(signalName)) + COMPILE_EXCEPTION(node->identifierToken, tr("Illegal signal name")); - _object->qmlSignals->append(signal); + QString error = _object->appendSignal(signal); + if (!error.isEmpty()) { + recordError(node->identifierToken, error); + return false; + } } else { const QStringRef &memberType = node->memberType; const QStringRef &name = node->name; @@ -662,7 +737,8 @@ bool QQmlCodeGenerator::visit(AST::UiPublicMember *node) else property->customTypeNameIndex = emptyStringIndex; - property->nameIndex = registerString(name.toString()); + const QString propName = name.toString(); + property->nameIndex = registerString(propName); AST::SourceLocation loc = node->firstSourceLocation(); property->location.line = loc.startLine; @@ -710,25 +786,38 @@ bool QQmlCodeGenerator::visit(AST::UiPublicMember *node) propertyValue += alias.at(2); } property->aliasPropertyValueIndex = registerString(propertyValue); - } else if (node->statement) - appendBinding(node->identifierToken, property->nameIndex, node->statement); + } else if (node->statement) { + qSwap(_propertyDeclaration, property); + appendBinding(node->identifierToken, _propertyDeclaration->nameIndex, node->statement); + qSwap(_propertyDeclaration, property); + } - _object->properties->append(property); + AST::SourceLocation errorLocation; + QString error; - if (node->isDefaultMember) { - if (_object->indexOfDefaultProperty != -1) { - QQmlError error; - error.setDescription(QCoreApplication::translate("QQmlParser","Duplicate default property")); - error.setLine(node->defaultToken.startLine); - error.setColumn(node->defaultToken.startColumn); - errors << error; - return false; - } - _object->indexOfDefaultProperty = _object->properties->count - 1; + if (illegalNames.contains(propName)) + error = tr("Illegal property name"); + else + error = _object->appendProperty(property, propName, node->isDefaultMember, node->defaultToken, &errorLocation); + + if (!error.isEmpty()) { + if (errorLocation.startLine == 0) + errorLocation = node->identifierToken; + + QQmlError qmlError; + qmlError.setDescription(error); + qmlError.setLine(errorLocation.startLine); + qmlError.setColumn(errorLocation.startColumn); + errors << qmlError; + return false; } - // process QML-like initializers (e.g. property Object o: Object {}) - AST::Node::accept(node->binding, this); + if (node->binding) { + qSwap(_propertyDeclaration, property); + // process QML-like initializers (e.g. property Object o: Object {}) + AST::Node::accept(node->binding, this); + qSwap(_propertyDeclaration, property); + } } return false; @@ -739,8 +828,13 @@ bool QQmlCodeGenerator::visit(AST::UiSourceElement *node) if (AST::FunctionDeclaration *funDecl = AST::cast<AST::FunctionDeclaration *>(node->sourceElement)) { _functions << funDecl; Function *f = New<Function>(); + f->functionDeclaration = funDecl; + AST::SourceLocation loc = funDecl->identifierToken; + f->location.line = loc.startLine; + f->location.column = loc.startColumn; f->index = _functions.size() - 1; - _object->functions->append(f); + f->nameIndex = registerString(funDecl->name.toString()); + _object->appendFunction(f); } else { QQmlError error; error.setDescription(QCoreApplication::translate("QQmlParser","JavaScript declaration outside Script element")); @@ -798,7 +892,12 @@ QStringRef QQmlCodeGenerator::textRefAt(const AST::SourceLocation &first, const void QQmlCodeGenerator::setBindingValue(QV4::CompiledData::Binding *binding, AST::Statement *statement) { + AST::SourceLocation loc = statement->firstSourceLocation(); + binding->valueLocation.line = loc.startLine; + binding->valueLocation.column = loc.startColumn; binding->type = QV4::CompiledData::Binding::Type_Invalid; + if (_propertyDeclaration && (_propertyDeclaration->flags & QV4::CompiledData::Property::IsReadOnly)) + binding->flags |= QV4::CompiledData::Binding::InitializerForReadOnlyDeclaration; if (AST::ExpressionStatement *stmt = AST::cast<AST::ExpressionStatement *>(statement)) { AST::ExpressionNode *expr = stmt->expression; @@ -844,23 +943,20 @@ void QQmlCodeGenerator::appendBinding(AST::UiQualifiedId *name, AST::Statement * qSwap(_object, object); } -void QQmlCodeGenerator::appendBinding(AST::UiQualifiedId *name, int objectIndex) +void QQmlCodeGenerator::appendBinding(AST::UiQualifiedId *name, int objectIndex, bool isOnAssignment) { QmlObject *object = 0; if (!resolveQualifiedId(&name, &object)) return; qSwap(_object, object); - appendBinding(name->identifierToken, registerString(name->name.toString()), objectIndex); + appendBinding(name->identifierToken, registerString(name->name.toString()), objectIndex, /*isListItem*/false, isOnAssignment); qSwap(_object, object); } -void QQmlCodeGenerator::appendBinding(const AST::SourceLocation &nameLocation, int propertyNameIndex, AST::Statement *value) +void QQmlCodeGenerator::appendBinding(const AST::SourceLocation &nameLocation, quint32 propertyNameIndex, AST::Statement *value) { - if (!sanityCheckPropertyName(nameLocation, propertyNameIndex)) - return; - if (stringAt(propertyNameIndex) == QStringLiteral("id")) { - setId(value); + setId(nameLocation, value); return; } @@ -870,14 +966,14 @@ void QQmlCodeGenerator::appendBinding(const AST::SourceLocation &nameLocation, i binding->location.column = nameLocation.startColumn; binding->flags = 0; setBindingValue(binding, value); - _object->bindings->append(binding); + QString error = bindingsTarget()->appendBinding(binding, /*isListBinding*/false); + if (!error.isEmpty()) { + recordError(nameLocation, error); + } } -void QQmlCodeGenerator::appendBinding(const AST::SourceLocation &nameLocation, int propertyNameIndex, int objectIndex, bool isListItem) +void QQmlCodeGenerator::appendBinding(const AST::SourceLocation &nameLocation, quint32 propertyNameIndex, int objectIndex, bool isListItem, bool isOnAssignment) { - if (!sanityCheckPropertyName(nameLocation, propertyNameIndex, isListItem)) - return; - if (stringAt(propertyNameIndex) == QStringLiteral("id")) { recordError(nameLocation, tr("Invalid component id specification")); return; @@ -887,26 +983,55 @@ void QQmlCodeGenerator::appendBinding(const AST::SourceLocation &nameLocation, i binding->propertyNameIndex = propertyNameIndex; binding->location.line = nameLocation.startLine; binding->location.column = nameLocation.startColumn; + + const QmlObject *obj = _objects.at(objectIndex); + binding->valueLocation = obj->location; + binding->flags = 0; - binding->type = QV4::CompiledData::Binding::Type_Object; + + if (_propertyDeclaration && (_propertyDeclaration->flags & QV4::CompiledData::Property::IsReadOnly)) + binding->flags |= QV4::CompiledData::Binding::InitializerForReadOnlyDeclaration; + + // No type name on the initializer means it must be a group property + if (_objects.at(objectIndex)->inheritedTypeNameIndex == emptyStringIndex) + binding->type = QV4::CompiledData::Binding::Type_GroupProperty; + else + binding->type = QV4::CompiledData::Binding::Type_Object; + + if (isOnAssignment) + binding->flags |= QV4::CompiledData::Binding::IsOnAssignment; + if (isListItem) + binding->flags |= QV4::CompiledData::Binding::IsListItem; + binding->value.objectIndex = objectIndex; - _object->bindings->append(binding); + QString error = bindingsTarget()->appendBinding(binding, isListItem); + if (!error.isEmpty()) { + recordError(nameLocation, error); + } +} + +QmlObject *QQmlCodeGenerator::bindingsTarget() const +{ + if (_propertyDeclaration && _object->declarationsOverride) + return _object->declarationsOverride; + return _object; } -bool QQmlCodeGenerator::setId(AST::Statement *value) +bool QQmlCodeGenerator::setId(const AST::SourceLocation &idLocation, AST::Statement *value) { AST::SourceLocation loc = value->firstSourceLocation(); QStringRef str; AST::Node *node = value; if (AST::ExpressionStatement *stmt = AST::cast<AST::ExpressionStatement *>(node)) { - if (AST::StringLiteral *lit = AST::cast<AST::StringLiteral *>(stmt->expression)) + if (AST::StringLiteral *lit = AST::cast<AST::StringLiteral *>(stmt->expression)) { str = lit->value; - else + node = 0; + } else node = stmt->expression; } - if (str.isEmpty()) + if (node && str.isEmpty()) str = asStringRef(node); if (str.isEmpty()) @@ -926,12 +1051,14 @@ bool QQmlCodeGenerator::setId(AST::Statement *value) COMPILE_EXCEPTION(loc, tr( "IDs must contain only letters, numbers, and underscores")); } -#if 0 // ### - if (enginePrivate->v8engine()->illegalNames().contains(str)) - COMPILE_EXCEPTION(v, tr( "ID illegally masks global JavaScript property")); -#endif + QString idQString(str.toString()); + if (illegalNames.contains(idQString)) + COMPILE_EXCEPTION(loc, tr( "ID illegally masks global JavaScript property")); - _object->idIndex = registerString(str.toString()); + if (_object->idIndex != emptyStringIndex) + COMPILE_EXCEPTION(idLocation, tr("Property value set multiple times")); + + _object->idIndex = registerString(idQString); _object->locationOfIdProperty.line = loc.startLine; _object->locationOfIdProperty.column = loc.startColumn; @@ -940,61 +1067,57 @@ bool QQmlCodeGenerator::setId(AST::Statement *value) bool QQmlCodeGenerator::resolveQualifiedId(AST::UiQualifiedId **nameToResolve, QmlObject **object) { - AST::UiQualifiedId *name = *nameToResolve; + AST::UiQualifiedId *qualifiedIdElement = *nameToResolve; + + if (qualifiedIdElement->name == QStringLiteral("id") && qualifiedIdElement->next) + COMPILE_EXCEPTION(qualifiedIdElement->identifierToken, tr( "Invalid use of id property")); + + // If it's a namespace, prepend the qualifier and we'll resolve it later to the correct type. + QString currentName = qualifiedIdElement->name.toString(); + if (qualifiedIdElement->next) { + foreach (QV4::CompiledData::Import* import, _imports) + if (import->qualifierIndex != emptyStringIndex + && stringAt(import->qualifierIndex) == currentName) { + qualifiedIdElement = qualifiedIdElement->next; + currentName += QLatin1Char('.'); + currentName += qualifiedIdElement->name; + + if (!qualifiedIdElement->name.unicode()->isUpper()) + COMPILE_EXCEPTION(qualifiedIdElement->firstSourceLocation(), tr("Expected type name")); - if (name->name == QStringLiteral("id") && name->next) - COMPILE_EXCEPTION(name->identifierToken, tr( "Invalid use of id property")); + break; + } + } *object = _object; - while (name->next) { + while (qualifiedIdElement->next) { Binding *binding = New<Binding>(); - binding->propertyNameIndex = registerString(name->name.toString()); - binding->location.line = name->identifierToken.startLine; - binding->location.column = name->identifierToken.startColumn; + binding->propertyNameIndex = registerString(currentName); + binding->location.line = qualifiedIdElement->identifierToken.startLine; + binding->location.column = qualifiedIdElement->identifierToken.startColumn; + binding->valueLocation.line = binding->valueLocation.column = 0; binding->flags = 0; - if (name->name.unicode()->isUpper()) + if (qualifiedIdElement->name.unicode()->isUpper()) binding->type = QV4::CompiledData::Binding::Type_AttachedProperty; else binding->type = QV4::CompiledData::Binding::Type_GroupProperty; - int objIndex = defineQMLObject(0, 0); + int objIndex = defineQMLObject(0, AST::SourceLocation(), 0, 0); binding->value.objectIndex = objIndex; - (*object)->bindings->append(binding); + QString error = (*object)->appendBinding(binding, /*isListBinding*/false); + if (!error.isEmpty()) { + recordError(qualifiedIdElement->identifierToken, error); + return false; + } *object = _objects[objIndex]; - name = name->next; - } - *nameToResolve = name; - return true; -} - -bool QQmlCodeGenerator::sanityCheckPropertyName(const AST::SourceLocation &nameLocation, int nameIndex, bool isListItem) -{ - const QString &name = jsGenerator->strings.at(nameIndex); - if (name.isEmpty()) - return true; - - // List items are implement by multiple bindings to the same name, so allow duplicates. - if (!isListItem) { - if (_propertyNames.contains(name)) - COMPILE_EXCEPTION(nameLocation, tr("Duplicate property name")); - - _propertyNames.insert(name); + qualifiedIdElement = qualifiedIdElement->next; + if (qualifiedIdElement) + currentName = qualifiedIdElement->name.toString(); } - - if (name.at(0).isUpper()) - COMPILE_EXCEPTION(nameLocation, tr("Property names cannot begin with an upper case letter")); - -#if 0 // ### how to check against illegalNames when in separate thread? - if (enginePrivate->v8engine()->illegalNames().contains(prop.name.toString())) { - COMPILE_EXCEPTION_LOCATION(prop.nameLocation.line, - prop.nameLocation.column, - tr("Illegal property name")); - } -#endif - + *nameToResolve = qualifiedIdElement; return true; } @@ -1011,20 +1134,21 @@ void QQmlCodeGenerator::recordError(const AST::SourceLocation &location, const Q void QQmlCodeGenerator::collectTypeReferences() { foreach (QmlObject *obj, _objects) { - if (!stringAt(obj->inheritedTypeNameIndex).isEmpty()) - _typeReferences.add(obj->inheritedTypeNameIndex, obj->location); - - for (QmlProperty *prop = obj->properties->first; prop; prop = prop->next) { - if (prop->type >= QV4::CompiledData::Property::Custom) - _typeReferences.add(prop->customTypeNameIndex, prop->location); + if (obj->inheritedTypeNameIndex != emptyStringIndex) { + QV4::CompiledData::TypeReference &r = _typeReferences.add(obj->inheritedTypeNameIndex, obj->location); + r.needsCreation = true; } - for (Signal *sig = obj->qmlSignals->first; sig; sig = sig->next) - for (SignalParameter *param = sig->parameters->first; param; param = param->next) - if (!stringAt(param->customTypeNameIndex).isEmpty()) - _typeReferences.add(param->customTypeNameIndex, param->location); + for (const QmlProperty *prop = obj->firstProperty(); prop; prop = prop->next) { + if (prop->type >= QV4::CompiledData::Property::Custom) { + // ### FIXME: We could report the more accurate location here by using prop->location, but the old + // compiler can't and the tests expect it to be the object location right now. + QV4::CompiledData::TypeReference &r = _typeReferences.add(prop->customTypeNameIndex, obj->location); + r.needsCreation = true; + } + } - for (Binding *binding = obj->bindings->first; binding; binding = binding->next) { + for (const Binding *binding = obj->firstBinding(); binding; binding = binding->next) { if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) _typeReferences.add(binding->propertyNameIndex, binding->location); } @@ -1082,10 +1206,10 @@ QV4::CompiledData::QmlUnit *QmlUnitGenerator::generate(ParsedQML &output, const int objectsSize = 0; foreach (QmlObject *o, output.objects) { objectOffsets.insert(o, unitSize + importSize + objectOffsetTableSize + objectsSize); - objectsSize += QV4::CompiledData::Object::calculateSizeExcludingSignals(o->functions->count, o->properties->count, o->qmlSignals->count, o->bindings->count); + objectsSize += QV4::CompiledData::Object::calculateSizeExcludingSignals(o->functionCount(), o->propertyCount(), o->signalCount(), o->bindingCount()); int signalTableSize = 0; - for (Signal *s = o->qmlSignals->first; s; s = s->next) + for (const Signal *s = o->firstSignal(); s; s = s->next) signalTableSize += QV4::CompiledData::Signal::calculateSize(s->parameters->count); objectsSize += signalTableSize; @@ -1128,46 +1252,45 @@ QV4::CompiledData::QmlUnit *QmlUnitGenerator::generate(ParsedQML &output, const quint32 nextOffset = sizeof(QV4::CompiledData::Object); - objectToWrite->nFunctions = o->functions->count; + objectToWrite->nFunctions = o->functionCount(); objectToWrite->offsetToFunctions = nextOffset; nextOffset += objectToWrite->nFunctions * sizeof(quint32); - objectToWrite->nProperties = o->properties->count; + objectToWrite->nProperties = o->propertyCount(); objectToWrite->offsetToProperties = nextOffset; nextOffset += objectToWrite->nProperties * sizeof(QV4::CompiledData::Property); - objectToWrite->nSignals = o->qmlSignals->count; + objectToWrite->nSignals = o->signalCount(); objectToWrite->offsetToSignals = nextOffset; nextOffset += objectToWrite->nSignals * sizeof(quint32); - objectToWrite->nBindings = o->bindings->count; + objectToWrite->nBindings = o->bindingCount(); objectToWrite->offsetToBindings = nextOffset; nextOffset += objectToWrite->nBindings * sizeof(QV4::CompiledData::Binding); quint32 *functionsTable = reinterpret_cast<quint32*>(objectPtr + objectToWrite->offsetToFunctions); - for (Function *f = o->functions->first; f; f = f->next) + for (const Function *f = o->firstFunction(); f; f = f->next) *functionsTable++ = runtimeFunctionIndices[f->index]; char *propertiesPtr = objectPtr + objectToWrite->offsetToProperties; - for (QmlProperty *p = o->properties->first; p; p = p->next) { + for (const QmlProperty *p = o->firstProperty(); p; p = p->next) { QV4::CompiledData::Property *propertyToWrite = reinterpret_cast<QV4::CompiledData::Property*>(propertiesPtr); *propertyToWrite = *p; propertiesPtr += sizeof(QV4::CompiledData::Property); } char *bindingPtr = objectPtr + objectToWrite->offsetToBindings; - for (Binding *b = o->bindings->first; b; b = b->next) { - QV4::CompiledData::Binding *bindingToWrite = reinterpret_cast<QV4::CompiledData::Binding*>(bindingPtr); - *bindingToWrite = *b; - if (b->type == QV4::CompiledData::Binding::Type_Script) - bindingToWrite->value.compiledScriptIndex = runtimeFunctionIndices[b->value.compiledScriptIndex]; - bindingPtr += sizeof(QV4::CompiledData::Binding); - } + bindingPtr = writeBindings(bindingPtr, o, runtimeFunctionIndices, &QV4::CompiledData::Binding::isValueBindingNoAlias); + bindingPtr = writeBindings(bindingPtr, o, runtimeFunctionIndices, &QV4::CompiledData::Binding::isSignalHandler); + bindingPtr = writeBindings(bindingPtr, o, runtimeFunctionIndices, &QV4::CompiledData::Binding::isAttachedProperty); + bindingPtr = writeBindings(bindingPtr, o, runtimeFunctionIndices, &QV4::CompiledData::Binding::isGroupProperty); + bindingPtr = writeBindings(bindingPtr, o, runtimeFunctionIndices, &QV4::CompiledData::Binding::isValueBindingToAlias); + Q_ASSERT((bindingPtr - objectToWrite->offsetToBindings - objectPtr) / sizeof(QV4::CompiledData::Binding) == unsigned(o->bindingCount())); quint32 *signalOffsetTable = reinterpret_cast<quint32*>(objectPtr + objectToWrite->offsetToSignals); quint32 signalTableSize = 0; char *signalPtr = objectPtr + nextOffset; - for (Signal *s = o->qmlSignals->first; s; s = s->next) { + for (const Signal *s = o->firstSignal(); s; s = s->next) { *signalOffsetTable++ = signalPtr - objectPtr; QV4::CompiledData::Signal *signalToWrite = reinterpret_cast<QV4::CompiledData::Signal*>(signalPtr); @@ -1184,7 +1307,7 @@ QV4::CompiledData::QmlUnit *QmlUnitGenerator::generate(ParsedQML &output, const signalPtr += size; } - objectPtr += QV4::CompiledData::Object::calculateSizeExcludingSignals(o->functions->count, o->properties->count, o->qmlSignals->count, o->bindings->count); + objectPtr += QV4::CompiledData::Object::calculateSizeExcludingSignals(o->functionCount(), o->propertyCount(), o->signalCount(), o->bindingCount()); objectPtr += signalTableSize; } @@ -1199,6 +1322,20 @@ QV4::CompiledData::QmlUnit *QmlUnitGenerator::generate(ParsedQML &output, const return qmlUnit; } +char *QmlUnitGenerator::writeBindings(char *bindingPtr, QmlObject *o, const QVector<int> &runtimeFunctionIndices, BindingFilter filter) const +{ + for (const Binding *b = o->firstBinding(); b; b = b->next) { + if (!(b->*(filter))()) + continue; + QV4::CompiledData::Binding *bindingToWrite = reinterpret_cast<QV4::CompiledData::Binding*>(bindingPtr); + *bindingToWrite = *b; + if (b->type == QV4::CompiledData::Binding::Type_Script) + bindingToWrite->value.compiledScriptIndex = runtimeFunctionIndices[b->value.compiledScriptIndex]; + bindingPtr += sizeof(QV4::CompiledData::Binding); + } + return bindingPtr; +} + int QmlUnitGenerator::getStringId(const QString &str) const { return jsUnitGenerator->getStringId(str); @@ -1620,16 +1757,24 @@ SignalHandlerConverter::SignalHandlerConverter(QQmlEnginePrivate *enginePrivate, : enginePrivate(enginePrivate) , parsedQML(parsedQML) , unit(unit) + , illegalNames(QV8Engine::get(QQmlEnginePrivate::get(enginePrivate))->illegalNames()) { } bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclarations() { - foreach (QmlObject *obj, parsedQML->objects) { + for (int objectIndex = 0; objectIndex < parsedQML->objects.count(); ++objectIndex) { + QmlObject * const obj = parsedQML->objects.at(objectIndex); QString elementName = stringAt(obj->inheritedTypeNameIndex); if (elementName.isEmpty()) continue; - QQmlPropertyCache *cache = unit->resolvedTypes[obj->inheritedTypeNameIndex].createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); + QQmlCompiledData::TypeReference *tr = unit->resolvedTypes.value(obj->inheritedTypeNameIndex); + QQmlCustomParser *customParser = (tr && tr->type) ? tr->type->customParser() : 0; + if (customParser && !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) + continue; + QQmlPropertyCache *cache = unit->propertyCaches.value(objectIndex); + if (!cache) + continue; if (!convertSignalHandlerExpressionsToFunctionDeclarations(obj, elementName, cache)) return false; } @@ -1641,12 +1786,13 @@ bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclaratio // map from signal name defined in qml itself to list of parameters QHash<QString, QStringList> customSignals; - for (Binding *binding = obj->bindings->first; binding; binding = binding->next) { + for (Binding *binding = obj->firstBinding(); binding; binding = binding->next) { QString propertyName = stringAt(binding->propertyNameIndex); // Attached property? if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) { QmlObject *attachedObj = parsedQML->objects[binding->value.objectIndex]; - QQmlType *type = unit->resolvedTypes.value(binding->propertyNameIndex).type; + QQmlCompiledData::TypeReference *typeRef = unit->resolvedTypes.value(binding->propertyNameIndex); + QQmlType *type = typeRef ? typeRef->type : 0; const QMetaObject *attachedType = type ? type->attachedPropertiesType() : 0; if (!attachedType) COMPILE_EXCEPTION(binding->location, tr("Non-existent attached object")); @@ -1659,10 +1805,6 @@ bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclaratio if (!QQmlCodeGenerator::isSignalPropertyName(propertyName)) continue; - if (binding->type != QV4::CompiledData::Binding::Type_Script) { - COMPILE_EXCEPTION(binding->location, tr("Incorrectly specified signal assignment")); - } - PropertyResolver resolver(propertyCache); Q_ASSERT(propertyName.startsWith(QStringLiteral("on"))); @@ -1683,8 +1825,22 @@ bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclaratio QQmlPropertyData *signal = resolver.signal(propertyName, ¬InRevision); if (signal) { int sigIndex = propertyCache->methodIndexToSignalIndex(signal->coreIndex); - foreach (const QByteArray ¶m, propertyCache->signalParameterNames(sigIndex)) - parameters << QString::fromUtf8(param); + sigIndex = propertyCache->originalClone(sigIndex); + + bool unnamedParameter = false; + + QList<QByteArray> parameterNames = propertyCache->signalParameterNames(sigIndex); + for (int i = 0; i < parameterNames.count(); ++i) { + const QString param = QString::fromUtf8(parameterNames.at(i)); + if (param.isEmpty()) + unnamedParameter = true; + else if (unnamedParameter) { + COMPILE_EXCEPTION(binding->location, tr("Signal uses unnamed parameter followed by named parameter.")); + } else if (illegalNames.contains(param)) { + COMPILE_EXCEPTION(binding->location, tr("Signal parameter \"%1\" hides global variable.").arg(param)); + } + parameters += param; + } } else { if (notInRevision) { // Try assinging it as a property later @@ -1693,7 +1849,8 @@ bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclaratio const QString &originalPropertyName = stringAt(binding->propertyNameIndex); - const QQmlType *type = unit->resolvedTypes.value(obj->inheritedTypeNameIndex).type; + QQmlCompiledData::TypeReference *typeRef = unit->resolvedTypes.value(obj->inheritedTypeNameIndex); + const QQmlType *type = typeRef ? typeRef->type : 0; if (type) { COMPILE_EXCEPTION(binding->location, tr("\"%1.%2\" is not available in %3 %4.%5.").arg(typeName).arg(originalPropertyName).arg(type->module()).arg(type->majorVersion()).arg(type->minorVersion())); } else { @@ -1705,12 +1862,12 @@ bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclaratio // build cache if necessary if (customSignals.isEmpty()) { - for (Signal *signal = obj->qmlSignals->first; signal; signal = signal->next) { + for (const Signal *signal = obj->firstSignal(); signal; signal = signal->next) { const QString &signalName = stringAt(signal->nameIndex); customSignals.insert(signalName, signal->parameterStringList(parsedQML->jsGenerator.strings)); } - for (QmlProperty *property = obj->properties->first; property; property = property->next) { + for (const QmlProperty *property = obj->firstProperty(); property; property = property->next) { const QString propName = stringAt(property->nameIndex); customSignals.insert(propName, QStringList()); } @@ -1731,6 +1888,18 @@ bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclaratio parameters = entry.value(); } + binding->propertyNameIndex = parsedQML->jsGenerator.registerString(propertyName); + + // Binding object to signal means connect the signal to the object's default method. + if (binding->type == QV4::CompiledData::Binding::Type_Object) { + binding->flags |= QV4::CompiledData::Binding::IsSignalHandlerObject; + continue; + } + + if (binding->type != QV4::CompiledData::Binding::Type_Script) { + COMPILE_EXCEPTION(binding->location, tr("Incorrectly specified signal assignment")); + } + QQmlJS::Engine &jsEngine = parsedQML->jsParserEngine; QQmlJS::MemoryPool *pool = jsEngine.pool(); @@ -1758,7 +1927,6 @@ bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclaratio parsedQML->functions[binding->value.compiledScriptIndex] = functionDeclaration; binding->flags |= QV4::CompiledData::Binding::IsSignalHandlerExpression; - binding->propertyNameIndex = parsedQML->jsGenerator.registerString(propertyName); } return true; } @@ -1773,11 +1941,11 @@ void SignalHandlerConverter::recordError(const QV4::CompiledData::Location &loca errors << error; } -QQmlPropertyData *PropertyResolver::property(const QString &name, bool *notInRevision) +QQmlPropertyData *PropertyResolver::property(const QString &name, bool *notInRevision, QObject *object, QQmlContextData *context) { if (notInRevision) *notInRevision = false; - QQmlPropertyData *d = cache->property(name, 0, 0); + QQmlPropertyData *d = cache->property(name, object, context); // Find the first property while (d && d->isFunction()) @@ -1792,11 +1960,11 @@ QQmlPropertyData *PropertyResolver::property(const QString &name, bool *notInRev } -QQmlPropertyData *PropertyResolver::signal(const QString &name, bool *notInRevision) +QQmlPropertyData *PropertyResolver::signal(const QString &name, bool *notInRevision, QObject *object, QQmlContextData *context) { if (notInRevision) *notInRevision = false; - QQmlPropertyData *d = cache->property(name, 0, 0); + QQmlPropertyData *d = cache->property(name, object, context); if (notInRevision) *notInRevision = false; while (d && !(d->isFunction())) @@ -1812,7 +1980,7 @@ QQmlPropertyData *PropertyResolver::signal(const QString &name, bool *notInRevis if (name.endsWith(QStringLiteral("Changed"))) { QString propName = name.mid(0, name.length() - static_cast<int>(strlen("Changed"))); - d = property(propName, notInRevision); + d = property(propName, notInRevision, object, context); if (d) return cache->signal(d->notifyIndex); } diff --git a/src/qml/compiler/qqmlcodegenerator_p.h b/src/qml/compiler/qqmlcodegenerator_p.h index 0a0e4f2d5b..7c09b8bfa2 100644 --- a/src/qml/compiler/qqmlcodegenerator_p.h +++ b/src/qml/compiler/qqmlcodegenerator_p.h @@ -93,15 +93,49 @@ struct PoolList T *last; int count; - void append(T *item) { + int append(T *item) { item->next = 0; if (last) last->next = item; else first = item; last = item; + return count++; + } + + void prepend(T *item) { + item->next = first; + first = item; + if (!last) + last = first; ++count; } + + template <typename Sortable, typename Base, Sortable Base::*sortMember> + T *findSortedInsertionPoint(T *item) const + { + T *insertPos = 0; + + for (T *it = first; it; it = it->next) { + if (!(it->*sortMember < item->*sortMember)) + break; + insertPos = it; + } + + return insertPos; + } + + void insertAfter(T *insertionPoint, T *item) { + if (!insertionPoint) { + prepend(item); + } else if (insertionPoint == last) { + append(item); + } else { + item->next = insertionPoint->next; + insertionPoint->next = item; + ++count; + } + } }; struct QmlObject; @@ -135,25 +169,73 @@ struct Binding : public QV4::CompiledData::Binding struct Function { + AST::FunctionDeclaration *functionDeclaration; + QV4::CompiledData::Location location; + int nameIndex; int index; // index in parsedQML::functions Function *next; }; +struct CompiledFunctionOrExpression +{ + CompiledFunctionOrExpression() + : node(0) + , disableAcceleratedLookups(false) + {} + CompiledFunctionOrExpression(AST::Node *n) + : node(n) + , disableAcceleratedLookups(false) + {} + AST::Node *node; // FunctionDeclaration, Statement or Expression + QString name; + bool disableAcceleratedLookups; +}; + struct QmlObject { - int inheritedTypeNameIndex; - int idIndex; + Q_DECLARE_TR_FUNCTIONS(QmlObject) +public: + quint32 inheritedTypeNameIndex; + quint32 idIndex; int indexOfDefaultProperty; QV4::CompiledData::Location location; QV4::CompiledData::Location locationOfIdProperty; + const QmlProperty *firstProperty() const { return properties->first; } + int propertyCount() const { return properties->count; } + const Signal *firstSignal() const { return qmlSignals->first; } + int signalCount() const { return qmlSignals->count; } + Binding *firstBinding() const { return bindings->first; } + int bindingCount() const { return bindings->count; } + const Function *firstFunction() const { return functions->first; } + int functionCount() const { return functions->count; } + + // If set, then declarations for this object (and init bindings for these) should go into the + // specified object. Used for declarations inside group properties. + QmlObject *declarationsOverride; + + void init(QQmlJS::MemoryPool *pool, int typeNameIndex, int id, const AST::SourceLocation &location = AST::SourceLocation()); + + void dump(DebugStream &out); + + QString sanityCheckFunctionNames(const QList<CompiledFunctionOrExpression> &allFunctions, const QSet<QString> &illegalNames, AST::SourceLocation *errorLocation); + + QString appendSignal(Signal *signal); + QString appendProperty(QmlProperty *prop, const QString &propertyName, bool isDefaultProperty, const AST::SourceLocation &defaultToken, AST::SourceLocation *errorLocation); + void appendFunction(Function *f); + + QString appendBinding(Binding *b, bool isListBinding); + +private: PoolList<QmlProperty> *properties; PoolList<Signal> *qmlSignals; PoolList<Binding> *bindings; PoolList<Function> *functions; - void dump(DebugStream &out); + QSet<int> propertyNames; + QSet<int> bindingNames; + QSet<int> signalNames; }; struct Pragma @@ -166,20 +248,6 @@ struct Pragma QV4::CompiledData::Location location; }; -struct CompiledFunctionOrExpression -{ - CompiledFunctionOrExpression() - : disableAcceleratedLookups(false) - {} - CompiledFunctionOrExpression(AST::Node *n) - : node(n) - , disableAcceleratedLookups(false) - {} - AST::Node *node; // FunctionDeclaration, Statement or Expression - QString name; - bool disableAcceleratedLookups; -}; - struct ParsedQML { ParsedQML(bool debugMode) @@ -207,7 +275,7 @@ struct Q_QML_EXPORT QQmlCodeGenerator : public AST::Visitor { Q_DECLARE_TR_FUNCTIONS(QQmlCodeGenerator) public: - QQmlCodeGenerator(); + QQmlCodeGenerator(const QSet<QString> &illegalNames); bool generateFromQml(const QString &code, const QUrl &url, const QString &urlString, ParsedQML *output); static bool isSignalPropertyName(const QString &name); @@ -234,9 +302,9 @@ public: void accept(AST::Node *node); // returns index in _objects - int defineQMLObject(AST::UiQualifiedId *qualifiedTypeNameId, AST::UiObjectInitializer *initializer); - int defineQMLObject(AST::UiObjectDefinition *node) - { return defineQMLObject(node->qualifiedTypeNameId, node->initializer); } + int defineQMLObject(AST::UiQualifiedId *qualifiedTypeNameId, const AST::SourceLocation &location, AST::UiObjectInitializer *initializer, QmlObject *declarationsOverride = 0); + int defineQMLObject(AST::UiObjectDefinition *node, QmlObject *declarationsOverride = 0) + { return defineQMLObject(node->qualifiedTypeNameId, node->qualifiedTypeNameId->firstSourceLocation(), node->initializer, declarationsOverride); } static QString asString(AST::UiQualifiedId *node); QStringRef asStringRef(AST::Node *node); @@ -253,26 +321,26 @@ public: void setBindingValue(QV4::CompiledData::Binding *binding, AST::Statement *statement); void appendBinding(AST::UiQualifiedId *name, AST::Statement *value); - void appendBinding(AST::UiQualifiedId *name, int objectIndex); - void appendBinding(const AST::SourceLocation &nameLocation, int propertyNameIndex, AST::Statement *value); - void appendBinding(const AST::SourceLocation &nameLocation, int propertyNameIndex, int objectIndex, bool isListItem = false); + void appendBinding(AST::UiQualifiedId *name, int objectIndex, bool isOnAssignment = false); + void appendBinding(const AST::SourceLocation &nameLocation, quint32 propertyNameIndex, AST::Statement *value); + void appendBinding(const AST::SourceLocation &nameLocation, quint32 propertyNameIndex, int objectIndex, bool isListItem = false, bool isOnAssignment = false); + + QmlObject *bindingsTarget() const; - bool setId(AST::Statement *value); + bool setId(const AST::SourceLocation &idLocation, AST::Statement *value); // resolves qualified name (font.pixelSize for example) and returns the last name along // with the object any right-hand-side of a binding should apply to. bool resolveQualifiedId(AST::UiQualifiedId **nameToResolve, QmlObject **object); - bool sanityCheckPropertyName(const AST::SourceLocation &nameLocation, int nameIndex, bool isListItem = false); - void recordError(const AST::SourceLocation &location, const QString &description); void collectTypeReferences(); static QQmlScript::LocationSpan location(AST::SourceLocation start, AST::SourceLocation end); - int registerString(const QString &str) const { return jsGenerator->registerString(str); } - template <typename _Tp> _Tp *New() { return new (pool->allocate(sizeof(_Tp))) _Tp(); } + quint32 registerString(const QString &str) const { return jsGenerator->registerString(str); } + template <typename _Tp> _Tp *New() { return pool->New<_Tp>(); } QString stringAt(int index) const { return jsGenerator->strings.at(index); } @@ -280,6 +348,8 @@ public: QList<QQmlError> errors; + QSet<QString> illegalNames; + QList<QV4::CompiledData::Import*> _imports; QList<Pragma*> _pragmas; QList<QmlObject*> _objects; @@ -288,15 +358,12 @@ public: QV4::CompiledData::TypeReferenceMap _typeReferences; QmlObject *_object; - QSet<QString> _propertyNames; - QSet<QString> _signalNames; + QmlProperty *_propertyDeclaration; QQmlJS::MemoryPool *pool; QString sourceCode; QUrl url; QV4::Compiler::JSUnitGenerator *jsGenerator; - int emptyStringIndex; - bool sanityCheckFunctionNames(); }; struct Q_QML_EXPORT QmlUnitGenerator @@ -309,6 +376,9 @@ struct Q_QML_EXPORT QmlUnitGenerator QV4::CompiledData::QmlUnit *generate(ParsedQML &output, const QVector<int> &runtimeFunctionIndices); private: + typedef bool (Binding::*BindingFilter)() const; + char *writeBindings(char *bindingPtr, QmlObject *o, const QVector<int> &runtimeFunctionIndices, BindingFilter filter) const; + int getStringId(const QString &str) const; QV4::Compiler::JSUnitGenerator *jsUnitGenerator; @@ -325,10 +395,10 @@ struct PropertyResolver return cache->property(index); } - QQmlPropertyData *property(const QString &name, bool *notInRevision = 0); + QQmlPropertyData *property(const QString &name, bool *notInRevision = 0, QObject *object = 0, QQmlContextData *context = 0); // This code must match the semantics of QQmlPropertyPrivate::findSignalByName - QQmlPropertyData *signal(const QString &name, bool *notInRevision); + QQmlPropertyData *signal(const QString &name, bool *notInRevision, QObject *object = 0, QQmlContextData *context = 0); QQmlPropertyCache *cache; }; @@ -357,6 +427,7 @@ private: QQmlEnginePrivate *enginePrivate; ParsedQML *parsedQML; QQmlCompiledData *unit; + const QSet<QString> &illegalNames; }; struct Q_QML_EXPORT JSCodeGen : public QQmlJS::Codegen diff --git a/src/qml/compiler/qqmltypecompiler.cpp b/src/qml/compiler/qqmltypecompiler.cpp new file mode 100644 index 0000000000..dae57fe6f4 --- /dev/null +++ b/src/qml/compiler/qqmltypecompiler.cpp @@ -0,0 +1,1908 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmltypecompiler_p.h" + +#include <private/qqmlcompiler_p.h> +#include <private/qqmlobjectcreator_p.h> +#include <private/qqmlcustomparser_p.h> +#include <private/qqmlvmemetaobject_p.h> +#include <private/qqmlcomponent_p.h> +#include <private/qqmlstringconverters_p.h> + +#define COMPILE_EXCEPTION(token, desc) \ + { \ + recordError((token)->location, desc); \ + return false; \ + } + +QT_BEGIN_NAMESPACE + +QQmlTypeCompiler::QQmlTypeCompiler(QQmlEnginePrivate *engine, QQmlCompiledData *compiledData, QQmlTypeData *typeData, QtQml::ParsedQML *parsedQML) + : engine(engine) + , compiledData(compiledData) + , typeData(typeData) + , parsedQML(parsedQML) +{ +} + +bool QQmlTypeCompiler::compile() +{ + compiledData->importCache = new QQmlTypeNameCache; + + foreach (const QString &ns, typeData->namespaces()) + compiledData->importCache->add(ns); + + // Add any Composite Singletons that were used to the import cache + foreach (const QQmlTypeData::TypeReference &singleton, typeData->compositeSingletons()) + compiledData->importCache->add(singleton.type->qmlTypeName(), singleton.type->sourceUrl(), singleton.prefix); + + typeData->imports().populateCache(compiledData->importCache); + compiledData->importCache->addref(); + + const QHash<int, QQmlTypeData::TypeReference> &resolvedTypes = typeData->resolvedTypeRefs(); + for (QHash<int, QQmlTypeData::TypeReference>::ConstIterator resolvedType = resolvedTypes.constBegin(), end = resolvedTypes.constEnd(); + resolvedType != end; ++resolvedType) { + QScopedPointer<QQmlCompiledData::TypeReference> ref(new QQmlCompiledData::TypeReference); + QQmlType *qmlType = resolvedType->type; + if (resolvedType->typeData) { + if (resolvedType->needsCreation && qmlType->isCompositeSingleton()) { + QQmlError error; + QString reason = tr("Composite Singleton Type %1 is not creatable.").arg(qmlType->qmlTypeName()); + error.setDescription(reason); + error.setColumn(resolvedType->location.column); + error.setLine(resolvedType->location.line); + recordError(error); + return false; + } + ref->component = resolvedType->typeData->compiledData(); + ref->component->addref(); + } else if (qmlType) { + ref->type = qmlType; + Q_ASSERT(ref->type); + + if (resolvedType->needsCreation && !ref->type->isCreatable()) { + QQmlError error; + QString reason = ref->type->noCreationReason(); + if (reason.isEmpty()) + reason = tr("Element is not creatable."); + error.setDescription(reason); + error.setColumn(resolvedType->location.column); + error.setLine(resolvedType->location.line); + recordError(error); + return false; + } + + if (ref->type->containsRevisionedAttributes()) { + QQmlError cacheError; + ref->typePropertyCache = engine->cache(ref->type, + resolvedType->minorVersion, + cacheError); + if (!ref->typePropertyCache) { + cacheError.setColumn(resolvedType->location.column); + cacheError.setLine(resolvedType->location.line); + recordError(cacheError); + return false; + } + ref->typePropertyCache->addref(); + } + } + ref->majorVersion = resolvedType->majorVersion; + ref->minorVersion = resolvedType->minorVersion; + compiledData->resolvedTypes.insert(resolvedType.key(), ref.take()); + } + + // Build property caches and VME meta object data + + const int objectCount = parsedQML->objects.count(); + compiledData->datas.reserve(objectCount); + compiledData->propertyCaches.reserve(objectCount); + + { + QQmlPropertyCacheCreator propertyCacheBuilder(this); + if (!propertyCacheBuilder.buildMetaObjects()) + return false; + } + + { + SignalHandlerConverter converter(engine, parsedQML, compiledData); + if (!converter.convertSignalHandlerExpressionsToFunctionDeclarations()) { + errors << converter.errors; + return false; + } + } + + { + QQmlEnumTypeResolver enumResolver(this); + if (!enumResolver.resolveEnumBindings()) + return false; + } + + { + QQmlAliasAnnotator annotator(this); + annotator.annotateBindingsToAliases(); + } + + // Collect imported scripts + const QList<QQmlTypeData::ScriptReference> &scripts = typeData->resolvedScripts(); + compiledData->scripts.reserve(scripts.count()); + for (int scriptIndex = 0; scriptIndex < scripts.count(); ++scriptIndex) { + const QQmlTypeData::ScriptReference &script = scripts.at(scriptIndex); + + QString qualifier = script.qualifier; + QString enclosingNamespace; + + const int lastDotIndex = qualifier.lastIndexOf(QLatin1Char('.')); + if (lastDotIndex != -1) { + enclosingNamespace = qualifier.left(lastDotIndex); + qualifier = qualifier.mid(lastDotIndex+1); + } + + compiledData->importCache->add(qualifier, scriptIndex, enclosingNamespace); + QQmlScriptData *scriptData = script.script->scriptData(); + scriptData->addref(); + compiledData->scripts << scriptData; + } + + // Resolve component boundaries and aliases + + { + // Scan for components, determine their scopes and resolve aliases within the scope. + QQmlComponentAndAliasResolver resolver(this); + if (!resolver.resolve()) + return false; + } + + // Compile JS binding expressions and signal handlers + + JSCodeGen jsCodeGen(typeData->finalUrlString(), parsedQML->code, &parsedQML->jsModule, &parsedQML->jsParserEngine, parsedQML->program, compiledData->importCache); + const QVector<int> runtimeFunctionIndices = jsCodeGen.generateJSCodeForFunctionsAndBindings(parsedQML->functions); + QList<QQmlError> jsErrors = jsCodeGen.errors(); + if (!jsErrors.isEmpty()) { + errors << jsErrors; + return false; + } + + QV4::ExecutionEngine *v4 = engine->v4engine(); + + QScopedPointer<QQmlJS::EvalInstructionSelection> isel(v4->iselFactory->create(engine, v4->executableAllocator, &parsedQML->jsModule, &parsedQML->jsGenerator)); + isel->setUseFastLookups(false); + QV4::CompiledData::CompilationUnit *jsUnit = isel->compile(/*generated unit data*/false); + + // Generate QML compiled type data structures + + QmlUnitGenerator qmlGenerator; + QV4::CompiledData::QmlUnit *qmlUnit = qmlGenerator.generate(*parsedQML, runtimeFunctionIndices); + + if (jsUnit) { + Q_ASSERT(!jsUnit->data); + jsUnit->ownsData = false; + jsUnit->data = &qmlUnit->header; + } + + compiledData->compilationUnit = jsUnit; + if (compiledData->compilationUnit) + compiledData->compilationUnit->ref(); + compiledData->qmlUnit = qmlUnit; // ownership transferred to m_compiledData + + // Add to type registry of composites + if (compiledData->isCompositeType()) + engine->registerInternalCompositeType(compiledData); + else { + const QV4::CompiledData::Object *obj = qmlUnit->objectAt(qmlUnit->indexOfRootObject); + QQmlCompiledData::TypeReference *typeRef = compiledData->resolvedTypes.value(obj->inheritedTypeNameIndex); + Q_ASSERT(typeRef); + if (typeRef->component) { + compiledData->metaTypeId = typeRef->component->metaTypeId; + compiledData->listMetaTypeId = typeRef->component->listMetaTypeId; + } else { + compiledData->metaTypeId = typeRef->type->typeId(); + compiledData->listMetaTypeId = typeRef->type->qListTypeId(); + } + } + + // Sanity check property bindings + QQmlPropertyValidator validator(this, runtimeFunctionIndices); + if (!validator.validate()) + return false; + + // Collect some data for instantiation later. + int bindingCount = 0; + int parserStatusCount = 0; + for (quint32 i = 0; i < qmlUnit->nObjects; ++i) { + const QV4::CompiledData::Object *obj = qmlUnit->objectAt(i); + bindingCount += obj->nBindings; + if (QQmlCompiledData::TypeReference *typeRef = compiledData->resolvedTypes.value(obj->inheritedTypeNameIndex)) { + if (QQmlType *qmlType = typeRef->type) { + if (qmlType->parserStatusCast() != -1) + ++parserStatusCount; + } + if (typeRef->component) { + bindingCount += typeRef->component->totalBindingsCount; + parserStatusCount += typeRef->component->totalParserStatusCount; + } + } + } + + compiledData->totalBindingsCount = bindingCount; + compiledData->totalParserStatusCount = parserStatusCount; + + return errors.isEmpty(); +} + +void QQmlTypeCompiler::recordError(const QQmlError &error) +{ + QQmlError e = error; + e.setUrl(compiledData->url); + errors << e; +} + +QString QQmlTypeCompiler::stringAt(int idx) const +{ + return parsedQML->stringAt(idx); +} + +int QQmlTypeCompiler::registerString(const QString &str) +{ + return parsedQML->jsGenerator.registerString(str); +} + +const QV4::CompiledData::QmlUnit *QQmlTypeCompiler::qmlUnit() const +{ + return compiledData->qmlUnit; +} + +const QQmlImports *QQmlTypeCompiler::imports() const +{ + return &typeData->imports(); +} + +QHash<int, QQmlCompiledData::TypeReference*> *QQmlTypeCompiler::resolvedTypes() +{ + return &compiledData->resolvedTypes; +} + +QList<QmlObject *> *QQmlTypeCompiler::qmlObjects() +{ + return &parsedQML->objects; +} + +int QQmlTypeCompiler::rootObjectIndex() const +{ + return parsedQML->indexOfRootObject; +} + +void QQmlTypeCompiler::setPropertyCaches(const QVector<QQmlPropertyCache *> &caches) +{ + Q_ASSERT(compiledData->propertyCaches.isEmpty()); + compiledData->propertyCaches = caches; + Q_ASSERT(caches.count() >= parsedQML->indexOfRootObject); + compiledData->rootPropertyCache = caches.at(parsedQML->indexOfRootObject); + compiledData->rootPropertyCache->addref(); +} + +const QVector<QQmlPropertyCache *> &QQmlTypeCompiler::propertyCaches() const +{ + return compiledData->propertyCaches; +} + +void QQmlTypeCompiler::setVMEMetaObjects(const QVector<QByteArray> &metaObjects) +{ + Q_ASSERT(compiledData->datas.isEmpty()); + compiledData->datas = metaObjects; +} + +QVector<QByteArray> *QQmlTypeCompiler::vmeMetaObjects() const +{ + return &compiledData->datas; +} + +QHash<int, int> *QQmlTypeCompiler::objectIndexToIdForRoot() +{ + return &compiledData->objectIndexToIdForRoot; +} + +QHash<int, QHash<int, int> > *QQmlTypeCompiler::objectIndexToIdPerComponent() +{ + return &compiledData->objectIndexToIdPerComponent; +} + +QHash<int, QByteArray> *QQmlTypeCompiler::customParserData() +{ + return &compiledData->customParserData; +} + +MemoryPool *QQmlTypeCompiler::memoryPool() +{ + return parsedQML->jsParserEngine.pool(); +} + +const QList<CompiledFunctionOrExpression> &QQmlTypeCompiler::functions() const +{ + return parsedQML->functions; +} + +void QQmlTypeCompiler::setCustomParserBindings(const QVector<int> &bindings) +{ + compiledData->customParserBindings = bindings; +} + +QQmlCompilePass::QQmlCompilePass(QQmlTypeCompiler *typeCompiler) + : compiler(typeCompiler) +{ +} + +void QQmlCompilePass::recordError(const QV4::CompiledData::Location &location, const QString &description) +{ + QQmlError error; + error.setLine(location.line); + error.setColumn(location.column); + error.setDescription(description); + compiler->recordError(error); +} + +static QAtomicInt classIndexCounter(0); + +QQmlPropertyCacheCreator::QQmlPropertyCacheCreator(QQmlTypeCompiler *typeCompiler) + : QQmlCompilePass(typeCompiler) + , enginePrivate(typeCompiler->enginePrivate()) + , qmlObjects(*typeCompiler->qmlObjects()) + , imports(typeCompiler->imports()) + , resolvedTypes(typeCompiler->resolvedTypes()) +{ +} + +QQmlPropertyCacheCreator::~QQmlPropertyCacheCreator() +{ + for (int i = 0; i < propertyCaches.count(); ++i) + if (QQmlPropertyCache *cache = propertyCaches.at(i)) + cache->release(); + propertyCaches.clear(); +} + +bool QQmlPropertyCacheCreator::buildMetaObjects() +{ + propertyCaches.resize(qmlObjects.count()); + vmeMetaObjects.resize(qmlObjects.count()); + + if (!buildMetaObjectRecursively(compiler->rootObjectIndex(), /*referencing object*/-1, /*instantiating binding*/0)) + return false; + + compiler->setVMEMetaObjects(vmeMetaObjects); + compiler->setPropertyCaches(propertyCaches); + propertyCaches.clear(); + + return true; +} + +bool QQmlPropertyCacheCreator::buildMetaObjectRecursively(int objectIndex, int referencingObjectIndex, const QV4::CompiledData::Binding *instantiatingBinding) +{ + const QmlObject *obj = qmlObjects.at(objectIndex); + + QQmlPropertyCache *baseTypeCache = 0; + QQmlPropertyData *instantiatingProperty = 0; + if (instantiatingBinding && instantiatingBinding->type == QV4::CompiledData::Binding::Type_GroupProperty) { + Q_ASSERT(referencingObjectIndex >= 0); + QQmlPropertyCache *parentCache = propertyCaches.at(referencingObjectIndex); + Q_ASSERT(parentCache); + Q_ASSERT(instantiatingBinding->propertyNameIndex != 0); + + bool notInRevision = false; + instantiatingProperty = PropertyResolver(parentCache).property(stringAt(instantiatingBinding->propertyNameIndex), ¬InRevision); + if (instantiatingProperty) { + if (instantiatingProperty->isQObject()) { + baseTypeCache = enginePrivate->rawPropertyCacheForType(instantiatingProperty->propType); + Q_ASSERT(baseTypeCache); + } else if (QQmlValueType *vt = QQmlValueTypeFactory::valueType(instantiatingProperty->propType)) { + baseTypeCache = enginePrivate->cache(vt->metaObject()); + Q_ASSERT(baseTypeCache); + } + } + } + + bool needVMEMetaObject = obj->propertyCount() != 0 || obj->signalCount() != 0 || obj->functionCount() != 0; + if (!needVMEMetaObject) { + for (const QtQml::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { + if (binding->type == QV4::CompiledData::Binding::Type_Object && (binding->flags & QV4::CompiledData::Binding::IsOnAssignment)) { + + // On assignments are implemented using value interceptors, which require a VME meta object. + needVMEMetaObject = true; + + // If the on assignment is inside a group property, we need to distinguish between QObject based + // group properties and value type group properties. For the former the base type is derived from + // the property that references us, for the latter we only need a meta-object on the referencing object + // because interceptors can't go to the shared value type instances. + if (instantiatingProperty && QQmlValueTypeFactory::isValueType(instantiatingProperty->propType)) { + needVMEMetaObject = false; + if (!ensureMetaObject(referencingObjectIndex)) + return false; + } + break; + } + } + } + + if (obj->inheritedTypeNameIndex != 0) { + QQmlCompiledData::TypeReference *typeRef = resolvedTypes->value(obj->inheritedTypeNameIndex); + Q_ASSERT(typeRef); + baseTypeCache = typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); + Q_ASSERT(baseTypeCache); + } + + if (needVMEMetaObject) { + if (!createMetaObject(objectIndex, obj, baseTypeCache)) + return false; + } else if (baseTypeCache) { + propertyCaches[objectIndex] = baseTypeCache; + baseTypeCache->addref(); + } + + if (propertyCaches.at(objectIndex)) { + for (const QtQml::Binding *binding = obj->firstBinding(); binding; binding = binding->next) + if (binding->type == QV4::CompiledData::Binding::Type_Object || binding->type == QV4::CompiledData::Binding::Type_GroupProperty) + if (!buildMetaObjectRecursively(binding->value.objectIndex, objectIndex, binding)) + return false; + } + + return true; +} + +bool QQmlPropertyCacheCreator::ensureMetaObject(int objectIndex) +{ + if (!vmeMetaObjects.at(objectIndex).isEmpty()) + return true; + const QtQml::QmlObject *obj = qmlObjects.at(objectIndex); + QQmlCompiledData::TypeReference *typeRef = resolvedTypes->value(obj->inheritedTypeNameIndex); + Q_ASSERT(typeRef); + QQmlPropertyCache *baseTypeCache = typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); + return createMetaObject(objectIndex, obj, baseTypeCache); +} + +bool QQmlPropertyCacheCreator::createMetaObject(int objectIndex, const QtQml::QmlObject *obj, QQmlPropertyCache *baseTypeCache) +{ + QQmlPropertyCache *cache = baseTypeCache->copyAndReserve(QQmlEnginePrivate::get(enginePrivate), + obj->propertyCount(), + obj->functionCount() + obj->propertyCount() + obj->signalCount(), + obj->signalCount() + obj->propertyCount()); + propertyCaches[objectIndex] = cache; + + struct TypeData { + QV4::CompiledData::Property::Type dtype; + int metaType; + } builtinTypes[] = { + { QV4::CompiledData::Property::Var, qMetaTypeId<QJSValue>() }, + { QV4::CompiledData::Property::Variant, QMetaType::QVariant }, + { QV4::CompiledData::Property::Int, QMetaType::Int }, + { QV4::CompiledData::Property::Bool, QMetaType::Bool }, + { QV4::CompiledData::Property::Real, QMetaType::Double }, + { QV4::CompiledData::Property::String, QMetaType::QString }, + { QV4::CompiledData::Property::Url, QMetaType::QUrl }, + { QV4::CompiledData::Property::Color, QMetaType::QColor }, + { QV4::CompiledData::Property::Font, QMetaType::QFont }, + { QV4::CompiledData::Property::Time, QMetaType::QTime }, + { QV4::CompiledData::Property::Date, QMetaType::QDate }, + { QV4::CompiledData::Property::DateTime, QMetaType::QDateTime }, + { QV4::CompiledData::Property::Rect, QMetaType::QRectF }, + { QV4::CompiledData::Property::Point, QMetaType::QPointF }, + { QV4::CompiledData::Property::Size, QMetaType::QSizeF }, + { QV4::CompiledData::Property::Vector2D, QMetaType::QVector2D }, + { QV4::CompiledData::Property::Vector3D, QMetaType::QVector3D }, + { QV4::CompiledData::Property::Vector4D, QMetaType::QVector4D }, + { QV4::CompiledData::Property::Matrix4x4, QMetaType::QMatrix4x4 }, + { QV4::CompiledData::Property::Quaternion, QMetaType::QQuaternion } + }; + static const uint builtinTypeCount = sizeof(builtinTypes) / sizeof(TypeData); + + QByteArray newClassName; + + if (false /* ### compileState->root == obj && !compileState->nested*/) { +#if 0 // ### + QString path = output->url.path(); + int lastSlash = path.lastIndexOf(QLatin1Char('/')); + if (lastSlash > -1) { + QString nameBase = path.mid(lastSlash + 1, path.length()-lastSlash-5); + if (!nameBase.isEmpty() && nameBase.at(0).isUpper()) + newClassName = nameBase.toUtf8() + "_QMLTYPE_" + + QByteArray::number(classIndexCounter.fetchAndAddRelaxed(1)); + } +#endif + } + if (newClassName.isEmpty()) { + newClassName = QQmlMetaObject(baseTypeCache).className(); + newClassName.append("_QML_"); + newClassName.append(QByteArray::number(classIndexCounter.fetchAndAddRelaxed(1))); + } + + cache->_dynamicClassName = newClassName; + + int aliasCount = 0; + int varPropCount = 0; + + PropertyResolver resolver(baseTypeCache); + + for (const QtQml::QmlProperty *p = obj->firstProperty(); p; p = p->next) { + if (p->type == QV4::CompiledData::Property::Alias) + aliasCount++; + else if (p->type == QV4::CompiledData::Property::Var) + varPropCount++; + + // No point doing this for both the alias and non alias cases + bool notInRevision = false; + QQmlPropertyData *d = resolver.property(stringAt(p->nameIndex), ¬InRevision); + if (d && d->isFinal()) + COMPILE_EXCEPTION(p, tr("Cannot override FINAL property")); + } + + typedef QQmlVMEMetaData VMD; + + QByteArray &dynamicData = vmeMetaObjects[objectIndex] = QByteArray(sizeof(QQmlVMEMetaData) + + obj->propertyCount() * sizeof(VMD::PropertyData) + + obj->functionCount() * sizeof(VMD::MethodData) + + aliasCount * sizeof(VMD::AliasData), 0); + + int effectivePropertyIndex = cache->propertyIndexCacheStart; + int effectiveMethodIndex = cache->methodIndexCacheStart; + + // For property change signal override detection. + // We prepopulate a set of signal names which already exist in the object, + // and throw an error if there is a signal/method defined as an override. + QSet<QString> seenSignals; + seenSignals << QStringLiteral("destroyed") << QStringLiteral("parentChanged") << QStringLiteral("objectNameChanged"); + QQmlPropertyCache *parentCache = cache; + while ((parentCache = parentCache->parent())) { + if (int pSigCount = parentCache->signalCount()) { + int pSigOffset = parentCache->signalOffset(); + for (int i = pSigOffset; i < pSigCount; ++i) { + QQmlPropertyData *currPSig = parentCache->signal(i); + // XXX TODO: find a better way to get signal name from the property data :-/ + for (QQmlPropertyCache::StringCache::ConstIterator iter = parentCache->stringCache.begin(); + iter != parentCache->stringCache.end(); ++iter) { + if (currPSig == (*iter).second) { + seenSignals.insert(iter.key()); + break; + } + } + } + } + } + + // First set up notify signals for properties - first normal, then var, then alias + enum { NSS_Normal = 0, NSS_Var = 1, NSS_Alias = 2 }; + for (int ii = NSS_Normal; ii <= NSS_Alias; ++ii) { // 0 == normal, 1 == var, 2 == alias + + if (ii == NSS_Var && varPropCount == 0) continue; + else if (ii == NSS_Alias && aliasCount == 0) continue; + + for (const QtQml::QmlProperty *p = obj->firstProperty(); p; p = p->next) { + if ((ii == NSS_Normal && (p->type == QV4::CompiledData::Property::Alias || + p->type == QV4::CompiledData::Property::Var)) || + ((ii == NSS_Var) && (p->type != QV4::CompiledData::Property::Var)) || + ((ii == NSS_Alias) && (p->type != QV4::CompiledData::Property::Alias))) + continue; + + quint32 flags = QQmlPropertyData::IsSignal | QQmlPropertyData::IsFunction | + QQmlPropertyData::IsVMESignal; + + QString changedSigName = stringAt(p->nameIndex) + QLatin1String("Changed"); + seenSignals.insert(changedSigName); + + cache->appendSignal(changedSigName, flags, effectiveMethodIndex++); + } + } + + // Dynamic signals + for (const QtQml::Signal *s = obj->firstSignal(); s; s = s->next) { + const int paramCount = s->parameters->count; + + QList<QByteArray> names; + QVarLengthArray<int, 10> paramTypes(paramCount?(paramCount + 1):0); + + if (paramCount) { + paramTypes[0] = paramCount; + + QtQml::SignalParameter *param = s->parameters->first; + for (int i = 0; i < paramCount; ++i, param = param->next) { + names.append(stringAt(param->nameIndex).toUtf8()); + if (param->type < builtinTypeCount) { + // built-in type + paramTypes[i + 1] = builtinTypes[param->type].metaType; + } else { + // lazily resolved type + Q_ASSERT(param->type == QV4::CompiledData::Property::Custom); + const QString customTypeName = stringAt(param->customTypeNameIndex); + QQmlType *qmltype = 0; + if (!imports->resolveType(customTypeName, &qmltype, 0, 0, 0)) + COMPILE_EXCEPTION(s, tr("Invalid signal parameter type: %1").arg(customTypeName)); + + if (qmltype->isComposite()) { + QQmlTypeData *tdata = enginePrivate->typeLoader.getType(qmltype->sourceUrl()); + Q_ASSERT(tdata); + Q_ASSERT(tdata->isComplete()); + + QQmlCompiledData *data = tdata->compiledData(); + + paramTypes[i + 1] = data->metaTypeId; + + tdata->release(); + } else { + paramTypes[i + 1] = qmltype->typeId(); + } + } + } + } + + ((QQmlVMEMetaData *)dynamicData.data())->signalCount++; + + quint32 flags = QQmlPropertyData::IsSignal | QQmlPropertyData::IsFunction | + QQmlPropertyData::IsVMESignal; + if (paramCount) + flags |= QQmlPropertyData::HasArguments; + + QString signalName = stringAt(s->nameIndex); + if (seenSignals.contains(signalName)) + COMPILE_EXCEPTION(s, tr("Duplicate signal name: invalid override of property change signal or superclass signal")); + seenSignals.insert(signalName); + + cache->appendSignal(signalName, flags, effectiveMethodIndex++, + paramCount?paramTypes.constData():0, names); + } + + + // Dynamic slots + for (const QtQml::Function *s = obj->firstFunction(); s; s = s->next) { + AST::FunctionDeclaration *astFunction = s->functionDeclaration; + + quint32 flags = QQmlPropertyData::IsFunction | QQmlPropertyData::IsVMEFunction; + + if (astFunction->formals) + flags |= QQmlPropertyData::HasArguments; + + QString slotName = astFunction->name.toString(); + if (seenSignals.contains(slotName)) + COMPILE_EXCEPTION(s, tr("Duplicate method name: invalid override of property change signal or superclass signal")); + // Note: we don't append slotName to the seenSignals list, since we don't + // protect against overriding change signals or methods with properties. + + QList<QByteArray> parameterNames; + AST::FormalParameterList *param = astFunction->formals; + while (param) { + parameterNames << param->name.toUtf8(); + param = param->next; + } + + cache->appendMethod(slotName, flags, effectiveMethodIndex++, parameterNames); + } + + + // Dynamic properties (except var and aliases) + int effectiveSignalIndex = cache->signalHandlerIndexCacheStart; + int propertyIdx = 0; + for (const QtQml::QmlProperty *p = obj->firstProperty(); p; p = p->next, ++propertyIdx) { + + if (p->type == QV4::CompiledData::Property::Alias || + p->type == QV4::CompiledData::Property::Var) + continue; + + int propertyType = 0; + int vmePropertyType = 0; + quint32 propertyFlags = 0; + + if (p->type < builtinTypeCount) { + propertyType = builtinTypes[p->type].metaType; + vmePropertyType = propertyType; + + if (p->type == QV4::CompiledData::Property::Variant) + propertyFlags |= QQmlPropertyData::IsQVariant; + } else { + Q_ASSERT(p->type == QV4::CompiledData::Property::CustomList || + p->type == QV4::CompiledData::Property::Custom); + + QQmlType *qmltype = 0; + if (!imports->resolveType(stringAt(p->customTypeNameIndex), &qmltype, 0, 0, 0)) { + COMPILE_EXCEPTION(p, tr("Invalid property type")); + } + + Q_ASSERT(qmltype); + if (qmltype->isComposite()) { + QQmlTypeData *tdata = enginePrivate->typeLoader.getType(qmltype->sourceUrl()); + Q_ASSERT(tdata); + Q_ASSERT(tdata->isComplete()); + + QQmlCompiledData *data = tdata->compiledData(); + + if (p->type == QV4::CompiledData::Property::Custom) { + propertyType = data->metaTypeId; + vmePropertyType = QMetaType::QObjectStar; + } else { + propertyType = data->listMetaTypeId; + vmePropertyType = qMetaTypeId<QQmlListProperty<QObject> >(); + } + + tdata->release(); + } else { + if (p->type == QV4::CompiledData::Property::Custom) { + propertyType = qmltype->typeId(); + vmePropertyType = QMetaType::QObjectStar; + } else { + propertyType = qmltype->qListTypeId(); + vmePropertyType = qMetaTypeId<QQmlListProperty<QObject> >(); + } + } + + if (p->type == QV4::CompiledData::Property::Custom) + propertyFlags |= QQmlPropertyData::IsQObjectDerived; + else + propertyFlags |= QQmlPropertyData::IsQList; + } + + if ((!p->flags & QV4::CompiledData::Property::IsReadOnly) && p->type != QV4::CompiledData::Property::CustomList) + propertyFlags |= QQmlPropertyData::IsWritable; + + + QString propertyName = stringAt(p->nameIndex); + if (propertyIdx == obj->indexOfDefaultProperty) cache->_defaultPropertyName = propertyName; + cache->appendProperty(propertyName, propertyFlags, effectivePropertyIndex++, + propertyType, effectiveSignalIndex); + + effectiveSignalIndex++; + + VMD *vmd = (QQmlVMEMetaData *)dynamicData.data(); + (vmd->propertyData() + vmd->propertyCount)->propertyType = vmePropertyType; + vmd->propertyCount++; + } + + // Now do var properties + propertyIdx = 0; + for (const QtQml::QmlProperty *p = obj->firstProperty(); p; p = p->next, ++propertyIdx) { + + if (p->type != QV4::CompiledData::Property::Var) + continue; + + quint32 propertyFlags = QQmlPropertyData::IsVarProperty; + if (!p->flags & QV4::CompiledData::Property::IsReadOnly) + propertyFlags |= QQmlPropertyData::IsWritable; + + VMD *vmd = (QQmlVMEMetaData *)dynamicData.data(); + (vmd->propertyData() + vmd->propertyCount)->propertyType = QMetaType::QVariant; + vmd->propertyCount++; + ((QQmlVMEMetaData *)dynamicData.data())->varPropertyCount++; + + QString propertyName = stringAt(p->nameIndex); + if (propertyIdx == obj->indexOfDefaultProperty) cache->_defaultPropertyName = propertyName; + cache->appendProperty(propertyName, propertyFlags, effectivePropertyIndex++, + QMetaType::QVariant, effectiveSignalIndex); + + effectiveSignalIndex++; + } + + // Alias property count. Actual data is setup in buildDynamicMetaAliases + ((QQmlVMEMetaData *)dynamicData.data())->aliasCount = aliasCount; + + // Dynamic slot data - comes after the property data + for (const QtQml::Function *s = obj->firstFunction(); s; s = s->next) { + AST::FunctionDeclaration *astFunction = s->functionDeclaration; + int formalsCount = 0; + AST::FormalParameterList *param = astFunction->formals; + while (param) { + formalsCount++; + param = param->next; + } + + VMD::MethodData methodData = { /* runtimeFunctionIndex*/ 0, // ### + formalsCount, + /* s->location.start.line */0 }; // ### + + VMD *vmd = (QQmlVMEMetaData *)dynamicData.data(); + VMD::MethodData &md = *(vmd->methodData() + vmd->methodCount); + vmd->methodCount++; + md = methodData; + } + + return true; +} + +QQmlEnumTypeResolver::QQmlEnumTypeResolver(QQmlTypeCompiler *typeCompiler) + : QQmlCompilePass(typeCompiler) + , qmlObjects(*typeCompiler->qmlObjects()) + , propertyCaches(typeCompiler->propertyCaches()) + , imports(typeCompiler->imports()) + , resolvedTypes(typeCompiler->resolvedTypes()) +{ +} + +bool QQmlEnumTypeResolver::resolveEnumBindings() +{ + for (int i = 0; i < qmlObjects.count(); ++i) { + QQmlPropertyCache *propertyCache = propertyCaches.at(i); + if (!propertyCache) + continue; + const QmlObject *obj = qmlObjects.at(i); + + PropertyResolver resolver(propertyCache); + + for (QtQml::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { + if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression + || binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject) + continue; + + if (binding->type != QV4::CompiledData::Binding::Type_Script) + continue; + + const QString propertyName = stringAt(binding->propertyNameIndex); + bool notInRevision = false; + QQmlPropertyData *pd = resolver.property(propertyName, ¬InRevision); + if (!pd) + continue; + + if (!pd->isEnum() && pd->propType != QMetaType::Int) + continue; + + if (!tryQualifiedEnumAssignment(obj, propertyCache, pd, binding)) + return false; + } + } + + return true; +} + +struct StaticQtMetaObject : public QObject +{ + static const QMetaObject *get() + { return &staticQtMetaObject; } +}; + +bool QQmlEnumTypeResolver::tryQualifiedEnumAssignment(const QmlObject *obj, const QQmlPropertyCache *propertyCache, const QQmlPropertyData *prop, Binding *binding) +{ + bool isIntProp = (prop->propType == QMetaType::Int) && !prop->isEnum(); + if (!prop->isEnum() && !isIntProp) + return true; + + if (!prop->isWritable() && !(binding->flags & QV4::CompiledData::Binding::InitializerForReadOnlyDeclaration)) + COMPILE_EXCEPTION(binding, tr("Invalid property assignment: \"%1\" is a read-only property").arg(stringAt(binding->propertyNameIndex))); + + Q_ASSERT(binding->type = QV4::CompiledData::Binding::Type_Script); + QString string = stringAt(binding->stringIndex); + if (!string.at(0).isUpper()) + return true; + + int dot = string.indexOf(QLatin1Char('.')); + if (dot == -1 || dot == string.length()-1) + return true; + + if (string.indexOf(QLatin1Char('.'), dot+1) != -1) + return true; + + QHashedStringRef typeName(string.constData(), dot); + QString enumValue = string.mid(dot+1); + + if (isIntProp) { + // Allow enum assignment to ints. + bool ok; + int enumval = evaluateEnum(typeName.toString(), enumValue.toUtf8(), &ok); + if (ok) { + binding->type = QV4::CompiledData::Binding::Type_Number; + binding->value.d = (double)enumval; + binding->flags |= QV4::CompiledData::Binding::IsResolvedEnum; + } + return true; + } + QQmlType *type = 0; + imports->resolveType(typeName, &type, 0, 0, 0); + + if (!type && typeName != QLatin1String("Qt")) + return true; + if (type && type->isComposite()) //No enums on composite (or composite singleton) types + return true; + + int value = 0; + bool ok = false; + + QQmlCompiledData::TypeReference *tr = resolvedTypes->value(obj->inheritedTypeNameIndex); + if (type && tr && tr->type == type) { + QMetaProperty mprop = propertyCache->firstCppMetaObject()->property(prop->coreIndex); + + // When these two match, we can short cut the search + if (mprop.isFlagType()) { + value = mprop.enumerator().keysToValue(enumValue.toUtf8().constData(), &ok); + } else { + value = mprop.enumerator().keyToValue(enumValue.toUtf8().constData(), &ok); + } + } else { + // Otherwise we have to search the whole type + if (type) { + value = type->enumValue(QHashedStringRef(enumValue), &ok); + } else { + QByteArray enumName = enumValue.toUtf8(); + const QMetaObject *metaObject = StaticQtMetaObject::get(); + for (int ii = metaObject->enumeratorCount() - 1; !ok && ii >= 0; --ii) { + QMetaEnum e = metaObject->enumerator(ii); + value = e.keyToValue(enumName.constData(), &ok); + } + } + } + + if (!ok) + return true; + + binding->type = QV4::CompiledData::Binding::Type_Number; + binding->value.d = (double)value; + binding->flags |= QV4::CompiledData::Binding::IsResolvedEnum; + return true; +} + +int QQmlEnumTypeResolver::evaluateEnum(const QString &scope, const QByteArray &enumValue, bool *ok) const +{ + Q_ASSERT_X(ok, "QQmlEnumTypeResolver::evaluateEnum", "ok must not be a null pointer"); + *ok = false; + + if (scope != QLatin1String("Qt")) { + QQmlType *type = 0; + imports->resolveType(scope, &type, 0, 0, 0); + return type ? type->enumValue(QHashedCStringRef(enumValue.constData(), enumValue.length()), ok) : -1; + } + + const QMetaObject *mo = StaticQtMetaObject::get(); + int i = mo->enumeratorCount(); + while (i--) { + int v = mo->enumerator(i).keyToValue(enumValue.constData(), ok); + if (*ok) + return v; + } + return -1; +} + + +QQmlAliasAnnotator::QQmlAliasAnnotator(QQmlTypeCompiler *typeCompiler) + : QQmlCompilePass(typeCompiler) + , qmlObjects(*typeCompiler->qmlObjects()) + , propertyCaches(typeCompiler->propertyCaches()) +{ +} + +void QQmlAliasAnnotator::annotateBindingsToAliases() +{ + for (int i = 0; i < qmlObjects.count(); ++i) { + QQmlPropertyCache *propertyCache = propertyCaches.at(i); + if (!propertyCache) + continue; + + const QmlObject *obj = qmlObjects.at(i); + + PropertyResolver resolver(propertyCache); + QQmlPropertyData *defaultProperty = obj->indexOfDefaultProperty != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty(); + + for (QtQml::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { + if (!binding->isValueBinding()) + continue; + bool notInRevision = false; + QQmlPropertyData *pd = binding->propertyNameIndex != 0 ? resolver.property(stringAt(binding->propertyNameIndex), ¬InRevision) : defaultProperty; + if (pd && pd->isAlias()) + binding->flags |= QV4::CompiledData::Binding::IsBindingToAlias; + } + } +} + +QQmlComponentAndAliasResolver::QQmlComponentAndAliasResolver(QQmlTypeCompiler *typeCompiler) + : QQmlCompilePass(typeCompiler) + , enginePrivate(typeCompiler->enginePrivate()) + , pool(typeCompiler->memoryPool()) + , qmlObjects(typeCompiler->qmlObjects()) + , indexOfRootObject(typeCompiler->rootObjectIndex()) + , _componentIndex(-1) + , _objectIndexToIdInScope(0) + , resolvedTypes(typeCompiler->resolvedTypes()) + , propertyCaches(typeCompiler->propertyCaches()) + , vmeMetaObjectData(typeCompiler->vmeMetaObjects()) + , objectIndexToIdForRoot(typeCompiler->objectIndexToIdForRoot()) + , objectIndexToIdPerComponent(typeCompiler->objectIndexToIdPerComponent()) +{ +} + +void QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents(const QtQml::QmlObject *obj, int objectIndex) +{ + QQmlPropertyCache *propertyCache = propertyCaches.value(objectIndex); + if (!propertyCache) + return; + + PropertyResolver propertyResolver(propertyCache); + + QQmlPropertyData *defaultProperty = obj->indexOfDefaultProperty != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty(); + + for (QtQml::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { + if (binding->type != QV4::CompiledData::Binding::Type_Object) + continue; + if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject) + continue; + + const QtQml::QmlObject *targetObject = qmlObjects->at(binding->value.objectIndex); + QQmlCompiledData::TypeReference *tr = resolvedTypes->value(targetObject->inheritedTypeNameIndex); + Q_ASSERT(tr); + QQmlType *targetType = tr->type; + if (targetType && targetType->metaObject() == &QQmlComponent::staticMetaObject) + continue; + + QQmlPropertyData *pd = 0; + if (binding->propertyNameIndex != 0) { + bool notInRevision = false; + pd = propertyResolver.property(stringAt(binding->propertyNameIndex), ¬InRevision); + } else { + pd = defaultProperty; + } + if (!pd || !pd->isQObject()) + continue; + + QQmlPropertyCache *pc = enginePrivate->rawPropertyCacheForType(pd->propType); + const QMetaObject *mo = pc->firstCppMetaObject(); + while (mo) { + if (mo == &QQmlComponent::staticMetaObject) + break; + mo = mo->superClass(); + } + + if (!mo) + continue; + + static QQmlType *componentType = QQmlMetaType::qmlType(&QQmlComponent::staticMetaObject); + Q_ASSERT(componentType); + + QtQml::QmlObject *syntheticComponent = pool->New<QtQml::QmlObject>(); + syntheticComponent->init(pool, compiler->registerString(QString::fromUtf8(componentType->typeName())), compiler->registerString(QString())); + + if (!resolvedTypes->contains(syntheticComponent->inheritedTypeNameIndex)) { + QQmlCompiledData::TypeReference *typeRef = new QQmlCompiledData::TypeReference; + typeRef->type = componentType; + typeRef->majorVersion = componentType->majorVersion(); + typeRef->minorVersion = componentType->minorVersion(); + resolvedTypes->insert(syntheticComponent->inheritedTypeNameIndex, typeRef); + } + + qmlObjects->append(syntheticComponent); + const int componentIndex = qmlObjects->count() - 1; + + QtQml::Binding *syntheticBinding = pool->New<QtQml::Binding>(); + *syntheticBinding = *binding; + syntheticBinding->type = QV4::CompiledData::Binding::Type_Object; + QString error = syntheticComponent->appendBinding(syntheticBinding, /*isListBinding*/false); + Q_ASSERT(error.isEmpty()); + Q_UNUSED(error); + + binding->value.objectIndex = componentIndex; + + componentRoots.append(componentIndex); + componentBoundaries.append(syntheticBinding->value.objectIndex); + } +} + +bool QQmlComponentAndAliasResolver::resolve() +{ + // Detect real Component {} objects as well as implicitly defined components, such as + // someItemDelegate: Item {} + // In the implicit case Item is surrounded by a synthetic Component {} because the property + // on the left hand side is of QQmlComponent type. + const int objCountWithoutSynthesizedComponents = qmlObjects->count(); + for (int i = 0; i < objCountWithoutSynthesizedComponents; ++i) { + const QtQml::QmlObject *obj = qmlObjects->at(i); + if (obj->inheritedTypeNameIndex == 0) + continue; + + QQmlCompiledData::TypeReference *tref = resolvedTypes->value(obj->inheritedTypeNameIndex); + Q_ASSERT(tref); + if (!tref->type || tref->type->metaObject() != &QQmlComponent::staticMetaObject) { + findAndRegisterImplicitComponents(obj, i); + continue; + } + + componentRoots.append(i); + + if (obj->functionCount() > 0) + COMPILE_EXCEPTION(obj, tr("Component objects cannot declare new functions.")); + if (obj->propertyCount() > 0) + COMPILE_EXCEPTION(obj, tr("Component objects cannot declare new properties.")); + if (obj->signalCount() > 0) + COMPILE_EXCEPTION(obj, tr("Component objects cannot declare new signals.")); + + if (obj->bindingCount() == 0) + COMPILE_EXCEPTION(obj, tr("Cannot create empty component specification")); + + const QtQml::Binding *rootBinding = obj->firstBinding(); + if (rootBinding->next || rootBinding->type != QV4::CompiledData::Binding::Type_Object) + COMPILE_EXCEPTION(rootBinding, tr("Component elements may not contain properties other than id")); + + componentBoundaries.append(rootBinding->value.objectIndex); + } + + std::sort(componentBoundaries.begin(), componentBoundaries.end()); + + for (int i = 0; i < componentRoots.count(); ++i) { + const QtQml::QmlObject *component = qmlObjects->at(componentRoots.at(i)); + const QtQml::Binding *rootBinding = component->firstBinding(); + + _componentIndex = i; + _idToObjectIndex.clear(); + + _objectIndexToIdInScope = &(*objectIndexToIdPerComponent)[componentRoots.at(i)]; + + _objectsWithAliases.clear(); + + if (!collectIdsAndAliases(rootBinding->value.objectIndex)) + return false; + + if (!resolveAliases()) + return false; + } + + // Collect ids and aliases for root + _componentIndex = -1; + _idToObjectIndex.clear(); + _objectIndexToIdInScope = objectIndexToIdForRoot; + _objectsWithAliases.clear(); + + collectIdsAndAliases(indexOfRootObject); + + resolveAliases(); + + return true; +} + +bool QQmlComponentAndAliasResolver::collectIdsAndAliases(int objectIndex) +{ + const QtQml::QmlObject *obj = qmlObjects->at(objectIndex); + + if (obj->idIndex != 0) { + if (_idToObjectIndex.contains(obj->idIndex)) { + recordError(obj->locationOfIdProperty, tr("id is not unique")); + return false; + } + _idToObjectIndex.insert(obj->idIndex, objectIndex); + _objectIndexToIdInScope->insert(objectIndex, _objectIndexToIdInScope->count()); + } + + for (const QtQml::QmlProperty *property = obj->firstProperty(); property; property = property->next) { + if (property->type == QV4::CompiledData::Property::Alias) { + _objectsWithAliases.append(objectIndex); + break; + } + } + + for (const QtQml::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { + if (binding->type != QV4::CompiledData::Binding::Type_Object + && binding->type != QV4::CompiledData::Binding::Type_AttachedProperty + && binding->type != QV4::CompiledData::Binding::Type_GroupProperty) + continue; + + // Stop at Component boundary + if (std::binary_search(componentBoundaries.constBegin(), componentBoundaries.constEnd(), binding->value.objectIndex)) + continue; + + if (!collectIdsAndAliases(binding->value.objectIndex)) + return false; + } + + return true; +} + +bool QQmlComponentAndAliasResolver::resolveAliases() +{ + foreach (int objectIndex, _objectsWithAliases) { + const QtQml::QmlObject *obj = qmlObjects->at(objectIndex); + + QQmlPropertyCache *propertyCache = propertyCaches.value(objectIndex); + Q_ASSERT(propertyCache); + + int effectiveSignalIndex = propertyCache->signalHandlerIndexCacheStart + propertyCache->propertyIndexCache.count(); + int effectivePropertyIndex = propertyCache->propertyIndexCacheStart + propertyCache->propertyIndexCache.count(); + int effectiveAliasIndex = 0; + + const QtQml::QmlProperty *p = obj->firstProperty(); + for (int propertyIndex = 0; propertyIndex < obj->propertyCount(); ++propertyIndex, p = p->next) { + if (p->type != QV4::CompiledData::Property::Alias) + continue; + + const int idIndex = p->aliasIdValueIndex; + const int targetObjectIndex = _idToObjectIndex.value(idIndex, -1); + if (targetObjectIndex == -1) { + recordError(p->aliasLocation, tr("Invalid alias reference. Unable to find id \"%1\"").arg(stringAt(idIndex))); + return false; + } + const int targetId = _objectIndexToIdInScope->value(targetObjectIndex, -1); + Q_ASSERT(targetId != -1); + + const QString aliasPropertyValue = stringAt(p->aliasPropertyValueIndex); + + QStringRef property; + QStringRef subProperty; + + const int propertySeparator = aliasPropertyValue.indexOf(QLatin1Char('.')); + if (propertySeparator != -1) { + property = aliasPropertyValue.leftRef(propertySeparator); + subProperty = aliasPropertyValue.midRef(propertySeparator + 1); + } else + property = QStringRef(&aliasPropertyValue, 0, aliasPropertyValue.length()); + + int propIdx = -1; + int propType = 0; + int notifySignal = -1; + int flags = 0; + int type = 0; + bool writable = false; + bool resettable = false; + + quint32 propertyFlags = QQmlPropertyData::IsAlias; + + if (property.isEmpty()) { + const QtQml::QmlObject *targetObject = qmlObjects->at(targetObjectIndex); + QQmlCompiledData::TypeReference *typeRef = resolvedTypes->value(targetObject->inheritedTypeNameIndex); + Q_ASSERT(typeRef); + + if (typeRef->type) + type = typeRef->type->typeId(); + else + type = typeRef->component->metaTypeId; + + flags |= QML_ALIAS_FLAG_PTR; + propertyFlags |= QQmlPropertyData::IsQObjectDerived; + } else { + QQmlPropertyCache *targetCache = propertyCaches.value(targetObjectIndex); + Q_ASSERT(targetCache); + QtQml::PropertyResolver resolver(targetCache); + + QQmlPropertyData *targetProperty = resolver.property(property.toString()); + if (!targetProperty || targetProperty->coreIndex > 0x0000FFFF) { + recordError(p->aliasLocation, tr("Invalid alias location")); + return false; + } + + propIdx = targetProperty->coreIndex; + type = targetProperty->propType; + + writable = targetProperty->isWritable(); + resettable = targetProperty->isResettable(); + notifySignal = targetProperty->notifyIndex; + + if (!subProperty.isEmpty()) { + QQmlValueType *valueType = QQmlValueTypeFactory::valueType(type); + if (!valueType) { + recordError(p->aliasLocation, tr("Invalid alias location")); + return false; + } + + propType = type; + + int valueTypeIndex = + valueType->metaObject()->indexOfProperty(subProperty.toString().toUtf8().constData()); + if (valueTypeIndex == -1) { + recordError(p->aliasLocation, tr("Invalid alias location")); + return false; + } + Q_ASSERT(valueTypeIndex <= 0x0000FFFF); + + propIdx |= (valueTypeIndex << 16); + if (valueType->metaObject()->property(valueTypeIndex).isEnumType()) + type = QVariant::Int; + else + type = valueType->metaObject()->property(valueTypeIndex).userType(); + + } else { + if (targetProperty->isEnum()) { + type = QVariant::Int; + } else { + // Copy type flags + propertyFlags |= targetProperty->getFlags() & QQmlPropertyData::PropTypeFlagMask; + + if (targetProperty->isVarProperty()) + propertyFlags |= QQmlPropertyData::IsQVariant; + + if (targetProperty->isQObject()) + flags |= QML_ALIAS_FLAG_PTR; + } + } + } + + QQmlVMEMetaData::AliasData aliasData = { targetId, propIdx, propType, flags, notifySignal }; + + typedef QQmlVMEMetaData VMD; + QByteArray &dynamicData = (*vmeMetaObjectData)[objectIndex]; + Q_ASSERT(!dynamicData.isEmpty()); + VMD *vmd = (QQmlVMEMetaData *)dynamicData.data(); + *(vmd->aliasData() + effectiveAliasIndex++) = aliasData; + + Q_ASSERT(dynamicData.isDetached()); + + if (!(p->flags & QV4::CompiledData::Property::IsReadOnly) && writable) + propertyFlags |= QQmlPropertyData::IsWritable; + else + propertyFlags &= ~QQmlPropertyData::IsWritable; + + if (resettable) + propertyFlags |= QQmlPropertyData::IsResettable; + else + propertyFlags &= ~QQmlPropertyData::IsResettable; + + QString propertyName = stringAt(p->nameIndex); + if (propertyIndex == obj->indexOfDefaultProperty) propertyCache->_defaultPropertyName = propertyName; + propertyCache->appendProperty(propertyName, propertyFlags, effectivePropertyIndex++, + type, effectiveSignalIndex++); + + } + } + return true; +} + + +QQmlPropertyValidator::QQmlPropertyValidator(QQmlTypeCompiler *typeCompiler, const QVector<int> &runtimeFunctionIndices) + : QQmlCompilePass(typeCompiler) + , enginePrivate(typeCompiler->enginePrivate()) + , qmlUnit(typeCompiler->qmlUnit()) + , resolvedTypes(*typeCompiler->resolvedTypes()) + , propertyCaches(typeCompiler->propertyCaches()) + , objectIndexToIdPerComponent(*typeCompiler->objectIndexToIdPerComponent()) + , customParserData(typeCompiler->customParserData()) + , runtimeFunctionIndices(runtimeFunctionIndices) +{ +} + +bool QQmlPropertyValidator::validate() +{ + if (!validateObject(qmlUnit->indexOfRootObject, /*instantiatingBinding*/0)) + return false; + compiler->setCustomParserBindings(customParserBindings); + return true; +} + +const QQmlImports &QQmlPropertyValidator::imports() const +{ + return *compiler->imports(); +} + +AST::Node *QQmlPropertyValidator::astForBinding(int scriptIndex) const +{ + // #### + int reverseIndex = runtimeFunctionIndices.indexOf(scriptIndex); + if (reverseIndex == -1) + return 0; + return compiler->functions().value(reverseIndex).node; +} + +QQmlBinding::Identifier QQmlPropertyValidator::bindingIdentifier(const QV4::CompiledData::Binding *binding, QQmlCustomParser *) +{ + int id = customParserBindings.count(); + customParserBindings.append(binding->value.compiledScriptIndex); + return id; +} + +bool QQmlPropertyValidator::validateObject(int objectIndex, const QV4::CompiledData::Binding *instantiatingBinding) +{ + const QV4::CompiledData::Object *obj = qmlUnit->objectAt(objectIndex); + + if (isComponent(objectIndex)) { + Q_ASSERT(obj->nBindings == 1); + const QV4::CompiledData::Binding *componentBinding = obj->bindingTable(); + Q_ASSERT(componentBinding->type == QV4::CompiledData::Binding::Type_Object); + return validateObject(componentBinding->value.objectIndex, componentBinding); + } + + QQmlPropertyCache *propertyCache = propertyCaches.at(objectIndex); + if (!propertyCache) + return true; + + QQmlCustomParser *customParser = 0; + QQmlCompiledData::TypeReference *objectType = resolvedTypes.value(obj->inheritedTypeNameIndex); + if (objectType && objectType->type) + customParser = objectType->type->customParser(); + QList<const QV4::CompiledData::Binding*> customBindings; + + PropertyResolver propertyResolver(propertyCache); + + QString defaultPropertyName; + QQmlPropertyData *defaultProperty = 0; + if (obj->indexOfDefaultProperty != -1) { + QQmlPropertyCache *cache = propertyCache->parent(); + defaultPropertyName = cache->defaultPropertyName(); + defaultProperty = cache->defaultProperty(); + } else { + defaultPropertyName = propertyCache->defaultPropertyName(); + defaultProperty = propertyCache->defaultProperty(); + } + + const QV4::CompiledData::Binding *binding = obj->bindingTable(); + for (quint32 i = 0; i < obj->nBindings; ++i, ++binding) { + + if (customParser) { + if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) { + if (customParser->flags() & QQmlCustomParser::AcceptsAttachedProperties) { + customBindings << binding; + continue; + } + } else if ((binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression) + && !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) { + customBindings << binding; + continue; + } else if (binding->type == QV4::CompiledData::Binding::Type_Object + || binding->type == QV4::CompiledData::Binding::Type_GroupProperty) { + customBindings << binding; + continue; + } + } + + if (binding->type >= QV4::CompiledData::Binding::Type_Object) { + if (!validateObject(binding->value.objectIndex, binding)) + return false; + // Nothing further to check for attached properties. + if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) + continue; + } + + // Signal handlers were resolved and checked earlier in the signal handler conversion pass. + if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression + || binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject) + continue; + + QString name = stringAt(binding->propertyNameIndex); + + bool bindingToDefaultProperty = false; + + bool notInRevision = false; + QQmlPropertyData *pd = 0; + if (!name.isEmpty()) { + if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression + || binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject) + pd = propertyResolver.signal(name, ¬InRevision); + else + pd = propertyResolver.property(name, ¬InRevision); + + if (notInRevision) { + QString typeName = stringAt(obj->inheritedTypeNameIndex); + if (objectType && objectType->type) { + COMPILE_EXCEPTION(binding, tr("\"%1.%2\" is not available in %3 %4.%5.").arg(typeName).arg(name).arg(objectType->type->module()).arg(objectType->majorVersion).arg(objectType->minorVersion)); + } else { + COMPILE_EXCEPTION(binding, tr("\"%1.%2\" is not available due to component versioning.").arg(typeName).arg(name)); + } + } + } else { + if (instantiatingBinding && instantiatingBinding->type == QV4::CompiledData::Binding::Type_GroupProperty) + COMPILE_EXCEPTION(binding, tr("Cannot assign a value directly to a grouped property")); + + pd = defaultProperty; + name = defaultPropertyName; + bindingToDefaultProperty = true; + } + + if (pd) { + if (!pd->isWritable() + && !pd->isQList() + && binding->type != QV4::CompiledData::Binding::Type_GroupProperty + && !(binding->flags & QV4::CompiledData::Binding::InitializerForReadOnlyDeclaration) + ) { + recordError(binding->valueLocation, tr("Invalid property assignment: \"%1\" is a read-only property").arg(name)); + return false; + } + + if (!pd->isQList() && (binding->flags & QV4::CompiledData::Binding::IsListItem)) { + QString error; + if (pd->propType == qMetaTypeId<QQmlScriptString>()) + error = tr( "Cannot assign multiple values to a script property"); + else + error = tr( "Cannot assign multiple values to a singular property"); + recordError(binding->valueLocation, error); + return false; + } + + if (binding->type < QV4::CompiledData::Binding::Type_Script) { + if (!validateLiteralBinding(propertyCache, pd, binding)) + return false; + } else if (binding->type == QV4::CompiledData::Binding::Type_Object) { + if (!validateObjectBinding(pd, name, binding)) + return false; + } + } else { + if (customParser) { + customBindings << binding; + continue; + } + if (bindingToDefaultProperty) { + COMPILE_EXCEPTION(binding, tr("Cannot assign to non-existent default property")); + } else { + COMPILE_EXCEPTION(binding, tr("Cannot assign to non-existent property \"%1\"").arg(name)); + } + } + } + + if (customParser && !customBindings.isEmpty()) { + customParser->clearErrors(); + customParser->compiler = this; + QByteArray data = customParser->compile(qmlUnit, customBindings); + customParser->compiler = 0; + customParserData->insert(objectIndex, data); + const QList<QQmlError> parserErrors = customParser->errors(); + if (!parserErrors.isEmpty()) { + foreach (QQmlError error, parserErrors) + compiler->recordError(error); + return false; + } + } + + return true; +} + +bool QQmlPropertyValidator::validateLiteralBinding(QQmlPropertyCache *propertyCache, QQmlPropertyData *property, const QV4::CompiledData::Binding *binding) +{ + if (property->isEnum()) { + if (binding->flags & QV4::CompiledData::Binding::IsResolvedEnum) + return true; + + QString value = binding->valueAsString(&qmlUnit->header); + QMetaProperty p = propertyCache->firstCppMetaObject()->property(property->coreIndex); + bool ok; + if (p.isFlagType()) { + p.enumerator().keysToValue(value.toUtf8().constData(), &ok); + } else + p.enumerator().keyToValue(value.toUtf8().constData(), &ok); + + if (!ok) { + recordError(binding->valueLocation, tr("Invalid property assignment: unknown enumeration")); + return false; + } + return true; + } + + switch (property->propType) { + case QMetaType::QVariant: + break; + case QVariant::String: { + if (binding->type != QV4::CompiledData::Binding::Type_String) { + recordError(binding->valueLocation, tr("Invalid property assignment: string expected")); + return false; + } + } + break; + case QVariant::StringList: { + if (binding->type != QV4::CompiledData::Binding::Type_String) { + recordError(binding->valueLocation, tr("Invalid property assignment: string or string list expected")); + return false; + } + } + break; + case QVariant::ByteArray: { + if (binding->type != QV4::CompiledData::Binding::Type_String) { + recordError(binding->valueLocation, tr("Invalid property assignment: byte array expected")); + return false; + } + } + break; + case QVariant::Url: { + if (binding->type != QV4::CompiledData::Binding::Type_String) { + recordError(binding->valueLocation, tr("Invalid property assignment: url expected")); + return false; + } + } + break; + case QVariant::UInt: { + if (binding->type == QV4::CompiledData::Binding::Type_Number) { + double d = binding->valueAsNumber(); + if (double(uint(d)) == d) + return true; + } + recordError(binding->valueLocation, tr("Invalid property assignment: unsigned int expected")); + return false; + } + break; + case QVariant::Int: { + if (binding->type == QV4::CompiledData::Binding::Type_Number) { + double d = binding->valueAsNumber(); + if (double(int(d)) == d) + return true; + } + recordError(binding->valueLocation, tr("Invalid property assignment: int expected")); + return false; + } + break; + case QMetaType::Float: { + if (binding->type != QV4::CompiledData::Binding::Type_Number) { + recordError(binding->valueLocation, tr("Invalid property assignment: number expected")); + return false; + } + } + break; + case QVariant::Double: { + if (binding->type != QV4::CompiledData::Binding::Type_Number) { + recordError(binding->valueLocation, tr("Invalid property assignment: number expected")); + return false; + } + } + break; + case QVariant::Color: { + bool ok = false; + QQmlStringConverters::rgbaFromString(binding->valueAsString(&qmlUnit->header), &ok); + if (!ok) { + recordError(binding->valueLocation, tr("Invalid property assignment: color expected")); + return false; + } + } + break; +#ifndef QT_NO_DATESTRING + case QVariant::Date: { + bool ok = false; + QQmlStringConverters::dateFromString(binding->valueAsString(&qmlUnit->header), &ok); + if (!ok) { + recordError(binding->valueLocation, tr("Invalid property assignment: date expected")); + return false; + } + } + break; + case QVariant::Time: { + bool ok = false; + QQmlStringConverters::timeFromString(binding->valueAsString(&qmlUnit->header), &ok); + if (!ok) { + recordError(binding->valueLocation, tr("Invalid property assignment: time expected")); + return false; + } + } + break; + case QVariant::DateTime: { + bool ok = false; + QQmlStringConverters::dateTimeFromString(binding->valueAsString(&qmlUnit->header), &ok); + if (!ok) { + recordError(binding->valueLocation, tr("Invalid property assignment: datetime expected")); + return false; + } + } + break; +#endif // QT_NO_DATESTRING + case QVariant::Point: { + bool ok = false; + QQmlStringConverters::pointFFromString(binding->valueAsString(&qmlUnit->header), &ok).toPoint(); + if (!ok) { + recordError(binding->valueLocation, tr("Invalid property assignment: point expected")); + return false; + } + } + break; + case QVariant::PointF: { + bool ok = false; + QQmlStringConverters::pointFFromString(binding->valueAsString(&qmlUnit->header), &ok); + if (!ok) { + recordError(binding->valueLocation, tr("Invalid property assignment: point expected")); + return false; + } + } + break; + case QVariant::Size: { + bool ok = false; + QQmlStringConverters::sizeFFromString(binding->valueAsString(&qmlUnit->header), &ok).toSize(); + if (!ok) { + recordError(binding->valueLocation, tr("Invalid property assignment: size expected")); + return false; + } + } + break; + case QVariant::SizeF: { + bool ok = false; + QQmlStringConverters::sizeFFromString(binding->valueAsString(&qmlUnit->header), &ok); + if (!ok) { + recordError(binding->valueLocation, tr("Invalid property assignment: size expected")); + return false; + } + } + break; + case QVariant::Rect: { + bool ok = false; + QQmlStringConverters::rectFFromString(binding->valueAsString(&qmlUnit->header), &ok).toRect(); + if (!ok) { + recordError(binding->valueLocation, tr("Invalid property assignment: rect expected")); + return false; + } + } + break; + case QVariant::RectF: { + bool ok = false; + QQmlStringConverters::rectFFromString(binding->valueAsString(&qmlUnit->header), &ok); + if (!ok) { + recordError(binding->valueLocation, tr("Invalid property assignment: point expected")); + return false; + } + } + break; + case QVariant::Bool: { + if (binding->type != QV4::CompiledData::Binding::Type_Boolean) { + recordError(binding->valueLocation, tr("Invalid property assignment: boolean expected")); + return false; + } + } + break; + case QVariant::Vector3D: { + struct { + float xp; + float yp; + float zy; + } vec; + if (!QQmlStringConverters::createFromString(QMetaType::QVector3D, binding->valueAsString(&qmlUnit->header), &vec, sizeof(vec))) { + recordError(binding->valueLocation, tr("Invalid property assignment: 3D vector expected")); + return false; + } + } + break; + case QVariant::Vector4D: { + struct { + float xp; + float yp; + float zy; + float wp; + } vec; + if (!QQmlStringConverters::createFromString(QMetaType::QVector4D, binding->valueAsString(&qmlUnit->header), &vec, sizeof(vec))) { + recordError(binding->valueLocation, tr("Invalid property assignment: 4D vector expected")); + return false; + } + } + break; + case QVariant::RegExp: + recordError(binding->valueLocation, tr("Invalid property assignment: regular expression expected; use /pattern/ syntax")); + return false; + default: { + // generate single literal value assignment to a list property if required + if (property->propType == qMetaTypeId<QList<qreal> >()) { + if (binding->type != QV4::CompiledData::Binding::Type_Number) { + recordError(binding->valueLocation, tr("Invalid property assignment: real or array of reals expected")); + return false; + } + break; + } else if (property->propType == qMetaTypeId<QList<int> >()) { + bool ok = (binding->type == QV4::CompiledData::Binding::Type_Number); + if (ok) { + double n = binding->valueAsNumber(); + if (double(int(n)) != n) + ok = false; + } + if (!ok) + recordError(binding->valueLocation, tr("Invalid property assignment: int or array of ints expected")); + break; + } else if (property->propType == qMetaTypeId<QList<bool> >()) { + if (binding->type != QV4::CompiledData::Binding::Type_Boolean) { + recordError(binding->valueLocation, tr("Invalid property assignment: bool or array of bools expected")); + return false; + } + break; + } else if (property->propType == qMetaTypeId<QList<QUrl> >()) { + if (binding->type != QV4::CompiledData::Binding::Type_String) { + recordError(binding->valueLocation, tr("Invalid property assignment: url or array of urls expected")); + return false; + } + break; + } else if (property->propType == qMetaTypeId<QList<QString> >()) { + if (binding->type != QV4::CompiledData::Binding::Type_String) { + recordError(binding->valueLocation, tr("Invalid property assignment: string or array of strings expected")); + return false; + } + break; + } else if (property->propType == qMetaTypeId<QJSValue>()) { + break; + } else if (property->propType == qMetaTypeId<QQmlScriptString>()) { + break; + } + + // otherwise, try a custom type assignment + QQmlMetaType::StringConverter converter = QQmlMetaType::customStringConverter(property->propType); + if (!converter) { + recordError(binding->location, tr("Invalid property assignment: unsupported type \"%1\"").arg(QString::fromLatin1(QMetaType::typeName(property->propType)))); + return false; + } + } + break; + } + return true; +} + +bool QQmlPropertyValidator::validateObjectBinding(QQmlPropertyData *property, const QString &propertyName, const QV4::CompiledData::Binding *binding) +{ + if (binding->flags & QV4::CompiledData::Binding::IsOnAssignment) { + Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_Object); + + bool isValueSource = false; + bool isPropertyInterceptor = false; + + QQmlType *qmlType = 0; + const QV4::CompiledData::Object *targetObject = qmlUnit->objectAt(binding->value.objectIndex); + QQmlCompiledData::TypeReference *typeRef = resolvedTypes.value(targetObject->inheritedTypeNameIndex); + if (typeRef) { + QQmlPropertyCache *cache = typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); + const QMetaObject *mo = cache->firstCppMetaObject(); + while (mo && !qmlType) { + qmlType = QQmlMetaType::qmlType(mo); + mo = mo->superClass(); + } + Q_ASSERT(qmlType); + } + + if (qmlType) { + isValueSource = qmlType->propertyValueSourceCast() != -1; + isPropertyInterceptor = qmlType->propertyValueInterceptorCast() != -1; + } + + if (!isValueSource && !isPropertyInterceptor) { + recordError(binding->valueLocation, tr("\"%1\" cannot operate on \"%2\"").arg(stringAt(targetObject->inheritedTypeNameIndex)).arg(propertyName)); + return false; + } + + return true; + } + if (isComponent(binding->value.objectIndex)) + return true; + + if (QQmlMetaType::isInterface(property->propType)) { + // Can only check at instantiation time if the created sub-object successfully casts to the + // target interface. + return true; + } else if (property->propType == QMetaType::QVariant) { + // We can convert everything to QVariant :) + return true; + } else if (property->isQList()) { + // ### TODO: list error handling + return true; + } else if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject && property->isFunction()) { + return true; + } else if (QQmlValueTypeFactory::isValueType(property->propType)) { + recordError(binding->location, tr("Unexpected object assignment")); + return false; + } else { + // We want to raw metaObject here as the raw metaobject is the + // actual property type before we applied any extensions that might + // effect the properties on the type, but don't effect assignability + QQmlPropertyCache *propertyMetaObject = enginePrivate->rawPropertyCacheForType(property->propType); + + // Will be true if the assgned type inherits propertyMetaObject + bool isAssignable = false; + // Determine isAssignable value + if (propertyMetaObject) { + QQmlPropertyCache *c = propertyCaches.value(binding->value.objectIndex); + while (c && !isAssignable) { + isAssignable |= c == propertyMetaObject; + c = c->parent(); + } + } + + if (!isAssignable) { + recordError(binding->valueLocation, tr("Cannot assign object to property")); + return false; + } + } + return true; +} + +QT_END_NAMESPACE diff --git a/src/qml/compiler/qqmltypecompiler_p.h b/src/qml/compiler/qqmltypecompiler_p.h new file mode 100644 index 0000000000..2e97d13481 --- /dev/null +++ b/src/qml/compiler/qqmltypecompiler_p.h @@ -0,0 +1,247 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QQMLTYPECOMPILER_P_H +#define QQMLTYPECOMPILER_P_H + +#include <qglobal.h> +#include <qqmlerror.h> +#include <qhash.h> +#include <private/qqmlcompiler_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlEnginePrivate; +class QQmlCompiledData; +class QQmlError; +class QQmlTypeData; +class QQmlImports; + +namespace QtQml { +struct ParsedQML; +} + +namespace QV4 { +namespace CompiledData { +struct QmlUnit; +struct Location; +} +} + +struct QQmlTypeCompiler +{ + Q_DECLARE_TR_FUNCTIONS(QQmlTypeCompiler) +public: + QQmlTypeCompiler(QQmlEnginePrivate *engine, QQmlCompiledData *compiledData, QQmlTypeData *typeData, QtQml::ParsedQML *parsedQML); + + bool compile(); + + QList<QQmlError> compilationErrors() const { return errors; } + void recordError(const QQmlError &error); + + QString stringAt(int idx) const; + int registerString(const QString &str); + + const QV4::CompiledData::QmlUnit *qmlUnit() const; + + QQmlEnginePrivate *enginePrivate() const { return engine; } + const QQmlImports *imports() const; + QHash<int, QQmlCompiledData::TypeReference *> *resolvedTypes(); + QList<QtQml::QmlObject*> *qmlObjects(); + int rootObjectIndex() const; + void setPropertyCaches(const QVector<QQmlPropertyCache *> &caches); + const QVector<QQmlPropertyCache *> &propertyCaches() const; + void setVMEMetaObjects(const QVector<QByteArray> &metaObjects); + QVector<QByteArray> *vmeMetaObjects() const; + QHash<int, int> *objectIndexToIdForRoot(); + QHash<int, QHash<int, int> > *objectIndexToIdPerComponent(); + QHash<int, QByteArray> *customParserData(); + QQmlJS::MemoryPool *memoryPool(); + const QList<CompiledFunctionOrExpression> &functions() const; + void setCustomParserBindings(const QVector<int> &bindings); + +private: + QList<QQmlError> errors; + QQmlEnginePrivate *engine; + QQmlCompiledData *compiledData; + QQmlTypeData *typeData; + QtQml::ParsedQML *parsedQML; +}; + +struct QQmlCompilePass +{ + virtual ~QQmlCompilePass() {} + + QQmlCompilePass(QQmlTypeCompiler *typeCompiler); + + QString stringAt(int idx) const { return compiler->stringAt(idx); } +protected: + void recordError(const QV4::CompiledData::Location &location, const QString &description); + + QQmlTypeCompiler *compiler; +}; + +class QQmlPropertyCacheCreator : public QQmlCompilePass +{ + Q_DECLARE_TR_FUNCTIONS(QQmlPropertyCacheCreator) +public: + QQmlPropertyCacheCreator(QQmlTypeCompiler *typeCompiler); + ~QQmlPropertyCacheCreator(); + + bool buildMetaObjects(); +protected: + bool buildMetaObjectRecursively(int objectIndex, int referencingObjectIndex, const QV4::CompiledData::Binding *instantiatingBinding); + bool ensureMetaObject(int objectIndex); + bool createMetaObject(int objectIndex, const QtQml::QmlObject *obj, QQmlPropertyCache *baseTypeCache); + + QQmlEnginePrivate *enginePrivate; + const QList<QtQml::QmlObject*> &qmlObjects; + const QQmlImports *imports; + QHash<int, QQmlCompiledData::TypeReference*> *resolvedTypes; + QVector<QByteArray> vmeMetaObjects; + QVector<QQmlPropertyCache*> propertyCaches; +}; + +// ### This will go away when the codegen resolves all enums to constant expressions +// and we replace the constant expression with a literal binding instead of using +// a script. +class QQmlEnumTypeResolver : public QQmlCompilePass +{ + Q_DECLARE_TR_FUNCTIONS(QQmlEnumTypeResolver) +public: + QQmlEnumTypeResolver(QQmlTypeCompiler *typeCompiler); + + bool resolveEnumBindings(); + +private: + bool tryQualifiedEnumAssignment(const QmlObject *obj, const QQmlPropertyCache *propertyCache, + const QQmlPropertyData *prop, + QtQml::Binding *binding); + int evaluateEnum(const QString &scope, const QByteArray &enumValue, bool *ok) const; + + + const QList<QtQml::QmlObject*> &qmlObjects; + const QVector<QQmlPropertyCache *> propertyCaches; + const QQmlImports *imports; + QHash<int, QQmlCompiledData::TypeReference *> *resolvedTypes; +}; + +// Annotate properties bound to aliases with a flag +class QQmlAliasAnnotator : public QQmlCompilePass +{ +public: + QQmlAliasAnnotator(QQmlTypeCompiler *typeCompiler); + + void annotateBindingsToAliases(); +private: + const QList<QtQml::QmlObject*> &qmlObjects; + const QVector<QQmlPropertyCache *> propertyCaches; +}; + +class QQmlComponentAndAliasResolver : public QQmlCompilePass +{ + Q_DECLARE_TR_FUNCTIONS(QQmlAnonymousComponentResolver) +public: + QQmlComponentAndAliasResolver(QQmlTypeCompiler *typeCompiler); + + bool resolve(); + +protected: + void findAndRegisterImplicitComponents(const QtQml::QmlObject *obj, int objectIndex); + bool collectIdsAndAliases(int objectIndex); + bool resolveAliases(); + + QQmlEnginePrivate *enginePrivate; + QQmlJS::MemoryPool *pool; + + QList<QtQml::QmlObject*> *qmlObjects; + const int indexOfRootObject; + + // indices of the objects that are actually Component {} + QVector<int> componentRoots; + // indices of objects that are the beginning of a new component + // scope. This is sorted and used for binary search. + QVector<int> componentBoundaries; + + int _componentIndex; + QHash<int, int> _idToObjectIndex; + QHash<int, int> *_objectIndexToIdInScope; + QList<int> _objectsWithAliases; + + QHash<int, QQmlCompiledData::TypeReference*> *resolvedTypes; + const QVector<QQmlPropertyCache *> propertyCaches; + QVector<QByteArray> *vmeMetaObjectData; + QHash<int, int> *objectIndexToIdForRoot; + QHash<int, QHash<int, int> > *objectIndexToIdPerComponent; +}; + +class QQmlPropertyValidator : public QQmlCompilePass, public QQmlCustomParserCompilerBackend +{ + Q_DECLARE_TR_FUNCTIONS(QQmlPropertyValidator) +public: + QQmlPropertyValidator(QQmlTypeCompiler *typeCompiler, const QVector<int> &runtimeFunctionIndices); + + bool validate(); + + // Re-implemented for QQmlCustomParser + virtual const QQmlImports &imports() const; + virtual QQmlJS::AST::Node *astForBinding(int scriptIndex) const; + virtual QQmlBinding::Identifier bindingIdentifier(const QV4::CompiledData::Binding *binding, QQmlCustomParser *parser); + +private: + bool validateObject(int objectIndex, const QV4::CompiledData::Binding *instantiatingBinding); + bool validateLiteralBinding(QQmlPropertyCache *propertyCache, QQmlPropertyData *property, const QV4::CompiledData::Binding *binding); + bool validateObjectBinding(QQmlPropertyData *property, const QString &propertyName, const QV4::CompiledData::Binding *binding); + + bool isComponent(int objectIndex) const { return objectIndexToIdPerComponent.contains(objectIndex); } + + QQmlEnginePrivate *enginePrivate; + const QV4::CompiledData::QmlUnit *qmlUnit; + const QHash<int, QQmlCompiledData::TypeReference*> &resolvedTypes; + const QVector<QQmlPropertyCache *> &propertyCaches; + const QHash<int, QHash<int, int> > objectIndexToIdPerComponent; + QHash<int, QByteArray> *customParserData; + QVector<int> customParserBindings; + const QVector<int> &runtimeFunctionIndices; +}; + +QT_END_NAMESPACE + +#endif // QQMLTYPECOMPILER_P_H diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp index ce0c7abf9e..aa8db06fd5 100644 --- a/src/qml/compiler/qv4compileddata.cpp +++ b/src/qml/compiler/qv4compileddata.cpp @@ -67,15 +67,15 @@ QV4::Function *CompilationUnit::linkToEngine(ExecutionEngine *engine) assert(!runtimeStrings); assert(data); - runtimeStrings = (QV4::SafeString *)malloc(data->stringTableSize * sizeof(QV4::SafeString)); + runtimeStrings = (QV4::StringValue *)malloc(data->stringTableSize * sizeof(QV4::StringValue)); // memset the strings to 0 in case a GC run happens while we're within the loop below - memset(runtimeStrings, 0, data->stringTableSize * sizeof(QV4::SafeString)); + memset(runtimeStrings, 0, data->stringTableSize * sizeof(QV4::StringValue)); for (uint i = 0; i < data->stringTableSize; ++i) runtimeStrings[i] = engine->newIdentifier(data->stringAt(i)); - runtimeRegularExpressions = new QV4::SafeValue[data->regexpTableSize]; + runtimeRegularExpressions = new QV4::Value[data->regexpTableSize]; // memset the regexps to 0 in case a GC run happens while we're within the loop below - memset(runtimeRegularExpressions, 0, data->regexpTableSize * sizeof(QV4::SafeValue)); + memset(runtimeRegularExpressions, 0, data->regexpTableSize * sizeof(QV4::Value)); for (uint i = 0; i < data->regexpTableSize; ++i) { const CompiledData::RegExp *re = data->regexpAt(i); int flags = 0; @@ -95,18 +95,25 @@ QV4::Function *CompilationUnit::linkToEngine(ExecutionEngine *engine) for (uint i = 0; i < data->lookupTableSize; ++i) { QV4::Lookup *l = runtimeLookups + i; - if (compiledLookups[i].type_and_flags == CompiledData::Lookup::Type_Getter) + Lookup::Type type = Lookup::Type(compiledLookups[i].type_and_flags); + if (type == CompiledData::Lookup::Type_Getter) l->getter = QV4::Lookup::getterGeneric; - else if (compiledLookups[i].type_and_flags == CompiledData::Lookup::Type_Setter) + else if (type == CompiledData::Lookup::Type_Setter) l->setter = QV4::Lookup::setterGeneric; - else if (compiledLookups[i].type_and_flags == CompiledData::Lookup::Type_GlobalGetter) + else if (type == CompiledData::Lookup::Type_GlobalGetter) l->globalGetter = QV4::Lookup::globalGetterGeneric; + else if (type == CompiledData::Lookup::Type_IndexedGetter) + l->indexedGetter = QV4::Lookup::indexedGetterGeneric; + else if (type == CompiledData::Lookup::Type_IndexedSetter) + l->indexedSetter = QV4::Lookup::indexedSetterGeneric; for (int j = 0; j < QV4::Lookup::Size; ++j) l->classList[j] = 0; l->level = -1; l->index = UINT_MAX; l->name = runtimeStrings[compiledLookups[i].nameIndex].asString(); + if (type == CompiledData::Lookup::Type_IndexedGetter || type == CompiledData::Lookup::Type_IndexedSetter) + l->engine = engine; } } @@ -193,6 +200,57 @@ QString Binding::valueAsString(const Unit *unit) const return QString(); } +//reverse of Lexer::singleEscape() +static QString escapedString(const QString &string) +{ + QString tmp = QLatin1String("\""); + for (int i = 0; i < string.length(); ++i) { + const QChar &c = string.at(i); + switch (c.unicode()) { + case 0x08: + tmp += QLatin1String("\\b"); + break; + case 0x09: + tmp += QLatin1String("\\t"); + break; + case 0x0A: + tmp += QLatin1String("\\n"); + break; + case 0x0B: + tmp += QLatin1String("\\v"); + break; + case 0x0C: + tmp += QLatin1String("\\f"); + break; + case 0x0D: + tmp += QLatin1String("\\r"); + break; + case 0x22: + tmp += QLatin1String("\\\""); + break; + case 0x27: + tmp += QLatin1String("\\\'"); + break; + case 0x5C: + tmp += QLatin1String("\\\\"); + break; + default: + tmp += c; + break; + } + } + tmp += QLatin1Char('\"'); + return tmp; +} + +QString Binding::valueAsScriptString(const Unit *unit) const +{ + if (type == Type_String) + return escapedString(unit->stringAt(stringIndex)); + else + return valueAsString(unit); +} + } } diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h index 90f27d5f57..94ea4bdc90 100644 --- a/src/qml/compiler/qv4compileddata_p.h +++ b/src/qml/compiler/qv4compileddata_p.h @@ -45,7 +45,7 @@ #include <QVector> #include <QStringList> #include <QHash> -#include <private/qv4value_def_p.h> +#include <private/qv4value_p.h> #include <private/qv4executableallocator_p.h> QT_BEGIN_NAMESPACE @@ -70,17 +70,32 @@ struct RegExp; struct Location { - int line; - int column; + qint32 line; + qint32 column; + inline bool operator<(const Location &other) const { + return line < other.line || + (line == other.line && column < other.column); + } +}; + +struct TypeReference +{ + TypeReference(const Location &loc) + : location(loc) + , needsCreation(false) + {} + Location location; // first use + bool needsCreation; // whether the type needs to be creatable or not }; // map from name index to location of first use -struct TypeReferenceMap : QHash<int, Location> +struct TypeReferenceMap : QHash<int, TypeReference> { - void add(int nameIndex, const Location &loc) { - if (contains(nameIndex)) - return; - insert(nameIndex, loc); + TypeReference &add(int nameIndex, const Location &loc) { + Iterator it = find(nameIndex); + if (it != end()) + return *it; + return *insert(nameIndex, loc); } }; @@ -102,7 +117,9 @@ struct Lookup enum Type { Type_Getter = 0x0, Type_Setter = 0x1, - Type_GlobalGetter = 2 + Type_GlobalGetter = 2, + Type_IndexedGetter = 3, + Type_IndexedSetter = 4 }; quint32 type_and_flags; @@ -188,8 +205,8 @@ struct Unit const RegExp *regexpAt(int index) const { return reinterpret_cast<const RegExp*>(reinterpret_cast<const char *>(this) + offsetToRegexpTable + index * sizeof(RegExp)); } - const QV4::SafeValue *constants() const { - return reinterpret_cast<const QV4::SafeValue*>(reinterpret_cast<const char *>(this) + offsetToConstantTable); + const QV4::Value *constants() const { + return reinterpret_cast<const QV4::Value*>(reinterpret_cast<const char *>(this) + offsetToConstantTable); } const JSClassMember *jsClassAt(int idx, int *nMembers) const { @@ -264,7 +281,7 @@ struct Function // Qml data structures -struct Binding +struct Q_QML_EXPORT Binding { quint32 propertyNameIndex; @@ -280,7 +297,13 @@ struct Binding }; enum Flags { - IsSignalHandlerExpression = 0x1 + IsSignalHandlerExpression = 0x1, + IsSignalHandlerObject = 0x2, + IsOnAssignment = 0x4, + InitializerForReadOnlyDeclaration = 0x8, + IsResolvedEnum = 0x10, + IsListItem = 0x20, + IsBindingToAlias = 0x40 }; quint32 flags : 16; @@ -294,8 +317,57 @@ struct Binding quint32 stringIndex; // Set for Type_String and Type_Script (the latter because of script strings) Location location; + Location valueLocation; + + bool isValueBinding() const + { + if (type == Type_AttachedProperty + || type == Type_GroupProperty) + return false; + if (flags & IsSignalHandlerExpression + || flags & IsSignalHandlerObject) + return false; + return true; + } + + bool isValueBindingNoAlias() const { return isValueBinding() && !(flags & IsBindingToAlias); } + bool isValueBindingToAlias() const { return isValueBinding() && (flags & IsBindingToAlias); } + + bool isSignalHandler() const + { + if (flags & IsSignalHandlerExpression || flags & IsSignalHandlerObject) { + Q_ASSERT(!isValueBinding()); + Q_ASSERT(!isAttachedProperty()); + Q_ASSERT(!isGroupProperty()); + return true; + } + return false; + } + + bool isAttachedProperty() const + { + if (type == Type_AttachedProperty) { + Q_ASSERT(!isValueBinding()); + Q_ASSERT(!isSignalHandler()); + Q_ASSERT(!isGroupProperty()); + return true; + } + return false; + } + + bool isGroupProperty() const + { + if (type == Type_GroupProperty) { + Q_ASSERT(!isValueBinding()); + Q_ASSERT(!isSignalHandler()); + Q_ASSERT(!isAttachedProperty()); + return true; + } + return false; + } QString valueAsString(const Unit *unit) const; + QString valueAsScriptString(const Unit *unit) const; double valueAsNumber() const { if (type == Type_Number) @@ -369,7 +441,7 @@ struct Object // it will be the name of the attached type. quint32 inheritedTypeNameIndex; quint32 idIndex; - quint32 indexOfDefaultProperty; + qint32 indexOfDefaultProperty; // -1 means no default property declared in this object quint32 nFunctions; quint32 offsetToFunctions; quint32 nProperties; @@ -491,9 +563,9 @@ struct Q_QML_EXPORT CompilationUnit QString fileName() const { return data->stringAt(data->sourceFileIndex); } - QV4::SafeString *runtimeStrings; // Array + QV4::StringValue *runtimeStrings; // Array QV4::Lookup *runtimeLookups; - QV4::SafeValue *runtimeRegularExpressions; + QV4::Value *runtimeRegularExpressions; QV4::InternalClass **runtimeClasses; QVector<QV4::Function *> runtimeFunctions; // QVector<QV4::Function *> runtimeFunctionsSortedByAddress; @@ -516,6 +588,8 @@ protected: } +Q_DECLARE_TYPEINFO(QV4::CompiledData::JSClassMember, Q_PRIMITIVE_TYPE); + QT_END_NAMESPACE #endif diff --git a/src/qml/compiler/qv4compiler.cpp b/src/qml/compiler/qv4compiler.cpp index 9041b04837..bc47b815f2 100644 --- a/src/qml/compiler/qv4compiler.cpp +++ b/src/qml/compiler/qv4compiler.cpp @@ -53,6 +53,8 @@ QV4::Compiler::JSUnitGenerator::JSUnitGenerator(QQmlJS::V4IR::Module *module, in if (headerSize == -1) headerSize = sizeof(QV4::CompiledData::Unit); this->headerSize = headerSize; + // Make sure the empty string always gets index 0 + registerString(QString()); } int QV4::Compiler::JSUnitGenerator::registerString(const QString &str) @@ -72,6 +74,24 @@ int QV4::Compiler::JSUnitGenerator::getStringId(const QString &string) const return stringToId.value(string); } +uint QV4::Compiler::JSUnitGenerator::registerIndexedGetterLookup() +{ + CompiledData::Lookup l; + l.type_and_flags = CompiledData::Lookup::Type_IndexedGetter; + l.nameIndex = 0; + lookups << l; + return lookups.size() - 1; +} + +uint QV4::Compiler::JSUnitGenerator::registerIndexedSetterLookup() +{ + CompiledData::Lookup l; + l.type_and_flags = CompiledData::Lookup::Type_IndexedSetter; + l.nameIndex = 0; + lookups << l; + return lookups.size() - 1; +} + uint QV4::Compiler::JSUnitGenerator::registerGetterLookup(const QString &name) { CompiledData::Lookup l; @@ -81,6 +101,7 @@ uint QV4::Compiler::JSUnitGenerator::registerGetterLookup(const QString &name) return lookups.size() - 1; } + uint QV4::Compiler::JSUnitGenerator::registerSetterLookup(const QString &name) { CompiledData::Lookup l; diff --git a/src/qml/compiler/qv4compiler_p.h b/src/qml/compiler/qv4compiler_p.h index 1596fcb622..6baefae7b7 100644 --- a/src/qml/compiler/qv4compiler_p.h +++ b/src/qml/compiler/qv4compiler_p.h @@ -70,6 +70,8 @@ struct Q_QML_EXPORT JSUnitGenerator { uint registerGetterLookup(const QString &name); uint registerSetterLookup(const QString &name); uint registerGlobalGetterLookup(const QString &name); + uint registerIndexedGetterLookup(); + uint registerIndexedSetterLookup(); int registerRegExp(QQmlJS::V4IR::RegExp *regexp); diff --git a/src/qml/compiler/qv4instr_moth_p.h b/src/qml/compiler/qv4instr_moth_p.h index 5b5667abb4..e3e227796e 100644 --- a/src/qml/compiler/qv4instr_moth_p.h +++ b/src/qml/compiler/qv4instr_moth_p.h @@ -43,7 +43,7 @@ #define QV4INSTR_MOTH_P_H #include <QtCore/qglobal.h> -#include <private/qv4value_def_p.h> +#include <private/qv4value_p.h> #include <private/qv4function_p.h> #include <private/qv4runtime_p.h> @@ -51,16 +51,20 @@ QT_BEGIN_NAMESPACE #define FOR_EACH_MOTH_INSTR(F) \ F(Ret, ret) \ + F(Debug, debug) \ F(LoadRuntimeString, loadRuntimeString) \ F(LoadRegExp, loadRegExp) \ F(LoadClosure, loadClosure) \ F(Move, move) \ + F(MoveConst, moveConst) \ F(SwapTemps, swapTemps) \ F(LoadName, loadName) \ F(GetGlobalLookup, getGlobalLookup) \ F(StoreName, storeName) \ F(LoadElement, loadElement) \ + F(LoadElementLookup, loadElementLookup) \ F(StoreElement, storeElement) \ + F(StoreElementLookup, storeElementLookup) \ F(LoadProperty, loadProperty) \ F(GetLookup, getLookup) \ F(StoreProperty, storeProperty) \ @@ -103,7 +107,8 @@ QT_BEGIN_NAMESPACE F(CreateActivationProperty, createActivationProperty) \ F(ConstructGlobalLookup, constructGlobalLookup) \ F(Jump, jump) \ - F(CJump, cjump) \ + F(JumpEq, jumpEq) \ + F(JumpNe, jumpNe) \ F(UNot, unot) \ F(UNotBool, unotBool) \ F(UPlus, uplus) \ @@ -117,15 +122,16 @@ QT_BEGIN_NAMESPACE F(BitAnd, bitAnd) \ F(BitOr, bitOr) \ F(BitXor, bitXor) \ + F(Shr, shr) \ + F(Shl, shl) \ F(BitAndConst, bitAndConst) \ F(BitOrConst, bitOrConst) \ F(BitXorConst, bitXorConst) \ + F(ShrConst, shrConst) \ + F(ShlConst, shlConst) \ F(Mul, mul) \ F(Sub, sub) \ F(BinopContext, binopContext) \ - F(AddNumberParams, addNumberParams) \ - F(MulNumberParams, mulNumberParams) \ - F(SubNumberParams, subNumberParams) \ F(LoadThis, loadThis) \ F(LoadQmlIdArray, loadQmlIdArray) \ F(LoadQmlImportedScripts, loadQmlImportedScripts) \ @@ -140,11 +146,9 @@ QT_BEGIN_NAMESPACE #define MOTH_INSTR_ALIGN_MASK (Q_ALIGNOF(QQmlJS::Moth::Instr) - 1) #ifdef MOTH_THREADED_INTERPRETER -# define MOTH_INSTR_HEADER void *code; \ - unsigned int breakPoint : 1; +# define MOTH_INSTR_HEADER void *code; #else -# define MOTH_INSTR_HEADER quint8 instructionType; \ - unsigned int breakPoint : 1; +# define MOTH_INSTR_HEADER quint32 instructionType; #endif #define MOTH_INSTR_ENUM(I, FMT) I, @@ -231,7 +235,11 @@ union Instr struct instr_ret { MOTH_INSTR_HEADER Param result; - }; + }; + struct instr_debug { + MOTH_INSTR_HEADER + quint32 breakPoint; + }; struct instr_loadRuntimeString { MOTH_INSTR_HEADER int stringId; @@ -247,6 +255,11 @@ union Instr Param source; Param result; }; + struct instr_moveConst { + MOTH_INSTR_HEADER + QV4::ReturnedValue source; + Param result; + }; struct instr_swapTemps { MOTH_INSTR_HEADER Param left; @@ -322,12 +335,26 @@ union Instr Param index; Param result; }; + struct instr_loadElementLookup { + MOTH_INSTR_HEADER + uint lookup; + Param base; + Param index; + Param result; + }; struct instr_storeElement { MOTH_INSTR_HEADER Param base; Param index; Param source; }; + struct instr_storeElementLookup { + MOTH_INSTR_HEADER + uint lookup; + Param base; + Param index; + Param source; + }; struct instr_push { MOTH_INSTR_HEADER quint32 value; @@ -525,13 +552,17 @@ union Instr }; struct instr_jump { MOTH_INSTR_HEADER - ptrdiff_t offset; + ptrdiff_t offset; + }; + struct instr_jumpEq { + MOTH_INSTR_HEADER + ptrdiff_t offset; + Param condition; }; - struct instr_cjump { + struct instr_jumpNe { MOTH_INSTR_HEADER ptrdiff_t offset; Param condition; - bool invert; }; struct instr_unot { MOTH_INSTR_HEADER @@ -604,57 +635,63 @@ union Instr Param rhs; Param result; }; - struct instr_bitAndConst { + struct instr_shr { MOTH_INSTR_HEADER Param lhs; - int rhs; + Param rhs; Param result; }; - struct instr_bitOrConst { + struct instr_shl { + MOTH_INSTR_HEADER + Param lhs; + Param rhs; + Param result; + }; + struct instr_bitAndConst { MOTH_INSTR_HEADER Param lhs; int rhs; Param result; }; - struct instr_bitXorConst { + struct instr_bitOrConst { MOTH_INSTR_HEADER Param lhs; int rhs; Param result; }; - struct instr_mul { + struct instr_bitXorConst { MOTH_INSTR_HEADER Param lhs; - Param rhs; + int rhs; Param result; }; - struct instr_sub { + struct instr_shrConst { MOTH_INSTR_HEADER Param lhs; - Param rhs; + int rhs; Param result; }; - struct instr_binopContext { + struct instr_shlConst { MOTH_INSTR_HEADER - QV4::BinOpContext alu; Param lhs; - Param rhs; + int rhs; Param result; }; - struct instr_addNumberParams { + struct instr_mul { MOTH_INSTR_HEADER Param lhs; Param rhs; Param result; }; - struct instr_mulNumberParams { + struct instr_sub { MOTH_INSTR_HEADER Param lhs; Param rhs; Param result; }; - struct instr_subNumberParams { + struct instr_binopContext { MOTH_INSTR_HEADER + QV4::BinOpContext alu; Param lhs; Param rhs; Param result; @@ -687,16 +724,20 @@ union Instr instr_common common; instr_ret ret; + instr_debug debug; instr_loadRuntimeString loadRuntimeString; instr_loadRegExp loadRegExp; instr_move move; + instr_moveConst moveConst; instr_swapTemps swapTemps; instr_loadClosure loadClosure; instr_loadName loadName; instr_getGlobalLookup getGlobalLookup; instr_storeName storeName; instr_loadElement loadElement; + instr_loadElementLookup loadElementLookup; instr_storeElement storeElement; + instr_storeElementLookup storeElementLookup; instr_loadProperty loadProperty; instr_getLookup getLookup; instr_loadQObjectProperty loadQObjectProperty; @@ -739,7 +780,8 @@ union Instr instr_createActivationProperty createActivationProperty; instr_constructGlobalLookup constructGlobalLookup; instr_jump jump; - instr_cjump cjump; + instr_jumpEq jumpEq; + instr_jumpNe jumpNe; instr_unot unot; instr_unotBool unotBool; instr_uplus uplus; @@ -753,15 +795,16 @@ union Instr instr_bitAnd bitAnd; instr_bitOr bitOr; instr_bitXor bitXor; + instr_shr shr; + instr_shl shl; instr_bitAndConst bitAndConst; instr_bitOrConst bitOrConst; instr_bitXorConst bitXorConst; + instr_shrConst shrConst; + instr_shlConst shlConst; instr_mul mul; instr_sub sub; instr_binopContext binopContext; - instr_addNumberParams addNumberParams; - instr_mulNumberParams mulNumberParams; - instr_subNumberParams subNumberParams; instr_loadThis loadThis; instr_loadQmlIdArray loadQmlIdArray; instr_loadQmlImportedScripts loadQmlImportedScripts; @@ -782,7 +825,7 @@ struct InstrMeta { typedef Instr::instr_##FMT DataType; \ static const DataType &data(const Instr &instr) { return instr.FMT; } \ static void setData(Instr &instr, const DataType &v) { instr.FMT = v; } \ - }; + }; FOR_EACH_MOTH_INSTR(MOTH_INSTR_META_TEMPLATE); #undef MOTH_INSTR_META_TEMPLATE diff --git a/src/qml/compiler/qv4isel_masm.cpp b/src/qml/compiler/qv4isel_masm.cpp index 0fcd770584..c89b108309 100644 --- a/src/qml/compiler/qv4isel_masm.cpp +++ b/src/qml/compiler/qv4isel_masm.cpp @@ -245,12 +245,12 @@ Assembler::Pointer Assembler::loadTempAddress(RegisterID baseReg, V4IR::Temp *t) case V4IR::Temp::Formal: case V4IR::Temp::ScopedFormal: { loadPtr(Address(context, qOffsetOf(ExecutionContext, callData)), baseReg); - offset = sizeof(CallData) + (t->index - 1) * sizeof(SafeValue); + offset = sizeof(CallData) + (t->index - 1) * sizeof(Value); } break; case V4IR::Temp::Local: case V4IR::Temp::ScopedLocal: { loadPtr(Address(context, qOffsetOf(CallContext, locals)), baseReg); - offset = t->index * sizeof(SafeValue); + offset = t->index * sizeof(Value); } break; case V4IR::Temp::StackSlot: { return stackSlotPointer(t); @@ -266,7 +266,7 @@ Assembler::Pointer Assembler::loadStringAddress(RegisterID reg, const QString &s loadPtr(Address(Assembler::ContextRegister, qOffsetOf(QV4::ExecutionContext, compilationUnit)), Assembler::ScratchRegister); loadPtr(Address(Assembler::ScratchRegister, qOffsetOf(QV4::CompiledData::CompilationUnit, runtimeStrings)), reg); const int id = _isel->registerString(string); - return Pointer(reg, id * sizeof(QV4::SafeString)); + return Pointer(reg, id * sizeof(QV4::StringValue)); } void Assembler::loadStringRef(RegisterID reg, const QString &string) @@ -274,7 +274,7 @@ void Assembler::loadStringRef(RegisterID reg, const QString &string) loadPtr(Address(Assembler::ContextRegister, qOffsetOf(QV4::ExecutionContext, compilationUnit)), reg); loadPtr(Address(reg, qOffsetOf(QV4::CompiledData::CompilationUnit, runtimeStrings)), reg); const int id = _isel->registerString(string); - addPtr(TrustedImmPtr(id * sizeof(QV4::SafeString)), reg); + addPtr(TrustedImmPtr(id * sizeof(QV4::StringValue)), reg); } template <typename Result, typename Source> @@ -615,7 +615,7 @@ void InstructionSelection::run(int functionIndex) const int locals = _as->stackLayout().calculateJSStackFrameSize(); _as->loadPtr(Address(Assembler::ContextRegister, qOffsetOf(ExecutionContext, engine)), Assembler::ScratchRegister); _as->loadPtr(Address(Assembler::ScratchRegister, qOffsetOf(ExecutionEngine, jsStackTop)), Assembler::LocalsRegister); - _as->addPtr(Assembler::TrustedImm32(sizeof(QV4::SafeValue)*locals), Assembler::LocalsRegister); + _as->addPtr(Assembler::TrustedImm32(sizeof(QV4::Value)*locals), Assembler::LocalsRegister); _as->storePtr(Assembler::LocalsRegister, Address(Assembler::ScratchRegister, qOffsetOf(ExecutionEngine, jsStackTop))); int lastLine = -1; @@ -1015,99 +1015,13 @@ void InstructionSelection::setQObjectProperty(V4IR::Expr *source, V4IR::Expr *ta void InstructionSelection::getElement(V4IR::Expr *base, V4IR::Expr *index, V4IR::Temp *target) { -#if QT_POINTER_SIZE == 8 - V4IR::Temp *tbase = base->asTemp(); - V4IR::Temp *tindex = index->asTemp(); - if (tbase && tindex && - tbase->kind != V4IR::Temp::PhysicalRegister) { - Assembler::Pointer addr = _as->loadTempAddress(Assembler::ReturnValueRegister, tbase); - _as->load64(addr, Assembler::ScratchRegister); - _as->move(Assembler::ScratchRegister, Assembler::ReturnValueRegister); - _as->urshift64(Assembler::TrustedImm32(QV4::Value::IsManaged_Shift), Assembler::ReturnValueRegister); - Assembler::Jump notManaged = _as->branch64(Assembler::NotEqual, Assembler::ReturnValueRegister, Assembler::TrustedImm64(0)); - // check whether we have an object with a simple array - Assembler::Address managedType(Assembler::ScratchRegister, qOffsetOf(QV4::Managed, flags)); - _as->load8(managedType, Assembler::ReturnValueRegister); - _as->and32(Assembler::TrustedImm32(QV4::Managed::SimpleArray), Assembler::ReturnValueRegister); - Assembler::Jump notSimple = _as->branch32(Assembler::Equal, Assembler::ReturnValueRegister, Assembler::TrustedImm32(0)); - - bool needNegativeCheck = false; - Assembler::Jump fallback, fallback2; - if (tindex->kind == V4IR::Temp::PhysicalRegister) { - if (tindex->type == V4IR::SInt32Type) { - fallback = _as->branch32(Assembler::LessThan, (Assembler::RegisterID)tindex->index, Assembler::TrustedImm32(0)); - _as->move((Assembler::RegisterID) tindex->index, Assembler::ScratchRegister); - needNegativeCheck = true; - } else { - // double, convert and check if it's a int - fallback2 = _as->branchTruncateDoubleToUint32((Assembler::FPRegisterID) tindex->index, Assembler::ScratchRegister); - _as->convertInt32ToDouble(Assembler::ScratchRegister, Assembler::FPGpr0); - fallback = _as->branchDouble(Assembler::DoubleNotEqual, Assembler::FPGpr0, (Assembler::FPRegisterID) tindex->index); - } - } else { - Assembler::Pointer indexAddr = _as->loadTempAddress(Assembler::ReturnValueRegister, tindex); - _as->load64(indexAddr, Assembler::ScratchRegister); - _as->move(Assembler::ScratchRegister, Assembler::ReturnValueRegister); - _as->urshift64(Assembler::TrustedImm32(QV4::Value::IsNumber_Shift), Assembler::ReturnValueRegister); - Assembler::Jump isInteger = _as->branch64(Assembler::Equal, Assembler::ReturnValueRegister, Assembler::TrustedImm64(1)); - - // other type, convert to double and check if it's a int - // this check is ok to do even if the type is something else than a double, as - // that would result in a NaN - _as->move(Assembler::TrustedImm64(QV4::Value::NaNEncodeMask), Assembler::ReturnValueRegister); - _as->xor64(Assembler::ScratchRegister, Assembler::ReturnValueRegister); - _as->move64ToDouble(Assembler::ReturnValueRegister, Assembler::FPGpr0); - fallback2 = _as->branchTruncateDoubleToUint32(Assembler::FPGpr0, Assembler::ScratchRegister); - _as->convertInt32ToDouble(Assembler::ScratchRegister, Assembler::FPGpr1); - fallback = _as->branchDouble(Assembler::DoubleNotEqualOrUnordered, Assembler::FPGpr0, Assembler::FPGpr1); - - isInteger.link(_as); - _as->or32(Assembler::TrustedImm32(0), Assembler::ScratchRegister); - needNegativeCheck = true; - } - - // get data, ScratchRegister holds index - addr = _as->loadTempAddress(Assembler::ReturnValueRegister, tbase); - _as->load64(addr, Assembler::ReturnValueRegister); - Address arrayDataLen(Assembler::ReturnValueRegister, qOffsetOf(Object, arrayDataLen)); - Assembler::Jump outOfRange; - if (needNegativeCheck) - outOfRange = _as->branch32(Assembler::LessThan, Assembler::ScratchRegister, Assembler::TrustedImm32(0)); - Assembler::Jump outOfRange2 = _as->branch32(Assembler::GreaterThanOrEqual, Assembler::ScratchRegister, arrayDataLen); - Address arrayData(Assembler::ReturnValueRegister, qOffsetOf(Object, arrayData)); - _as->load64(arrayData, Assembler::ReturnValueRegister); - Q_ASSERT(sizeof(Property) == (1<<4)); - _as->lshift64(Assembler::TrustedImm32(4), Assembler::ScratchRegister); - _as->add64(Assembler::ReturnValueRegister, Assembler::ScratchRegister); - Address value(Assembler::ScratchRegister, qOffsetOf(Property, value)); - _as->load64(value, Assembler::ReturnValueRegister); - - // check that the value is not empty - _as->move(Assembler::ReturnValueRegister, Assembler::ScratchRegister); - _as->urshift64(Assembler::TrustedImm32(32), Assembler::ScratchRegister); - Assembler::Jump emptyValue = _as->branch32(Assembler::Equal, Assembler::TrustedImm32(QV4::Value::Empty_Type), Assembler::ScratchRegister); - _as->storeReturnValue(target); - - Assembler::Jump done = _as->jump(); - - emptyValue.link(_as); - if (outOfRange.isSet()) - outOfRange.link(_as); - outOfRange2.link(_as); - if (fallback.isSet()) - fallback.link(_as); - if (fallback2.isSet()) - fallback2.link(_as); - notSimple.link(_as); - notManaged.link(_as); - - generateFunctionCall(target, __qmljs_get_element, Assembler::ContextRegister, - Assembler::PointerToValue(base), Assembler::PointerToValue(index)); - - done.link(_as); + if (useFastLookups) { + uint lookup = registerIndexedGetterLookup(); + generateLookupCall(target, lookup, qOffsetOf(QV4::Lookup, indexedGetter), + Assembler::PointerToValue(base), + Assembler::PointerToValue(index)); return; } -#endif generateFunctionCall(target, __qmljs_get_element, Assembler::ContextRegister, Assembler::PointerToValue(base), Assembler::PointerToValue(index)); @@ -1115,6 +1029,13 @@ void InstructionSelection::getElement(V4IR::Expr *base, V4IR::Expr *index, V4IR: void InstructionSelection::setElement(V4IR::Expr *source, V4IR::Expr *targetBase, V4IR::Expr *targetIndex) { + if (useFastLookups) { + uint lookup = registerIndexedSetterLookup(); + generateLookupCall(Assembler::Void, lookup, qOffsetOf(QV4::Lookup, indexedSetter), + Assembler::PointerToValue(targetBase), Assembler::PointerToValue(targetIndex), + Assembler::PointerToValue(source)); + return; + } generateFunctionCall(Assembler::Void, __qmljs_set_element, Assembler::ContextRegister, Assembler::PointerToValue(targetBase), Assembler::PointerToValue(targetIndex), Assembler::PointerToValue(source)); @@ -2010,7 +1931,7 @@ void InstructionSelection::visitRet(V4IR::Ret *s) _as->exceptionReturnLabel = _as->label(); const int locals = _as->stackLayout().calculateJSStackFrameSize(); - _as->subPtr(Assembler::TrustedImm32(sizeof(QV4::SafeValue)*locals), Assembler::LocalsRegister); + _as->subPtr(Assembler::TrustedImm32(sizeof(QV4::Value)*locals), Assembler::LocalsRegister); _as->loadPtr(Address(Assembler::ContextRegister, qOffsetOf(ExecutionContext, engine)), Assembler::ScratchRegister); _as->storePtr(Assembler::LocalsRegister, Address(Assembler::ScratchRegister, qOffsetOf(ExecutionEngine, jsStackTop))); diff --git a/src/qml/compiler/qv4isel_masm_p.h b/src/qml/compiler/qv4isel_masm_p.h index 7dfe6a820a..76fe6c6391 100644 --- a/src/qml/compiler/qv4isel_masm_p.h +++ b/src/qml/compiler/qv4isel_masm_p.h @@ -45,7 +45,7 @@ #include "qv4jsir_p.h" #include "qv4isel_p.h" #include "qv4isel_util_p.h" -#include "private/qv4value_def_p.h" +#include "private/qv4value_p.h" #include "private/qv4lookup_p.h" #include <QtCore/QHash> @@ -351,7 +351,7 @@ public: // space for the callee saved registers int frameSize = RegisterSize * calleeSavedRegisterCount; - frameSize += savedRegCount * sizeof(QV4::SafeValue); // these get written out as Values, not as native registers + frameSize += savedRegCount * sizeof(QV4::Value); // these get written out as Values, not as native registers frameSize = WTF::roundUpToMultipleOf(StackAlignment, frameSize + stackSpaceAllocatedOtherwise); frameSize -= stackSpaceAllocatedOtherwise; @@ -361,8 +361,8 @@ public: int calculateJSStackFrameSize() const { - const int locals = (localCount + sizeof(QV4::CallData)/sizeof(QV4::SafeValue) - 1 + maxOutgoingArgumentCount) + 1; - int frameSize = locals * sizeof(QV4::SafeValue); + const int locals = (localCount + sizeof(QV4::CallData)/sizeof(QV4::Value) - 1 + maxOutgoingArgumentCount) + 1; + int frameSize = locals * sizeof(QV4::Value); return frameSize; } @@ -372,7 +372,7 @@ public: Q_ASSERT(idx < localCount); Pointer addr = callDataAddress(0); - addr.offset -= sizeof(QV4::SafeValue) * (idx + 1); + addr.offset -= sizeof(QV4::Value) * (idx + 1); return addr; } @@ -384,11 +384,11 @@ public: Q_ASSERT(argument < maxOutgoingArgumentCount); const int index = maxOutgoingArgumentCount - argument; - return Pointer(Assembler::LocalsRegister, sizeof(QV4::SafeValue) * (-index)); + return Pointer(Assembler::LocalsRegister, sizeof(QV4::Value) * (-index)); } Pointer callDataAddress(int offset = 0) const { - return Pointer(Assembler::LocalsRegister, -(sizeof(QV4::CallData) + sizeof(QV4::SafeValue) * (maxOutgoingArgumentCount - 1)) + offset); + return Pointer(Assembler::LocalsRegister, -(sizeof(QV4::CallData) + sizeof(QV4::Value) * (maxOutgoingArgumentCount - 1)) + offset); } Address savedRegPointer(int offset) const @@ -396,7 +396,7 @@ public: Q_ASSERT(offset >= 0); Q_ASSERT(offset < savedRegCount); - const int off = offset * sizeof(QV4::SafeValue); + const int off = offset * sizeof(QV4::Value); return Address(Assembler::StackFrameRegister, - calleeSavedRegisterSpace() - off); } @@ -657,7 +657,7 @@ public: void storeUInt32ReturnValue(RegisterID dest) { - Pointer tmp(StackPointerRegister, -int(sizeof(QV4::SafeValue))); + Pointer tmp(StackPointerRegister, -int(sizeof(QV4::Value))); storeReturnValue(tmp); toUInt32Register(tmp, dest); } @@ -669,7 +669,7 @@ public: xor64(ScratchRegister, ReturnValueRegister); move64ToDouble(ReturnValueRegister, dest); #else - Pointer tmp(StackPointerRegister, -int(sizeof(QV4::SafeValue))); + Pointer tmp(StackPointerRegister, -int(sizeof(QV4::Value))); storeReturnValue(tmp); loadDouble(tmp, dest); #endif @@ -1584,8 +1584,8 @@ private: int prepareVariableArguments(V4IR::ExprList* args); int prepareCallData(V4IR::ExprList* args, V4IR::Expr *thisObject); - template <typename Retval, typename Arg1, typename Arg2> - void generateLookupCall(Retval retval, uint index, uint getterSetterOffset, Arg1 arg1, Arg2 arg2) + template <typename Retval, typename Arg1, typename Arg2, typename Arg3> + void generateLookupCall(Retval retval, uint index, uint getterSetterOffset, Arg1 arg1, Arg2 arg2, Arg3 arg3) { Assembler::RegisterID lookupRegister; #if CPU(ARM) @@ -1599,13 +1599,13 @@ private: getterSetter.offset += getterSetterOffset; _as->generateFunctionCallImp(retval, "lookup getter/setter", - RelativeCall(getterSetter), lookupAddr, arg1, arg2); + RelativeCall(getterSetter), lookupAddr, arg1, arg2, arg3); } - template <typename Arg1> - void generateLookupCall(uint index, uint getterSetterOffset, Arg1 arg1) + template <typename Retval, typename Arg1, typename Arg2> + void generateLookupCall(Retval retval, uint index, uint getterSetterOffset, Arg1 arg1, Arg2 arg2) { - generateLookupCall(index, getterSetterOffset, arg1, Assembler::VoidType()); + generateLookupCall(retval, index, getterSetterOffset, arg1, arg2, Assembler::VoidType()); } /// This is a temporary method, and will be removed when registers are fully supported. diff --git a/src/qml/compiler/qv4isel_moth.cpp b/src/qml/compiler/qv4isel_moth.cpp index bf9af178fe..130725f3ed 100644 --- a/src/qml/compiler/qv4isel_moth.cpp +++ b/src/qml/compiler/qv4isel_moth.cpp @@ -47,6 +47,7 @@ #include <private/qv4function_p.h> #include <private/qv4regexpobject_p.h> #include <private/qv4compileddata_p.h> +#include <private/qqmlengine_p.h> #undef USE_TYPE_INFO @@ -150,6 +151,167 @@ inline bool isBoolType(V4IR::Expr *e) return (e->type == V4IR::BoolType); } +/* + * stack slot allocation: + * + * foreach bb do + * foreach stmt do + * if the current statement is not a phi-node: + * purge ranges that end before the current statement + * check for life ranges to activate, and if they don't have a stackslot associated then allocate one + * renumber temps to stack + * for phi nodes: check if all temps (src+dst) are assigned stack slots and marked as allocated + * if it's a jump: + * foreach phi node in the successor: + * allocate slots for each temp (both sources and targets) if they don't have one allocated already + * insert moves before the jump + */ +class AllocateStackSlots: protected ConvertTemps +{ + const QVector<V4IR::LifeTimeInterval> _intervals; + QVector<const V4IR::LifeTimeInterval *> _unhandled; + QVector<const V4IR::LifeTimeInterval *> _live; + QBitArray _slotIsInUse; + V4IR::Function *_function; + +public: + AllocateStackSlots(const QVector<V4IR::LifeTimeInterval> &intervals) + : _intervals(intervals) + , _slotIsInUse(intervals.size(), false) + , _function(0) + { + _live.reserve(8); + _unhandled.reserve(_intervals.size()); + for (int i = _intervals.size() - 1; i >= 0; --i) + _unhandled.append(&_intervals.at(i)); + } + + void forFunction(V4IR::Function *function) + { + V4IR::Optimizer::showMeTheCode(function); + _function = function; + toStackSlots(function); + +// QTextStream os(stdout, QIODevice::WriteOnly); +// os << "Frame layout:" << endl; +// foreach (int t, _stackSlotForTemp.keys()) { +// os << "\t" << t << " -> " << _stackSlotForTemp[t] << endl; +// } + } + +protected: + virtual int allocateFreeSlot() + { + for (int i = 0, ei = _slotIsInUse.size(); i != ei; ++i) { + if (!_slotIsInUse[i]) { + if (_nextUnusedStackSlot <= i) { + Q_ASSERT(_nextUnusedStackSlot == i); + _nextUnusedStackSlot = i + 1; + } + _slotIsInUse[i] = true; + return i; + } + } + + Q_UNREACHABLE(); + return -1; + } + + virtual void process(V4IR::Stmt *s) + { + Q_ASSERT(s->id > 0); + +// qDebug("L%d statement %d:", _currentBasicBlock->index, s->id); + + if (V4IR::Phi *phi = s->asPhi()) { + visitPhi(phi); + } else { + // purge ranges no longer alive: + for (int i = 0; i < _live.size(); ) { + const V4IR::LifeTimeInterval *lti = _live.at(i); + if (lti->end() < s->id) { +// qDebug() << "\t - moving temp" << lti->temp().index << "to handled, freeing slot" << _stackSlotForTemp[lti->temp().index]; + _live.remove(i); + Q_ASSERT(_slotIsInUse[_stackSlotForTemp[lti->temp().index]]); + _slotIsInUse[_stackSlotForTemp[lti->temp().index]] = false; + continue; + } else { + ++i; + } + } + + // active new ranges: + while (!_unhandled.isEmpty()) { + const V4IR::LifeTimeInterval *lti = _unhandled.last(); + if (lti->start() > s->id) + break; // we're done + Q_ASSERT(!_stackSlotForTemp.contains(lti->temp().index)); + _stackSlotForTemp[lti->temp().index] = allocateFreeSlot(); +// qDebug() << "\t - activating temp" << lti->temp().index << "on slot" << _stackSlotForTemp[lti->temp().index]; + _live.append(lti); + _unhandled.removeLast(); + } + + s->accept(this); + } + + if (V4IR::Jump *jump = s->asJump()) { + V4IR::MoveMapping moves; + foreach (V4IR::Stmt *succStmt, jump->target->statements) { + if (V4IR::Phi *phi = succStmt->asPhi()) { + forceActivation(*phi->targetTemp); + for (int i = 0, ei = phi->d->incoming.size(); i != ei; ++i) { + V4IR::Expr *e = phi->d->incoming[i]; + if (V4IR::Temp *t = e->asTemp()) { + forceActivation(*t); + } + if (jump->target->in[i] == _currentBasicBlock) + moves.add(phi->d->incoming[i], phi->targetTemp); + } + } else { + break; + } + } + moves.order(); + QList<V4IR::Move *> newMoves = moves.insertMoves(_currentBasicBlock, _function, true); + foreach (V4IR::Move *move, newMoves) + move->accept(this); + } + } + + void forceActivation(const V4IR::Temp &t) + { + if (_stackSlotForTemp.contains(t.index)) + return; + + int i = _unhandled.size() - 1; + for (; i >= 0; --i) { + const V4IR::LifeTimeInterval *lti = _unhandled[i]; + if (lti->temp() == t) { + _live.append(lti); + _unhandled.remove(i); + break; + } + } + Q_ASSERT(i >= 0); // check that we always found the entry + + _stackSlotForTemp[t.index] = allocateFreeSlot(); +// qDebug() << "\t - force activating temp" << t.index << "on slot" << _stackSlotForTemp[t.index]; + } + + virtual void visitPhi(V4IR::Phi *phi) + { + Q_UNUSED(phi); +#if !defined(QT_NO_DEBUG) + Q_ASSERT(_stackSlotForTemp.contains(phi->targetTemp->index)); + Q_ASSERT(_slotIsInUse[_stackSlotForTemp[phi->targetTemp->index]]); + foreach (V4IR::Expr *e, phi->d->incoming) { + if (V4IR::Temp *t = e->asTemp()) + Q_ASSERT(_stackSlotForTemp.contains(t->index)); + } +#endif // defined(QT_NO_DEBUG) + } +}; } // anonymous namespace InstructionSelection::InstructionSelection(QQmlEnginePrivate *qmlEngine, QV4::ExecutableAllocator *execAllocator, V4IR::Module *module, QV4::Compiler::JSUnitGenerator *jsGenerator) @@ -194,10 +356,19 @@ void InstructionSelection::run(int functionIndex) V4IR::Optimizer opt(_function); opt.run(qmlEngine); if (opt.isInSSA()) { - opt.convertOutOfSSA(); + static const bool doStackSlotAllocation = + qgetenv("QV4_NO_INTERPRETER_STACK_SLOT_ALLOCATION").isEmpty(); + + if (doStackSlotAllocation) { + AllocateStackSlots(opt.lifeRanges()).forFunction(_function); + } else { + opt.convertOutOfSSA(); + ConvertTemps().toStackSlots(_function); + } opt.showMeTheCode(_function); + } else { + ConvertTemps().toStackSlots(_function); } - ConvertTemps().toStackSlots(_function); QSet<V4IR::Jump *> removableJumps = opt.calculateOptionalJumps(); qSwap(_removableJumps, removableJumps); @@ -217,7 +388,13 @@ void InstructionSelection::run(int functionIndex) QVector<uint> lineNumberMappings; lineNumberMappings.reserve(_function->basicBlocks.size() * 2); + uint currentLine = -1; for (int i = 0, ei = _function->basicBlocks.size(); i != ei; ++i) { + if (irModule->debugMode) { + Instruction::Debug debug; + debug.breakPoint = 0; + addInstruction(debug); + } _block = _function->basicBlocks[i]; _nextBlock = (i < ei - 1) ? _function->basicBlocks[i + 1] : 0; _addrs.insert(_block, _codeNext - _codeStart); @@ -237,8 +414,15 @@ void InstructionSelection::run(int functionIndex) foreach (V4IR::Stmt *s, _block->statements) { _currentStatement = s; - if (s->location.isValid()) + if (s->location.isValid()) { lineNumberMappings << _codeNext - _codeStart << s->location.startLine; + if (irModule->debugMode && s->location.startLine != currentLine) { + Instruction::Debug debug; + debug.breakPoint = 0; + addInstruction(debug); + currentLine = s->location.startLine; + } + } s->accept(this); } @@ -323,7 +507,7 @@ void InstructionSelection::callSubscript(V4IR::Expr *base, V4IR::Expr *index, V4 void InstructionSelection::convertType(V4IR::Temp *source, V4IR::Temp *target) { // FIXME: do something more useful with this info - if (target->type & V4IR::NumberType) + if (target->type & V4IR::NumberType && !(source->type & V4IR::NumberType)) unop(V4IR::OpUPlus, source, target); else copyValue(source, target); @@ -428,8 +612,8 @@ void InstructionSelection::loadConst(V4IR::Const *sourceConst, V4IR::Temp *targe { assert(sourceConst); - Instruction::Move move; - move.source = getParam(sourceConst); + Instruction::MoveConst move; + move.source = convertToValue(sourceConst).asReturnedValue(); move.result = getResultParam(targetTemp); addInstruction(move); } @@ -546,6 +730,15 @@ void InstructionSelection::getQObjectProperty(V4IR::Expr *base, int propertyInde void InstructionSelection::getElement(V4IR::Expr *base, V4IR::Expr *index, V4IR::Temp *target) { + if (useFastLookups) { + Instruction::LoadElementLookup load; + load.lookup = registerIndexedGetterLookup(); + load.base = getParam(base); + load.index = getParam(index); + load.result = getResultParam(target); + addInstruction(load); + return; + } Instruction::LoadElement load; load.base = getParam(base); load.index = getParam(index); @@ -556,6 +749,15 @@ void InstructionSelection::getElement(V4IR::Expr *base, V4IR::Expr *index, V4IR: void InstructionSelection::setElement(V4IR::Expr *source, V4IR::Expr *targetBase, V4IR::Expr *targetIndex) { + if (useFastLookups) { + Instruction::StoreElementLookup store; + store.lookup = registerIndexedSetterLookup(); + store.base = getParam(targetBase); + store.index = getParam(targetIndex); + store.source = getParam(source); + addInstruction(store); + return; + } Instruction::StoreElement store; store.base = getParam(targetBase); store.index = getParam(targetIndex); @@ -613,7 +815,8 @@ void InstructionSelection::unop(V4IR::AluOp oper, V4IR::Temp *sourceTemp, V4IR:: Instruction::Move move; move.source = getParam(sourceTemp); move.result = getResultParam(targetTemp); - addInstruction(move); + if (move.source != move.result) + addInstruction(move); return; } Instruction::UPlus uplus; @@ -664,37 +867,6 @@ void InstructionSelection::binop(V4IR::AluOp oper, V4IR::Expr *leftSource, V4IR: Param InstructionSelection::binopHelper(V4IR::AluOp oper, V4IR::Expr *leftSource, V4IR::Expr *rightSource, V4IR::Temp *target) { - if (isNumberType(leftSource) && isNumberType(rightSource)) { - switch (oper) { - case V4IR::OpAdd: { - Instruction::AddNumberParams instr; - instr.lhs = getParam(leftSource); - instr.rhs = getParam(rightSource); - instr.result = getResultParam(target); - addInstruction(instr); - return instr.result; - } - case V4IR::OpMul: { - Instruction::MulNumberParams instr; - instr.lhs = getParam(leftSource); - instr.rhs = getParam(rightSource); - instr.result = getResultParam(target); - addInstruction(instr); - return instr.result; - } - case V4IR::OpSub: { - Instruction::SubNumberParams instr; - instr.lhs = getParam(leftSource); - instr.rhs = getParam(rightSource); - instr.result = getResultParam(target); - addInstruction(instr); - return instr.result; - } - default: - break; - } - } - if (oper == V4IR::OpAdd) { Instruction::Add add; add.lhs = getParam(leftSource); @@ -773,6 +945,38 @@ Param InstructionSelection::binopHelper(V4IR::AluOp oper, V4IR::Expr *leftSource addInstruction(bitXor); return bitXor.result; } + if (oper == V4IR::OpRShift) { + if (V4IR::Const *c = rightSource->asConst()) { + Instruction::ShrConst shr; + shr.lhs = getParam(leftSource); + shr.rhs = convertToValue(c).Value::toInt32() & 0x1f; + shr.result = getResultParam(target); + addInstruction(shr); + return shr.result; + } + Instruction::Shr shr; + shr.lhs = getParam(leftSource); + shr.rhs = getParam(rightSource); + shr.result = getResultParam(target); + addInstruction(shr); + return shr.result; + } + if (oper == V4IR::OpLShift) { + if (V4IR::Const *c = rightSource->asConst()) { + Instruction::ShlConst shl; + shl.lhs = getParam(leftSource); + shl.rhs = convertToValue(c).Value::toInt32() & 0x1f; + shl.result = getResultParam(target); + addInstruction(shl); + return shl.result; + } + Instruction::Shl shl; + shl.lhs = getParam(leftSource); + shl.rhs = getParam(rightSource); + shl.result = getResultParam(target); + addInstruction(shl); + return shl.result; + } if (oper == V4IR::OpInstanceof || oper == V4IR::OpIn || oper == V4IR::OpAdd) { Instruction::BinopContext binop; @@ -810,10 +1014,17 @@ void InstructionSelection::prepareCallArgs(V4IR::ExprList *e, quint32 &argc, qui // We need to move all the temps into the function arg array assert(argLocation >= 0); while (e) { - Instruction::Move move; - move.source = getParam(e->expr); - move.result = Param::createTemp(argLocation); - addInstruction(move); + if (V4IR::Const *c = e->expr->asConst()) { + Instruction::MoveConst move; + move.source = convertToValue(c).asReturnedValue(); + move.result = Param::createTemp(argLocation); + addInstruction(move); + } else { + Instruction::Move move; + move.source = getParam(e->expr); + move.result = Param::createTemp(argLocation); + addInstruction(move); + } ++argLocation; ++argc; e = e->next; @@ -846,16 +1057,16 @@ void InstructionSelection::visitCJump(V4IR::CJump *s) Q_UNIMPLEMENTED(); } - Instruction::CJump jump; - jump.offset = 0; - jump.condition = condition; - if (s->iftrue == _nextBlock) { - jump.invert = true; + Instruction::JumpNe jump; + jump.offset = 0; + jump.condition = condition; ptrdiff_t falseLoc = addInstruction(jump) + (((const char *)&jump.offset) - ((const char *)&jump)); _patches[s->iffalse].append(falseLoc); } else { - jump.invert = false; + Instruction::JumpEq jump; + jump.offset = 0; + jump.condition = condition; ptrdiff_t trueLoc = addInstruction(jump) + (((const char *)&jump.offset) - ((const char *)&jump)); _patches[s->iftrue].append(trueLoc); @@ -959,9 +1170,8 @@ void InstructionSelection::callBuiltinDeleteName(const QString &name, V4IR::Temp void InstructionSelection::callBuiltinDeleteValue(V4IR::Temp *result) { - Instruction::Move move; - int idx = jsUnitGenerator()->registerConstant(QV4::Encode(false)); - move.source = Param::createConstant(idx); + Instruction::MoveConst move; + move.source = QV4::Encode(false); move.result = getResultParam(result); addInstruction(move); } @@ -1082,10 +1292,17 @@ void InstructionSelection::callBuiltinDefineObjectLiteral(V4IR::Temp *result, V4 bool isData = it->expr->asConst()->value; it = it->next; - Instruction::Move move; - move.source = getParam(it->expr); - move.result = Param::createTemp(argLocation); - addInstruction(move); + if (V4IR::Const *c = it->expr->asConst()) { + Instruction::MoveConst move; + move.source = convertToValue(c).asReturnedValue(); + move.result = Param::createTemp(argLocation); + addInstruction(move); + } else { + Instruction::Move move; + move.source = getParam(it->expr); + move.result = Param::createTemp(argLocation); + addInstruction(move); + } ++argLocation; if (!isData) { @@ -1124,12 +1341,12 @@ void QQmlJS::Moth::InstructionSelection::callBuiltinConvertThisToObject() ptrdiff_t InstructionSelection::addInstructionHelper(Instr::Type type, Instr &instr) { + #ifdef MOTH_THREADED_INTERPRETER instr.common.code = VME::instructionJumpTable()[static_cast<int>(type)]; #else instr.common.instructionType = type; #endif - instr.common.breakPoint = 0; int instructionSize = Instr::size(type); if (_codeEnd - _codeNext < instructionSize) { diff --git a/src/qml/compiler/qv4isel_moth_p.h b/src/qml/compiler/qv4isel_moth_p.h index d8a85ff249..b43868d186 100644 --- a/src/qml/compiler/qv4isel_moth_p.h +++ b/src/qml/compiler/qv4isel_moth_p.h @@ -46,7 +46,7 @@ #include <private/qv4isel_p.h> #include <private/qv4isel_util_p.h> #include <private/qv4jsir_p.h> -#include <private/qv4value_def_p.h> +#include <private/qv4value_p.h> #include "qv4instr_moth_p.h" QT_BEGIN_NAMESPACE @@ -160,7 +160,7 @@ private: int scratchTempIndex() const { return _function->tempCount; } int callDataStart() const { return scratchTempIndex() + 1; } - int outgoingArgumentTempStart() const { return callDataStart() + qOffsetOf(QV4::CallData, args)/sizeof(QV4::SafeValue); } + int outgoingArgumentTempStart() const { return callDataStart() + qOffsetOf(QV4::CallData, args)/sizeof(QV4::Value); } int frameSize() const { return outgoingArgumentTempStart() + _function->maxNumberOfArguments; } template <int Instr> diff --git a/src/qml/compiler/qv4isel_p.h b/src/qml/compiler/qv4isel_p.h index 5ddafc07ab..57222672b9 100644 --- a/src/qml/compiler/qv4isel_p.h +++ b/src/qml/compiler/qv4isel_p.h @@ -72,6 +72,8 @@ public: void setUseFastLookups(bool b) { useFastLookups = b; } int registerString(const QString &str) { return jsGenerator->registerString(str); } + uint registerIndexedGetterLookup() { return jsGenerator->registerIndexedGetterLookup(); } + uint registerIndexedSetterLookup() { return jsGenerator->registerIndexedSetterLookup(); } uint registerGetterLookup(const QString &name) { return jsGenerator->registerGetterLookup(name); } uint registerSetterLookup(const QString &name) { return jsGenerator->registerSetterLookup(name); } uint registerGlobalGetterLookup(const QString &name) { return jsGenerator->registerGlobalGetterLookup(name); } diff --git a/src/qml/compiler/qv4isel_util_p.h b/src/qml/compiler/qv4isel_util_p.h index 38ea682d3b..637746dce1 100644 --- a/src/qml/compiler/qv4isel_util_p.h +++ b/src/qml/compiler/qv4isel_util_p.h @@ -42,7 +42,7 @@ #ifndef QV4ISEL_UTIL_P_H #define QV4ISEL_UTIL_P_H -#include "private/qv4value_def_p.h" +#include "private/qv4value_p.h" #include "qv4jsir_p.h" QT_BEGIN_NAMESPACE @@ -97,38 +97,52 @@ inline QV4::Primitive convertToValue(V4IR::Const *c) class ConvertTemps: protected V4IR::StmtVisitor, protected V4IR::ExprVisitor { - int _nextFreeStackSlot; - QHash<V4IR::Temp, int> _stackSlotForTemp; - void renumber(V4IR::Temp *t) { if (t->kind != V4IR::Temp::VirtualRegister) return; - int stackSlot = _stackSlotForTemp.value(*t, -1); + int stackSlot = _stackSlotForTemp.value(t->index, -1); if (stackSlot == -1) { - stackSlot = _nextFreeStackSlot++; - _stackSlotForTemp[*t] = stackSlot; + stackSlot = allocateFreeSlot(); + _stackSlotForTemp[t->index] = stackSlot; } t->kind = V4IR::Temp::StackSlot; t->index = stackSlot; } +protected: + int _nextUnusedStackSlot; + QHash<int, int> _stackSlotForTemp; + V4IR::BasicBlock *_currentBasicBlock; + virtual int allocateFreeSlot() + { + return _nextUnusedStackSlot++; + } + + virtual void process(V4IR::Stmt *s) + { + s->accept(this); + } + public: ConvertTemps() - : _nextFreeStackSlot(0) + : _nextUnusedStackSlot(0) + , _currentBasicBlock(0) {} void toStackSlots(V4IR::Function *function) { _stackSlotForTemp.reserve(function->tempCount); - foreach (V4IR::BasicBlock *bb, function->basicBlocks) + foreach (V4IR::BasicBlock *bb, function->basicBlocks) { + _currentBasicBlock = bb; foreach (V4IR::Stmt *s, bb->statements) - s->accept(this); + process(s); + } - function->tempCount = _nextFreeStackSlot; + function->tempCount = _nextUnusedStackSlot; } protected: diff --git a/src/qml/compiler/qv4jsir.cpp b/src/qml/compiler/qv4jsir.cpp index deb1af51b4..ea92bb468d 100644 --- a/src/qml/compiler/qv4jsir.cpp +++ b/src/qml/compiler/qv4jsir.cpp @@ -740,7 +740,7 @@ Temp *BasicBlock::LOCAL(unsigned index, unsigned scope) } Expr *BasicBlock::CONST(Type type, double value) -{ +{ Const *e = function->New<Const>(); if (type == NumberType) { int ival = (int)value; @@ -769,7 +769,7 @@ Expr *BasicBlock::REGEXP(const QString *value, int flags) } Name *BasicBlock::NAME(const QString &id, quint32 line, quint32 column) -{ +{ Name *e = function->New<Name>(); e->init(function->newString(id), line, column); return e; @@ -805,7 +805,7 @@ Expr *BasicBlock::CONVERT(Expr *expr, Type type) } Expr *BasicBlock::UNOP(AluOp op, Expr *expr) -{ +{ Unop *e = function->New<Unop>(); e->init(op, expr); return e; @@ -819,7 +819,7 @@ Expr *BasicBlock::BINOP(AluOp op, Expr *left, Expr *right) } Expr *BasicBlock::CALL(Expr *base, ExprList *args) -{ +{ Call *e = function->New<Call>(); e->init(base, args); int argc = 0; @@ -851,7 +851,7 @@ Expr *BasicBlock::MEMBER(Expr *base, const QString *name, QQmlPropertyData *prop } Stmt *BasicBlock::EXP(Expr *expr) -{ +{ if (isTerminated()) return 0; @@ -862,7 +862,7 @@ Stmt *BasicBlock::EXP(Expr *expr) } Stmt *BasicBlock::MOVE(Expr *target, Expr *source) -{ +{ if (isTerminated()) return 0; @@ -872,7 +872,7 @@ Stmt *BasicBlock::MOVE(Expr *target, Expr *source) return s; } -Stmt *BasicBlock::JUMP(BasicBlock *target) +Stmt *BasicBlock::JUMP(BasicBlock *target) { if (isTerminated()) return 0; @@ -890,7 +890,7 @@ Stmt *BasicBlock::JUMP(BasicBlock *target) return s; } -Stmt *BasicBlock::CJUMP(Expr *cond, BasicBlock *iftrue, BasicBlock *iffalse) +Stmt *BasicBlock::CJUMP(Expr *cond, BasicBlock *iftrue, BasicBlock *iffalse) { if (isTerminated()) return 0; diff --git a/src/qml/compiler/qv4regalloc.cpp b/src/qml/compiler/qv4regalloc.cpp index ea2338acad..cc9ce4fc96 100644 --- a/src/qml/compiler/qv4regalloc.cpp +++ b/src/qml/compiler/qv4regalloc.cpp @@ -40,7 +40,7 @@ ****************************************************************************/ #include "qv4regalloc_p.h" -#include <private/qv4value_p.h> +#include <private/qv4value_inl_p.h> #include <algorithm> @@ -60,6 +60,9 @@ struct Use { } QT_BEGIN_NAMESPACE + +Q_DECLARE_TYPEINFO(Use, Q_MOVABLE_TYPE); + using namespace QQmlJS::V4IR; namespace QQmlJS { diff --git a/src/qml/compiler/qv4ssa.cpp b/src/qml/compiler/qv4ssa.cpp index 594c38d109..8f3e186cc7 100644 --- a/src/qml/compiler/qv4ssa.cpp +++ b/src/qml/compiler/qv4ssa.cpp @@ -3552,7 +3552,13 @@ private: for (int i = bb->statements.size() - 1; i >= 0; --i) { Stmt *s = bb->statements[i]; if (Phi *phi = s->asPhi()) { - live.remove(*phi->targetTemp); + LiveRegs::iterator it = live.find(*phi->targetTemp); + if (it == live.end()) { + // a phi node target that is only defined, but never used + _intervals[*phi->targetTemp].setFrom(s); + } else { + live.erase(it); + } continue; } collector.collect(s); @@ -3991,15 +3997,21 @@ void MoveMapping::order() qSwap(_moves, output); } -void MoveMapping::insertMoves(BasicBlock *bb, Function *function, bool atEnd) const +QList<V4IR::Move *> MoveMapping::insertMoves(BasicBlock *bb, Function *function, bool atEnd) const { + QList<V4IR::Move *> newMoves; + newMoves.reserve(_moves.size()); + int insertionPoint = atEnd ? bb->statements.size() - 1 : 0; foreach (const Move &m, _moves) { V4IR::Move *move = function->New<V4IR::Move>(); - move->init(m.to, m.from); + move->init(clone(m.to, function), clone(m.from, function)); move->swap = m.needsSwap; bb->statements.insert(insertionPoint++, move); + newMoves.append(move); } + + return newMoves; } void MoveMapping::dump() const diff --git a/src/qml/compiler/qv4ssa_p.h b/src/qml/compiler/qv4ssa_p.h index 2ec81b9577..95bed40a27 100644 --- a/src/qml/compiler/qv4ssa_p.h +++ b/src/qml/compiler/qv4ssa_p.h @@ -188,7 +188,7 @@ class MoveMapping public: void add(Expr *from, Temp *to); void order(); - void insertMoves(BasicBlock *bb, Function *function, bool atEnd) const; + QList<V4IR::Move *> insertMoves(BasicBlock *bb, Function *function, bool atEnd) const; void dump() const; |