Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. shiboken6 6.11.1: SIGSEGV in QUndoStack::clear() — pushed QUndoCommand double-deleted when stack and command are cyclic garbage
Qt 6.11 is out! See what's new in the release blog

shiboken6 6.11.1: SIGSEGV in QUndoStack::clear() — pushed QUndoCommand double-deleted when stack and command are cyclic garbage

Scheduled Pinned Locked Moved Unsolved Qt for Python
4 Posts 3 Posters 589 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • D Offline
    D Offline
    demotu
    wrote last edited by
    #1

    Environment: PySide6 / shiboken6 6.11.1, CPython 3.14.5, Linux x86_64 (glibc 2.43)

    Summary: when a parentless QUndoStack created from Python (so the wrapper owns the C++ object) holds a pushed QUndoCommand, and both wrappers become unreachable in the same Python reference-cycle collection, gc.collect() segfaults inside QUndoStack::clear(). No QApplication and no Python subclassing required.

    Deterministic reproducer (crashes 10/10, exit 139):

    import gc
    from PySide6.QtGui import QUndoCommand, QUndoStack
    
    cmd = QUndoCommand("cmd")   # command wrapper allocated BEFORE the stack wrapper
    stack = QUndoStack()
    stack.me = stack            # any cycle: the stack must die inside the GC pass
    stack.push(cmd)             # shiboken parentship: stack owns command
    
    del cmd, stack
    gc.collect()                # SIGSEGV
    

    Mechanism (shiboken6 6.11.1, sources/shiboken6/libshiboken/basewrapper.cpp):

    When CPython's delete_garbage() tp_clears the command wrapper first, SbkObject_tp_clear (line 298) calls Shiboken::Object::removeParent(child) with the default arguments giveOwnershipBack=true, keepReference=false (line 1746), handing C++ ownership of the command back to Python. The follow-up DECREF frees the wrapper and SbkDeallocWrapperCommon runs cpp_dtor, deleting the C++ QUndoCommand while the C++ QUndoStack::d->command_list still holds the raw pointer (push() is an unconditional ownership transfer; there is no removal API). When the stack wrapper is torn down later in the same pass, ~QUndoStack() → clear() → qDeleteAll(d->command_list) makes a virtual call through the freed command's clobbered vptr → SIGSEGV (faulting pc is 0x0 in gdb).

    The opposite tp_clear order is handled correctly (_destroyParentInfo invalidates children with giveOwnershipBack=false), which is why this presents as selection-dependent "flaky" crashes in real applications. We first hit it as intermittent pytest-teardown segfaults in a widgets app whose panels hold a parentless undo stack.

    Faulthandler C stack of the crash:

    libQt6Gui.so.6              QUndoStack::clear() + 0x142
    libQt6Gui.so.6              QUndoStack::~QUndoStack() + 0x36
    QtGui.abi3.so               (wrapper dealloc)
    libshiboken6.abi3.so.6.11   SbkDeallocWrapperCommon
    python                      _Py_Dealloc
    python                      gc_collect_main   ("Garbage-collecting")
    

    Workarounds: parent the QUndoStack to a QObject, or explicitly stack.clear() while both halves are alive before dropping the last Python references.

    JonBJ 1 Reply Last reply
    0
    • D demotu

      Environment: PySide6 / shiboken6 6.11.1, CPython 3.14.5, Linux x86_64 (glibc 2.43)

      Summary: when a parentless QUndoStack created from Python (so the wrapper owns the C++ object) holds a pushed QUndoCommand, and both wrappers become unreachable in the same Python reference-cycle collection, gc.collect() segfaults inside QUndoStack::clear(). No QApplication and no Python subclassing required.

      Deterministic reproducer (crashes 10/10, exit 139):

      import gc
      from PySide6.QtGui import QUndoCommand, QUndoStack
      
      cmd = QUndoCommand("cmd")   # command wrapper allocated BEFORE the stack wrapper
      stack = QUndoStack()
      stack.me = stack            # any cycle: the stack must die inside the GC pass
      stack.push(cmd)             # shiboken parentship: stack owns command
      
      del cmd, stack
      gc.collect()                # SIGSEGV
      

      Mechanism (shiboken6 6.11.1, sources/shiboken6/libshiboken/basewrapper.cpp):

      When CPython's delete_garbage() tp_clears the command wrapper first, SbkObject_tp_clear (line 298) calls Shiboken::Object::removeParent(child) with the default arguments giveOwnershipBack=true, keepReference=false (line 1746), handing C++ ownership of the command back to Python. The follow-up DECREF frees the wrapper and SbkDeallocWrapperCommon runs cpp_dtor, deleting the C++ QUndoCommand while the C++ QUndoStack::d->command_list still holds the raw pointer (push() is an unconditional ownership transfer; there is no removal API). When the stack wrapper is torn down later in the same pass, ~QUndoStack() → clear() → qDeleteAll(d->command_list) makes a virtual call through the freed command's clobbered vptr → SIGSEGV (faulting pc is 0x0 in gdb).

      The opposite tp_clear order is handled correctly (_destroyParentInfo invalidates children with giveOwnershipBack=false), which is why this presents as selection-dependent "flaky" crashes in real applications. We first hit it as intermittent pytest-teardown segfaults in a widgets app whose panels hold a parentless undo stack.

      Faulthandler C stack of the crash:

      libQt6Gui.so.6              QUndoStack::clear() + 0x142
      libQt6Gui.so.6              QUndoStack::~QUndoStack() + 0x36
      QtGui.abi3.so               (wrapper dealloc)
      libshiboken6.abi3.so.6.11   SbkDeallocWrapperCommon
      python                      _Py_Dealloc
      python                      gc_collect_main   ("Garbage-collecting")
      

      Workarounds: parent the QUndoStack to a QObject, or explicitly stack.clear() while both halves are alive before dropping the last Python references.

      JonBJ Offline
      JonBJ Offline
      JonB
      wrote last edited by
      #2

      @demotu
      Since this is a user forum, you should report this at https://qt-project.atlassian.net/jira/software/c/projects/PYSIDE/list.

      1 Reply Last reply
      0
      • D Offline
        D Offline
        demotu
        wrote last edited by
        #3

        Yes, I would normally file this on Jira, but my account currently can't create issues. Posting here so it doesn't stall — if a maintainer wants to file it, please do.

        jsulmJ 1 Reply Last reply
        0
        • D demotu

          Yes, I would normally file this on Jira, but my account currently can't create issues. Posting here so it doesn't stall — if a maintainer wants to file it, please do.

          jsulmJ Offline
          jsulmJ Offline
          jsulm
          Lifetime Qt Champion
          wrote last edited by
          #4

          @demotu Take a look at https://forum.qt.io/topic/164253/about-accessing-the-bug-report-system?_=1784033640203

          https://forum.qt.io/topic/113070/qt-code-of-conduct

          1 Reply Last reply
          0

          • Login

          • Login or register to search.
          • First post
            Last post
          0
          • Categories
          • Recent
          • Tags
          • Popular
          • Users
          • Groups
          • Search
          • Get Qt Extensions
          • Unsolved