Fix erroneous exclusion of classes from related meta objects in moc

Assume an unrelated class that declares an enum and uses Q_ENUMS. Consider
then a class that uses UnrelatedClass::Enum as a Q_PROPERTY. We used to
include UnrelatedClass in the primary class's related meta objects, in order
to support use-cases like
    obj->setProperty("enumProperty", "ValueOfEnumAsString");

If however moc happens to see Q_DECLARE_METATYPE(UnrelatedClass::Enum), then it
would exclude it from the related meta objects, which would silently break the
string based enum value conversion. This was meant as an optimization, but it
isn't apparent to the developer why sometimes the string conversion would
work and sometimes not (depending on whether somebody declares that macro).
This also becomes visible in QML, which relies on the same embedded type
information for enum assignments.

This patch removes that check in moc's code generator and cleans up the code a
little. However always including the prefix of Q_PROPERTY(SomePrefix::Enum ...)
is not correct either, because it may be that SomePrefix is a namespace, which
would cause compilation issues. Therefore we limit the inclusion of related
meta objects only to Q_OBJECT decorated classes the moc has seen, and for these
we save the fully qualified name in the related meta objects array (for QTBUG-2151).

While this patch makes the previous workaround for namespace issues by using a
Q_DECLARE_METATYPE not workable anymore, by saving the fully qualified name we
are making a hopefully sufficient effort to not require a workaround in the
first place. There's always the new workaround of fully qualifying the type in
Q_PROPERTY.

One side-effect of this change is that in the autoPropertyMetaTypeRegistration
test of tst_moc, the CustomQObject for Q_PROPERTY(CustomQObject::Number
enumValue ...) is now a related meta object, and therefore when querying for
the type of this property via QMetaProperty::userType(), we are now aware of
this being an enum and try to resolve CustomQObject::Number via
QMetaType::type(qualfiedName). As there is no guarantee for this to succeed, we
must now also do what is done in the non-enum code path in ::userType(), which
is to call the moc generated type registration function.

Task-number: QTBUG-33577
Task-number: QTBUG-2151
Change-Id: Ibf20e7421cba464c558a25c76a7e1eef002c6cff
Reviewed-by: Olivier Goffart <ogoffart@woboq.com>
This commit is contained in:
Simon Hausmann 2013-12-12 10:50:00 +01:00 committed by The Qt Project
parent d56f5df84f
commit 54f0733e7d
8 changed files with 162 additions and 22 deletions

View File

@ -2694,10 +2694,14 @@ int QMetaProperty::userType() const
if (type != QMetaType::UnknownType) if (type != QMetaType::UnknownType)
return type; return type;
if (isEnumType()) { if (isEnumType()) {
int enumMetaTypeId = QMetaType::type(qualifiedName(menum)); type = QMetaType::type(qualifiedName(menum));
if (enumMetaTypeId == QMetaType::UnknownType) if (type == QMetaType::UnknownType) {
return QVariant::Int; // Match behavior of QMetaType::type() void *argv[] = { &type };
return enumMetaTypeId; mobj->static_metacall(QMetaObject::RegisterPropertyMetaType, idx, argv);
if (type == -1 || type == QMetaType::UnknownType)
return QVariant::Int; // Match behavior of QMetaType::type()
}
return type;
} }
type = QMetaType::type(typeName()); type = QMetaType::type(typeName());
if (type != QMetaType::UnknownType) if (type != QMetaType::UnknownType)

View File

