0

I've observed a crash to desktop. After debugging it turned out that it was somehow due to an unintended destruction of an object, but I'd like to understand why this results in heap corruption. And why this doesn't trigger our crash handler.

There is a QDialog A that is supposed to stay alive through the lifetime of the application. It's allocated with new, and managed by a std::shared_ptr. Under some circumstances, this dialog was given another dialog B as parent. That other dialog went out of scope, and destroyed all its children, including dialog A. The stack trace is:

    ntdll.dll!RtlReportCriticalFailure()    
    ntdll.dll!RtlpHeapHandleError() 
    ntdll.dll!RtlpHpHeapHandleError()   
    ntdll.dll!RtlpLogHeapFailure()  
    ntdll.dll!RtlpFreeHeapInternal()    
    ntdll.dll!RtlFreeHeap() 
    ucrtbase.dll!_free_base()   
        A::`scalar deleting destructor'(unsigned int)
    QtCore4.dll!00000000573ba2cf()  
    QtGui4.dll!000000005696be43()   
        functionWhereBGoesOutOfScope()
        ...
        

Why does Windows complain already at this point in time? I would have expected the dtor to go through fine here, and instead expect an access violation later when the application closes and the std::unique_ptr tries to destroy A (again).

Exception handling?

Also why does this not trigger our crash handling? We call SetUnhandledExceptionFilter to register a callback that writes a crash dump. But this doesn't happen at all. It's just a crash to desktop. Without a debugger attached, the process vanishes without a trace.

-> Answered in comment: Heap corruption is special and won't trigger any exception handler.

What I've tried so far

Read documentation. The RtlFreeHeap documentation is unhelpful. Error cases or heap corruption are not addressed at all. The other functions have no public documentation at all.

Attached WinDbg after reading https://stackoverflow.com/a/22074401/872616. !heap says

**************************************************************
*                                                            *
*                  HEAP ERROR DETECTED                       *
*                                                            *
**************************************************************

Details:

Heap address:  00000182adfa0000
Error address: 00000182ba16f050
Error type: HEAP_FAILURE_BLOCK_NOT_BUSY
Details:    The caller performed an operation (such as a free
            or a size check) that is illegal on a free block.
Follow-up:  Check the error's stack trace to find the culprit.


Stack trace:
Stack trace at 0x00007fff8ef39848
    00007fff8eede361: ntdll!RtlpLogHeapFailure+0x45
    00007fff8edf5bf0: ntdll!RtlpFreeHeapInternal+0x4e0
    00007fff8edf47b1: ntdll!RtlFreeHeap+0x51
    00007fff8c81f05b: ucrtbase!_free_base+0x1b
*** WARNING: Unable to verify checksum for xxx64.dll
    00007ffe8aacc227: xxx64!A::`scalar deleting destructor'+0x87
*** WARNING: Unable to verify checksum for ...\Qt-4.8.7-VS15x64\bin\QtCore4.dll
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for ...\Qt-4.8.7-VS15x64\bin\QtCore4.dll - 
    00000000573ba2cf: QtCore4!QObjectPrivate::deleteChildren+0x9f
*** WARNING: Unable to verify checksum for ...\Qt-4.8.7-VS15x64\bin\QtGui4.dll
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for ...\Qt-4.8.7-VS15x64\bin\QtGui4.dll - 
    000000005696be43: QtGui4!QWidget::~QWidget+0x893
    00007ffe8b1a8425: xxx64!functionWhereBGoesOutOfScope+0x345

It says "illegal on a free block", implying a double free. That would explain why Windows complains. But as far as I know, that block should not be free.

I've tried to set breakpoints in A's dtor, but they're not triggered in Release mode, probably all inlined. In Debug mode they're triggered, but only once, which would rule out the double free.

Andreas Haferburg
  • 5,189
  • 3
  • 37
  • 63
  • 1
    A broken heap is a too fundamental problem to cause an exception - the process just can't continue at that point. – molbdnilo May 05 '23 at 11:14
  • @molbdnilo Thanks. Your comment made me investigate a bit more, and I've found this https://stackoverflow.com/a/43214586/872616 – Andreas Haferburg May 05 '23 at 11:16
  • 1
    If you are using MSVC try an [AddressSanitizer](https://learn.microsoft.com/en-us/cpp/sanitizers/asan?view=msvc-170) build. – Richard Critten May 05 '23 at 11:19
  • @RichardCritten I'll try a rebuild with `/fsanitize=address`, thanks. – Andreas Haferburg May 05 '23 at 11:22
  • From what it looks you create a widget (maybe on the stack) and set it as parent to another widget. When this gets destroyed, the other widget is already gone. – chehrlic May 05 '23 at 11:23
  • 2
    Looks like a double deletion of the same object. Be careful with smart pointers and QObject subclasses, as Qt has a built in mechanism to destroy child objects when parent destroys. – vahancho May 05 '23 at 11:27
  • I'm trying to understand why that would result in a double free/heap corruption. – Andreas Haferburg May 05 '23 at 11:42
  • It could easily result in a double free. If the parent QObject frees the object then the `std::unique_ptr` goes out of scope and frees it a second time. – drescherjm May 05 '23 at 11:49
  • @AndreasHaferburg, you already explain it yourself: `"That other dialog went out of scope, and destroyed all its children, including dialog A"`. From another hand the same dialog is probably destroying by `std::unique_ptr` that owns the dialog. Simply don't use `std::unique_ptr` here. – vahancho May 05 '23 at 11:49
  • Yes, that would explain it. But the heap corruption already occurs way before the `std::unique_ptr` does anything. As far as I can tell there's only one free. The dtor is only called once, and then Windows reports heap corruption. That's what I don't understand. – Andreas Haferburg May 05 '23 at 11:52
  • My advice is to use your debugger and put a breakpoint in the destructor of your dialog. – drescherjm May 05 '23 at 11:54
  • I did. That's how I found out that the dtor is only called once. – Andreas Haferburg May 05 '23 at 11:54
  • Maybe this is not the reason and your code has some other type of heap corruption. – drescherjm May 05 '23 at 11:55

1 Answers1

0

The reason was std::make_shared, probably because it uses placement new with some pointer magic.

As an experiment, when I set the shared_ptr variable with std::shared_ptr::reset and call new directly, it behaves as expected: The problems occur later. Either because the shared_ptr is accessed or when the application exits and the shared_ptr tries to delete the dialog for the second time.

The proper fix is not to use shared_ptr with QObjects.

Andreas Haferburg
  • 5,189
  • 3
  • 37
  • 63
  • 3
    You **must not** use shared pointers with QObject's until you know exactly what you're doing (and deleting). – chehrlic May 05 '23 at 13:28
  • I absolutely agree, that's how I fixed it: Use a raw pointer, transfer ownership to Qt. But the point of the question was not how to fix the problem, but to understand why the bug materialized in the way that it did. Sorry if that wasn't clear. – Andreas Haferburg May 05 '23 at 13:56