Bug 374568

Summary: Applications crash when menu bar is accessed
Product: [Frameworks and Libraries] QtCurve Reporter: Eugene Shalygin <eugene.shalygin+bugzilla.kde>
Component: qt5Assignee: Yichao Yu <yyc1992>
Status: RESOLVED FIXED    
Severity: crash CC: hein, rjvbertin
Priority: NOR    
Version: git   
Target Milestone: ---   
Platform: Gentoo Packages   
OS: Linux   
Latest Commit: Version Fixed In:
Sentry Crash Report:

Description Eugene Shalygin 2017-01-05 00:55:08 UTC
GCC 6.3.0, -fsanitaze=address

==31500==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x7ff7ec132047 bp 0x7ffd660bc7c0 sp 0x7ffd660bc740 T0)
    #0 0x7ff7ec132046 in QString::startsWith(QString const&, Qt::CaseSensitivity) const (/usr/lib64/libQt5Core.so.5+0x121046)
    #1 0x7ff7cfbb5a6e in determineFileName /home/eugene/develop/KDE/live/qtcurve/qt5/common/config_file.cpp:51
    #2 0x7ff7cfb5b67a in loadImage(QString const&, QtCPixmap*) (/home/eugene/develop/KDE/live/qtcurve/build/qt5/style/qtcurve.so+0x1867a)
    #3 0x7ff7cfbabbc1 in QtCurve::updateMenuBarEvent(QMouseEvent*, QMenuBar*) /home/eugene/develop/KDE/live/qtcurve/qt5/style/qtcurve_utils.cpp:250
    #4 0x7ff7cfb7f42a in QtCurve::Style::eventFilter(QObject*, QEvent*) /home/eugene/develop/KDE/live/qtcurve/qt5/style/qtcurve_api.cpp:969
    #5 0x7ff7ec2496d1 in QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject*, QEvent*) (/usr/lib64/libQt5Core.so.5+0x2386d1)
    #6 0x7ff7eca69a34 in QApplicationPrivate::notify_helper(QObject*, QEvent*) (/usr/lib64/libQt5Widgets.so.5+0x159a34)
    #7 0x7ff7eca7174c in QApplication::notify(QObject*, QEvent*) (/usr/lib64/libQt5Widgets.so.5+0x16174c)
    #8 0x7ff7ec249899 in QCoreApplication::notifyInternal2(QObject*, QEvent*) (/usr/lib64/libQt5Core.so.5+0x238899)
    #9 0x7ff7eca701cc in QApplicationPrivate::sendMouseEvent(QWidget*, QMouseEvent*, QWidget*, QWidget*, QWidget**, QPointer<QWidget>&, bool) (/usr/lib64/libQt5Widgets.so.5+0x1601cc)
    #10 0x7ff7ecac7f2d  (/usr/lib64/libQt5Widgets.so.5+0x1b7f2d)
    #11 0x7ff7ecaca7da  (/usr/lib64/libQt5Widgets.so.5+0x1ba7da)
    #12 0x7ff7eca69a5b in QApplicationPrivate::notify_helper(QObject*, QEvent*) (/usr/lib64/libQt5Widgets.so.5+0x159a5b)
    #13 0x7ff7eca70c08 in QApplication::notify(QObject*, QEvent*) (/usr/lib64/libQt5Widgets.so.5+0x160c08)
    #14 0x7ff7ec249899 in QCoreApplication::notifyInternal2(QObject*, QEvent*) (/usr/lib64/libQt5Core.so.5+0x238899)
    #15 0x7ff7ec57b752 in QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::MouseEvent*) (/usr/lib64/libQt5Gui.so.5+0xf0752)
    #16 0x7ff7ec57d174 in QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent*) (/usr/lib64/libQt5Gui.so.5+0xf2174)
    #17 0x7ff7ec55f51a in QWindowSystemInterface::sendWindowSystemEvents(QFlags<QEventLoop::ProcessEventsFlag>) (/usr/lib64/libQt5Gui.so.5+0xd451a)
    #18 0x7ff7d57723bf  (/usr/lib64/libQt5XcbQpa.so.5+0x703bf)
    #19 0x7ff7e2be8096 in g_main_context_dispatch (/usr/lib64/libglib-2.0.so.0+0x49096)
    #20 0x7ff7e2be82c7  (/usr/lib64/libglib-2.0.so.0+0x492c7)
    #21 0x7ff7e2be836b in g_main_context_iteration (/usr/lib64/libglib-2.0.so.0+0x4936b)
    #22 0x7ff7ec294fae in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) (/usr/lib64/libQt5Core.so.5+0x283fae)
    #23 0x7ff7ec248929 in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) (/usr/lib64/libQt5Core.so.5+0x237929)
    #24 0x7ff7ec25014c in QCoreApplication::exec() (/usr/lib64/libQt5Core.so.5+0x23f14c)
    #25 0x4038f0  (/usr/bin/pictorialist+0x4038f0)
    #26 0x7ff7eb387740 in __libc_start_main (/lib64/libc.so.6+0x20740)
    #27 0x404148  (/usr/bin/pictorialist+0x404148)

