aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Tismer <tismer@stackless.com>2024-03-15 13:46:02 +0100
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2024-03-21 13:45:57 +0000
commitca30731b7b883106c86a679cf051c783f0093e2d (patch)
treee660e3587f0711736a196b0e0d7b0eb4227d127a
parentfb5cda4626b3be85861e96f7c03bbefc4636749d (diff)
Lazy Init: Evict the Achilles Heel of Laziness and fix Nuitka
Instead of using the external __getattr__ attribute, patch the module tp_getattro function globally. NOTE: The error was a rare racing condition in Nuitka which was fixed by early insertion of a module into sys.modules . The Achilles heel was not the cause. Task-number: PYSIDE-2404 Change-Id: I929a9187f77cde7cde8318db28d4404b8ba8c1b3 Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> (cherry picked from commit c828416efc0b88747cab85d941a149d91487466f) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--sources/shiboken6/libshiboken/sbkmodule.cpp103
1 files changed, 53 insertions, 50 deletions
diff --git a/sources/shiboken6/libshiboken/sbkmodule.cpp b/sources/shiboken6/libshiboken/sbkmodule.cpp
index 356cb6220..b705e8380 100644
--- a/sources/shiboken6/libshiboken/sbkmodule.cpp
+++ b/sources/shiboken6/libshiboken/sbkmodule.cpp
@@ -55,8 +55,11 @@ LIBSHIBOKEN_API PyTypeObject *get(TypeInitStruct &typeStruct)
auto startPos = dotPos + 1;
AutoDecRef modName(String::fromCppStringView(names.substr(0, dotPos)));
auto *modOrType = PyDict_GetItem(sysModules, modName);
- if (!modOrType)
- modOrType = PyImport_Import(modName);
+ if (modOrType == nullptr) {
+ PyErr_Format(PyExc_SystemError, "Module %s should already be in sys.modules",
+ PyModule_GetName(modOrType));
+ return nullptr;
+ }
do {
dotPos = names.find('.', startPos);
@@ -78,6 +81,7 @@ static PyTypeObject *incarnateType(PyObject *module, const char *name,
auto funcIter = nameToFunc.find(name);
if (funcIter == nameToFunc.end()) {
// attribute does really not exist.
+ PyErr_SetNone(PyExc_AttributeError);
return nullptr;
}
// - call this function that returns a PyTypeObject
@@ -137,31 +141,43 @@ void resolveLazyClasses(PyObject *module)
}
}
-// PYSIDE-2404: Use module getattr to do on-demand initialization.
-static PyObject *_module_getattr_template(PyObject * /* self */, PyObject *args)
+// PYSIDE-2404: Override the gettattr function of modules.
+static getattrofunc origModuleGetattro{};
+
+// PYSIDE-2404: Use the patched module getattr to do on-demand initialization.
+// This modifies _all_ modules but should have no impact.
+static PyObject *PyModule_lazyGetAttro(PyObject *module, PyObject *name)
{
- // An attribute was not found. Look it up in the shadow dict, resolve it
- // and put it into the module dict afterwards.
- PyObject *module{};
- PyObject *attrName{};
- if (!PyArg_ParseTuple(args, "OO", &module, &attrName))
- return nullptr;
+ // - check if the attribute is present and return it.
+ auto *attr = PyObject_GenericGetAttr(module, name);
+ // - we handle AttributeError, only.
+ if (!(attr == nullptr && PyErr_ExceptionMatches(PyExc_AttributeError)))
+ return attr;
+ PyErr_Clear();
// - locate the module in the moduleTofuncs mapping
auto tableIter = moduleToFuncs.find(module);
- assert(tableIter != moduleToFuncs.end());
+ // - if this is not our module, use the original
+ if (tableIter == moduleToFuncs.end())
+ return origModuleGetattro(module, name);
+
// - locate the name and retrieve the generating function
- const char *attrNameStr = Shiboken::String::toCString(attrName);
+ const char *attrNameStr = Shiboken::String::toCString(name);
auto &nameToFunc = tableIter->second;
-
+ // - create the real type (incarnateType checks this)
auto *type = incarnateType(module, attrNameStr, nameToFunc);
auto *ret = reinterpret_cast<PyObject *>(type);
- if (ret == nullptr) // attribute does really not exist. Should not happen.
- PyErr_SetNone(PyExc_AttributeError);
+ // - if attribute does really not exist use the original
+ if (ret == nullptr && PyErr_ExceptionMatches(PyExc_AttributeError)) {
+ PyErr_Clear();
+ return origModuleGetattro(module, name);
+ }
+
return ret;
}
// PYSIDE-2404: Supply a new module dir for not yet visible entries.
+// This modification is only for "our" modules.
static PyObject *_module_dir_template(PyObject * /* self */, PyObject *args)
{
static PyObject *const _dict = Shiboken::String::createStaticString("__dict__");
@@ -184,8 +200,7 @@ static PyObject *_module_dir_template(PyObject * /* self */, PyObject *args)
return ret;
}
-PyMethodDef module_methods[] = {
- {"__getattr__", (PyCFunction)_module_getattr_template, METH_VARARGS, nullptr},
+static PyMethodDef module_methods[] = {
{"__dir__", (PyCFunction)_module_dir_template, METH_VARARGS, nullptr},
{nullptr, nullptr, 0, nullptr}
};
@@ -281,6 +296,9 @@ void AddTypeCreationFunction(PyObject *module,
const char *name,
TypeCreationFunction func)
{
+ static const char *flag = getenv("PYSIDE6_OPTION_LAZY");
+ static const int value = flag != nullptr ? std::atoi(flag) : 1;
+
// - locate the module in the moduleTofuncs mapping
auto tableIter = moduleToFuncs.find(module);
assert(tableIter != moduleToFuncs.end());
@@ -301,8 +319,6 @@ void AddTypeCreationFunction(PyObject *module,
// 3 - lazy loading for any module.
//
// By default we lazy load all known modules (option = 1).
- const auto *flag = getenv("PYSIDE6_OPTION_LAZY");
- const int value = flag != nullptr ? std::atoi(flag) : 1;
if (value == 0 // completely disabled
|| canNotLazyLoad(module) // for some reason we cannot lazy load
@@ -398,40 +414,21 @@ static PyMethodDef lazy_methods[] = {
{nullptr, nullptr, 0, nullptr}
};
-// PYSIDE-2404: Nuitka is stealing our `__getattr__` entry from the
-// module dicts. Until we remove this vulnerability from
-// our modules, we disable Lazy Init when Nuitka is present.
-static bool isNuitkaPresent()
-{
- static PyObject *const sysModules = PyImport_GetModuleDict();
- static PyObject *const compiled = Shiboken::String::createStaticString("__compiled__");
-
- PyObject *key{}, *value{};
- Py_ssize_t pos = 0;
- while (PyDict_Next(sysModules, &pos, &key, &value)) {
- if (PyObject_HasAttr(value, compiled))
- return true;
- }
- return false;
-}
-
PyObject *create(const char * /* modName */, void *moduleData)
{
+ static auto *sysModules = PyImport_GetModuleDict();
static auto *builtins = PyEval_GetBuiltins();
static auto *partial = Pep_GetPartialFunction();
static bool lazy_init{};
- static bool nuitkaPresent = isNuitkaPresent();
Shiboken::init();
auto *module = PyModule_Create(reinterpret_cast<PyModuleDef *>(moduleData));
- // Setup of a getattr function for "missing" classes and a dir replacement.
- for (int idx = 0; module_methods[idx].ml_name != nullptr; ++idx) {
- auto *pyFuncPlus = PyCFunction_NewEx(&module_methods[idx], nullptr, nullptr);
- // Turn this function into a bound object, so we have access to the module.
- auto *pyFunc = PyObject_CallFunctionObjArgs(partial, pyFuncPlus, module, nullptr);
- PyModule_AddObject(module, module_methods[idx].ml_name, pyFunc); // steals reference
- }
+ // Setup of a dir function for "missing" classes.
+ auto *moduleDirTemplate = PyCFunction_NewEx(module_methods, nullptr, nullptr);
+ // Turn this function into a bound object, so we have access to the module.
+ auto *moduleDir = PyObject_CallFunctionObjArgs(partial, moduleDirTemplate, module, nullptr);
+ PyModule_AddObject(module, module_methods->ml_name, moduleDir); // steals reference
// Insert an initial empty table for the module.
NameToTypeFunctionMap empty;
moduleToFuncs.insert(std::make_pair(module, empty));
@@ -440,18 +437,24 @@ PyObject *create(const char * /* modName */, void *moduleData)
if (isImportStar(module))
dontLazyLoad.insert(PyModule_GetName(module));
- // For now, we also need to disable Lazy Init when Nuitka is there.
- if (nuitkaPresent)
- dontLazyLoad.insert(PyModule_GetName(module));
-
- // Also add the lazy import redirection.
if (!lazy_init) {
+ // Install the getattr patch.
+ origModuleGetattro = PyModule_Type.tp_getattro;
+ PyModule_Type.tp_getattro = PyModule_lazyGetAttro;
+ // Add the lazy import redirection.
origImportFunc = PyDict_GetItemString(builtins, "__import__");
- // The single function to be called.
auto *func = PyCFunction_NewEx(lazy_methods, nullptr, nullptr);
PyDict_SetItemString(builtins, "__import__", func);
+ // Everything is set.
lazy_init = true;
}
+ // PYSIDE-2404: Nuitka inserts some additional code in standalone mode
+ // in an invisible virtual module (i.e. `QtCore-postLoad`)
+ // that gets imported before the running import can call
+ // `_PyImport_FixupExtensionObject` which does the insertion
+ // into `sys.modules`. This can cause a race condition.
+ // Insert the module early into the module dict to prevend recursion.
+ PyDict_SetItemString(sysModules, PyModule_GetName(module), module);
return module;
}