From 8e1476699fd4d8bc045800fb1e17dd45f14a3d13 Mon Sep 17 00:00:00 2001 From: Jookia Date: Sun, 7 Jul 2024 00:30:26 +1000 Subject: [PATCH] GUI: Work around Shiboken 6 bug for CallTips Shiboken 6.4.0 to 6.8.0 and newer crash when using getAttr on their objects. On these versions and newer, use a wrapper function to call getattr. Fixes #14101 --- src/Gui/CallTips.cpp | 51 +++++++++++++++++++++++++++++++++----------- src/Gui/CallTips.h | 1 + 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/Gui/CallTips.cpp b/src/Gui/CallTips.cpp index f022637eb8..1c9131022d 100644 --- a/src/Gui/CallTips.cpp +++ b/src/Gui/CallTips.cpp @@ -340,24 +340,51 @@ QMap CallTipsList::extractTips(const QString& context) const return tips; } +bool shibokenMayCrash(void) +{ + // Shiboken 6.4.0 to 6.8.0 crash if we try to read their object + // atttributes without a current stack frame. + // FreeCAD issue: https://github.com/FreeCAD/FreeCAD/issues/14101 + // Qt issue: https://bugreports.qt.io/browse/PYSIDE-2796 + + Py::Module shiboken("shiboken6"); + Py::Tuple version(shiboken.getAttr("__version_info__")); + int major = Py::Long(version.getItem(0)); + int minor = Py::Long(version.getItem(1)); + int patch = Py::Long(version.getItem(2)); + bool brokenVersion = (major == 6 && minor >= 4 && minor < 8); + bool fixedVersion = (major == 6 && minor == 7 && patch > 2); + return brokenVersion && !fixedVersion; +} + +Py::Object CallTipsList::getAttrWorkaround(Py::Object& obj, Py::String& name) const +{ + QString typestr(QLatin1String(Py_TYPE(obj.ptr())->tp_name)); + bool hasWorkingGetAttr = + !(typestr == QLatin1String("Shiboken.ObjectType") && shibokenMayCrash()); + + if (hasWorkingGetAttr) { + return obj.getAttr(name.as_string()); + } + + Py::Dict evalDict; + evalDict.setItem("obj", obj); + evalDict.setItem("attr", name); + + Py::Object bouncer(Py_CompileString("getattr(obj, attr)", "", Py_eval_input)); + Py::Object attr(PyEval_EvalCode(bouncer.ptr(), evalDict.ptr(), evalDict.ptr())); + + return attr; +} + void CallTipsList::extractTipsFromObject(Py::Object& obj, Py::List& list, QMap& tips) const { for (Py::List::iterator it = list.begin(); it != list.end(); ++it) { try { Py::String attrname(*it); - std::string name = attrname.as_string(); + std::string name(attrname.as_string()); - // If 'name' is an invalid attribute then PyCXX raises an exception - // for Py2 but silently accepts it for Py3. - // - // FIXME: Add methods of extension to the current instance and not its type object - // https://forum.freecad.org/viewtopic.php?f=22&t=18105 - // https://forum.freecad.org/viewtopic.php?f=3&t=20009&p=154447#p154447 - // https://forum.freecad.org/viewtopic.php?f=10&t=12534&p=155290#p155290 - // - // https://forum.freecad.org/viewtopic.php?f=39&t=33874&p=286759#p286759 - // https://forum.freecad.org/viewtopic.php?f=39&t=33874&start=30#p286772 - Py::Object attr = obj.getAttr(name); + Py::Object attr = getAttrWorkaround(obj, attrname); if (!attr.ptr()) { Base::Console().Log("Python attribute '%s' returns null!\n", name.c_str()); continue; diff --git a/src/Gui/CallTips.h b/src/Gui/CallTips.h index 04473b074a..91253f21a0 100644 --- a/src/Gui/CallTips.h +++ b/src/Gui/CallTips.h @@ -75,6 +75,7 @@ private: void extractTipsFromObject(Py::Object&, Py::List&, QMap&) const; void extractTipsFromProperties(Py::Object&, QMap&) const; QString stripWhiteSpace(const QString&) const; + Py::Object getAttrWorkaround(Py::Object&, Py::String&) const; private: QPlainTextEdit* textEdit;