@ -87,7 +87,7 @@ QT_FOR_EACH_STATIC_TYPE(RETURN_METATYPENAME_STRING)
return 0; return 0;
} }
Generator::Generator(ClassDef *classDef, const QList<QByteArray> &metaTypes, const QSet<QByteArray> &knownQObjectClasses, FILE *outfile) Generator::Generator(ClassDef *classDef, const QList<QByteArray> &metaTypes, const QHash<QByteArray, QByteArray> &knownQObjectClasses, FILE *outfile)
: out(outfile), cdef(classDef), metaTypes(metaTypes), knownQObjectClasses(knownQObjectClasses) : out(outfile), cdef(classDef), metaTypes(metaTypes), knownQObjectClasses(knownQObjectClasses)
{ {
if (cdef->superclassList.size()) if (cdef->superclassList.size())
@ -438,15 +438,31 @@ void Generator::generateCode()
QList<QByteArray> extraList; QList<QByteArray> extraList;
for (int i = 0; i < cdef->propertyList.count(); ++i) { for (int i = 0; i < cdef->propertyList.count(); ++i) {
const PropertyDef &p = cdef->propertyList.at(i); const PropertyDef &p = cdef->propertyList.at(i);
if (!isBuiltinType(p.type) && !metaTypes.contains(p.type) && !p.type.contains('*') && if (isBuiltinType(p.type))
!p.type.contains('<') && !p.type.contains('>')) { continue;
int s = p.type.lastIndexOf("::");
if (s > 0) { if (p.type.contains('*') || p.type.contains('<') || p.type.contains('>'))
QByteArray scope = p.type.left(s); continue;
if (scope != "Qt" && !qualifiedNameEquals(cdef->qualified, scope) && !extraList.contains(scope))
extraList += scope; int s = p.type.lastIndexOf("::");
} if (s <= 0)
} continue;
QByteArray unqualifiedScope = p.type.left(s);
// The scope may be a namespace for example, so it's only safe to include scopes that are known QObjects (QTBUG-2151)
QHash<QByteArray, QByteArray>::ConstIterator scopeIt = knownQObjectClasses.find(unqualifiedScope);
if (scopeIt == knownQObjectClasses.constEnd())
continue;
const QByteArray &scope = *scopeIt;
if (scope == "Qt")
continue;
if (qualifiedNameEquals(cdef->qualified, scope))
continue;
if (!extraList.contains(scope))
extraList += scope;
} }
// QTBUG-20639 - Accept non-local enums for QML signal/slot parameters. // QTBUG-20639 - Accept non-local enums for QML signal/slot parameters.

View File

@ -52,7 +52,7 @@ class Generator
ClassDef *cdef; ClassDef *cdef;
QVector<uint> meta_data; QVector<uint> meta_data;
public: public:
Generator(ClassDef *classDef, const QList<QByteArray> &metaTypes, const QSet<QByteArray> &knownQObjectClasses, FILE *outfile = 0); Generator(ClassDef *classDef, const QList<QByteArray> &metaTypes, const QHash<QByteArray, QByteArray> &knownQObjectClasses, FILE *outfile = 0);
void generateCode(); void generateCode();
private: private:
bool registerableMetaType(const QByteArray &propertyType); bool registerableMetaType(const QByteArray &propertyType);
@ -79,7 +79,7 @@ private:
QList<QByteArray> strings; QList<QByteArray> strings;
QByteArray purestSuperClass; QByteArray purestSuperClass;
QList<QByteArray> metaTypes; QList<QByteArray> metaTypes;
QSet<QByteArray> knownQObjectClasses; QHash<QByteArray, QByteArray> knownQObjectClasses;
}; };
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -621,8 +621,8 @@ void Moc::parse()
if (inNamespace(&namespaceList.at(i))) if (inNamespace(&namespaceList.at(i)))
def.qualified.prepend(namespaceList.at(i).name + "::"); def.qualified.prepend(namespaceList.at(i).name + "::");
knownQObjectClasses.insert(def.classname); knownQObjectClasses.insert(def.classname, def.qualified);
knownQObjectClasses.insert(def.qualified); knownQObjectClasses.insert(def.qualified, def.qualified);
continue; } continue; }
default: break; default: break;
@ -795,8 +795,8 @@ void Moc::parse()
checkProperties(&def); checkProperties(&def);
classList += def; classList += def;
knownQObjectClasses.insert(def.classname); knownQObjectClasses.insert(def.classname, def.qualified);
knownQObjectClasses.insert(def.qualified); knownQObjectClasses.insert(def.qualified, def.qualified);
} }
} }
} }

View File

@ -215,7 +215,8 @@ public:
QList<ClassDef> classList; QList<ClassDef> classList;
QMap<QByteArray, QByteArray> interface2IdMap; QMap<QByteArray, QByteArray> interface2IdMap;
QList<QByteArray> metaTypes; QList<QByteArray> metaTypes;
QSet<QByteArray> knownQObjectClasses; // map from class name to fully qualified name
QHash<QByteArray, QByteArray> knownQObjectClasses;
QMap<QString, QJsonArray> metaArgs; QMap<QString, QJsonArray> metaArgs;
void parse(); void parse();

