moc/QMetaProperty: Remove limitation on non-own-class notify signals

The moc generated code does a sanity check that NOTIFY signals actually
exist in the parent class when they cannot be found in the class moc
currently runs on.
The logic there was however too simplistic, and couldn't deal with
signals taking a parameter.
Fix this, and take the opportunity to use a proper static_assert instead
of generating a "normal" compile error.

We do not do any checks for the presence of QPrivateSignal, as the whole
point of QPrivateSignal is that it should be private (and not e.g.
protected).

Moreover, we adjust QMetaProperty::notifySignalIndex to take
single-argument notify methods into consideration as well when
encontering an unresolved notify index.

Fixes: QTBUG-115989
Change-Id: I8a056a15777f3132691e207b4b9ab6c2c9b2126d
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
(cherry picked from commit ac001bef798b79f4932d7ca8f4fb812159ba75ca)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Fabian Kosmale 2023-08-11 10:45:51 +02:00 committed by Qt Cherry-pick Bot
parent 6df9074a1a
commit d2f663eebc
5 changed files with 86 additions and 21 deletions

View File

@ -3973,21 +3973,24 @@ int QMetaProperty::notifySignalIndex() const
if (!mobj || data.notifyIndex() == std::numeric_limits<uint>::max()) if (!mobj || data.notifyIndex() == std::numeric_limits<uint>::max())
return -1; return -1;
uint methodIndex = data.notifyIndex(); uint methodIndex = data.notifyIndex();
if (methodIndex & IsUnresolvedSignal) { if (!(methodIndex & IsUnresolvedSignal))
return methodIndex + mobj->methodOffset();
methodIndex &= ~IsUnresolvedSignal; methodIndex &= ~IsUnresolvedSignal;
const QByteArray signalName = stringData(mobj, methodIndex); const QByteArray signalName = stringData(mobj, methodIndex);
const QMetaObject *m = mobj; const QMetaObject *m = mobj;
const int idx = QMetaObjectPrivate::indexOfMethodRelative<MethodSignal>(&m, signalName, 0, nullptr); // try 0-arg signal
if (idx >= 0) { int idx = QMetaObjectPrivate::indexOfMethodRelative<MethodSignal>(&m, signalName, 0, nullptr);
if (idx >= 0)
return idx + m->methodOffset();
// try 1-arg signal
QArgumentType argType(typeId());
idx = QMetaObjectPrivate::indexOfMethodRelative<MethodSignal>(&m, signalName, 1, &argType);
if (idx >= 0)
return idx + m->methodOffset(); return idx + m->methodOffset();
} else {
qWarning("QMetaProperty::notifySignal: cannot find the NOTIFY signal %s in class %s for property '%s'", qWarning("QMetaProperty::notifySignal: cannot find the NOTIFY signal %s in class %s for property '%s'",
signalName.constData(), mobj->className(), name()); signalName.constData(), mobj->className(), name());
return -1; return -1;
} }
}
return methodIndex + mobj->methodOffset();
}
// This method has been around for a while, but the documentation was marked \internal until 5.1 // This method has been around for a while, but the documentation was marked \internal until 5.1
/*! /*!

View File

@ -637,12 +637,29 @@ void Generator::generateCode()
// Generate function to make sure the non-class signals exist in the parent classes // Generate function to make sure the non-class signals exist in the parent classes
// //
if (!cdef->nonClassSignalList.isEmpty()) { if (!cdef->nonClassSignalList.isEmpty()) {
fprintf(out, "// If you get a compile error in this function it can be because either\n"); fprintf(out, "namespace CheckNotifySignalValidity_%s {\n", qualifiedClassNameIdentifier.constData());
fprintf(out, "// a) You are using a NOTIFY signal that does not exist. Fix it.\n"); for (const QByteArray &nonClassSignal : std::as_const(cdef->nonClassSignalList)) {
fprintf(out, "// b) You are using a NOTIFY signal that does exist (in a parent class) but has a non-empty parameter list. This is a moc limitation.\n"); const auto propertyIt = std::find_if(cdef->propertyList.constBegin(),
fprintf(out, "[[maybe_unused]] static void checkNotifySignalValidity_%s(%s *t) {\n", qualifiedClassNameIdentifier.constData(), cdef->qualified.constData()); cdef->propertyList.constEnd(),
for (const QByteArray &nonClassSignal : std::as_const(cdef->nonClassSignalList)) [&nonClassSignal](const PropertyDef &p) {
fprintf(out, " t->%s();\n", nonClassSignal.constData()); return nonClassSignal == p.notify;
});
// must find something, otherwise checkProperties wouldn't have inserted an entry into nonClassSignalList
Q_ASSERT(propertyIt != cdef->propertyList.constEnd());
fprintf(out, "template<typename T> using has_nullary_%s = decltype(std::declval<T>().%s());\n",
nonClassSignal.constData(),
nonClassSignal.constData());
const auto &propertyType = propertyIt->type;
fprintf(out, "template<typename T> using has_unary_%s = decltype(std::declval<T>().%s(std::declval<%s>()));\n",
nonClassSignal.constData(),
nonClassSignal.constData(),
propertyType.constData());
fprintf(out, "static_assert(qxp::is_detected_v<has_nullary_%s, %s> || qxp::is_detected_v<has_unary_%s, %s>,\n"
" \"NOTIFY signal %s does not exist in class (or is private in its parent)\");\n",
nonClassSignal.constData(), cdef->qualified.constData(),
nonClassSignal.constData(), cdef->qualified.constData(),
nonClassSignal.constData());
}
fprintf(out, "}\n"); fprintf(out, "}\n");
} }
} }

View File

@ -1167,6 +1167,7 @@ void Moc::generate(FILE *out, FILE *jsonOutput)
fprintf(out, "\n#include <QtCore/qtmochelpers.h>\n"); fprintf(out, "\n#include <QtCore/qtmochelpers.h>\n");
fprintf(out, "\n#include <memory>\n\n"); // For std::addressof fprintf(out, "\n#include <memory>\n\n"); // For std::addressof
fprintf(out, "\n#include <QtCore/qxptype_traits.h>\n"); // is_detected
fprintf(out, "#if !defined(Q_MOC_OUTPUT_REVISION)\n" fprintf(out, "#if !defined(Q_MOC_OUTPUT_REVISION)\n"
"#error \"The header file '%s' doesn't include <QObject>.\"\n", fn.constData()); "#error \"The header file '%s' doesn't include <QObject>.\"\n", fn.constData());

View File

@ -22,7 +22,14 @@ struct CustomType
Q_DECLARE_METATYPE(CustomType) Q_DECLARE_METATYPE(CustomType)
class tst_QMetaProperty : public QObject class Base : public QObject
{
Q_OBJECT
signals:
void baseSignal(int);
};
class tst_QMetaProperty : public Base
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(EnumType value WRITE setValue READ getValue) Q_PROPERTY(EnumType value WRITE setValue READ getValue)
@ -33,6 +40,7 @@ class tst_QMetaProperty : public QObject
Q_PROPERTY(int value10 READ value10 FINAL) Q_PROPERTY(int value10 READ value10 FINAL)
Q_PROPERTY(QMap<int, int> map MEMBER map) Q_PROPERTY(QMap<int, int> map MEMBER map)
Q_PROPERTY(CustomType custom MEMBER custom) Q_PROPERTY(CustomType custom MEMBER custom)
Q_PROPERTY(int propWithInheritedSig READ propWithInheritedSig NOTIFY baseSignal)
private slots: private slots:
void hasStdCppSet(); void hasStdCppSet();
@ -43,6 +51,7 @@ private slots:
void mapProperty(); void mapProperty();
void conversion(); void conversion();
void enumsFlags(); void enumsFlags();
void notifySignalIndex();
public: public:
enum EnumType { EnumType1 }; enum EnumType { EnumType1 };
@ -57,6 +66,8 @@ public:
int value9() const { return 1; } int value9() const { return 1; }
int value10() const { return 1; } int value10() const { return 1; }
int propWithInheritedSig() const { return 0; }
QString value7; QString value7;
QMap<int, int> map; QMap<int, int> map;
CustomType custom; CustomType custom;
@ -274,5 +285,20 @@ void tst_QMetaProperty::enumsFlags()
QCOMPARE(t.flagProperty(), EnumFlagsTester::flag2); QCOMPARE(t.flagProperty(), EnumFlagsTester::flag2);
} }
void tst_QMetaProperty::notifySignalIndex()
{
auto mo = this->metaObject();
auto propIndex = mo->indexOfProperty("propWithInheritedSig");
auto propWithInheritedSig = mo->property(propIndex);
QVERIFY(propWithInheritedSig.isValid());
QVERIFY(propWithInheritedSig.hasNotifySignal());
QVERIFY(propWithInheritedSig.notifySignalIndex() != -1);
QMetaMethod notifySignal = propWithInheritedSig.notifySignal();
QVERIFY(notifySignal.isValid());
QCOMPARE(notifySignal.name(), "baseSignal");
QCOMPARE(notifySignal.parameterCount(), 1);
}
QTEST_MAIN(tst_QMetaProperty) QTEST_MAIN(tst_QMetaProperty)
#include "tst_qmetaproperty.moc" #include "tst_qmetaproperty.moc"

View File

@ -257,6 +257,24 @@ public:
CreatableGadget creatableGadget; // Force the compiler to use the constructor CreatableGadget creatableGadget; // Force the compiler to use the constructor
struct ParentWithSignalWithArgument : QObject {
Q_OBJECT
Q_PROPERTY(int i READ i WRITE setI NOTIFY iChanged)
public:
int i() const {return 0;}
void setI(int) {}
signals:
void iChanged(int);
};
struct SignalWithArgumentInParent : ParentWithSignalWithArgument
{
Q_OBJECT
Q_PROPERTY(int otherI READ i WRITE setI NOTIFY iChanged)
};
struct MyStruct {}; struct MyStruct {};
struct MyStruct2 {}; struct MyStruct2 {};