Read access violation in QAbstractItemView::event -> QAbstractScrollArea::event -> QPainter::QPainter -> QPainter::begin
-
I'm trying to fix a large C++ legacy application that intermixes Qt with MFC. The main application contains data points in a mapped area. The user can click a "Find" function that uses Qt to pop up a window with a list of items held in a QTableView object. The items are allocated in the C++ heap and are basically DAO's on steroids.
When the user pops open the Find dialog, the backing code sorts the list via emission of a series of Qt signals. The user can filter the items in the view via a TextBox control where text patterns are entered to whittle down the list of items to those that match the entered text. By default everything is viewable.
The internal backing code will rebuild and resort the list based on the changes made. The user can also select an item for editing. This pops up an "Edit" MFC dialog where changes can be made to the item, and either [OK] or [Cancel] can be clicked. The "Find" Qt dialog handles these events separately but in either case will refresh itself.
In very random and difficult-to-recreate cases, upon return to the Find dialog the code crashes deep in the Qt event handling. I have set breakpoints, and I can see what's happening, however, while I've been around 30+ years and am very comfortable stepping through assembler code, I am not a Qt-savvy programmer so I'm looking for some guidance/ideas to help track this down.
I've been able to establish the crash is occurring in some element of the refreshing logic. It appears to me that when the Edit (MFC) dialog exits, the Find (Qt) dialog issues an update and a re-select of the item(s) that was chosen for edit -- guessing the author found that upon return the selection index was lost (?). At any rate, the calls to "table->selectionModel()->setCurrentIndex" and "table->scrollTo" are (I think) scheduling update and selection events.
Later in what appears to be Qt event processing, a QEvent::Paint (0x0C) bubbles up the stack eventually reaching the QAbstractItemView::event, which invokes the QAbstractScrollArea::event code. There a local QPainter p(this) is instantiated which will be passed in the next line to a style()->drawPrimitive method. The "this" pointer is the address of the QTableView object in the Find Qt dialog.
In the constructor for the QPainter, the begin method is called with this same QTableView object referenced as a QPaintDevice. begin invokes a "redirected" method with a reference to a local QPoint object that redirected is populating and a return value from redirected of type **QPaintDevice ***.
This does not appear extraordinary, as I've traced non-crashing events through this code and it seems like most of the time -- maybe all of the time -- the redirected method returns a valid pointer to a different QPaintDevice. What is happening on a relatively random basis, however, is that the QPaintDevice pointer returned by redirected is invalid. It's non-null but the data is wonky, like values of 0xfeedfeed, a value in the painters property of 65501, etc. I believe (theory) the object has been deleted and garbage collected.
I'm in a debug crashed state right now, and the exception/error is being thrown inside begin and from a QArrayData::shared_null invocation.
What I "think" is happening is some edge condition where in certain cases the underlying redirected QPaintDevice has been dereferenced or deleted or corrupted, not sure which, but if I go with the assumption that it's not data corruption then maybe it's normal for Qt to delete and reinitialize a redirected QPaintDevice and that the real problem is the architecture of this legacy app in the way MFC & Qt are interacting.
I believe the Qt version is 5.2.1 from this comment in a moc_ file: "Created by: The Qt Meta Object Compiler version 67 (Qt 5.2.1)". The legacy C++ application is compiled in MS Visual Studio 2008 (yes, I know that's way old, but that's what it uses). Due to certification and compliance issues, I do not have the option to simply migrate the application to a newer Visual Studio and/or newer Qt library. Plus this app is HUGE (over 1000 C++ files), and I'm just trying to fix one intermittent crash. Migration would likely be an order of magnitude more work.
Does anyone have an idea of where I should look? It may be that everything Qt is doing is perfectly acceptable and that the original author was violating some of Qt's rules of engagement. Since the error occurs in event processing, is very random, and I'm not well versed in Qt's GUI processing flow, it's difficult for me to determine where to set my breakpoints or even what to conclude should be going on versus what is going on. I am working off the notion this is a race condition between the code the author wrote that's attempting to update the screen with Qt's screen-processing model. I may be wrong, but since I can often crash the app simply by invoking the Edit dialog and [Cancel]'ing out where nothing's actually changed, I'm going with a timing error (race condition). I'm also guessing the original author wasn't well versed in Qt, but I don't know that (I've been told this code is fairly)
I'm new to this forum, so my initial post was empty. I'm not sure exactly how to post a stack-dump, but I'll try right after I update my original post with this text. I want to make sure I at least get the crux of the biscuit posted in case someone recognizes the error pattern I'm experiencing.
Thanks in advance for any guidance, suggestions, or commentary!
-
Hi
To get any help for this, you must provide more info.
Platform
Qt version
VS studio used
How you installed Qt. ( self compiled, from Qt online installer )Any info on using MFC also with QT. What did you do ?
And then the stack trace from the crash.
else its just impossible to guess at. :)
-
I know mrjj -- my bad, I totally JUST registered and hadn't quite gotten the hang of posting. :) I'll post the stack trace next.
-
I did a screenshot of the stack trace, but I can't figure out how to store this into my post (the embedded Image link appears to want something that's server-accessible and not stored locally...?). So here's what will probably be a fairly unsightly cut-n-paste from VS 2008 into here:
Qt5Cored.dll!QArrayData::shared_null()
Qt5Guid.dll!QPainter::begin(QPaintDevice * pd=0x0d1d050c) Line 1726 + 0xd bytes
Qt5Guid.dll!QPainter::QPainter(QPaintDevice * pd=0x101de450) Line 1469
Qt5Widgetsd.dll!QAbstractScrollArea::event(QEvent * e=0x0018c5bc) Line 1017
Qt5Widgetsd.dll!QAbstractItemView::event(QEvent * event=0x0018c5bc) Line 1623
Qt5Widgetsd.dll!QApplicationPrivate::notify_helper(QObject * receiver=0x101de448, QEvent * e=0x0018c5bc) Line 3482 + 0x11 bytes
Qt5Widgetsd.dll!QApplication::notify(QObject * receiver=0x101de448, QEvent * e=0x0018c5bc) Line 3447 + 0x10 bytes
Qt5Cored.dll!QCoreApplication::notifyInternal(QObject * receiver=0x101de448, QEvent * event=0x0018c5bc) Line 881 + 0x15 bytes
Qt5Cored.dll!QCoreApplication::sendSpontaneousEvent(QObject * receiver=0x101de448, QEvent * event=0x0018c5bc) Line 235 + 0x38 bytes
Qt5Widgetsd.dll!QWidgetPrivate::drawWidget(QPaintDevice * pdev=0x0d1d050c, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5138 + 0xe bytes
Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x0000001a, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5333
Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x0000001c, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x0000001d, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x0000001e, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x0000001f, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x00000020, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x00000021, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x00000022, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x00000023, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x00000024, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x00000025, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x00000028, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
Qt5Widgetsd.dll!QWidgetPrivate::drawWidget(QPaintDevice * pdev=0x0d1d050c, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000005, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5191
Qt5Widgetsd.dll!QWidgetBackingStore::sync() Line 1084
Qt5Widgetsd.dll!QWidgetPrivate::syncBackingStore() Line 1688
Qt5Widgetsd.dll!QWidget::event(QEvent * event=0x0d4e6d60) Line 8237
Qt5Widgetsd.dll!QApplicationPrivate::notify_helper(QObject * receiver=0x0d2573e8, QEvent * e=0x0d4e6d60) Line 3482 + 0x11 bytes
Qt5Widgetsd.dll!QApplication::notify(QObject * receiver=0x0d2573e8, QEvent * e=0x0d4e6d60) Line 3447 + 0x10 bytes
Qt5Cored.dll!QCoreApplication::notifyInternal(QObject * receiver=0x0d2573e8, QEvent * event=0x0d4e6d60) Line 881 + 0x15 bytes
Qt5Cored.dll!QCoreApplication::sendEvent(QObject * receiver=0x0d2573e8, QEvent * event=0x0d4e6d60) Line 232 + 0x39 bytes
Qt5Cored.dll!QCoreApplicationPrivate::sendPostedEvents(QObject * receiver=0x00000000, int event_type=0x00000000, QThreadData * data=0x059b2bc8) Line 1485 + 0xd bytes
Qt5Cored.dll!QCoreApplication::sendPostedEvents(QObject * receiver=0x00000000, int event_type=0x00000000) Line 1343 + 0x11 bytes
qwindowsd.dll!0f0ee6a1()
[Frames below may be incorrect and/or missing, no symbols loaded for qwindowsd.dll]
Qt5Cored.dll!qt_internal_proc(HWND__ * hwnd=0x002106ee, unsigned int message=0x00000401, unsigned int wp=0x00000000, long lp=0x00000000) Line 423
user32.dll!760962fa()
user32.dll!76096d3a()
user32.dll!76096ce9()
user32.dll!760a0d3f()
user32.dll!760a853a()
mfc90d.dll!AfxActivationWndProc(HWND_ * hWnd=0x002106ee, unsigned int nMsg=0x00000401, unsigned int wParam=0x00000000, long lParam=0x00000000) Line 463 + 0x1a bytes
user32.dll!760962fa()
user32.dll!76096d3a()
user32.dll!76096ce9() -
FOLLOW-UP:
Platform: Windows XP (originally), now Windows 7, 8 & 10
Qt version: 5.2.1
VS studio used: VS 2008, C++ Solution, uses MFC in a shared DLL, not using ATL, uses Multi-Byte character set, links in lots of libraries but for Qt: Qt5Core.lib, Qt5Gui.lib, Qt5Sql.lib, Qt5Network.lib, Qt5Widgets.lib - also qtwinmigrated.lib (that may be an internal library -- not sure), FreeImage.lib, glu32.lib, opengl32.lib, UxTheme.lib, gdiplus.lib, libbz2.lib, and many others
How you installed Qt. ( self compiled, from Qt online installer ): I don't know. It's included as a distribution project (submodule) via git/Gerrit. You check out this program and it pulls in Qt header files and DLL's.Any info on using MFC also with QT. What did you do ?: As best that I can tell, Qt looks like it was added on to an existing Windows Forms app. This is a VERY LARGE and really complex app that's used by many, many people, so it's full of a very diverse set of functions commingled together in one program. Think of a mutant Swiss Army Knife on steroids and you're probably envisioning 20% of what this application does.
The application does not crash frequently. This Find Qt pop-up dialog is one part that does, though I would say it's random/intermittent in nature and not frequent. I have not found a Use Case that will consistently reproduce the crash.
My guess is that its architecture violates Qt's 10 commandments, but since I'm not familiar with Qt I can't say that for sure. Per Qt, I have never used it, so I'm learning it as I'm debugging this code. It would be helpful to go through some tutorials, build some test apps, all of that, but that's beyond the scope of what I'm tasked to do, which is locate the cause of the crash and attempt to fix it.
-
Hi and welcome to devnet,
Looks rather like a FrankenHydraMonster kind of application ;)
So if I understand correctly, your MFC dialog modifies the data contained in the model set on the QTableView, right ?
-
Ha, yes, well stated.
That's correct. It's a strange and unnecessarily overly-complicated design (both opinions) where the items in the QTableView are regular C++-heap-allocated non-Qt objects.
When the user selects an item and clicks [Edit], the Edit Qt pop-up dialog provides the user a way to edit the item. Internally the Edit pop-up is generated by the selected item, or in other words when you click [Edit] inside the C++ app there's code that looks like this:
if (pSelectedItem->Edit()) { .....
the Edit method of the object instantiates a local Edit dialog of derived from QDialog, centers it on the main application, which centers it on the Find dialog, and then does a:
return ( ( Edit.exec() == QDialog::Accepted ) ? true : false );
(disclaimer: for the record, this isn't code I wrote)
If true is returned then the main application updates the object, which updates the main application's screen, and then the main application invokes the Find dialog's UpdateDialog method which ends up calling:
QMetaObject::activate(this, &staticMetaObject, 1, 0);
That call bubbles back through to the Find dialog (still visible on the screen), which updates a number of controls on the Find screen and resorts the list of items to handle the case where the user changed the name.
To say it's extremely convoluted would be like calling the temperature in Antartical "kind of chilly". The logic itself is very serpentine, but it is what it is. I'm not tasked with rewriting it (though believe me I'd like to).
What I'm hoping is that there's something Qt-oriented I can plant some hooks in to see why the redirected QPaintDevice is seemingly getting deleted during a time when a repaint event is sitting in the event queue. I feel like there's a design pattern or architecture to be followed that's probably being violated in this application, like calling the UpdateDialog method directly rather than possibly binding that and invoking it via signal emission. I'm not expecting anyone to point me right to the bug. My hope is that there might be a pattern where a particular coding practice tends to lead to the early deletion of a redirected QPaintDevice. Maybe the local QDialog declared in the C++ item and invoked from there? I have seen this done in tutorial samples I perused, but none of them are wired together (a somewhat loose description for this app) like this particular application.
Does that make sense?
-
I think you just described several anti-patterns that have already ended in the Daily WTF...
Even though you're not supposed to re-write it from the ground (I'd add my voice to yours if it could help), there are certainly some cleanup to do.
First thing i'd check is what method correspond to that
QMetaObject::activate
code.Before digging further, is it really a QTableView or a QTableWidget ?
-
I should have read this before responding to my other post. :)
The declaration in the class header file is:
QTableView* m_UIObjecdtResultsTable;
The pointer stored here is the same pointer that QPainter::begin receives as the QPaintDevice *pd which is also the "this" pointer in the QAbstractScrollArea when the app crashes. This pointer does get replaced inside of the QPainter::begin processing when:
QPoint redirectionOffset; QPaintDevice *rpd = pd->redirected(&redirectionOffset); if (rpd) pd = rpd;
The assignment of rpd to pd is pointing to memory that appears to be invalid, i.e. deleted. For instance, the painters member variable has an atypical value of 0xfdfd, and the reserved value, which is decalred as a QPaintDevicePrivate*, contains 0xabababab. The __vfptr has 7 slots with values like you see below:
-
pd 0x0d1d050c {painters=0xfdfd reserved=0xabababab } QPaintDevice *
-
__vfptr 0x10399d60 * [0x0] 0x00000001 * [0x1] 0x67042750 struct QArrayData const * const QArrayData::shared_null * [0x2] 0x00000001 * [0x3] 0x00000018 * [0x4] 0x00000380 * [0x5] 0x00000034 * [0x6] 0x00000001 * painters 0xfdfd unsigned short reserved 0xabababab QPaintDevicePrivate *
-
-
A little more info after starting a 2nd debug session.
I set a breakpoint so that I could walk through the first call to QAbstractItemView::event -> QAbstractScrollArea::event -> QPainter::QPainter -> QPainter::begin
The first call to redirected has the redirectedDev pointer in the QPaintDevice pointing to a valid QPaintDevice with a QImage, 7 slots in the __vfptr that look valid, and both the painters and reserved member variables set to 0 (zero):
redirectDev 0x0d3ea70c {d=0x0d3ea760 } QPaintDevice * [QImage] {d=0x0d3ea760 } QImage __vfptr 0x0551c988 const QImage::`vftable' * [0x0] 0x050113fa QImage::`vector deleting destructor'(unsigned int) * [0x1] 0x04ff8ae9 QImage::devType(void) * [0x2] 0x04ffe27d QImage::paintEngine(void) * [0x3] 0x0500b077 QImage::metric(enum QPaintDevice::PaintDeviceMetric) * [0x4] 0x05001757 QPaintDevice::initPainter(class QPainter *) * [0x5] 0x04ff570e QPaintDevice::redirected(class QPoint *) * [0x6] 0x04ff9246 QPaintDevice::sharedPainter(void) * painters 0x0000 unsigned short reserved 0x00000000 QPaintDevicePrivate *
The redirectDev is a QTableView member variable that's already been populated. I will run through a few cycles to see if this value is changing.