Bug 427379 - Segfault in ProjectFileDataProvider::projectOpened() when disabling Quick Open plugin right after a large project is opened
Summary: Segfault in ProjectFileDataProvider::projectOpened() when disabling Quick Ope...
Status: RESOLVED FIXED
Alias: None
Product: kdevelop
Classification: Applications
Component: UI: QuickOpen (show other bugs)
Version: git master
Platform: Compiled Sources Linux
: NOR crash
Target Milestone: ---
Assignee: Igor Kushnir
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2020-10-06 09:32 UTC by Igor Kushnir
Modified: 2021-02-13 08:37 UTC (History)
0 users

See Also:
Latest Commit:
Version Fixed In: 5.7.0
Sentry Crash Report:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Igor Kushnir 2020-10-06 09:32:39 UTC
SUMMARY
When a user disables Quick Open plugin right after a large project is opened, before ProjectFileDataProvider::projectOpened() slot returns, the ProjectFileDataProvider object is already destroyed when the projectOpened()'s QApplication::processEvents() returns. This causes a segmentation fault when the destroyed ProjectFileDataProvider resumes adding files to set.

STEPS TO REPRODUCE
1. Open a large project with tens of thousands of files, such as KDevelop itself.
2. Locate the Quick Open plugin on KDevelop's Configure=>Plugins page and uncheck its checkbox while the project is being loaded. Do not press "Apply" or "OK" button yet.
3. As soon as the just opened project appears in Projects tool view, press the "OK" button in the KDevelop's Configure dialog.

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: Segmentation fault

