Crash on lambda destructor (QString reference count)
-
I am having a very strange crash when assiging
nullptrto a lambda. It appears that before assiging thenullptr, the last lambda value is destroyed. The last lambda value captured a couple ofQStringsand it seems that Qt crashes when is trying to decrease the counter of theQStringinstances.I don't know if I am doing something ilegal, or if this is a known issue, I could not find any info online. I attach some screenshots of the callstack.
Environment:
- Qt 5.6.3
- CentOS 7
- GCC 4.8.5
This is where the
nullptris assigned (stack number10):
This is where the last lambda value is destroyed and the crash follows (stack number
6):
-
@VRonin sure, it is
std::function<void(void)> m_funcUpdatePartnerHost -
@VRonin sure, it is
std::function<void(void)> m_funcUpdatePartnerHostWhat is
strSocketHostandstrSocketPort? What ism_generalPartnerand what does.fetch()do/return? -
Sorry,
strSocketHostandstrSocketPortareQStringinstances (captured by the two lambdas shown in the screenshot.
To explain further;-
The
.done([this, strSocketHost, strSocketPort]() { ... })callback gets called after fetching some data from the network. -
During the execution of the
.donecallback, them_funcUpdatePartnerhostis assigned to[this, strSocketHost, strSocketPort]() { ... }as seen in the screenshot. -
Later, when another event occurs, the
m_funcUpdatePartnerhostis assigned tonullptrand this is when the crash occurs.
As I understand, the lambdas capture the
strSocketHostandstrSocketPort(QString) by value, therefore Qt should increase the instance count of those strings, so they do not get deleted.Then when the
m_funcUpdatePartnerhostis assigned tonullptr, the old lambda ([this, strSocketHost, strSocketPort]() { ... }) get deleted, therefore theQStringdestructor reduces the instance count, and this is when the crash occurs.Hope I made the scenario clearer.
-
-
Nice example on how to abuse lambdas so nobody understands what's going on there :)
Use valgrind to see where the object get's deleted. Simplify the code until either we can compile and test it or it no longer crashes. -
Sorry,
strSocketHostandstrSocketPortareQStringinstances (captured by the two lambdas shown in the screenshot.
To explain further;-
The
.done([this, strSocketHost, strSocketPort]() { ... })callback gets called after fetching some data from the network. -
During the execution of the
.donecallback, them_funcUpdatePartnerhostis assigned to[this, strSocketHost, strSocketPort]() { ... }as seen in the screenshot. -
Later, when another event occurs, the
m_funcUpdatePartnerhostis assigned tonullptrand this is when the crash occurs.
As I understand, the lambdas capture the
strSocketHostandstrSocketPort(QString) by value, therefore Qt should increase the instance count of those strings, so they do not get deleted.Then when the
m_funcUpdatePartnerhostis assigned tonullptr, the old lambda ([this, strSocketHost, strSocketPort]() { ... }) get deleted, therefore theQStringdestructor reduces the instance count, and this is when the crash occurs.Hope I made the scenario clearer.
@juangburgos said in Crash on lambda destructor (QString reference count):
The .done(this, strSocketHost, strSocketPort { ... }) callback gets called after fetching some data from the network.
.done()is a method, just as is.fetch(), I'll ask the same thing again, what do they return, at the very least provide the declarations of the relevant types.Later, when another event occurs, the m_funcUpdatePartnerhost is assigned to nullptr and this is when the crash occurs.
The logic is wrong. You're introducing a state which is shared between all events, you're begging for trouble.
As I understand, the lambdas capture the strSocketHost and strSocketPort (QString) by value, therefore Qt should increase the instance count of those strings, so they do not get deleted.
This doesn't guard against you smashing the stack (or heap).

Take note:
@Christian-Ehrlicher said in Crash on lambda destructor (QString reference count):
Nice example on how to abuse lambdas so nobody understands what's going on there :)
Use valgrind to see where the object get's deleted. Simplify the code until either we can compile and test it or it no longer crashes. -
-
I tried this simplified case but can't reproduce the problem (in Qt 5.15). Can you:
- try the example below on your configuration and see if it crashes (Qt 5.6 is ancient and I can't fire it up easily on my end)?
- suggest how to make it more similar to your case to the point where we can test the crash?
#include <QCoreApplication> #include <QDebug> #include <QTimer> class TestLambda : public QObject{ public: explicit TestLambda(QObject* parent = nullptr) :QObject(parent) { QString strSocketHost(QLatin1String("localhost")); QString strSocketPort(QLatin1String("8080")); m_generalPartner = [this,strSocketHost,strSocketPort](){ qDebug() << strSocketHost << strSocketPort; m_funcUpdatePartnerHost= [this,strSocketHost,strSocketPort](){ qDebug() << strSocketHost << strSocketPort; QTimer::singleShot(1000,this,&TestLambda::setFunctNull); }; QTimer::singleShot(1000,this,m_funcUpdatePartnerHost); }; } void setFunctNull(){ qDebug("Setting to NULL"); m_funcUpdatePartnerHost=nullptr; QTimer::singleShot(1000,qApp,&QCoreApplication::quit); } void start(){ m_generalPartner(); } std::function<void(void)> m_funcUpdatePartnerHost; std::function<void(void)> m_generalPartner; }; int main(int argc, char *argv[]) { QCoreApplication app(argc,argv); TestLambda testCrash; testCrash.start(); return app.exec(); } -
I tried this simplified case but can't reproduce the problem (in Qt 5.15). Can you:
- try the example below on your configuration and see if it crashes (Qt 5.6 is ancient and I can't fire it up easily on my end)?
- suggest how to make it more similar to your case to the point where we can test the crash?
#include <QCoreApplication> #include <QDebug> #include <QTimer> class TestLambda : public QObject{ public: explicit TestLambda(QObject* parent = nullptr) :QObject(parent) { QString strSocketHost(QLatin1String("localhost")); QString strSocketPort(QLatin1String("8080")); m_generalPartner = [this,strSocketHost,strSocketPort](){ qDebug() << strSocketHost << strSocketPort; m_funcUpdatePartnerHost= [this,strSocketHost,strSocketPort](){ qDebug() << strSocketHost << strSocketPort; QTimer::singleShot(1000,this,&TestLambda::setFunctNull); }; QTimer::singleShot(1000,this,m_funcUpdatePartnerHost); }; } void setFunctNull(){ qDebug("Setting to NULL"); m_funcUpdatePartnerHost=nullptr; QTimer::singleShot(1000,qApp,&QCoreApplication::quit); } void start(){ m_generalPartner(); } std::function<void(void)> m_funcUpdatePartnerHost; std::function<void(void)> m_generalPartner; }; int main(int argc, char *argv[]) { QCoreApplication app(argc,argv); TestLambda testCrash; testCrash.start(); return app.exec(); }@VRonin said in Crash on lambda destructor (QString reference count):
suggest how to make it more similar to your case to the point where we can test the crash?
Yes, please. I've encountered a bug in the code generation (the capture was generating wrong assembly) with MSVC (it was an old version and was fixed in the update), but never with gcc ...
-
@VRonin said in Crash on lambda destructor (QString reference count):
suggest how to make it more similar to your case to the point where we can test the crash?
Yes, please. I've encountered a bug in the code generation (the capture was generating wrong assembly) with MSVC (it was an old version and was fixed in the update), but never with gcc ...
I wish I could reproduce it easily. I have indeed tried in multiple ways. This is a bug that happens rarely in production. The code shown is single threaded.
The stack you see is actually from a dump file. I am using QDeferred for the
.donemethod.Will keep trying to minimally reproduce.