diff options
author | Christian Tismer <tismer@stackless.com> | 2022-11-21 12:06:18 +0100 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2022-11-29 21:10:22 +0000 |
commit | 1bb1f9b5ce6d1ad656c21768f2393d1bc75845d2 (patch) | |
tree | 9a6e6d9e8524e03c045afc0dd9f8604b4d49900e | |
parent | a2d56c9fb19ab163ace79babe64578c4e1536bb3 (diff) |
__feature__: heavily rework the context switching
The example of the issue shows the qasync.py module which
fails miserably when using snake_case.
The reason:
-----------
Reason is the way how feature switching is implemented.
Modules like qasync get a default switching of "ignore".
This ignores that the qasync module itself imports QtCore,
and feature switching is of course relevant, suggesting
a default setting of "normal" (explicitly no features).
The real problem:
-----------------
Testing the simple approach showed a serious problem with
feature switching: The functions get switched when a certain
function (getattr etc.) is called.
But the switching is sometimes not done due to a caching problem.
This fix removes caching that was wrong. Optimization will
be done in a different step with a different approach.
This Change was not qasync specific, but happens with PySide imports.
Actions done:
- adjust the inline structure
- implement a feature_imported callback
- identify Python functions that use PySide during import
[ChangeLog][PySide6] __feature__ switching now works even with
recursive imports like in the qasync module.
Fixes: PYSIDE-2029
Change-Id: I3340f54f293083a09fb509383688f73bbd9b60ae
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
(cherry picked from commit 7377d2b8130ce7290775cd8a343e75c0561fc854)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
9 files changed, 132 insertions, 40 deletions
diff --git a/sources/pyside6/libpyside/feature_select.cpp b/sources/pyside6/libpyside/feature_select.cpp index b3117d40d..17c6169af 100644 --- a/sources/pyside6/libpyside/feature_select.cpp +++ b/sources/pyside6/libpyside/feature_select.cpp @@ -163,15 +163,6 @@ static inline void setCurrentSelectId(PyTypeObject *type, int id) SbkObjectType_SetReserved(type, id); } -static inline PyObject *getCurrentSelectId(PyTypeObject *type) -{ - int id = SbkObjectType_GetReserved(type); - // This can be too early. - if (id < 0) - id = 0; - return fast_id_array[id]; -} - static bool replaceClassDict(PyTypeObject *type) { /* @@ -218,7 +209,7 @@ static bool addNewDict(PyTypeObject *type, PyObject *select_id) return true; } -static bool moveToFeatureSet(PyTypeObject *type, PyObject *select_id) +static inline bool moveToFeatureSet(PyTypeObject *type, PyObject *select_id) { /* * Rotate the ring to the given `select_id` and return `true`. @@ -227,7 +218,6 @@ static bool moveToFeatureSet(PyTypeObject *type, PyObject *select_id) auto initial_dict = type->tp_dict; auto dict = initial_dict; do { - dict = nextInCircle(dict); AutoDecRef current_id(getSelectId(dict)); // This works because small numbers are singleton objects. if (current_id == select_id) { @@ -235,6 +225,7 @@ static bool moveToFeatureSet(PyTypeObject *type, PyObject *select_id) setCurrentSelectId(type, select_id); return true; } + dict = nextInCircle(dict); } while (dict != initial_dict); type->tp_dict = initial_dict; setCurrentSelectId(type, getSelectId(initial_dict)); @@ -290,7 +281,7 @@ static bool createNewFeatureSet(PyTypeObject *type, PyObject *select_id) return true; } -static bool SelectFeatureSetSubtype(PyTypeObject *type, PyObject *select_id) +static inline bool SelectFeatureSetSubtype(PyTypeObject *type, PyObject *select_id) { /* * This is the selector for one sublass. We need to call this for @@ -330,28 +321,21 @@ static inline void SelectFeatureSet(PyTypeObject *type) return; } } + PyObject *select_id = getFeatureSelectId(); // borrowed - PyObject *current_id = getCurrentSelectId(type); // borrowed - static PyObject *undef = fast_id_array[-1]; - - // PYSIDE-1019: During import PepType_SOTP is still zero. - if (current_id == undef) - current_id = select_id = fast_id_array[0]; - - if (select_id != current_id) { - PyObject *mro = type->tp_mro; - Py_ssize_t idx, n = PyTuple_GET_SIZE(mro); - // We leave 'Shiboken.Object' and 'object' alone, therefore "n - 2". - for (idx = 0; idx < n - 2; idx++) { - auto *sub_type = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx)); - // When any subtype is already resolved (false), we can stop. - if (!SelectFeatureSetSubtype(sub_type, select_id)) - break; - } - // PYSIDE-1436: Clear all caches for the type and subtypes. - PyType_Modified(type); + + // PYSIDE-2029: We are no longer caching extremely, but switching safe. + PyObject *mro = type->tp_mro; + Py_ssize_t idx, n = PyTuple_GET_SIZE(mro); + // We leave 'Shiboken.Object' and 'object' alone, therefore "n - 2". + for (idx = 0; idx < n - 2; idx++) { + auto *sub_type = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx)); + // When any subtype is already resolved (false), we can stop. + if (!SelectFeatureSetSubtype(sub_type, select_id)) + break; } - return; + // PYSIDE-1436: Clear all caches for the type and subtypes. + PyType_Modified(type); } // For cppgenerator: diff --git a/sources/pyside6/tests/pysidetest/CMakeLists.txt b/sources/pyside6/tests/pysidetest/CMakeLists.txt index 3965455c9..eaf748517 100644 --- a/sources/pyside6/tests/pysidetest/CMakeLists.txt +++ b/sources/pyside6/tests/pysidetest/CMakeLists.txt @@ -144,6 +144,7 @@ PYSIDE_TEST(new_inherited_functions_test.py) PYSIDE_TEST(notify_id.py) PYSIDE_TEST(properties_test.py) PYSIDE_TEST(property_python_test.py) +PYSIDE_TEST(snake_case_test.py) PYSIDE_TEST(true_property_test.py) PYSIDE_TEST(qapp_like_a_macro_test.py) PYSIDE_TEST(qvariant_test.py) diff --git a/sources/pyside6/tests/pysidetest/snake_case_sub.py b/sources/pyside6/tests/pysidetest/snake_case_sub.py new file mode 100644 index 000000000..5056d50bb --- /dev/null +++ b/sources/pyside6/tests/pysidetest/snake_case_sub.py @@ -0,0 +1,22 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import os +import sys +import unittest + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from init_paths import init_test_paths +init_test_paths(False) + +""" +PYSIDE-2029: Tests that snake_case is isolated from imported modules +""" + +from PySide6.QtWidgets import QWidget + +def test_no_snake_case(): + print(__name__) + widget = QWidget() + check = widget.sizeHint diff --git a/sources/pyside6/tests/pysidetest/snake_case_test.py b/sources/pyside6/tests/pysidetest/snake_case_test.py new file mode 100644 index 000000000..aaa3d3f2a --- /dev/null +++ b/sources/pyside6/tests/pysidetest/snake_case_test.py @@ -0,0 +1,34 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import os +import sys +import unittest + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from init_paths import init_test_paths +init_test_paths(False) + +""" +PYSIDE-2029: Tests that snake_case is isolated from imported modules +""" + +from PySide6.QtCore import QSize +from PySide6.QtWidgets import QWidget, QSpinBox +from __feature__ import snake_case +from helper.usesqapplication import UsesQApplication + +import snake_case_sub + +class SnakeCaseNoPropagateTest(UsesQApplication): + + def testSnakeCase(self): + # this worked + widget = QWidget() + check = widget.size_hint + + snake_case_sub.test_no_snake_case() + +if __name__ == '__main__': + unittest.main() diff --git a/sources/shiboken6/libshiboken/signature/signature.cpp b/sources/shiboken6/libshiboken/signature/signature.cpp index 6c28c758b..808edc5cf 100644 --- a/sources/shiboken6/libshiboken/signature/signature.cpp +++ b/sources/shiboken6/libshiboken/signature/signature.cpp @@ -284,7 +284,18 @@ static PyObject *feature_import(PyObject * /* self */, PyObject *args, PyObject if (import_func == nullptr) { Py_FatalError("builtins has no \"__orig_import__\" function"); } - return PyObject_Call(import_func, args, kwds); + ret = PyObject_Call(import_func, args, kwds); + if (ret) { + // PYSIDE-2029: Intercept after the import to search for PySide usage. + PyObject *post = PyObject_CallFunctionObjArgs(pyside_globals->feature_imported_func, + ret, nullptr); + Py_XDECREF(post); + if (post == nullptr) { + Py_DECREF(ret); + return nullptr; + } + } + return ret; } PyMethodDef signature_methods[] = { diff --git a/sources/shiboken6/libshiboken/signature/signature_globals.cpp b/sources/shiboken6/libshiboken/signature/signature_globals.cpp index b861e791a..d0ea86fc6 100644 --- a/sources/shiboken6/libshiboken/signature/signature_globals.cpp +++ b/sources/shiboken6/libshiboken/signature/signature_globals.cpp @@ -186,6 +186,9 @@ static int init_phase_2(safe_globals_struc *p, PyMethodDef *methods) p->feature_import_func = PyObject_GetAttrString(loader, "feature_import"); if (p->feature_import_func == nullptr) break; + p->feature_imported_func = PyObject_GetAttrString(loader, "feature_imported"); + if (p->feature_imported_func == nullptr) + break; return 0; } while (0); diff --git a/sources/shiboken6/libshiboken/signature_p.h b/sources/shiboken6/libshiboken/signature_p.h index faa98f93c..7ea03877a 100644 --- a/sources/shiboken6/libshiboken/signature_p.h +++ b/sources/shiboken6/libshiboken/signature_p.h @@ -24,6 +24,7 @@ typedef struct safe_globals_struc { PyObject *make_helptext_func; PyObject *finish_import_func; PyObject *feature_import_func; + PyObject *feature_imported_func; } safe_globals_struc, *safe_globals; extern safe_globals pyside_globals; diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/feature.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/feature.py index 92bc30d2b..5171d59e9 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/feature.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/feature.py @@ -18,6 +18,7 @@ and takes an optional `mod_name` parameter. The select id `-1` has the spectial meaning "ignore this module". """ +import inspect import sys from contextlib import contextmanager @@ -84,6 +85,7 @@ def feature_import(name, *args, **kwargs): # PYSIDE-1398: sys._getframe(1) may not exist when embedding. # PYSIDE-1338: The "1" below is the redirection in loader.py . # PYSIDE-1548: Ensure that features are not affected by other imports. + # PYSIDE-2029: Need to always switch. The cache was wrong interpreted. calling_frame = _cf = sys._getframe(1).f_back importing_module = _cf.f_globals.get("__name__", "__main__") if _cf else "__main__" existing = pyside_feature_dict.get(importing_module, 0) @@ -105,11 +107,6 @@ def feature_import(name, *args, **kwargs): # Initialize feature (multiple times allowed) and clear cache. sys.modules["PySide6.QtCore"].__init_feature__() return sys.modules["__feature__"] - - if importing_module not in pyside_feature_dict: - # Ignore new modules if not from PySide. - default = 0 if name.split(".")[0] == "PySide6" else -1 - pyside_feature_dict[importing_module] = default # Redirect to the original import return None @@ -121,11 +118,43 @@ def __init__(): # use _one_ recursive import... import PySide6.QtCore # Initialize all prior imported modules - for name in sys.modules: - pyside_feature_dict.setdefault(name, -1) + for name, module in sys.modules.items(): + if name not in pyside_feature_dict: + pyside_feature_dict[name] = 0 if _mod_uses_pyside(module) else -1 _is_initialized = True +def feature_imported(module): + # PYSIDE-2029: Need to inspect imported modules for PySide usage. + """ + Set the module feature default after import. + + A module that uses PySide has a switching default of 0 = "no feature". + Otherwise the default is -1 = "ignore this module". + """ + name = module.__name__ + if name not in pyside_feature_dict: + pyside_feature_dict[name] = 0 if _mod_uses_pyside(module) else -1 + + +def _mod_uses_pyside(module): + """ + Find out if this module uses PySide. + + Simple approach: Search the source code for the string "PySide6". + Maybe we later support source-less modules by inspecting all code objects. + """ + try: + source = inspect.getsource(module) + except TypeError: + # this is a builtin module like sys + return False + except OSError: + # this is a module withot source file + return False + return "PySide6" in source + + def set_selection(select_id, mod_name=None): """ Internal use: Set the feature directly by Id. diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/loader.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/loader.py index 34977f3bd..b6a432ba9 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/loader.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/loader.py @@ -53,6 +53,13 @@ def feature_import(*args, **kwds): feature_import = feature.feature_import return feature_import(*args, **kwds) +# name used in signature.cpp +def feature_imported(*args, **kwds): + # don't spend a stack level here for speed and compatibility + global feature_imported + feature_imported = feature.feature_imported + return feature_imported(*args, **kwds) + import builtins import signature_bootstrap |