[KCrash Handler]
#4  std::__atomic_base<int>::load(std::memory_order) const (__m=std::memory_order_relaxed, this=0x40) at /usr/include/c++/10.2.0/bits/atomic_base.h:426
#5  QAtomicOps<int>::loadRelaxed<int>(std::atomic<int> const&) (_q_value=...) at /usr/include/qt/QtCore/qatomic_cxx11.h:239
#6  0x00007f44103a5a1a in QBasicAtomicInteger<int>::loadRelaxed() const (this=0x40) at /usr/include/qt/QtCore/qbasicatomic.h:107
#7  0x00007f44103af5c0 in QtPrivate::RefCount::isShared() const (this=0x40) at /usr/include/qt/QtCore/qrefcount.h:101
#8  0x00007f44103cc49b in QVector<ProjectFile>::isDetached() const (this=0x55c4d27d0ab0) at /usr/include/qt/QtCore/qvector.h:113
#9  0x00007f44103cb64b in QVector<ProjectFile>::detach() (this=0x55c4d27d0ab0) at /usr/include/qt/QtCore/qvector.h:405
#10 0x00007f44103c9962 in QVector<ProjectFile>::end(QTypedArrayData<ProjectFile>::iterator) (this=0x55c4d27d0ab0) at /usr/include/qt/QtCore/qvector.h:223
#11 0x00007f44103c6d82 in ProjectFileDataProvider::fileAddedToSet(KDevelop::ProjectFileItem*) (this=0x55c4d27d0a80, file=0x7f44381208e0) at ../plugins/quickopen/projectfilequickopen.cpp:270
#12 0x00007f44103c6a69 in ProjectFileDataProvider::projectOpened(KDevelop::IProject*) (this=0x55c4d27d0a80, project=0x55c4d807eb10) at ../plugins/quickopen/projectfilequickopen.cpp:249
#13 0x00007f44103d03e8 in QtPrivate::FunctorCall<QtPrivate::IndexesList<0>, QtPrivate::List<KDevelop::IProject*>, void, void (ProjectFileDataProvider::*)(KDevelop::IProject*)>::call(void (ProjectFileDataProvider::*)(KDevelop::IProject*), ProjectFileDataProvider*, void**) (f=(void (ProjectFileDataProvider::*)(ProjectFileDataProvider * const, KDevelop::IProject *)) 0x7f44103c6984 <ProjectFileDataProvider::projectOpened(KDevelop::IProject*)>, o=0x55c4d27d0a80, arg=0x7ffe68903210) at /usr/include/qt/QtCore/qobjectdefs_impl.h:152
#14 0x00007f44103ced66 in QtPrivate::FunctionPointer<void (ProjectFileDataProvider::*)(KDevelop::IProject*)>::call<QtPrivate::List<KDevelop::IProject*>, void>(void (ProjectFileDataProvider::*)(KDevelop::IProject*), ProjectFileDataProvider*, void**) (f=(void (ProjectFileDataProvider::*)(ProjectFileDataProvider * const, KDevelop::IProject *)) 0x7f44103c6984 <ProjectFileDataProvider::projectOpened(KDevelop::IProject*)>, o=0x55c4d27d0a80, arg=0x7ffe68903210) at /usr/include/qt/QtCore/qobjectdefs_impl.h:185
#15 0x00007f44103cd69a in QtPrivate::QSlotObject<void (ProjectFileDataProvider::*)(KDevelop::IProject*), QtPrivate::List<KDevelop::IProject*>, void>::impl(int, QtPrivate::QSlotObjectBase*, QObject*, void**, bool*) (which=1, this_=0x55c4d10ca8a0, r=0x55c4d27d0a80, a=0x7ffe68903210, ret=0x0) at /usr/include/qt/QtCore/qobjectdefs_impl.h:418
#16 0x00007f44aa18fa26 in QtPrivate::QSlotObjectBase::call(QObject*, void**) (a=0x7ffe68903210, r=0x55c4d27d0a80, this=0x55c4d10ca8a0) at ../../include/QtCore/../../src/corelib/kernel/qobjectdefs_impl.h:398
#17 doActivate<false>(QObject*, int, void**) (sender=0x55c4cfc1dde0, signal_index=4, argv=0x7ffe68903210) at kernel/qobject.cpp:3886
#18 0x00007f44aca91550 in KDevelop::IProjectController::projectOpened(KDevelop::IProject*) (this=0x55c4cfc1dde0, _t1=0x55c4d807eb10) at kdevplatform/interfaces/KDevPlatformInterfaces_autogen/EWIEGA46WW/moc_iprojectcontroller.cpp:266
#19 0x00007f44adf7e48c in KDevelop::ProjectController::projectImportingFinished(KDevelop::IProject*) (this=0x55c4cfc1dde0, project=0x55c4d807eb10) at ../kdevplatform/shell/projectcontroller.cpp:1000
#20 0x00007f44adf9738d in KDevelop::ProjectPrivate::importDone(KJob*) (this=0x55c4d45570c0, job=0x55c4d7c37b70) at ../kdevplatform/shell/project.cpp:221
#21 0x00007f44adf94c0a in operator()(KJob*) const (__closure=0x55c4d326e220, job=0x55c4d7c37b70) at ../kdevplatform/shell/project.cpp:548
#22 0x00007f44adf96d7c in QtPrivate::FunctorCall<QtPrivate::IndexesList<0>, QtPrivate::List<KJob*>, void, KDevelop::Project::open(const KDevelop::Path&)::<lambda(KJob*)> >::call(struct {...} &, void **) (f=..., arg=0x7ffe689034d0) at /usr/include/qt/QtCore/qobjectdefs_impl.h:146
#23 0x00007f44adf96cd4 in QtPrivate::Functor<KDevelop::Project::open(const KDevelop::Path&)::<lambda(KJob*)>, 1>::call<QtPrivate::List<KJob*>, void>(struct {...} &, void *, void **) (f=..., arg=0x7ffe689034d0) at /usr/include/qt/QtCore/qobjectdefs_impl.h:256
#24 0x00007f44adf96c79 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_=0x55c4d326e210, r=0x55c4d807eb10, a=0x7ffe689034d0, ret=0x0) at /usr/include/qt/QtCore/qobjectdefs_impl.h:443
#25 0x00007f44aa18fa26 in QtPrivate::QSlotObjectBase::call(QObject*, void**) (a=0x7ffe689034d0, r=0x55c4d807eb10, this=0x55c4d326e210) at ../../include/QtCore/../../src/corelib/kernel/qobjectdefs_impl.h:398
#26 doActivate<false>(QObject*, int, void**) (sender=0x55c4d7c37b70, signal_index=6, argv=argv@entry=0x7ffe689034d0) at kernel/qobject.cpp:3886
#27 0x00007f44aa188bc0 in QMetaObject::activate(QObject*, QMetaObject const*, int, void**) (sender=sender@entry=0x55c4d7c37b70, m=m@entry=0x7f44aa69ae00 <KJob::staticMetaObject>, local_signal_index=local_signal_index@entry=3, argv=argv@entry=0x7ffe689034d0) at kernel/qobject.cpp:3946
#28 0x00007f44aa648e0d in KJob::result(KJob*, KJob::QPrivateSignal) (this=this@entry=0x55c4d7c37b70, _t1=<optimized out>, _t1@entry=0x55c4d7c37b70, _t2=...) at /usr/src/debug/build/src/lib/KF5CoreAddons_autogen/include/moc_kjob.cpp:576
#29 0x00007f44aa64992c in KJob::finishJob(bool) (this=0x55c4d7c37b70, emitResult=<optimized out>) at /usr/src/debug/kcoreaddons-5.73.0/src/lib/jobs/kjob.cpp:96
#30 0x00007f44acafd71d in KDevelop::ExecuteCompositeJob::slotResult(KJob*) (this=0x55c4d7c37b70, job=0x55c4d3181150) at ../kdevplatform/util/executecompositejob.cpp:129
#31 0x00007f44aa18fa26 in QtPrivate::QSlotObjectBase::call(QObject*, void**) (a=0x7ffe689036a0, r=0x55c4d7c37b70, this=0x55c4d8159390) at ../../include/QtCore/../../src/corelib/kernel/qobjectdefs_impl.h:398
#32 doActivate<false>(QObject*, int, void**) (sender=0x55c4d3181150, signal_index=6, argv=argv@entry=0x7ffe689036a0) at kernel/qobject.cpp:3886
#33 0x00007f44aa188bc0 in QMetaObject::activate(QObject*, QMetaObject const*, int, void**) (sender=sender@entry=0x55c4d3181150, m=m@entry=0x7f44aa69ae00 <KJob::staticMetaObject>, local_signal_index=local_signal_index@entry=3, argv=argv@entry=0x7ffe689036a0) at kernel/qobject.cpp:3946
#34 0x00007f44aa648e0d in KJob::result(KJob*, KJob::QPrivateSignal) (this=this@entry=0x55c4d3181150, _t1=<optimized out>, _t1@entry=0x55c4d3181150, _t2=...) at /usr/src/debug/build/src/lib/KF5CoreAddons_autogen/include/moc_kjob.cpp:576
#35 0x00007f44aa64992c in KJob::finishJob(bool) (this=0x55c4d3181150, emitResult=<optimized out>) at /usr/src/debug/kcoreaddons-5.73.0/src/lib/jobs/kjob.cpp:96
#36 0x00007f44a98d43e5 in KDevelop::FileManagerListJob::handleResults(QList<KIO::UDSEntry> const&) (this=0x55c4d3181150, entriesIn=...) at ../kdevplatform/project/filemanagerlistjob.cpp:196
#37 0x00007f44a98a04f3 in KDevelop::FileManagerListJob::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) (_o=0x55c4d3181150, _c=QMetaObject::InvokeMetaMethod, _id=4, _a=0x7f443c9f67f8) at kdevplatform/project/KDevPlatformProject_autogen/EWIEGA46WW/moc_filemanagerlistjob.cpp:108
#38 0x00007f44aa1851d2 in QObject::event(QEvent*) (this=0x55c4d3181150, e=0x7f443c9f67b0) at kernel/qobject.cpp:1314
#39 0x00007f44aafe3702 in QApplicationPrivate::notify_helper(QObject*, QEvent*) (this=<optimized out>, receiver=0x55c4d3181150, e=0x7f443c9f67b0) at kernel/qapplication.cpp:3671
#40 0x00007f44aa1587ba in QCoreApplication::notifyInternal2(QObject*, QEvent*) (receiver=0x55c4d3181150, event=0x7f443c9f67b0) at ../../include/QtCore/5.15.0/QtCore/private/../../../../../src/corelib/thread/qthread_p.h:325
#41 0x00007f44aa15b2a3 in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) (receiver=0x0, event_type=0, data=0x55c4cf0ef090) at kernel/qcoreapplication.cpp:1815
#42 0x00007f44aa1b1cf4 in postEventSourceDispatch(GSource*, GSourceFunc, gpointer) (s=0x55c4cf1c26c0) at kernel/qeventdispatcher_glib.cpp:277
#43 0x00007f44a650b43c in g_main_context_dispatch () at /usr/lib/libglib-2.0.so.0
#44 0x00007f44a65591d9 in  () at /usr/lib/libglib-2.0.so.0
#45 0x00007f44a650a221 in g_main_context_iteration () at /usr/lib/libglib-2.0.so.0
#46 0x00007f44aa1b1331 in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) (this=0x55c4cf103cd0, flags=...) at kernel/qeventdispatcher_glib.cpp:423
#47 0x00007f44aa15713c in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) (this=this@entry=0x7ffe68903b10, flags=..., flags@entry=...) at ../../include/QtCore/../../src/corelib/global/qflags.h:141
#48 0x00007f44ab1e6bda in QDialog::exec() (this=0x7ffe68903c60) at ../../include/QtCore/../../src/corelib/global/qflags.h:121
#49 0x00007f44adf6ee32 in KDevelop::UiController::showSettingsDialog() (this=0x55c4cf7aa980) at ../kdevplatform/shell/uicontroller.cpp:580
#50 0x00007f44adf5304b in KDevelop::MainWindowPrivate::settingsDialog() (this=0x55c4cf821c50) at ../kdevplatform/shell/mainwindow_actions.cpp:110
#51 0x00007f44adf17e50 in KDevelop::MainWindowPrivate::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) (_o=0x55c4cf821c50, _c=QMetaObject::InvokeMetaMethod, _id=21, _a=0x7ffe68903eb0) at kdevplatform/shell/KDevPlatformShell_autogen/EWIEGA46WW/moc_mainwindow_p.cpp:227
#52 0x00007f44aa18fa60 in doActivate<false>(QObject*, int, void**) (sender=0x55c4cf8dc1e0, signal_index=4, argv=argv@entry=0x7ffe68903eb0) at ../../include/QtCore/../../src/corelib/kernel/qobjectdefs_impl.h:395
#53 0x00007f44aa188bc0 in QMetaObject::activate(QObject*, QMetaObject const*, int, void**) (sender=sender@entry=0x55c4cf8dc1e0, m=m@entry=0x7f44ab4ecc20 <QAction::staticMetaObject>, local_signal_index=local_signal_index@entry=1, argv=argv@entry=0x7ffe68903eb0) at kernel/qobject.cpp:3946
#54 0x00007f44aafdcf23 in QAction::triggered(bool) (this=this@entry=0x55c4cf8dc1e0, _t1=<optimized out>) at .moc/moc_qaction.cpp:376
#55 0x00007f44aafdf805 in QAction::activate(QAction::ActionEvent) (this=this@entry=0x55c4cf8dc1e0, event=event@entry=QAction::Trigger) at kernel/qaction.cpp:1161
#56 0x00007f44ab160f4b in QMenuPrivate::activateCausedStack(QVector<QPointer<QWidget> > const&, QAction*, QAction::ActionEvent, bool) (this=this@entry=0x55c4cfcea550, causedStack=..., action=action@entry=0x55c4cf8dc1e0, action_e=action_e@entry=QAction::Trigger, self=self@entry=true) at widgets/qmenu.cpp:1353
#57 0x00007f44ab168582 in QMenuPrivate::activateAction(QAction*, QAction::ActionEvent, bool) (this=0x55c4cfcea550, action=0x55c4cf8dc1e0, action_e=QAction::Trigger, self=<optimized out>) at widgets/qmenu.cpp:1430
#58 0x00007f44ab024c8e in QWidget::event(QEvent*) (this=0x55c4cfcec3d0, event=0x7ffe68904530) at kernel/qwidget.cpp:9027
#59 0x00007f44aafe3702 in QApplicationPrivate::notify_helper(QObject*, QEvent*) (this=this@entry=0x55c4cf0ffdb0, receiver=receiver@entry=0x55c4cfcec3d0, e=e@entry=0x7ffe68904530) at kernel/qapplication.cpp:3671
#60 0x00007f44aafea987 in QApplication::notify(QObject*, QEvent*) (this=0x7ffe68904230, receiver=0x55c4cfcec3d0, e=0x7ffe68904530) at kernel/qapplication.cpp:3115
#61 0x00007f44aa1587ba in QCoreApplication::notifyInternal2(QObject*, QEvent*) (receiver=0x55c4cfcec3d0, event=0x7ffe68904530) at ../../include/QtCore/5.15.0/QtCore/private/../../../../../src/corelib/thread/qthread_p.h:325
#62 0x00007f44aafe986e in QApplicationPrivate::sendMouseEvent(QWidget*, QMouseEvent*, QWidget*, QWidget*, QWidget**, QPointer<QWidget>&, bool, bool) (receiver=0x55c4cfcec3d0, event=event@entry=0x7ffe68904530, alienWidget=0x0, nativeWidget=0x55c4cfcec3d0, buttonDown=buttonDown@entry=0x7f44ab51c350 <qt_button_down>, lastMouseReceiver=..., spontaneous=true, onlyDispatchEnterLeave=false) at kernel/qapplication.cpp:2603
#63 0x00007f44ab03d94f in QWidgetWindow::handleMouseEvent(QMouseEvent*) (this=0x55c4d22a1df0, event=0x7ffe689047f0) at /usr/include/c++/10.1.0/bits/atomic_base.h:420
#64 0x00007f44ab0407bf in QWidgetWindow::event(QEvent*) (this=0x55c4d22a1df0, event=0x7ffe689047f0) at kernel/qwidgetwindow.cpp:296
#65 0x00007f44aafe3702 in QApplicationPrivate::notify_helper(QObject*, QEvent*) (this=<optimized out>, receiver=0x55c4d22a1df0, e=0x7ffe689047f0) at kernel/qapplication.cpp:3671
#66 0x00007f44aa1587ba in QCoreApplication::notifyInternal2(QObject*, QEvent*) (receiver=0x55c4d22a1df0, event=0x7ffe689047f0) at ../../include/QtCore/5.15.0/QtCore/private/../../../../../src/corelib/thread/qthread_p.h:325
#67 0x00007f44aa7df37c in QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::MouseEvent*) (e=0x55c4d30d5410) at kernel/qguiapplication.cpp:2214
#68 0x00007f44aa7b4b4c in QWindowSystemInterface::sendWindowSystemEvents(QFlags<QEventLoop::ProcessEventsFlag>) (flags=flags@entry=...) at kernel/qwindowsysteminterface.cpp:1175
#69 0x00007f4499103c8c in xcbSourceDispatch(GSource*, GSourceFunc, gpointer) (source=<optimized out>) at qxcbeventdispatcher.cpp:105
#70 0x00007f44a650b43c in g_main_context_dispatch () at /usr/lib/libglib-2.0.so.0
#71 0x00007f44a65591d9 in  () at /usr/lib/libglib-2.0.so.0
#72 0x00007f44a650a221 in g_main_context_iteration () at /usr/lib/libglib-2.0.so.0
#73 0x00007f44aa1b1331 in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) (this=0x55c4cf103cd0, flags=...) at kernel/qeventdispatcher_glib.cpp:423
#74 0x00007f44aa15713c in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) (this=this@entry=0x7ffe68904b20, flags=..., flags@entry=...) at ../../include/QtCore/../../src/corelib/global/qflags.h:141
#75 0x00007f44aa15f5c4 in QCoreApplication::exec() () at ../../include/QtCore/../../src/corelib/global/qflags.h:121
#76 0x000055c4cdac984a in main(int, char**) (argc=3, argv=0x7ffe68904f98) at ../app/main.cpp:856
[Inferior 1 (process 212766) detached]
Comment 1 Igor Kushnir 2021-02-13 08:37:33 UTC
Git commit 3f1b6392cb528d7b8986ab9a0a641cd9e346ef8d by Igor Kushnir.
Committed on 13/02/2021 at 08:36.
Pushed by igorkushnir into branch 'master'.