The stacktrace is strange and seems to be corrupted. I don't understand how the hack in updateMenuBarEvent() could work, but since QObject::event() is virtual and public, I propose the following change:

diff --git a/qt5/style/qtcurve_utils.cpp b/qt5/style/qtcurve_utils.cpp
index 608876b1..057d4f15 100644
--- a/qt5/style/qtcurve_utils.cpp
+++ b/qt5/style/qtcurve_utils.cpp
@@ -238,16 +238,8 @@ updateMenuBarEvent(QMouseEvent *event, QMenuBar *menu)
         }
     };
 
-    struct HackedMenu: public QMenuBar {
-        void
-        send(QMouseEvent *ev)
-        {
-            event(ev);
-        }
-    };
-
     if (((HackEvent*)event)->adjust()) {
-        ((HackedMenu*)menu)->send(event);
+        static_cast<QObject*>(menu)->event(event);
         return true;
     }
     return false;
Comment 1 RJVB 2017-01-06 16:30:36 UTC
I would guess that the HackedMenu hack has the same intention as what you are doing. Have you tried this patch extensively, run it through valgrind etc?

I must say it surprises me a bit that you are catching bugs no one has ever run into (I for one have been using QtCurve for years now).
Comment 2 Eugene Shalygin 2017-01-06 16:39:44 UTC
I was using QtCurve for many years too, but now I check my apps using GCC's sanitizer. This is the reason, I believe. The first problem (memcmp()) was manifesting itself when client code was compiled with the address sanitizer, but then I tried to compile QtCurve itself with it, and this one appeared. I'm using this change for a day only; without it, every touch of any app menu lead to a crash, while now it does not. Did not try Valgrind yet.
Comment 3 RJVB 2017-01-06 17:07:19 UTC
As you noted, the backtrace is strange and almost suggests that the sanitiser itself is causing side-effects. It seems highly unlikely that a Qt event would cause a jump directly into config_file.cpp's loadImage()!
Wouldn't be the first time that a measurement instrument changes the measured observables (or that a bug jumps elsewhere when you try to look at it).

I don't see how the original hack can be bad, either. It just subclasses QMenuBar with an additional wrapper to be able to call QMenuBar::event(). Maybe it's related to using a C-style case, or a struct instead of a class HackedMenu but the main difference there should just be that all "struct members" are public, no? But you could try with `class HackedMenu` too, and with dynamic_cast<HackedMenu*>(menu)->send(event).

Not that I'm against simplifying the code as you propose, but I'd like to be able to understand why the sanitiser trips over it. What does that sanitiser do anyway,
Comment 4 Yichao Yu 2017-01-06 21:47:24 UTC
That patch looks correct.

IIRC that hack was probably needed since the method was protected. (maybe on qt4? My internet connection is too terrible before Jan. 10 for me to do a detail check)

Just curious, why would the old version not work? Is it accidentally defining a virtual method?
Comment 5 RJVB 2017-01-06 23:27:07 UTC
The method is still protected in Qt5 from what I saw. 

