Package: src:pyside6
Version: 6.10.3-3
User: [email protected]
Usertags: python3.15-default
Tags: patch, ftbfs, forky, sid
Severity: important

Hi!

While rebuilding the python related packages against the Python 3.15rc2
as the default version we found that pyside6 fails to build from source
[1].

The error is:

error: ‘PyWeakref_GetObject’ was not declared in this scope; did you mean 
‘PyErr_SetObject’?

This is a common problem when building against Python 3.15, and it
happens because that function was removed in Python 3.15, and the code
needs to be adapted. This only surfaces when building with 3.15 as the
default because pyside only builds with the default version.

To fix this problem and other 3.15 related issues, I had to apply a
number of upstream commits:

- 
https://code.qt.io/cgit/pyside/pyside-setup.git/commit/?id=ccdba08a37998bbbabadf42abfae871af5df8ac3
 (Enumerations: Enable the forgiveness feature ENOPT_NO_ZERODEFAULT for 3.15)
- 
https://code.qt.io/cgit/pyside/pyside-setup.git/commit/?id=daca53fd7326646bb22d816ccb3ed58932ca0747
 (libshiboken: Increase robustness of star import check)
- 
https://code.qt.io/cgit/pyside/pyside-setup.git/commit/?id=df277d155916ea3b5a8499dafb9ffd022fd5728f
 (Fix the star import test for 3.15)
- 
https://code.qt.io/cgit/pyside/pyside-setup.git/commit/?id=3eb4199e0e0a80756ddb95dbc29ad4cf99c5f8ae
 (enum_test.py: Adapt to 3.15.0b2+dev)
- 
https://code.qt.io/cgit/pyside/pyside-setup.git/commit/?id=c3a4d68fd9dcbe4f1b9c8a74acf8056f48c8a7a8
 (Fix QtAsyncio to work with Python 3.15)
- 
https://code.qt.io/cgit/pyside/pyside-setup.git/commit/?id=33641ce807353f56d083f3ed1059bf67ce800e92
 (libpyside: Fix tests bug_408/994 crashing in debug mode or when using Python 
3.15)

I've applied these fixes in the sandbox [2] to verify that pyside6
builds successfully, please consider applying the patch to support the
upcoming 3.15 version.

Setting the severity to important for now. Once Python 3.15 becomes the
default version this bug will become release critical.

Happy hacking,

[1]: https://debusine.debian.net/debian/r-python-python3.15/artifact/4720943/
[2]: https://debusine.debian.net/debian/r-python-python3.15/

