aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Tismer <tismer@stackless.com>2022-11-21 12:06:18 +0100
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2022-11-29 21:10:22 +0000
commit1bb1f9b5ce6d1ad656c21768f2393d1bc75845d2 (patch)
tree9a6e6d9e8524e03c045afc0dd9f8604b4d49900e
parenta2d56c9fb19ab163ace79babe64578c4e1536bb3 (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>
-rw-r--r--sources/pyside6/libpyside/feature_select.cpp48
-rw-r--r--sources/pyside6/tests/pysidetest/CMakeLists.txt1
-rw-r--r--sources/pyside6/tests/pysidetest/snake_case_sub.py22
-rw-r--r--sources/pyside6/tests/pysidetest/snake_case_test.py34
-rw-r--r--sources/shiboken6/libshiboken/signature/signature.cpp13
-rw-r--r--sources/shiboken6/libshiboken/signature/signature_globals.cpp3
-rw-r--r--sources/shiboken6/libshiboken/signature_p.h1
-rw-r--r--sources/shiboken6/shibokenmodule/files.dir/shibokensupport/feature.py43
-rw-r--r--sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/loader.py7
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