Bug 427387

Summary: Crash in ParseProjectJob::start() when exiting KDevelop soon after a large project is opened
Product: [Applications] kdevelop Reporter: Igor Kushnir <igorkuo>
Component: generalAssignee: Igor Kushnir <igorkuo>
Status: RESOLVED FIXED    
Severity: crash    
Priority: NOR    
Version: git master   
Target Milestone: ---   
Platform: Compiled Sources   
OS: Linux   
Latest Commit: Version Fixed In: 5.6.1

Description Igor Kushnir 2020-10-06 12:34:20 UTC
SUMMARY
When a user exits KDevelop soon after a large project is opened, before ParseProjectJob::start() returns, DUChain::shutdown() may be called in the ParseProjectJob::start()'s nested event loop. This causes a crash if the ParseProjectJob is killed but not destroyed by the time its QApplication::processEvents() returns and the start() indirectly accesses an IndexedString.

SOFTWARE/OS VERSIONS
Manjaro GNU/Linux, Xfce
KDE Frameworks Version: 5.74.0
Qt Version: 5.15.1

ADDITIONAL INFORMATION
I am going to create a Merge Request with a fix soon.

BACKTRACE
Application: KDevelop (kdevelop), signal: Aborted

[KCrash Handler]
#4  0x00007f2d63043355 in raise () at /usr/lib/libc.so.6
#5  0x00007f2d6302c853 in abort () at /usr/lib/libc.so.6
#6  0x00007f2d6359b9ac in  () at /usr/lib/libQt5Core.so.5
#7  0x00007f2d6359ad59 in qt_assert_x(char const*, char const*, char const*, int) () at /usr/lib/libQt5Core.so.5
#8  0x00007f2d661dd67c in KDevelop::IndexedString::toUrl() const (this=0x5565053195ac) at ../kdevplatform/serialization/indexedstring.cpp:304
#9  0x00007f2d663c0c6a in (anonymous namespace)::isValidURL(KDevelop::IndexedString const&) (url=...) at ../kdevplatform/language/backgroundparser/backgroundparser.cpp:97
#10 0x00007f2d663c1887 in KDevelop::BackgroundParser::addDocument(KDevelop::IndexedString const&, KDevelop::TopDUContext::Features, int, QObject*, QFlags<KDevelop::ParseJob::SequentialProcessingFlag>, int) (this=0x5564fc126170, url=..., features=KDevelop::TopDUContext::VisibleDeclarationsAndContexts, priority=10000, notifyWhenReady=0x556503086fd0, flags=..., delay=-1) at ../kdevplatform/language/backgroundparser/backgroundparser.cpp:609
#11 0x00007f2d663d9f44 in KDevelop::ParseProjectJob::start() (this=0x556503086fd0) at ../kdevplatform/language/backgroundparser/parseprojectjob.cpp:184
#12 0x00007f2d67628dd7 in KDevelop::RunController::registerJob(KJob*) (this=0x5564fc11d360, job=0x556503086fd0) at ../kdevplatform/shell/runcontroller.cpp:628
#13 0x00007f2d675e8711 in KDevelop::ProjectController::reparseProject(KDevelop::IProject*, bool, bool) (this=0x5564fc0bacd0, project=0x5564fdd0cfc0, forceUpdate=false, forceAll=false) at ../kdevplatform/shell/projectcontroller.cpp:1381
#14 0x00007f2d675e60b2 in KDevelop::ProjectController::projectImportingFinished(KDevelop::IProject*) (this=0x5564fc0bacd0, project=0x5564fdd0cfc0) at ../kdevplatform/shell/projectcontroller.cpp:1002
#15 0x00007f2d675fe9b5 in KDevelop::ProjectPrivate::importDone(KJob*) (this=0x5564fde1f4a0, job=0x5564ffb8de70) at ../kdevplatform/shell/project.cpp:221
#16 0x00007f2d675fc232 in operator()(KJob*) const (__closure=0x5564ffe28840, job=0x5564ffb8de70) at ../kdevplatform/shell/project.cpp:548
#17 0x00007f2d675fe3a4 in QtPrivate::FunctorCall<QtPrivate::IndexesList<0>, QtPrivate::List<KJob*>, void, KDevelop::Project::open(const KDevelop::Path&)::<lambda(KJob*)> >::call(struct {...} &, void **) (f=..., arg=0x7ffee5c93e10) at /usr/include/qt/QtCore/qobjectdefs_impl.h:146
#18 0x00007f2d675fe2fc in QtPrivate::Functor<KDevelop::Project::open(const KDevelop::Path&)::<lambda(KJob*)>, 1>::call<QtPrivate::List<KJob*>, void>(struct {...} &, void *, void **) (f=..., arg=0x7ffee5c93e10) at /usr/include/qt/QtCore/qobjectdefs_impl.h:256
#19 0x00007f2d675fe2a1 in QtPrivate::QFunctorSlotObject<KDevelop::Project::open(const KDevelop::Path&)::<lambda(KJob*)>, 1, QtPrivate::List<KJob*>, void>::impl(int, QtPrivate::QSlotObjectBase *, QObject *, void **, bool *) (which=1, this_=0x5564ffe28830, r=0x5564fdd0cfc0, a=0x7ffee5c93e10, ret=0x0) at /usr/include/qt/QtCore/qobjectdefs_impl.h:443
#20 0x00007f2d637f4a26 in  () at /usr/lib/libQt5Core.so.5
#21 0x00007f2d63cafced in KJob::result(KJob*, KJob::QPrivateSignal) () at /usr/lib/libKF5CoreAddons.so.5
#22 0x00007f2d63cb080c in KJob::finishJob(bool) () at /usr/lib/libKF5CoreAddons.so.5
#23 0x00007f2d66159b9d in KDevelop::ExecuteCompositeJob::slotResult(KJob*) (this=0x5564ffb8de70, job=0x55650008f780) at ../kdevplatform/util/executecompositejob.cpp:129
#24 0x00007f2d637f4a26 in  () at /usr/lib/libQt5Core.so.5
#25 0x00007f2d63cafced in KJob::result(KJob*, KJob::QPrivateSignal) () at /usr/lib/libKF5CoreAddons.so.5
#26 0x00007f2d63cb080c in KJob::finishJob(bool) () at /usr/lib/libKF5CoreAddons.so.5
#27 0x00007f2d62f3e3e5 in KDevelop::FileManagerListJob::handleResults(QList<KIO::UDSEntry> const&) (this=0x55650008f780, entriesIn=...) at ../kdevplatform/project/filemanagerlistjob.cpp:196
#28 0x00007f2d62f0a4f3 in KDevelop::FileManagerListJob::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) (_o=0x55650008f780, _c=QMetaObject::InvokeMetaMethod, _id=4, _a=0x7f2cf02c8798) at kdevplatform/project/KDevPlatformProject_autogen/EWIEGA46WW/moc_filemanagerlistjob.cpp:108
#29 0x00007f2d637ea1d2 in QObject::event(QEvent*) () at /usr/lib/libQt5Core.so.5
#30 0x00007f2d64641702 in QApplicationPrivate::notify_helper(QObject*, QEvent*) () at /usr/lib/libQt5Widgets.so.5
#31 0x00007f2d637bd7ba in QCoreApplication::notifyInternal2(QObject*, QEvent*) () at /usr/lib/libQt5Core.so.5
#32 0x00007f2d637c02a3 in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) () at /usr/lib/libQt5Core.so.5
#33 0x00007f2d63816cf4 in  () at /usr/lib/libQt5Core.so.5
#34 0x00007f2d616a543c in g_main_context_dispatch () at /usr/lib/libglib-2.0.so.0
#35 0x00007f2d616f31d9 in  () at /usr/lib/libglib-2.0.so.0
#36 0x00007f2d616a4221 in g_main_context_iteration () at /usr/lib/libglib-2.0.so.0
#37 0x00007f2d63816331 in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () at /usr/lib/libQt5Core.so.5
#38 0x00007f2d637bc13c in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) () at /usr/lib/libQt5Core.so.5
#39 0x00007f2d637c45c4 in QCoreApplication::exec() () at /usr/lib/libQt5Core.so.5
#40 0x00005564f984681f in main(int, char**) (argc=3, argv=0x7ffee5c948b8) at ../app/main.cpp:850
[Inferior 1 (process 54203) detached]
Comment 1 Igor Kushnir 2020-10-07 10:23:53 UTC
Git commit 0b037a8cccc80018e5c7d679a9127b0f806aa926 by Igor Kushnir.
Committed on 07/10/2020 at 10:11.
Pushed by igorkushnir into branch '5.6'.