Don't process events in ProjectFileDataProvider::projectOpened()

When a user exits KDevelop or closes the just opened project in the
ProjectFileDataProvider::projectOpened()'s nested event loop,
IProjectController::projectClosing() signal is emitted before some slots
connected to IProjectController::projectOpened() signal ale invoked
(more precisely, the slots that were connected to projectOpened() signal
later than the ProjectFileDataProvider::projectOpened() slot). Thus a
valid assumption that a slot connected to projectOpened() is always
called before a slot connected to projectClosing() becomes incorrect and
causes a crash, e.g. a segmentation fault in
ClassModel::removeProjectNode(). In addition,
ProjectController::projectImportingFinished() calls reparseProject()
after emitting the projectOpened() signal. Delaying this call until
after the project is closed and possibly destroyed would likely case a
crash or some other bug.

Queuing the slow code and its associated QApplication::processEvents()
instead of calling it directly in
ProjectFileDataProvider::projectOpened() would not eliminate the crash,
but move the segmentation fault from an unrelated class to
ProjectFileDataProvider::fileAddedToSet().

When the user disables the Quick Open plugin in this projectOpened()'s
nested event loop, a similar crash occurs.

These crashes could be fixed by checking whether the project is still
open and checking QPointer{this} after the call to
QApplication::processEvents(). But the code wouldn't become crash-proof
even then. ProjectFileDataProvider should not enter a nested event loop
while iterating over the project tree, because a project item can be
destroyed during the event loop when a user removes a file, which can
make one of the pointers to ProjectBaseItem held by
KDevelop::forEachFile() dangling.

The benefits of eliminating the nested event loop:
1) no need for verbose crash workarounds;
2) opening projects can be optimized by sorting and merging sorted
ranges. In particular, a performance pitfall, when multiple large
projects are opened in a certain order, can be eliminated.
Related: bug 427378
FIXED-IN: 5.7.0

M  +0    -8    plugins/quickopen/projectfilequickopen.cpp

https://invent.kde.org/kdevelop/kdevelop/commit/3f1b6392cb528d7b8986ab9a0a641cd9e346ef8d