Fix crash in QProcess::waitForFinished on Windows

Suppose the user connects QProcess::readyReadStandardOutput with a
slot that calls QCoreApplication::processEvents.
Assume the event loop did not handle events between QProcess::start
and QProcess::waitForFinished. The process writes to stdout and exits.
QProcessPrivate::waitForFinished calls drainOutputPipes which calls
QWindowsPipeWriter::waitForReadyRead. This in turn will trigger
_q_processDied via the readyRead signal and processEvents.
_q_processDied will delete the pid object and set pid to null.
After drainOutputPipes returns, _q_processDied is called again but it
must not be called if pid is already destroyed.

Prevent calling _q_processDied if pid is null.

Task-number: QTBUG-48697
Change-Id: Iee047938ee1529057a1a43d71f4e882750903c7e
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com>
This commit is contained in:
Joerg Bornemann 2016-02-24 12:36:36 +01:00
parent c7e213334b
commit 6f75c189e1
2 changed files with 37 additions and 2 deletions

View File

@ -647,7 +647,8 @@ bool QProcessPrivate::waitForReadyRead(int msecs)
return false; return false;
if (WaitForSingleObjectEx(pid->hProcess, 0, false) == WAIT_OBJECT_0) { if (WaitForSingleObjectEx(pid->hProcess, 0, false) == WAIT_OBJECT_0) {
bool readyReadEmitted = drainOutputPipes(); bool readyReadEmitted = drainOutputPipes();
_q_processDied(); if (pid)
_q_processDied();
return readyReadEmitted; return readyReadEmitted;
} }
@ -752,7 +753,8 @@ bool QProcessPrivate::waitForFinished(int msecs)
if (WaitForSingleObject(pid->hProcess, timer.nextSleepTime()) == WAIT_OBJECT_0) { if (WaitForSingleObject(pid->hProcess, timer.nextSleepTime()) == WAIT_OBJECT_0) {
drainOutputPipes(); drainOutputPipes();
_q_processDied(); if (pid)
_q_processDied();
return true; return true;
} }

View File

@ -145,6 +145,8 @@ private slots:
void startStopStartStop(); void startStopStartStop();
void startStopStartStopBuffers_data(); void startStopStartStopBuffers_data();
void startStopStartStopBuffers(); void startStopStartStopBuffers();
void processEventsInAReadyReadSlot_data();
void processEventsInAReadyReadSlot();
// keep these at the end, since they use lots of processes and sometimes // keep these at the end, since they use lots of processes and sometimes
// caused obscure failures to occur in tests that followed them (esp. on the Mac) // caused obscure failures to occur in tests that followed them (esp. on the Mac)
@ -157,6 +159,7 @@ private slots:
protected slots: protected slots:
void readFromProcess(); void readFromProcess();
void exitLoopSlot(); void exitLoopSlot();
void processApplicationEvents();
#ifndef Q_OS_WINCE #ifndef Q_OS_WINCE
void restartProcess(); void restartProcess();
void waitForReadyReadInAReadyReadSlotSlot(); void waitForReadyReadInAReadyReadSlotSlot();
@ -475,6 +478,11 @@ void tst_QProcess::exitLoopSlot()
QTestEventLoop::instance().exitLoop(); QTestEventLoop::instance().exitLoop();
} }
void tst_QProcess::processApplicationEvents()
{
QCoreApplication::processEvents();
}
#ifndef Q_OS_WINCE #ifndef Q_OS_WINCE
// Reading and writing to a process is not supported on Qt/CE // Reading and writing to a process is not supported on Qt/CE
void tst_QProcess::echoTest2() void tst_QProcess::echoTest2()
@ -2499,6 +2507,31 @@ void tst_QProcess::startStopStartStopBuffers()
} }
} }
void tst_QProcess::processEventsInAReadyReadSlot_data()
{
QTest::addColumn<bool>("callWaitForReadyRead");
QTest::newRow("no waitForReadyRead") << false;
QTest::newRow("waitForReadyRead") << true;
}
void tst_QProcess::processEventsInAReadyReadSlot()
{
// Test whether processing events in a readyReadXXX slot crashes. (QTBUG-48697)
QFETCH(bool, callWaitForReadyRead);
QProcess process;
QObject::connect(&process, &QProcess::readyReadStandardOutput,
this, &tst_QProcess::processApplicationEvents);
process.start("testProcessEcho/testProcessEcho");
QVERIFY(process.waitForStarted());
const QByteArray data(156, 'x');
process.write(data.constData(), data.size() + 1);
if (callWaitForReadyRead)
QVERIFY(process.waitForReadyRead());
if (process.state() == QProcess::Running)
QVERIFY(process.waitForFinished());
}
#endif //QT_NO_PROCESS #endif //QT_NO_PROCESS
QTEST_MAIN(tst_QProcess) QTEST_MAIN(tst_QProcess)