Kill ParseProjectJob before closing its project

Remove the now redundant deleting of the ParseProjectJob when its
project is destroyed. ParseProjectJob objects are created in
ProjectController::reparseProject(), which is called when the project is
already fully open. So the project is guaranteed to be closed before it
is destroyed.

A Project is destroyed via deleteLater() after it is closed, so if the
event loop is busy at the time, &IProject::destroyed can be emitted much
later than IProjectController::projectClosing(). With this commit
ParseProjectJob is killed earlier than it was destroyed without the
commit.

Early-return from ParseProjectJob::queueFilesToParse() not only when the
job has been destroyed, but when it has been killed too. The earlier
return not just avoids unnecessary work, but is essential during the
application shutdown: prevents a crash by not accessing IndexedString
after DUChain::shutdown(). Note that KJob::kill() calls deleteLater(),
so a job can be destroyed a long time after it is killed if the event
loop is busy, as it is at shutdown. ParseProjectJob is killed early in
the shutdown process from RunController::cleanup() - before
ProjectController::cleanup(), which would kill it otherwise, and
(importantly to prevent the crash) before DUChain::shutdown().

Remove the shuttingDown() check from ParseProjectJob::start(), because
this member function doesn't access globals on its own. The appropriate
safety checks are now performed in the scheduled
ParseProjectJob::queueFilesToParse().

Don't call deleteLater() in ParseProjectJob::doKill() just before
returning true: rely on auto-delete KJob base class to call it.
FIXED-IN: 5.6.1

M  +24   -13   kdevplatform/language/backgroundparser/parseprojectjob.cpp
M  +0    -3    kdevplatform/language/backgroundparser/parseprojectjob.h
M  +3    -0    kdevplatform/shell/projectcontroller.cpp

https://invent.kde.org/kdevelop/kdevelop/commit/0b037a8cccc80018e5c7d679a9127b0f806aa926