Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-72771

Corrupted stack memory when inheriting from Qt classes with data pointer

    XMLWordPrintable

Details

    • Bug
    • Resolution: Invalid
    • Not Evaluated
    • None
    • 5.6.1, 5.11.2
    • Core: Other
    • None
    • Windows 10 x64
      Microsoft Visual Studio 2017 Win64 15.9.4
      Microsoft Visual C++ Compiler 19.16.27025.1
    • Windows

    Description

      I'm experiencing an obscure behavior of Microsoft Visual C++ Compiler on Windows 10 in Debug Configuration.
      I couldn't get rid of the QString entity in the example, cause in the other circumstances it is not reproducible or it is merely hidden from the eyes.
      Here is a test code snippet where the issue manifests:

      #include <QtCore/QString>
      
      struct MyQString: QString
      {
         using QString::QString;
         int m_b = 42;
      };
      
      struct TestString
      {
         operator MyQString()
         {
            return {};
         }
      };
      
      int takeTwo(QString const & a1, QString const & a2)
      {
         return a2.size();
      }
      
      TestString ts;
      int good1 = takeTwo(ts.operator MyQString(), QString("Foo"));
      int good2 = takeTwo(static_cast<MyQString>(ts), QString("Bar"));
      int bad  = takeTwo(ts, QString("Baz"));
      

      When initializing the bad variable within the scope of takeTwo, the a2 argument gets corrupted i.e. its QString::d member (pointer) obtains value 0x2a (42). Since the values are passed by reference, the issue hides in that how the compiler puts the rvalues of the arguments to be passed in takeTwo in the stack. The compiler computes the arguments from right to left (see the disassembly below), and in this case the left argument's stack memory somehow overlaps the memory of the right one. The last wins.

      I inspected the Disassembly view at each takeTwo call and found out that the bad one has differences in the stack pointer offsets.

      Posting here the takeTwo call disassemblies

      ; int good1 = takeTwo(ts.operator MyQString(), QString("Foo"));
      00007FF8B1311B50  sub         rsp,68h  
      00007FF8B1311B54  mov         qword ptr [rsp+48h],0FFFFFFFFFFFFFFFEh  
      00007FF8B1311B5D  lea         rdx,[string "Foo" (07FF8B16A4E9Ch)]  
      00007FF8B1311B64  lea         rcx,[rsp+40h]  
      00007FF8B1311B69  call        qword ptr [__imp_QString::QString (07FF8B1682898h)]  
      00007FF8B1311B6F  mov         qword ptr [rsp+20h],rax  
      00007FF8B1311B74  mov         rax,qword ptr [rsp+20h]  
      00007FF8B1311B79  mov         qword ptr [rsp+30h],rax  
      00007FF8B1311B7E  lea         rdx,[rsp+50h]  
      00007FF8B1311B83  lea         rcx,[ts (07FF8B1783640h)]  
      00007FF8B1311B8A  call        TestString::operator MyQString (07FF8B141F9A0h)  
      00007FF8B1311B8F  mov         qword ptr [rsp+28h],rax  
      00007FF8B1311B94  mov         rax,qword ptr [rsp+28h]  
      00007FF8B1311B99  mov         qword ptr [rsp+38h],rax  
      00007FF8B1311B9E  mov         rdx,qword ptr [rsp+30h]  
      00007FF8B1311BA3  mov         rcx,qword ptr [rsp+38h]  
      00007FF8B1311BA8  call        takeTwo (07FF8B13F57E0h)  
      00007FF8B1311BAD  mov         dword ptr [good1 (07FF8B1783644h)],eax  
      00007FF8B1311BB3  lea         rcx,[rsp+50h]  
      00007FF8B1311BB8  call        MyQString::~MyQString (07FF8B141C5D0h)  
      00007FF8B1311BBD  nop  
      00007FF8B1311BBE  lea         rcx,[rsp+40h]  
      00007FF8B1311BC3  call        qword ptr [__imp_QString::~QString (07FF8B1682890h)]  
      00007FF8B1311BC9  add         rsp,68h  
      00007FF8B1311BCD  ret
      
      ; int good2 = takeTwo(static_cast<MyQString>(ts), QString("Bar"));
      00007FF8B1311BD0  sub         rsp,68h  
      00007FF8B1311BD4  mov         qword ptr [rsp+48h],0FFFFFFFFFFFFFFFEh  
      00007FF8B1311BDD  lea         rdx,[string "Bar" (07FF8B16A4EA0h)]  
      00007FF8B1311BE4  lea         rcx,[rsp+40h]  
      00007FF8B1311BE9  call        qword ptr [__imp_QString::QString (07FF8B1682898h)]  
      00007FF8B1311BEF  mov         qword ptr [rsp+20h],rax  
      00007FF8B1311BF4  mov         rax,qword ptr [rsp+20h]  
      00007FF8B1311BF9  mov         qword ptr [rsp+30h],rax  
      00007FF8B1311BFE  lea         rdx,[rsp+50h]  
      00007FF8B1311C03  lea         rcx,[ts (07FF8B1783640h)]  
      00007FF8B1311C0A  call        TestString::operator MyQString (07FF8B141F9A0h)  
      00007FF8B1311C0F  mov         qword ptr [rsp+28h],rax  
      00007FF8B1311C14  mov         rax,qword ptr [rsp+28h]  
      00007FF8B1311C19  mov         qword ptr [rsp+38h],rax  
      00007FF8B1311C1E  mov         rdx,qword ptr [rsp+30h]  
      00007FF8B1311C23  mov         rcx,qword ptr [rsp+38h]  
      00007FF8B1311C28  call        takeTwo (07FF8B13F57E0h)  
      00007FF8B1311C2D  mov         dword ptr [good2 (07FF8B1783648h)],eax  
      00007FF8B1311C33  lea         rcx,[rsp+50h]  
      00007FF8B1311C38  call        MyQString::~MyQString (07FF8B141C5D0h)  
      00007FF8B1311C3D  nop  
      00007FF8B1311C3E  lea         rcx,[rsp+40h]  
      00007FF8B1311C43  call        qword ptr [__imp_QString::~QString (07FF8B1682890h)]  
      00007FF8B1311C49  add         rsp,68h  
      00007FF8B1311C4D  ret
      
      ; int bad  = takeTwo(ts, QString("Baz"));
      00007FF8B1311A60  sub         rsp,68h  
      00007FF8B1311A64  mov         qword ptr [rsp+50h],0FFFFFFFFFFFFFFFEh  
      00007FF8B1311A6D  lea         rdx,[string "Baz" (07FF8B16A4EA4h)]  
      00007FF8B1311A74  lea         rcx,[rsp+48h]  
      00007FF8B1311A79  call        qword ptr [__imp_QString::QString (07FF8B1682898h)]  
      00007FF8B1311A7F  mov         qword ptr [rsp+20h],rax  
      00007FF8B1311A84  mov         rax,qword ptr [rsp+20h]  
      00007FF8B1311A89  mov         qword ptr [rsp+30h],rax  
      00007FF8B1311A8E  lea         rdx,[rsp+40h]  
      00007FF8B1311A93  lea         rcx,[ts (07FF8B1783640h)]  
      00007FF8B1311A9A  call        TestString::operator MyQString (07FF8B141F9A0h)  
      00007FF8B1311A9F  mov         qword ptr [rsp+28h],rax  
      00007FF8B1311AA4  mov         rax,qword ptr [rsp+28h]  
      00007FF8B1311AA9  mov         qword ptr [rsp+38h],rax  
      00007FF8B1311AAE  mov         rdx,qword ptr [rsp+30h]  
      00007FF8B1311AB3  mov         rcx,qword ptr [rsp+38h]  
      00007FF8B1311AB8  call        takeTwo (07FF8B13F57E0h)  
      00007FF8B1311ABD  mov         dword ptr [bad (07FF8B1783658h)],eax  
      00007FF8B1311AC3  lea         rcx,[rsp+40h]  
      00007FF8B1311AC8  call        MyQString::~MyQString (07FF8B141C5D0h)  
      00007FF8B1311ACD  nop  
      00007FF8B1311ACE  lea         rcx,[rsp+48h]  
      00007FF8B1311AD3  call        qword ptr [__imp_QString::~QString (07FF8B1682890h)]  
      00007FF8B1311AD9  add         rsp,68h  
      00007FF8B1311ADD  ret
      

      Observe the lea instructions just before the __imp_QString::QString call. In the bad test it differs by 8 bytes in comparison with the good tests! I suspect that's the reason why the 2nd arg gets ruined.
      To me it looks mysterious because for the compiler it makes difference if there is implicit or explicit conversion to QString, as it affects the final result.

      Also posting here the cl command line from QtCreator output:

      cl -c -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -Zi -MDd -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -EHsc /Fddebug\TestQtProject.vc.pdb -DUNICODE -D_UNICODE -DWIN32 -DWIN64 -D_ENABLE_EXTENDED_ALIGNED_STORAGE -DQT_DEPRECATED_WARNINGS -DQT_QML_DEBUG -DQT_CORE_LIB -I..\TestQtProject -I. -I..\..\..\..\Qt\5.11.2\msvc2017_64\include -I..\..\..\..\Qt\5.11.2\msvc2017_64\include\QtCore -Idebug -I..\..\..\..\Qt\5.11.2\msvc2017_64\mkspecs\win32-msvc -Fodebug\ @C:\Users\USE~1\AppData\Local\Temp\main.obj.13544.0.jom main.cpp
      

      I've tested the issue on the other PC in Visual Studio 2017 15.9.3 and ran into the same issue. But on yet another PC in Visual Studio 2017 Win64 15.7.6 and even Visual Studio 2015 it is not reproducible.

      Naturally, I'd have posted all this to Microsoft if I could omit the QString dependency. Probably I have missed some subtle detail, but spent a day to figure out the reason why the sfw is crashing in Debug.

      Attachments

        No reviews matched the request. Check your Options in the drop-down menu of this sections header.

        Activity

          People

            thiago Thiago Macieira
            qbatman qbatman
            Votes:
            1 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved:

              Gerrit Reviews

                There are no open Gerrit changes