Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Heap Corruption when signalling from C++ to QML



  • I'm new to Qt and I typically following the example codes at Qt Documentation.

    I have an Backend C++ application which signals Frontend QML application passing two int variable.
    I'm seeing a crash after some time on the signal function showing "heap corruption"

    Can anyone help me to fix the issue.

    // myfunction.pro
    QT += qml quick widgets core
    CONFIG += c++11 static QT_QML_DEBUG
    QMAKE_CXXFLAGS_RELEASE *= -O3
    

    I have around 142 png files embedded into my project. Debug Executable size : 4882 KB

    Snippet (c++ file)

    #ifndef BUTTON_BACKEND_H
    #define BUTTON_BACKEND_H
    
    #include <QObject>
    typedef enum {
        tpKeyReleased = 0,
        tpKeyTouched,
        tpKeyPressed
    }tpKeyStates_t;
    
    class ButtonBackend : public QObject
    {
        Q_OBJECT
        Q_PROPERTY(QString tpSide WRITE setTouchPadSide)
    
    public:
        explicit ButtonBackend(QObject *parent = nullptr);
        ~ButtonBackend();
        void tpSide();
        void setTouchPadSide(const QString &buttonIndex);
    
    signals:
        void funcButtonChanged(int index, int state);
    
    private:
        int m_key_idx = 0;
        tpKeyStates_t m_key_state = tpKeyReleased;
        tpKeyStates_t *m_funcKeyState_prev = nullptr;
        void decodeFunctionalAreaInfo(QList<QByteArray>);
    };
    
    #include "buttonbackend.h"
    ButtonBackend::ButtonBackend(QObject *parent) : QObject(parent)
    {
        m_key_idx = 0;
        if(m_funcKeyState_prev == nullptr)  
            m_funcKeyState_prev = new (nothrow) tpKeyStates_t[3];
    }
    
    ButtonBackend::~ButtonBackend()
    {}
    
    void ButtonBackend::decodeFunctionalAreaInfo(QList<QByteArray> inputData)
    {
        if(3 <= static_cast<unsigned int>(inputData.size()) {
            m_key_idx = 0;
            for(QList<QByteArray>::iterator iter = inputData.begin(); iter != inputData.end(); iter++, m_key_idx++)  {
                m_key_state = static_cast<tpKeyStates_t>(atoi(*iter));
                if(m_key_state != m_funcKeyState_prev[m_key_idx]) {
                    m_funcKeyState_prev[m_key_idx] = m_key_state;
                    emit funcButtonChanged(m_key_idx, m_key_state);
                 } // emit only if the current key state is changed from previous state
             }
        }
        else {
            qDebug().nospace() << ERROR_MSG << __func__ << "() Function Key input data count doesn't match (3" << " vs " << inputData.size() << ")"; }
    }
    
    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include "src/buttonbackend.h"
    
    int main(int argc, char *argv[])
    {
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
        QGuiApplication app(argc, argv);
        qmlRegisterType<ButtonBackend>("io.qt.ButtonBackend",1,0,"ButtonBackend");
    
        QQmlApplicationEngine engine;
        const QUrl url(QStringLiteral("qrc:/qml/main.qml"));
        QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                         &app, [url](QObject *obj, const QUrl &objUrl) {
            if (!obj && url == objUrl)
                QCoreApplication::exit(-1);
        }, Qt::QueuedConnection);
        engine.load(url);
    
        return app.exec();
    }
    

    Snippet (QML file):

    import QtQuick 2.2
    import QtQuick.Window 2.1
    import QtQuick.Window 2.12
    import QtQuick.Controls.Styles 1.4
    import QtQuick.Extras 1.4
    import QtGraphicalEffects 1.0
    import io.qt.ButtonBackend 1.0
    
    Window {
        id: window
        visible: true
        width: 400
        height: 400
        color: "#000000"
       flags: Qt.FramelessWindowHint
       
      Item {
        id: backendContainer
        FuncLayoutTemplate { id:func_layout;
        ButtonBackend { id:func_BkEnd } 
        Connections {
            target: func_BkEnd 
            onFuncButtonChanged: {
                var l_index = index;
                var l_state = state;
                console.log(" AF : Function Button Changed ",l_index);
                func_layout.recv_func_butn_state(l_index,l_state);
            }
         }
        }       
      }
    }
    
    // FuncLayoutTemplate.qml
    import QtQuick 2.0
    import QtGraphicalEffects 1.12
    
    Item{
        id:funcTemplate
        width: parent.width
        height: parent.height
        int m_func_scr_idx : -1
        int p_buttonState : 0
        
        Repeater {
          id: myFuncRepeater
          model: 3
          MyButton { onSendButtonIndex: m_func_scr_idx = index;}     
      }
    
    onM_func_scr_idxChanged: {
        if(m_func_scr_idx > -1) {
            // functional logic here...
            m_func_scr_idx = -1
        }
       
    function recv_func_butn_state(l_index, l_state){
      if(l_index > -1 &&  l_index < 3) {
        myFuncRepeater.itemAt(l_index).p_buttonState = l_state;
      }else
         console.log("ERROR : invalid function button index : ",l_index);
    }
    
    // MyButton.qml
    import QtQuick 2.0
    import QtGraphicalEffects 1.12
    
    Rectangle {
      id: buttonRect
      border.color: "#000000"
      border.width: 5
      color: "black"
      anchors.centerIn: parent
      height: parent.height * 0.5
      width: parent.width * 0.5
      radius: width * 0.5
      property int p_buttonState: 0
      signal sendButtonIndex(int index)
    
      states: [
        State {
          name: "State1";
          when: p_buttonState == 1
          PropertyChanges { target: buttonRect; border.color: "#00ffff"; }
        },
        State {
          name: "State2";
          when: p_buttonState == 2
          PropertyChanges { target: buttonRect; border.color: "#01ff10";}
        }
      ]
    
      transitions: [
        Transition {
          from: "State2";  to: "*";
          onRunningChanged: { 
            if((!running) sendButtonIndex(index)
          }
        }
      ]
    

    The backend c++ code receives from function button state as string from an other application via tcp socket (Format : "0,0,0") at every 25mSec interval, not matter the key state is changed or not.

    The above code snippet is the logic that I'm using to receive the decoded string from backend and send to qml as integer value.
    After few minutes of running (I tested in both release mode and debug mode), the program crashes saying "A Heap has been Corrupted" at moc_buttonbackend.cpp after emitting the signal.

    void ButtonBackend::funcButtonChanged(int _t1, int _t2)
    {
        void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))), const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t2))) };
        QMetaObject::activate(this, &staticMetaObject, 0, _a);
    }
    

    I have tested the code commenting myFuncRepeater.itemAt(l_index).p_buttonState = l_state; which works fine. Meaning I'm not consuming/using the information passed by the backend. What could be causing the heap to grow once I started consuming.

    1. What happens to var l_index and var l_state and its scope?
    2. How does the backend knows the emitted signal is fully consumed?

    -Thanks in advance.


  • Moderators

    @Vyuvaraj said in Heap Corruption when signalling from C++ to QML:

    Are you sure l_index is greater than zero? I don't see a check for that in the code (but I've only had a brief look).

    what happens to var l_index and var l_state and its scope?

    These values are completely independent of your C++ backend. When you emit a signal, POC types (like int) are copied. It does not matter whether values in C++ are still in scope or not.

    How does the backend knows the emitted signal is fully consumed?

    It has no idea. Emitting a signal is a "fire and forget" kind of action.



  • @Vyuvaraj said in Heap Corruption when signalling from C++ to QML:

    function recv_func_butn_state(l_index, l_state){
    if(l_index > -1 && l_index < 3) {
    myFuncRepeater.itemAt(l_index).p_buttonState = l_state;
    }else
    console.log("ERROR : invalid function button index : ",l_index);
    }


  • Moderators

    @Vyuvaraj said in Heap Corruption when signalling from C++ to QML:

    Meaning I'm not consuming/using the information passed by the backend. What could be causing the heap to grow once I started consuming.

    Heap corruption does not mean that the heap is growing. Instead, it means that data was written to the wrong place in the heap.

    1. How does the backend knows the emitted signal is fully consumed?

    As @sierdzio said: It doesn't know, and it doesn't need to know.

    When the signal is emitted, the signal data is passed to the signal handler function. In your case, the signal handler is onFuncButtonChanged.

    The signal handler function runs from start to finish, and then the signal data is freed from memory.

    1. What happens to var l_index and var l_state and its scope?

    Nothing happens to them.

    This:

    Connections {
        target: func_BkEnd 
        onFuncButtonChanged: {
            var l_index = index;
            var l_state = state;
            console.log(" AF : Function Button Changed ",l_index);
            func_layout.recv_func_butn_state(l_index,l_state);
        }
    }
    

    ...is the same as this:

    function processButton(idx, st) {
        var l_index = idx;
        var l_state = st;
        console.log(" AF : Function Button Changed ",l_index);
        func_layout.recv_func_butn_state(l_index,l_state);
    }
    
    Connections {
        target: func_BkEnd 
        onFuncButtonChanged: processButton(index, state);
    }
    

    Even if you comment out myFuncRepeater.itemAt(l_index).p_buttonState = l_state;, the other code in processButton() still runs.

    tpKeyStates_t *m_funcKeyState_prev = nullptr;
    

    It's a good idea to convert the above to QVector<tpKeyStates_t>.

    Raw arrays make it easier for you to accidentally corrupt your heap.

    if(3 <= static_cast<unsigned int>(inputData.size())
    

    You don't need a static_cast here.


Log in to reply