View File

@ -24,7 +24,8 @@ HEADERS += using-namespaces.h no-keywords.h task87883.h c-comments.h backslash-n
parse-defines.h \ parse-defines.h \
function-with-attributes.h \ function-with-attributes.h \
plugin_metadata.h \ plugin_metadata.h \
single-quote-digit-separator-n3781.h single-quote-digit-separator-n3781.h \
related-metaobjects-in-namespaces.h
if(*-g++*|*-icc*|*-clang*|*-llvm):!irix-*:!win32-*: HEADERS += os9-newlines.h win-newlines.h if(*-g++*|*-icc*|*-clang*|*-llvm):!irix-*:!win32-*: HEADERS += os9-newlines.h win-newlines.h

View File

@ -0,0 +1,60 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the test suite 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 <QObject>
namespace QTBUG_2151 {
class A : public QObject {
Q_OBJECT
Q_ENUMS(SomeEnum)
public:
enum SomeEnum { SomeEnumValue = 0 };
};
class B : public QObject
{
Q_OBJECT
Q_PROPERTY(A::SomeEnum blah READ blah)
public:
A::SomeEnum blah() const { return A::SomeEnumValue; }
};
}

View File

@ -76,6 +76,7 @@
#include "cxx11-explicit-override-control.h" #include "cxx11-explicit-override-control.h"
#include "parse-defines.h" #include "parse-defines.h"
#include "related-metaobjects-in-namespaces.h"
QT_USE_NAMESPACE QT_USE_NAMESPACE
@ -568,6 +569,8 @@ private slots:
void preprocessorOnly(); void preprocessorOnly();
void unterminatedFunctionMacro(); void unterminatedFunctionMacro();
void QTBUG32933_relatedObjectsDontIncludeItself(); void QTBUG32933_relatedObjectsDontIncludeItself();
void writeEnumFromUnrelatedClass();
void relatedMetaObjectsWithinNamespaces();
signals: signals:
void sigWithUnsignedArg(unsigned foo); void sigWithUnsignedArg(unsigned foo);
@ -3104,6 +3107,61 @@ void tst_Moc::QTBUG32933_relatedObjectsDontIncludeItself()
} }
class UnrelatedClass : public QObject
{
Q_OBJECT
Q_ENUMS(UnrelatedEnum)
public:
enum UnrelatedEnum {
UnrelatedInvalidValue = -1,
UnrelatedValue = 42
};
};
// The presence of this macro used to confuse moc and prevent
// UnrelatedClass from being listed in the related meta objects.
Q_DECLARE_METATYPE(UnrelatedClass::UnrelatedEnum)
class TestClassReferencingUnrelatedEnum : public QObject
{
Q_OBJECT
Q_PROPERTY(UnrelatedClass::UnrelatedEnum enumProperty READ enumProperty WRITE setEnumProperty)
public:
TestClassReferencingUnrelatedEnum()
: m_enumProperty(UnrelatedClass::UnrelatedInvalidValue)
{}
UnrelatedClass::UnrelatedEnum enumProperty() const {
return m_enumProperty;
}
void setEnumProperty(UnrelatedClass::UnrelatedEnum arg) {
m_enumProperty = arg;
}
private:
UnrelatedClass::UnrelatedEnum m_enumProperty;
};
void tst_Moc::writeEnumFromUnrelatedClass()
{
TestClassReferencingUnrelatedEnum obj;
QString enumValueAsString("UnrelatedValue");
obj.setProperty("enumProperty", enumValueAsString);
QCOMPARE(int(obj.enumProperty()), int(UnrelatedClass::UnrelatedValue));
}
void tst_Moc::relatedMetaObjectsWithinNamespaces()
{
const QMetaObject *relatedMo = &QTBUG_2151::A::staticMetaObject;
const QMetaObject *testMo = &QTBUG_2151::B::staticMetaObject;
QVERIFY(testMo->d.relatedMetaObjects);
QVERIFY(testMo->d.relatedMetaObjects[0] == relatedMo);
}
QTEST_MAIN(tst_Moc) QTEST_MAIN(tst_Moc)
// the generated code must compile with QT_NO_KEYWORDS // the generated code must compile with QT_NO_KEYWORDS