Details
-
Bug
-
Resolution: Incomplete
-
P1: Critical
-
None
-
5.9.1
-
None
-
I have observed this crash on CentOS 7.3, Ubuntu 17.04, and macOS Sierra.[^qt-5.9.1.patch]
Description
I don't have an easily-distillable example that demonstrates this problem, but I have confirmed that QSGDefaultDistanceFieldGlyphCache can exhibit a use-after-free bug that causes unpredictable behavior (often a segmentation fault).
Steps to reproduce:
- Create a QQuickWidget whose scene graph contains a QML Text item (represented by a QQuickTextNode). I don't think it's key to this issue, but my scene graph contains a Map item, with the Text item added to the Map as a MapQuickItem object.
- Place the QQuickWidget into a QDockWidget that is docked into a QMainWindow.
- Create some code that updates the contents of the text periodically (I don't know how the internals of the scene graph works, so I'm not sure what is required to trigger the bug). For instance, a timer that changes the contents of the text.
- Undock the widget from the main window so it becomes its own top-level window.
- Redock the widget to the main window.
- Repeat steps 4-5 until you see a segmentation fault.
By tracing through the Qt source (the below stack traces are from a macOS build), I found that when you undock the widget from the main window, a resizeEvent() on the QQuickWidget is triggered. This triggers the creation of a new frame buffer object, which results in recreating the OpenGL context. Here is the stack trace where the OpenGL context is destroyed:
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 11.2 * frame #0: 0x0000000107fc24f0 QtGui_debug`QOpenGLFunctions::~QOpenGLFunctions(this=0x000000012555c640) at qopenglfunctions.cpp:285 frame #1: 0x0000000107c84f73 QtGui_debug`QOpenGLContext::destroy(this=0x0000000128094e60) at qopenglcontext.cpp:655 frame #2: 0x0000000107c84d58 QtGui_debug`QOpenGLContext::create(this=0x0000000128094e60) at qopenglcontext.cpp:610 frame #3: 0x0000000110f330f7 QtQuickWidgets_debug`QQuickWidget::createFramebufferObject(this=0x0000000128787a00) at qquickwidget.cpp:934 frame #4: 0x0000000110f3403c QtQuickWidgets_debug`QQuickWidget::resizeEvent(this=0x0000000128787a00, e=0x00007fff5fbf7d08) at qquickwidget.cpp:1196 frame #5: 0x0000000106fa7ad5 QtWidgets_debug`QWidget::event(this=0x0000000128787a00, event=0x00007fff5fbf7d08) at qwidget.cpp:8962 frame #6: 0x0000000110f34e69 QtQuickWidgets_debug`QQuickWidget::event(this=0x0000000128787a00, e=0x00007fff5fbf7d08) at qquickwidget.cpp:1450 frame #7: 0x0000000106f4b74f QtWidgets_debug`QApplicationPrivate::notify_helper(this=0x000000010b70a390, receiver=0x0000000128787a00, e=0x00007fff5fbf7d08) at qapplication.cpp:3717 frame #8: 0x0000000106f50979 QtWidgets_debug`QApplication::notify(this=0x00007fff5fbfd148, receiver=0x0000000128787a00, e=0x00007fff5fbf7d08) at qapplication.cpp:3680 frame #9: 0x0000000108afc665 QtCore_debug`QCoreApplication::notifyInternal2(receiver=0x0000000128787a00, event=0x00007fff5fbf7d08) at qcoreapplication.cpp:1018 frame #10: 0x0000000106f379a8 QtWidgets_debug`QCoreApplication::sendEvent(receiver=0x0000000128787a00, event=0x00007fff5fbf7d08) at qcoreapplication.h:233 frame #11: 0x0000000106f9de01 QtWidgets_debug`QWidgetPrivate::sendPendingMoveAndResizeEvents(this=0x0000000128787de0, recursive=false, disableUpdates=false) at qwidget.cpp:7826 frame #12: 0x0000000106fa5a5a QtWidgets_debug`QWidgetPrivate::show_helper(this=0x0000000128787de0) at qwidget.cpp:7877 frame #13: 0x0000000106fa5a02 QtWidgets_debug`QWidgetPrivate::show_recursive(this=0x0000000128787de0) at qwidget.cpp:7807 frame #14: 0x0000000106fa5e6e QtWidgets_debug`QWidgetPrivate::showChildren(this=0x00000001274d1650, spontaneous=false) at qwidget.cpp:8313 frame #15: 0x0000000106fa5a78 QtWidgets_debug`QWidgetPrivate::show_helper(this=0x00000001274d1650) at qwidget.cpp:7883 frame #16: 0x0000000106fa6afd QtWidgets_debug`QWidget::setVisible(this=0x00000001274d1600, visible=true) at qwidget.cpp:8232 frame #17: 0x0000000106fa5906 QtWidgets_debug`QWidget::show(this=0x00000001274d1600) at qwidget.cpp:7784 frame #18: 0x00000001070eb28d QtWidgets_debug`QDockWidgetPrivate::setWindowState(this=0x00000001274d1650, floating=true, unplug=true, rect=0x00007fff5fbf8118) at qdockwidget.cpp:1120 frame #19: 0x00000001070eb063 QtWidgets_debug`QDockWidgetPrivate::unplug(this=0x00000001274d1650, rect=0x00007fff5fbf8238) at qdockwidget.cpp:1068 frame #20: 0x000000010712a9e9 QtWidgets_debug`QMainWindowLayout::unplug(this=0x000000010d063600, widget=0x00000001274d1600, group=true) at qmainwindowlayout.cpp:2425 frame #21: 0x00000001070e9f0f QtWidgets_debug`QDockWidgetPrivate::startDrag(this=0x00000001274d1650, group=true) at qdockwidget.cpp:762 frame #22: 0x00000001070ea867 QtWidgets_debug`QDockWidgetPrivate::mouseMoveEvent(this=0x00000001274d1650, event=0x00007fff5fbf9068) at qdockwidget.cpp:921 frame #23: 0x00000001070ec31b QtWidgets_debug`QDockWidget::event(this=0x00000001274d1600, event=0x00007fff5fbf9068) at qdockwidget.cpp:1519 frame #24: 0x0000000106f4b74f QtWidgets_debug`QApplicationPrivate::notify_helper(this=0x000000010b70a390, receiver=0x00000001274d1600, e=0x00007fff5fbf9068) at qapplication.cpp:3717 frame #25: 0x0000000106f4e044 QtWidgets_debug`QApplication::notify(this=0x00007fff5fbfd148, receiver=0x0000000129440a50, e=0x00007fff5fbf9658) at qapplication.cpp:3193 frame #26: 0x0000000108afc665 QtCore_debug`QCoreApplication::notifyInternal2(receiver=0x0000000129440a50, event=0x00007fff5fbf9658) at qcoreapplication.cpp:1018 frame #27: 0x0000000106f4a2cc QtWidgets_debug`QCoreApplication::sendSpontaneousEvent(receiver=0x0000000129440a50, event=0x00007fff5fbf9658) at qcoreapplication.h:236 frame #28: 0x0000000106f4c7f0 QtWidgets_debug`QApplicationPrivate::sendMouseEvent(receiver=0x0000000129440a50, event=0x00007fff5fbf9658, alienWidget=0x0000000129440a50, nativeWidget=0x00007fff5fbfcfa8, buttonDown=0x00000001075a5048, lastMouseReceiver=0x00000001075a5060, spontaneous=true) at qapplication.cpp:2697 frame #29: 0x0000000106fd60e2 QtWidgets_debug`QWidgetWindow::handleMouseEvent(this=0x000000010c973cd0, event=0x00007fff5fbfaa28) at qwidgetwindow.cpp:621 frame #30: 0x0000000106fd47f6 QtWidgets_debug`QWidgetWindow::event(this=0x000000010c973cd0, event=0x00007fff5fbfaa28) at qwidgetwindow.cpp:243 frame #31: 0x0000000106f4b74f QtWidgets_debug`QApplicationPrivate::notify_helper(this=0x000000010b70a390, receiver=0x000000010c973cd0, e=0x00007fff5fbfaa28) at qapplication.cpp:3717 frame #32: 0x0000000106f4d827 QtWidgets_debug`QApplication::notify(this=0x00007fff5fbfd148, receiver=0x000000010c973cd0, e=0x00007fff5fbfaa28) at qapplication.cpp:3089 frame #33: 0x0000000108afc665 QtCore_debug`QCoreApplication::notifyInternal2(receiver=0x000000010c973cd0, event=0x00007fff5fbfaa28) at qcoreapplication.cpp:1018 frame #34: 0x0000000107c21c0c QtGui_debug`QCoreApplication::sendSpontaneousEvent(receiver=0x000000010c973cd0, event=0x00007fff5fbfaa28) at qcoreapplication.h:236 frame #35: 0x0000000107c1d619 QtGui_debug`QGuiApplicationPrivate::processMouseEvent(e=0x00000001287d7d40) at qguiapplication.cpp:1949 frame #36: 0x0000000107c1caa7 QtGui_debug`QGuiApplicationPrivate::processWindowSystemEvent(e=0x00000001287d7d40) at qguiapplication.cpp:1733 frame #37: 0x0000000107bfa464 QtGui_debug`QWindowSystemInterface::sendWindowSystemEvents(flags=(i = 0)) at qwindowsysteminterface.cpp:939 frame #38: 0x00000001191d2ee1 libqcocoa_debug.dylib`QCocoaEventDispatcherPrivate::processPostedEvents(this=0x000000010c928190) at qcocoaeventdispatcher.mm:889 frame #39: 0x00000001191d3d8f libqcocoa_debug.dylib`QCocoaEventDispatcherPrivate::postedEventsSourceCallback(info=0x000000010c928190) at qcocoaeventdispatcher.mm:925
The QQuickTextNode in the scene graph creates a QSGDistanceFieldGlyphNode object to represent the distance field glyphs that are used to render the text. The QSGDistanceFieldGlyphNode object has a member variable called m_glyph_cache of type QSGDefaultDistanceFieldGlyphCache *. This cache is used during the rendering process.
QSGDefaultDistanceFieldGlyphCache has a member variable called m_funcs of type QOpenGLFunctions *. This pointer is used to access the OpenGL API function table as needed. However, it is initialized in the QSGDefaultDistanceFieldGlyphCache constructor to the QOpenGLFunctions object associated with the context provided to the constructor. There is no protection to catch the case where that context is later destroyed while the QSGDefaultDistanceFieldGlyphCache object is still in use (which is what occurs during the undocking process described above). The cache retains a dangling pointer to the functions table associated with the now-destroyed context, which resides in memory that is now freed for use by the allocator again. Therefore, the memory pointed to by m_funcs may be reallocated somewhere else in the application and overwritten, resulting in corruption of the function table (and a segmentation fault).
Here's an example of the stack trace at the point of the crash:
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0xd00000003) * frame #0: 0x0000000d00000003 frame #1: 0x0000000109dd1baf QtQuick_debug`QOpenGLFunctions::glGetIntegerv(this=0x000000012555c640, pname=3317, params=0x00007fff5fbf92ac) at qopenglfunctions.h:851 frame #2: 0x0000000109e345de QtQuick_debug`QSGDefaultDistanceFieldGlyphCache::storeGlyphs(this=0x000000012ad65bc0, glyphs=0x00007fff5fbf9380) at qsgdefaultdistancefieldglyphcache.cpp:166 frame #3: 0x0000000109e14327 QtQuick_debug`QSGDistanceFieldGlyphCache::update(this=0x000000012ad65bc0) at qsgadaptationlayer.cpp:197 frame #4: 0x0000000109e39c55 QtQuick_debug`QSGDistanceFieldGlyphNode::preprocess(this=0x000000010c963120) at qsgdistancefieldglyphnode.cpp:165 frame #5: 0x0000000109dd9552 QtQuick_debug`QSGRenderer::preprocess(this=0x000000012ad82ac0) at qsgrenderer.cpp:302 frame #6: 0x0000000109dd8da7 QtQuick_debug`QSGRenderer::renderScene(this=0x000000012ad82ac0, bindable=0x00007fff5fbf9530) at qsgrenderer.cpp:218 frame #7: 0x0000000109dd8cb5 QtQuick_debug`QSGRenderer::renderScene(this=0x000000012ad82ac0, fboId=1) at qsgrenderer.cpp:187 frame #8: 0x0000000109e41514 QtQuick_debug`QSGDefaultRenderContext::renderNextFrame(this=0x0000000128786ed0, renderer=0x000000012ad82ac0, fboId=1) at qsgdefaultrendercontext.cpp:181 frame #9: 0x0000000109ebf8c9 QtQuick_debug`QQuickWindowPrivate::renderSceneGraph(this=0x0000000128786fb0, size=0x00007fff5fbf9700) at qquickwindow.cpp:470 frame #10: 0x0000000109fe6f4b QtQuick_debug`QQuickRenderControl::render(this=0x0000000128788070) at qquickrendercontrol.cpp:344 frame #11: 0x0000000110f30880 QtQuickWidgets_debug`QQuickWidgetPrivate::render(this=0x0000000128787de0, needsSync=true) at qquickwidget.cpp:287 frame #12: 0x0000000110f30bd9 QtQuickWidgets_debug`QQuickWidgetPrivate::renderSceneGraph(this=0x0000000128787de0) at qquickwidget.cpp:334 frame #13: 0x0000000110f33d1b QtQuickWidgets_debug`QQuickWidget::timerEvent(this=0x0000000128787a00, e=0x00007fff5fbfad58) at qquickwidget.cpp:1114 frame #14: 0x0000000108b495c1 QtCore_debug`QObject::event(this=0x0000000128787a00, e=0x00007fff5fbfad58) at qobject.cpp:1227 frame #15: 0x0000000106fa890b QtWidgets_debug`QWidget::event(this=0x0000000128787a00, event=0x00007fff5fbfad58) at qwidget.cpp:9244 frame #16: 0x0000000110f34e69 QtQuickWidgets_debug`QQuickWidget::event(this=0x0000000128787a00, e=0x00007fff5fbfad58) at qquickwidget.cpp:1450 frame #17: 0x0000000106f4b74f QtWidgets_debug`QApplicationPrivate::notify_helper(this=0x000000010b70a390, receiver=0x0000000128787a00, e=0x00007fff5fbfad58) at qapplication.cpp:3717 frame #18: 0x0000000106f50979 QtWidgets_debug`QApplication::notify(this=0x00007fff5fbfd148, receiver=0x0000000128787a00, e=0x00007fff5fbfad58) at qapplication.cpp:3680 frame #19: 0x0000000108afc665 QtCore_debug`QCoreApplication::notifyInternal2(receiver=0x0000000128787a00, event=0x00007fff5fbfad58) at qcoreapplication.cpp:1018 frame #20: 0x0000000108afdfa8 QtCore_debug`QCoreApplication::sendEvent(receiver=0x0000000128787a00, event=0x00007fff5fbfad58) at qcoreapplication.h:233 frame #21: 0x0000000108ba07e8 QtCore_debug`QTimerInfoList::activateTimers(this=0x000000010c928210) at qtimerinfo_unix.cpp:643 frame #22: 0x00000001191d0db4 libqcocoa_debug.dylib`QCocoaEventDispatcherPrivate::activateTimersSourceCallback(info=0x000000010c928190) at qcocoaeventdispatcher.mm:124
It's not clear to me what the appropriate fix is here. I found that if I discontinued use of m_funcs altogether and instead always used the current OpenGL context to provide the QOpenGLFunctions object, the crash seemed to go away. For reference, I attached a quick patch that implements this (although I'm sure it needs some massaging before it would be suitable for merging).