The old version doesn't work with GCC 6's address sanitiser active. If it just overrides an existing virtual method there should probably be side-effects even without the sanitiser. Maybe run it through cppcheck and clang's analyser?
Comment 6 Eugene Shalygin 2017-01-07 00:14:43 UTC
Update: even when QtCurve is compiled without sanitizer, it crashes. QObject::event() is public in Qt 4.
The following does not crash:

struct HackedMenu: public QMenuBar {
     using QMenuBar::event;
};
 
if (((HackEvent*)event)->adjust()) {
   ((HackedMenu*)menu)->event(event);
   return true;
}
return false;

And Qt 4 version does not crash.
Comment 7 RJVB 2017-01-07 00:44:01 UTC
(In reply to Eugene Shalygin from comment #6)
> Update: even when QtCurve is compiled without sanitizer, it crashes.

You must have hit a border condition. Please try to debug and obtain a proper backtrace. Ideally set a breakpoint in HackedMenu::send() or just before calling it, and then step through the code.
Comment 8 Eugene Shalygin 2017-01-07 01:14:43 UTC
(In reply to RJVB from comment #7)

> You must have hit a border condition.

I disagree. AFAIK, the code shall lead to UB, because we call a virtual function from an object, whose constuctor was not called (HackedMenu). This is what I meant earlier when said that I can't understand how this could work. In support of this version, if I call the method explicitly specifying the class (QMenuBar::event(ev)) it does not crash and under debugger I see that the function is actually called. In the original code, the execution jumps to random location, which I can change by moving HackedMenu declaration around (e.g. moving it out of the function to an anonymous namespace or making it public class). 

> Please try to debug and obtain a proper backtrace.

As such I think the backtrace is correct. It does differ from our expectations, but this is because of UB.

I just can't understand why does it work in Qt 4 version...
Comment 9 Eugene Shalygin 2017-01-07 01:32:06 UTC
(In reply to Eugene Shalygin from comment #8)
> if I call the method explicitly specifying the class (QMenuBar::event(ev)) it
> does not crash and under debugger I see

Specifying either by using directive or writing QMenuBar::event(ev) in HackedMenu::send(). I.e. when I call non-virtual function, the code works.
Comment 10 Yichao Yu 2017-01-07 08:47:27 UTC
I agree the code is likely UB. Still curious why it only crashes on your setup and what code the compiler actually generate. When the method is protected, would the following work?

```
struct HackedMenu: public QMenuBar {
static void send(QMenuBar *menubar, QMouseEvent *event)
{
    menubar->event(event);
}
};
 
if (((HackEvent*)event)->adjust()) {
   HackedMenu::send(menu, event);
   return true;
}
```

This should not be UB (I hope?) and does not have any of the casts.
Comment 11 RJVB 2017-01-07 09:04:00 UTC
(In reply to Yichao Yu from comment #10)

> I agree the code is likely UB. Still curious why it only crashes on your
> setup and what code the compiler actually generate. When the method is

If UB = undefined behaviour in this context then it wouldn't be the first time a change in GCC made things go weird that always worked. Then again, there's only 1 way to be certain about it (and that it's not a regression in the compiler): ask on the gcc ML or bugzilla.
But isn't cppcheck supposed to catch known cases of undefined behaviour and doesn't gcc itself have an option to warn about them?

It still is surprising though that this code has been in use for years on many different platforms and with different compilers (Mac and Linux, gcc and clang on both in my case), without ever causing trouble. Usually that kind of wide-spread application tends to bring out the very subtle bugs.

> protected, would the following work?

> struct HackedMenu: public QMenuBar {
> static void send(QMenuBar *menubar, QMouseEvent *event)

That's no longer really a HackedMenu, just a WrappedMenu ;)
Comment 12 RJVB 2017-01-07 09:08:15 UTC
Eugene : do you use any other unusual compiler options, a different assembler or linker? Do you see the issue with -O0?

FWIW, on Linux I use -Ofast -flto with gcc "6.2.0-3ubuntu11~14.04".
Comment 13 RJVB 2017-01-07 10:03:29 UTC
I've been running qtcurve_utils.cpp through a few analysers. Cppcheck doesn't complain about anything, nor does the clang 3.9 static and coverage analyser (but I never used the latter before so may have misused it).

Clazy however finds this, with level3 (all relevant and related messages):

qtcq5-lnx-work/QtCurve-1.8.18/qt5/style/qtcurve.h:62:1: warning: 
      QtCurve::Style should take QObject parent argument in CTOR [-Wclazy-ctor-missing-parent-argument]
class Style: public QCommonStyle {
^
>>> should it? <<<

qtcq5-lnx-work/QtCurve-1.8.18/qt5/style/qtcurve_utils.cpp:226:5: warning: Polymorphic class is copyable. Potential
      slicing. [-Wclazy-copyable-polymorphic]
    struct HackEvent: public QMouseEvent {
    ^
qtcq5-lnx-work/QtCurve-1.8.18/qt5/style/qtcurve_utils.cpp:241:5: warning: HackedMenu should take QWidget parent
      argument in CTOR [-Wclazy-ctor-missing-parent-argument]
    struct HackedMenu: public QMenuBar {
    ^
qtcq5-lnx-work/QtCurve-1.8.18/qt5/style/qtcurve_utils.cpp:241:5: warning: HackedMenu is missing a Q_OBJECT macro
      [-Wclazy-missing-qobject-macro]


The missing Q_OBJECT macro might explain why casts to HackedMenu can go subtly wrong.
Comment 14 RJVB 2017-01-07 15:42:10 UTC
(In reply to Yichao Yu from comment #10)

> setup and what code the compiler actually generate. When the method is
> protected, would the following work?

Nice try, but no:

qtcq5-lnx-work/QtCurve-1.8.18/qt5/style/qtcurve_utils.cpp:244:22: error: 'event' is a protected member of 'QMenuBar'
            menubar->event(event);
                     ^
/opt/local/libexec/qt5/Library/Frameworks/QtWidgets.framework/Headers/qmenubar.h:133:10: note: can only access this member on an object of type 'HackedMenu'
Comment 15 Eugene Shalygin 2017-01-07 16:33:42 UTC
I was wrong about Qt 4 case. There was a mistake in my setup and Qt4 style.so was from release build. Then I found that the problem shows up in Qt4 as well and seems to be disappearing when inlining is active, so it present in debug builds only. That is why, probably, regular users (-02 and similar) were not affected.

https://godbolt.org/g/tq82MX shows that with inlining GCC generates direct call to QMenuBar::event().
Comment 16 RJVB 2017-01-07 17:51:39 UTC
So that's why we don't see it!
And so in optimised builds there is in fact no bug in the compiled code.

If you have the time and opportunity, could you check if you get the crash with other compilers and -O0 (GCC versions and clang)? It strikes me as really odd that a compiler won't warn about a situation for which it can generate correct code when optimising but not (intentionally?) without optimisation.

FWIW, Gcc has `-fisolate-erroneous-path-*`, -fsanitize-undefined-trap-on-error (maybe you already used that?) and -Wsequence-point that are all related to catching undefined behaviour.
Comment 17 RJVB 2017-01-07 18:03:37 UTC
And FWIW2: rebuilding just qtcurve_utils.cpp with -O0 and clang on Mac doesn't cause any crashing.
Comment 18 Eugene Shalygin 2017-01-07 19:19:51 UTC
Output with -fsanitize=address -fsanitize=undefined -fsanitize=vptr

/usr/bin> /usr/bin/pictorialist
.cmake_utils_base/cmake_c_macros/include_fix/qtcurve-utils/utils.h:110:37: runtime error: load of value 268435456, which is not a valid value for type 'StateFlag'
/home/eugene/develop/KDE/live/qtcurve/qt5/style/qtcurve_utils.cpp:249:22: runtime error: downcast of address 0x7ffc16b265d0 which does not point to an object of type 'HackEvent'
0x7ffc16b265d0: note: object is of type 'QMouseEvent'
 00 00 36 40  78 2e 46 95 80 7f 00 00  00 00 00 00 00 00 00 00  05 00 06 00 00 00 00 00  ae 1a 79 08
              ^~~~~~~~~~~~~~~~~~~~~~~
              vptr for 'QMouseEvent'
/home/eugene/develop/KDE/live/qtcurve/qt5/style/qtcurve_utils.cpp:249:36: runtime error: member call on address 0x7ffc16b265d0 which does not point to an object of type 'HackEvent'
0x7ffc16b265d0: note: object is of type 'QMouseEvent'
 00 00 36 40  78 2e 46 95 80 7f 00 00  00 00 00 00 00 00 00 00  05 00 06 00 00 00 00 00  ae 1a 79 08
              ^~~~~~~~~~~~~~~~~~~~~~~
              vptr for 'QMouseEvent'
/home/eugene/develop/KDE/live/qtcurve/qt5/style/qtcurve_utils.cpp:250:23: runtime error: downcast of address 0x604000099410 which does not point to an object of type 'HackedMenu'
0x604000099410: note: object is of type 'QMenuBar'
 1a 00 80 04  e0 44 a9 95 80 7f 00 00  80 51 00 00 60 61 00 00  90 46 a9 95 80 7f 00 00  00 00 be be
              ^~~~~~~~~~~~~~~~~~~~~~~
              vptr for 'QMenuBar'
/home/eugene/develop/KDE/live/qtcurve/qt5/style/qtcurve_utils.cpp:250:34: runtime error: member call on address 0x604000099410 which does not point to an object of type 'HackedMenu'
0x604000099410: note: object is of type 'QMenuBar'
 1a 00 80 04  e0 44 a9 95 80 7f 00 00  80 51 00 00 60 61 00 00  90 46 a9 95 80 7f 00 00  00 00 be be
              ^~~~~~~~~~~~~~~~~~~~~~~
              vptr for 'QMenuBar'
/home/eugene/develop/KDE/live/qtcurve/qt5/style/qtcurve_utils.cpp:245:22: runtime error: execution reached a __builtin_unreachable() call
*** Failure: Exit code 1 ***
Comment 19 Eugene Shalygin 2017-01-07 22:18:35 UTC
clang builds do not crash here too.
Comment 20 Eugene Shalygin 2017-01-08 14:42:45 UTC
GCC 5.4.0 does not crash too, although -fsanitize=vptr prints out the same errors as the version 6.3.0 does.

Anyway, since the hack is not needed, let's replace it with a proper cast and QObject::event() call?
Comment 21 RJVB 2017-01-08 15:14:32 UTC
I have nothing against your initial explicit cast to a QObject*, what about you Yichao?

I do think this has to be reported to GCC though. I think the vptr warning is more a bad-semantics-practice warning than one about something that could cause a crash like you're seeing.

Although: I don't know about C-style casting to a different class, but casting to a *non packed* struct might let you run into issues caused by different alignment. 
And *that* could explain the jump into the unknown.

Anyway, bug or feature, in the end this is probably something that only people intimately familiar with GCC's internals might be able to explain.
Comment 22 RJVB 2017-01-08 15:17:46 UTC
Re: alignment: you could compare the function addresses, i.e.

    event in HackedMenu::send() (printing that will also teach us which function call takes the hyperjump)
vs.
    static_cast<QObject*>(menu)->event
Comment 23 Yichao Yu 2017-01-08 15:44:21 UTC
> I have nothing against your initial explicit cast to a QObject*, what about you Yichao?

Sounds good to me too.
Comment 24 RJVB 2017-01-08 16:11:13 UTC
Git commit 79f9e0a81a180b820abe2e4a2fbb21f8e1ff5592 by R.J.V. Bertin.
Committed on 08/01/2017 at 16:09.
Pushed by rjvbb into branch 'master'.

Use a cast-to-QObject rather than a "HackedMenu hack"

Call QMenuBar::event() by casting to a QObject rather
than to a wrapper class.

M  +1    -9    qt4/style/qtcurve.cpp
M  +1    -9    qt5/style/qtcurve_utils.cpp

https://commits.kde.org/qtcurve/79f9e0a81a180b820abe2e4a2fbb21f8e1ff5592
Comment 25 Eugene Shalygin 2017-01-09 10:36:44 UTC
Thanks. I will prepare a MWE and if an answer will not be found by other means, will ask in GCC mailing list.