diff options
Diffstat (limited to 'src/qmlcompiler/qqmljstypepropagator.cpp')
-rw-r--r-- | src/qmlcompiler/qqmljstypepropagator.cpp | 1262 |
1 files changed, 850 insertions, 412 deletions
diff --git a/src/qmlcompiler/qqmljstypepropagator.cpp b/src/qmlcompiler/qqmljstypepropagator.cpp index 380042e2fe..d7a7d68d9f 100644 --- a/src/qmlcompiler/qqmljstypepropagator.cpp +++ b/src/qmlcompiler/qqmljstypepropagator.cpp @@ -9,6 +9,8 @@ #include <private/qv4compilerscanfunctions_p.h> +#include <QtQmlCompiler/private/qqmlsasourcelocation_p.h> + QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; @@ -26,20 +28,20 @@ using namespace Qt::StringLiterals; QQmlJSTypePropagator::QQmlJSTypePropagator(const QV4::Compiler::JSUnitGenerator *unitGenerator, const QQmlJSTypeResolver *typeResolver, - QQmlJSLogger *logger, QQmlJSTypeInfo *typeInfo, + QQmlJSLogger *logger, BasicBlocks basicBlocks, + InstructionAnnotations annotations, QQmlSA::PassManager *passManager) - : QQmlJSCompilePass(unitGenerator, typeResolver, logger), - m_typeInfo(typeInfo), + : QQmlJSCompilePass(unitGenerator, typeResolver, logger, basicBlocks, annotations), m_passManager(passManager) { } -QQmlJSCompilePass::InstructionAnnotations QQmlJSTypePropagator::run( +QQmlJSCompilePass::BlocksAndAnnotations QQmlJSTypePropagator::run( const Function *function, QQmlJS::DiagnosticMessage *error) { m_function = function; m_error = error; - m_returnType = m_typeResolver->globalType(m_function->returnType); + m_returnType = m_function->returnType; do { // Reset the error if we need to do another pass @@ -48,6 +50,7 @@ QQmlJSCompilePass::InstructionAnnotations QQmlJSTypePropagator::run( m_prevStateAnnotations = m_state.annotations; m_state = PassState(); + m_state.annotations = m_annotations; m_state.State::operator=(initialState(m_function)); reset(); @@ -58,34 +61,49 @@ QQmlJSCompilePass::InstructionAnnotations QQmlJSTypePropagator::run( // This means that we won't start over for the same reason again. } while (m_state.needsMorePasses); - return m_state.annotations; + return { std::move(m_basicBlocks), std::move(m_state.annotations) }; } -#define INSTR_PROLOGUE_NOT_IMPLEMENTED() \ - setError(u"Instruction \"%1\" not implemented"_s \ - .arg(QString::fromUtf8(__func__))); \ - return; +#define INSTR_PROLOGUE_NOT_IMPLEMENTED() \ + setError(u"Instruction \"%1\" not implemented"_s.arg(QString::fromUtf8(__func__))); \ + return; #define INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE() \ m_logger->log(u"Instruction \"%1\" not implemented"_s.arg(QString::fromUtf8(__func__)), \ qmlCompiler, QQmlJS::SourceLocation()); \ return; +void QQmlJSTypePropagator::generate_ret_SAcheck() +{ + if (!m_function->isProperty) + return; + QQmlSA::PassManagerPrivate::get(m_passManager) + ->analyzeBinding(QQmlJSScope::createQQmlSAElement(m_function->qmlScope), + QQmlJSScope::createQQmlSAElement( + m_typeResolver->containedType(m_state.accumulatorIn())), + QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation( + getCurrentBindingSourceLocation())); +} void QQmlJSTypePropagator::generate_Ret() { - if (m_passManager != nullptr && m_function->isProperty) { - m_passManager->analyzeBinding(m_function->qmlScope, - m_typeResolver->containedType(m_state.accumulatorIn()), - getCurrentBindingSourceLocation()); - } + if (m_passManager != nullptr) + generate_ret_SAcheck(); if (m_function->isSignalHandler) { // Signal handlers cannot return anything. - } else if (!m_returnType.isValid() && m_state.accumulatorIn().isValid() - && !m_typeResolver->registerContains( - m_state.accumulatorIn(), m_typeResolver->voidType())) { - setError(u"function without type annotation returns %1"_s - .arg(m_state.accumulatorIn().descriptiveName())); + } else if (m_typeResolver->registerContains( + m_state.accumulatorIn(), m_typeResolver->voidType())) { + // You can always return undefined. + } else if (!m_returnType.isValid() && m_state.accumulatorIn().isValid()) { + setError(u"function without return type annotation returns %1. This may prevent proper "_s + u"compilation to Cpp."_s.arg(m_state.accumulatorIn().descriptiveName())); + + if (m_function->isFullyTyped) { + // Do not complain if the function didn't have a valid annotation in the first place. + m_logger->log(u"Function without return type annotation returns %1"_s.arg( + m_typeResolver->containedTypeName(m_state.accumulatorIn(), true)), + qmlIncompatibleType, getCurrentBindingSourceLocation()); + } return; } else if (!canConvertFromTo(m_state.accumulatorIn(), m_returnType)) { setError(u"cannot convert from %1 to %2"_s @@ -124,7 +142,7 @@ void QQmlJSTypePropagator::generate_LoadConst(int index) void QQmlJSTypePropagator::generate_LoadZero() { - setAccumulator(m_typeResolver->globalType(m_typeResolver->intType())); + setAccumulator(m_typeResolver->globalType(m_typeResolver->int32Type())); } void QQmlJSTypePropagator::generate_LoadTrue() @@ -149,7 +167,7 @@ void QQmlJSTypePropagator::generate_LoadUndefined() void QQmlJSTypePropagator::generate_LoadInt(int) { - setAccumulator(m_typeResolver->globalType(m_typeResolver->intType())); + setAccumulator(m_typeResolver->globalType(m_typeResolver->int32Type())); } void QQmlJSTypePropagator::generate_MoveConst(int constIndex, int destTemp) @@ -292,7 +310,7 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM return; } - std::optional<FixSuggestion> suggestion; + std::optional<QQmlJSFixSuggestion> suggestion; auto childScopes = m_function->qmlScope->childScopes(); for (qsizetype i = 0; i < m_function->qmlScope->childScopes().size(); i++) { @@ -304,12 +322,9 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM if (scope->childScopes().size() == 0) continue; - const auto jsId = scope->childScopes().first()->findJSIdentifier(name); + const auto jsId = scope->childScopes().first()->jsIdentifier(name); if (jsId.has_value() && jsId->kind == QQmlJSScope::JavaScriptIdentifier::Injected) { - - suggestion = FixSuggestion {}; - const QQmlJSScope::JavaScriptIdentifier id = jsId.value(); QQmlJS::SourceLocation fixLocation = id.location; @@ -328,15 +343,15 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM fixString += handler.isMultiline ? u") "_s : u") => "_s; - suggestion->fixes << FixSuggestion::Fix { - name - + QString::fromLatin1(" is accessible in this scope because " - "you are handling a signal at %1:%2. Use a " - "function instead.\n") - .arg(id.location.startLine) - .arg(id.location.startColumn), - fixLocation, fixString, QString(), false + suggestion = QQmlJSFixSuggestion { + name + u" is accessible in this scope because you are handling a signal" + " at %1:%2. Use a function instead.\n"_s + .arg(id.location.startLine) + .arg(id.location.startColumn), + fixLocation, + fixString }; + suggestion->setAutoApplicable(); } break; } @@ -354,11 +369,10 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM if (!it->hasObject()) continue; if (it->objectType() == m_function->qmlScope) { - suggestion = FixSuggestion {}; - - suggestion->fixes << FixSuggestion::Fix { - name + u" is implicitly injected into this delegate. Add a required property instead."_s, - m_function->qmlScope->sourceLocation(), QString(), QString(), true + suggestion = QQmlJSFixSuggestion { + name + " is implicitly injected into this delegate." + " Add a required property instead."_L1, + m_function->qmlScope->sourceLocation() }; }; @@ -371,38 +385,36 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM for (QQmlJSScope::ConstPtr scope = m_function->qmlScope; !scope.isNull(); scope = scope->parentScope()) { if (scope->hasProperty(name)) { - const QString id = m_function->addressableScopes.id(scope); - - suggestion = FixSuggestion {}; + const QString id = m_function->addressableScopes.id(scope, m_function->qmlScope); QQmlJS::SourceLocation fixLocation = location; fixLocation.length = 0; - suggestion->fixes << FixSuggestion::Fix { - name + QLatin1String(" is a member of a parent element\n") - + QLatin1String(" You can qualify the access with its id " - "to avoid this warning:\n"), - fixLocation, (id.isEmpty() ? u"<id>."_s : (id + u'.')), QString(), id.isEmpty() + suggestion = QQmlJSFixSuggestion{ + name + + " is a member of a parent element.\n You can qualify the access " + "with its id to avoid this warning.\n"_L1, + fixLocation, (id.isEmpty() ? u"<id>."_s : (id + u'.')) }; - if (id.isEmpty()) { - suggestion->fixes << FixSuggestion::Fix { - u"You first have to give the element an id"_s, QQmlJS::SourceLocation {}, {} - }; - } + if (id.isEmpty()) + suggestion->setHint("You first have to give the element an id"_L1); + else + suggestion->setAutoApplicable(); } } } if (!suggestion.has_value() && !m_function->addressableScopes.componentsAreBound() && m_function->addressableScopes.existsAnywhereInDocument(name)) { - FixSuggestion::Fix bindComponents; const QLatin1String replacement = "pragma ComponentBehavior: Bound"_L1; - bindComponents.replacementString = replacement + '\n'_L1; - bindComponents.message = "Set \"%1\" in order to use IDs " - "from outer components in nested components."_L1.arg(replacement); - bindComponents.cutLocation = QQmlJS::SourceLocation(0, 0, 1, 1); - bindComponents.isHint = false; - suggestion = FixSuggestion {{ bindComponents }}; + QQmlJSFixSuggestion bindComponents { + "Set \"%1\" in order to use IDs from outer components in nested components."_L1 + .arg(replacement), + QQmlJS::SourceLocation(0, 0, 1, 1), + replacement + '\n'_L1 + }; + bindComponents.setAutoApplicable(); + suggestion = bindComponents; } if (!suggestion.has_value()) { @@ -468,52 +480,6 @@ void QQmlJSTypePropagator::checkDeprecated(QQmlJSScope::ConstPtr scope, const QS m_logger->log(message, qmlDeprecated, getCurrentSourceLocation()); } -bool QQmlJSTypePropagator::isRestricted(const QString &propertyName) const -{ - QString restrictedKind; - - const auto accumulatorIn = m_state.registers.find(Accumulator); - if (accumulatorIn == m_state.registers.end()) - return false; - - if (accumulatorIn.value().content.isList() && propertyName != u"length") { - restrictedKind = u"a list"_s; - } else if (accumulatorIn.value().content.isEnumeration()) { - const auto metaEn = accumulatorIn.value().content.enumeration(); - if (metaEn.isScoped()) { - if (!metaEn.hasKey(propertyName)) - restrictedKind = u"an enum"_s; - } else { - restrictedKind = u"an unscoped enum"_s; - } - } else if (accumulatorIn.value().content.isMethod()) { - auto overloadSet = accumulatorIn.value().content.method(); - auto potentiallyJSMethod = std::any_of( - overloadSet.cbegin(), overloadSet.cend(), - [](const QQmlJSMetaMethod &overload){ - return overload.isJavaScriptFunction(); - }); - if (potentiallyJSMethod) { - /* JS global constructors like Number get detected as methods - However, they still have properties that can be accessed - e.g. Number.EPSILON. This also isn't restricted to constructor - functions, so use isJavaScriptFunction as an overapproximation. - That catches also QQmlV4Function, but we're purging uses of it - anyway. - */ - return false; - } - restrictedKind = u"a method"_s; - } - - if (!restrictedKind.isEmpty()) - m_logger->log(u"Type is %1. You cannot access \"%2\" from here."_s.arg(restrictedKind, - propertyName), - qmlRestrictedType, getCurrentSourceLocation()); - - return !restrictedKind.isEmpty(); -} - // Only to be called once a lookup has already failed QQmlJSTypePropagator::PropertyResolution QQmlJSTypePropagator::propertyResolution( QQmlJSScope::ConstPtr scope, const QString &propertyName) const @@ -554,15 +520,17 @@ bool QQmlJSTypePropagator::isCallingProperty(QQmlJSScope::ConstPtr scope, const if (!methods.isEmpty()) { errorType = u"shadowed by a property."_s; switch (methods.first().methodType()) { - case QQmlJSMetaMethod::Signal: + case QQmlJSMetaMethodType::Signal: propertyType = u"Signal"_s; break; - case QQmlJSMetaMethod::Slot: + case QQmlJSMetaMethodType::Slot: propertyType = u"Slot"_s; break; - case QQmlJSMetaMethod::Method: + case QQmlJSMetaMethodType::Method: propertyType = u"Method"_s; break; + default: + Q_UNREACHABLE(); } } else if (m_typeResolver->equals(property.type(), m_typeResolver->varType())) { errorType = @@ -580,6 +548,17 @@ bool QQmlJSTypePropagator::isCallingProperty(QQmlJSScope::ConstPtr scope, const return true; } + +void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup_SAcheck(const QString &name) +{ + QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead( + QQmlJSScope::createQQmlSAElement(m_function->qmlScope), name, + QQmlJSScope::createQQmlSAElement(m_function->qmlScope), + QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation( + getCurrentBindingSourceLocation())); +} + + void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index) { // LoadQmlContextPropertyLookup does not use accumulatorIn. It always refers to the scope. @@ -588,7 +567,7 @@ void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index) const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index); const QString name = m_jsUnitGenerator->stringForIndex(nameIndex); - setAccumulator(m_typeResolver->scopedType(m_function->qmlScope, name)); + setAccumulator(m_typeResolver->scopedType(m_function->qmlScope, name, index)); if (!m_state.accumulatorOut().isValid() && m_typeResolver->isPrefix(name)) { const QQmlJSRegisterContent inType = m_typeResolver->globalType(m_function->qmlScope); @@ -603,36 +582,77 @@ void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index) if (!m_state.accumulatorOut().isValid()) { setError(u"Cannot access value for name "_s + name); handleUnqualifiedAccess(name, false); - } else if (m_typeResolver->genericType(m_state.accumulatorOut().storedType()).isNull()) { + return; + } + + const QQmlJSScope::ConstPtr outStored + = m_typeResolver->genericType(m_state.accumulatorOut().storedType()); + + if (outStored.isNull()) { // It should really be valid. // We get the generic type from aotContext->loadQmlContextPropertyIdLookup(). setError(u"Cannot determine generic type for "_s + name); - } else if (m_passManager != nullptr) { - m_passManager->analyzeRead(m_function->qmlScope, name, m_function->qmlScope, - getCurrentSourceLocation()); + return; + } + + if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ObjectById + && !outStored->isReferenceType()) { + setError(u"Cannot retrieve a non-object type by ID: "_s + name); + return; } + if (m_passManager != nullptr) + generate_LoadQmlContextPropertyLookup_SAcheck(name); + if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ScopeAttached) m_attachedContext = QQmlJSScope::ConstPtr(); } -void QQmlJSTypePropagator::generate_StoreNameSloppy(int nameIndex) +void QQmlJSTypePropagator::generate_StoreNameCommon_SAcheck(const QQmlJSRegisterContent &in, const QString &name) +{ + QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite( + QQmlJSScope::createQQmlSAElement(m_function->qmlScope), name, + QQmlJSScope::createQQmlSAElement(m_typeResolver->containedType(in)), + QQmlJSScope::createQQmlSAElement(m_function->qmlScope), + QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation( + getCurrentBindingSourceLocation())); +} + +/*! + \internal + As far as type propagation is involved, StoreNameSloppy and + StoreNameStrict are completely the same + StoreNameStrict is rejecting a few writes (where the variable was not + defined before) that would work in a sloppy context in JS, but the + compiler would always reject this. And for type propagation, this does + not matter at all. + \a nameIndex is the index in the string table corresponding to + the name which we are storing + */ +void QQmlJSTypePropagator::generate_StoreNameCommon(int nameIndex) { const QString name = m_jsUnitGenerator->stringForIndex(nameIndex); const QQmlJSRegisterContent type = m_typeResolver->scopedType(m_function->qmlScope, name); const QQmlJSRegisterContent in = m_state.accumulatorIn(); if (!type.isValid()) { + handleUnqualifiedAccess(name, false); setError(u"Cannot find name "_s + name); return; } if (!type.isProperty()) { + QString message = type.isMethod() ? u"Cannot assign to method %1"_s + : u"Cannot assign to non-property %1"_s; + // The interpreter treats methods as read-only properties in its error messages + // and we lack a better fitting category. We might want to revisit this later. + m_logger->log(message.arg(name), qmlReadOnlyProperty, + getCurrentSourceLocation()); setError(u"Cannot assign to non-property "_s + name); return; } - if (!type.isWritable() && !m_function->qmlScope->hasOwnProperty(name)) { + if (!type.isWritable()) { setError(u"Can't assign to read-only property %1"_s.arg(name)); m_logger->log(u"Cannot assign to read-only property %1"_s.arg(name), qmlReadOnlyProperty, @@ -646,53 +666,95 @@ void QQmlJSTypePropagator::generate_StoreNameSloppy(int nameIndex) .arg(in.descriptiveName(), type.descriptiveName())); } - if (m_passManager != nullptr) { - m_passManager->analyzeWrite(m_function->qmlScope, name, - m_typeResolver->containedType(in), - m_function->qmlScope, getCurrentSourceLocation()); - } + if (m_passManager != nullptr) + generate_StoreNameCommon_SAcheck(in, name); - m_state.setHasSideEffects(true); if (m_typeResolver->canHoldUndefined(in) && !m_typeResolver->canHoldUndefined(type)) { - if (type.property().reset().isEmpty()) - setError(u"Cannot assign potential undefined to %1"_s.arg(type.descriptiveName())); - else if (m_typeResolver->registerIsStoredIn(in, m_typeResolver->voidType())) + if (m_typeResolver->registerIsStoredIn(in, m_typeResolver->voidType())) addReadAccumulator(m_typeResolver->globalType(m_typeResolver->varType())); else addReadAccumulator(in); } else { addReadAccumulator(type); } + + m_state.setHasSideEffects(true); +} + +void QQmlJSTypePropagator::generate_StoreNameSloppy(int nameIndex) +{ + return generate_StoreNameCommon(nameIndex); } void QQmlJSTypePropagator::generate_StoreNameStrict(int name) { - m_state.setHasSideEffects(true); - Q_UNUSED(name) - INSTR_PROLOGUE_NOT_IMPLEMENTED(); + return generate_StoreNameCommon(name); } -void QQmlJSTypePropagator::generate_LoadElement(int base) +bool QQmlJSTypePropagator::checkForEnumProblems( + const QQmlJSRegisterContent &base, const QString &propertyName) { - const QQmlJSRegisterContent baseRegister = m_state.registers[base].content; + if (base.isEnumeration()) { + const auto metaEn = base.enumeration(); + if (!metaEn.hasKey(propertyName)) { + auto fixSuggestion = QQmlJSUtils::didYouMean(propertyName, metaEn.keys(), + getCurrentSourceLocation()); + const QString error = u"\"%1\" is not an entry of enum \"%2\"."_s + .arg(propertyName, metaEn.name()); + setError(error); + m_logger->log( + error, qmlMissingEnumEntry, getCurrentSourceLocation(), true, true, + fixSuggestion); + return true; + } + } else if (base.variant() == QQmlJSRegisterContent::MetaType) { + const QQmlJSMetaEnum metaEn = base.scopeType()->enumeration(propertyName); + if (metaEn.isValid() && !metaEn.isScoped() && !metaEn.isQml()) { + const QString error + = u"You cannot access unscoped enum \"%1\" from here."_s.arg(propertyName); + setError(error); + m_logger->log(error, qmlRestrictedType, getCurrentSourceLocation()); + return true; + } + } - if ((baseRegister.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence - && !m_typeResolver->registerIsStoredIn(baseRegister, m_typeResolver->stringType())) - || !m_typeResolver->isNumeric(m_state.accumulatorIn())) { + return false; +} + +void QQmlJSTypePropagator::generate_LoadElement(int base) +{ + const auto fallback = [&]() { const auto jsValue = m_typeResolver->globalType(m_typeResolver->jsValueType()); addReadAccumulator(jsValue); addReadRegister(base, jsValue); setAccumulator(jsValue); + }; + + const QQmlJSRegisterContent baseRegister = m_state.registers[base].content; + if (!baseRegister.isList() + && !m_typeResolver->registerContains(baseRegister, m_typeResolver->stringType())) { + fallback(); return; } + addReadRegister(base, baseRegister); - if (m_typeResolver->isIntegral(m_state.accumulatorIn())) - addReadAccumulator(m_typeResolver->globalType(m_typeResolver->intType())); - else - addReadAccumulator(m_typeResolver->globalType(m_typeResolver->realType())); + if (m_typeResolver->isNumeric(m_state.accumulatorIn())) { + const auto contained = m_typeResolver->containedType(m_state.accumulatorIn()); + if (m_typeResolver->isSignedInteger(contained)) + addReadAccumulator(m_typeResolver->globalType(m_typeResolver->sizeType())); + else if (m_typeResolver->isUnsignedInteger(contained)) + addReadAccumulator(m_typeResolver->globalType(m_typeResolver->uint32Type())); + else + addReadAccumulator(m_typeResolver->globalType(m_typeResolver->realType())); + } else if (m_typeResolver->isNumeric(m_typeResolver->extractNonVoidFromOptionalType( + m_state.accumulatorIn()))) { + addReadAccumulator(m_state.accumulatorIn()); + } else { + fallback(); + return; + } - addReadRegister(base, baseRegister); // We can end up with undefined. setAccumulator(m_typeResolver->merge( m_typeResolver->valueType(baseRegister), @@ -704,7 +766,7 @@ void QQmlJSTypePropagator::generate_StoreElement(int base, int index) const QQmlJSRegisterContent baseRegister = m_state.registers[base].content; const QQmlJSRegisterContent indexRegister = checkedInputRegister(index); - if (baseRegister.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence + if (!baseRegister.isList() || !m_typeResolver->isNumeric(indexRegister)) { const auto jsValue = m_typeResolver->globalType(m_typeResolver->jsValueType()); addReadAccumulator(jsValue); @@ -717,8 +779,11 @@ void QQmlJSTypePropagator::generate_StoreElement(int base, int index) return; } - if (m_typeResolver->isIntegral(indexRegister)) - addReadRegister(index, m_typeResolver->globalType(m_typeResolver->intType())); + const auto contained = m_typeResolver->containedType(indexRegister); + if (m_typeResolver->isSignedInteger(contained)) + addReadRegister(index, m_typeResolver->globalType(m_typeResolver->int32Type())); + else if (m_typeResolver->isUnsignedInteger(contained)) + addReadRegister(index, m_typeResolver->globalType(m_typeResolver->uint32Type())); else addReadRegister(index, m_typeResolver->globalType(m_typeResolver->realType())); @@ -733,7 +798,22 @@ void QQmlJSTypePropagator::generate_StoreElement(int base, int index) m_state.setHasSideEffects(true); } -void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName) +void QQmlJSTypePropagator::propagatePropertyLookup_SAcheck(const QString &propertyName) +{ + const bool isAttached = + m_state.accumulatorIn().variant() == QQmlJSRegisterContent::ObjectAttached; + + QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead( + QQmlJSScope::createQQmlSAElement( + m_typeResolver->containedType(m_state.accumulatorIn())), + propertyName, + QQmlJSScope::createQQmlSAElement(isAttached ? m_attachedContext + : m_function->qmlScope), + QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation( + getCurrentBindingSourceLocation())); +} + +void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName, int lookupIndex) { setAccumulator( m_typeResolver->memberType( @@ -741,52 +821,7 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName) m_state.accumulatorIn().isImportNamespace() ? m_jsUnitGenerator->stringForIndex(m_state.accumulatorIn().importNamespace()) + u'.' + propertyName - : propertyName)); - - if (m_typeInfo != nullptr - && m_state.accumulatorIn().variant() == QQmlJSRegisterContent::ScopeAttached) { - QQmlJSScope::ConstPtr attachedType = m_typeResolver->originalType( - m_state.accumulatorIn().scopeType()); - - for (QQmlJSScope::ConstPtr scope = m_function->qmlScope->parentScope(); !scope.isNull(); - scope = scope->parentScope()) { - if (m_typeInfo->usedAttachedTypes.values(scope).contains(attachedType)) { - - // Ignore enum accesses, as these will not cause the attached object to be created - if (m_state.accumulatorOut().isValid() && m_state.accumulatorOut().isEnumeration()) - continue; - - const QString id = m_function->addressableScopes.id(scope); - - FixSuggestion suggestion; - - QQmlJS::SourceLocation fixLocation = getCurrentSourceLocation(); - fixLocation.length = 0; - - suggestion.fixes << FixSuggestion::Fix { u"Reference it by id instead:"_s, - fixLocation, - id.isEmpty() ? u"<id>."_s : (id + u'.'), - QString(), id.isEmpty() }; - - fixLocation = scope->sourceLocation(); - fixLocation.length = 0; - - if (id.isEmpty()) { - suggestion.fixes - << FixSuggestion::Fix { u"You first have to give the element an id"_s, - QQmlJS::SourceLocation {}, - {} }; - } - - m_logger->log( - u"Using attached type %1 already initialized in a parent scope."_s.arg( - m_state.accumulatorIn().scopeType()->internalName()), - qmlAttachedPropertyReuse, getCurrentSourceLocation(), true, true, - suggestion); - } - } - m_typeInfo->usedAttachedTypes.insert(m_function->qmlScope, attachedType); - } + : propertyName, lookupIndex)); if (!m_state.accumulatorOut().isValid()) { if (m_typeResolver->isPrefix(propertyName)) { @@ -808,17 +843,29 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName) u"Cannot access singleton as a property of an object. Did you want to access an attached object?"_s, qmlAccessSingleton, getCurrentSourceLocation()); setAccumulator(QQmlJSRegisterContent()); + } else if (m_state.accumulatorOut().isEnumeration()) { + switch (m_state.accumulatorIn().variant()) { + case QQmlJSRegisterContent::ExtensionObjectEnum: + case QQmlJSRegisterContent::MetaType: + case QQmlJSRegisterContent::ObjectAttached: + case QQmlJSRegisterContent::ObjectEnum: + case QQmlJSRegisterContent::ObjectModulePrefix: + case QQmlJSRegisterContent::ScopeAttached: + case QQmlJSRegisterContent::ScopeModulePrefix: + case QQmlJSRegisterContent::Singleton: + break; // OK, can look up enums on that thing + default: + setAccumulator(QQmlJSRegisterContent()); + } } - const bool isRestrictedProperty = isRestricted(propertyName); - if (!m_state.accumulatorOut().isValid()) { + if (checkForEnumProblems(m_state.accumulatorIn(), propertyName)) + return; + setError(u"Cannot load property %1 from %2."_s .arg(propertyName, m_state.accumulatorIn().descriptiveName())); - if (isRestrictedProperty) - return; - const QString typeName = m_typeResolver->containedTypeName(m_state.accumulatorIn(), true); if (typeName == u"QVariant") @@ -832,7 +879,10 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName) if (propertyResolution(baseType, propertyName) != PropertyMissing) return; - std::optional<FixSuggestion> fixSuggestion; + if (baseType->isScript()) + return; + + std::optional<QQmlJSFixSuggestion> fixSuggestion; if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->properties().keys(), getCurrentSourceLocation()); @@ -854,7 +904,7 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName) } } - m_logger->log(u"Property \"%1\" not found on type \"%2\""_s.arg(propertyName).arg(typeName), + m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(propertyName).arg(typeName), qmlMissingProperty, getCurrentSourceLocation(), true, true, fixSuggestion); return; } @@ -865,6 +915,22 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName) } if (m_state.accumulatorOut().isProperty()) { + const QQmlJSScope::ConstPtr mathObject + = m_typeResolver->jsGlobalObject()->property(u"Math"_s).type(); + if (m_typeResolver->registerContains(m_state.accumulatorIn(), mathObject)) { + QQmlJSMetaProperty prop; + prop.setPropertyName(propertyName); + prop.setTypeName(u"double"_s); + prop.setType(m_typeResolver->realType()); + setAccumulator( + QQmlJSRegisterContent::create( + m_typeResolver->realType(), prop, m_state.accumulatorIn().resultLookupIndex(), lookupIndex, + QQmlJSRegisterContent::GenericObjectProperty, mathObject) + ); + + return; + } + if (m_typeResolver->registerContains( m_state.accumulatorOut(), m_typeResolver->voidType())) { setError(u"Type %1 does not have a property %2 for reading"_s @@ -879,14 +945,8 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName) } } - if (m_passManager != nullptr) { - const bool isAttached = - m_state.accumulatorIn().variant() == QQmlJSRegisterContent::ObjectAttached; - - m_passManager->analyzeRead( - m_typeResolver->containedType(m_state.accumulatorIn()), propertyName, - isAttached ? m_attachedContext : m_function->qmlScope, getCurrentSourceLocation()); - } + if (m_passManager != nullptr) + propagatePropertyLookup_SAcheck(propertyName); if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ObjectAttached) m_attachedContext = m_typeResolver->containedType(m_state.accumulatorIn()); @@ -920,14 +980,29 @@ void QQmlJSTypePropagator::generate_LoadOptionalProperty(int name, int offset) void QQmlJSTypePropagator::generate_GetLookup(int index) { - propagatePropertyLookup(m_jsUnitGenerator->lookupName(index)); + propagatePropertyLookup(m_jsUnitGenerator->lookupName(index), index); } void QQmlJSTypePropagator::generate_GetOptionalLookup(int index, int offset) { - Q_UNUSED(index); Q_UNUSED(offset); - INSTR_PROLOGUE_NOT_IMPLEMENTED(); + saveRegisterStateForJump(offset); + propagatePropertyLookup(m_jsUnitGenerator->lookupName(index), index); +} + +void QQmlJSTypePropagator::generate_StoreProperty_SAcheck(const QString propertyName, const QQmlJSRegisterContent &callBase) +{ + const bool isAttached = callBase.variant() == QQmlJSRegisterContent::ObjectAttached; + + QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite( + QQmlJSScope::createQQmlSAElement(m_typeResolver->containedType(callBase)), + propertyName, + QQmlJSScope::createQQmlSAElement( + m_typeResolver->containedType(m_state.accumulatorIn())), + QQmlJSScope::createQQmlSAElement(isAttached ? m_attachedContext + : m_function->qmlScope), + QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation( + getCurrentBindingSourceLocation())); } void QQmlJSTypePropagator::generate_StoreProperty(int nameIndex, int base) @@ -942,7 +1017,13 @@ void QQmlJSTypePropagator::generate_StoreProperty(int nameIndex, int base) return; } - if (!property.isWritable()) { + if (property.storedType().isNull()) { + setError(u"Cannot determine type for property %1 of type %2"_s.arg( + propertyName, callBase.descriptiveName())); + return; + } + + if (!property.isWritable() && !property.storedType()->isListProperty()) { setError(u"Can't assign to read-only property %1"_s.arg(propertyName)); m_logger->log(u"Cannot assign to read-only property %1"_s.arg(propertyName), @@ -957,18 +1038,25 @@ void QQmlJSTypePropagator::generate_StoreProperty(int nameIndex, int base) return; } - if (m_passManager != nullptr) { - const bool isAttached = callBase.variant() == QQmlJSRegisterContent::ObjectAttached; + if (m_passManager != nullptr) + generate_StoreProperty_SAcheck(propertyName, callBase); - m_passManager->analyzeWrite(m_typeResolver->containedType(callBase), propertyName, - m_typeResolver->containedType(m_state.accumulatorIn()), - isAttached ? m_attachedContext : m_function->qmlScope, - getCurrentSourceLocation()); - } + // If the input can hold undefined we must not coerce it to the property type + // as that might eliminate an undefined value. For example, undefined -> string + // becomes "undefined". + // We need the undefined value for either resetting the property if that is supported + // or generating an exception otherwise. Therefore we explicitly require the value to + // be given as QVariant. This triggers the QVariant fallback path that's also used for + // shadowable properties. QVariant can hold undefined and the lookup functions will + // handle that appropriately. - m_state.setHasSideEffects(true); - addReadAccumulator(property); + const QQmlJSScope::ConstPtr varType = m_typeResolver->varType(); + const QQmlJSRegisterContent readType = m_typeResolver->canHoldUndefined(m_state.accumulatorIn()) + ? property.storedIn(varType).castTo(varType) + : std::move(property); + addReadAccumulator(readType); addReadRegister(base, callBase); + m_state.setHasSideEffects(true); } void QQmlJSTypePropagator::generate_SetLookup(int index, int base) @@ -1028,88 +1116,123 @@ static bool isLoggingMethod(const QString &consoleMethod) || consoleMethod == u"warn" || consoleMethod == u"error"; } -void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int argc, int argv) +void QQmlJSTypePropagator::generate_CallProperty_SCMath(int base, int argc, int argv) { - Q_ASSERT(m_state.registers.contains(base)); - const auto callBase = m_state.registers[base].content; - const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex); + // If we call a method on the Math object we don't need the actual Math object. We do need + // to transfer the type information to the code generator so that it knows that this is the + // Math object. Read the base register as void. void isn't stored, and the place where it's + // created will be optimized out if there are no other readers. The code generator can + // retrieve the original type and determine that it was the Math object. - if (m_typeResolver->registerContains( - callBase, m_typeResolver->jsGlobalObject()->property(u"Math"_s).type())) { + addReadRegister(base, m_typeResolver->globalType(m_typeResolver->voidType())); - // If we call a method on the Math object we don't need the actual Math object. We do need - // to transfer the type information to the code generator so that it knows that this is the - // Math object. Read the base register as void. void isn't stored, and the place where it's - // created will be optimized out if there are no other readers. The code generator can - // retrieve the original type and determine that it was the Math object. - addReadRegister(base, m_typeResolver->globalType(m_typeResolver->voidType())); + QQmlJSRegisterContent realType = m_typeResolver->returnType( + m_typeResolver->realType(), QQmlJSRegisterContent::MethodReturnValue, + m_typeResolver->mathObject()); + for (int i = 0; i < argc; ++i) + addReadRegister(argv + i, realType); + setAccumulator(realType); +} - QQmlJSRegisterContent realType = m_typeResolver->globalType(m_typeResolver->realType()); - for (int i = 0; i < argc; ++i) - addReadRegister(argv + i, realType); - setAccumulator(realType); - return; - } +void QQmlJSTypePropagator::generate_CallProperty_SCconsole(int base, int argc, int argv) +{ + const QQmlJSRegisterContent voidType + = m_typeResolver->globalType(m_typeResolver->voidType()); - if (m_typeResolver->registerContains( - callBase, m_typeResolver->jsGlobalObject()->property(u"console"_s).type()) - && isLoggingMethod(propertyName)) { + // If we call a method on the console object we don't need the console object. + addReadRegister(base, voidType); - const QQmlJSRegisterContent voidType - = m_typeResolver->globalType(m_typeResolver->voidType()); + const QQmlJSRegisterContent stringType + = m_typeResolver->globalType(m_typeResolver->stringType()); - // If we call a method on the console object we don't need the console object. - addReadRegister(base, voidType); + if (argc > 0) { + const QQmlJSRegisterContent firstContent = m_state.registers[argv].content; + const QQmlJSScope::ConstPtr firstArg = m_typeResolver->containedType(firstContent); + switch (firstArg->accessSemantics()) { + case QQmlJSScope::AccessSemantics::Reference: + // We cannot know whether this will be a logging category at run time. + // Therefore we always pass any object types as special last argument. + addReadRegister(argv, m_typeResolver->globalType( + m_typeResolver->genericType(firstArg))); + break; + case QQmlJSScope::AccessSemantics::Sequence: + addReadRegister(argv, firstContent); + break; + default: + addReadRegister(argv, stringType); + break; + } + } - const QQmlJSRegisterContent stringType - = m_typeResolver->globalType(m_typeResolver->stringType()); + for (int i = 1; i < argc; ++i) { + const QQmlJSRegisterContent argContent = m_state.registers[argv + i].content; + const QQmlJSScope::ConstPtr arg = m_typeResolver->containedType(argContent); + addReadRegister( + argv + i, + arg->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence + ? argContent + : stringType); + } - if (argc > 0) { - const QQmlJSScope::ConstPtr firstArg - = m_typeResolver->containedType(m_state.registers[argv].content); - if (firstArg->isReferenceType()) { - // We cannot know whether this will be a logging category at run time. - // Therefore we always pass any object types as special last argument. - addReadRegister(argv, m_typeResolver->globalType( - m_typeResolver->genericType(firstArg))); - } else { - addReadRegister(argv, stringType); - } - } + m_state.setHasSideEffects(true); + setAccumulator(m_typeResolver->returnType( + m_typeResolver->voidType(), QQmlJSRegisterContent::MethodReturnValue, + m_typeResolver->consoleObject())); +} - for (int i = 1; i < argc; ++i) - addReadRegister(argv + i, stringType); +void QQmlJSTypePropagator::generate_callProperty_SAcheck(const QString propertyName, const QQmlJSScope::ConstPtr &baseType) +{ + // TODO: Should there be an analyzeCall() in the future? (w. corresponding onCall in Pass) + QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead( + QQmlJSScope::createQQmlSAElement(baseType), propertyName, + QQmlJSScope::createQQmlSAElement(m_function->qmlScope), + QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation( + getCurrentBindingSourceLocation())); +} - m_state.setHasSideEffects(true); - setAccumulator(voidType); +void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int argc, int argv) +{ + Q_ASSERT(m_state.registers.contains(base)); + const auto callBase = m_state.registers[base].content; + const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex); + + if (m_typeResolver->registerContains(callBase, m_typeResolver->mathObject())) { + generate_CallProperty_SCMath(base, argc, argv); return; } - if (m_typeResolver->registerContains(callBase, m_typeResolver->jsValueType()) - || m_typeResolver->registerContains(callBase, m_typeResolver->varType())) { - const auto jsValueType = m_typeResolver->globalType(m_typeResolver->jsValueType()); - addReadRegister(base, jsValueType); - for (int i = 0; i < argc; ++i) - addReadRegister(argv + i, jsValueType); - setAccumulator(jsValueType); - m_state.setHasSideEffects(true); + if (m_typeResolver->registerContains(callBase, m_typeResolver->consoleObject()) && isLoggingMethod(propertyName)) { + generate_CallProperty_SCconsole(base, argc, argv); return; } + const auto baseType = m_typeResolver->containedType(callBase); const auto member = m_typeResolver->memberType(callBase, propertyName); + if (!member.isMethod()) { + if (m_typeResolver->registerContains(callBase, m_typeResolver->jsValueType()) + || m_typeResolver->registerContains(callBase, m_typeResolver->varType())) { + const auto jsValueType = m_typeResolver->globalType(m_typeResolver->jsValueType()); + addReadRegister(base, jsValueType); + for (int i = 0; i < argc; ++i) + addReadRegister(argv + i, jsValueType); + m_state.setHasSideEffects(true); + setAccumulator(m_typeResolver->returnType( + m_typeResolver->jsValueType(), QQmlJSRegisterContent::JavaScriptReturnValue, + m_typeResolver->jsValueType())); + return; + } + setError(u"Type %1 does not have a property %2 for calling"_s .arg(callBase.descriptiveName(), propertyName)); if (callBase.isType() && isCallingProperty(callBase.type(), propertyName)) return; - if (isRestricted(propertyName)) + if (checkForEnumProblems(callBase, propertyName)) return; - std::optional<FixSuggestion> fixSuggestion; - - const auto baseType = m_typeResolver->containedType(callBase); + std::optional<QQmlJSFixSuggestion> fixSuggestion; if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->methods().keys(), getCurrentSourceLocation()); @@ -1117,20 +1240,16 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar fixSuggestion = suggestion; } - m_logger->log(u"Property \"%1\" not found on type \"%2\""_s.arg( + m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg( propertyName, m_typeResolver->containedTypeName(callBase, true)), qmlMissingProperty, getCurrentSourceLocation(), true, true, fixSuggestion); return; } - checkDeprecated(m_typeResolver->containedType(callBase), propertyName, true); + checkDeprecated(baseType, propertyName, true); - if (m_passManager != nullptr) { - // TODO: Should there be an analyzeCall() in the future? (w. corresponding onCall in Pass) - m_passManager->analyzeRead( - m_typeResolver->containedType(callBase), - propertyName, m_function->qmlScope, getCurrentSourceLocation()); - } + if (m_passManager != nullptr) + generate_callProperty_SAcheck(propertyName, baseType); addReadRegister(base, callBase); @@ -1141,6 +1260,12 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar } } + if (baseType->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence + && m_typeResolver->equals(member.scopeType(), m_typeResolver->arrayPrototype()) + && propagateArrayMethod(propertyName, argc, argv, callBase)) { + return; + } + propagateCall(member.method(), argc, argv, member.scopeType()); } @@ -1148,10 +1273,13 @@ QQmlJSMetaMethod QQmlJSTypePropagator::bestMatchForCall(const QList<QQmlJSMetaMe int argc, int argv, QStringList *errors) { QQmlJSMetaMethod javascriptFunction; + QQmlJSMetaMethod candidate; + bool hasMultipleCandidates = false; + for (const auto &method : methods) { // If we encounter a JavaScript function, use this as a fallback if no other method matches - if (method.isJavaScriptFunction()) + if (method.isJavaScriptFunction() && !javascriptFunction.isValid()) javascriptFunction = method; if (method.returnType().isNull() && !method.returnTypeName().isEmpty()) { @@ -1168,33 +1296,55 @@ QQmlJSMetaMethod QQmlJSTypePropagator::bestMatchForCall(const QList<QQmlJSMetaMe continue; } - bool matches = true; + bool fuzzyMatch = true; + bool exactMatch = true; for (int i = 0; i < argc; ++i) { const auto argumentType = arguments[i].type(); if (argumentType.isNull()) { errors->append( u"type %1 for argument %2 cannot be resolved"_s.arg(arguments[i].typeName()) .arg(i)); - matches = false; + exactMatch = false; + fuzzyMatch = false; break; } - if (canConvertFromTo(m_state.registers[argv + i].content, - m_typeResolver->globalType(argumentType))) { + const auto content = m_state.registers[argv + i].content; + if (m_typeResolver->registerContains(content, argumentType)) + continue; + + exactMatch = false; + if (canConvertFromTo(content, m_typeResolver->globalType(argumentType))) + continue; + + // We can try to call a method that expects a derived type. + if (argumentType->isReferenceType() + && m_typeResolver->inherits( + argumentType->baseType(), m_typeResolver->containedType(content))) { continue; } errors->append( u"argument %1 contains %2 but is expected to contain the type %3"_s.arg(i).arg( - m_state.registers[argv + i].content.descriptiveName(), - arguments[i].typeName())); - matches = false; + content.descriptiveName(), arguments[i].typeName())); + fuzzyMatch = false; break; } - if (matches) + + if (exactMatch) { return method; + } else if (fuzzyMatch) { + if (!candidate.isValid()) + candidate = method; + else + hasMultipleCandidates = true; + } } - return javascriptFunction; + + if (hasMultipleCandidates) + return QQmlJSMetaMethod(); + + return candidate.isValid() ? candidate : javascriptFunction; } void QQmlJSTypePropagator::setAccumulator(const QQmlJSRegisterContent &content) @@ -1221,9 +1371,14 @@ void QQmlJSTypePropagator::mergeRegister( int index, const QQmlJSRegisterContent &a, const QQmlJSRegisterContent &b) { auto merged = m_typeResolver->merge(a, b); - Q_ASSERT(merged.isValid()); - Q_ASSERT(merged.isConversion()); + + if (!merged.isConversion()) { + // The registers were the same. We're already tracking them. + m_state.annotations[currentInstructionOffset()].typeConversions[index].content = merged; + m_state.registers[index].content = merged; + return; + } auto tryPrevStateConversion = [this](int index, const QQmlJSRegisterContent &merged) -> bool { auto it = m_prevStateAnnotations.find(currentInstructionOffset()); @@ -1234,19 +1389,20 @@ void QQmlJSTypePropagator::mergeRegister( if (conversion == it->second.typeConversions.end()) return false; - const QQmlJSRegisterContent &lastTry = conversion.value().content; + const VirtualRegister &lastTry = conversion.value(); - Q_ASSERT(lastTry.isValid()); - Q_ASSERT(lastTry.isConversion()); + Q_ASSERT(lastTry.content.isValid()); + if (!lastTry.content.isConversion()) + return false; - if (!m_typeResolver->equals(lastTry.conversionResult(), merged.conversionResult()) - || lastTry.conversionOrigins() != merged.conversionOrigins()) { + if (!m_typeResolver->equals(lastTry.content.conversionResult(), merged.conversionResult()) + || lastTry.content.conversionOrigins() != merged.conversionOrigins()) { return false; } // We don't need to track it again if we've come to the same conclusion before. - m_state.annotations[currentInstructionOffset()].typeConversions[index].content = lastTry; - m_state.registers[index].content = lastTry; + m_state.annotations[currentInstructionOffset()].typeConversions[index] = lastTry; + m_state.registers[index] = lastTry; return true; }; @@ -1272,11 +1428,15 @@ void QQmlJSTypePropagator::propagateCall( const QQmlJSMetaMethod match = bestMatchForCall(methods, argc, argv, &errors); if (!match.isValid()) { - Q_ASSERT(errors.size() == methods.size()); - if (methods.size() == 1) + if (methods.size() == 1) { + // Cannot have multiple fuzzy matches if there is only one method + Q_ASSERT(errors.size() == 1); setError(errors.first()); - else + } else if (errors.size() < methods.size()) { + setError(u"Multiple matching overrides found. Cannot determine the right one."_s); + } else { setError(u"No matching override found. Candidates:\n"_s + errors.join(u'\n')); + } return; } @@ -1292,7 +1452,6 @@ void QQmlJSTypePropagator::propagateCall( if (!m_state.accumulatorOut().isValid()) setError(u"Cannot store return type of method %1()."_s.arg(match.methodName())); - m_state.setHasSideEffects(true); const auto types = match.parameters(); for (int i = 0; i < argc; ++i) { if (i < types.size()) { @@ -1306,6 +1465,7 @@ void QQmlJSTypePropagator::propagateCall( } addReadRegister(argv + i, m_typeResolver->globalType(m_typeResolver->jsValueType())); } + m_state.setHasSideEffects(true); } bool QQmlJSTypePropagator::propagateTranslationMethod( @@ -1316,7 +1476,7 @@ bool QQmlJSTypePropagator::propagateTranslationMethod( const QQmlJSMetaMethod method = methods.front(); const QQmlJSRegisterContent intType - = m_typeResolver->globalType(m_typeResolver->intType()); + = m_typeResolver->globalType(m_typeResolver->int32Type()); const QQmlJSRegisterContent stringType = m_typeResolver->globalType(m_typeResolver->stringType()); const QQmlJSRegisterContent returnType @@ -1425,22 +1585,195 @@ void QQmlJSTypePropagator::propagateStringArgCall(int argv) const QQmlJSScope::ConstPtr input = m_typeResolver->containedType( m_state.registers[argv].content); - for (QQmlJSScope::ConstPtr targetType : { - m_typeResolver->intType(), - m_typeResolver->uintType(), - m_typeResolver->realType(), - m_typeResolver->floatType(), - m_typeResolver->boolType(), - }) { - if (m_typeResolver->equals(input, targetType)) { - addReadRegister(argv, m_typeResolver->globalType(targetType)); - return; - } + + if (m_typeResolver->equals(input, m_typeResolver->uint32Type())) { + addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->realType())); + return; + } + + if (m_typeResolver->isIntegral(input)) { + addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->int32Type())); + return; + } + + if (m_typeResolver->isNumeric(input)) { + addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->realType())); + return; + } + + if (m_typeResolver->equals(input, m_typeResolver->boolType())) { + addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->boolType())); + return; } addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->stringType())); } +bool QQmlJSTypePropagator::propagateArrayMethod( + const QString &name, int argc, int argv, const QQmlJSRegisterContent &baseType) +{ + // TODO: + // * For concat() we need to decide what kind of array to return and what kinds of arguments to + // accept. + // * For entries(), keys(), and values() we need iterators. + // * For find(), findIndex(), sort(), every(), some(), forEach(), map(), filter(), reduce(), + // and reduceRight() we need typed function pointers. + + const auto intType = m_typeResolver->globalType(m_typeResolver->int32Type()); + const auto stringType = m_typeResolver->globalType(m_typeResolver->stringType()); + const auto baseContained = m_typeResolver->containedType(baseType); + const auto valueContained = baseContained->valueType(); + const auto valueType = m_typeResolver->globalType(valueContained); + + const bool canHaveSideEffects = (baseType.isProperty() && baseType.isWritable()) + || baseContained->isListProperty() + || baseType.isConversion(); + + const auto setReturnType = [&](const QQmlJSScope::ConstPtr type) { + setAccumulator(m_typeResolver->returnType( + type, QQmlJSRegisterContent::MethodReturnValue, baseContained)); + }; + + if (name == u"copyWithin" && argc > 0 && argc < 4) { + for (int i = 0; i < argc; ++i) { + if (!canConvertFromTo(m_state.registers[argv + i].content, intType)) + return false; + } + + for (int i = 0; i < argc; ++i) + addReadRegister(argv + i, intType); + + m_state.setHasSideEffects(canHaveSideEffects); + setReturnType(baseContained); + return true; + } + + if (name == u"fill" && argc > 0 && argc < 4) { + if (!canConvertFromTo(m_state.registers[argv].content, valueType)) + return false; + + for (int i = 1; i < argc; ++i) { + if (!canConvertFromTo(m_state.registers[argv + i].content, intType)) + return false; + } + + addReadRegister(argv, valueType); + + for (int i = 1; i < argc; ++i) + addReadRegister(argv + i, intType); + + m_state.setHasSideEffects(canHaveSideEffects); + setReturnType(baseContained); + return true; + } + + if (name == u"includes" && argc > 0 && argc < 3) { + if (!canConvertFromTo(m_state.registers[argv].content, valueType)) + return false; + + if (argc == 2) { + if (!canConvertFromTo(m_state.registers[argv + 1].content, intType)) + return false; + addReadRegister(argv + 1, intType); + } + + addReadRegister(argv, valueType); + setReturnType(m_typeResolver->boolType()); + return true; + } + + if (name == u"toString" || (name == u"join" && argc < 2)) { + if (argc == 1) { + if (!canConvertFromTo(m_state.registers[argv].content, stringType)) + return false; + addReadRegister(argv, stringType); + } + + setReturnType(m_typeResolver->stringType()); + return true; + } + + if ((name == u"pop" || name == u"shift") && argc == 0) { + m_state.setHasSideEffects(canHaveSideEffects); + setReturnType(valueContained); + return true; + } + + if (name == u"push" || name == u"unshift") { + for (int i = 0; i < argc; ++i) { + if (!canConvertFromTo(m_state.registers[argv + i].content, valueType)) + return false; + } + + for (int i = 0; i < argc; ++i) + addReadRegister(argv + i, valueType); + + m_state.setHasSideEffects(canHaveSideEffects); + setReturnType(m_typeResolver->int32Type()); + return true; + } + + if (name == u"reverse" && argc == 0) { + m_state.setHasSideEffects(canHaveSideEffects); + setReturnType(baseContained); + return true; + } + + if (name == u"slice" && argc < 3) { + for (int i = 0; i < argc; ++i) { + if (!canConvertFromTo(m_state.registers[argv + i].content, intType)) + return false; + } + + for (int i = 0; i < argc; ++i) + addReadRegister(argv + i, intType); + + setReturnType(baseType.storedType()->isListProperty() + ? m_typeResolver->qObjectListType() + : baseContained); + return true; + } + + if (name == u"splice" && argc > 0) { + for (int i = 0; i < 2; ++i) { + if (!canConvertFromTo(m_state.registers[argv + i].content, intType)) + return false; + } + + for (int i = 2; i < argc; ++i) { + if (!canConvertFromTo(m_state.registers[argv + i].content, valueType)) + return false; + } + + for (int i = 0; i < 2; ++i) + addReadRegister(argv + i, intType); + + for (int i = 2; i < argc; ++i) + addReadRegister(argv + i, valueType); + + m_state.setHasSideEffects(canHaveSideEffects); + setReturnType(baseContained); + return true; + } + + if ((name == u"indexOf" || name == u"lastIndexOf") && argc > 0 && argc < 3) { + if (!canConvertFromTo(m_state.registers[argv].content, valueType)) + return false; + + if (argc == 2) { + if (!canConvertFromTo(m_state.registers[argv + 1].content, intType)) + return false; + addReadRegister(argv + 1, intType); + } + + addReadRegister(argv, valueType); + setReturnType(m_typeResolver->int32Type()); + return true; + } + + return false; +} + void QQmlJSTypePropagator::generate_CallPropertyLookup(int lookupIndex, int base, int argc, int argv) { @@ -1517,14 +1850,70 @@ void QQmlJSTypePropagator::generate_TailCall(int func, int thisObject, int argc, INSTR_PROLOGUE_NOT_IMPLEMENTED(); } +void QQmlJSTypePropagator::generate_Construct_SCDate(int argc, int argv) +{ + setAccumulator(m_typeResolver->globalType(m_typeResolver->dateTimeType())); + + if (argc == 1) { + const QQmlJSRegisterContent argType = m_state.registers[argv].content; + if (m_typeResolver->isNumeric(argType)) { + addReadRegister( + argv, m_typeResolver->globalType(m_typeResolver->realType())); + } else if (m_typeResolver->registerContains(argType, m_typeResolver->stringType())) { + addReadRegister( + argv, m_typeResolver->globalType(m_typeResolver->stringType())); + } else if (m_typeResolver->registerContains(argType, m_typeResolver->dateTimeType()) + || m_typeResolver->registerContains(argType, m_typeResolver->dateType()) + || m_typeResolver->registerContains(argType, m_typeResolver->timeType())) { + addReadRegister( + argv, m_typeResolver->globalType(m_typeResolver->dateTimeType())); + } else { + addReadRegister( + argv, m_typeResolver->globalType(m_typeResolver->jsPrimitiveType())); + } + } else { + constexpr int maxArgc = 7; // year, month, day, hours, minutes, seconds, milliseconds + for (int i = 0; i < std::min(argc, maxArgc); ++i) { + addReadRegister( + argv + i, m_typeResolver->globalType(m_typeResolver->realType())); + } + } +} + +void QQmlJSTypePropagator::generate_Construct_SCArray(int argc, int argv) +{ + if (argc == 1) { + if (m_typeResolver->isNumeric(m_state.registers[argv].content)) { + setAccumulator(m_typeResolver->globalType(m_typeResolver->variantListType())); + addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->realType())); + } else { + generate_DefineArray(argc, argv); + } + } else { + generate_DefineArray(argc, argv); + } +} void QQmlJSTypePropagator::generate_Construct(int func, int argc, int argv) { - m_state.setHasSideEffects(true); - Q_UNUSED(func) - Q_UNUSED(argv) + const QQmlJSRegisterContent type = m_state.registers[func].content; + if (!type.isMethod()) { + m_state.setHasSideEffects(true); + setAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType())); + return; + } - Q_UNUSED(argc) + if (type.method() == m_typeResolver->jsGlobalObject()->methods(u"Date"_s)) { + generate_Construct_SCDate(argc, argv); + return; + } + + if (type.method() == m_typeResolver->jsGlobalObject()->methods(u"Array"_s)) { + generate_Construct_SCArray(argc, argv); + + return; + } + m_state.setHasSideEffects(true); setAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType())); } @@ -1560,8 +1949,22 @@ void QQmlJSTypePropagator::generate_UnwindToLabel(int level, int offset) void QQmlJSTypePropagator::generate_DeadTemporalZoneCheck(int name) { - Q_UNUSED(name) - INSTR_PROLOGUE_NOT_IMPLEMENTED(); + const auto fail = [this, name]() { + setError(u"Cannot statically assert the dead temporal zone check for %1"_s.arg( + name ? m_jsUnitGenerator->stringForIndex(name) : u"the anonymous accumulator"_s)); + }; + + const QQmlJSRegisterContent in = m_state.accumulatorIn(); + if (in.isConversion()) { + for (const QQmlJSScope::ConstPtr &origin : in.conversionOrigins()) { + if (!m_typeResolver->equals(origin, m_typeResolver->emptyType())) + continue; + fail(); + break; + } + } else if (m_typeResolver->registerContains(in, m_typeResolver->emptyType())) { + fail(); + } } void QQmlJSTypePropagator::generate_ThrowException() @@ -1634,28 +2037,41 @@ void QQmlJSTypePropagator::generate_PopContext() void QQmlJSTypePropagator::generate_GetIterator(int iterator) { - Q_UNUSED(iterator) - INSTR_PROLOGUE_NOT_IMPLEMENTED(); + const QQmlJSRegisterContent listType = m_state.accumulatorIn(); + if (!listType.isList()) { + const auto jsValue = m_typeResolver->globalType(m_typeResolver->jsValueType()); + addReadAccumulator(jsValue); + setAccumulator(jsValue); + return; + } + + addReadAccumulator(listType); + setAccumulator(m_typeResolver->iteratorPointer( + listType, QQmlJS::AST::ForEachType(iterator), currentInstructionOffset())); } -void QQmlJSTypePropagator::generate_IteratorNext(int value, int done) +void QQmlJSTypePropagator::generate_IteratorNext(int value, int offset) { - Q_UNUSED(value) - Q_UNUSED(done) - INSTR_PROLOGUE_NOT_IMPLEMENTED(); + const QQmlJSRegisterContent iteratorType = m_state.accumulatorIn(); + addReadAccumulator(iteratorType); + setRegister(value, m_typeResolver->merge( + m_typeResolver->valueType(iteratorType), + m_typeResolver->globalType(m_typeResolver->voidType()))); + saveRegisterStateForJump(offset); + m_state.setHasSideEffects(true); } -void QQmlJSTypePropagator::generate_IteratorNextForYieldStar(int iterator, int object) +void QQmlJSTypePropagator::generate_IteratorNextForYieldStar(int iterator, int object, int offset) { Q_UNUSED(iterator) Q_UNUSED(object) + Q_UNUSED(offset) INSTR_PROLOGUE_NOT_IMPLEMENTED(); } -void QQmlJSTypePropagator::generate_IteratorClose(int done) +void QQmlJSTypePropagator::generate_IteratorClose() { - Q_UNUSED(done) - INSTR_PROLOGUE_NOT_IMPLEMENTED(); + // Noop } void QQmlJSTypePropagator::generate_DestructureRestElement() @@ -1696,9 +2112,7 @@ void QQmlJSTypePropagator::generate_DeclareVar(int varName, int isDeletable) void QQmlJSTypePropagator::generate_DefineArray(int argc, int args) { - setAccumulator(m_typeResolver->globalType(argc == 0 - ? m_typeResolver->emptyListType() - : m_typeResolver->variantListType())); + setAccumulator(m_typeResolver->globalType(m_typeResolver->variantListType())); // Track all arguments as the same type. const QQmlJSRegisterContent elementType @@ -1709,12 +2123,34 @@ void QQmlJSTypePropagator::generate_DefineArray(int argc, int args) void QQmlJSTypePropagator::generate_DefineObjectLiteral(int internalClassId, int argc, int args) { - // TODO: computed property names, getters, and setters are unsupported. How do we catch them? + const int classSize = m_jsUnitGenerator->jsClassSize(internalClassId); + Q_ASSERT(argc >= classSize); - Q_UNUSED(internalClassId) - Q_UNUSED(argc) - Q_UNUSED(args) - setAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType())); + // Track each element as separate type + for (int i = 0; i < classSize; ++i) { + addReadRegister( + args + i, + m_typeResolver->tracked(m_typeResolver->globalType(m_typeResolver->varType()))); + } + + for (int i = classSize; i < argc; i += 3) { + // layout for remaining members is: + // 0: ObjectLiteralArgument - Value|Method|Getter|Setter + // We cannot do anything useful with this. Any code that would call a getter/setter/method + // could not be compiled to C++. Ignore it. + + // 1: name of argument + addReadRegister( + args + i + 1, + m_typeResolver->tracked(m_typeResolver->globalType(m_typeResolver->stringType()))); + + // 2: value of argument + addReadRegister( + args + i + 2, + m_typeResolver->tracked(m_typeResolver->globalType(m_typeResolver->varType()))); + } + + setAccumulator(m_typeResolver->globalType(m_typeResolver->variantMapType())); } void QQmlJSTypePropagator::generate_CreateClass(int classIndex, int heritage, int computedNames) @@ -1743,7 +2179,7 @@ void QQmlJSTypePropagator::generate_CreateRestParameter(int argIndex) void QQmlJSTypePropagator::generate_ConvertThisToObject() { - INSTR_PROLOGUE_NOT_IMPLEMENTED(); + setRegister(This, m_typeResolver->globalType(m_typeResolver->qObjectType())); } void QQmlJSTypePropagator::generate_LoadSuperConstructor() @@ -1772,8 +2208,8 @@ void QQmlJSTypePropagator::generate_JumpTrue(int offset) return; } saveRegisterStateForJump(offset); - m_state.setHasSideEffects(true); addReadAccumulator(m_typeResolver->globalType(m_typeResolver->boolType())); + m_state.setHasSideEffects(true); } void QQmlJSTypePropagator::generate_JumpFalse(int offset) @@ -1785,8 +2221,8 @@ void QQmlJSTypePropagator::generate_JumpFalse(int offset) return; } saveRegisterStateForJump(offset); - m_state.setHasSideEffects(true); addReadAccumulator(m_typeResolver->globalType(m_typeResolver->boolType())); + m_state.setHasSideEffects(true); } void QQmlJSTypePropagator::generate_JumpNoException(int offset) @@ -1833,42 +2269,33 @@ void QQmlJSTypePropagator::recordEqualsType(int lhs) return content.isEnumeration() || m_typeResolver->isNumeric(content); }; - const auto isIntCompatible = [this](const QQmlJSRegisterContent &content) { - const QQmlJSScope::ConstPtr contained = m_typeResolver->containedType(content); - return contained->scopeType() == QQmlJSScope::EnumScope - || m_typeResolver->equals(contained, m_typeResolver->intType()) - || m_typeResolver->equals(contained, m_typeResolver->uintType()); - }; - const auto accumulatorIn = m_state.accumulatorIn(); const auto lhsRegister = m_state.registers[lhs].content; // If the types are primitive, we compare directly ... if (m_typeResolver->isPrimitive(accumulatorIn) || accumulatorIn.isEnumeration()) { if (m_typeResolver->registerContains( - accumulatorIn, m_typeResolver->containedType(lhsRegister))) { - addReadRegister(lhs, accumulatorIn); + accumulatorIn, m_typeResolver->containedType(lhsRegister)) + || (isNumericOrEnum(accumulatorIn) && isNumericOrEnum(lhsRegister)) + || m_typeResolver->isPrimitive(lhsRegister)) { + addReadRegister(lhs, lhsRegister); addReadAccumulator(accumulatorIn); return; - } else if (isNumericOrEnum(accumulatorIn) && isNumericOrEnum(lhsRegister)) { - const auto targetType = isIntCompatible(accumulatorIn) && isIntCompatible(lhsRegister) - ? m_typeResolver->globalType(m_typeResolver->intType()) - : m_typeResolver->globalType(m_typeResolver->realType()); - addReadRegister(lhs, targetType); - addReadAccumulator(targetType); - return; - } else if (m_typeResolver->isPrimitive(lhsRegister)) { - const QQmlJSRegisterContent primitive = m_typeResolver->globalType( - m_typeResolver->jsPrimitiveType()); - addReadRegister(lhs, primitive); - addReadAccumulator(primitive); - return; } } - // We don't modify types if the types are comparable with QObject or var - if (canStrictlyCompareWithVar(m_typeResolver, lhsRegister, accumulatorIn) - || canCompareWithQObject(m_typeResolver, lhsRegister, accumulatorIn)) { + const auto containedAccumulatorIn = m_typeResolver->isOptionalType(accumulatorIn) + ? m_typeResolver->extractNonVoidFromOptionalType(accumulatorIn) + : m_typeResolver->containedType(accumulatorIn); + + const auto containedLhs = m_typeResolver->isOptionalType(lhsRegister) + ? m_typeResolver->extractNonVoidFromOptionalType(lhsRegister) + : m_typeResolver->containedType(lhsRegister); + + // We don't modify types if the types are comparable with QObject, QUrl or var types + if (canStrictlyCompareWithVar(m_typeResolver, containedLhs, containedAccumulatorIn) + || canCompareWithQObject(m_typeResolver, containedLhs, containedAccumulatorIn) + || canCompareWithQUrl(m_typeResolver, containedLhs, containedAccumulatorIn)) { addReadRegister(lhs, lhsRegister); addReadAccumulator(accumulatorIn); return; @@ -1912,7 +2339,7 @@ void QQmlJSTypePropagator::generate_CmpEqInt(int lhsConst) recordEqualsIntType(); Q_UNUSED(lhsConst) setAccumulator(QQmlJSRegisterContent(m_typeResolver->typeForBinaryOperation( - QSOperator::Op::Equal, m_typeResolver->globalType(m_typeResolver->intType()), + QSOperator::Op::Equal, m_typeResolver->globalType(m_typeResolver->int32Type()), m_state.accumulatorIn()))); } @@ -1921,7 +2348,7 @@ void QQmlJSTypePropagator::generate_CmpNeInt(int lhsConst) recordEqualsIntType(); Q_UNUSED(lhsConst) setAccumulator(QQmlJSRegisterContent(m_typeResolver->typeForBinaryOperation( - QSOperator::Op::NotEqual, m_typeResolver->globalType(m_typeResolver->intType()), + QSOperator::Op::NotEqual, m_typeResolver->globalType(m_typeResolver->int32Type()), m_state.accumulatorIn()))); } @@ -1994,32 +2421,53 @@ void QQmlJSTypePropagator::generate_CmpInstanceOf(int lhs) void QQmlJSTypePropagator::generate_As(int lhs) { const QQmlJSRegisterContent input = checkedInputRegister(lhs); - QQmlJSScope::ConstPtr contained; + const QQmlJSScope::ConstPtr inContained = m_typeResolver->containedType(input); + + QQmlJSScope::ConstPtr outContained; switch (m_state.accumulatorIn().variant()) { case QQmlJSRegisterContent::ScopeAttached: - contained = m_state.accumulatorIn().scopeType(); + outContained = m_state.accumulatorIn().scopeType(); break; case QQmlJSRegisterContent::MetaType: - contained = m_state.accumulatorIn().scopeType(); - if (contained->isComposite()) // Otherwise we don't need it + outContained = m_state.accumulatorIn().scopeType(); + if (outContained->isComposite()) // Otherwise we don't need it addReadAccumulator(m_typeResolver->globalType(m_typeResolver->metaObjectType())); break; default: - contained = m_typeResolver->containedType(m_state.accumulatorIn()); + outContained = m_typeResolver->containedType(m_state.accumulatorIn()); break; } - addReadRegister(lhs, m_typeResolver->globalType(contained)); + QQmlJSRegisterContent output; + + if (outContained->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) { + // A referece type cast can result in either the type or null. + // Reference types can hold null. We don't need to special case that. - if (m_typeResolver->containedType(input)->accessSemantics() - != QQmlJSScope::AccessSemantics::Reference - || contained->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) { + if (m_typeResolver->inherits(inContained, outContained)) + output = input; + else + output = m_typeResolver->cast(input, outContained); + } else if (!m_typeResolver->canAddressValueTypes()) { setError(u"invalid cast from %1 to %2. You can only cast object types."_s - .arg(input.descriptiveName(), m_state.accumulatorIn().descriptiveName())); + .arg(input.descriptiveName(), m_state.accumulatorIn().descriptiveName())); + return; } else { - setAccumulator(m_typeResolver->globalType(contained)); + if (m_typeResolver->inherits(inContained, outContained)) { + // A "slicing" cannot result in void + output = m_typeResolver->cast(input, outContained); + } else { + // A value type cast can result in either the type or undefined. + // Using convert() retains the variant of the input type. + output = m_typeResolver->merge( + m_typeResolver->cast(input, outContained), + m_typeResolver->cast(input, m_typeResolver->voidType())); + } } + + addReadRegister(lhs, input); + setAccumulator(output); } void QQmlJSTypePropagator::checkConversion( @@ -2085,7 +2533,7 @@ void QQmlJSTypePropagator::generateBinaryConstArithmeticOperation(QSOperator::Op { const QQmlJSRegisterContent type = m_typeResolver->typeForBinaryOperation( op, m_state.accumulatorIn(), - m_typeResolver->builtinType(m_typeResolver->intType())); + m_typeResolver->builtinType(m_typeResolver->int32Type())); checkConversion(m_state.accumulatorIn(), type); addReadAccumulator(type); @@ -2190,9 +2638,8 @@ void QQmlJSTypePropagator::generate_Sub(int lhs) void QQmlJSTypePropagator::generate_InitializeBlockDeadTemporalZone(int firstReg, int count) { - Q_UNUSED(firstReg) - Q_UNUSED(count) - // Ignore. We reject uninitialized values anyway. + for (int reg = firstReg, end = firstReg + count; reg < end; ++reg) + setRegister(reg, m_typeResolver->globalType(m_typeResolver->emptyType())); } void QQmlJSTypePropagator::generate_ThrowOnNullOrUndefined() @@ -2206,31 +2653,6 @@ void QQmlJSTypePropagator::generate_GetTemplateObject(int index) INSTR_PROLOGUE_NOT_IMPLEMENTED(); } -static bool instructionManipulatesContext(QV4::Moth::Instr::Type type) -{ - using Type = QV4::Moth::Instr::Type; - switch (type) { - case Type::PopContext: - case Type::PopScriptContext: - case Type::CreateCallContext: - case Type::CreateCallContext_Wide: - case Type::PushCatchContext: - case Type::PushCatchContext_Wide: - case Type::PushWithContext: - case Type::PushWithContext_Wide: - case Type::PushBlockContext: - case Type::PushBlockContext_Wide: - case Type::CloneBlockContext: - case Type::CloneBlockContext_Wide: - case Type::PushScriptContext: - case Type::PushScriptContext_Wide: - return true; - default: - break; - } - return false; -} - QV4::Moth::ByteCodeHandler::Verdict QQmlJSTypePropagator::startInstruction(QV4::Moth::Instr::Type type) { @@ -2298,9 +2720,6 @@ void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr) currentInstruction.readRegisters = m_state.takeReadRegisters(); currentInstruction.hasSideEffects = m_state.hasSideEffects(); currentInstruction.isRename = m_state.isRename(); - m_state.setHasSideEffects(false); - m_state.setIsRename(false); - m_state.setReadRegisters(VirtualRegisters()); switch (instr) { // the following instructions are not expected to produce output in the accumulator @@ -2324,6 +2743,10 @@ void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr) case QV4::Moth::Instr::Type::PushCatchContext: case QV4::Moth::Instr::Type::UnwindDispatch: case QV4::Moth::Instr::Type::InitializeBlockDeadTemporalZone: + case QV4::Moth::Instr::Type::ConvertThisToObject: + case QV4::Moth::Instr::Type::DeadTemporalZoneCheck: + case QV4::Moth::Instr::Type::IteratorNext: + case QV4::Moth::Instr::Type::IteratorNextForYieldStar: if (m_state.changedRegisterIndex() == Accumulator && !m_error->isValid()) { setError(u"Instruction is not expected to populate the accumulator"_s); return; @@ -2339,11 +2762,26 @@ void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr) } } + if (!(m_error->isValid() && m_error->isError()) + && instr != QV4::Moth::Instr::Type::DeadTemporalZoneCheck) { + // An instruction needs to have side effects or write to another register otherwise it's a + // noop. DeadTemporalZoneCheck is not needed by the compiler and is ignored. + Q_ASSERT(m_state.hasSideEffects() || m_state.changedRegisterIndex() != -1); + } + if (m_state.changedRegisterIndex() != InvalidRegister) { Q_ASSERT(m_error->isValid() || m_state.changedRegister().isValid()); - m_state.registers[m_state.changedRegisterIndex()].content = m_state.changedRegister(); + VirtualRegister &r = m_state.registers[m_state.changedRegisterIndex()]; + r.content = m_state.changedRegister(); + r.canMove = false; + r.affectedBySideEffects = m_state.isRename() + && m_state.isRegisterAffectedBySideEffects(m_state.renameSourceRegisterIndex()); m_state.clearChangedRegister(); } + + m_state.setHasSideEffects(false); + m_state.setIsRename(false); + m_state.setReadRegisters(VirtualRegisters()); } QQmlJSRegisterContent QQmlJSTypePropagator::propagateBinaryOperation(QSOperator::Op op, int lhs) |