diff options
Diffstat (limited to 'sources/shiboken6/libshiboken/sbkfeature_base.cpp')
-rw-r--r-- | sources/shiboken6/libshiboken/sbkfeature_base.cpp | 408 |
1 files changed, 316 insertions, 92 deletions
diff --git a/sources/shiboken6/libshiboken/sbkfeature_base.cpp b/sources/shiboken6/libshiboken/sbkfeature_base.cpp index e191204e4..f31b8f4f7 100644 --- a/sources/shiboken6/libshiboken/sbkfeature_base.cpp +++ b/sources/shiboken6/libshiboken/sbkfeature_base.cpp @@ -4,12 +4,16 @@ #include "basewrapper.h" #include "basewrapper_p.h" #include "autodecref.h" -#include "sbkenum_p.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; @@ -18,64 +22,293 @@ extern "C" //////////////////////////////////////////////////////////////////////////// // -// getFeatureSelectId +// Minimal __feature__ support in Shiboken // -// This function is needed here already for signature handling. -// Maybe the same function from feature_select.cpp will be replaced. +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; +} // +//////////////////////////////////////////////////////////////////////////// -static PyObject *cached_globals{}; -static PyObject *last_select_id{}; +// 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; -PyObject *getFeatureSelectId() +static bool currentOpcode_Is_CallMethNoArgs() { - static PyObject *undef = PyLong_FromLong(-1); - static PyObject *feature_dict = GetFeatureDict(); - // these things are all borrowed - PyObject *globals = PyEval_GetGlobals(); - if (globals == nullptr - || globals == cached_globals) - return last_select_id; + // 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]; - PyObject *modname = PyDict_GetItem(globals, PyMagicName::name()); - if (modname == nullptr) - return last_select_id; + 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; - PyObject *select_id = PyDict_GetItem(feature_dict, modname); - if (select_id == nullptr - || !PyLong_Check(select_id) // int/long cheating - || select_id == undef) - return last_select_id; + opcode2 = co_code[f_lasti + 2]; + oparg2 = co_code[f_lasti + 3]; - cached_globals = globals; - last_select_id = select_id; - assert(PyLong_AsSsize_t(select_id) >= 0); - return select_id; + return opcode2 == CALL && oparg2 == 0; } -int currentSelectId(PyTypeObject *type) +void initEnumFlagsDict(PyTypeObject *type) { - int sel = SbkObjectType_GetReserved(type); - // This could theoretically be -1 if used too early. - assert(sel >= 0); - return sel; + // 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; } -void initFeatureShibokenPart() +static PyObject *replaceNoArgWithZero(PyObject *callable) { - static PyObject *no_sel = PyLong_FromLong(0); - last_select_id = no_sel; - // Reset the cache. This is called at any "from __feature__ import". - cached_globals = nullptr; + static auto *partial = Pep_GetPartialFunction(); + static auto *zero = PyLong_FromLong(0); + return PyObject_CallFunctionObjArgs(partial, callable, zero, nullptr); } -static SelectableFeatureHook SelectFeatureSet = nullptr; - -SelectableFeatureHook initSelectableFeature(SelectableFeatureHook func) +static PyObject *lookupUnqualifiedOrOldEnum(PyTypeObject *type, PyObject *name) { - auto ret = SelectFeatureSet; - SelectFeatureSet = func; - return ret; + // 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) @@ -86,67 +319,64 @@ PyObject *mangled_type_getattro(PyTypeObject *type, PyObject *name) * with the complex `tp_getattro` of `QObject` and other instances. * What we change here is the meta class of `QObject`. */ - static getattrofunc type_getattro = PyType_Type.tp_getattro; - static PyObject *ignAttr1 = PyName::qtStaticMetaObject(); - static PyObject *ignAttr2 = PyMagicName::get(); + 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) - type->tp_dict = SelectFeatureSet(type); + 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 forgivingness: + // 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; + PyObject *error_type{}, *error_value{}, *error_traceback{}; PyErr_Fetch(&error_type, &error_value, &error_traceback); - - // This is similar to `find_name_in_mro`, but instead of looking directly into - // tp_dict, we search for the attribute in local classes of that dict. - PyObject *mro = type->tp_mro; - assert(PyTuple_Check(mro)); - size_t idx, n = PyTuple_GET_SIZE(mro); - for (idx = 0; idx < n; ++idx) { - // FIXME This loop should further be optimized by installing an extra - // <classname>_EnumInfo structure. This comes with the next compatibility patch. - auto *base = PyTuple_GET_ITEM(mro, idx); - auto *type_base = reinterpret_cast<PyTypeObject *>(base); - auto *dict = type_base->tp_dict; - PyObject *key, *value; - Py_ssize_t pos = 0; - while (PyDict_Next(dict, &pos, &key, &value)) { - static auto *EnumMeta = getPyEnumMeta(); - if (Py_TYPE(value) == EnumMeta) { - auto *valtype = reinterpret_cast<PyTypeObject *>(value); - auto *result = PyDict_GetItem(valtype->tp_dict, name); - if (result) { - Py_INCREF(result); - return result; - } - } - } + 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); } - PyErr_Restore(error_type, error_value, error_traceback); } return ret; } -PyObject *Sbk_TypeGet___dict__(PyTypeObject *type, void *context) +PyObject *Sbk_TypeGet___dict__(PyTypeObject *type, void * /* context */) { /* * This is the override for getting a dict. */ - auto dict = type->tp_dict; + AutoDecRef tpDict(PepType_GetDict(type)); + auto *dict = tpDict.object();; if (dict == nullptr) Py_RETURN_NONE; - if (SelectFeatureSet != nullptr) - dict = SelectFeatureSet(type); + if (SelectFeatureSet != nullptr) { + SelectFeatureSet(type); + tpDict.reset(PepType_GetDict(type)); + dict = tpDict.object(); + } return PyDictProxy_New(dict); } @@ -157,7 +387,7 @@ PyObject *SbkObject_GenericGetAttr(PyObject *obj, PyObject *name) { auto type = Py_TYPE(obj); if (SelectFeatureSet != nullptr) - type->tp_dict = SelectFeatureSet(type); + SelectFeatureSet(type); return PyObject_GenericGetAttr(obj, name); } @@ -165,21 +395,10 @@ int SbkObject_GenericSetAttr(PyObject *obj, PyObject *name, PyObject *value) { auto type = Py_TYPE(obj); if (SelectFeatureSet != nullptr) - type->tp_dict = SelectFeatureSet(type); + SelectFeatureSet(type); return PyObject_GenericSetAttr(obj, name, value); } -// Caching the select Id. -int SbkObjectType_GetReserved(PyTypeObject *type) -{ - return PepType_SOTP(type)->pyside_reserved_bits; -} - -void SbkObjectType_SetReserved(PyTypeObject *type, int value) -{ - PepType_SOTP(type)->pyside_reserved_bits = value; -} - const char **SbkObjectType_GetPropertyStrings(PyTypeObject *type) { return PepType_SOTP(type)->propertyStrings; @@ -190,11 +409,16 @@ 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) - type->tp_dict = SelectFeatureSet(type); + SelectFeatureSet(type); } } // extern "C" |