// Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qqmljsshadowcheck_p.h" QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; /*! * \internal * \class QQmlJSShadowCheck * * This pass looks for possible shadowing when accessing members of QML-exposed * types. A member can be shadowed if a non-final property is re-declared in a * derived class. As the QML engine will always pick up the most derived variant * of that property, we cannot rely on any property of a type to be actually * accessible, unless one of a few special cases holds: * * 1. We are dealing with a direct scope lookup, without an intermediate object. * Such lookups are protected from shadowing. For example "property int a: b" * always works. * 2. The object we are retrieving the property from is identified by an ID, or * an attached property or a singleton. Such objects cannot be replaced. * Therefore we can be sure to see all the type information at compile time. * 3. The property is declared final. * 4. The object we are retrieving the property from is a value type. Value * types cannot be used polymorphically. * * If the property is potentially shadowed, we can still retrieve it, but we * don't know its type. We should assume "var" then. * * All of the above also holds for methods. There we have to transform the * arguments and return types into "var". */ void QQmlJSShadowCheck::run( InstructionAnnotations *annotations, const Function *function, QQmlJS::DiagnosticMessage *error) { m_annotations = annotations; m_function = function; m_error = error; m_state = initialState(function); decode(m_function->code.constData(), static_cast(m_function->code.size())); } void QQmlJSShadowCheck::generate_LoadProperty(int nameIndex) { if (!m_state.readsRegister(Accumulator)) return; // enum lookup cannot be shadowed. auto accumulatorIn = m_state.registers.find(Accumulator); if (accumulatorIn != m_state.registers.end()) { checkShadowing( accumulatorIn.value().content, m_jsUnitGenerator->stringForIndex(nameIndex), Accumulator); } } void QQmlJSShadowCheck::generate_GetLookup(int index) { if (!m_state.readsRegister(Accumulator)) return; // enum lookup cannot be shadowed. auto accumulatorIn = m_state.registers.find(Accumulator); if (accumulatorIn != m_state.registers.end()) { checkShadowing( accumulatorIn.value().content, m_jsUnitGenerator->lookupName(index), Accumulator); } } void QQmlJSShadowCheck::generate_StoreProperty(int nameIndex, int base) { checkShadowing( m_state.registers[base].content, m_jsUnitGenerator->stringForIndex(nameIndex), base); } void QQmlJSShadowCheck::generate_SetLookup(int index, int base) { checkShadowing(m_state.registers[base].content, m_jsUnitGenerator->lookupName(index), base); } void QQmlJSShadowCheck::generate_CallProperty(int nameIndex, int base, int argc, int argv) { Q_UNUSED(argc); Q_UNUSED(argv); checkShadowing(m_state.registers[base].content, m_jsUnitGenerator->lookupName(nameIndex), base); } void QQmlJSShadowCheck::generate_CallPropertyLookup(int nameIndex, int base, int argc, int argv) { Q_UNUSED(argc); Q_UNUSED(argv); checkShadowing(m_state.registers[base].content, m_jsUnitGenerator->lookupName(nameIndex), base); } QV4::Moth::ByteCodeHandler::Verdict QQmlJSShadowCheck::startInstruction(QV4::Moth::Instr::Type) { m_state = nextStateFromAnnotations(m_state, *m_annotations); return (m_state.hasSideEffects() || m_state.changedRegisterIndex() != InvalidRegister) ? ProcessInstruction : SkipInstruction; } void QQmlJSShadowCheck::endInstruction(QV4::Moth::Instr::Type) { } void QQmlJSShadowCheck::checkShadowing( const QQmlJSRegisterContent &baseType, const QString &memberName, int baseRegister) { if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) return; switch (baseType.variant()) { case QQmlJSRegisterContent::ObjectProperty: case QQmlJSRegisterContent::ExtensionObjectProperty: case QQmlJSRegisterContent::ScopeProperty: case QQmlJSRegisterContent::ExtensionScopeProperty: { const QQmlJSRegisterContent member = m_typeResolver->memberType(baseType, memberName); // You can have something like parent.QtQuick.Screen.pixelDensity // In that case "QtQuick" cannot be resolved as member type and we would later have to look // for "QtQuick.Screen" instead. However, you can only do that with attached properties and // those are not shadowable. if (!member.isValid()) { Q_ASSERT(m_typeResolver->isPrefix(memberName)); return; } if (member.isProperty()) { if (member.property().isFinal()) return; // final properties can't be shadowed } else if (!member.isMethod()) { return; // Only properties and methods can be shadowed } m_logger->log( u"Member %1 of %2 can be shadowed"_s.arg( memberName, m_state.accumulatorIn().descriptiveName()), qmlCompiler, currentSourceLocation()); // Make it "var". We don't know what it is. const QQmlJSScope::ConstPtr varType = m_typeResolver->varType(); const QQmlJSRegisterContent varContent = m_typeResolver->globalType(varType); InstructionAnnotation ¤tAnnotation = (*m_annotations)[currentInstructionOffset()]; if (currentAnnotation.changedRegisterIndex != InvalidRegister) { m_typeResolver->adjustOriginalType( currentAnnotation.changedRegister.storedType(), varType); m_typeResolver->adjustOriginalType( m_typeResolver->containedType(currentAnnotation.changedRegister), varType); } for (auto it = currentAnnotation.readRegisters.begin(), end = currentAnnotation.readRegisters.end(); it != end; ++it) { if (it.key() != baseRegister) it->second.content = m_typeResolver->convert(it->second.content, varContent); } return; } default: // In particular ObjectById is fine as that cannot change into something else // Singleton should also be fine, unless the factory function creates an object // with different property types than the declared class. return; } } QT_END_NAMESPACE