aboutsummaryrefslogtreecommitdiffstats
path: root/sources/shiboken6/libshiboken/sbkfeature_base.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'sources/shiboken6/libshiboken/sbkfeature_base.cpp')
-rw-r--r--sources/shiboken6/libshiboken/sbkfeature_base.cpp424
1 files changed, 424 insertions, 0 deletions
diff --git a/sources/shiboken6/libshiboken/sbkfeature_base.cpp b/sources/shiboken6/libshiboken/sbkfeature_base.cpp
new file mode 100644
index 000000000..f31b8f4f7
--- /dev/null
+++ b/sources/shiboken6/libshiboken/sbkfeature_base.cpp
@@ -0,0 +1,424 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "basewrapper.h"
+#include "basewrapper_p.h"
+#include "autodecref.h"
+#include "pep384ext.h"
+#include "sbkenum.h"
+#include "sbkstring.h"
+#include "sbkstaticstrings.h"
+#include "sbkstaticstrings_p.h"
+#include "signature.h"
+#include "sbkfeature_base.h"
+#include "gilstate.h"
+
+#include <cctype>
+
+using namespace Shiboken;
+
+extern "C"
+{
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Minimal __feature__ support in Shiboken
+//
+int currentSelectId(PyTypeObject *type)
+{
+ AutoDecRef tpDict(PepType_GetDict(type));
+ PyObject *PyId = PyObject_GetAttr(tpDict.object(), PyName::select_id());
+ if (PyId == nullptr) {
+ PyErr_Clear();
+ return 0x00;
+ }
+ int sel = PyLong_AsLong(PyId);
+ Py_DECREF(PyId);
+ return sel;
+}
+
+static SelectableFeatureHook SelectFeatureSet = nullptr;
+static SelectableFeatureCallback featureCb = nullptr;
+
+void setSelectableFeatureCallback(SelectableFeatureCallback func)
+{
+ featureCb = func;
+}
+
+SelectableFeatureHook initSelectableFeature(SelectableFeatureHook func)
+{
+ auto ret = SelectFeatureSet;
+ SelectFeatureSet = func;
+ if (featureCb)
+ featureCb(SelectFeatureSet != nullptr);
+ return ret;
+}
+//
+////////////////////////////////////////////////////////////////////////////
+
+// This useful function is for debugging
+void disassembleFrame(const char *marker)
+{
+ Shiboken::GilState gil;
+ PyObject *error_type, *error_value, *error_traceback;
+ PyErr_Fetch(&error_type, &error_value, &error_traceback);
+ static PyObject *dismodule = PyImport_ImportModule("dis");
+ static PyObject *disco = PyObject_GetAttrString(dismodule, "disco");
+ static PyObject *const _f_lasti = Shiboken::String::createStaticString("f_lasti");
+ static PyObject *const _f_lineno = Shiboken::String::createStaticString("f_lineno");
+ static PyObject *const _f_code = Shiboken::String::createStaticString("f_code");
+ static PyObject *const _co_filename = Shiboken::String::createStaticString("co_filename");
+ AutoDecRef ignore{};
+ auto *frame = reinterpret_cast<PyObject *>(PyEval_GetFrame());
+ if (frame == nullptr) {
+ fprintf(stdout, "\n%s BEGIN no frame END\n\n", marker);
+ } else {
+ AutoDecRef f_lasti(PyObject_GetAttr(frame, _f_lasti));
+ AutoDecRef f_lineno(PyObject_GetAttr(frame, _f_lineno));
+ AutoDecRef f_code(PyObject_GetAttr(frame, _f_code));
+ AutoDecRef co_filename(PyObject_GetAttr(f_code, _co_filename));
+ long line = PyLong_AsLong(f_lineno);
+ const char *fname = String::toCString(co_filename);
+ fprintf(stdout, "\n%s BEGIN line=%ld %s\n", marker, line, fname);
+ ignore.reset(PyObject_CallFunctionObjArgs(disco, f_code.object(), f_lasti.object(), nullptr));
+ fprintf(stdout, "%s END line=%ld %s\n\n", marker, line, fname);
+ }
+#if PY_VERSION_HEX >= 0x030C0000 && !Py_LIMITED_API
+ if (error_type)
+ PyErr_DisplayException(error_value);
+#endif
+ static PyObject *stdout_file = PySys_GetObject("stdout");
+ ignore.reset(PyObject_CallMethod(stdout_file, "flush", nullptr));
+ PyErr_Restore(error_type, error_value, error_traceback);
+}
+
+// python 3.12
+static int const CALL = 171;
+// Python 3.11
+static int const PRECALL = 166;
+// we have "big instructions" with gaps after them
+static int const LOAD_METHOD_GAP_311 = 10 * 2;
+static int const LOAD_ATTR_GAP_311 = 4 * 2;
+static int const LOAD_ATTR_GAP = 9 * 2;
+// Python 3.7 - 3.10
+static int const LOAD_METHOD = 160;
+static int const CALL_METHOD = 161;
+// Python 3.6
+static int const CALL_FUNCTION = 131;
+static int const LOAD_ATTR = 106;
+// NoGil (how long will this exist in this form?)
+static int const LOAD_METHOD_NOGIL = 55;
+static int const CALL_METHOD_NOGIL = 72;
+
+static bool currentOpcode_Is_CallMethNoArgs()
+{
+ // PYSIDE-2221: Special case for the NoGil version:
+ // Find out if we have such a version.
+ // We could also ask the variable `Py_NOGIL`.
+ static PyObject *flags = PySys_GetObject("flags");
+ static bool isNoGil = PyObject_HasAttrString(flags, "nogil");
+ // We look into the currently active operation if we are going to call
+ // a method with zero arguments.
+ auto *frame = PyEval_GetFrame();
+#if !Py_LIMITED_API && !defined(PYPY_VERSION)
+ auto *f_code = PyFrame_GetCode(frame);
+#else
+ static PyObject *const _f_code = Shiboken::String::createStaticString("f_code");
+ AutoDecRef dec_f_code(PyObject_GetAttr(reinterpret_cast<PyObject *>(frame), _f_code));
+ auto *f_code = dec_f_code.object();
+#endif
+#if PY_VERSION_HEX >= 0x030B0000 && !Py_LIMITED_API
+ AutoDecRef dec_co_code(PyCode_GetCode(f_code));
+ Py_ssize_t f_lasti = PyFrame_GetLasti(frame);
+#else
+ static PyObject *const _f_lasti = Shiboken::String::createStaticString("f_lasti");
+ static PyObject *const _co_code = Shiboken::String::createStaticString("co_code");
+ AutoDecRef dec_co_code(PyObject_GetAttr(reinterpret_cast<PyObject *>(f_code), _co_code));
+ AutoDecRef dec_f_lasti(PyObject_GetAttr(reinterpret_cast<PyObject *>(frame), _f_lasti));
+ Py_ssize_t f_lasti = PyLong_AsSsize_t(dec_f_lasti);
+#endif
+ Py_ssize_t code_len;
+ char *co_code{};
+ PyBytes_AsStringAndSize(dec_co_code, &co_code, &code_len);
+ uint8_t opcode1 = co_code[f_lasti];
+ if (isNoGil) {
+ uint8_t opcode2 = co_code[f_lasti + 4];
+ uint8_t oparg2 = co_code[f_lasti + 6];
+ return opcode1 == LOAD_METHOD_NOGIL && opcode2 == CALL_METHOD_NOGIL && oparg2 == 1;
+ }
+ uint8_t opcode2 = co_code[f_lasti + 2];
+ uint8_t oparg2 = co_code[f_lasti + 3];
+ static auto number = _PepRuntimeVersion();
+ if (number < 0x030B00)
+ return opcode1 == LOAD_METHOD && opcode2 == CALL_METHOD && oparg2 == 0;
+
+ if (number < 0x030C00) {
+ // With Python 3.11, the opcodes get bigger and change a bit.
+ // Note: The new adaptive opcodes are elegantly hidden and we
+ // don't need to take care of them.
+ if (opcode1 == LOAD_METHOD)
+ f_lasti += LOAD_METHOD_GAP_311;
+ else if (opcode1 == LOAD_ATTR)
+ f_lasti += LOAD_ATTR_GAP_311;
+ else
+ return false;
+
+ opcode2 = co_code[f_lasti + 2];
+ oparg2 = co_code[f_lasti + 3];
+
+ return opcode2 == PRECALL && oparg2 == 0;
+ }
+ // With Python 3.12, the opcodes get again bigger and change a bit.
+ // Note: The new adaptive opcodes are elegantly hidden and we
+ // don't need to take care of them.
+ if (opcode1 == LOAD_ATTR)
+ f_lasti += LOAD_ATTR_GAP;
+ else
+ return false;
+
+ opcode2 = co_code[f_lasti + 2];
+ oparg2 = co_code[f_lasti + 3];
+
+ return opcode2 == CALL && oparg2 == 0;
+}
+
+void initEnumFlagsDict(PyTypeObject *type)
+{
+ // We create a dict for all flag enums that holds the original C++ name
+ // and a dict that gives every enum/flag type name.
+ static PyObject *const split = Shiboken::String::createStaticString("split");
+ static PyObject *const colon = Shiboken::String::createStaticString(":");
+ auto sotp = PepType_SOTP(type);
+ auto **enumFlagInfo = sotp->enumFlagInfo;
+ auto *dict = PyDict_New();
+ auto *typeDict = PyDict_New();
+ for (; *enumFlagInfo; ++enumFlagInfo) {
+ AutoDecRef line(PyUnicode_FromString(*enumFlagInfo));
+ AutoDecRef parts(PyObject_CallMethodObjArgs(line, split, colon, nullptr));
+ auto *name = PyList_GetItem(parts, 0);
+ if (PyList_Size(parts) == 3) {
+ auto *key = PyList_GetItem(parts, 2);
+ auto *value = name;
+ PyDict_SetItem(dict, key, value);
+ }
+ auto *typeName = PyList_GetItem(parts, 1);
+ PyDict_SetItem(typeDict, name, typeName);
+ }
+ sotp->enumFlagsDict = dict;
+ sotp->enumTypeDict = typeDict;
+}
+
+static PyObject *replaceNoArgWithZero(PyObject *callable)
+{
+ static auto *partial = Pep_GetPartialFunction();
+ static auto *zero = PyLong_FromLong(0);
+ return PyObject_CallFunctionObjArgs(partial, callable, zero, nullptr);
+}
+
+static PyObject *lookupUnqualifiedOrOldEnum(PyTypeObject *type, PyObject *name)
+{
+ // MRO has been observed to be 0 in case of errors with QML decorators
+ if (type == nullptr || type->tp_mro == nullptr)
+ return nullptr;
+ // Quick Check: Avoid "__..", "_slots", etc.
+ if (std::isalpha(Shiboken::String::toCString(name)[0]) == 0)
+ return nullptr;
+ static PyTypeObject *const EnumMeta = getPyEnumMeta();
+ static PyObject *const _member_map_ = String::createStaticString("_member_map_");
+ // This is similar to `find_name_in_mro`, but instead of looking directly into
+ // tp_dict, we also search for the attribute in local classes of that dict (Part 2).
+ PyObject *mro = type->tp_mro;
+ PyObject *result{};
+ assert(PyTuple_Check(mro));
+ Py_ssize_t idx, n = PyTuple_GET_SIZE(mro);
+ for (idx = 0; idx < n; ++idx) {
+ auto *base = PyTuple_GET_ITEM(mro, idx);
+ auto *type_base = reinterpret_cast<PyTypeObject *>(base);
+ if (!SbkObjectType_Check(type_base))
+ continue;
+ auto sotp = PepType_SOTP(type_base);
+ // The EnumFlagInfo structure tells us if there are Enums at all.
+ const char **enumFlagInfo = sotp->enumFlagInfo;
+ if (!(enumFlagInfo))
+ continue;
+ if (!sotp->enumFlagsDict)
+ initEnumFlagsDict(type_base);
+ bool useFakeRenames = !(Enum::enumOption & Enum::ENOPT_NO_FAKERENAMES);
+ if (useFakeRenames) {
+ auto *rename = PyDict_GetItem(sotp->enumFlagsDict, name);
+ if (rename) {
+ /*
+ * Part 1: Look into the enumFlagsDict if we have an old flags name.
+ * -------------------------------------------------------------
+ * We need to replace the parameterless
+
+ QtCore.Qt.Alignment()
+
+ * by the one-parameter call
+
+ QtCore.Qt.AlignmentFlag(0)
+
+ * That means: We need to bind the zero as default into a wrapper and
+ * return that to be called.
+ *
+ * Addendum:
+ * ---------
+ * We first need to look into the current opcode of the bytecode to find
+ * out if we have a call like above or just a type lookup.
+ */
+ AutoDecRef tpDict(PepType_GetDict(type_base));
+ auto *flagType = PyDict_GetItem(tpDict.object(), rename);
+ if (currentOpcode_Is_CallMethNoArgs())
+ return replaceNoArgWithZero(flagType);
+ Py_INCREF(flagType);
+ return flagType;
+ }
+ }
+ bool useFakeShortcuts = !(Enum::enumOption & Enum::ENOPT_NO_FAKESHORTCUT);
+ if (useFakeShortcuts) {
+ AutoDecRef tpDict(PepType_GetDict(type_base));
+ auto *dict = tpDict.object();
+ PyObject *key, *value;
+ Py_ssize_t pos = 0;
+ while (PyDict_Next(dict, &pos, &key, &value)) {
+ /*
+ * Part 2: Check for a duplication into outer scope.
+ * -------------------------------------------------
+ * We need to replace the shortcut
+
+ QtCore.Qt.AlignLeft
+
+ * by the correct call
+
+ QtCore.Qt.AlignmentFlag.AlignLeft
+
+ * That means: We need to search all Enums of the class.
+ */
+ if (Py_TYPE(value) == EnumMeta) {
+ auto *valtype = reinterpret_cast<PyTypeObject *>(value);
+ AutoDecRef valtypeDict(PepType_GetDict(valtype));
+ auto *member_map = PyDict_GetItem(valtypeDict.object(), _member_map_);
+ if (member_map && PyDict_Check(member_map)) {
+ result = PyDict_GetItem(member_map, name);
+ Py_XINCREF(result);
+ if (result)
+ return result;
+ }
+ }
+ }
+ }
+ }
+ return nullptr;
+}
+
+PyObject *mangled_type_getattro(PyTypeObject *type, PyObject *name)
+{
+ /*
+ * Note: This `type_getattro` version is only the default that comes
+ * from `PyType_Type.tp_getattro`. This does *not* interfere in any way
+ * with the complex `tp_getattro` of `QObject` and other instances.
+ * What we change here is the meta class of `QObject`.
+ */
+ static getattrofunc const type_getattro = PepExt_Type_GetGetAttroSlot(&PyType_Type);
+ static PyObject *const ignAttr1 = PyName::qtStaticMetaObject();
+ static PyObject *const ignAttr2 = PyMagicName::get();
+ static PyTypeObject *const EnumMeta = getPyEnumMeta();
+
+ if (SelectFeatureSet != nullptr)
+ SelectFeatureSet(type);
+ auto *ret = type_getattro(reinterpret_cast<PyObject *>(type), name);
+
+ // PYSIDE-1735: Be forgiving with strict enums and fetch the enum, silently.
+ // The PYI files now look correct, but the old duplication is
+ // emulated here. This should be removed in Qt 7, see `parser.py`.
+ //
+ // FIXME PYSIDE7 should remove this forgiveness:
+ //
+ // The duplication of enum values into the enclosing scope, allowing to write
+ // Qt.AlignLeft instead of Qt.Alignment.AlignLeft, is still implemented but
+ // no longer advertized in PYI files or line completion.
+
+ if (ret && Py_TYPE(ret) == EnumMeta && currentOpcode_Is_CallMethNoArgs()) {
+ bool useZeroDefault = !(Enum::enumOption & Enum::ENOPT_NO_ZERODEFAULT);
+ if (useZeroDefault) {
+ // We provide a zero argument for compatibility if it is a call with no args.
+ auto *hold = replaceNoArgWithZero(ret);
+ Py_DECREF(ret);
+ ret = hold;
+ }
+ }
+
+ if (!ret && name != ignAttr1 && name != ignAttr2) {
+ PyObject *error_type{}, *error_value{}, *error_traceback{};
+ PyErr_Fetch(&error_type, &error_value, &error_traceback);
+ ret = lookupUnqualifiedOrOldEnum(type, name);
+ if (ret) {
+ Py_DECREF(error_type);
+ Py_XDECREF(error_value);
+ Py_XDECREF(error_traceback);
+ } else {
+ PyErr_Restore(error_type, error_value, error_traceback);
+ }
+ }
+ return ret;
+}
+
+PyObject *Sbk_TypeGet___dict__(PyTypeObject *type, void * /* context */)
+{
+ /*
+ * This is the override for getting a dict.
+ */
+ AutoDecRef tpDict(PepType_GetDict(type));
+ auto *dict = tpDict.object();;
+ if (dict == nullptr)
+ Py_RETURN_NONE;
+ if (SelectFeatureSet != nullptr) {
+ SelectFeatureSet(type);
+ tpDict.reset(PepType_GetDict(type));
+ dict = tpDict.object();
+ }
+ return PyDictProxy_New(dict);
+}
+
+// These functions replace the standard PyObject_Generic(Get|Set)Attr functions.
+// They provide the default that "object" inherits.
+// Everything else is directly handled by cppgenerator that calls `Feature::Select`.
+PyObject *SbkObject_GenericGetAttr(PyObject *obj, PyObject *name)
+{
+ auto type = Py_TYPE(obj);
+ if (SelectFeatureSet != nullptr)
+ SelectFeatureSet(type);
+ return PyObject_GenericGetAttr(obj, name);
+}
+
+int SbkObject_GenericSetAttr(PyObject *obj, PyObject *name, PyObject *value)
+{
+ auto type = Py_TYPE(obj);
+ if (SelectFeatureSet != nullptr)
+ SelectFeatureSet(type);
+ return PyObject_GenericSetAttr(obj, name, value);
+}
+
+const char **SbkObjectType_GetPropertyStrings(PyTypeObject *type)
+{
+ return PepType_SOTP(type)->propertyStrings;
+}
+
+void SbkObjectType_SetPropertyStrings(PyTypeObject *type, const char **strings)
+{
+ PepType_SOTP(type)->propertyStrings = strings;
+}
+
+void SbkObjectType_SetEnumFlagInfo(PyTypeObject *type, const char **strings)
+{
+ PepType_SOTP(type)->enumFlagInfo = strings;
+}
+
+// PYSIDE-1626: Enforcing a context switch without further action.
+void SbkObjectType_UpdateFeature(PyTypeObject *type)
+{
+ if (SelectFeatureSet != nullptr)
+ SelectFeatureSet(type);
+}
+
+} // extern "C"