--
"Can you imagine what I would do if I could do all I can?" -- Sun Tzu
Saludos /\/\ /\ >< `/
From: Friedemann Kleint <[email protected]>
Date: Fri, 17 Apr 2026 14:18:25 +0200
Subject: Enumerations: Enable the forgiveness feature ENOPT_NO_ZERODEFAULT for 3.15

Adapt the op codes for detecting a parameterless __init__() call in
enum code to 3.15.

Task-number: PYSIDE-3221
Task-number: PYSIDE-1735
Change-Id: I21b8b8108e2d74e13ff4cb0f7fe2f41121361422
Reviewed-by: Shyamnath Premnadh <[email protected]>
Reviewed-by: Ece Cinucen <[email protected]>
Origin: upstream, https://code.qt.io/cgit/pyside/pyside-setup.git/commit/?id=ccdba08a37998bbbabadf42abfae871af5df8ac3
---
 sources/pyside6/tests/pysidetest/enum_test.py | 23 +++++++++++++++++--
 .../shiboken6/libshiboken/sbkfeature_base.cpp |  8 ++++++-
 sources/shiboken6/libshiboken/sbkmodule.cpp   |  2 ++
 3 files changed, 30 insertions(+), 3 deletions(-)

Index: pyside6/sources/pyside6/tests/pysidetest/enum_test.py
===================================================================
--- pyside6.orig/sources/pyside6/tests/pysidetest/enum_test.py
+++ pyside6/sources/pyside6/tests/pysidetest/enum_test.py
@@ -209,8 +209,27 @@ class InvestigateOpcodesTest(unittest.Te
                         ('LOAD_CONST',    82, 0),
                         ('RETURN_VALUE',  35, None)]
 
-        self.assertEqual(self.read_code(self.probe_function1), result_1)
-        self.assertEqual(self.read_code(self.probe_function2), result_2)
+        if sys.version_info[:2] >= (3, 15):
+            result_1 = [('RESUME', 128, 0),
+                        ('LOAD_GLOBAL', 91, 0),
+                        ('LOAD_ATTR', 79, 2),
+                        ('STORE_FAST', 111, 1),
+                        ('LOAD_CONST', 81, 0),
+                        ('RETURN_VALUE', 33, None)
+                        ]
+            result_2 = [('RESUME', 128, 0),
+                        ('LOAD_GLOBAL', 91, 0),
+                        ('LOAD_ATTR', 79, 2),
+                        ('PUSH_NULL', 31, None),
+                        ('CALL', 50, 0),
+                        ('STORE_FAST', 111, 1),
+                        ('LOAD_CONST', 81, 0),
+                        ('RETURN_VALUE', 33, None)]
+
+        actual1 = self.read_code(self.probe_function1)
+        actual2 = self.read_code(self.probe_function2)
+        self.assertEqual(actual1, result_1)
+        self.assertEqual(actual2, result_2)
 
 
 if __name__ == '__main__':
Index: pyside6/sources/shiboken6/libshiboken/sbkfeature_base.cpp
===================================================================
--- pyside6.orig/sources/shiboken6/libshiboken/sbkfeature_base.cpp
+++ pyside6/sources/shiboken6/libshiboken/sbkfeature_base.cpp
@@ -113,10 +113,12 @@ void disassembleFrame(const char *marker
 
 // OpCodes: Adapt for each Python version by checking the defines in the generated header opcode_ids.h
 // egrep '( LOAD_ATTR | CALL | PUSH_NULL )' opcode_ids.h
-// See also test sources/pyside6/tests/pysidetest/enum_test.py
+// See also test sources/pyside6/tests/pysidetest/enum_test.py, sbkmodule.cpp:300
 
 static int constexpr LOAD_ATTR_OpCode(long pyVersion)
 {
+    if (pyVersion >= 0x030F00) // 3.15
+        return 79;
     if (pyVersion >= 0x030E00) // 3.14
         return 80;
     if (pyVersion >= 0x030D00) // 3.13
@@ -126,6 +128,8 @@ static int constexpr LOAD_ATTR_OpCode(lo
 
 static int constexpr CALL_OpCode(long pyVersion)
 {
+    if (pyVersion >= 0x030F00) // 3.15
+        return 50;
     if (pyVersion >= 0x030E00) // 3.14
         return 52;
     if (pyVersion >= 0x030D00) // 3.13
@@ -135,6 +139,8 @@ static int constexpr CALL_OpCode(long py
 
 static int constexpr PUSH_NULL_OpCode(long pyVersion)
 {
+    if (pyVersion >= 0x030F00) // 3.15
+        return 31;
     if (pyVersion >= 0x030E00) // 3.14
         return 33;
     return 34;
Index: pyside6/sources/shiboken6/libshiboken/sbkmodule.cpp
===================================================================
--- pyside6.orig/sources/shiboken6/libshiboken/sbkmodule.cpp
+++ pyside6/sources/shiboken6/libshiboken/sbkmodule.cpp
@@ -276,6 +276,8 @@ static int constexpr LOAD_CONST_OpCode(l
 
 static int constexpr IMPORT_NAME_OpCode(long pyVersion)
 {
+    if (pyVersion >= 0x030F00) // 3.15
+        return 72;
     if (pyVersion >= 0x030E00) // 3.14
         return 73;
     if (pyVersion >= 0x030D00) // 3.13
From: Friedemann Kleint <[email protected]>
Date: Mon, 20 Apr 2026 11:03:31 +0200
Subject: libshiboken: Increase robustness of star import check

The code shows crashes in Python 3.15 due to out-of-bounds
tuple access after cpython/46d5106cfa903329821c097c3bf309e3efcd718f
(Lazy import).

Split out a helper function and perform more index checks.

Pick-to: 6.11 6.8
Task-number: PYSIDE-2404
Task-number: PYSIDE-3221
Change-Id: I6df5565198722a6201983d00497caf39cdd41904
Reviewed-by: Cristian Maureira-Fredes <[email protected]>
Origin: upstream, https://code.qt.io/cgit/pyside/pyside-setup.git/commit/?id=daca53fd7326646bb22d816ccb3ed58932ca0747
---
Index: pyside6/sources/shiboken6/libshiboken/sbkmodule.cpp
===================================================================
--- pyside6.orig/sources/shiboken6/libshiboken/sbkmodule.cpp
+++ pyside6/sources/shiboken6/libshiboken/sbkmodule.cpp
@@ -285,20 +285,65 @@ static int constexpr IMPORT_NAME_OpCode(
     return 108;
 }
 
-static bool isImportStar(PyObject *module)
+static bool detectImportStar(PyObject *dec_frame, PyObject *module)
 {
-    // Find out whether we have a star import. This must work even
-    // when we have no import support from feature.
     static PyObject *const _f_code = Shiboken::String::createStaticString("f_code");
     static PyObject *const _f_lasti = Shiboken::String::createStaticString("f_lasti");
-    static PyObject *const _f_back = Shiboken::String::createStaticString("f_back");
     static PyObject *const _co_code = Shiboken::String::createStaticString("co_code");
     static PyObject *const _co_consts = Shiboken::String::createStaticString("co_consts");
     static PyObject *const _co_names = Shiboken::String::createStaticString("co_names");
 
+    static const long pyVersion = _PepRuntimeVersion();
+    static const int LOAD_CONST = LOAD_CONST_OpCode(pyVersion);
+    static const int IMPORT_NAME = IMPORT_NAME_OpCode(pyVersion);
+
+    AutoDecRef dec_f_code(PyObject_GetAttr(dec_frame, _f_code));
+    AutoDecRef dec_co_code(PyObject_GetAttr(dec_f_code, _co_code));
+    AutoDecRef dec_f_lasti(PyObject_GetAttr(dec_frame, _f_lasti));
+    if (dec_f_code.isNull() || dec_co_code.isNull() || dec_f_lasti.isNull())
+        return false;
+
+    Py_ssize_t code_len{};
+    char *co_code{};
+    if (PyBytes_AsStringAndSize(dec_co_code, &co_code, &code_len) != 0)
+        return false;
+
+    const Py_ssize_t f_lasti = PyLong_AsSsize_t(dec_f_lasti);
+    if (f_lasti < 2 || f_lasti >= code_len - 1
+        || co_code[f_lasti] != IMPORT_NAME || co_code[f_lasti - 2] != LOAD_CONST) {
+        return false;
+    }
+
+    const Py_ssize_t oparg1 = uint8_t(co_code[f_lasti - 1]);
+    AutoDecRef dec_co_consts(PyObject_GetAttr(dec_f_code, _co_consts));
+    if (dec_co_consts.isNull() || PyTuple_Check(dec_co_consts.object()) == 0
+        || oparg1 >= PyTuple_Size(dec_co_consts.object())) {
+        return false;
+    }
+
+    auto *fromlist = PyTuple_GetItem(dec_co_consts, oparg1);
+    if (PyTuple_Check(fromlist) == 0 || PyTuple_Size(fromlist) != 1
+        || Shiboken::String::toCString(PyTuple_GetItem(fromlist, 0))[0] != '*') {
+        return false;
+    }
+
+    Py_ssize_t oparg2 = uint8_t(co_code[f_lasti + 1]);
+    AutoDecRef dec_co_names(PyObject_GetAttr(dec_f_code, _co_names));
+    if (dec_co_names.isNull() || PyTuple_Check(dec_co_names.object()) == 0
+        || oparg2 >= PyTuple_Size(dec_co_names.object())) {
+        return false;
+    }
+    const char *name = String::toCString(PyTuple_GetItem(dec_co_names, oparg2));
+    const char *modName = PyModule_GetName(module);
+    return std::strcmp(name, modName) == 0;
+}
 
-    static const int LOAD_CONST = LOAD_CONST_OpCode(_PepRuntimeVersion());
-    static const int IMPORT_NAME = IMPORT_NAME_OpCode(_PepRuntimeVersion());
+static bool isImportStar(PyObject *module)
+{
+    // Find out whether we have a star import. This must work even
+    // when we have no import support from feature.
+
+    static PyObject *const _f_back = Shiboken::String::createStaticString("f_back");
 
     auto *obFrame = reinterpret_cast<PyObject *>(PyEval_GetFrame());
     if (obFrame == nullptr)
@@ -310,29 +355,8 @@ static bool isImportStar(PyObject *modul
     // Calculate the offset of the running import_name opcode on the stack.
     // Right before that there must be a load_const with the tuple `("*",)`.
     while (dec_frame.object() != Py_None) {
-        AutoDecRef dec_f_code(PyObject_GetAttr(dec_frame, _f_code));
-        AutoDecRef dec_co_code(PyObject_GetAttr(dec_f_code, _co_code));
-        AutoDecRef dec_f_lasti(PyObject_GetAttr(dec_frame, _f_lasti));
-        Py_ssize_t f_lasti = PyLong_AsSsize_t(dec_f_lasti);
-        Py_ssize_t code_len{};
-        char *co_code{};
-        PyBytes_AsStringAndSize(dec_co_code, &co_code, &code_len);
-        uint8_t opcode2 = co_code[f_lasti];
-        uint8_t opcode1 = co_code[f_lasti - 2];
-        if (opcode1 == LOAD_CONST && opcode2 == IMPORT_NAME) {
-            uint8_t oparg1 = co_code[f_lasti - 1];
-            uint8_t oparg2 = co_code[f_lasti + 1];
-            AutoDecRef dec_co_consts(PyObject_GetAttr(dec_f_code, _co_consts));
-            auto *fromlist = PyTuple_GetItem(dec_co_consts, oparg1);
-            if (PyTuple_Check(fromlist) && PyTuple_Size(fromlist) == 1
-                    && Shiboken::String::toCString(PyTuple_GetItem(fromlist, 0))[0] == '*') {
-                AutoDecRef dec_co_names(PyObject_GetAttr(dec_f_code, _co_names));
-                const char *name = String::toCString(PyTuple_GetItem(dec_co_names, oparg2));
-                const char *modName = PyModule_GetName(module);
-                if (std::strcmp(name, modName) == 0)
-                    return true;
-            }
-        }
+        if (detectImportStar(dec_frame.object(), module))
+            return true;
         dec_frame.reset(PyObject_GetAttr(dec_frame, _f_back));
     }
     return false;
From: Friedemann Kleint <[email protected]>
Date: Mon, 20 Apr 2026 08:59:12 +0200
Subject: Fix the star import test for 3.15

Adapt the op codes for star import detection and adapt the oparg2
parameter handling to cpython/46d5106cfa903329821c097c3bf309e3efcd718f
(lazy import freature in 3.15).

Task-number: PYSIDE-3221
Task-number: PYSIDE-1735
Change-Id: Ief2a742c21d5073d5fae7ae409f0779cdd6adb77
Reviewed-by: Shyamnath Premnadh <[email protected]>
Origin: upstream, https://code.qt.io/cgit/pyside/pyside-setup.git/commit/?id=df277d155916ea3b5a8499dafb9ffd022fd5728f
---
Index: pyside6/sources/shiboken6/libshiboken/sbkmodule.cpp
===================================================================
--- pyside6.orig/sources/shiboken6/libshiboken/sbkmodule.cpp
+++ pyside6/sources/shiboken6/libshiboken/sbkmodule.cpp
@@ -267,6 +267,8 @@ static PyMethodDef module_methods[] = {
 
 static int constexpr LOAD_CONST_OpCode(long pyVersion)
 {
+    if (pyVersion >= 0x030F00) // 3.15
+        return 81;
     if (pyVersion >= 0x030E00) // 3.14
         return 82;
     if (pyVersion >= 0x030D00) // 3.13
@@ -328,6 +330,10 @@ static bool detectImportStar(PyObject *d
     }
 
     Py_ssize_t oparg2 = uint8_t(co_code[f_lasti + 1]);
+    // Cf cpython/46d5106cfa903329821c097c3bf309e3efcd718f, Lazy import in 3.15,
+    //cpython/Python/generated_cases.c.h:6410
+    if (pyVersion >= 0x030F00) // 3.15
+        oparg2 >>= 2;
     AutoDecRef dec_co_names(PyObject_GetAttr(dec_f_code, _co_names));
     if (dec_co_names.isNull() || PyTuple_Check(dec_co_names.object()) == 0
         || oparg2 >= PyTuple_Size(dec_co_names.object())) {
From: Friedemann Kleint <[email protected]>
Date: Mon, 15 Jun 2026 09:00:08 +0200
Subject: enum_test.py: Adapt to 3.15.0b2+dev

Adapt the op codes used to check for enumerations.

Complements ccdba08a37998bbbabadf42abfae871af5df8ac3.

Task-number: PYSIDE-3221
Task-number: PYSIDE-1735
Change-Id: I3ac4d397299d32ab2e49a00f7165659792a04092
Reviewed-by: Cristian Maureira-Fredes <[email protected]>
Origin: upstream, https://code.qt.io/cgit/pyside/pyside-setup.git/commit/?id=3eb4199e0e0a80756ddb95dbc29ad4cf99c5f8ae
---
Index: pyside6/sources/pyside6/tests/pysidetest/enum_test.py
===================================================================
--- pyside6.orig/sources/pyside6/tests/pysidetest/enum_test.py
+++ pyside6/sources/pyside6/tests/pysidetest/enum_test.py
@@ -214,7 +214,7 @@ class InvestigateOpcodesTest(unittest.Te
                         ('LOAD_GLOBAL', 91, 0),
                         ('LOAD_ATTR', 79, 2),
                         ('STORE_FAST', 111, 1),
-                        ('LOAD_CONST', 81, 0),
+                        ('LOAD_COMMON_CONSTANT', 80, 7),
                         ('RETURN_VALUE', 33, None)
                         ]
             result_2 = [('RESUME', 128, 0),
@@ -223,7 +223,7 @@ class InvestigateOpcodesTest(unittest.Te
                         ('PUSH_NULL', 31, None),
                         ('CALL', 50, 0),
                         ('STORE_FAST', 111, 1),
-                        ('LOAD_CONST', 81, 0),
+                        ('LOAD_COMMON_CONSTANT', 80, 7),
                         ('RETURN_VALUE', 33, None)]
 
         actual1 = self.read_code(self.probe_function1)
From: Friedemann Kleint <[email protected]>
Date: Thu, 17 Sep 2026 13:41:51 +0200
Subject: Fix QtAsyncio to work with Python 3.15

Import class ThreadPoolExecutor directly, fixing:

 File ".../pyside6/PySide6/QtAsyncio/events.py", line 213, in __init__
    self._default_executor = concurrent.futures.ThreadPoolExecutor()
                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
TypeError: 'lazy_import' object is not callable

Task-number: PYSIDE-3221
Change-Id: I26a5730d6bbd15114c6deb17e468db6778513708
Reviewed-by: Cristian Maureira-Fredes <[email protected]>
Origin: upstream, https://code.qt.io/cgit/pyside/pyside-setup.git/commit/?id=c3a4d68fd9dcbe4f1b9c8a74acf8056f48c8a7a8
---
Index: pyside6/sources/pyside6/PySide6/QtAsyncio/events.py
===================================================================
--- pyside6.orig/sources/pyside6/PySide6/QtAsyncio/events.py
+++ pyside6/sources/pyside6/PySide6/QtAsyncio/events.py
@@ -12,7 +12,7 @@ from typing import Any, Callable, TypeVa
 
 import asyncio
 import collections.abc
-import concurrent.futures
+from concurrent.futures import ThreadPoolExecutor
 import contextvars
 import enum
 import os
@@ -209,7 +209,7 @@ class QAsyncioEventLoop(asyncio.BaseEven
 
         # Starting with Python 3.11, this must be an instance of
         # ThreadPoolExecutor.
-        self._default_executor = concurrent.futures.ThreadPoolExecutor()
+        self._default_executor = ThreadPoolExecutor()
 
         # The exception handler, if set with set_exception_handler(). The
         # exception handler is currently called in two places: One, if an
@@ -575,7 +575,7 @@ class QAsyncioEventLoop(asyncio.BaseEven
 
     # Executing code in thread or process pools
 
-    def run_in_executor(self, executor: concurrent.futures.ThreadPoolExecutor | None,
+    def run_in_executor(self, executor: ThreadPoolExecutor | None,
                         func: Callable[[Unpack[_Ts]], _T],
                         *args: Unpack[_Ts]) -> asyncio.Future[_T]:
         if self.is_closed():
@@ -593,8 +593,8 @@ class QAsyncioEventLoop(asyncio.BaseEven
         return asyncio.futures.wrap_future(executor.submit(wrapper.do), loop=self)
 
     def set_default_executor(self,
-                             executor: concurrent.futures.ThreadPoolExecutor | None) -> None:
-        if not isinstance(executor, concurrent.futures.ThreadPoolExecutor):
+                             executor: ThreadPoolExecutor | None) -> None:
+        if not isinstance(executor, ThreadPoolExecutor):
             raise TypeError("The executor must be a ThreadPoolExecutor")
         self._default_executor = executor
 
From: Friedemann Kleint <[email protected]>
Date: Mon, 27 Apr 2026 22:02:54 +0200
Subject: libpyside: Fix tests bug_408/994 crashing in debug mode or when using Python 3.15

Python asserts about memory allocation for the metaObject attribute
string when not holding the GIL when connections  done in Qt C++ are
disconnected by the destructor, triggering
disconnectNotify()/metaObject(). To fix this, ensure the string is
created at initialization time.

The scenario of appears in ~QTextStream when wrapping it around
after another QIODevice after qtbase/e3c290e1947515992821e6bf97d74d65c9254271.

Task-number: PYSIDE-3221
Pick-to: 6.11 6.8
Change-Id: I76d0cf7567bfe073ad419b2f4b1d6617918bdb85
Reviewed-by: Shyamnath Premnadh <[email protected]>
Origin: upstream, https://code.qt.io/cgit/pyside/pyside-setup.git/commit/?id=33641ce807353f56d083f3ed1059bf67ce800e92
---
Index: pyside6/sources/pyside6/libpyside/signalmanager.cpp
===================================================================
--- pyside6.orig/sources/pyside6/libpyside/signalmanager.cpp
+++ pyside6/sources/pyside6/libpyside/signalmanager.cpp
@@ -295,6 +295,14 @@ static PyObject *CopyCppToPythonPyObject
 
 void SignalManager::init()
 {
+    // Force the metaObject attribute into existence. This fixes an
+    // exit crash (Python 3.15/allocation asserting since GIL is not held)
+    // when connections done in Qt C++ are disconnected by the destructor,
+    // triggering disconnectNotify()/metaObject().
+    // Note: SbkDeallocWrapperCommon() temporarily releases the GIL for
+    // legacy bug 500 (~QPrintDialog hanging).
+    [[maybe_unused]] auto *mo = metaObjectAttr();
+
     // Register Qt primitive typedefs used on signals.
     using namespace Shiboken;
 

Reply via email to