From 03ac6a0990afeb0ea3294e22d6e553a4370f32eb Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Tue, 6 Feb 2024 17:01:45 +0100 Subject: QtQml: Re-allow manual calling of signal handlers The fact that you could do this was due to a mistake in the implementation of QQmlPropertyCache. The cache entry for the signal handler looked like the signal itself. Make it possible to call QmlSignalHandler objects, and output a categorized warning when doing so. Also, align the call code between the interpreter and the JIT. Pick-to: 6.7 Fixes: QTBUG-120573 Change-Id: Ic76d37f587d21b68c55d77a08ac2d30950bec133 Reviewed-by: Fabian Kosmale Reviewed-by: Yifan Zhu Reviewed-by: Qt CI Bot --- src/qml/jsruntime/qv4qobjectwrapper.cpp | 22 ++++++++++++++++++++++ src/qml/jsruntime/qv4qobjectwrapper_p.h | 2 ++ src/qml/jsruntime/qv4runtime.cpp | 13 ++++++++++--- src/qml/jsruntime/qv4vme_moth.cpp | 12 ++++++++---- 4 files changed, 42 insertions(+), 7 deletions(-) (limited to 'src/qml/jsruntime') diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index e233b65f4e..89e09d6542 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include @@ -60,6 +61,7 @@ Q_LOGGING_CATEGORY(lcBindingRemoval, "qt.qml.binding.removal", QtWarningMsg) Q_LOGGING_CATEGORY(lcObjectConnect, "qt.qml.object.connect", QtWarningMsg) Q_LOGGING_CATEGORY(lcOverloadResolution, "qt.qml.overloadresolution", QtWarningMsg) Q_LOGGING_CATEGORY(lcMethodBehavior, "qt.qml.method.behavior") +Q_LOGGING_CATEGORY(lcSignalHandler, "qt.qml.signalhandler") // The code in this file does not violate strict aliasing, but GCC thinks it does // so turn off the warnings for us to have a clean build @@ -3179,6 +3181,26 @@ void Heap::QmlSignalHandler::init(QObject *object, int signalIndex) DEFINE_OBJECT_VTABLE(QmlSignalHandler); +ReturnedValue QmlSignalHandler::call(const Value *thisObject, const Value *argv, int argc) const +{ + const QString handlerName = QQmlSignalNames::signalNameToHandlerName( + object()->metaObject()->method(signalIndex()).name()); + qCWarning(lcSignalHandler).noquote() + << QStringLiteral("Property '%1' of object %2 is a signal handler. You should " + "not call it directly. Make it a proper function and call " + "that or emit the signal.") + .arg(handlerName, thisObject->toQStringNoThrow()); + + Scope scope(engine()); + Scoped method( + scope, QObjectMethod::create( + scope.engine->rootContext(), + static_cast(nullptr), + signalIndex())); + + return method->call(thisObject, argv, argc); +} + void QmlSignalHandler::initProto(ExecutionEngine *engine) { if (engine->signalHandlerPrototype()->d_unchecked()) diff --git a/src/qml/jsruntime/qv4qobjectwrapper_p.h b/src/qml/jsruntime/qv4qobjectwrapper_p.h index 7a2dbe7745..2cd2faedf8 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper_p.h +++ b/src/qml/jsruntime/qv4qobjectwrapper_p.h @@ -411,6 +411,8 @@ struct Q_QML_EXPORT QmlSignalHandler : public QV4::Object int signalIndex() const { return d()->signalIndex; } QObject *object() const { return d()->object(); } + ReturnedValue call(const Value *thisObject, const Value *argv, int argc) const; + static void initProto(ExecutionEngine *v4); }; diff --git a/src/qml/jsruntime/qv4runtime.cpp b/src/qml/jsruntime/qv4runtime.cpp index 8b3f89414b..c51c94ffe4 100644 --- a/src/qml/jsruntime/qv4runtime.cpp +++ b/src/qml/jsruntime/qv4runtime.cpp @@ -1503,10 +1503,17 @@ ReturnedValue Runtime::CallPropertyLookup::call(ExecutionEngine *engine, const V // ok to have the value on the stack here Value f = Value::fromReturnedValue(l->getter(l, engine, base)); - if (!f.isFunctionObject()) - return engine->throwTypeError(); + if (Q_LIKELY(f.isFunctionObject())) + return checkedResult(engine, static_cast(f).call(&base, argv, argc)); + + if (QmlSignalHandler *handler = f.as()) + return checkedResult(engine, handler->call(&base, argv, argc)); - return checkedResult(engine, static_cast(f).call(&base, argv, argc)); + const QString message = QStringLiteral("Property '%1' of object %2 is not a function") + .arg(engine->currentStackFrame->v4Function->compilationUnit + ->runtimeStrings[l->nameIndex]->toQString()) + .arg(base.toQStringNoThrow()); + return engine->throwTypeError(message); } ReturnedValue Runtime::CallValue::call(ExecutionEngine *engine, const Value &func, Value *argv, int argc) diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp index 1ea0a569fb..696368fa3f 100644 --- a/src/qml/jsruntime/qv4vme_moth.cpp +++ b/src/qml/jsruntime/qv4vme_moth.cpp @@ -794,15 +794,19 @@ QV4::ReturnedValue VME::interpret(JSTypesStackFrame *frame, ExecutionEngine *eng // ok to have the value on the stack here Value f = Value::fromReturnedValue(l->getter(l, engine, STACK_VALUE(base))); - if (Q_UNLIKELY(!f.isFunctionObject())) { - QString message = QStringLiteral("Property '%1' of object %2 is not a function") - .arg(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]->toQString()) + if (Q_LIKELY(f.isFunctionObject())) { + acc = static_cast(f).call(stack + base, stack + argv, argc); + } else if (QmlSignalHandler *handler = f.as()) { + acc = handler->call(stack + base, stack + argv, argc); + } else { + const QString message = QStringLiteral("Property '%1' of object %2 is not a function") + .arg(engine->currentStackFrame->v4Function->compilationUnit + ->runtimeStrings[l->nameIndex]->toQString()) .arg(STACK_VALUE(base).toQStringNoThrow()); acc = engine->throwTypeError(message); goto handleUnwind; } - acc = static_cast(f).call(stack + base, stack + argv, argc); CHECK_EXCEPTION; MOTH_END_INSTR(CallPropertyLookup) -- cgit v1.2.3