diff options
Diffstat (limited to 'sources/shiboken6/libshiboken')
75 files changed, 16430 insertions, 0 deletions
diff --git a/sources/shiboken6/libshiboken/CMakeLists.txt b/sources/shiboken6/libshiboken/CMakeLists.txt new file mode 100644 index 000000000..b5bbb498a --- /dev/null +++ b/sources/shiboken6/libshiboken/CMakeLists.txt @@ -0,0 +1,199 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +project(libshiboken) + +option(ENABLE_VERSION_SUFFIX "Used to use current version in suffix to generated files. This is used to allow multiples versions installed simultaneous." FALSE) +if(ENABLE_VERSION_SUFFIX) + set(shiboken6_SUFFIX "-${shiboken_MAJOR_VERSION}.${shiboken_MINOR_VERSION}") +else() + set(shiboken6_SUFFIX "") +endif() + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/sbkversion.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/sbkversion.h" @ONLY) +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/embed/signature_bootstrap.py" + "${CMAKE_CURRENT_BINARY_DIR}/embed/signature_bootstrap.py" @ONLY) + +# Variable from enclosing scope. +list(TRANSFORM shiboken_python_files + PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/../shibokenmodule/files.dir/shibokensupport/" + OUTPUT_VARIABLE embedded_shiboken_files) + +if (QUIET_BUILD) + set(embedding_option "--quiet") +else() + set(embedding_option "") +endif() + +if(SHIBOKEN_IS_CROSS_BUILD) + set(host_python_path "${QFP_PYTHON_HOST_PATH}") + set(use_pyc_in_embedding FALSE) +else() + set(host_python_path "${Python_EXECUTABLE}") + if(PYTHON_LIMITED_API) + set(use_pyc_in_embedding FALSE) + else() + set(use_pyc_in_embedding TRUE) + endif() +endif() + +add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/embed/signature_bootstrap_inc.h" + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/embed/signature_inc.h" + COMMAND ${host_python_path} -E + "${CMAKE_CURRENT_SOURCE_DIR}/embed/embedding_generator.py" + --cmake-dir "${CMAKE_CURRENT_BINARY_DIR}/embed" + --use-pyc ${use_pyc_in_embedding} + ${embedding_option} + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/embed/embedding_generator.py" + "${CMAKE_CURRENT_SOURCE_DIR}/embed/signature_bootstrap.py" + ${embedded_shiboken_files} + ) + +set(libshiboken_MAJOR_VERSION ${shiboken_MAJOR_VERSION}) +set(libshiboken_MINOR_VERSION ${shiboken_MINOR_VERSION}) +set(libshiboken_MICRO_VERSION ${shiboken_MICRO_VERSION}) +set(libshiboken_VERSION "${libshiboken_MAJOR_VERSION}.${libshiboken_MINOR_VERSION}.${libshiboken_MICRO_VERSION}") +set(libshiboken_SOVERSION "${shiboken6_library_so_version}") + +set(libshiboken_SRC +autodecref.h +basewrapper.cpp basewrapper.h basewrapper_p.h +bindingmanager.cpp bindingmanager.h +bufferprocs_py37.cpp bufferprocs_py37.h +debugfreehook.cpp debugfreehook.h +gilstate.cpp gilstate.h +helper.cpp helper.h +pep384impl.cpp pep384impl.h +pyobjectholder.h +sbkarrayconverter.cpp sbkarrayconverter.h sbkarrayconverter_p.h +sbkcontainer.cpp sbkcontainer.h +sbkconverter.cpp sbkconverter.h sbkconverter_p.h +sbkcppstring.cpp sbkcppstring.h sbkcpptonumpy.h +sbkenum.cpp sbkenum.h +sbkerrors.cpp sbkerrors.h +sbkfeature_base.cpp sbkfeature_base.h +sbkmodule.cpp sbkmodule.h +sbknumpy.cpp sbknumpycheck.h +sbknumpyview.h +sbkpython.h +sbksmartpointer.cpp sbksmartpointer.h +sbkstaticstrings.cpp sbkstaticstrings.h sbkstaticstrings_p.h +sbkstring.cpp sbkstring.h +sbktypefactory.cpp sbktypefactory.h +sbkwindows.h +shiboken.h +shibokenbuffer.cpp shibokenbuffer.h +shibokenmacros.h +threadstatesaver.cpp threadstatesaver.h +voidptr.cpp voidptr.h + +embed/signature_bootstrap_inc.h +embed/signature_inc.h + +signature/signature.cpp signature.h signature_p.h +signature/signature_globals.cpp +signature/signature_extend.cpp +signature/signature_helper.cpp +) + +# This is needed to let the header obey a variable in "pep384impl.h". +# Note: This must be set on the cpp file! +set_property(SOURCE "pep384impl.cpp" PROPERTY SKIP_UNITY_BUILD_INCLUSION ON) + +add_library(libshiboken SHARED ${libshiboken_SRC}) +add_library(Shiboken6::libshiboken ALIAS libshiboken) + +target_include_directories(libshiboken PUBLIC + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> + $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}> + $<INSTALL_INTERFACE:include/shiboken6> +) + +if (NOT "${NUMPY_INCLUDE_DIR}" STREQUAL "") + message(STATUS "NUMPY_INCLUDE_DIR: " ${NUMPY_INCLUDE_DIR}) + target_include_directories(libshiboken PRIVATE ${NUMPY_INCLUDE_DIR}) + target_compile_definitions(libshiboken PRIVATE -DHAVE_NUMPY + PRIVATE -DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION) +else() + message(STATUS "NUMPY not found") +endif() + +if(SHIBOKEN_IS_CROSS_BUILD) + target_compile_definitions(libshiboken PRIVATE -DSHIBOKEN_NO_EMBEDDING_PYC=1) +endif() + +shiboken_compute_python_includes() +# On Windows we need to link against the python.lib import library. +# On macOS and Linux we don't link against the python shared / static library, +# the dynamic linker will pick up the python symbols at runtime automatically. +shiboken_compute_python_libraries() + +if(PYTHON_LIMITED_API) + target_compile_definitions(libshiboken PUBLIC "-DPy_LIMITED_API=0x03060000") +endif() + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + if(PYTHON_WITH_DEBUG) + target_compile_definitions(libshiboken PUBLIC "-DPy_DEBUG") + endif() + if (PYTHON_WITH_COUNT_ALLOCS) + target_compile_definitions(libshiboken PUBLIC "-DCOUNT_ALLOCS") + endif() +elseif(CMAKE_BUILD_TYPE STREQUAL "Release") + target_compile_definitions(libshiboken PUBLIC "-DNDEBUG") +endif() + +set_target_properties(libshiboken PROPERTIES OUTPUT_NAME "shiboken6${shiboken6_SUFFIX}${PYTHON_SHARED_LIBRARY_SUFFIX}" + VERSION ${libshiboken_VERSION} + SOVERSION ${libshiboken_SOVERSION} + DEFINE_SYMBOL BUILD_LIBSHIBOKEN) + +qfp_strip_library("libshiboken") + +install(FILES + autodecref.h + basewrapper.h + basewrapper_p.h + bindingmanager.h + gilstate.h + helper.h + pyobjectholder.h + sbkarrayconverter.h + sbkcontainer.h + sbkconverter.h + sbkcpptonumpy.h + sbkenum.h + sbkerrors.h + sbkfeature_base.h + sbkmodule.h + sbknumpycheck.h + sbknumpyview.h + sbkstring.h + sbkcppstring.h + sbksmartpointer.h + sbkstaticstrings.h + sbktypefactory.h + shiboken.h + shibokenmacros.h + threadstatesaver.h + shibokenbuffer.h + sbkpython.h + sbkwindows.h + pep384impl.h + pep384ext.h + voidptr.h + bufferprocs_py37.h + "${CMAKE_CURRENT_BINARY_DIR}/sbkversion.h" + + signature.h + signature_p.h + + DESTINATION include/shiboken6${shiboken6_SUFFIX}) +install(TARGETS libshiboken EXPORT Shiboken6Targets + LIBRARY DESTINATION "${LIB_INSTALL_DIR}" + ARCHIVE DESTINATION "${LIB_INSTALL_DIR}" + RUNTIME DESTINATION bin) +install(EXPORT Shiboken6Targets NAMESPACE Shiboken6:: + DESTINATION ${LIB_INSTALL_DIR}/cmake/Shiboken6) diff --git a/sources/shiboken6/libshiboken/autodecref.h b/sources/shiboken6/libshiboken/autodecref.h new file mode 100644 index 000000000..62a8584e1 --- /dev/null +++ b/sources/shiboken6/libshiboken/autodecref.h @@ -0,0 +1,87 @@ +// 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 + +#ifndef AUTODECREF_H +#define AUTODECREF_H + +#include "sbkpython.h" + +#include <utility> + +struct SbkObject; +namespace Shiboken +{ + +/** + * AutoDecRef holds a PyObject pointer and decrement its reference counter when destroyed. + */ +struct AutoDecRef +{ +public: + AutoDecRef(const AutoDecRef &) = delete; + AutoDecRef(AutoDecRef &&o) noexcept : m_pyObj{std::exchange(o.m_pyObj, nullptr)} {} + AutoDecRef &operator=(const AutoDecRef &) = delete; + AutoDecRef &operator=(AutoDecRef &&o) noexcept + { + m_pyObj = std::exchange(o.m_pyObj, nullptr); + return *this; + } + + /// AutoDecRef constructor. + /// \param pyobj A borrowed reference to a Python object + explicit AutoDecRef(PyObject *pyObj) noexcept : m_pyObj(pyObj) {} + /// AutoDecRef constructor. + /// \param pyobj A borrowed reference to a wrapped Python object + explicit AutoDecRef(SbkObject *pyObj) noexcept : m_pyObj(reinterpret_cast<PyObject *>(pyObj)) {} + /// AutoDecref default constructor. + /// To be used later with reset(): + AutoDecRef() noexcept = default; + + /// Decref the borrowed python reference + ~AutoDecRef() + { + Py_XDECREF(m_pyObj); + } + + [[nodiscard]] bool isNull() const { return m_pyObj == nullptr; } + /// Returns the pointer of the Python object being held. + [[nodiscard]] PyObject *object() const { return m_pyObj; } + [[nodiscard]] operator PyObject *() const { return m_pyObj; } +#ifndef Py_LIMITED_API + [[deprecated]] inline operator PyTupleObject *() + { return reinterpret_cast<PyTupleObject *>(m_pyObj); } +#endif + inline operator bool() const { return m_pyObj != nullptr; } + inline PyObject *operator->() { return m_pyObj; } + + template<typename T> + [[deprecated]] T cast() + { + return reinterpret_cast<T>(m_pyObj); + } + + /** + * Decref the current borrowed python reference and borrow \p other. + */ + void reset(PyObject *other) + { + // Safely decref m_pyObj. See Py_XSETREF in object.h . + PyObject *_py_tmp = m_pyObj; + m_pyObj = other; + Py_XDECREF(_py_tmp); + } + + PyObject *release() + { + PyObject *result = m_pyObj; + m_pyObj = nullptr; + return result; + } + +private: + PyObject *m_pyObj = nullptr; +}; + +} // namespace Shiboken + +#endif // AUTODECREF_H diff --git a/sources/shiboken6/libshiboken/basewrapper.cpp b/sources/shiboken6/libshiboken/basewrapper.cpp new file mode 100644 index 000000000..0ce80d0c6 --- /dev/null +++ b/sources/shiboken6/libshiboken/basewrapper.cpp @@ -0,0 +1,1924 @@ +// Copyright (C) 2019 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 "bindingmanager.h" +#include "helper.h" +#include "pep384ext.h" +#include "sbkconverter.h" +#include "sbkenum.h" +#include "sbkerrors.h" +#include "sbkfeature_base.h" +#include "sbkmodule.h" +#include "sbkstring.h" +#include "sbkstaticstrings.h" +#include "sbkstaticstrings_p.h" +#include "autodecref.h" +#include "gilstate.h" +#include <string> +#include <cstring> +#include <cstddef> +#include <set> +#include <sstream> +#include <algorithm> +#include "threadstatesaver.h" +#include "signature.h" +#include "signature_p.h" +#include "voidptr.h" + +#include <string> +#include <iostream> +#include <sstream> + +#if defined(__APPLE__) +#include <dlfcn.h> +#endif + +namespace { + void _destroyParentInfo(SbkObject *obj, bool keepReference); +} + +namespace Shiboken +{ +// Walk through the first level of non-user-type Sbk base classes relevant for +// C++ object allocation. Return true from the predicate to terminate. +template <class Predicate> +bool walkThroughBases(PyTypeObject *currentType, Predicate predicate) +{ + PyObject *bases = currentType->tp_bases; + const Py_ssize_t numBases = PyTuple_Size(bases); + bool result = false; + for (Py_ssize_t i = 0; !result && i < numBases; ++i) { + auto type = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(bases, i)); + if (PyType_IsSubtype(type, SbkObject_TypeF()) != 0) { + result = PepType_SOTP(type)->is_user_type + ? walkThroughBases(type, predicate) : predicate(type); + } + } + return result; +} + +int getTypeIndexOnHierarchy(PyTypeObject *baseType, PyTypeObject *desiredType) +{ + int index = -1; + walkThroughBases(baseType, [&index, desiredType](PyTypeObject *node) { + ++index; + return PyType_IsSubtype(node, desiredType) != 0; + }); + return index; +} + +int getNumberOfCppBaseClasses(PyTypeObject *baseType) +{ + int count = 0; + walkThroughBases(baseType, [&count](PyTypeObject *) { + ++count; + return false; + }); + return count; +} + +std::vector<PyTypeObject *> getCppBaseClasses(PyTypeObject *baseType) +{ + std::vector<PyTypeObject *> cppBaseClasses; + walkThroughBases(baseType, [&cppBaseClasses](PyTypeObject *node) { + cppBaseClasses.push_back(node); + return false; + }); + return cppBaseClasses; +} + +using DestructorEntries = std::vector<DestructorEntry>; + +DestructorEntries getDestructorEntries(SbkObject *o) +{ + DestructorEntries result; + void **cptrs = o->d->cptr; + walkThroughBases(Py_TYPE(o), [&result, cptrs](PyTypeObject *node) { + auto *sotp = PepType_SOTP(node); + auto index = result.size(); + result.push_back(DestructorEntry{sotp->cpp_dtor, + cptrs[index]}); + return false; + }); + return result; +} + +static void callDestructor(const DestructorEntries &dts) +{ + for (const auto &e : dts) { + Shiboken::ThreadStateSaver threadSaver; + threadSaver.save(); + e.destructor(e.cppInstance); + } +} + +} // namespace Shiboken + +extern "C" +{ + +// PYSIDE-939: A general replacement for object_dealloc. +void Sbk_object_dealloc(PyObject *self) +{ + if (PepRuntime_38_flag) { + // PYSIDE-939: Handling references correctly. + // This was not needed before Python 3.8 (Python issue 35810) + Py_DECREF(Py_TYPE(self)); + } + PepExt_TypeCallFree(self); +} + +static void SbkObjectType_tp_dealloc(PyTypeObject *pyType); +static PyTypeObject *SbkObjectType_tp_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds); + +static DestroyQAppHook DestroyQApplication = nullptr; + +// PYSIDE-1470: Provide a hook to kill an Application from Shiboken. +void setDestroyQApplication(DestroyQAppHook func) +{ + DestroyQApplication = func; +} + +// PYSIDE-535: Use the C API in PyPy instead of `op->ob_dict`, directly +LIBSHIBOKEN_API PyObject *SbkObject_GetDict_NoRef(PyObject *op) +{ + assert(Shiboken::Object::checkType(op)); +#ifdef PYPY_VERSION + Shiboken::GilState state; + auto *ret = PyObject_GenericGetDict(op, nullptr); + Py_DECREF(ret); + return ret; +#else + auto *sbkObj = reinterpret_cast<SbkObject *>(op); + if (!sbkObj->ob_dict) { + Shiboken::GilState state; + sbkObj->ob_dict = PyDict_New(); + } + return sbkObj->ob_dict; +#endif +} + +static int +check_set_special_type_attr(PyTypeObject *type, PyObject *value, const char *name) +{ + if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) { + PyErr_Format(PyExc_TypeError, + "can't set %s.%s", type->tp_name, name); + return 0; + } + if (!value) { + PyErr_Format(PyExc_TypeError, + "can't delete %s.%s", type->tp_name, name); + return 0; + } + return 1; +} + +// PYSIDE-1177: Add a setter to allow setting type doc. +static int +type_set_doc(PyTypeObject *type, PyObject *value, void * /* context */) +{ + if (!check_set_special_type_attr(type, value, "__doc__")) + return -1; + PyType_Modified(type); + Shiboken::AutoDecRef tpDict(PepType_GetDict(type)); + return PyDict_SetItem(tpDict.object(), Shiboken::PyMagicName::doc(), value); +} + +// PYSIDE-908: The function PyType_Modified does not work in PySide, so we need to +// explicitly pass __doc__. +static PyGetSetDef SbkObjectType_tp_getset[] = { + {const_cast<char *>("__doc__"), reinterpret_cast<getter>(Sbk_TypeGet___doc__), + reinterpret_cast<setter>(type_set_doc), nullptr, nullptr}, + {const_cast<char *>("__dict__"), reinterpret_cast<getter>(Sbk_TypeGet___dict__), + nullptr, nullptr, nullptr}, + {nullptr, nullptr, nullptr, nullptr, nullptr} // Sentinel +}; + +static PyTypeObject *createObjectTypeType() +{ + PyType_Slot SbkObjectType_Type_slots[] = { + {Py_tp_dealloc, reinterpret_cast<void *>(SbkObjectType_tp_dealloc)}, + {Py_tp_getattro, reinterpret_cast<void *>(mangled_type_getattro)}, + {Py_tp_base, static_cast<void *>(&PyType_Type)}, + {Py_tp_alloc, reinterpret_cast<void *>(PyType_GenericAlloc)}, + {Py_tp_new, reinterpret_cast<void *>(SbkObjectType_tp_new)}, + {Py_tp_free, reinterpret_cast<void *>(PyObject_GC_Del)}, + {Py_tp_getset, reinterpret_cast<void *>(SbkObjectType_tp_getset)}, + {0, nullptr} + }; + + // PYSIDE-535: The tp_itemsize field is inherited and does not need to be set. + // In PyPy, it _must_ not be set, because it would have the meanin + // that a `__len__` field must be defined. Not doing so creates + // a hard-to-find crash. + // + // PYSIDE-2230: In Python < 3.12, the decision which base class should create + // the instance is arbitrarily drawn by the size of the type. + // Ignoring this creates a bug in the new version of bug_825 that + // selects the wrong metatype. + // + PyType_Spec SbkObjectType_Type_spec = { + "1:Shiboken.ObjectType", + static_cast<int>(PyType_Type.tp_basicsize) + 1, // see above + 0, // sizeof(PyMemberDef), not for PyPy without a __len__ defined + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_TYPE_SUBCLASS, + SbkObjectType_Type_slots, + }; + + PyType_Spec SbkObjectType_Type_spec_312 = { + "1:Shiboken.ObjectType", + -long(sizeof(SbkObjectTypePrivate)), + 0, // sizeof(PyMemberDef), not for PyPy without a __len__ defined + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_TYPE_SUBCLASS, + SbkObjectType_Type_slots, + }; + + return SbkType_FromSpec(_PepRuntimeVersion() >= 0x030C00 ? + &SbkObjectType_Type_spec_312 : + &SbkObjectType_Type_spec); +} + +PyTypeObject *SbkObjectType_TypeF(void) +{ + static auto *type = createObjectTypeType(); + return type; +} + +static PyObject *SbkObjectGetDict(PyObject *pObj, void *) +{ + auto ret = SbkObject_GetDict_NoRef(pObj); + Py_XINCREF(ret); + return ret; +} + +static PyGetSetDef SbkObject_tp_getset[] = { + {const_cast<char *>("__dict__"), SbkObjectGetDict, nullptr, nullptr, nullptr}, + {nullptr, nullptr, nullptr, nullptr, nullptr} // Sentinel +}; + +static int SbkObject_tp_traverse(PyObject *self, visitproc visit, void *arg) +{ + auto *sbkSelf = reinterpret_cast<SbkObject *>(self); + + //Visit children + Shiboken::ParentInfo *pInfo = sbkSelf->d->parentInfo; + if (pInfo) { + for (SbkObject *c : pInfo->children) + Py_VISIT(c); + } + + //Visit refs + Shiboken::RefCountMap *rInfo = sbkSelf->d->referredObjects; + if (rInfo) { + for (auto it = rInfo->begin(), end = rInfo->end(); it != end; ++it) + Py_VISIT(it->second); + } + + if (sbkSelf->ob_dict) + Py_VISIT(sbkSelf->ob_dict); + + // This was not needed before Python 3.9 (Python issue 35810 and 40217) + Py_VISIT(Py_TYPE(self)); + return 0; +} + +static int SbkObject_tp_clear(PyObject *self) +{ + auto *sbkSelf = reinterpret_cast<SbkObject *>(self); + + Shiboken::Object::removeParent(sbkSelf); + + if (sbkSelf->d->parentInfo) + _destroyParentInfo(sbkSelf, true); + + Shiboken::Object::clearReferences(sbkSelf); + + if (sbkSelf->ob_dict) + Py_CLEAR(sbkSelf->ob_dict); + return 0; +} + +static PyTypeObject *createObjectType() +{ + PyType_Slot SbkObject_Type_slots[] = { + {Py_tp_getattro, reinterpret_cast<void *>(SbkObject_GenericGetAttr)}, + {Py_tp_setattro, reinterpret_cast<void *>(SbkObject_GenericSetAttr)}, + {Py_tp_dealloc, reinterpret_cast<void *>(SbkDeallocWrapperWithPrivateDtor)}, + {Py_tp_traverse, reinterpret_cast<void *>(SbkObject_tp_traverse)}, + {Py_tp_clear, reinterpret_cast<void *>(SbkObject_tp_clear)}, + // unsupported: {Py_tp_weaklistoffset, (void *)offsetof(SbkObject, weakreflist)}, + {Py_tp_getset, reinterpret_cast<void *>(SbkObject_tp_getset)}, + // unsupported: {Py_tp_dictoffset, (void *)offsetof(SbkObject, ob_dict)}, + {0, nullptr} + }; + + PyType_Spec SbkObject_Type_spec = { + "1:Shiboken.Object", + sizeof(SbkObject), + 0, + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, + SbkObject_Type_slots, + }; + + // PYSIDE-2230: When creating this type, we cannot easily handle the metaclass. + // In versions < Python 3.12, the metaclass can only be set + // indirectly by a base which has that metaclass. + // But before 3.12 is the minimum version, we cannot use the new + // function, although we would need this for 3.12 :-D + // We do a special patching here that is triggered through Py_None. + auto *type = SbkType_FromSpec_BMDWB(&SbkObject_Type_spec, + Py_None, // bases, spectial flag! + SbkObjectType_TypeF(), + offsetof(SbkObject, ob_dict), + offsetof(SbkObject, weakreflist), + nullptr); // bufferprocs + return type; +} + +PyTypeObject *SbkObject_TypeF(void) +{ + static auto *type = createObjectType(); // bufferprocs + return type; +} + +static const char *SbkObject_SignatureStrings[] = { + "Shiboken.Object(self)", + nullptr}; // Sentinel + +static int mainThreadDeletionHandler(void *) +{ + if (Py_IsInitialized()) + Shiboken::BindingManager::instance().runDeletionInMainThread(); + return 0; +} + +static void SbkDeallocWrapperCommon(PyObject *pyObj, bool canDelete) +{ + auto *sbkObj = reinterpret_cast<SbkObject *>(pyObj); + PyTypeObject *pyType = Py_TYPE(pyObj); + + // Need to decref the type if this is the dealloc func; if type + // is subclassed, that dealloc func will decref (see subtype_dealloc + // in typeobject.c in the python sources) + auto dealloc = PyType_GetSlot(pyType, Py_tp_dealloc); + bool needTypeDecref = dealloc == SbkDeallocWrapper + || dealloc == SbkDeallocWrapperWithPrivateDtor; + if (PepRuntime_38_flag) { + // PYSIDE-939: Additional rule: Also when a subtype is heap allocated, + // then the subtype_dealloc deref will be suppressed, and we need again + // to supply a decref. + needTypeDecref |= (pyType->tp_base->tp_flags & Py_TPFLAGS_HEAPTYPE) != 0; + } + +#if defined(__APPLE__) + // Just checking once that our assumptions are right. + if (false) { + void *p = PyType_GetSlot(pyType, Py_tp_dealloc); + Dl_info dl_info; + dladdr(p, &dl_info); + fprintf(stderr, "tp_dealloc is %s\n", dl_info.dli_sname); + } + // Gives one of our functions + // "Sbk_object_dealloc" + // "SbkDeallocWrapperWithPrivateDtor" + // "SbkDeallocQAppWrapper" + // "SbkDeallocWrapper" + // but for typedealloc_test.py we get + // "subtype_dealloc" +#endif + + // Ensure that the GC is no longer tracking this object to avoid a + // possible reentrancy problem. Since there are multiple steps involved + // in deallocating a SbkObject it is possible for the garbage collector to + // be invoked and it trying to delete this object while it is still in + // progress from the first time around, resulting in a double delete and a + // crash. + PyObject_GC_UnTrack(pyObj); + + // Check that Python is still initialized as sometimes this is called by a static destructor + // after Python interpeter is shutdown. + if (sbkObj->weakreflist && Py_IsInitialized()) + PyObject_ClearWeakRefs(pyObj); + + // If I have ownership and is valid delete C++ pointer + auto *sotp = PepType_SOTP(pyType); + canDelete &= sbkObj->d->hasOwnership && sbkObj->d->validCppObject; + if (canDelete) { + if (sotp->delete_in_main_thread && Shiboken::currentThreadId() != Shiboken::mainThreadId()) { + auto &bindingManager = Shiboken::BindingManager::instance(); + if (sotp->is_multicpp) { + const auto entries = Shiboken::getDestructorEntries(sbkObj); + for (const auto &e : entries) + bindingManager.addToDeletionInMainThread(e); + } else { + Shiboken::DestructorEntry e{sotp->cpp_dtor, sbkObj->d->cptr[0]}; + bindingManager.addToDeletionInMainThread(e); + } + Py_AddPendingCall(mainThreadDeletionHandler, nullptr); + canDelete = false; + } + } + + PyObject *error_type, *error_value, *error_traceback; + + /* Save the current exception, if any. */ + PyErr_Fetch(&error_type, &error_value, &error_traceback); + + if (canDelete) { + if (sotp->is_multicpp) { + const auto entries = Shiboken::getDestructorEntries(sbkObj); + Shiboken::Object::deallocData(sbkObj, true); + callDestructor(entries); + } else { + void *cptr = sbkObj->d->cptr[0]; + Shiboken::Object::deallocData(sbkObj, true); + + Shiboken::ThreadStateSaver threadSaver; + if (Py_IsInitialized()) + threadSaver.save(); + sotp->cpp_dtor(cptr); + } + } else { + Shiboken::Object::deallocData(sbkObj, true); + } + + /* Restore the saved exception. */ + PyErr_Restore(error_type, error_value, error_traceback); + + if (needTypeDecref) + Py_DECREF(pyType); + if (PepRuntime_38_flag) { + // PYSIDE-939: Handling references correctly. + // This was not needed before Python 3.8 (Python issue 35810) + Py_DECREF(pyType); + } +} + +static inline PyObject *_Sbk_NewVarObject(PyTypeObject *type) +{ + // PYSIDE-1970: Support __slots__, implemented by PyVarObject + auto const baseSize = sizeof(SbkObject); + auto varCount = Py_SIZE(type); + auto *self = PyObject_GC_NewVar(PyObject, type, varCount); + if (varCount) + std::memset(reinterpret_cast<char *>(self) + baseSize, 0, varCount * sizeof(void *)); + return self; +} + +void SbkDeallocWrapper(PyObject *pyObj) +{ + SbkDeallocWrapperCommon(pyObj, true); +} + +void SbkDeallocQAppWrapper(PyObject *pyObj) +{ + SbkDeallocWrapper(pyObj); + // PYSIDE-571: make sure to create a singleton deleted qApp. + Py_DECREF(MakeQAppWrapper(nullptr)); +} + +void SbkDeallocWrapperWithPrivateDtor(PyObject *self) +{ + SbkDeallocWrapperCommon(self, false); +} + +void SbkObjectType_tp_dealloc(PyTypeObject *sbkType) +{ + SbkObjectTypePrivate *sotp = PepType_SOTP(sbkType); + auto pyObj = reinterpret_cast<PyObject *>(sbkType); + + PyObject_GC_UnTrack(pyObj); +#if !defined(Py_LIMITED_API) && !defined(PYPY_VERSION) +# if PY_VERSION_HEX >= 0x030A0000 + Py_TRASHCAN_BEGIN(pyObj, 1); +# else + Py_TRASHCAN_SAFE_BEGIN(pyObj); +# endif +#endif + if (sotp) { + if (sotp->user_data && sotp->d_func) { + sotp->d_func(sotp->user_data); + sotp->user_data = nullptr; + } + free(sotp->original_name); + sotp->original_name = nullptr; + if (!Shiboken::ObjectType::isUserType(sbkType)) + Shiboken::Conversions::deleteConverter(sotp->converter); + PepType_SOTP_delete(sbkType); + } +#if !defined(Py_LIMITED_API) && !defined(PYPY_VERSION) +# if PY_VERSION_HEX >= 0x030A0000 + Py_TRASHCAN_END; +# else + Py_TRASHCAN_SAFE_END(pyObj); +# endif +#endif + if (PepRuntime_38_flag) { + // PYSIDE-939: Handling references correctly. + // This was not needed before Python 3.8 (Python issue 35810) + Py_DECREF(Py_TYPE(pyObj)); + } +} + +//////////////////////////////////////////////////////////////////////////// +// +// Support for the qApp macro. +// +// qApp is a macro in Qt5. In Python, we simulate that a little by a +// variable that monitors Q*Application.instance(). +// This variable is also able to destroy the app by qApp.shutdown(). +// + +PyObject *MakeQAppWrapper(PyTypeObject *type) +{ + static PyObject *qApp_last = nullptr; + + // protecting from multiple application instances + if (!(type == nullptr || qApp_last == Py_None)) { + const char *res_name = qApp_last != nullptr + ? PepType_GetNameStr(Py_TYPE(qApp_last)) : "<Unknown>"; + const char *type_name = PepType_GetNameStr(type); + PyErr_Format(PyExc_RuntimeError, "Please destroy the %s singleton before" + " creating a new %s instance.", res_name, type_name); + return nullptr; + } + + // monitoring the last application state + PyObject *qApp_curr = type != nullptr ? _Sbk_NewVarObject(type) : Py_None; + static PyObject *builtins = PyEval_GetBuiltins(); + if (PyDict_SetItem(builtins, Shiboken::PyName::qApp(), qApp_curr) < 0) + return nullptr; + qApp_last = qApp_curr; + // Note: This Py_INCREF would normally be wrong because the qApp + // object already has a reference from PyObject_GC_New. But this is + // exactly the needed reference that keeps qApp alive from alone! + Py_INCREF(qApp_curr); + // PYSIDE-1470: As a side effect, the interactive "_" variable tends to + // create reference cycles. This is disturbing when trying + // to remove qApp with del. + // PYSIDE-1758: Since we moved to an explicit qApp.shutdown() call, we + // no longer initialize "_" with Py_None. + return qApp_curr; +} + +static PyTypeObject *SbkObjectType_tp_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) +{ + // Check if all bases are new style before calling type.tp_new + // Was causing gc assert errors in test_bug704.py when + // this check happened after creating the type object. + // Argument parsing take from type.tp_new code. + + // PYSIDE-595: Also check if all bases allow inheritance. + // Before we changed to heap types, it was sufficient to remove the + // Py_TPFLAGS_BASETYPE flag. That does not work, because PySide does + // not respect this flag itself! + PyObject *name; + PyObject *pyBases; + PyObject *dict; + static const char *kwlist[] = { "name", "bases", "dict", nullptr}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO!O!:sbktype", const_cast<char **>(kwlist), + &name, + &PyTuple_Type, &pyBases, + &PyDict_Type, &dict)) + return nullptr; + + for (int i=0, i_max=PyTuple_GET_SIZE(pyBases); i < i_max; i++) { + PyObject *baseType = PyTuple_GET_ITEM(pyBases, i); + if (PepExt_Type_GetNewSlot(reinterpret_cast<PyTypeObject *>(baseType)) == SbkDummyNew) { + // PYSIDE-595: A base class does not allow inheritance. + return reinterpret_cast<PyTypeObject *>(SbkDummyNew(metatype, args, kwds)); + } + } + + // PYSIDE-939: This is still a temporary patch that circumvents the problem + // with Py_TPFLAGS_METHOD_DESCRIPTOR. The problem exists in Python 3.8 + // until 3.9.12, only. We check the runtime and hope for this version valishing. + // https://github.com/python/cpython/issues/92112 will not be fixed for 3.8 :/ + PyTypeObject *newType{}; + static auto triplet = _PepRuntimeVersion(); + if (triplet >= (3 << 16 | 8 << 8 | 0) && triplet < (3 << 16 | 9 << 8 | 13)) { + auto hold = PyMethodDescr_Type.tp_flags; + PyMethodDescr_Type.tp_flags &= ~Py_TPFLAGS_METHOD_DESCRIPTOR; + newType = PepType_Type_tp_new(metatype, args, kwds); + PyMethodDescr_Type.tp_flags = hold; + } else { + newType = PepType_Type_tp_new(metatype, args, kwds); + } + + if (!newType) + return nullptr; + + SbkObjectTypePrivate *sotp = PepType_SOTP(newType); + + const auto bases = Shiboken::getCppBaseClasses(newType); + if (bases.size() == 1) { + SbkObjectTypePrivate *parentType = PepType_SOTP(bases.front()); + sotp->mi_offsets = parentType->mi_offsets; + sotp->mi_init = parentType->mi_init; + sotp->mi_specialcast = parentType->mi_specialcast; + sotp->type_discovery = parentType->type_discovery; + sotp->cpp_dtor = parentType->cpp_dtor; + sotp->is_multicpp = 0; + sotp->converter = parentType->converter; + } else { + sotp->mi_offsets = nullptr; + sotp->mi_init = nullptr; + sotp->mi_specialcast = nullptr; + sotp->type_discovery = nullptr; + sotp->cpp_dtor = nullptr; + sotp->is_multicpp = 1; + sotp->converter = nullptr; + } + if (bases.size() == 1) { + const char *original_name = PepType_SOTP(bases.front())->original_name; + if (original_name == nullptr) + original_name = "object"; + sotp->original_name = strdup(original_name); + } + else + sotp->original_name = strdup("object"); + sotp->user_data = nullptr; + sotp->d_func = nullptr; + sotp->is_user_type = 1; + + // PYSIDE-1463: Prevent feature switching while in the creation process + auto saveFeature = initSelectableFeature(nullptr); + for (PyTypeObject *base : bases) { + sotp = PepType_SOTP(base); + if (sotp->subtype_init) + sotp->subtype_init(newType, args, kwds); + } + initSelectableFeature(saveFeature); + return newType; +} + +static PyObject *_setupNew(PyObject *obSelf, PyTypeObject *subtype) +{ + auto *obSubtype = reinterpret_cast<PyObject *>(subtype); + auto *sbkSubtype = subtype; + auto *self = reinterpret_cast<SbkObject *>(obSelf); + + Py_INCREF(obSubtype); + auto d = new SbkObjectPrivate; + + auto *sotp = PepType_SOTP(sbkSubtype); + int numBases = ((sotp && sotp->is_multicpp) ? + Shiboken::getNumberOfCppBaseClasses(subtype) : 1); + d->cptr = new void *[numBases]; + std::memset(d->cptr, 0, sizeof(void *) *size_t(numBases)); + d->hasOwnership = 1; + d->containsCppWrapper = 0; + d->validCppObject = 0; + d->parentInfo = nullptr; + d->referredObjects = nullptr; + d->cppObjectCreated = 0; + d->isQAppSingleton = 0; + self->ob_dict = nullptr; + self->weakreflist = nullptr; + self->d = d; + PyObject_GC_Track(obSelf); + return obSelf; +} + +PyObject *SbkObject_tp_new(PyTypeObject *subtype, PyObject * /* args */, PyObject * /* kwds */) +{ + PyObject *self = _Sbk_NewVarObject(subtype); + return _setupNew(self, subtype); +} + +PyObject *SbkQApp_tp_new(PyTypeObject *subtype, PyObject *, PyObject *) +{ + auto *obSelf = MakeQAppWrapper(subtype); + auto *self = reinterpret_cast<SbkObject *>(obSelf); + if (self == nullptr) + return nullptr; + auto ret = _setupNew(obSelf, subtype); + auto priv = self->d; + priv->isQAppSingleton = 1; + return ret; +} + +PyObject *SbkDummyNew(PyTypeObject *type, PyObject *, PyObject *) +{ + // PYSIDE-595: Give the same error as type_call does when tp_new is NULL. + const char regret[] = "¯\\_(ツ)_/¯"; + PyErr_Format(PyExc_TypeError, + "cannot create '%.100s' instances %s", type->tp_name, regret); + return nullptr; +} + +// PYSIDE-74: Fallback used in all types now. +PyObject *FallbackRichCompare(PyObject *self, PyObject *other, int op) +{ + // This is a very simple implementation that supplies a simple identity. + static const char * const opstrings[] = {"<", "<=", "==", "!=", ">", ">="}; + PyObject *res; + + switch (op) { + + case Py_EQ: + res = (self == other) ? Py_True : Py_False; + break; + case Py_NE: + res = (self != other) ? Py_True : Py_False; + break; + default: + PyErr_Format(PyExc_TypeError, + "'%s' not supported between instances of '%.100s' and '%.100s'", + opstrings[op], + self->ob_type->tp_name, + other->ob_type->tp_name); + return nullptr; + } + Py_INCREF(res); + return res; +} + +bool SbkObjectType_Check(PyTypeObject *type) +{ + static auto *meta = SbkObjectType_TypeF(); + return Py_TYPE(type) == meta || PyType_IsSubtype(Py_TYPE(type), meta); +} + +} //extern "C" + + +namespace +{ + +void _destroyParentInfo(SbkObject *obj, bool keepReference) +{ + Shiboken::ParentInfo *pInfo = obj->d->parentInfo; + if (pInfo) { + while(!pInfo->children.empty()) { + SbkObject *first = *pInfo->children.begin(); + // Mark child as invalid + Shiboken::Object::invalidate(first); + Shiboken::Object::removeParent(first, false, keepReference); + } + Shiboken::Object::removeParent(obj, false); + } +} + +} + +namespace Shiboken +{ + +// Wrapper metatype and base type ---------------------------------------------------------- + +void _initMainThreadId(); // helper.cpp + +static std::string msgFailedToInitializeType(const char *description) +{ + std::ostringstream stream; + stream << "[libshiboken] Failed to initialize " << description; + if (auto *error = PepErr_GetRaisedException()) { + if (auto *str = PyObject_Str(error)) + stream << ": " << Shiboken::String::toCString(str); + Py_DECREF(error); + } + stream << '.'; + return stream.str(); +} + +namespace Conversions { void init(); } + +void init() +{ + static bool shibokenAlreadInitialised = false; + if (shibokenAlreadInitialised) + return; + + _initMainThreadId(); + + Conversions::init(); + + //Init private data + Pep384_Init(); + + auto *type = SbkObjectType_TypeF(); + if (type == nullptr || PyType_Ready(type) < 0) + Py_FatalError(msgFailedToInitializeType("Shiboken.BaseWrapperType metatype").c_str()); + + type = SbkObject_TypeF(); + if (type == nullptr || PyType_Ready(type) < 0) + Py_FatalError(msgFailedToInitializeType("Shiboken.BaseWrapper type").c_str()); + + VoidPtr::init(); + + shibokenAlreadInitialised = true; +} + +// PYSIDE-1415: Publish Shiboken objects. +// PYSIDE-1735: Initialize the whole Shiboken startup. +void initShibokenSupport(PyObject *module) +{ + Py_INCREF(SbkObject_TypeF()); + PyModule_AddObject(module, "Object", reinterpret_cast<PyObject *>(SbkObject_TypeF())); + + // PYSIDE-1735: When the initialization was moved into Shiboken import, this + // Py_INCREF became necessary. No idea why. + Py_INCREF(module); + init_shibokensupport_module(); + + auto *type = SbkObject_TypeF(); + if (InitSignatureStrings(type, SbkObject_SignatureStrings) < 0) + Py_FatalError("Error in initShibokenSupport"); +} + +// setErrorAboutWrongArguments now gets overload info from the signature module. +// Info can be nullptr and contains extra info. +void setErrorAboutWrongArguments(PyObject *args, const char *funcName, PyObject *info) +{ + SetError_Argument(args, funcName, info); +} + +PyObject *returnWrongArguments(PyObject *args, const char *funcName, PyObject *info) +{ + setErrorAboutWrongArguments(args, funcName, info); + return {}; +} + +int returnWrongArguments_Zero(PyObject *args, const char *funcName, PyObject *info) +{ + setErrorAboutWrongArguments(args, funcName, info); + return 0; +} + +int returnWrongArguments_MinusOne(PyObject *args, const char *funcName, PyObject *info) +{ + setErrorAboutWrongArguments(args, funcName, info); + return -1; +} + +PyObject *returnFromRichCompare(PyObject *result) +{ + if (result && !PyErr_Occurred()) + return result; + Shiboken::Errors::setOperatorNotImplemented(); + return {}; +} + +PyObject *checkInvalidArgumentCount(Py_ssize_t numArgs, Py_ssize_t minArgs, Py_ssize_t maxArgs) +{ + PyObject *result = nullptr; + // for seterror_argument(), signature/errorhandler.py + if (numArgs > maxArgs) { + static PyObject *const tooMany = Shiboken::String::createStaticString(">"); + result = tooMany; + Py_INCREF(result); + } else if (numArgs < minArgs) { + static PyObject *const tooFew = Shiboken::String::createStaticString("<"); + static PyObject *const noArgs = Shiboken::String::createStaticString("0"); + result = numArgs > 0 ? tooFew : noArgs; + Py_INCREF(result); + } + return result; +} + +std::vector<SbkObject *> splitPyObject(PyObject *pyObj) +{ + std::vector<SbkObject *> result; + if (PySequence_Check(pyObj)) { + AutoDecRef lst(PySequence_Fast(pyObj, "Invalid keep reference object.")); + if (!lst.isNull()) { + for (Py_ssize_t i = 0, i_max = PySequence_Fast_GET_SIZE(lst.object()); i < i_max; ++i) { + PyObject *item = PySequence_Fast_GET_ITEM(lst.object(), i); + if (Object::checkType(item)) + result.push_back(reinterpret_cast<SbkObject *>(item)); + } + } + } else { + result.push_back(reinterpret_cast<SbkObject *>(pyObj)); + } + return result; +} + +template <class Iterator> +inline void decRefPyObjectList(Iterator i1, Iterator i2) +{ + for (; i1 != i2; ++i1) + Py_DECREF(i1->second); +} + +namespace ObjectType +{ + +bool checkType(PyTypeObject *type) +{ + return PyType_IsSubtype(type, SbkObject_TypeF()) != 0; +} + +bool isUserType(PyTypeObject *type) +{ + return checkType(type) && PepType_SOTP(type)->is_user_type; +} + +bool canCallConstructor(PyTypeObject *myType, PyTypeObject *ctorType) +{ + auto findBasePred = [ctorType](PyTypeObject *type) { return type == ctorType; }; + if (!walkThroughBases(myType, findBasePred)) { + PyErr_Format(PyExc_TypeError, "%s isn't a direct base class of %s", ctorType->tp_name, myType->tp_name); + return false; + } + return true; +} + +bool hasCast(PyTypeObject *type) +{ + return PepType_SOTP(type)->mi_specialcast != nullptr; +} + +void *cast(PyTypeObject *sourceType, SbkObject *obj, PyTypeObject *pyTargetType) +{ + auto *sotp = PepType_SOTP(sourceType); + return sotp->mi_specialcast(Object::cppPointer(obj, pyTargetType), pyTargetType); +} + +void setCastFunction(PyTypeObject *type, SpecialCastFunction func) +{ + auto *sotp = PepType_SOTP(type); + sotp->mi_specialcast = func; +} + +void setOriginalName(PyTypeObject *type, const char *name) +{ + auto *sotp = PepType_SOTP(type); + if (sotp->original_name) + free(sotp->original_name); + sotp->original_name = strdup(name); +} + +const char *getOriginalName(PyTypeObject *type) +{ + return PepType_SOTP(type)->original_name; +} + +void setTypeDiscoveryFunctionV2(PyTypeObject *type, TypeDiscoveryFuncV2 func) +{ + PepType_SOTP(type)->type_discovery = func; +} + +void copyMultipleInheritance(PyTypeObject *type, PyTypeObject *other) +{ + auto *sotp_type = PepType_SOTP(type); + auto *sotp_other = PepType_SOTP(other); + sotp_type->mi_init = sotp_other->mi_init; + sotp_type->mi_offsets = sotp_other->mi_offsets; + sotp_type->mi_specialcast = sotp_other->mi_specialcast; +} + +void setMultipleInheritanceFunction(PyTypeObject *type, MultipleInheritanceInitFunction function) +{ + PepType_SOTP(type)->mi_init = function; +} + +MultipleInheritanceInitFunction getMultipleInheritanceFunction(PyTypeObject *type) +{ + return PepType_SOTP(type)->mi_init; +} + +void setDestructorFunction(PyTypeObject *type, ObjectDestructor func) +{ + PepType_SOTP(type)->cpp_dtor = func; +} + +PyTypeObject * +introduceWrapperType(PyObject *enclosingObject, + const char *typeName, + const char *originalName, + PyType_Spec *typeSpec, + ObjectDestructor cppObjDtor, + PyObject *bases, + unsigned wrapperFlags) +{ + assert(PySequence_Fast_GET_SIZE(bases) > 0); + typeSpec->slots[0].pfunc = PySequence_Fast_GET_ITEM(bases, 0); + + auto *type = SbkType_FromSpecBasesMeta(typeSpec, bases, SbkObjectType_TypeF()); + + auto sotp = PepType_SOTP(type); + if (wrapperFlags & DeleteInMainThread) + sotp->delete_in_main_thread = 1; + sotp->type_behaviour = (wrapperFlags & Value) != 0 + ? BEHAVIOUR_VALUETYPE : BEHAVIOUR_OBJECTTYPE; + + setOriginalName(type, originalName); + setDestructorFunction(type, cppObjDtor); + auto *ob_type = reinterpret_cast<PyObject *>(type); + + if (wrapperFlags & InnerClass) { + // PYSIDE-2230: Instead of tp_dict, use the enclosing type. + // This stays interface compatible. + if (PyType_Check(enclosingObject)) { + AutoDecRef tpDict(PepType_GetDict(reinterpret_cast<PyTypeObject *>(enclosingObject))); + return PyDict_SetItemString(tpDict, typeName, ob_type) == 0 ? type : nullptr; + } + assert(PyDict_Check(enclosingObject)); + return PyDict_SetItemString(enclosingObject, typeName, ob_type) == 0 ? type : nullptr; + } + + // PyModule_AddObject steals type's reference. + Py_INCREF(ob_type); + if (PyModule_AddObject(enclosingObject, typeName, ob_type) != 0) { + std::cerr << "Warning: " << __FUNCTION__ << " returns nullptr for " + << typeName << '/' << originalName << " due to PyModule_AddObject(enclosingObject=" + << enclosingObject << ", ob_type=" << ob_type << ") failing\n"; + return nullptr; + } + return type; +} + +void setSubTypeInitHook(PyTypeObject *type, SubTypeInitHook func) +{ + assert(SbkObjectType_Check(type)); + PepType_SOTP(type)->subtype_init = func; +} + +void *getTypeUserData(PyTypeObject *type) +{ + assert(SbkObjectType_Check(type)); + return PepType_SOTP(type)->user_data; +} + +void setTypeUserData(PyTypeObject *type, void *userData, DeleteUserDataFunc d_func) +{ + assert(SbkObjectType_Check(type)); + auto *sotp = PepType_SOTP(type); + sotp->user_data = userData; + sotp->d_func = d_func; +} + +// Try to find the exact type of cptr. +PyTypeObject *typeForTypeName(const char *typeName) +{ + PyTypeObject *result{}; + if (typeName) { + if (PyTypeObject *pyType = Shiboken::Conversions::getPythonTypeObject(typeName)) + result = pyType; + } + return result; +} + +bool hasSpecialCastFunction(PyTypeObject *sbkType) +{ + const auto *d = PepType_SOTP(sbkType); + return d != nullptr && d->mi_specialcast != nullptr; +} + +// Find whether base is a direct single line base class of type +// (no multiple inheritance), that is, a C++ pointer cast can safely be done. +static bool isDirectAncestor(PyTypeObject *type, PyTypeObject *base) +{ + if (type == base) + return true; + if (PyTuple_Size(type->tp_bases) == 0) + return false; + auto *sbkObjectType = SbkObject_TypeF(); + auto *firstBase = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(type->tp_bases, 0)); + return firstBase != sbkObjectType + && PyType_IsSubtype(type, sbkObjectType) != 0 + && isDirectAncestor(firstBase, base); +} + +bool canDowncastTo(PyTypeObject *baseType, PyTypeObject *targetType) +{ + return isDirectAncestor(targetType, baseType); +} + +} // namespace ObjectType + + +namespace Object +{ + +static void recursive_invalidate(SbkObject *self, std::set<SbkObject *>& seen); + +bool checkType(PyObject *pyObj) +{ + return ObjectType::checkType(Py_TYPE(pyObj)); +} + +bool isUserType(PyObject *pyObj) +{ + return ObjectType::isUserType(Py_TYPE(pyObj)); +} + +Py_hash_t hash(PyObject *pyObj) +{ + assert(Shiboken::Object::checkType(pyObj)); + return reinterpret_cast<Py_hash_t>(pyObj); +} + +static void setSequenceOwnership(PyObject *pyObj, bool owner) +{ + + bool has_length = true; + + if (!pyObj) + return; + + if (PySequence_Size(pyObj) < 0) { + PyErr_Clear(); + has_length = false; + } + + if (PySequence_Check(pyObj) && has_length) { + Py_ssize_t size = PySequence_Size(pyObj); + if (size > 0) { + const auto objs = splitPyObject(pyObj); + if (owner) { + for (SbkObject *o : objs) + getOwnership(o); + } else { + for (SbkObject *o : objs) + releaseOwnership(o); + } + } + } else if (Object::checkType(pyObj)) { + if (owner) + getOwnership(reinterpret_cast<SbkObject *>(pyObj)); + else + releaseOwnership(reinterpret_cast<SbkObject *>(pyObj)); + } +} + +void setValidCpp(SbkObject *pyObj, bool value) +{ + pyObj->d->validCppObject = value; +} + +void setHasCppWrapper(SbkObject *pyObj, bool value) +{ + pyObj->d->containsCppWrapper = value; +} + +bool hasCppWrapper(SbkObject *pyObj) +{ + return pyObj->d->containsCppWrapper; +} + +bool wasCreatedByPython(SbkObject *pyObj) +{ + return pyObj->d->cppObjectCreated; +} + +void callCppDestructors(SbkObject *pyObj) +{ + auto priv = pyObj->d; + if (priv->isQAppSingleton && DestroyQApplication) { + // PYSIDE-1470: Allow to destroy the application from Shiboken. + DestroyQApplication(); + return; + } + PyTypeObject *type = Py_TYPE(pyObj); + auto *sotp = PepType_SOTP(type); + if (sotp->is_multicpp) { + callDestructor(getDestructorEntries(pyObj)); + } else { + Shiboken::ThreadStateSaver threadSaver; + threadSaver.save(); + sotp->cpp_dtor(pyObj->d->cptr[0]); + } + + if (priv->validCppObject && priv->containsCppWrapper) { + BindingManager::instance().releaseWrapper(pyObj); + } + + /* invalidate needs to be called before deleting pointer array because + it needs to delete entries for them from the BindingManager hash table; + also release wrapper explicitly if object contains C++ wrapper because + invalidate doesn't */ + invalidate(pyObj); + + delete[] priv->cptr; + priv->cptr = nullptr; + priv->validCppObject = false; +} + +bool hasOwnership(SbkObject *pyObj) +{ + return pyObj->d->hasOwnership; +} + +void getOwnership(SbkObject *self) +{ + // skip if already have the ownership + if (self->d->hasOwnership) + return; + + // skip if this object has parent + if (self->d->parentInfo && self->d->parentInfo->parent) + return; + + // Get back the ownership + self->d->hasOwnership = true; + + if (self->d->containsCppWrapper) + Py_DECREF(reinterpret_cast<PyObject *>(self)); // Remove extra ref + else + makeValid(self); // Make the object valid again +} + +void getOwnership(PyObject *pyObj) +{ + if (pyObj) + setSequenceOwnership(pyObj, true); +} + +void releaseOwnership(SbkObject *self) +{ + // skip if the ownership have already moved to c++ + auto *selfType = Py_TYPE(self); + if (!self->d->hasOwnership || Shiboken::Conversions::pythonTypeIsValueType(PepType_SOTP(selfType)->converter)) + return; + + // remove object ownership + self->d->hasOwnership = false; + + // If We have control over object life + if (self->d->containsCppWrapper) + Py_INCREF(reinterpret_cast<PyObject *>(self)); // keep the python object alive until the wrapper destructor call + else + invalidate(self); // If I do not know when this object will die We need to invalidate this to avoid use after +} + +void releaseOwnership(PyObject *self) +{ + setSequenceOwnership(self, false); +} + +/* Needed forward declarations */ +static void recursive_invalidate(PyObject *pyobj, std::set<SbkObject *>& seen); +static void recursive_invalidate(SbkObject *self, std::set<SbkObject *> &seen); + +void invalidate(PyObject *pyobj) +{ + std::set<SbkObject *> seen; + recursive_invalidate(pyobj, seen); +} + +void invalidate(SbkObject *self) +{ + std::set<SbkObject *> seen; + recursive_invalidate(self, seen); +} + +static void recursive_invalidate(PyObject *pyobj, std::set<SbkObject *> &seen) +{ + const auto objs = splitPyObject(pyobj); + for (SbkObject *o : objs) + recursive_invalidate(o, seen); +} + +static void recursive_invalidate(SbkObject *self, std::set<SbkObject *> &seen) +{ + // Skip if this object not is a valid object or if it's already been seen + if (!self || reinterpret_cast<PyObject *>(self) == Py_None || seen.find(self) != seen.end()) + return; + seen.insert(self); + + if (!self->d->containsCppWrapper) { + self->d->validCppObject = false; // Mark object as invalid only if this is not a wrapper class + BindingManager::instance().releaseWrapper(self); + } + + // If it is a parent invalidate all children. + if (self->d->parentInfo) { + // Create a copy because this list can be changed during the process + ChildrenList copy = self->d->parentInfo->children; + + for (SbkObject *child : copy) { + // invalidate the child + recursive_invalidate(child, seen); + + // if the parent not is a wrapper class, then remove children from him, because We do not know when this object will be destroyed + if (!self->d->validCppObject) + removeParent(child, true, true); + } + } + + // If has ref to other objects invalidate all + if (self->d->referredObjects) { + RefCountMap &refCountMap = *(self->d->referredObjects); + for (auto it = refCountMap.begin(), end = refCountMap.end(); it != end; ++it) + recursive_invalidate(it->second, seen); + } +} + +void makeValid(SbkObject *self) +{ + // Skip if this object not is a valid object + if (!self || reinterpret_cast<PyObject *>(self) == Py_None || self->d->validCppObject) + return; + + // Mark object as invalid only if this is not a wrapper class + self->d->validCppObject = true; + + // If it is a parent make all children valid + if (self->d->parentInfo) { + for (SbkObject *child : self->d->parentInfo->children) + makeValid(child); + } + + // If has ref to other objects make all valid again + if (self->d->referredObjects) { + const RefCountMap &refCountMap = *(self->d->referredObjects); + for (const auto &p : refCountMap) { + if (Shiboken::Object::checkType(p.second)) + makeValid(reinterpret_cast<SbkObject *>(p.second)); + } + } +} + +void *cppPointer(SbkObject *pyObj, PyTypeObject *desiredType) +{ + PyTypeObject *pyType = Py_TYPE(pyObj); + auto *sotp = PepType_SOTP(pyType); + int idx = 0; + if (sotp->is_multicpp) + idx = getTypeIndexOnHierarchy(pyType, desiredType); + if (pyObj->d->cptr) + return pyObj->d->cptr[idx]; + return nullptr; +} + +std::vector<void *> cppPointers(SbkObject *pyObj) +{ + int n = getNumberOfCppBaseClasses(Py_TYPE(pyObj)); + std::vector<void *> ptrs(n); + for (int i = 0; i < n; ++i) + ptrs[i] = pyObj->d->cptr[i]; + return ptrs; +} + + +bool setCppPointer(SbkObject *sbkObj, PyTypeObject *desiredType, void *cptr) +{ + PyTypeObject *type = Py_TYPE(sbkObj); + int idx = 0; + if (PepType_SOTP(type)->is_multicpp) + idx = getTypeIndexOnHierarchy(type, desiredType); + + const bool alreadyInitialized = sbkObj->d->cptr[idx] != nullptr; + if (alreadyInitialized) + PyErr_Format(PyExc_RuntimeError, "You can't initialize an %s object in class %s twice!", + desiredType->tp_name, type->tp_name); + else + sbkObj->d->cptr[idx] = cptr; + + sbkObj->d->cppObjectCreated = true; + return !alreadyInitialized; +} + +bool isValid(PyObject *pyObj) +{ + if (!pyObj || pyObj == Py_None + || PyType_Check(pyObj) != 0 + || Py_TYPE(Py_TYPE(pyObj)) != SbkObjectType_TypeF()) { + return true; + } + + auto priv = reinterpret_cast<SbkObject *>(pyObj)->d; + + if (!priv->cppObjectCreated && isUserType(pyObj)) { + PyErr_Format(PyExc_RuntimeError, "'__init__' method of object's base class (%s) not called.", + Py_TYPE(pyObj)->tp_name); + return false; + } + + if (!priv->validCppObject) { + PyErr_Format(PyExc_RuntimeError, "Internal C++ object (%s) already deleted.", + Py_TYPE(pyObj)->tp_name); + return false; + } + + return true; +} + +bool isValid(SbkObject *pyObj, bool throwPyError) +{ + if (!pyObj) + return false; + + SbkObjectPrivate *priv = pyObj->d; + if (!priv->cppObjectCreated && isUserType(reinterpret_cast<PyObject *>(pyObj))) { + if (throwPyError) + PyErr_Format(PyExc_RuntimeError, "Base constructor of the object (%s) not called.", + Py_TYPE(pyObj)->tp_name); + return false; + } + + if (!priv->validCppObject) { + if (throwPyError) + PyErr_Format(PyExc_RuntimeError, "Internal C++ object (%s) already deleted.", + (Py_TYPE(pyObj))->tp_name); + return false; + } + + return true; +} + +bool isValid(PyObject *pyObj, bool throwPyError) +{ + if (!pyObj || pyObj == Py_None || + !PyType_IsSubtype(Py_TYPE(pyObj), SbkObject_TypeF())) { + return true; + } + return isValid(reinterpret_cast<SbkObject *>(pyObj), throwPyError); +} + +SbkObject *findColocatedChild(SbkObject *wrapper, + const PyTypeObject *instanceType) +{ + // Degenerate case, wrapper is the correct wrapper. + if (reinterpret_cast<const void *>(Py_TYPE(wrapper)) == reinterpret_cast<const void *>(instanceType)) + return wrapper; + + if (!(wrapper->d && wrapper->d->cptr)) + return nullptr; + + ParentInfo *pInfo = wrapper->d->parentInfo; + if (!pInfo) + return nullptr; + + ChildrenList &children = pInfo->children; + + for (SbkObject *child : children) { + if (!(child->d && child->d->cptr)) + continue; + if (child->d->cptr[0] == wrapper->d->cptr[0]) { + return reinterpret_cast<const void *>(Py_TYPE(child)) == reinterpret_cast<const void *>(instanceType) + ? child : findColocatedChild(child, instanceType); + } + } + return nullptr; +} + +// Legacy, for compatibility only. +PyObject *newObject(PyTypeObject *instanceType, + void *cptr, + bool hasOwnership, + bool isExactType, + const char *typeName) +{ + return isExactType + ? newObjectForType(instanceType, cptr, hasOwnership) + : newObjectWithHeuristics(instanceType, cptr, hasOwnership, typeName); +} + +static PyObject *newObjectWithHeuristicsHelper(PyTypeObject *instanceType, + PyTypeObject *exactType, + void *cptr, + bool hasOwnership) +{ + // Try to find the exact type of cptr. For hierarchies with + // non-virtual destructors, typeid() will return the base name. + // Try type discovery in these cases. + if (exactType == nullptr || exactType == instanceType) { + auto resolved = BindingManager::instance().findDerivedType(cptr, instanceType); + if (resolved.first != nullptr + && Shiboken::ObjectType::canDowncastTo(instanceType, resolved.first)) { + exactType = resolved.first; + cptr = resolved.second; + } + } + + return newObjectForType(exactType != nullptr ? exactType : instanceType, + cptr, hasOwnership); +} + +PyObject *newObjectForPointer(PyTypeObject *instanceType, + void *cptr, + bool hasOwnership, + const char *typeName) +{ + // Try to find the exact type of cptr. + PyTypeObject *exactType = ObjectType::typeForTypeName(typeName); + // PYSIDE-868: In case of multiple inheritance, (for example, + // a function returning a QPaintDevice * from a QWidget *), + // use instance type to avoid pointer offset errors. + return exactType != nullptr && !Shiboken::ObjectType::canDowncastTo(instanceType, exactType) + ? newObjectForType(instanceType, cptr, hasOwnership) + : newObjectWithHeuristicsHelper(instanceType, exactType, cptr, hasOwnership); +} + + +PyObject *newObjectWithHeuristics(PyTypeObject *instanceType, + void *cptr, + bool hasOwnership, + const char *typeName) +{ + return newObjectWithHeuristicsHelper(instanceType, + ObjectType::typeForTypeName(typeName), + cptr, hasOwnership); +} + +PyObject *newObjectForType(PyTypeObject *instanceType, void *cptr, bool hasOwnership) +{ + bool shouldCreate = true; + bool shouldRegister = true; + SbkObject *self = nullptr; + + // Some logic to ensure that colocated child field does not overwrite the parent + if (BindingManager::instance().hasWrapper(cptr)) { + SbkObject *existingWrapper = BindingManager::instance().retrieveWrapper(cptr); + + self = findColocatedChild(existingWrapper, instanceType); + if (self) { + // Wrapper already registered for cptr. + // This should not ideally happen, binding code should know when a wrapper + // already exists and retrieve it instead. + shouldRegister = shouldCreate = false; + } else if (hasOwnership && + (!(Shiboken::Object::hasCppWrapper(existingWrapper) || + Shiboken::Object::hasOwnership(existingWrapper)))) { + // Old wrapper is likely junk, since we have ownership and it doesn't. + BindingManager::instance().releaseWrapper(existingWrapper); + } else { + // Old wrapper may be junk caused by some bug in identifying object deletion + // but it may not be junk when a colocated field is accessed for an + // object which was not created by python (returned from c++ factory function). + // Hence we cannot release the wrapper confidently so we do not register. + shouldRegister = false; + } + } + + if (shouldCreate) { + self = reinterpret_cast<SbkObject *>(SbkObject_tp_new(instanceType, nullptr, nullptr)); + self->d->cptr[0] = cptr; + self->d->hasOwnership = hasOwnership; + self->d->validCppObject = 1; + if (shouldRegister) { + BindingManager::instance().registerWrapper(self, cptr); + } + } else { + Py_IncRef(reinterpret_cast<PyObject *>(self)); + } + return reinterpret_cast<PyObject *>(self); +} + +void destroy(SbkObject *self, void *cppData) +{ + // Skip if this is called with NULL pointer this can happen in derived classes + if (!self) + return; + + // This can be called in c++ side + Shiboken::GilState gil; + + // Remove all references attached to this object + clearReferences(self); + + // Remove the object from parent control + + // Verify if this object has parent + bool hasParent = (self->d->parentInfo && self->d->parentInfo->parent); + + if (self->d->parentInfo) { + // Check for children information and make all invalid if they exists + _destroyParentInfo(self, true); + // If this object has parent then the pyobject can be invalid now, because we remove the last ref after remove from parent + } + + //if !hasParent this object could still alive + if (!hasParent && self->d->containsCppWrapper && !self->d->hasOwnership) { + // Remove extra ref used by c++ object this will case the pyobject destruction + // This can cause the object death + Py_DECREF(reinterpret_cast<PyObject *>(self)); + } + + //Python Object is not destroyed yet + if (cppData && Shiboken::BindingManager::instance().hasWrapper(cppData)) { + // Remove from BindingManager + Shiboken::BindingManager::instance().releaseWrapper(self); + self->d->hasOwnership = false; + + // the cpp object instance was deleted + delete[] self->d->cptr; + self->d->cptr = nullptr; + } + + // After this point the object can be death do not use the self pointer bellow +} + +void removeParent(SbkObject *child, bool giveOwnershipBack, bool keepReference) +{ + ParentInfo *pInfo = child->d->parentInfo; + if (!pInfo || !pInfo->parent) { + if (pInfo && pInfo->hasWrapperRef) { + pInfo->hasWrapperRef = false; + } + return; + } + + ChildrenList &oldBrothers = pInfo->parent->d->parentInfo->children; + // Verify if this child is part of parent list + auto iChild = oldBrothers.find(child); + if (iChild == oldBrothers.end()) + return; + + oldBrothers.erase(iChild); + + pInfo->parent = nullptr; + + // This will keep the wrapper reference, will wait for wrapper destruction to remove that + if (keepReference && + child->d->containsCppWrapper) { + //If have already a extra ref remove this one + if (pInfo->hasWrapperRef) + Py_DECREF(child); + else + pInfo->hasWrapperRef = true; + return; + } + + // Transfer ownership back to Python + child->d->hasOwnership = giveOwnershipBack; + + // Remove parent ref + Py_DECREF(child); +} + +void setParent(PyObject *parent, PyObject *child) +{ + if (!child || child == Py_None || child == parent) + return; + + /* + * setParent is recursive when the child is a native Python sequence, i.e. objects not binded by Shiboken + * like tuple and list. + * + * This "limitation" exists to fix the following problem: A class multiple inherits QObject and QString, + * so if you pass this class to someone that takes the ownership, we CAN'T enter in this if, but hey! QString + * follows the sequence protocol. + */ + if (PySequence_Check(child) && !Object::checkType(child)) { + Shiboken::AutoDecRef seq(PySequence_Fast(child, nullptr)); + for (Py_ssize_t i = 0, max = PySequence_Size(seq); i < max; ++i) + setParent(parent, PySequence_Fast_GET_ITEM(seq.object(), i)); + return; + } + + bool parentIsNull = !parent || parent == Py_None; + auto parent_ = reinterpret_cast<SbkObject *>(parent); + auto child_ = reinterpret_cast<SbkObject *>(child); + + if (!parentIsNull) { + if (!parent_->d->parentInfo) + parent_->d->parentInfo = new ParentInfo; + + // do not re-add a child + if (child_->d->parentInfo && (child_->d->parentInfo->parent == parent_)) + return; + } + + ParentInfo *pInfo = child_->d->parentInfo; + bool hasAnotherParent = pInfo && pInfo->parent && pInfo->parent != parent_; + + //Avoid destroy child during reparent operation + Py_INCREF(child); + + // check if we need to remove this child from the old parent + if (parentIsNull || hasAnotherParent) + removeParent(child_); + + // Add the child to the new parent + pInfo = child_->d->parentInfo; + if (!parentIsNull) { + if (!pInfo) + pInfo = child_->d->parentInfo = new ParentInfo; + + pInfo->parent = parent_; + parent_->d->parentInfo->children.insert(child_); + + // Add Parent ref + Py_INCREF(child_); + + // Remove ownership + child_->d->hasOwnership = false; + } + + // Remove previous safe ref + Py_DECREF(child); +} + +void deallocData(SbkObject *self, bool cleanup) +{ + // Make cleanup if this is not a wrapper otherwise this will be done on wrapper destructor + if(cleanup) { + removeParent(self); + + if (self->d->parentInfo) + _destroyParentInfo(self, true); + + clearReferences(self); + } + + if (self->d->cptr) { + // Remove from BindingManager + Shiboken::BindingManager::instance().releaseWrapper(self); + delete[] self->d->cptr; + self->d->cptr = nullptr; + // delete self->d; PYSIDE-205: wrong! + } + delete self->d; // PYSIDE-205: always delete d. + Py_XDECREF(self->ob_dict); + PepExt_TypeCallFree(reinterpret_cast<PyObject *>(self)); +} + +void setTypeUserData(SbkObject *wrapper, void *userData, DeleteUserDataFunc d_func) +{ + auto *type = Py_TYPE(wrapper); + auto *sotp = PepType_SOTP(type); + if (sotp->user_data) + sotp->d_func(sotp->user_data); + + sotp->d_func = d_func; + sotp->user_data = userData; +} + +void *getTypeUserData(SbkObject *wrapper) +{ + auto *type = Py_TYPE(wrapper); + return PepType_SOTP(type)->user_data; +} + +static inline bool isNone(const PyObject *o) +{ + return o == nullptr || o == Py_None; +} + +static void removeRefCountKey(SbkObject *self, const char *key) +{ + if (self->d->referredObjects) { + const auto iterPair = self->d->referredObjects->equal_range(key); + if (iterPair.first != iterPair.second) { + decRefPyObjectList(iterPair.first, iterPair.second); + self->d->referredObjects->erase(iterPair.first, iterPair.second); + } + } +} + +void keepReference(SbkObject *self, const char *key, PyObject *referredObject, bool append) +{ + if (isNone(referredObject)) { + removeRefCountKey(self, key); + return; + } + + if (!self->d->referredObjects) { + self->d->referredObjects = + new Shiboken::RefCountMap{RefCountMap::value_type{key, referredObject}}; + Py_INCREF(referredObject); + return; + } + + RefCountMap &refCountMap = *(self->d->referredObjects); + const auto iterPair = refCountMap.equal_range(key); + if (std::any_of(iterPair.first, iterPair.second, + [referredObject](const RefCountMap::value_type &v) { return v.second == referredObject; })) { + return; + } + + if (!append && iterPair.first != iterPair.second) { + decRefPyObjectList(iterPair.first, iterPair.second); + refCountMap.erase(iterPair.first, iterPair.second); + } + + refCountMap.insert(RefCountMap::value_type{key, referredObject}); + Py_INCREF(referredObject); +} + +void removeReference(SbkObject *self, const char *key, PyObject *referredObject) +{ + if (!isNone(referredObject)) + removeRefCountKey(self, key); +} + +void clearReferences(SbkObject *self) +{ + if (!self->d->referredObjects) + return; + + RefCountMap &refCountMap = *(self->d->referredObjects); + for (auto it = refCountMap.begin(), end = refCountMap.end(); it != end; ++it) + Py_DECREF(it->second); + self->d->referredObjects->clear(); +} + +// Helpers for debug / info formatting + +static std::vector<PyTypeObject *> getBases(SbkObject *self) +{ + return ObjectType::isUserType(Py_TYPE(self)) + ? getCppBaseClasses(Py_TYPE(self)) + : std::vector<PyTypeObject *>(1, Py_TYPE(self)); +} + +static bool isValueType(SbkObject *self) +{ + return PepType_SOTP(Py_TYPE(self))->type_behaviour == BEHAVIOUR_VALUETYPE; +} + +void _debugFormat(std::ostream &s, SbkObject *self) +{ + assert(self); + auto *d = self->d; + if (!d) { + s << "[Invalid]"; + return; + } + if (d->cptr) { + const std::vector<PyTypeObject *> bases = getBases(self); + for (size_t i = 0, size = bases.size(); i < size; ++i) + s << ", C++: " << bases[i]->tp_name << '/' << self->d->cptr[i]; + } else { + s << " [Deleted]"; + } + if (d->hasOwnership) + s << " [hasOwnership]"; + if (d->containsCppWrapper) + s << " [containsCppWrapper]"; + if (d->validCppObject) + s << " [validCppObject]"; + if (d->cppObjectCreated) + s << " [wasCreatedByPython]"; + s << (isValueType(self) ? " [value]" : " [object]"); + + if (d->parentInfo) { + if (auto *parent = d->parentInfo->parent) + s << ", parent=" << reinterpret_cast<PyObject *>(parent)->ob_type->tp_name + << '/' << parent; + if (!d->parentInfo->children.empty()) + s << ", " << d->parentInfo->children.size() << " child(ren)"; + } + if (d->referredObjects && !d->referredObjects->empty()) + s << ", " << d->referredObjects->size() << " referred object(s)"; +} + +std::string info(SbkObject *self) +{ + std::ostringstream s; + + if (self->d && self->d->cptr) { + const std::vector<PyTypeObject *> bases = getBases(self); + + s << "C++ address....... "; + for (size_t i = 0, size = bases.size(); i < size; ++i) + s << bases[i]->tp_name << '/' << self->d->cptr[i] << ' '; + s << "\n"; + } + else { + s << "C++ address....... <<Deleted>>\n"; + } + + s << "hasOwnership...... " << bool(self->d->hasOwnership) << "\n" + "containsCppWrapper " << self->d->containsCppWrapper << "\n" + "validCppObject.... " << self->d->validCppObject << "\n" + "wasCreatedByPython " << self->d->cppObjectCreated << "\n" + "value...... " << isValueType(self) << "\n" + "reference count... " << reinterpret_cast<PyObject *>(self)->ob_refcnt << '\n'; + + if (self->d->parentInfo && self->d->parentInfo->parent) { + s << "parent............ "; + Shiboken::AutoDecRef parent(PyObject_Str(reinterpret_cast<PyObject *>(self->d->parentInfo->parent))); + s << String::toCString(parent) << "\n"; + } + + if (self->d->parentInfo && !self->d->parentInfo->children.empty()) { + s << "children.......... "; + for (SbkObject *sbkChild : self->d->parentInfo->children) { + Shiboken::AutoDecRef child(PyObject_Str(reinterpret_cast<PyObject *>(sbkChild))); + s << String::toCString(child) << ' '; + } + s << '\n'; + } + + if (self->d->referredObjects && !self->d->referredObjects->empty()) { + const Shiboken::RefCountMap &map = *self->d->referredObjects; + s << "referred objects.. "; + std::string lastKey; + for (const auto &p : map) { + if (p.first != lastKey) { + if (!lastKey.empty()) + s << " "; + s << '"' << p.first << "\" => "; + lastKey = p.first; + } + Shiboken::AutoDecRef obj(PyObject_Str(p.second)); + s << String::toCString(obj) << ' '; + } + s << '\n'; + } + return s.str(); +} + +} // namespace Object + +} // namespace Shiboken diff --git a/sources/shiboken6/libshiboken/basewrapper.h b/sources/shiboken6/libshiboken/basewrapper.h new file mode 100644 index 000000000..ec5545aea --- /dev/null +++ b/sources/shiboken6/libshiboken/basewrapper.h @@ -0,0 +1,519 @@ +// Copyright (C) 2019 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 + +#ifndef BASEWRAPPER_H +#define BASEWRAPPER_H + +#include "sbkpython.h" +#include "shibokenmacros.h" +#include "sbktypefactory.h" + +#include <vector> +#include <string> + +extern "C" +{ + +struct SbkConverter; +struct SbkObjectPrivate; + +/// Base Python object for all the wrapped C++ classes. +struct LIBSHIBOKEN_API SbkObject +{ + PyObject_HEAD + /// Instance dictionary. + PyObject *ob_dict; + /// List of weak references + PyObject *weakreflist; + SbkObjectPrivate *d; +}; + + +/// PYSIDE-939: A general replacement for object_dealloc. +LIBSHIBOKEN_API void Sbk_object_dealloc(PyObject *self); + +/// Dealloc the python object \p pyObj and the C++ object represented by it. +LIBSHIBOKEN_API void SbkDeallocWrapper(PyObject *pyObj); +LIBSHIBOKEN_API void SbkDeallocQAppWrapper(PyObject *pyObj); +LIBSHIBOKEN_API void SbkDeallocWrapperWithPrivateDtor(PyObject *self); + +/// Function signature for the multiple inheritance information initializers that should be provided by classes with multiple inheritance. +using MultipleInheritanceInitFunction = int *(*)(const void *); + +/** + * Special cast function is used to correctly cast an object when it's + * part of a multiple inheritance hierarchy. + * The implementation of this function is auto generated by the generator and you don't need to care about it. + */ +using SpecialCastFunction = void *(*)(void *, PyTypeObject *); +using TypeDiscoveryFunc = PyTypeObject *(*)(void *, PyTypeObject *); +using TypeDiscoveryFuncV2 = void *(*)(void *, PyTypeObject *); + +// Used in userdata dealloc function +using DeleteUserDataFunc = void (*)(void *); + +using ObjectDestructor = void (*)(void *); + +using SubTypeInitHook = void (*)(PyTypeObject *, PyObject *, PyObject *); + +/// PYSIDE-1019: Set the function to select the current feature. +/// Return value is the previous content. +using SelectableFeatureHook = void (*)(PyTypeObject *); +using SelectableFeatureCallback = void (*)(bool); +LIBSHIBOKEN_API SelectableFeatureHook initSelectableFeature(SelectableFeatureHook func); +LIBSHIBOKEN_API void setSelectableFeatureCallback(SelectableFeatureCallback func); + +/// PYSIDE-1626: Enforcing a context switch without further action. +LIBSHIBOKEN_API void SbkObjectType_UpdateFeature(PyTypeObject *type); + +/// PYSIDE-1019: Get access to PySide property strings. +LIBSHIBOKEN_API const char **SbkObjectType_GetPropertyStrings(PyTypeObject *type); +LIBSHIBOKEN_API void SbkObjectType_SetPropertyStrings(PyTypeObject *type, const char **strings); + +/// PYSIDE-1735: Store the enumFlagInfo. +LIBSHIBOKEN_API void SbkObjectType_SetEnumFlagInfo(PyTypeObject *type, const char **strings); + +/// PYSIDE-1470: Set the function to kill a Q*Application. +using DestroyQAppHook = void(*)(); +LIBSHIBOKEN_API void setDestroyQApplication(DestroyQAppHook func); + +/// PYSIDE-535: Use the C API in PyPy instead of `op->ob_dict`, directly (borrowed ref) +LIBSHIBOKEN_API PyObject *SbkObject_GetDict_NoRef(PyObject *op); + +extern LIBSHIBOKEN_API PyTypeObject *SbkObjectType_TypeF(void); +extern LIBSHIBOKEN_API PyTypeObject *SbkObject_TypeF(void); + + +struct SbkObjectTypePrivate; +/// PyTypeObject extended with C++ multiple inheritance information. + +LIBSHIBOKEN_API PyObject *SbkObject_tp_new(PyTypeObject *subtype, PyObject *, PyObject *); + +/// The special case of a switchable singleton Q*Application. +LIBSHIBOKEN_API PyObject *SbkQApp_tp_new(PyTypeObject *subtype, PyObject *, PyObject *); + +/// Create a new Q*Application wrapper and monitor it. +LIBSHIBOKEN_API PyObject *MakeQAppWrapper(PyTypeObject *type); + +/** + * PYSIDE-832: Use object_dealloc instead of nullptr. + * + * When moving to heaptypes, we were struck by a special default behavior of + * PyType_FromSpec that inserts subtype_dealloc when tp_dealloc is + * nullptr. But the default before conversion to heaptypes was to assign + * object_dealloc. This seems to be a bug in the Limited API. + */ +/// PYSIDE-939: Replaced by Sbk_object_dealloc. +LIBSHIBOKEN_API PyObject *SbkDummyNew(PyTypeObject *type, PyObject *, PyObject *); + +/// PYSIDE-74: Fallback used in all types now. +LIBSHIBOKEN_API PyObject *FallbackRichCompare(PyObject *self, PyObject *other, int op); + +/// PYSIDE-1970: Be easily able to see what is happening in the running code. +LIBSHIBOKEN_API void disassembleFrame(const char *marker); + +/// PYSIDE-2230: Check if an object is an SbkObject. +LIBSHIBOKEN_API bool SbkObjectType_Check(PyTypeObject *type); + +} // extern "C" + +namespace Shiboken +{ + +/** +* Init shiboken library. +*/ +LIBSHIBOKEN_API void init(); + +/// PYSIDE-1415: Publish Shiboken objects. +LIBSHIBOKEN_API void initShibokenSupport(PyObject *module); + +/// Delete the class T allocated on \p cptr. +template<typename T> +void callCppDestructor(void *cptr) +{ + delete reinterpret_cast<T *>(cptr); +} + +/// setErrorAboutWrongArguments now gets overload information from the signature module. +/// The extra info argument can contain additional data about the error. +LIBSHIBOKEN_API void setErrorAboutWrongArguments(PyObject *args, const char *funcName, + PyObject *info); + +/// Return values for the different retun variants. +/// This is used instead of goto. +LIBSHIBOKEN_API PyObject *returnWrongArguments(PyObject *args, const char *funcName, + PyObject *info); + +LIBSHIBOKEN_API int returnWrongArguments_Zero(PyObject *args, const char *funcName, + PyObject *info); + +LIBSHIBOKEN_API int returnWrongArguments_MinusOne(PyObject *args, const char *funcName, + PyObject *info); + +/// A simple special version for the end of rich comparison. +LIBSHIBOKEN_API PyObject *returnFromRichCompare(PyObject *result); + +// Return error information object if the argument count is wrong +LIBSHIBOKEN_API PyObject *checkInvalidArgumentCount(Py_ssize_t numArgs, + Py_ssize_t minArgs, + Py_ssize_t maxArgs); + +namespace ObjectType { + +/** +* Returns true if the object is an instance of a type created by the Shiboken generator. +*/ +LIBSHIBOKEN_API bool checkType(PyTypeObject *pyObj); + +/** +* Returns true if this object is an instance of an user defined type derived from an Shiboken type. +*/ +LIBSHIBOKEN_API bool isUserType(PyTypeObject *pyObj); + +/** +* Returns true if the constructor of \p ctorType can be called for a instance of type \p myType. +* \note This function set a python error when returning false. +*/ +LIBSHIBOKEN_API bool canCallConstructor(PyTypeObject *myType, PyTypeObject *ctorType); + +/** + * Tells if the \p type represents an object of a class with multiple inheritance in C++. + * When this occurs, the C++ pointer held by the Python wrapper will need to be cast when + * passed as a parameter that expects a type of its ancestry. + * \returns true if a call to ObjectType::cast() is needed to obtain the correct + * C++ pointer for Python objects of type \p type. + */ +LIBSHIBOKEN_API bool hasCast(PyTypeObject *type); +/** + * Cast the C++ pointer held by a Python object \p obj of type \p sourceType, + * to a C++ pointer of a C++ class indicated by type \p targetType. + * \returns The cast C++ pointer. + */ +LIBSHIBOKEN_API void *cast(PyTypeObject *sourceType, SbkObject *obj, PyTypeObject *targetType); +/// Set the C++ cast function for \p type. +LIBSHIBOKEN_API void setCastFunction(PyTypeObject *type, SpecialCastFunction func); + +LIBSHIBOKEN_API void setOriginalName(PyTypeObject *self, const char *name); +LIBSHIBOKEN_API const char *getOriginalName(PyTypeObject *self); + +LIBSHIBOKEN_API void setTypeDiscoveryFunctionV2(PyTypeObject *self, TypeDiscoveryFuncV2 func); +LIBSHIBOKEN_API void copyMultipleInheritance(PyTypeObject *self, PyTypeObject *other); +LIBSHIBOKEN_API void setMultipleInheritanceFunction(PyTypeObject *self, MultipleInheritanceInitFunction func); +LIBSHIBOKEN_API MultipleInheritanceInitFunction getMultipleInheritanceFunction(PyTypeObject *type); + +LIBSHIBOKEN_API void setDestructorFunction(PyTypeObject *self, ObjectDestructor func); + +enum WrapperFlags +{ + InnerClass = 0x1, + DeleteInMainThread = 0x2, + Value = 0x4 +}; + +/** + * Initializes a Shiboken wrapper type and adds it to the module, + * or to the enclosing class if the type is an inner class. + * This function also calls setDestructorFunction. + * \param enclosingObject The module or enclosing class to where the new \p type will be added. + * \param typeName Name by which the type will be known in Python. + * \param originalName Original C++ name of the type. + * \param type The new type to be initialized and added to the module. + * \param cppObjDtor Memory deallocation function for the C++ object held by \p type. + * Should not be used if the underlying C++ class has a private destructor. + * \param baseType Base type from whom the new \p type inherits. + * \param baseTypes Other base types from whom the new \p type inherits. + * \param isInnerClass Tells if the new \p type is an inner class (the default is that it isn't). + * If false then the \p enclosingObject is a module, otherwise it is another + * wrapper type. + * \returns true if the initialization went fine, false otherwise. + */ +LIBSHIBOKEN_API PyTypeObject *introduceWrapperType(PyObject *enclosingObject, + const char *typeName, + const char *originalName, + PyType_Spec *typeSpec, + ObjectDestructor cppObjDtor, + PyObject *bases, + unsigned wrapperFlags = 0); + +/** + * Set the subtype init hook for a type. + * + * This hook will be invoked every time the user creates a sub-type inherited from a Shiboken based type. + * The hook gets 3 params, they are: The new type being created, args and kwds. The last two are the very + * same got from tp_new. + */ +LIBSHIBOKEN_API void setSubTypeInitHook(PyTypeObject *self, SubTypeInitHook func); + +/** + * Get the user data previously set by Shiboken::Object::setTypeUserData + */ +LIBSHIBOKEN_API void *getTypeUserData(PyTypeObject *self); +LIBSHIBOKEN_API void setTypeUserData(PyTypeObject *self, void *userData, DeleteUserDataFunc d_func); + +/** + * Return an instance of PyTypeObject for a C++ type name as determined by + * typeinfo().name(). + * \param typeName Type name + * \since 5.12 + */ +LIBSHIBOKEN_API PyTypeObject *typeForTypeName(const char *typeName); + +/** + * Returns whether PyTypeObject has a special cast function (multiple inheritance) + * \param sbkType Sbk type + * \since 5.12 + */ +LIBSHIBOKEN_API bool hasSpecialCastFunction(PyTypeObject *sbkType); + +/// Returns whether a C++ pointer of \p baseType can be safely downcast +/// to \p targetType (base is a direct, single line base class of targetType). +/// (is a direct, single-line inheritance) +/// \param baseType Python type of base class +/// \param targetType Python type of derived class +/// \since 6.8 +LIBSHIBOKEN_API bool canDowncastTo(PyTypeObject *baseType, PyTypeObject *targetType); +} + +namespace Object { + +/** + * Returns a string with information about the internal state of the instance object, useful for debug purposes. + */ +LIBSHIBOKEN_API std::string info(SbkObject *self); + +/** +* Returns true if the object is an instance of a type created by the Shiboken generator. +*/ +LIBSHIBOKEN_API bool checkType(PyObject *pyObj); + +/** + * Returns true if this object type is an instance of an user defined type derived from an Shiboken type. + * \see Shiboken::ObjectType::isUserType + */ +LIBSHIBOKEN_API bool isUserType(PyObject *pyObj); + +/** + * Generic function used to make ObjectType hashable, the C++ pointer is used as hash value. + */ +LIBSHIBOKEN_API Py_hash_t hash(PyObject *pyObj); + +/** + * Find a child of given wrapper having same address having the specified type. + */ +LIBSHIBOKEN_API SbkObject *findColocatedChild(SbkObject *wrapper, + const PyTypeObject *instanceType); + +/** + * Bind a C++ object to Python. Forwards to + * newObjectWithHeuristics(), newObjectForType() depending on \p isExactType. + * \param instanceType equivalent Python type for the C++ object. + * \param hasOwnership if true, Python will try to delete the underlying C++ object when there's no more refs. + * \param isExactType if false, Shiboken will use some heuristics to detect the correct Python type of this C++ + * object, in any case you must provide \p instanceType, it'll be used as search starting point + * and as fallback. + * \param typeName If non-null, this will be used as helper to find the correct Python type for this object. + */ +LIBSHIBOKEN_API PyObject *newObject(PyTypeObject *instanceType, + void *cptr, + bool hasOwnership = true, + bool isExactType = false, + const char *typeName = nullptr); + +/// Bind a C++ object to Python for polymorphic pointers. Calls +/// newObjectWithHeuristics() with an additional check for multiple +/// inheritance, in which case it will fall back to instanceType. +/// \param instanceType Equivalent Python type for the C++ object. +/// \param hasOwnership if true, Python will try to delete the underlying C++ object +/// when there's no more refs. +/// \param typeName If non-null, this will be used as helper to find the correct +/// Python type for this object (obtained by typeid().name(). +LIBSHIBOKEN_API PyObject *newObjectForPointer(PyTypeObject *instanceType, + void *cptr, + bool hasOwnership = true, + const char *typeName = nullptr); + +/// Bind a C++ object to Python using some heuristics to detect the correct +/// Python type of this C++ object. In any case \p instanceType must be provided; +/// it'll be used as search starting point and as fallback. +/// \param instanceType Equivalent Python type for the C++ object. +/// \param hasOwnership if true, Python will try to delete the underlying C++ object +/// C++ object when there are no more references. +/// when there's no more refs. +/// \param typeName If non-null, this will be used as helper to find the correct +/// Python type for this object (obtained by typeid().name(). +LIBSHIBOKEN_API PyObject *newObjectWithHeuristics(PyTypeObject *instanceType, + void *cptr, + bool hasOwnership = true, + const char *typeName = nullptr); + +/// Bind a C++ object to Python using the given type. +/// \param instanceType Equivalent Python type for the C++ object. +/// \param hasOwnership if true, Python will try to delete the underlying +/// C++ object when there are no more references. +LIBSHIBOKEN_API PyObject *newObjectForType(PyTypeObject *instanceType, + void *cptr, bool hasOwnership = true); + +/** + * Changes the valid flag of a PyObject, invalid objects will raise an exception when someone tries to access it. + */ +LIBSHIBOKEN_API void setValidCpp(SbkObject *pyObj, bool value); +/** + * Tells shiboken the Python object \p pyObj has a C++ wrapper used to intercept virtual method calls. + */ +LIBSHIBOKEN_API void setHasCppWrapper(SbkObject *pyObj, bool value); +/** + * Return true if the Python object \p pyObj has a C++ wrapper used to intercept virtual method calls. + */ +LIBSHIBOKEN_API bool hasCppWrapper(SbkObject *pyObj); + +/** + * Return true if the Python object was created by Python, false otherwise. + * \note This function was added to libshiboken only to be used by shiboken.wasCreatedByPython() + */ +LIBSHIBOKEN_API bool wasCreatedByPython(SbkObject *pyObj); + +/** + * Call the C++ object destructor and invalidates the Python object. + * \note This function was added to libshiboken only to be used by shiboken.delete() + */ +LIBSHIBOKEN_API void callCppDestructors(SbkObject *pyObj); + +/** + * Return true if the Python is responsible for deleting the underlying C++ object. + */ +LIBSHIBOKEN_API bool hasOwnership(SbkObject *pyObj); + +/** + * Sets python as responsible to delete the underlying C++ object. + * \note You this overload only when the PyObject can be a sequence and you want to + * call this function for every item in the sequence. + * \see getOwnership(SbkObject *) + */ +LIBSHIBOKEN_API void getOwnership(PyObject *pyObj); + +/** + * Sets python as responsible to delete the underlying C++ object. + */ +LIBSHIBOKEN_API void getOwnership(SbkObject *pyObj); + +/** + * Release the ownership, so Python will not delete the underlying C++ object. + * \note You this overload only when the PyObject can be a sequence and you want to + * call this function for every item in the sequence. + * \see releaseOwnership(SbkObject *) + */ +LIBSHIBOKEN_API void releaseOwnership(PyObject *pyObj); +/** + * Release the ownership, so Python will not delete the underlying C++ object. + */ +LIBSHIBOKEN_API void releaseOwnership(SbkObject *pyObj); + +/** + * Get the C++ pointer of type \p desiredType from a Python object. + */ +LIBSHIBOKEN_API void *cppPointer(SbkObject *pyObj, PyTypeObject *desiredType); + +/** + * Return a list with all C++ pointers held from a Python object. + * \note This function was added to libshiboken only to be used by shiboken.getCppPointer() + */ +LIBSHIBOKEN_API std::vector<void *>cppPointers(SbkObject *pyObj); + +/** + * Set the C++ pointer of type \p desiredType of a Python object. + */ +LIBSHIBOKEN_API bool setCppPointer(SbkObject *sbkObj, PyTypeObject *desiredType, void *cptr); + +/** + * Returns false and sets a Python RuntimeError if the Python wrapper is not marked as valid. + */ +LIBSHIBOKEN_API bool isValid(PyObject *pyObj); + +/** + * Returns false if the Python wrapper is not marked as valid. + * \param pyObj the object. + * \param throwPyError sets a Python RuntimeError when the object isn't valid. + */ +LIBSHIBOKEN_API bool isValid(SbkObject *pyObj, bool throwPyError = true); + +/** + * Returns false if the Python wrapper is not marked as valid. + * \param pyObj the object. + * \param throwPyError sets a Python RuntimeError when the object isn't valid. + */ +LIBSHIBOKEN_API bool isValid(PyObject *pyObj, bool throwPyError); + +/** +* Set the parent of \p child to \p parent. +* When an object dies, all their children, grandchildren, etc, are tagged as invalid. +* \param parent the parent object, if null, the child will have no parents. +* \param child the child. +*/ +LIBSHIBOKEN_API void setParent(PyObject *parent, PyObject *child); + +/** +* Remove this child from their parent, if any. +* \param child the child. +*/ +LIBSHIBOKEN_API void removeParent(SbkObject *child, bool giveOwnershipBack = true, bool keepReferenc = false); + +/** + * Mark the object as invalid + */ +LIBSHIBOKEN_API void invalidate(SbkObject *self); + +/** + * Help function can be used to invalidate a sequence of object + **/ +LIBSHIBOKEN_API void invalidate(PyObject *pyobj); + +/** + * Make the object valid again + */ +LIBSHIBOKEN_API void makeValid(SbkObject *self); + +/** + * Destroy any data in Shiboken structure and c++ pointer if the pyboject has the ownership + */ +LIBSHIBOKEN_API void destroy(SbkObject *self, void *cppData); + +/** + * Set user data on type of \p wrapper. + * \param wrapper instance object, the user data will be set on his type + * \param userData the user data + * \param d_func a function used to delete the user data + */ +LIBSHIBOKEN_API void setTypeUserData(SbkObject *wrapper, void *userData, DeleteUserDataFunc d_func); +/** + * Get the user data previously set by Shiboken::Object::setTypeUserData + */ +LIBSHIBOKEN_API void *getTypeUserData(SbkObject *wrapper); + +/** + * Increments the reference count of the referred Python object. + * A previous Python object in the same position identified by the 'key' parameter + * will have its reference counter decremented automatically when replaced. + * All the kept references should be decremented when the Python wrapper indicated by + * 'self' dies. + * No checking is done for any of the passed arguments, since it is meant to be used + * by generated code it is supposed that the generator is correct. + * \param self the wrapper instance that keeps references to other objects. + * \param key a key that identifies the C++ method signature and argument where the referred Object came from. + * \param referredObject the object whose reference is used by the self object. + */ +LIBSHIBOKEN_API void keepReference(SbkObject *self, const char *key, PyObject *referredObject, bool append = false); + +/** + * Removes any reference previously added by keepReference function + * \param self the wrapper instance that keeps references to other objects. + * \param key a key that identifies the C++ method signature and argument from where the referred Object came. + * \param referredObject the object whose reference is used by the self object. + */ +LIBSHIBOKEN_API void removeReference(SbkObject *self, const char *key, PyObject *referredObject); + +} // namespace Object + +} // namespace Shiboken + +#endif // BASEWRAPPER_H diff --git a/sources/shiboken6/libshiboken/basewrapper_p.h b/sources/shiboken6/libshiboken/basewrapper_p.h new file mode 100644 index 000000000..fb9140793 --- /dev/null +++ b/sources/shiboken6/libshiboken/basewrapper_p.h @@ -0,0 +1,167 @@ +// 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 + +#ifndef BASEWRAPPER_P_H +#define BASEWRAPPER_P_H + +#include "sbkpython.h" +#include "basewrapper.h" + +#include <unordered_map> +#include <set> +#include <string> +#include <vector> +#include <iosfwd> + +struct SbkObject; +struct SbkConverter; + +namespace Shiboken +{ +/** + * This mapping associates a method and argument of an wrapper object with the wrapper of + * said argument when it needs the binding to help manage its reference count. + */ +using RefCountMap = std::unordered_multimap<std::string, PyObject *> ; + +/// Linked list of SbkBaseWrapper pointers +using ChildrenList = std::set<SbkObject *>; + +/// Structure used to store information about object parent and children. +struct ParentInfo +{ + /// Pointer to parent object. + SbkObject *parent = nullptr; + /// List of object children. + ChildrenList children; + /// has internal ref + bool hasWrapperRef = false; +}; + +} // namespace Shiboken + +extern "C" +{ + +/** + * \internal + * Private data for SbkBaseWrapper + */ +struct SbkObjectPrivate +{ + SbkObjectPrivate() noexcept = default; + SbkObjectPrivate(const SbkObjectPrivate &) = delete; + SbkObjectPrivate(SbkObjectPrivate &&o) = delete; + SbkObjectPrivate &operator=(const SbkObjectPrivate &) = delete; + SbkObjectPrivate &operator=(SbkObjectPrivate &&o) = delete; + + /// Pointer to the C++ class. + void ** cptr; + /// True when Python is responsible for freeing the used memory. + unsigned int hasOwnership : 1; + /// This is true when the C++ class of the wrapped object has a virtual destructor AND was created by Python. + unsigned int containsCppWrapper : 1; + /// Marked as false when the object is lost to C++ and the binding can not know if it was deleted or not. + unsigned int validCppObject : 1; + /// Marked as true when the object constructor was called + unsigned int cppObjectCreated : 1; + /// PYSIDE-1470: Marked as true if this is the Q*Application singleton. + /// This bit allows app deletion from shiboken?.delete() . + unsigned int isQAppSingleton : 1; + /// Information about the object parents and children, may be null. + Shiboken::ParentInfo *parentInfo; + /// Manage reference count of objects that are referred to but not owned from. + Shiboken::RefCountMap *referredObjects; + + ~SbkObjectPrivate() + { + delete parentInfo; + parentInfo = nullptr; + delete referredObjects; + referredObjects = nullptr; + } +}; + +// TODO-CONVERTERS: to be deprecated/removed +/// The type behaviour was not defined yet +#define BEHAVIOUR_UNDEFINED 0 +/// The type is a value type +#define BEHAVIOUR_VALUETYPE 1 +/// The type is an object type +#define BEHAVIOUR_OBJECTTYPE 2 + +struct SbkObjectTypePrivate +{ + SbkConverter *converter; + int *mi_offsets; + MultipleInheritanceInitFunction mi_init; + + /// Special cast function, null if this class doesn't have multiple inheritance. + SpecialCastFunction mi_specialcast; + TypeDiscoveryFuncV2 type_discovery; + /// Pointer to a function responsible for deletion of the C++ instance calling the proper destructor. + ObjectDestructor cpp_dtor; + /// C++ name + char *original_name; + /// Type user data + void *user_data; + DeleteUserDataFunc d_func; + void (*subtype_init)(PyTypeObject *, PyObject *, PyObject *); + const char **propertyStrings; + const char **enumFlagInfo; + PyObject *enumFlagsDict; + PyObject *enumTypeDict; + + /// True if this type holds two or more C++ instances, e.g.: a Python class which inherits from two C++ classes. + unsigned int is_multicpp : 1; + /// True if this type was defined by the user (a class written in Python inheriting + /// a class provided by a Shiboken binding). + unsigned int is_user_type : 1; + /// Tells is the type is a value type or an object-type, see BEHAVIOUR_ *constants. + unsigned int type_behaviour : 2; + unsigned int delete_in_main_thread : 1; +}; + + +} // extern "C" + +namespace Shiboken +{ + +/** + * \internal + * Data required to invoke a C++ destructor + */ +struct DestructorEntry +{ + ObjectDestructor destructor; + void *cppInstance; +}; + +/** + * Utility function used to transform a PyObject that implements sequence protocol into a std::list. + **/ +std::vector<SbkObject *> splitPyObject(PyObject *pyObj); + +int getNumberOfCppBaseClasses(PyTypeObject *baseType); + +namespace Object +{ +/** +* Decrements the reference counters of every object referred by self. +* \param self the wrapper instance that keeps references to other objects. +*/ +void clearReferences(SbkObject *self); + +/** + * Destroy internal data + **/ +void deallocData(SbkObject *self, bool doCleanup); + + +void _debugFormat(std::ostream &str, SbkObject *self); +} // namespace Object + +} // namespace Shiboken + +#endif diff --git a/sources/shiboken6/libshiboken/bindingmanager.cpp b/sources/shiboken6/libshiboken/bindingmanager.cpp new file mode 100644 index 000000000..83c927ae5 --- /dev/null +++ b/sources/shiboken6/libshiboken/bindingmanager.cpp @@ -0,0 +1,549 @@ +// 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 "autodecref.h" +#include "basewrapper.h" +#include "basewrapper_p.h" +#include "bindingmanager.h" +#include "gilstate.h" +#include "helper.h" +#include "sbkmodule.h" +#include "sbkstring.h" +#include "sbkstaticstrings.h" +#include "sbkfeature_base.h" +#include "debugfreehook.h" + +#include <cstddef> +#include <cstring> +#include <fstream> +#include <iostream> +#include <mutex> +#include <string_view> +#include <unordered_map> +#include <unordered_set> + +// GraphNode for the dependency graph. It keeps a pointer to +// the TypeInitStruct to be able to lazily create the type and hashes +// by the full type name. +struct GraphNode +{ + explicit GraphNode(Shiboken::Module::TypeInitStruct *i) : name(i->fullName), initStruct(i) {} + explicit GraphNode(const char *n) : name(n), initStruct(nullptr) {} // Only for searching + + std::string_view name; + Shiboken::Module::TypeInitStruct *initStruct; + + friend bool operator==(const GraphNode &n1, const GraphNode &n2) { return n1.name == n2.name; } + friend bool operator!=(const GraphNode &n1, const GraphNode &n2) { return n1.name != n2.name; } +}; + +template <> +struct std::hash<GraphNode> { + size_t operator()(const GraphNode &n) const noexcept + { + return std::hash<std::string_view>{}(n.name); + } +}; + +namespace Shiboken +{ + +using WrapperMap = std::unordered_map<const void *, SbkObject *>; + +template <class NodeType> +class BaseGraph +{ +public: + using NodeList = std::vector<NodeType>; + using NodeSet = std::unordered_set<NodeType>; + + using Edges = std::unordered_map<NodeType, NodeList>; + + Edges m_edges; + + BaseGraph() = default; + + void addEdge(NodeType from, NodeType to) + { + m_edges[from].push_back(to); + } + + NodeSet nodeSet() const + { + NodeSet result; + for (const auto &p : m_edges) { + result.insert(p.first); + for (const auto node2 : p.second) + result.insert(node2); + } + return result; + } +}; + +class Graph : public BaseGraph<GraphNode> +{ +public: + using TypeCptrPair = BindingManager::TypeCptrPair; + + TypeCptrPair identifyType(void *cptr, PyTypeObject *type, PyTypeObject *baseType) const + { + return identifyType(cptr, GraphNode(type->tp_name), type, baseType); + } + + bool dumpTypeGraph(const char *fileName) const; + +private: + TypeCptrPair identifyType(void *cptr, const GraphNode &typeNode, PyTypeObject *type, + PyTypeObject *baseType) const; +}; + +Graph::TypeCptrPair Graph::identifyType(void *cptr, + const GraphNode &typeNode, PyTypeObject *type, + PyTypeObject *baseType) const +{ + assert(typeNode.initStruct != nullptr || type != nullptr); + auto edgesIt = m_edges.find(typeNode); + if (edgesIt != m_edges.end()) { + const NodeList &adjNodes = edgesIt->second; + for (const auto &node : adjNodes) { + auto newType = identifyType(cptr, node, nullptr, baseType); + if (newType.first != nullptr) + return newType; + } + } + + if (type == nullptr) { + if (typeNode.initStruct->type == nullptr) // Layzily create type + type = Shiboken::Module::get(*typeNode.initStruct); + else + type = typeNode.initStruct->type; + } + + auto *sotp = PepType_SOTP(type); + if (sotp->type_discovery != nullptr) { + if (void *derivedCPtr = sotp->type_discovery(cptr, baseType)) + return {type, derivedCPtr}; + } + return {nullptr, nullptr}; +} + +static void formatDotNode(std::string_view name, std::ostream &file) +{ + auto lastDot = name.rfind('.'); + file << " \"" << name << "\" [ label="; + if (lastDot != std::string::npos) { + file << '"' << name.substr(lastDot + 1) << "\" tooltip=\"" + << name.substr(0, lastDot) << '"'; + } else { + file << '"' << name << '"'; + } + file << " ]\n"; +} + +bool Graph::dumpTypeGraph(const char *fileName) const +{ + std::ofstream file(fileName); + if (!file.good()) + return false; + + file << "digraph D {\n"; + + // Define nodes with short names + for (const auto &node : nodeSet()) + formatDotNode(node.name, file); + + // Write edges + for (const auto &p : m_edges) { + const auto &node1 = p.first; + const NodeList &nodeList = p.second; + for (const auto &node2 : nodeList) + file << " \"" << node2.name << "\" -> \"" << node1.name << "\"\n"; + } + file << "}\n"; + return true; +} + +struct BindingManager::BindingManagerPrivate { + using DestructorEntries = std::vector<DestructorEntry>; + + WrapperMap wrapperMapper; + // Guard wrapperMapper mainly for QML which calls into the generated + // QObject::metaObject() and elsewhere from threads without GIL, causing + // crashes for example in retrieveWrapper(). std::shared_mutex was rejected due to: + // https://stackoverflow.com/questions/50972345/when-is-stdshared-timed-mutex-slower-than-stdmutex-and-when-not-to-use-it + std::recursive_mutex wrapperMapLock; + Graph classHierarchy; + DestructorEntries deleteInMainThread; + + bool releaseWrapper(void *cptr, SbkObject *wrapper, const int *bases = nullptr); + bool releaseWrapperHelper(void *cptr, SbkObject *wrapper); + + void assignWrapper(SbkObject *wrapper, const void *cptr, const int *bases = nullptr); + void assignWrapperHelper(SbkObject *wrapper, const void *cptr); +}; + +inline bool BindingManager::BindingManagerPrivate::releaseWrapperHelper(void *cptr, SbkObject *wrapper) +{ + // The wrapper argument is checked to ensure that the correct wrapper is released. + // Returns true if the correct wrapper is found and released. + // If wrapper argument is NULL, no such check is performed. + auto iter = wrapperMapper.find(cptr); + if (iter != wrapperMapper.end() && (wrapper == nullptr || iter->second == wrapper)) { + wrapperMapper.erase(iter); + return true; + } + return false; +} + +bool BindingManager::BindingManagerPrivate::releaseWrapper(void *cptr, SbkObject *wrapper, + const int *bases) +{ + assert(cptr); + std::lock_guard<std::recursive_mutex> guard(wrapperMapLock); + const bool result = releaseWrapperHelper(cptr, wrapper); + if (bases != nullptr) { + auto *base = static_cast<uint8_t *>(cptr); + for (const auto *offset = bases; *offset != -1; ++offset) + releaseWrapperHelper(base + *offset, wrapper); + } + return result; +} + +inline void BindingManager::BindingManagerPrivate::assignWrapperHelper(SbkObject *wrapper, + const void *cptr) +{ + auto iter = wrapperMapper.find(cptr); + if (iter == wrapperMapper.end()) + wrapperMapper.insert(std::make_pair(cptr, wrapper)); +} + +void BindingManager::BindingManagerPrivate::assignWrapper(SbkObject *wrapper, const void *cptr, + const int *bases) +{ + assert(cptr); + std::lock_guard<std::recursive_mutex> guard(wrapperMapLock); + assignWrapperHelper(wrapper, cptr); + if (bases != nullptr) { + const auto *base = static_cast<const uint8_t *>(cptr); + for (const auto *offset = bases; *offset != -1; ++offset) + assignWrapperHelper(wrapper, base + *offset); + } +} + +BindingManager::BindingManager() +{ + m_d = new BindingManager::BindingManagerPrivate; + +#ifdef SHIBOKEN_INSTALL_FREE_DEBUG_HOOK + debugInstallFreeHook(); +#endif +} + +BindingManager::~BindingManager() +{ +#ifdef SHIBOKEN_INSTALL_FREE_DEBUG_HOOK + debugRemoveFreeHook(); +#endif +#ifndef NDEBUG + if (Shiboken::pyVerbose() > 0) + dumpWrapperMap(); +#endif + /* Cleanup hanging references. We just invalidate them as when + * the BindingManager is being destroyed the interpreter is alredy + * shutting down. */ + if (Py_IsInitialized()) { // ensure the interpreter is still valid + std::lock_guard<std::recursive_mutex> guard(m_d->wrapperMapLock); + while (!m_d->wrapperMapper.empty()) { + Object::destroy(m_d->wrapperMapper.begin()->second, const_cast<void *>(m_d->wrapperMapper.begin()->first)); + } + assert(m_d->wrapperMapper.empty()); + } + delete m_d; +} + +BindingManager &BindingManager::instance() { + static BindingManager singleton; + return singleton; +} + +bool BindingManager::hasWrapper(const void *cptr) +{ + std::lock_guard<std::recursive_mutex> guard(m_d->wrapperMapLock); + return m_d->wrapperMapper.find(cptr) != m_d->wrapperMapper.end(); +} + +void BindingManager::registerWrapper(SbkObject *pyObj, void *cptr) +{ + auto *instanceType = Py_TYPE(pyObj); + auto *d = PepType_SOTP(instanceType); + + if (!d) + return; + + if (d->mi_init && !d->mi_offsets) + d->mi_offsets = d->mi_init(cptr); + m_d->assignWrapper(pyObj, cptr, d->mi_offsets); +} + +void BindingManager::releaseWrapper(SbkObject *sbkObj) +{ + auto *sbkType = Py_TYPE(sbkObj); + auto *d = PepType_SOTP(sbkType); + int numBases = ((d && d->is_multicpp) ? getNumberOfCppBaseClasses(Py_TYPE(sbkObj)) : 1); + + void ** cptrs = reinterpret_cast<SbkObject *>(sbkObj)->d->cptr; + const int *mi_offsets = d != nullptr ? d->mi_offsets : nullptr; + for (int i = 0; i < numBases; ++i) { + if (cptrs[i] != nullptr) + m_d->releaseWrapper(cptrs[i], sbkObj, mi_offsets); + } + sbkObj->d->validCppObject = false; +} + +void BindingManager::runDeletionInMainThread() +{ + for (const DestructorEntry &e : m_d->deleteInMainThread) + e.destructor(e.cppInstance); + m_d->deleteInMainThread.clear(); +} + +void BindingManager::addToDeletionInMainThread(const DestructorEntry &e) +{ + m_d->deleteInMainThread.push_back(e); +} + +SbkObject *BindingManager::retrieveWrapper(const void *cptr) +{ + std::lock_guard<std::recursive_mutex> guard(m_d->wrapperMapLock); + auto iter = m_d->wrapperMapper.find(cptr); + if (iter == m_d->wrapperMapper.end()) + return nullptr; + return iter->second; +} + +PyObject *BindingManager::getOverride(const void *cptr, + PyObject *nameCache[], + const char *methodName) +{ + SbkObject *wrapper = retrieveWrapper(cptr); + // The refcount can be 0 if the object is dieing and someone called + // a virtual method from the destructor + if (!wrapper || Py_REFCNT(reinterpret_cast<const PyObject *>(wrapper)) == 0) + return nullptr; + + // PYSIDE-1626: Touch the type to initiate switching early. + SbkObjectType_UpdateFeature(Py_TYPE(wrapper)); + + int flag = currentSelectId(Py_TYPE(wrapper)); + int propFlag = isdigit(methodName[0]) ? methodName[0] - '0' : 0; + bool is_snake = flag & 0x01; + PyObject *pyMethodName = nameCache[is_snake]; // borrowed + if (pyMethodName == nullptr) { + if (propFlag) + methodName += 2; // skip the propFlag and ':' + pyMethodName = Shiboken::String::getSnakeCaseName(methodName, is_snake); + nameCache[is_snake] = pyMethodName; + } + + auto *obWrapper = reinterpret_cast<PyObject *>(wrapper); + auto *wrapper_dict = SbkObject_GetDict_NoRef(obWrapper); + if (PyObject *method = PyDict_GetItem(wrapper_dict, pyMethodName)) { + // Note: This special case was implemented for duck-punching, which happens + // in the instance dict. It does not work with properties. + Py_INCREF(method); + return method; + } + + PyObject *method = PyObject_GetAttr(reinterpret_cast<PyObject *>(wrapper), pyMethodName); + + PyObject *function = nullptr; + + // PYSIDE-1523: PyMethod_Check is not accepting compiled methods, we do this rather + // crude check for them. + if (method) { + // PYSIDE-535: This macro is redefined in a compatible way in pep384 + if (PyMethod_Check(method)) { + if (PyMethod_GET_SELF(method) == reinterpret_cast<PyObject *>(wrapper)) { + function = PyMethod_GET_FUNCTION(method); + } else { + Py_DECREF(method); + method = nullptr; + } + } else if (PyObject_HasAttr(method, PyName::im_self()) + && PyObject_HasAttr(method, PyName::im_func()) + && PyObject_HasAttr(method, Shiboken::PyMagicName::code())) { + PyObject *im_self = PyObject_GetAttr(method, PyName::im_self()); + // Not retaining a reference inline with what PyMethod_GET_SELF does. + Py_DECREF(im_self); + + if (im_self == reinterpret_cast<PyObject *>(wrapper)) { + function = PyObject_GetAttr(method, PyName::im_func()); + // Not retaining a reference inline with what PyMethod_GET_FUNCTION does. + Py_DECREF(function); + } else { + Py_DECREF(method); + method = nullptr; + } + } else { + Py_DECREF(method); + method = nullptr; + } + } + + if (method != nullptr) { + PyObject *defaultMethod{}; + PyObject *mro = Py_TYPE(wrapper)->tp_mro; + + int size = PyTuple_GET_SIZE(mro); + bool defaultFound = false; + // The first class in the mro (index 0) is the class being checked and it should not be tested. + // The last class in the mro (size - 1) is the base Python object class which should not be tested also. + for (int idx = 1; idx < size - 1; ++idx) { + auto *parent = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx)); + AutoDecRef tpDict(PepType_GetDict(parent)); + auto *parentDict = tpDict.object(); + if (parentDict) { + defaultMethod = PyDict_GetItem(parentDict, pyMethodName); + if (defaultMethod) { + defaultFound = true; + if (function != defaultMethod) + return method; + } + } + } + // PYSIDE-2255: If no default method was found, use the method. + if (!defaultFound) + return method; + Py_DECREF(method); + } + + return nullptr; +} + +void BindingManager::addClassInheritance(Module::TypeInitStruct *parent, + Module::TypeInitStruct *child) +{ + m_d->classHierarchy.addEdge(GraphNode(parent), GraphNode(child)); +} + +BindingManager::TypeCptrPair BindingManager::findDerivedType(void *cptr, PyTypeObject *type) const +{ + return m_d->classHierarchy.identifyType(cptr, type, type); +} + +// FIXME PYSIDE7: remove, just for compatibility +PyTypeObject *BindingManager::resolveType(void **cptr, PyTypeObject *type) +{ + auto result = findDerivedType(*cptr, type); + if (result.second != nullptr) + *cptr = result.second; + return result.first != nullptr ? result.first : type; +} + +std::set<PyObject *> BindingManager::getAllPyObjects() +{ + std::set<PyObject *> pyObjects; + std::lock_guard<std::recursive_mutex> guard(m_d->wrapperMapLock); + const WrapperMap &wrappersMap = m_d->wrapperMapper; + auto it = wrappersMap.begin(); + for (; it != wrappersMap.end(); ++it) + pyObjects.insert(reinterpret_cast<PyObject *>(it->second)); + + return pyObjects; +} + +void BindingManager::visitAllPyObjects(ObjectVisitor visitor, void *data) +{ + WrapperMap copy = m_d->wrapperMapper; + for (const auto &p : copy) { + if (hasWrapper(p.first)) + visitor(p.second, data); + } +} + +bool BindingManager::dumpTypeGraph(const char *fileName) const +{ + return m_d->classHierarchy.dumpTypeGraph(fileName); +} + +void BindingManager::dumpWrapperMap() +{ + const auto &wrapperMap = m_d->wrapperMapper; + std::cerr << "-------------------------------\n" + << "WrapperMap size: " << wrapperMap.size() << " Types: " + << m_d->classHierarchy.nodeSet().size() << '\n'; + for (auto it = wrapperMap.begin(), end = wrapperMap.end(); it != end; ++it) { + const SbkObject *sbkObj = it->second; + std::cerr << "key: " << it->first << ", value: " + << static_cast<const void *>(sbkObj) << " (" + << (Py_TYPE(sbkObj))->tp_name << ", refcnt: " + << Py_REFCNT(reinterpret_cast<const PyObject *>(sbkObj)) << ")\n"; + } + std::cerr << "-------------------------------\n"; +} + +static bool isPythonType(PyTypeObject *type) +{ + // This is a type which should be called by multiple inheritance. + // It is either a pure Python type or a derived PySide type. + return !ObjectType::checkType(type) || ObjectType::isUserType(type); +} + +bool callInheritedInit(PyObject *self, PyObject *args, PyObject *kwds, + const char *fullName) +{ + using Shiboken::AutoDecRef; + + static PyObject *const _init = String::createStaticString("__init__"); + static PyObject *objectInit = + PyObject_GetAttr(reinterpret_cast<PyObject *>(&PyBaseObject_Type), _init); + + // A native C++ self cannot have multiple inheritance. + if (!Object::isUserType(self)) + return false; + + auto *startType = Py_TYPE(self); + auto *mro = startType->tp_mro; + Py_ssize_t idx, n = PyTuple_GET_SIZE(mro); + auto classNameLen = std::strrchr(fullName, '.') - fullName; + /* No need to check the last one: it's gonna be skipped anyway. */ + for (idx = 0; idx + 1 < n; ++idx) { + auto *lookType = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx)); + const char *lookName = lookType->tp_name; + auto lookLen = long(std::strlen(lookName)); + if (std::strncmp(lookName, fullName, classNameLen) == 0 && lookLen == classNameLen) + break; + } + // We are now at the first non-Python class `QObject`. + // mro: ('C', 'A', 'QObject', 'Object', 'B', 'object') + // We want to catch class `B` and call its `__init__`. + for (idx += 1; idx + 1 < n; ++idx) { + auto *t = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx)); + if (isPythonType(t)) + break; + } + if (idx >= n) + return false; + + auto *obSubType = PyTuple_GET_ITEM(mro, idx); + auto *subType = reinterpret_cast<PyTypeObject *>(obSubType); + if (subType == &PyBaseObject_Type) + return false; + AutoDecRef func(PyObject_GetAttr(obSubType, _init)); + // PYSIDE-2654: If this has no implementation then we get object.__init__ + // but that is the same case like above. + if (func == objectInit) + return false; + // PYSIDE-2294: We need to explicitly ignore positional args in a mixin class. + SBK_UNUSED(args); + AutoDecRef newArgs(PyTuple_New(1)); + auto *newArgsOb = newArgs.object(); + Py_INCREF(self); + PyTuple_SET_ITEM(newArgsOb, 0, self); + // Note: This can fail, so please always check the error status. + AutoDecRef result(PyObject_Call(func, newArgs, kwds)); + return true; +} + +} // namespace Shiboken + diff --git a/sources/shiboken6/libshiboken/bindingmanager.h b/sources/shiboken6/libshiboken/bindingmanager.h new file mode 100644 index 000000000..54c4e486a --- /dev/null +++ b/sources/shiboken6/libshiboken/bindingmanager.h @@ -0,0 +1,93 @@ +// 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 + +#ifndef BINDINGMANAGER_H +#define BINDINGMANAGER_H + +#include "sbkpython.h" +#include "shibokenmacros.h" + +#include <set> +#include <utility> + +struct SbkObject; + +namespace Shiboken +{ + +namespace Module { +struct TypeInitStruct; +} + +struct DestructorEntry; + +using ObjectVisitor = void (*)(SbkObject *, void *); + +class LIBSHIBOKEN_API BindingManager +{ +public: + BindingManager(const BindingManager &) = delete; + BindingManager(BindingManager &&) = delete; + BindingManager &operator=(const BindingManager &) = delete; + BindingManager &operator=(BindingManager &&) = delete; + + static BindingManager &instance(); + + bool hasWrapper(const void *cptr); + + void registerWrapper(SbkObject *pyObj, void *cptr); + void releaseWrapper(SbkObject *wrapper); + + void runDeletionInMainThread(); + void addToDeletionInMainThread(const DestructorEntry &); + + SbkObject *retrieveWrapper(const void *cptr); + PyObject *getOverride(const void *cptr, PyObject *nameCache[], const char *methodName); + + void addClassInheritance(Module::TypeInitStruct *parent, Module::TypeInitStruct *child); + /// Try to find the correct type of cptr via type discovery knowing that it's at least + /// of type \p type. If a derived class is found, it returns a cptr cast to the type + /// (which may be different in case of multiple inheritance. + /// \param cptr a pointer to the instance of type \p type + /// \param type type of cptr + using TypeCptrPair = std::pair<PyTypeObject *, void *>; + TypeCptrPair findDerivedType(void *cptr, PyTypeObject *type) const; + + /** + * Try to find the correct type of *cptr knowing that it's at least of type \p type. + * In case of multiple inheritance this function may change the contents of cptr. + * \param cptr a pointer to a pointer to the instance of type \p type + * \param type type of *cptr + * \warning This function is slow, use it only as last resort. + */ + [[deprecated]] PyTypeObject *resolveType(void **cptr, PyTypeObject *type); + + std::set<PyObject *> getAllPyObjects(); + + /** + * Calls the function \p visitor for each object registered on binding manager. + * \note As various C++ pointers can point to the same PyObject due to multiple inheritance + * a PyObject can be called more than one time for each PyObject. + * \param visitor function called for each object. + * \param data user data passed as second argument to the visitor function. + */ + void visitAllPyObjects(ObjectVisitor visitor, void *data); + + bool dumpTypeGraph(const char *fileName) const; + void dumpWrapperMap(); + +private: + ~BindingManager(); + BindingManager(); + + struct BindingManagerPrivate; + BindingManagerPrivate *m_d; +}; + +LIBSHIBOKEN_API bool callInheritedInit(PyObject *self, PyObject *args, PyObject *kwds, + const char *fullName); + +} // namespace Shiboken + +#endif // BINDINGMANAGER_H + diff --git a/sources/shiboken6/libshiboken/bufferprocs_py37.cpp b/sources/shiboken6/libshiboken/bufferprocs_py37.cpp new file mode 100644 index 000000000..4ccf970e5 --- /dev/null +++ b/sources/shiboken6/libshiboken/bufferprocs_py37.cpp @@ -0,0 +1,360 @@ +// Copyright (C) 2018 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 + +/***************************************************************************** + * + * Copied from abstract.c + * + * Py_buffer has been replaced by Pep_buffer + * + */ + +#ifdef Py_LIMITED_API + +#include "sbkpython.h" +/* Buffer C-API for Python 3.0 */ + +int +PyObject_GetBuffer(PyObject *obj, Pep_buffer *view, int flags) +{ + PyBufferProcs *pb = PepType_AS_BUFFER(Py_TYPE(obj)); + + if (pb == NULL || pb->bf_getbuffer == NULL) { + PyErr_Format(PyExc_TypeError, + "a bytes-like object is required, not '%.100s'", + Py_TYPE(obj)->tp_name); + return -1; + } + return (*pb->bf_getbuffer)(obj, view, flags); +} + +static int +_IsFortranContiguous(const Pep_buffer *view) +{ + Py_ssize_t sd, dim; + int i; + + /* 1) len = product(shape) * itemsize + 2) itemsize > 0 + 3) len = 0 <==> exists i: shape[i] = 0 */ + if (view->len == 0) return 1; + if (view->strides == NULL) { /* C-contiguous by definition */ + /* Trivially F-contiguous */ + if (view->ndim <= 1) return 1; + + /* ndim > 1 implies shape != NULL */ + assert(view->shape != NULL); + + /* Effectively 1-d */ + sd = 0; + for (i=0; i<view->ndim; i++) { + if (view->shape[i] > 1) sd += 1; + } + return sd <= 1; + } + + /* strides != NULL implies both of these */ + assert(view->ndim > 0); + assert(view->shape != NULL); + + sd = view->itemsize; + for (i=0; i<view->ndim; i++) { + dim = view->shape[i]; + if (dim > 1 && view->strides[i] != sd) { + return 0; + } + sd *= dim; + } + return 1; +} + +static int +_IsCContiguous(const Pep_buffer *view) +{ + Py_ssize_t sd, dim; + int i; + + /* 1) len = product(shape) * itemsize + 2) itemsize > 0 + 3) len = 0 <==> exists i: shape[i] = 0 */ + if (view->len == 0) return 1; + if (view->strides == NULL) return 1; /* C-contiguous by definition */ + + /* strides != NULL implies both of these */ + assert(view->ndim > 0); + assert(view->shape != NULL); + + sd = view->itemsize; + for (i=view->ndim-1; i>=0; i--) { + dim = view->shape[i]; + if (dim > 1 && view->strides[i] != sd) { + return 0; + } + sd *= dim; + } + return 1; +} + +int +PyBuffer_IsContiguous(const Pep_buffer *view, char order) +{ + + if (view->suboffsets != NULL) return 0; + + if (order == 'C') + return _IsCContiguous(view); + else if (order == 'F') + return _IsFortranContiguous(view); + else if (order == 'A') + return (_IsCContiguous(view) || _IsFortranContiguous(view)); + return 0; +} + + +void * +PyBuffer_GetPointer(Pep_buffer *view, Py_ssize_t *indices) +{ + int i; + auto pointer = reinterpret_cast<char *>(view->buf); + for (i = 0; i < view->ndim; i++) { + pointer += view->strides[i]*indices[i]; + if ((view->suboffsets != NULL) && (view->suboffsets[i] >= 0)) { + pointer = *reinterpret_cast<char **>(pointer) + view->suboffsets[i]; + } + } + return pointer; +} + + +void +_Py_add_one_to_index_F(int nd, Py_ssize_t *index, const Py_ssize_t *shape) +{ + int k; + + for (k=0; k<nd; k++) { + if (index[k] < shape[k]-1) { + index[k]++; + break; + } + else { + index[k] = 0; + } + } +} + +void +_Py_add_one_to_index_C(int nd, Py_ssize_t *index, const Py_ssize_t *shape) +{ + int k; + + for (k=nd-1; k>=0; k--) { + if (index[k] < shape[k]-1) { + index[k]++; + break; + } + else { + index[k] = 0; + } + } +} + +int +PyBuffer_FromContiguous(Pep_buffer *view, void *buf, Py_ssize_t len, char fort) +{ + int k; + void (*addone)(int, Py_ssize_t *, const Py_ssize_t *); + Py_ssize_t *indices, elements; + char *src, *ptr; + + if (len > view->len) { + len = view->len; + } + + if (PyBuffer_IsContiguous(view, fort)) { + /* simplest copy is all that is needed */ + memcpy(view->buf, buf, len); + return 0; + } + + /* Otherwise a more elaborate scheme is needed */ + + /* view->ndim <= 64 */ + indices = (Py_ssize_t *)PyMem_Malloc(sizeof(Py_ssize_t)*(view->ndim)); + if (indices == NULL) { + PyErr_NoMemory(); + return -1; + } + for (k=0; k<view->ndim; k++) { + indices[k] = 0; + } + + if (fort == 'F') { + addone = _Py_add_one_to_index_F; + } + else { + addone = _Py_add_one_to_index_C; + } + src = (char *)buf; // patched by CT + /* XXX : This is not going to be the fastest code in the world + several optimizations are possible. + */ + elements = len / view->itemsize; + while (elements--) { + ptr = (char *)PyBuffer_GetPointer(view, indices); // patched by CT + memcpy(ptr, src, view->itemsize); + src += view->itemsize; + addone(view->ndim, indices, view->shape); + } + + PyMem_Free(indices); + return 0; +} + +int PyObject_CopyData(PyObject *dest, PyObject *src) +{ + Pep_buffer view_dest, view_src; + int k; + Py_ssize_t *indices, elements; + char *dptr, *sptr; + + if (!PyObject_CheckBuffer(dest) || + !PyObject_CheckBuffer(src)) { + PyErr_SetString(PyExc_TypeError, + "both destination and source must be "\ + "bytes-like objects"); + return -1; + } + + if (PyObject_GetBuffer(dest, &view_dest, PyBUF_FULL) != 0) return -1; + if (PyObject_GetBuffer(src, &view_src, PyBUF_FULL_RO) != 0) { + PyBuffer_Release(&view_dest); + return -1; + } + + if (view_dest.len < view_src.len) { + PyErr_SetString(PyExc_BufferError, + "destination is too small to receive data from source"); + PyBuffer_Release(&view_dest); + PyBuffer_Release(&view_src); + return -1; + } + + if ((PyBuffer_IsContiguous(&view_dest, 'C') && + PyBuffer_IsContiguous(&view_src, 'C')) || + (PyBuffer_IsContiguous(&view_dest, 'F') && + PyBuffer_IsContiguous(&view_src, 'F'))) { + /* simplest copy is all that is needed */ + memcpy(view_dest.buf, view_src.buf, view_src.len); + PyBuffer_Release(&view_dest); + PyBuffer_Release(&view_src); + return 0; + } + + /* Otherwise a more elaborate copy scheme is needed */ + + /* XXX(nnorwitz): need to check for overflow! */ + indices = (Py_ssize_t *)PyMem_Malloc(sizeof(Py_ssize_t)*view_src.ndim); + if (indices == NULL) { + PyErr_NoMemory(); + PyBuffer_Release(&view_dest); + PyBuffer_Release(&view_src); + return -1; + } + for (k=0; k<view_src.ndim;k++) { + indices[k] = 0; + } + elements = 1; + for (k=0; k<view_src.ndim; k++) { + /* XXX(nnorwitz): can this overflow? */ + elements *= view_src.shape[k]; + } + while (elements--) { + _Py_add_one_to_index_C(view_src.ndim, indices, view_src.shape); + dptr = (char *)PyBuffer_GetPointer(&view_dest, indices); // patched by CT + sptr = (char *)PyBuffer_GetPointer(&view_src, indices); // patched by CT + memcpy(dptr, sptr, view_src.itemsize); + } + PyMem_Free(indices); + PyBuffer_Release(&view_dest); + PyBuffer_Release(&view_src); + return 0; +} + +void +PyBuffer_FillContiguousStrides(int nd, Py_ssize_t *shape, + Py_ssize_t *strides, int itemsize, + char fort) +{ + int k; + Py_ssize_t sd; + + sd = itemsize; + if (fort == 'F') { + for (k=0; k<nd; k++) { + strides[k] = sd; + sd *= shape[k]; + } + } + else { + for (k=nd-1; k>=0; k--) { + strides[k] = sd; + sd *= shape[k]; + } + } + return; +} + +int +PyBuffer_FillInfo(Pep_buffer *view, PyObject *obj, void *buf, Py_ssize_t len, + int readonly, int flags) +{ + if (view == NULL) { + PyErr_SetString(PyExc_BufferError, + "PyBuffer_FillInfo: view==NULL argument is obsolete"); + return -1; + } + + if (((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) && + (readonly == 1)) { + PyErr_SetString(PyExc_BufferError, + "Object is not writable."); + return -1; + } + + view->obj = obj; + if (obj) + Py_INCREF(obj); + view->buf = buf; + view->len = len; + view->readonly = readonly; + view->itemsize = 1; + view->format = NULL; + if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) + view->format = (char *)"B"; // patched by CT + view->ndim = 1; + view->shape = NULL; + if ((flags & PyBUF_ND) == PyBUF_ND) + view->shape = &(view->len); + view->strides = NULL; + if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) + view->strides = &(view->itemsize); + view->suboffsets = NULL; + view->internal = NULL; + return 0; +} + +void +PyBuffer_Release(Pep_buffer *view) +{ + PyObject *obj = view->obj; + PyBufferProcs *pb; + if (obj == NULL) + return; + pb = PepType_AS_BUFFER(Py_TYPE(obj)); + if (pb && pb->bf_releasebuffer) + pb->bf_releasebuffer(obj, view); + view->obj = NULL; + Py_DECREF(obj); +} + +#endif // Py_LIMITED_API diff --git a/sources/shiboken6/libshiboken/bufferprocs_py37.h b/sources/shiboken6/libshiboken/bufferprocs_py37.h new file mode 100644 index 000000000..e16194e50 --- /dev/null +++ b/sources/shiboken6/libshiboken/bufferprocs_py37.h @@ -0,0 +1,109 @@ +// Copyright (C) 2018 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 + +/* +PSF LICENSE AGREEMENT FOR PYTHON 3.7.0 + +1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and + the Individual or Organization ("Licensee") accessing and otherwise using Python + 3.7.0 software in source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby + grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, + analyze, test, perform and/or display publicly, prepare derivative works, + distribute, and otherwise use Python 3.7.0 alone or in any derivative + version, provided, however, that PSF's License Agreement and PSF's notice of + copyright, i.e., "Copyright © 2001-2018 Python Software Foundation; All Rights + Reserved" are retained in Python 3.7.0 alone or in any derivative version + prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on or + incorporates Python 3.7.0 or any part thereof, and wants to make the + derivative work available to others as provided herein, then Licensee hereby + agrees to include in any such work a brief summary of the changes made to Python + 3.7.0. + +4. PSF is making Python 3.7.0 available to Licensee on an "AS IS" basis. + PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF + EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR + WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE + USE OF PYTHON 3.7.0 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.7.0 + FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF + MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.7.0, OR ANY DERIVATIVE + THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material breach of + its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any relationship + of agency, partnership, or joint venture between PSF and Licensee. This License + Agreement does not grant permission to use PSF trademarks or trade name in a + trademark sense to endorse or promote products or services of Licensee, or any + third party. + +8. By copying, installing or otherwise using Python 3.7.0, Licensee agrees + to be bound by the terms and conditions of this License Agreement. +*/ + +#ifndef BUFFER_REENABLE_H +#define BUFFER_REENABLE_H + +/* buffer interface */ +// This has been renamed to Pep_buffer and will be used. +typedef struct bufferinfo { + void *buf; + PyObject *obj; /* owned reference */ + Py_ssize_t len; + Py_ssize_t itemsize; /* This is Py_ssize_t so it can be + pointed to by strides in simple case.*/ + int readonly; + int ndim; + char *format; + Py_ssize_t *shape; + Py_ssize_t *strides; + Py_ssize_t *suboffsets; + void *internal; +} Pep_buffer; + +using getbufferproc =int (*)(PyObject *, Pep_buffer *, int); +using releasebufferproc = void (*)(PyObject *, Pep_buffer *); + +/* Maximum number of dimensions */ +#define PyBUF_MAX_NDIM 64 + +/* Flags for getting buffers */ +#define PyBUF_SIMPLE 0 +#define PyBUF_WRITABLE 0x0001 +/* we used to include an E, backwards compatible alias */ +#define PyBUF_WRITEABLE PyBUF_WRITABLE +#define PyBUF_FORMAT 0x0004 +#define PyBUF_ND 0x0008 +#define PyBUF_STRIDES (0x0010 | PyBUF_ND) +#define PyBUF_C_CONTIGUOUS (0x0020 | PyBUF_STRIDES) +#define PyBUF_F_CONTIGUOUS (0x0040 | PyBUF_STRIDES) +#define PyBUF_ANY_CONTIGUOUS (0x0080 | PyBUF_STRIDES) +#define PyBUF_INDIRECT (0x0100 | PyBUF_STRIDES) + +#define PyBUF_CONTIG (PyBUF_ND | PyBUF_WRITABLE) +#define PyBUF_CONTIG_RO (PyBUF_ND) + +#define PyBUF_STRIDED (PyBUF_STRIDES | PyBUF_WRITABLE) +#define PyBUF_STRIDED_RO (PyBUF_STRIDES) + +#define PyBUF_RECORDS (PyBUF_STRIDES | PyBUF_WRITABLE | PyBUF_FORMAT) +#define PyBUF_RECORDS_RO (PyBUF_STRIDES | PyBUF_FORMAT) + +#define PyBUF_FULL (PyBUF_INDIRECT | PyBUF_WRITABLE | PyBUF_FORMAT) +#define PyBUF_FULL_RO (PyBUF_INDIRECT | PyBUF_FORMAT) + + +#define PyBUF_READ 0x100 +#define PyBUF_WRITE 0x200 + +/* End buffer interface */ +LIBSHIBOKEN_API PyObject *PyMemoryView_FromBuffer(Pep_buffer *info); +#define Py_buffer Pep_buffer + +#endif // BUFFER_REENABLE_H diff --git a/sources/shiboken6/libshiboken/debugfreehook.cpp b/sources/shiboken6/libshiboken/debugfreehook.cpp new file mode 100644 index 000000000..13df6bd6c --- /dev/null +++ b/sources/shiboken6/libshiboken/debugfreehook.cpp @@ -0,0 +1,158 @@ +// 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 "debugfreehook.h" +#include "bindingmanager.h" +#include "gilstate.h" + +#if defined(_WIN32) && defined(_DEBUG) +# include <sbkwindows.h> +# include <crtdbg.h> +#endif + +#ifdef __GLIBC__ +#include <malloc.h> +#endif + +#ifdef __APPLE__ +#include <malloc/malloc.h> +#include <mach/mach.h> +#include <mach/mach_vm.h> +#endif + +#ifdef SHIBOKEN_INSTALL_FREE_DEBUG_HOOK +extern "C" { + +static int testPointerBeingFreed(void *ptr) +{ + // It is an error for a deleted pointer address to still be registered + // in the BindingManager + if (Shiboken::BindingManager::instance().hasWrapper(ptr)) { + Shiboken::GilState state; + + SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(ptr); + + fprintf(stderr, "SbkObject still in binding map when deleted: "); + PyObject_Print(reinterpret_cast<PyObject *>(wrapper), stderr, 0); + fprintf(stderr, "\n"); + +#ifdef _WIN32 + DebugBreak(); +#else + assert(0); +#endif + return FALSE; + } + + return TRUE; +} + +#if defined(_WIN32) && defined(_DEBUG) +static _CRT_ALLOC_HOOK lastCrtAllocHook; +static int DebugAllocHook(int nAllocType, void *pvData, + size_t nSize, int nBlockUse, long lRequest, + const unsigned char * szFileName, int nLine) +{ + // It is an error for a deleted pointer address to still be registered + // in the BindingManager + if ( nAllocType == _HOOK_FREE) { + if ( !testPointerBeingFreed(pvData) ) { + return 0; + } + } + + if ( lastCrtAllocHook != NULL ) { + return lastCrtAllocHook(nAllocType, pvData, nSize, nBlockUse, lRequest, + szFileName, nLine); + } + + return 1; +} +#endif // _WIN32 && _DEBUG + +#ifdef __GLIBC__ +static void (*lastFreeHook)(void *ptr, const void *caller); +static void DebugFreeHook(void *ptr, const void *caller) +{ + testPointerBeingFreed(ptr); + + if ( lastFreeHook != NULL ) + lastFreeHook(ptr, caller); +} +#endif // __GLIBC__ + +#ifdef __APPLE__ +static malloc_zone_t lastMallocZone; +static void DebugFreeHook(malloc_zone_t *zone, void *ptr) +{ + testPointerBeingFreed(ptr); + + if ( lastMallocZone.free != NULL ) + lastMallocZone.free(zone, ptr); +} +static void DebugFreeDefiniteSizeHook(malloc_zone_t *zone, void *ptr, size_t size) +{ + testPointerBeingFreed(ptr); + + if ( lastMallocZone.free_definite_size != NULL ) + lastMallocZone.free_definite_size(zone, ptr, size); +} +#endif __APPLE__ + +void debugInstallFreeHook(void) +{ +#if defined(_WIN32) && defined(_DEBUG) + lastCrtAllocHook = _CrtSetAllocHook(DebugAllocHook); +#endif + +#ifdef __GLIBC__ + // __free_hook is not thread safe so it marked as deprecated. Use here + // is hopefully safe and should catch errors in a single threaded program + // and only miss some in a multithreaded program + lastFreeHook = __free_hook; + __free_hook = DebugFreeHook; +#endif + +#ifdef __APPLE__ + malloc_zone_t *zone = malloc_default_zone(); + assert(zone != NULL); + //remove the write protection from the zone struct + if (zone->version >= 8) { + vm_protect(mach_task_self(), (uintptr_t)zone, sizeof(*zone), 0, VM_PROT_READ | VM_PROT_WRITE); + } + lastMallocZone = *zone; + zone->free = DebugFreeHook; + zone->free_definite_size = DebugFreeDefiniteSizeHook; + if (zone->version >= 8) { + vm_protect(mach_task_self(), (uintptr_t)zone, sizeof(*zone), 0, VM_PROT_READ); + } +#endif +} + +void debugRemoveFreeHook(void) +{ +#if defined(_WIN32) && defined(_DEBUG) + _CrtSetAllocHook(lastCrtAllocHook); +#endif + +#ifdef __GLIBC__ + __free_hook = lastFreeHook; +#endif + +#ifdef __APPLE__ + malloc_zone_t *zone = malloc_default_zone(); + assert(zone != NULL); + //remove the write protection from the zone struct + if (zone->version >= 8) { + vm_protect(mach_task_self(), (uintptr_t)zone, sizeof(*zone), 0, VM_PROT_READ | VM_PROT_WRITE); + } + zone->free = lastMallocZone.free; + zone->free_definite_size = lastMallocZone.free_definite_size; + if (zone->version >= 8) { + vm_protect(mach_task_self(), (uintptr_t)zone, sizeof(*zone), 0, VM_PROT_READ); + } +#endif +} + +} // extern "C" +#endif // SHIBOKEN_INSTALL_DEBUG_FREE_HOOK diff --git a/sources/shiboken6/libshiboken/debugfreehook.h b/sources/shiboken6/libshiboken/debugfreehook.h new file mode 100644 index 000000000..c19e36ab2 --- /dev/null +++ b/sources/shiboken6/libshiboken/debugfreehook.h @@ -0,0 +1,25 @@ +// 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 + +#ifndef DEBUGFREEHOOK_H +#define DEBUGFREEHOOK_H + +// These functions enable C library runtime hooks to try to catch cases where +// C++ object addresses remain in hash table of valid wrappers when the address +// is passed to free. The hooks are probably not thread safe and thus +// should only be enabled in single threaded environments + +// To enable the hook, uncomment the following define. +//#define SHIBOKEN_INSTALL_FREE_DEBUG_HOOK + +#ifdef SHIBOKEN_INSTALL_FREE_DEBUG_HOOK +extern "C" { + +void debugInstallFreeHook(void); +void debugRemoveFreeHook(void); + +} // extern "C" + +#endif // SHIBOKEN_INSTALL_FREE_DEBUG_HOOK + +#endif // DEBUGFREEHOOK_H diff --git a/sources/shiboken6/libshiboken/embed/embedding_generator.py b/sources/shiboken6/libshiboken/embed/embedding_generator.py new file mode 100644 index 000000000..a058fd372 --- /dev/null +++ b/sources/shiboken6/libshiboken/embed/embedding_generator.py @@ -0,0 +1,220 @@ +# Copyright (C) 2022 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 + +""" +embedding_generator.py + +This file takes the content of the two supported directories and inserts +it into a zip file. The zip file is then converted into a C++ source +file that can easily be unpacked again with Python (see signature.cpp, +constant 'PySide_PythonCode'). + +Note that this _is_ a zipfile, but since it is embedded into the shiboken +binary, we cannot use the zipimport module from Python. +But a similar solution is possible that allows for normal imports. + +See signature_bootstrap.py for details. +""" + +import sys +import os +import subprocess +import textwrap +import tempfile +import argparse +import marshal +import traceback +from pathlib import Path + +# work_dir is set to the source for testing, only. +# It can be overridden in the command line. +work_dir = Path(__file__).parent.resolve() +embed_dir = work_dir +cur_dir = Path.cwd() +source_dir = work_dir.parents[2] +assert source_dir.name == "sources" +build_script_dir = work_dir.parents[3] +assert (build_script_dir / "build_scripts").exists() + +sys.path.insert(0, os.fspath(build_script_dir)) + +from build_scripts import utils + + +def runpy(cmd, **kw): + subprocess.call([sys.executable, '-E'] + cmd.split(), **kw) + + +def create_zipfile(use_pyc, quiet): + """ + Collect all Python files, compile them, create a zip file + and make a chunked base64 encoded file from it. + """ + zip_name = "signature.zip" + inc_name = "signature_inc.h" + flag = '-b' + os.chdir(work_dir) + + # Remove all left-over py[co] and other files first, in case we use '--reuse-build'. + # Note that we could improve that with the PyZipfile function to use .pyc files + # in different folders, but that makes only sense when COIN allows us to have + # multiple Python versions in parallel. + for root, dirs, files in os.walk(work_dir): + for name in files: + fpath = Path(root) / name + ew = name.endswith + if ew(".pyc") or ew(".pyo") or ew(".zip") or ew(".inc"): + os.remove(fpath) + # We copy every Python file into this dir, but only for the right version. + # For testing in the source dir, we need to filter. + ignore = [] + utils.copydir(source_dir / "shiboken6" / "shibokenmodule" / "files.dir" / "shibokensupport", + work_dir / "shibokensupport", + ignore=ignore, file_filter_function=lambda name, n2: name.endswith(".py")) + if embed_dir != work_dir: + utils.copyfile(embed_dir / "signature_bootstrap.py", work_dir) + + if not use_pyc: + pass # We cannot compile, unless we have folders per Python version + else: + files = ' '.join(fn for fn in os.listdir('.')) + runpy(f'-m compileall -q {flag} {files}') + files = ' '.join(fn for fn in os.listdir('.') if not fn == zip_name) + runpy(f'-m zipfile -c {zip_name} {files}') + tmp = tempfile.TemporaryFile(mode="w+") + runpy(f'-m base64 {zip_name}', stdout=tmp) + # now generate the include file + tmp.seek(0) + with open(inc_name, "w") as inc: + _embed_file(tmp, inc) + tmp.close() + + # also generate a simple embeddable .pyc file for signature_bootstrap.pyc + boot_name = "signature_bootstrap.py" if not use_pyc else "signature_bootstrap.pyc" + with open(boot_name, "rb") as ldr, open("signature_bootstrap_inc.h", "w") as inc: + _embed_bytefile(ldr, inc, not use_pyc) + os.chdir(cur_dir) + if quiet: + return + + # have a look at our populated folder unless quiet option + def tree(directory): + print(f'+ {directory}') + for path in sorted(directory.rglob('*')): + depth = len(path.relative_to(directory).parts) + spacer = ' ' * depth + print(f'{spacer}+ {path.name}') + + print("++++ Current contents of") + tree(work_dir) + print("++++") + + +def _embed_file(fin, fout): + """ + Format a text file for embedding in a C++ source file. + """ + # MSVC has a 64k string limitation. In C, it would be easy to create an + # array of 64 byte strings and use them as one big array. In C++ this does + # not work, since C++ insists in having the terminating nullbyte. + # Therefore, we split the string after an arbitrary number of lines + # (chunked file). + limit = 50 + text = fin.readlines() + print(textwrap.dedent(""" + // This is a ZIP archive of all Python files in the directory + // "shiboken6/shibokenmodule/files.dir/shibokensupport/signature" + // There is also a toplevel file "signature_bootstrap.py[c]" that will be + // directly executed from C++ as a bootstrap loader. + """).strip(), file=fout) + block, blocks = 0, len(text) // limit + 1 + for idx, line in enumerate(text): + if idx % limit == 0: + if block: + fout.write(')"\n') + comma = "," if block else "" + block += 1 + fout.write(f'\n{comma} // Block {block} of {blocks}\nR"(') + else: + fout.write('\n') + fout.write(line.strip()) + fout.write(')"\n\n/* Sentinel */, ""\n') + + +def _embed_bytefile(fin, fout, is_text): + """ + Format a binary file for embedding in a C++ source file. + This version works directly with a single .pyc file. + """ + fname = fin.name + remark = ("No .pyc file because '--LIMITED-API=yes'" if is_text else + "The .pyc header is stripped away") + print(textwrap.dedent(f""" + /* + * This is the file "{fname}" as a simple byte array. + * It can be directly embedded without any further processing. + * {remark}. + */ + """), file=fout) + headsize = ( 0 if is_text else + 16 if sys.version_info >= (3, 7) else 12 if sys.version_info >= (3, 3) else 8) + binstr = fin.read()[headsize:] + if is_text: + try: + compile(binstr, fin.name, "exec") + except SyntaxError as e: + print(e) + traceback.print_exc(file=sys.stdout) + print(textwrap.dedent(f""" + ************************************************************************* + *** + *** Could not compile the boot loader '{fname}'! + *** + ************************************************************************* + """)) + raise SystemError + else: + try: + marshal.loads(binstr) + except ValueError as e: + print(e) + traceback.print_exc(file=sys.stdout) + version = sys.version_info[:3] + print(textwrap.dedent(f""" + ************************************************************************* + *** + *** This Python version {version} seems to have a new .pyc header size. + *** Please correct the 'headsize' constant ({headsize}). + *** + ************************************************************************* + """)) + raise SystemError + + print(file=fout) + use_ord = sys.version_info[0] == 2 + for i in range(0, len(binstr), 16): + for c in bytes(binstr[i : i + 16]): + ord_c = ord(c) if use_ord else c + print(f"{ord_c:#4},", file=fout, end="") + print(file=fout) + print("/* End Of File */", file=fout) + + +def str2bool(v): + if v.lower() in ('yes', 'true', 't', 'y', '1'): + return True + elif v.lower() in ('no', 'false', 'f', 'n', '0'): + return False + else: + raise argparse.ArgumentTypeError('Boolean value expected.') + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument('--cmake-dir', nargs="?") + parser.add_argument('--use-pyc', type=str2bool) + parser.add_argument('--quiet', action='store_true') + args = parser.parse_args() + if args.cmake_dir: + work_dir = Path(args.cmake_dir).resolve() + create_zipfile(args.use_pyc, args.quiet) diff --git a/sources/shiboken6/libshiboken/embed/module_collector.py b/sources/shiboken6/libshiboken/embed/module_collector.py new file mode 100644 index 000000000..a58ce6e4f --- /dev/null +++ b/sources/shiboken6/libshiboken/embed/module_collector.py @@ -0,0 +1,71 @@ +# Copyright (C) 2022 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 + +""" +module_collector.py + +Collect a number of modules listed on the command line. + +The purpose of this script is to generate the scripts needed for +a complete isolation of the signature extension. + +Usage: + +Run this script in one of the used python versions. +It will create an executable archive of the files on the command line. +""" + +import sys +import os +import argparse +import pickle +from textwrap import dedent +from pathlib import path + + +def source_archive(module, modname): + fname = Path(module.__file__).stem + ".py" + with open(fname) as source: + text = source.read() + encoded = text.replace("'''", "(triple_single)") + # modname = module.__name__ + # Do not use: Some modules rename themselves! + version = ".".join(map(str, sys.version_info[:3])) + shortname = fname.stem + preamble = dedent(fr""" + # BEGIN SOURCE ARCHIVE Python {version} module {modname} + + sources = {{}} if "sources" not in globals() else sources + sources["{modname}"] = '''\ + {encoded}'''.replace("(triple_single)", "'''") + + # END SOURCE ARCHIVE Python {version} module {modname} + """) + return preamble + + +def read_all(modules): + collected = "" + for modname in modules: + mod = __import__(modname) + collected += source_archive(mod, modname) + return collected + + +def license_header(): + license = Path(__file__).parent / "qt_python_license.txt" + with license.open() as f: + return f.read() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument('modules', nargs="+") + args = parser.parse_args() + print("modules:", args.modules) + ret = license_header() + read_all(args.modules) + ma_mi = "_".join(map(str, sys.version_info[:2])) + outpath = Path(__file__).parents[2] / Path("shibokenmodule", + "files.dir", "shibokensupport", f"python_minilib_{ma_mi}.py") + with outpath.open("w") as f: + f.write(ret) diff --git a/sources/shiboken6/libshiboken/embed/qt_python_license.txt b/sources/shiboken6/libshiboken/embed/qt_python_license.txt new file mode 100644 index 000000000..e5fdfdf4d --- /dev/null +++ b/sources/shiboken6/libshiboken/embed/qt_python_license.txt @@ -0,0 +1,48 @@ +# Copyright (C) 2022 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 + +## +## PSF LICENSE AGREEMENT FOR PYTHON 3.7.0 +## +## 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and +## the Individual or Organization ("Licensee") accessing and otherwise using Python +## 3.7.0 software in source or binary form and its associated documentation. +## +## 2. Subject to the terms and conditions of this License Agreement, PSF hereby +## grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +## analyze, test, perform and/or display publicly, prepare derivative works, +## distribute, and otherwise use Python 3.7.0 alone or in any derivative +## version, provided, however, that PSF's License Agreement and PSF's notice of +## copyright, i.e., "Copyright © 2001-2018 Python Software Foundation; All Rights +## Reserved" are retained in Python 3.7.0 alone or in any derivative version +## prepared by Licensee. +## +## 3. In the event Licensee prepares a derivative work that is based on or +## incorporates Python 3.7.0 or any part thereof, and wants to make the +## derivative work available to others as provided herein, then Licensee hereby +## agrees to include in any such work a brief summary of the changes made to Python +## 3.7.0. +## +## 4. PSF is making Python 3.7.0 available to Licensee on an "AS IS" basis. +## PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF +## EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR +## WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE +## USE OF PYTHON 3.7.0 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. +## +## 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.7.0 +## FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF +## MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.7.0, OR ANY DERIVATIVE +## THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. +## +## 6. This License Agreement will automatically terminate upon a material breach of +## its terms and conditions. +## +## 7. Nothing in this License Agreement shall be deemed to create any relationship +## of agency, partnership, or joint venture between PSF and Licensee. This License +## Agreement does not grant permission to use PSF trademarks or trade name in a +## trademark sense to endorse or promote products or services of Licensee, or any +## third party. +## +## 8. By copying, installing or otherwise using Python 3.7.0, Licensee agrees +## to be bound by the terms and conditions of this License Agreement. +## diff --git a/sources/shiboken6/libshiboken/embed/signature_bootstrap.py b/sources/shiboken6/libshiboken/embed/signature_bootstrap.py new file mode 100644 index 000000000..37f95cd35 --- /dev/null +++ b/sources/shiboken6/libshiboken/embed/signature_bootstrap.py @@ -0,0 +1,204 @@ +# Copyright (C) 2022 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 + +""" +signature_bootstrap.py +---------------------- + +This file was originally directly embedded into the C source. +After it grew more and more, I now prefer to have it as Python file. + +Meanwhile, there is also no more a stub loader necessary: +Because we meanwhile have embedding support, we could also load this file +directly from a .pyc file. + +This file replaces the hard to read Python stub in 'signature.cpp', and we +could distinguish better between bootstrap related functions and loader +functions. +It is embedded into 'signature.cpp' as "embed/signature_bootstrap.inc". + +# PYSIDE-1436: Python 3.10 had a problem with EmbeddableZipImporter because the +imports were in the functions. Moved them outside into the globals. +""" + +recursion_trap = 0 + +import base64 +import importlib +import io +import os +import sys +import traceback +import zipfile + +from contextlib import contextmanager +from importlib.machinery import ModuleSpec +from pathlib import Path + + +def bootstrap(): + + global recursion_trap + if recursion_trap: + # we are probably called from outside, already + print("Recursion occurred in Bootstrap. Did you start by hand? Then it's ok.") + print("But you should trigger start by '_init_pyside_extension()', only!") + recursion_trap += 1 + + @contextmanager + def ensure_shibokensupport(target, support_path): + # Make sure that we always have the shibokensupport containing package first. + # Also remove any prior loaded module of this name, just in case. + # PYSIDE-1621: support_path can also be a finder instance. + target.insert(0, support_path) + + sbks = "shibokensupport" + if sbks in sys.modules: + del sys.modules[sbks] + prefix = sbks + "." + for key in list(key for key in sys.modules if key.startswith(prefix)): + del sys.modules[key] + try: + import shibokensupport + yield + except Exception as e: + f = sys.stderr + print("Problem importing shibokensupport:", file=f) + print(f"{e.__class__.__name__}: {e}", file=f) + traceback.print_exc() + print("sys.path:", file=f) + for p in sys.path: + print(" " + p, file=f) + f.flush() + sys.exit(-1) + target.remove(support_path) + + # Here we decide if we re-incarnate the embedded files or use embedding. + incarnated = find_incarnated_files() + if incarnated: + target, support_path = sys.path, os.fspath(incarnated) + else: + target, support_path = prepare_zipfile() + with ensure_shibokensupport(target, support_path): + from shibokensupport.signature import loader + return loader + +# Newer functionality: +# This function checks if the support directory exist and returns it. +# If does not exist, we try to create it and return it. +# Otherwise, we return None. + +def find_incarnated_files(): + import shiboken6 as root + files_dir = Path(root.__file__).resolve().parent / "files.dir" + handle_embedding_switch(files_dir) + if files_dir.exists(): + sys.path.insert(0, os.fspath(files_dir)) + # Note: To avoid recursion problems, we need to preload the loader. + # But that has the side-effect that we need to delay the feature + # initialization until all function pointers are set. + # See `post_init_func` in signature_globals.cpp . + import shibokensupport.signature.loader + del sys.path[0] + return files_dir + return None + + +def handle_embedding_switch(files_dir): + """ + This handles the optional environment variable `SBK_EMBED` + if not set : do nothing + if set to 0, false, no : de-virtualize the Python files + if set to 1, true, yes : virtualize again (delete "files.dir") + """ + env_name = "SBK_EMBED" + env_var = os.environ.get(env_name) + if not env_var: + return + if env_var.lower() in ("1", "t", "true", "y", "yes"): + import shutil + shutil.rmtree(files_dir, ignore_errors=True) + elif env_var.lower() in ("0", "f", "false", "n", "no"): + reincarnate_files(files_dir) + + +def reincarnate_files(files_dir): + target, zip = prepare_zipfile() + names = (_ for _ in zip.zfile.namelist() if _.endswith(".py")) + try: + # First check mkdir to get an error when we cannot write. + files_dir.mkdir(exist_ok=True) + except os.error as e: + print(f"SBK_EMBED=False: Warning: Cannot write into {files_dir}") + return None + try: + # Then check for a real error when unpacking the zip file. + zip.zfile.extractall(path=files_dir, members=names) + return files_dir + except Exception as e: + print(f"{e.__class__.__name__}: {e}", file=sys.stderr) + traceback.print_exc() + raise + +# New functionality: Loading from a zip archive. +# There exists the zip importer, but as it is written, only real zip files are +# supported. Before I will start an own implementation, it is easiest to use +# a temporary zip file. +# PYSIDE-1621: make zip file access totally virtual + +def prepare_zipfile(): + """ + Old approach: + + Write the zip file to a real file and return its name. + It will be implicitly opened as such when we add the name to sys.path . + + New approach (Python 3, only): + + Use EmbeddableZipImporter and pass the zipfile structure directly. + The sys.path way does not work, instead we need to use sys.meta_path . + See https://docs.python.org/3/library/sys.html#sys.meta_path + """ + + # 'zipstring_sequence' comes from signature.cpp + zipbytes = base64.b64decode(''.join(zipstring_sequence)) + vzip = zipfile.ZipFile(io.BytesIO(zipbytes)) + return sys.meta_path, EmbeddableZipImporter(vzip) + + +class EmbeddableZipImporter(object): + + def __init__(self, zip_file): + def p2m(filename): + if filename.endswith("/__init__.py"): + return filename[:-12].replace("/", ".") + if filename.endswith(".py"): + return filename[:-3].replace("/", ".") + return None + + self.zfile = zip_file + self._mod2path = {p2m(_.filename) : _.filename for _ in zip_file.filelist} + + def find_spec(self, fullname, path, target=None): + path = self._mod2path.get(fullname) + return ModuleSpec(fullname, self) if path else None + + def create_module(self, spec): + return None + + def exec_module(self, module): + fullname = module.__spec__.name + filename = self._mod2path[fullname] + with self.zfile.open(filename, "r") as f: # "rb" not for zipfile + codeob = compile(f.read(), filename, "exec") + exec(codeob, module.__dict__) + module.__file__ = filename + module.__loader__ = self + if filename.endswith("/__init__.py"): + module.__path__ = [] + module.__package__ = fullname + else: + module.__package__ = fullname.rpartition('.')[0] + sys.modules[fullname] = module + +# eof diff --git a/sources/shiboken6/libshiboken/gilstate.cpp b/sources/shiboken6/libshiboken/gilstate.cpp new file mode 100644 index 000000000..8daa93b8b --- /dev/null +++ b/sources/shiboken6/libshiboken/gilstate.cpp @@ -0,0 +1,38 @@ +// 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 "gilstate.h" + +namespace Shiboken +{ + +GilState::GilState() +{ + if (Py_IsInitialized()) { + m_gstate = PyGILState_Ensure(); + m_locked = true; + } +} + +GilState::~GilState() +{ + release(); +} + +void GilState::release() +{ + if (m_locked && Py_IsInitialized()) { + PyGILState_Release(m_gstate); + m_locked = false; + } +} + +// Abandon the lock: Only for special situations, like termination of a +// POSIX thread (PYSIDE 1282). +void GilState::abandon() +{ + m_locked = false; +} + +} // namespace Shiboken + diff --git a/sources/shiboken6/libshiboken/gilstate.h b/sources/shiboken6/libshiboken/gilstate.h new file mode 100644 index 000000000..abad9d955 --- /dev/null +++ b/sources/shiboken6/libshiboken/gilstate.h @@ -0,0 +1,33 @@ +// 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 + +#ifndef GILSTATE_H +#define GILSTATE_H + +#include <shibokenmacros.h> +#include "sbkpython.h" + +namespace Shiboken +{ + +class LIBSHIBOKEN_API GilState +{ +public: + GilState(const GilState &) = delete; + GilState(GilState &&) = delete; + GilState &operator=(const GilState &) = delete; + GilState &operator=(GilState &&) = delete; + + GilState(); + ~GilState(); + void release(); + void abandon(); +private: + PyGILState_STATE m_gstate; + bool m_locked = false; +}; + +} // namespace Shiboken + +#endif // GILSTATE_H + diff --git a/sources/shiboken6/libshiboken/helper.cpp b/sources/shiboken6/libshiboken/helper.cpp new file mode 100644 index 000000000..46af68956 --- /dev/null +++ b/sources/shiboken6/libshiboken/helper.cpp @@ -0,0 +1,637 @@ +// Copyright (C) 2020 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 "helper.h" +#include "basewrapper_p.h" +#include "sbkstring.h" +#include "sbkstaticstrings.h" +#include "sbkstaticstrings.h" +#include "pep384impl.h" + +#include <algorithm> +#include <optional> + +#include <iomanip> +#include <iostream> +#include <climits> +#include <cstring> +#include <cstdarg> +#include <cctype> + +#ifdef _WIN32 +# include <sbkwindows.h> +#else +# include <pthread.h> +#endif + +static std::optional<std::string> getStringAttr(PyObject *obj, const char *what) +{ + if (PyObject_HasAttrString(obj, what) != 0) { // Check first to suppress error. + Shiboken::AutoDecRef result(PyObject_GetAttrString(obj, what)); + if (PyUnicode_Check(result.object()) != 0) + return _PepUnicode_AsString(result.object()); + } + return std::nullopt; +} + +static std::optional<int> getIntAttr(PyObject *obj, const char *what) +{ + if (PyObject_HasAttrString(obj, what) != 0) { // Check first to suppress error. + Shiboken::AutoDecRef result(PyObject_GetAttrString(obj, what)); + if (PyLong_Check(result.object()) != 0) + return PyLong_AsLong(result.object()); + } + return std::nullopt; +} + +static bool verbose = false; + +static void formatTypeTuple(PyObject *t, const char *what, std::ostream &str); + +static void formatPyTypeObject(const PyTypeObject *obj, std::ostream &str, bool verbose) +{ + if (obj == nullptr) { + str << '0'; + return; + } + + str << '"' << obj->tp_name << '"'; + if (verbose) { + bool immutableType = false; + str << ", 0x" << std::hex << obj->tp_flags << std::dec; + if (obj->tp_flags & Py_TPFLAGS_HEAPTYPE) + str << " [heaptype]"; + if (obj->tp_flags & Py_TPFLAGS_BASETYPE) + str << " [base]"; + if (obj->tp_flags & Py_TPFLAGS_HAVE_GC) + str << " [gc]"; + if (obj->tp_flags & Py_TPFLAGS_LONG_SUBCLASS) + str << " [long]"; + if (obj->tp_flags & Py_TPFLAGS_LIST_SUBCLASS) + str << " [list]"; + if (obj->tp_flags & Py_TPFLAGS_TUPLE_SUBCLASS) + str << " [tuple]"; + if (obj->tp_flags & Py_TPFLAGS_BYTES_SUBCLASS) + str << " [bytes]"; + if (obj->tp_flags & Py_TPFLAGS_UNICODE_SUBCLASS) + str << " [unicode]"; + if (obj->tp_flags & Py_TPFLAGS_DICT_SUBCLASS) + str << " [dict]"; + if (obj->tp_flags & Py_TPFLAGS_TYPE_SUBCLASS) + str << " [type]"; + if (obj->tp_flags & Py_TPFLAGS_IS_ABSTRACT) + str << " [abstract]"; + if (obj->tp_flags & Py_TPFLAGS_READY) + str << " [ready]"; + if (obj->tp_flags & Py_TPFLAGS_READYING) + str << " [readying]"; + if (obj->tp_flags & Py_TPFLAGS_METHOD_DESCRIPTOR) + str << " [method_descriptor]"; +# ifndef Py_LIMITED_API + if (obj->tp_flags & Py_TPFLAGS_HAVE_VECTORCALL) + str << " [vectorcall]"; +# endif // !Py_LIMITED_API +# if PY_VERSION_HEX >= 0x030A0000 + immutableType = (obj->tp_flags & Py_TPFLAGS_IMMUTABLETYPE) != 0; + if (immutableType) + str << " [immutabletype]"; + if (obj->tp_flags & Py_TPFLAGS_DISALLOW_INSTANTIATION) + str << " [disallow_instantiation]"; +# ifndef Py_LIMITED_API + if (obj->tp_flags & Py_TPFLAGS_MAPPING) + str << " [mapping]"; + if (obj->tp_flags & Py_TPFLAGS_SEQUENCE) + str << " [sequence]"; +# endif // !Py_LIMITED_API +# endif // 3.10 + if (obj->tp_basicsize != 0) + str << ", basicsize=" << obj->tp_basicsize; + if (verbose) { + formatTypeTuple(obj->tp_bases, "bases", str); + formatTypeTuple(obj->tp_mro, "mro", str); + if (!immutableType) { + auto *underlying = reinterpret_cast<const PyObject *>(obj)->ob_type; + if (underlying != nullptr && underlying != obj) { + str << ", underlying=\"" << underlying->tp_name << '"'; + } + } + } + } +} + +static void formatTypeTuple(PyObject *t, const char *what, std::ostream &str) +{ + const Py_ssize_t size = t != nullptr && PyTuple_Check(t) != 0 ? PyTuple_Size(t) : 0; + if (size > 0) { + str << ", " << what << "=[" << size << "]{"; + for (Py_ssize_t i = 0; i < size; ++i) { + if (i != 0) + str << ", "; + Shiboken::AutoDecRef item(PyTuple_GetItem(t, i)); + if (item.isNull()) + str << '0'; // Observed with non-ready types + else + str << '"' << reinterpret_cast<PyTypeObject *>(item.object())->tp_name << '"'; + } + str << '}'; + } +} + +static void formatPyObject(PyObject *obj, std::ostream &str); + +static void formatPySequence(PyObject *obj, std::ostream &str) +{ + const Py_ssize_t size = PySequence_Size(obj); + const Py_ssize_t printSize = std::min(size, Py_ssize_t(5)); + str << size << " <"; + for (Py_ssize_t i = 0; i < printSize; ++i) { + if (i) + str << ", "; + str << '('; + PyObject *item = PySequence_GetItem(obj, i); + formatPyObject(item, str); + str << ')'; + Py_XDECREF(item); + } + if (printSize < size) + str << ",..."; + str << '>'; +} + +static void formatPyTuple(PyObject *obj, std::ostream &str) +{ + + const Py_ssize_t size = PyTuple_Size(obj); + str << size << " <"; + for (Py_ssize_t i = 0; i < size; ++i) { + if (i) + str << ", "; + str << '('; + PyObject *item = PyTuple_GetItem(obj, i); + formatPyObject(item, str); + str << ')'; + Py_XDECREF(item); + } + str << '>'; +} + +static void formatPyDict(PyObject *obj, std::ostream &str) +{ + PyObject *key; + PyObject *value; + Py_ssize_t pos = 0; + str << '{'; + while (PyDict_Next(obj, &pos, &key, &value) != 0) { + if (pos) + str << ", "; + str << Shiboken::debugPyObject(key) << '=' << Shiboken::debugPyObject(value); + } + str << '}'; +} + +// Helper to format a 0-terminated character sequence +template <class Char> +static void formatCharSequence(const Char *s, std::ostream &str) +{ + str << '"'; + const auto oldFillC = str.fill('0'); + str << std::hex; + for (; *s; ++s) { + const unsigned c = *s; + if (c < 127) + str << char(c); + else + str << "0x" << std::right << std::setw(sizeof(Char) * 2) << c << std::left; + } + str << std::dec; + str.fill(oldFillC); + str << '"'; +} + +static void formatPyUnicode(PyObject *obj, std::ostream &str) +{ + // Note: The below call create the PyCompactUnicodeObject.utf8 representation + str << '"' << _PepUnicode_AsString(obj) << '"'; + if (!verbose) + return; + + str << " (" << PyUnicode_GetLength(obj) << ')'; + const auto kind = _PepUnicode_KIND(obj); + switch (kind) { +#if PY_VERSION_HEX < 0x030C0000 + case PepUnicode_WCHAR_KIND: + str << " [wchar]"; + break; +#endif + case PepUnicode_1BYTE_KIND: + str << " [1byte]"; + break; + case PepUnicode_2BYTE_KIND: + str << " [2byte]"; + break; + case PepUnicode_4BYTE_KIND: + str << " [4byte]"; + break; + } + + const bool ascii = _PepUnicode_IS_ASCII(obj); + if (ascii) + str << " [ascii]"; + const bool compact = _PepUnicode_IS_COMPACT(obj); + if (compact) + str << " [compact]"; + void *data =_PepUnicode_DATA(obj); + str << ", data="; + switch (kind) { +#if PY_VERSION_HEX < 0x030C0000 + case PepUnicode_WCHAR_KIND: + formatCharSequence(reinterpret_cast<const wchar_t *>(data), str); +#endif + break; + case PepUnicode_1BYTE_KIND: + formatCharSequence(reinterpret_cast<const Py_UCS1 *>(data), str); + break; + case PepUnicode_2BYTE_KIND: + formatCharSequence(reinterpret_cast<const Py_UCS2 *>(data), str); + break; + case PepUnicode_4BYTE_KIND: + formatCharSequence(reinterpret_cast<const Py_UCS4 *>(data), str); + break; + } + +#ifndef Py_LIMITED_API + const char *utf8 = nullptr; + if (!ascii && compact && kind == PepUnicode_1BYTE_KIND) { + const auto *compactObj = reinterpret_cast<const PyCompactUnicodeObject *>(obj); + if (compactObj->utf8_length) + utf8 = compactObj->utf8; + } + if (utf8) { + str << ", utf8="; + formatCharSequence(reinterpret_cast<const Py_UCS1 *>(utf8), str); + } else { + str << ", no-utf8"; + } +#endif // !Py_LIMITED_API +} + +static std::string getQualName(PyObject *obj) +{ + Shiboken::AutoDecRef result(PyObject_GetAttr(obj, Shiboken::PyMagicName::qualname())); + return result.object() != nullptr + ? _PepUnicode_AsString(result.object()) : std::string{}; +} + +static void formatPyFunction(PyObject *obj, std::ostream &str) +{ + str << '"' << getQualName(obj) << "()\""; +} + +static void formatPyMethod(PyObject *obj, std::ostream &str) +{ + if (auto *func = PyMethod_Function(obj)) + formatPyFunction(func, str); + str << ", instance=" << PyMethod_Self(obj); +} + +static void formatPyCodeObject(PyObject *obj, std::ostream &str) +{ + if (auto name = getStringAttr(obj, "co_name")) + str << '"' << name.value() << '"'; + if (auto qualName = getStringAttr(obj, "co_qualname")) + str << ", co_qualname=\"" << qualName.value() << '"'; + if (auto flags = getIntAttr(obj, "co_flags")) + str << ", flags=0x" << std::hex << flags.value() << std::dec; + if (auto c = getIntAttr(obj, "co_argcount")) + str << ", co_argcounts=" << c.value(); + if (auto c = getIntAttr(obj, "co_posonlyargcount")) + str << ", co_posonlyargcount=" << c.value(); + if (auto c = getIntAttr(obj, "co_kwonlyargcount")) + str << ", co_kwonlyargcount=" << c.value(); + if (auto fileName = getStringAttr(obj, "co_filename")) { + str << " @" << fileName.value(); + if (auto l = getIntAttr(obj, "co_firstlineno")) + str << ':'<< l.value(); + } +} + +static void formatPyObjectHelper(PyObject *obj, std::ostream &str) +{ + str << ", "; + if (obj == Py_None) { + str << "None"; + return; + } + if (obj == Py_True) { + str << "True"; + return; + } + if (obj == Py_False) { + str << "False"; + return; + } + const auto refs = Py_REFCNT(obj); + if (refs == UINT_MAX) // _Py_IMMORTAL_REFCNT + str << "immortal, "; + else + str << "refs=" << refs << ", "; + if (PyType_Check(obj)) { + str << "type: "; + formatPyTypeObject(reinterpret_cast<PyTypeObject *>(obj), str, true); + return; + } + formatPyTypeObject(obj->ob_type, str, false); + str << ", "; + if (PyLong_Check(obj)) { + const auto llv = PyLong_AsLongLong(obj); + if (PyErr_Occurred() != PyExc_OverflowError) { + str << llv; + } else { + PyErr_Clear(); + str << "0x" << std::hex << PyLong_AsUnsignedLongLong(obj) << std::dec; + } + } + else if (PyFloat_Check(obj)) + str << PyFloat_AsDouble(obj); + else if (PyUnicode_Check(obj)) + formatPyUnicode(obj, str); + else if (PyFunction_Check(obj) != 0) + formatPyFunction(obj, str); + else if (PyMethod_Check(obj) != 0) + formatPyMethod(obj, str); + else if (PepCode_Check(obj) != 0) + formatPyCodeObject(obj, str); + else if (PySequence_Check(obj)) + formatPySequence(obj, str); + else if (PyDict_Check(obj)) + formatPyDict(obj, str); + else if (PyTuple_CheckExact(obj)) + formatPyTuple(obj, str); + else + str << "<unknown>"; +} + +static void formatPyObject(PyObject *obj, std::ostream &str) +{ + str << obj; + if (obj) + formatPyObjectHelper(obj, str); +} + +namespace Shiboken +{ + +debugPyObject::debugPyObject(PyObject *o) : m_object(o) +{ +} + +debugSbkObject::debugSbkObject(SbkObject *o) : m_object(o) +{ +} + +debugPyTypeObject::debugPyTypeObject(const PyTypeObject *o) : m_object(o) +{ +} + +debugPyBuffer::debugPyBuffer(const Py_buffer &b) : m_buffer(b) +{ +} + +std::ostream &operator<<(std::ostream &str, const debugPyTypeObject &o) +{ + str << "PyTypeObject("; + formatPyTypeObject(o.m_object, str, true); + str << ')'; + return str; +} + +std::ostream &operator<<(std::ostream &str, const debugSbkObject &o) +{ + str << "SbkObject(" << o.m_object; + if (o.m_object) { + Shiboken::Object::_debugFormat(str, o.m_object); + formatPyObjectHelper(reinterpret_cast<PyObject *>(o.m_object), str); + } + str << ')'; + return str; +} + +std::ostream &operator<<(std::ostream &str, const debugPyObject &o) +{ + str << "PyObject("; + formatPyObject(o.m_object, str); + str << ')'; + return str; +} + +std::ostream &operator<<(std::ostream &str, const debugPyBuffer &b) +{ + str << "PyBuffer(buf=" << b.m_buffer.buf << ", len=" + << b.m_buffer.len << ", itemsize=" << b.m_buffer.itemsize + << ", readonly=" << b.m_buffer.readonly << ", ndim=" << b.m_buffer.ndim; + if (b.m_buffer.format) + str << ", format=\"" << b.m_buffer.format << '"'; + str << ", shape=" << b.m_buffer.shape << ", strides=" << b.m_buffer.strides + << ", suboffsets=" << b.m_buffer.suboffsets << ')'; + return str; +} + +std::ios_base &debugVerbose(std::ios_base &s) +{ + verbose = true; + return s; +} + +std::ios_base &debugBrief(std::ios_base &s) +{ + verbose = false; + return s; +} + +#ifdef _WIN32 +// Converts a Unicode string to a string encoded in the Windows console's +// code page via wchar_t for use with argv (PYSIDE-1425). +// FIXME: Remove once Windows console uses UTF-8 +static char *toWindowsConsoleEncoding(PyObject *unicode) +{ + wchar_t *buf = PyUnicode_AsWideCharString(unicode, nullptr); + if (buf == nullptr) + return nullptr; + const int required = WideCharToMultiByte(CP_ACP, 0, buf, -1, + nullptr, 0, nullptr, nullptr); + if (required == 0) { + PyMem_Free(buf); + return nullptr; + } + char *result = new char[required]; + WideCharToMultiByte(CP_ACP, 0, buf, -1, + result, required, nullptr, nullptr); + PyMem_Free(buf); + return result; +} +#endif // _WIN32 + +// PySide-510: Changed from PySequence to PyList, which is correct. +bool listToArgcArgv(PyObject *argList, int *argc, char ***argv, const char *defaultAppName) +{ + if (!PyList_Check(argList)) + return false; + + if (!defaultAppName) + defaultAppName = "PySideApplication"; + + // Check all items + Shiboken::AutoDecRef args(PySequence_Fast(argList, nullptr)); + int numArgs = int(PySequence_Fast_GET_SIZE(argList)); + for (int i = 0; i < numArgs; ++i) { + PyObject *item = PyList_GET_ITEM(args.object(), i); + if (!PyBytes_Check(item) && !PyUnicode_Check(item)) + return false; + } + + bool hasEmptyArgList = numArgs == 0; + if (hasEmptyArgList) + numArgs = 1; + + *argc = numArgs; + *argv = new char *[*argc]; + + if (hasEmptyArgList) { + // Try to get the script name + PyObject *globals = PyEval_GetGlobals(); + PyObject *appName = PyDict_GetItem(globals, Shiboken::PyMagicName::file()); + (*argv)[0] = strdup(appName ? Shiboken::String::toCString(appName) : defaultAppName); + } else { + for (int i = 0; i < numArgs; ++i) { + PyObject *item = PyList_GET_ITEM(args.object(), i); + char *string = nullptr; + if (Shiboken::String::check(item)) { +#ifdef _WIN32 + string = toWindowsConsoleEncoding(item); +#else + string = strdup(Shiboken::String::toCString(item)); +#endif + } + (*argv)[i] = string; + } + } + + return true; +} + +int *sequenceToIntArray(PyObject *obj, bool zeroTerminated) +{ + AutoDecRef seq(PySequence_Fast(obj, "Sequence of ints expected")); + if (seq.isNull()) + return nullptr; + + Py_ssize_t size = PySequence_Fast_GET_SIZE(seq.object()); + int *array = new int[size + (zeroTerminated ? 1 : 0)]; + + for (int i = 0; i < size; i++) { + PyObject *item = PySequence_Fast_GET_ITEM(seq.object(), i); + if (!PyLong_Check(item)) { + PyErr_SetString(PyExc_TypeError, "Sequence of ints expected"); + delete[] array; + return nullptr; + } + array[i] = PyLong_AsLong(item); + } + + if (zeroTerminated) + array[size] = 0; + + return array; +} + + +int warning(PyObject *category, int stacklevel, const char *format, ...) +{ + va_list args; + va_start(args, format); +#ifdef _WIN32 + va_list args2 = args; +#else + va_list args2; + va_copy(args2, args); +#endif + + // check the necessary memory + int size = vsnprintf(nullptr, 0, format, args) + 1; + auto message = new char[size]; + int result = 0; + if (message) { + // format the message + vsnprintf(message, size, format, args2); + result = PyErr_WarnEx(category, message, stacklevel); + delete [] message; + } + va_end(args2); + va_end(args); + return result; +} + +ThreadId currentThreadId() +{ +#if defined(_WIN32) + return GetCurrentThreadId(); +#elif defined(__APPLE_CC__) + return reinterpret_cast<ThreadId>(pthread_self()); +#else + return pthread_self(); +#endif +} + +// Internal, used by init() from main thread +static ThreadId _mainThreadId{0}; +void _initMainThreadId() { _mainThreadId = currentThreadId(); } + +ThreadId mainThreadId() +{ + return _mainThreadId; +} + +const char *typeNameOf(const char *typeIdName) +{ + auto size = std::strlen(typeIdName); +#if defined(Q_CC_MSVC) // MSVC: "class QPaintDevice * __ptr64" + if (auto *lastStar = strchr(typeName, '*')) { + // MSVC: "class QPaintDevice * __ptr64" + while (*--lastStar == ' ') { + } + size = lastStar - typeName + 1; + } +#else // g++, Clang: "QPaintDevice *" -> "P12QPaintDevice" + if (size > 2 && typeIdName[0] == 'P' && std::isdigit(typeIdName[1])) { + ++typeIdName; + --size; + } +#endif + char *result = new char[size + 1]; + result[size] = '\0'; + std::memcpy(result, typeIdName, size); + return result; +} + +#if !defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030A0000 && !defined(PYPY_VERSION) +static int _getPyVerbose() +{ + PyConfig config; + PyConfig_InitPythonConfig(&config); + return config.verbose; +} +#endif // !Py_LIMITED_API >= 3.10 + +int pyVerbose() +{ +#ifdef Py_LIMITED_API + return Pep_GetVerboseFlag(); +#elif PY_VERSION_HEX >= 0x030A0000 && !defined(PYPY_VERSION) + static const int result = _getPyVerbose(); + return result; +#else + return Py_VerboseFlag; +#endif +} + +} // namespace Shiboken diff --git a/sources/shiboken6/libshiboken/helper.h b/sources/shiboken6/libshiboken/helper.h new file mode 100644 index 000000000..f226e8c24 --- /dev/null +++ b/sources/shiboken6/libshiboken/helper.h @@ -0,0 +1,120 @@ +// 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 + +#ifndef HELPER_H +#define HELPER_H + +#include "sbkpython.h" +#include "shibokenmacros.h" +#include "autodecref.h" + +#include <iosfwd> + +#define SBK_UNUSED(x) (void)(x); + +namespace Shiboken +{ + +/** +* It transforms a python sequence into two C variables, argc and argv. +* This function tries to find the application (script) name and put it into argv[0], if +* the application name can't be guessed, defaultAppName will be used. +* +* No memory is allocated is an error occur. +* +* \note argc must be a valid address. +* \note The argv array is allocated using new operator and each item is allocated using malloc. +* \returns True on sucess, false otherwise. +*/ +LIBSHIBOKEN_API bool listToArgcArgv(PyObject *argList, int *argc, char ***argv, const char *defaultAppName = nullptr); + +/** + * Convert a python sequence into a heap-allocated array of ints. + * + * \returns The newly allocated array or NULL in case of error or empty sequence. Check with PyErr_Occurred + * if it was successfull. + */ +LIBSHIBOKEN_API int *sequenceToIntArray(PyObject *obj, bool zeroTerminated = false); + +/// Fix a type name returned by typeid(t).name(), depending on compiler. +/// \returns Fixed name (allocated). +LIBSHIBOKEN_API const char *typeNameOf(const char *typeIdName); + +/** + * Creates and automatically deallocates C++ arrays. + */ +template<class T> +class AutoArrayPointer +{ + public: + AutoArrayPointer(const AutoArrayPointer &) = delete; + AutoArrayPointer(AutoArrayPointer &&) = delete; + AutoArrayPointer &operator=(const AutoArrayPointer &) = delete; + AutoArrayPointer &operator=(AutoArrayPointer &&) = delete; + + + explicit AutoArrayPointer(Py_ssize_t size) { data = new T[size]; } + T &operator[](Py_ssize_t pos) { return data[pos]; } + operator T *() const { return data; } + ~AutoArrayPointer() { delete[] data; } + private: + T *data; +}; + +using ThreadId = unsigned long long; +LIBSHIBOKEN_API ThreadId currentThreadId(); +LIBSHIBOKEN_API ThreadId mainThreadId(); + +LIBSHIBOKEN_API int pyVerbose(); + +/** + * An utility function used to call PyErr_WarnEx with a formatted message. + */ +LIBSHIBOKEN_API int warning(PyObject *category, int stacklevel, const char *format, ...); + +struct LIBSHIBOKEN_API debugPyObject +{ + explicit debugPyObject(PyObject *o); + + PyObject *m_object; +}; + +struct LIBSHIBOKEN_API debugSbkObject +{ + explicit debugSbkObject(SbkObject *o); + + SbkObject *m_object; +}; + +struct LIBSHIBOKEN_API debugPyTypeObject +{ + explicit debugPyTypeObject(const PyTypeObject *o); + + const PyTypeObject *m_object; +}; + +struct LIBSHIBOKEN_API debugPyBuffer +{ + explicit debugPyBuffer(const Py_buffer &b); + + const Py_buffer &m_buffer; +}; + +struct debugPyArrayObject +{ + explicit debugPyArrayObject(PyObject *object) : m_object(object) {} + + PyObject *m_object; +}; + +LIBSHIBOKEN_API std::ostream &operator<<(std::ostream &str, const debugPyObject &o); +LIBSHIBOKEN_API std::ostream &operator<<(std::ostream &str, const debugSbkObject &o); +LIBSHIBOKEN_API std::ostream &operator<<(std::ostream &str, const debugPyTypeObject &o); +LIBSHIBOKEN_API std::ostream &operator<<(std::ostream &str, const debugPyBuffer &b); +LIBSHIBOKEN_API std::ostream &operator<<(std::ostream &str, const debugPyArrayObject &b); +LIBSHIBOKEN_API std::ios_base &debugVerbose(std::ios_base &s); +LIBSHIBOKEN_API std::ios_base &debugBrief(std::ios_base &s); +} // namespace Shiboken + + +#endif // HELPER_H diff --git a/sources/shiboken6/libshiboken/pep384ext.h b/sources/shiboken6/libshiboken/pep384ext.h new file mode 100644 index 000000000..021c53d16 --- /dev/null +++ b/sources/shiboken6/libshiboken/pep384ext.h @@ -0,0 +1,89 @@ +// Copyright (C) 2024 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 + +#ifndef PEP384EXT_H +#define PEP384EXT_H + +#include "pep384impl.h" + +/// Returns the allocator slot of the PyTypeObject. +inline allocfunc PepExt_Type_GetAllocSlot(PyTypeObject *t) +{ + return reinterpret_cast<allocfunc>(PepType_GetSlot(t, Py_tp_alloc)); +} + +/// Invokes the allocator slot of the PyTypeObject. +template <class Type> +inline Type *PepExt_TypeCallAlloc(PyTypeObject *t, Py_ssize_t nitems) +{ + PyObject *result = PepExt_Type_GetAllocSlot(t)(t, nitems); + return reinterpret_cast<Type *>(result); +} + +/// Returns the getattro slot of the PyTypeObject. +inline getattrofunc PepExt_Type_GetGetAttroSlot(PyTypeObject *t) +{ + return reinterpret_cast<getattrofunc>(PepType_GetSlot(t, Py_tp_getattro)); +} + +/// Returns the setattro slot of the PyTypeObject. +inline setattrofunc PepExt_Type_GetSetAttroSlot(PyTypeObject *t) +{ + return reinterpret_cast<setattrofunc>(PepType_GetSlot(t, Py_tp_setattro)); +} + +/// Returns the descr_get slot of the PyTypeObject. +inline descrgetfunc PepExt_Type_GetDescrGetSlot(PyTypeObject *t) +{ + return reinterpret_cast<descrgetfunc>(PepType_GetSlot(t, Py_tp_descr_get)); +} + +/// Invokes the descr_get slot of the PyTypeObject. +inline PyObject *PepExt_Type_CallDescrGet(PyObject *self, PyObject *obj, PyObject *type) +{ + return PepExt_Type_GetDescrGetSlot(Py_TYPE(self))(self, obj, type); +} + +/// Returns the descr_set slot of the PyTypeObject. +inline descrsetfunc PepExt_Type_GetDescrSetSlot(PyTypeObject *t) +{ + return reinterpret_cast<descrsetfunc>(PepType_GetSlot(t, Py_tp_descr_set)); +} + +/// Returns the call slot of the PyTypeObject. +inline ternaryfunc PepExt_Type_GetCallSlot(PyTypeObject *t) +{ + return reinterpret_cast<ternaryfunc>(PepType_GetSlot(t, Py_tp_call)); +} + +/// Returns the new slot of the PyTypeObject. +inline newfunc PepExt_Type_GetNewSlot(PyTypeObject *t) +{ + return reinterpret_cast<newfunc>(PepType_GetSlot(t, Py_tp_new)); +} + +/// Returns the init slot of the PyTypeObject. +inline initproc PepExt_Type_GetInitSlot(PyTypeObject *t) +{ + return reinterpret_cast<initproc>(PepType_GetSlot(t, Py_tp_init)); +} + +/// Returns the free slot of the PyTypeObject. +inline freefunc PepExt_Type_GetFreeSlot(PyTypeObject *t) +{ + return reinterpret_cast<freefunc>(PepType_GetSlot(t, Py_tp_free)); +} + +/// Invokes the free slot of the PyTypeObject. +inline void PepExt_TypeCallFree(PyTypeObject *t, void *object) +{ + PepExt_Type_GetFreeSlot(t)(object); +} + +/// Invokes the free slot of the PyTypeObject. +inline void PepExt_TypeCallFree(PyObject *object) +{ + PepExt_Type_GetFreeSlot(Py_TYPE(object))(object); +} + +#endif // PEP384EXT_H diff --git a/sources/shiboken6/libshiboken/pep384impl.cpp b/sources/shiboken6/libshiboken/pep384impl.cpp new file mode 100644 index 000000000..5310207a3 --- /dev/null +++ b/sources/shiboken6/libshiboken/pep384impl.cpp @@ -0,0 +1,1319 @@ +// Copyright (C) 2023 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 + +#define PEP384_INTERN + +#include "sbkpython.h" +#include "autodecref.h" +#include "sbkstaticstrings.h" +#include "sbkstaticstrings_p.h" +#include "basewrapper.h" +#include "basewrapper_p.h" +#include "sbkenum.h" +#include "voidptr.h" + +#include <cstdlib> +#include <cstring> + +extern "C" +{ + +/* + * The documentation is located in `sources/pyside6/doc/developer/limited_api.rst`. + + * Here is the verification code for PyTypeObject. + * We create a type object and check if its fields + * appear at the right offsets. + */ +#ifdef Py_LIMITED_API + +#define make_dummy_int(x) (x * sizeof(void *)) +#define make_dummy(x) (reinterpret_cast<void *>(make_dummy_int(x))) + +static PyObject * +dummy_func(PyObject * /* self */, PyObject * /* args */) +{ + Py_RETURN_NONE; +} + +static struct PyMethodDef probe_methoddef[] = { + {"dummy", dummy_func, METH_NOARGS, nullptr}, + {nullptr, nullptr, 0, nullptr} +}; + +static PyGetSetDef probe_getseters[] = { + {nullptr, nullptr, nullptr, nullptr, nullptr} /* Sentinel */ +}; + +static PyMemberDef probe_members[] = { + {nullptr, 0, 0, 0, nullptr} /* Sentinel */ +}; + +#define probe_tp_dealloc make_dummy(1) +#define probe_tp_repr make_dummy(2) +#define probe_tp_call make_dummy(3) +#define probe_tp_getattro make_dummy(16) +#define probe_tp_setattro make_dummy(17) +#define probe_tp_str make_dummy(4) +#define probe_tp_traverse make_dummy(5) +#define probe_tp_clear make_dummy(6) +#define probe_tp_iternext make_dummy(7) +#define probe_tp_methods probe_methoddef +#define probe_tp_members probe_members +#define probe_tp_getset probe_getseters +#define probe_tp_descr_get make_dummy(10) +#define probe_tp_descr_set make_dummy(18) +#define probe_tp_init make_dummy(11) +#define probe_tp_alloc make_dummy(12) +#define probe_tp_new make_dummy(13) +#define probe_tp_free make_dummy(14) +#define probe_tp_is_gc make_dummy(15) + +#define probe_tp_name "type.probe" +#define probe_tp_basicsize make_dummy_int(42) + +static PyType_Slot typeprobe_slots[] = { + {Py_tp_dealloc, probe_tp_dealloc}, + {Py_tp_repr, probe_tp_repr}, + {Py_tp_call, probe_tp_call}, + {Py_tp_getattro, probe_tp_getattro}, + {Py_tp_setattro, probe_tp_setattro}, + {Py_tp_str, probe_tp_str}, + {Py_tp_traverse, probe_tp_traverse}, + {Py_tp_clear, probe_tp_clear}, + {Py_tp_iternext, probe_tp_iternext}, + {Py_tp_methods, probe_tp_methods}, + {Py_tp_members, probe_tp_members}, + {Py_tp_getset, probe_tp_getset}, + {Py_tp_descr_get, probe_tp_descr_get}, + {Py_tp_descr_set, probe_tp_descr_set}, + {Py_tp_init, probe_tp_init}, + {Py_tp_alloc, probe_tp_alloc}, + {Py_tp_new, probe_tp_new}, + {Py_tp_free, probe_tp_free}, + {Py_tp_is_gc, probe_tp_is_gc}, + {0, nullptr} +}; +static PyType_Spec typeprobe_spec = { + probe_tp_name, + probe_tp_basicsize, + 0, + Py_TPFLAGS_DEFAULT, + typeprobe_slots, +}; + +static void +check_PyTypeObject_valid() +{ + auto *typetype = &PyType_Type; + auto *obtype = reinterpret_cast<PyObject *>(typetype); + auto *probe_tp_base_obj = PyObject_GetAttr(obtype, Shiboken::PyMagicName::base()); + auto *probe_tp_base = reinterpret_cast<PyTypeObject *>(probe_tp_base_obj); + auto *probe_tp_bases = PyObject_GetAttr(obtype, Shiboken::PyMagicName::bases()); + auto *checkObj = PyType_FromSpecWithBases(&typeprobe_spec, probe_tp_bases); + auto *check = reinterpret_cast<PyTypeObject *>(checkObj); + PyObject *w = PyObject_GetAttr(obtype, Shiboken::PyMagicName::weakrefoffset()); + long probe_tp_weakrefoffset = PyLong_AsLong(w); + PyObject *d = PyObject_GetAttr(obtype, Shiboken::PyMagicName::dictoffset()); + long probe_tp_dictoffset = PyLong_AsLong(d); + PyObject *probe_tp_mro = PyObject_GetAttr(obtype, Shiboken::PyMagicName::mro()); + Shiboken::AutoDecRef tpDict(PepType_GetDict(check)); + auto *checkDict = tpDict.object(); + if (false + || strcmp(probe_tp_name, check->tp_name) != 0 + || probe_tp_basicsize != check->tp_basicsize + || probe_tp_dealloc != check->tp_dealloc + || probe_tp_repr != check->tp_repr + || probe_tp_call != check->tp_call + || probe_tp_getattro != check->tp_getattro + || probe_tp_setattro != check->tp_setattro + || probe_tp_str != check->tp_str + || probe_tp_traverse != check->tp_traverse + || probe_tp_clear != check->tp_clear + || probe_tp_weakrefoffset != typetype->tp_weaklistoffset + || probe_tp_iternext != check->tp_iternext + || probe_tp_methods != check->tp_methods + || probe_tp_getset != check->tp_getset + || probe_tp_base != typetype->tp_base + || !PyDict_Check(checkDict) + || !PyDict_GetItemString(checkDict, "dummy") + || probe_tp_descr_get != check->tp_descr_get + || probe_tp_descr_set != check->tp_descr_set + || probe_tp_dictoffset != typetype->tp_dictoffset + || probe_tp_init != check->tp_init + || probe_tp_alloc != check->tp_alloc + || probe_tp_new != check->tp_new + || probe_tp_free != check->tp_free + || probe_tp_is_gc != check->tp_is_gc + || probe_tp_bases != typetype->tp_bases + || probe_tp_mro != typetype->tp_mro + || Py_TPFLAGS_DEFAULT != (check->tp_flags & Py_TPFLAGS_DEFAULT)) + Py_FatalError("The structure of type objects has changed!"); + Py_DECREF(checkObj); + Py_DECREF(probe_tp_base_obj); + Py_DECREF(w); + Py_DECREF(d); + Py_DECREF(probe_tp_bases); + Py_DECREF(probe_tp_mro); +} + +#endif // Py_LIMITED_API + +/***************************************************************************** + * + * Additional for object.h / class properties + * + */ +#ifdef Py_LIMITED_API +/* + * This implementation of `_PyType_Lookup` works for lookup in our classes. + * The implementation ignores all caching and versioning and is also + * less optimized. This is reduced from the Python implementation. + */ + +/* Internal API to look for a name through the MRO, bypassing the method cache. + This returns a borrowed reference, and might set an exception. + 'error' is set to: -1: error with exception; 1: error without exception; 0: ok */ +static PyObject * +find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) +{ + Py_ssize_t i, n; + PyObject *mro, *res, *base; + + /* Look in tp_dict of types in MRO */ + mro = type->tp_mro; + + res = nullptr; + /* Keep a strong reference to mro because type->tp_mro can be replaced + during dict lookup, e.g. when comparing to non-string keys. */ + Py_INCREF(mro); + assert(PyTuple_Check(mro)); + n = PyTuple_GET_SIZE(mro); + for (i = 0; i < n; i++) { + base = PyTuple_GET_ITEM(mro, i); + assert(PyType_Check(base)); + auto *type = reinterpret_cast<PyTypeObject *>(base); + Shiboken::AutoDecRef dict(PepType_GetDict(type)); + assert(!dict.isNull() && PyDict_Check(dict.object())); + res = PyDict_GetItem(dict.object(), name); + if (res != nullptr) + break; + if (PyErr_Occurred()) { + *error = -1; + goto done; + } + } + *error = 0; +done: + Py_DECREF(mro); + return res; +} + +/* Internal API to look for a name through the MRO. + This returns a borrowed reference, and doesn't set an exception! */ +PyObject * +_PepType_Lookup(PyTypeObject *type, PyObject *name) +{ + PyObject *res; + int error; + + /* We may end up clearing live exceptions below, so make sure it's ours. */ + assert(!PyErr_Occurred()); + + res = find_name_in_mro(type, name, &error); + /* Only put NULL results into cache if there was no error. */ + if (error) { + /* It's not ideal to clear the error condition, + but this function is documented as not setting + an exception, and I don't want to change that. + E.g., when PyType_Ready() can't proceed, it won't + set the "ready" flag, so future attempts to ready + the same type will call it again -- hopefully + in a context that propagates the exception out. + */ + if (error == -1) { + PyErr_Clear(); + } + return nullptr; + } + return res; +} + +#endif // Py_LIMITED_API + +/***************************************************************************** + * + * Support for unicodeobject.h + * + */ +#ifdef Py_LIMITED_API + +// structs and macros modelled after their equivalents in +// cpython/Include/cpython/unicodeobject.h + +struct PepASCIIObject // since 3.12 +{ + PyObject_HEAD + Py_ssize_t length; /* Number of code points in the string */ + Py_hash_t hash; /* Hash value; -1 if not set */ + struct { + unsigned int interned:2; + unsigned int kind:3; + unsigned int compact:1; + unsigned int ascii:1; + unsigned int ready:1; + unsigned int :24; + } state; +}; + +struct PepASCIIObject_311 : public PepASCIIObject +{ + wchar_t *wstr; /* wchar_t representation (null-terminated) */ +}; + +struct PepCompactUnicodeObject // since 3.12 +{ + PepASCIIObject _base; + Py_ssize_t utf8_length; + char *utf8; /* UTF-8 representation (null-terminated) */ +}; + +struct PepCompactUnicodeObject_311 // since 3.12 +{ + PepASCIIObject_311 _base; + Py_ssize_t utf8_length; + char *utf8; /* UTF-8 representation (null-terminated) */ + Py_ssize_t wstr_length; /* Number of code points in wstr */ +}; + +struct PepUnicodeObject // since 3.12 +{ + PepCompactUnicodeObject _base; + union { + void *any; + Py_UCS1 *latin1; + Py_UCS2 *ucs2; + Py_UCS4 *ucs4; + } data; /* Canonical, smallest-form Unicode buffer */ +}; + +struct PepUnicodeObject_311 +{ + PepCompactUnicodeObject_311 _base; + union { + void *any; + Py_UCS1 *latin1; + Py_UCS2 *ucs2; + Py_UCS4 *ucs4; + } data; /* Canonical, smallest-form Unicode buffer */ +}; + +int _PepUnicode_KIND(PyObject *str) +{ + return reinterpret_cast<PepASCIIObject *>(str)->state.kind; +} + +int _PepUnicode_IS_ASCII(PyObject *str) +{ + auto *asciiObj = reinterpret_cast<PepASCIIObject *>(str); + return asciiObj->state.ascii; +} + +int _PepUnicode_IS_COMPACT(PyObject *str) +{ + auto *asciiObj = reinterpret_cast<PepASCIIObject *>(str); + return asciiObj->state.compact; +} + +static void *_PepUnicode_ASCII_DATA(PyObject *str) +{ + if (_PepRuntimeVersion() < 0x030C00) { + auto *asciiObj_311 = reinterpret_cast<PepASCIIObject_311 *>(str); + return asciiObj_311 + 1; + } + auto *asciiObj = reinterpret_cast<PepASCIIObject *>(str); + return asciiObj + 1; +} + +static void *_PepUnicode_COMPACT_DATA(PyObject *str) +{ + if (_PepUnicode_IS_ASCII(str) != 0) + return _PepUnicode_ASCII_DATA(str); + if (_PepRuntimeVersion() < 0x030C00) { + auto *compactObj_311 = reinterpret_cast<PepCompactUnicodeObject_311 *>(str); + return compactObj_311 + 1; + } + auto *compactObj = reinterpret_cast<PepCompactUnicodeObject *>(str); + return compactObj + 1; +} + +static void *_PepUnicode_NONCOMPACT_DATA(PyObject *str) +{ + return _PepRuntimeVersion() < 0x030C00 + ? reinterpret_cast<PepUnicodeObject_311 *>(str)->data.any + : reinterpret_cast<PepUnicodeObject *>(str)->data.any; +} + +void *_PepUnicode_DATA(PyObject *str) +{ + return _PepUnicode_IS_COMPACT(str) + ? _PepUnicode_COMPACT_DATA(str) : _PepUnicode_NONCOMPACT_DATA(str); +} + +// Fast path accessing UTF8 data without doing a conversion similar +// to _PyUnicode_AsUTF8String +static const char *utf8FastPath_311(PyObject *str) +{ + if (PyUnicode_GetLength(str) == 0) + return ""; + auto *asciiObj = reinterpret_cast<PepASCIIObject_311 *>(str); + if (asciiObj->state.kind != PepUnicode_1BYTE_KIND || asciiObj->state.compact == 0) + return nullptr; // Empirical: PyCompactUnicodeObject.utf8 is only valid for 1 byte + if (asciiObj->state.ascii) { + auto *data = asciiObj + 1; + return reinterpret_cast<const char *>(data); + } + auto *compactObj = reinterpret_cast<PepCompactUnicodeObject_311 *>(str); + if (compactObj->utf8_length) + return compactObj->utf8; + return nullptr; +} + +static const char *utf8FastPath(PyObject *str) +{ + if (PyUnicode_GetLength(str) == 0) + return ""; + auto *asciiObj = reinterpret_cast<PepASCIIObject *>(str); + if (asciiObj->state.kind != PepUnicode_1BYTE_KIND || asciiObj->state.compact == 0) + return nullptr; // Empirical: PyCompactUnicodeObject.utf8 is only valid for 1 byte + if (asciiObj->state.ascii) { + auto *data = asciiObj + 1; + return reinterpret_cast<const char *>(data); + } + auto *compactObj = reinterpret_cast<PepCompactUnicodeObject *>(str); + if (compactObj->utf8_length) + return compactObj->utf8; + return nullptr; +} + +const char *_PepUnicode_AsString(PyObject *str) +{ + /* + * This function is the surrogate for PyUnicode_AsUTF8, which keeps the data + * in the unicode object as long as that object exists. + * + * The function does too much if not optimized by utf8, because it keeps the + * string alive, unconditionally. + * We should not rely on this behavior and think of PyUnicode_AsUTF8, only. + */ +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +#define AT __FILE__ ":" TOSTRING(__LINE__) + + if (const auto *utf8 = _PepRuntimeVersion() < 0x030C00 + ? utf8FastPath_311(str) : utf8FastPath(str)) { + return utf8; + } + + static PyObject *cstring_dict = nullptr; + if (cstring_dict == nullptr) { + cstring_dict = PyDict_New(); + if (cstring_dict == nullptr) + Py_FatalError("Error in " AT); + } + PyObject *bytesStr = PyUnicode_AsEncodedString(str, "utf8", nullptr); + PyObject *entry = PyDict_GetItemWithError(cstring_dict, bytesStr); + if (entry == nullptr) { + int e = PyDict_SetItem(cstring_dict, bytesStr, bytesStr); + if (e != 0) + Py_FatalError("Error in " AT); + entry = bytesStr; + } + else + Py_DECREF(bytesStr); + return PyBytes_AsString(entry); +} +#endif // Py_LIMITED_API + +/***************************************************************************** + * + * Support for pydebug.h + * + */ +#ifdef Py_LIMITED_API + +static PyObject *sys_flags = nullptr; + +int +Pep_GetFlag(const char *name) +{ + static int initialized = 0; + int ret = -1; + + if (!initialized) { + sys_flags = PySys_GetObject("flags"); + // func gives no error if nullptr is returned and does not incref. + Py_XINCREF(sys_flags); + initialized = 1; + } + if (sys_flags != nullptr) { + PyObject *ob_ret = PyObject_GetAttrString(sys_flags, name); + if (ob_ret != nullptr) { + long long_ret = PyLong_AsLong(ob_ret); + Py_DECREF(ob_ret); + ret = (int) long_ret; + } + } + return ret; +} + +int +Pep_GetVerboseFlag() +{ + static int initialized = 0; + static int verbose_flag = -1; + + if (!initialized) { + verbose_flag = Pep_GetFlag("verbose"); + if (verbose_flag != -1) + initialized = 1; + } + return verbose_flag; +} +#endif // Py_LIMITED_API + +// Support for pyerrors.h + +#if defined(Py_LIMITED_API) || PY_VERSION_HEX < 0x030C0000 +// Emulate PyErr_GetRaisedException() using the deprecated PyErr_Fetch()/PyErr_Store() +PyObject *PepErr_GetRaisedException() +{ + PyObject *type{}; + PyObject *value{}; + PyObject *traceback{}; + PyErr_Fetch(&type, &value, &traceback); + Py_XINCREF(value); + PyErr_Restore(type, value, traceback); + return value; +} + +struct PepException_HEAD +{ + PyObject_HEAD + PyObject *x1; // dict + PyObject *args; +}; + +// PyException_GetArgs/PyException_SetArgs were added to the stable API in 3.12 +PyObject *PepException_GetArgs(PyObject *ex) +{ + auto *h = reinterpret_cast<PepException_HEAD *>(ex); + Py_XINCREF(h->args); + return h->args; +} + +LIBSHIBOKEN_API void PepException_SetArgs(PyObject *ex, PyObject *args) +{ + auto *h = reinterpret_cast<PepException_HEAD *>(ex); + Py_XINCREF(args); + auto *old = h->args; // Py_XSETREF() + h->args = args; + Py_XDECREF(old); + +} +#endif // Limited or < 3.12 + +/***************************************************************************** + * + * Support for code.h + * + */ +#ifdef Py_LIMITED_API + +int +PepCode_Get(PepCodeObject *co, const char *name) +{ + PyObject *ob = reinterpret_cast<PyObject *>(co); + PyObject *ob_ret; + int ret = -1; + + ob_ret = PyObject_GetAttrString(ob, name); + if (ob_ret != nullptr) { + long long_ret = PyLong_AsLong(ob_ret); + Py_DECREF(ob_ret); + ret = (int) long_ret; + } + return ret; +} + +int PepCode_Check(PyObject *o) +{ + return o != nullptr && std::strcmp(Py_TYPE(o)->tp_name, "code") == 0 ? 1 : 0; +} + +#endif // Py_LIMITED_API + +#if defined(Py_LIMITED_API) || defined(PYPY_VERSION) +PyObject *PepFunction_GetDefaults(PyObject *function) +{ + auto *ob_ret = PyObject_GetAttrString(function, "__defaults__"); + Py_XDECREF(ob_ret); // returns borrowed ref + return ob_ret != Py_None ? ob_ret : nullptr; +} + +#endif // defined(Py_LIMITED_API) || defined(PYPY_VERSION) + +/***************************************************************************** + * + * Support for datetime.h + * + */ +#ifdef Py_LIMITED_API + +datetime_struc *PyDateTimeAPI = nullptr; + +static PyTypeObject *dt_getCheck(const char *name) +{ + PyObject *op = PyObject_GetAttrString(PyDateTimeAPI->module, name); + if (op == nullptr) { + fprintf(stderr, "datetime.%s not found\n", name); + Py_FatalError("aborting"); + } + return reinterpret_cast<PyTypeObject *>(op); +} + +// init_DateTime is called earlier than our module init. +// We use the provided PyDateTime_IMPORT machinery. +datetime_struc * +init_DateTime(void) +{ + static int initialized = 0; + if (!initialized) { + PyDateTimeAPI = (datetime_struc *)malloc(sizeof(datetime_struc)); + if (PyDateTimeAPI == nullptr) + Py_FatalError("PyDateTimeAPI malloc error, aborting"); + PyDateTimeAPI->module = PyImport_ImportModule("datetime"); + if (PyDateTimeAPI->module == nullptr) + Py_FatalError("datetime module not found, aborting"); + PyDateTimeAPI->DateType = dt_getCheck("date"); + PyDateTimeAPI->DateTimeType = dt_getCheck("datetime"); + PyDateTimeAPI->TimeType = dt_getCheck("time"); + PyDateTimeAPI->DeltaType = dt_getCheck("timedelta"); + PyDateTimeAPI->TZInfoType = dt_getCheck("tzinfo"); + initialized = 1; + } + return PyDateTimeAPI; +} + +int +PyDateTime_Get(PyObject *ob, const char *name) +{ + PyObject *ob_ret; + int ret = -1; + + ob_ret = PyObject_GetAttrString(ob, name); + if (ob_ret != nullptr) { + long long_ret = PyLong_AsLong(ob_ret); + Py_DECREF(ob_ret); + ret = (int) long_ret; + } + return ret; +} + +PyObject * +PyDate_FromDate(int year, int month, int day) +{ + return PyObject_CallFunction((PyObject *)PyDateTimeAPI->DateType, + (char *)"(iii)", year, month, day); +} + +PyObject * +PyDateTime_FromDateAndTime(int year, int month, int day, + int hour, int min, int sec, int usec) +{ + return PyObject_CallFunction((PyObject *)PyDateTimeAPI->DateTimeType, + (char *)"(iiiiiii)", year, month, day, + hour, min, sec, usec); +} + +PyObject * +PyTime_FromTime(int hour, int min, int sec, int usec) +{ + return PyObject_CallFunction((PyObject *)PyDateTimeAPI->TimeType, + (char *)"(iiii)", hour, min, sec, usec); +} +#endif // Py_LIMITED_API + +/***************************************************************************** + * + * Support for pythonrun.h + * + */ +#ifdef Py_LIMITED_API + +// Flags are ignored in these simple helpers. +PyObject * +PyRun_String(const char *str, int start, PyObject *globals, PyObject *locals) +{ + PyObject *code = Py_CompileString(str, "pyscript", start); + PyObject *ret = nullptr; + + if (code != nullptr) { + ret = PyEval_EvalCode(code, globals, locals); + } + Py_XDECREF(code); + return ret; +} + +#endif // Py_LIMITED_API + +/***************************************************************************** + * + * Support for classobject.h + * + */ +#ifdef Py_LIMITED_API + +PyTypeObject *PepMethod_TypePtr = nullptr; + +static PyTypeObject *getMethodType(void) +{ + static const char prog[] = + "class _C:\n" + " def _m(self): pass\n" + "result = type(_C()._m)\n"; + return reinterpret_cast<PyTypeObject *>(PepRun_GetResult(prog)); +} + +// We have no access to PyMethod_New and must call types.MethodType, instead. +PyObject * +PyMethod_New(PyObject *func, PyObject *self) +{ + return PyObject_CallFunction((PyObject *)PepMethod_TypePtr, + (char *)"(OO)", func, self); +} + +PyObject * +PyMethod_Function(PyObject *im) +{ + PyObject *ret = PyObject_GetAttr(im, Shiboken::PyMagicName::func()); + + // We have to return a borrowed reference. + Py_DECREF(ret); + return ret; +} + +PyObject * +PyMethod_Self(PyObject *im) +{ + PyObject *ret = PyObject_GetAttr(im, Shiboken::PyMagicName::self()); + + // We have to return a borrowed reference. + // If we don't obey that here, then we get a test error! + Py_DECREF(ret); + return ret; +} +#endif // Py_LIMITED_API + +/***************************************************************************** + * + * Support for funcobject.h + * + */ +#ifdef Py_LIMITED_API + +PyObject * +PepFunction_Get(PyObject *ob, const char *name) +{ + PyObject *ret; + + // We have to return a borrowed reference. + ret = PyObject_GetAttrString(ob, name); + Py_XDECREF(ret); + return ret; +} + +// This became necessary after Windows was activated. + +PyTypeObject *PepFunction_TypePtr = nullptr; + +static PyTypeObject *getFunctionType(void) +{ + static const char prog[] = + "from types import FunctionType as result\n"; + return reinterpret_cast<PyTypeObject *>(PepRun_GetResult(prog)); +} +#endif // Py_LIMITED_API || Python 2 + +/***************************************************************************** + * + * Support for dictobject.h + * + */ + +/***************************************************************************** + * + * Extra support for signature.cpp + * + */ +#ifdef Py_LIMITED_API + +PyTypeObject *PepStaticMethod_TypePtr = nullptr; + +static PyTypeObject * +getStaticMethodType(void) +{ + static const char prog[] = + "result = type(str.__dict__['maketrans'])\n"; + return reinterpret_cast<PyTypeObject *>(PepRun_GetResult(prog)); +} + +typedef struct { + PyObject_HEAD + PyObject *sm_callable; + PyObject *sm_dict; +} staticmethod; + +PyObject * +PyStaticMethod_New(PyObject *callable) +{ + staticmethod *sm = (staticmethod *) + PyType_GenericAlloc(PepStaticMethod_TypePtr, 0); + if (sm != nullptr) { + Py_INCREF(callable); + sm->sm_callable = callable; + } + return reinterpret_cast<PyObject *>(sm); +} +#endif // Py_LIMITED_API + +#ifdef PYPY_VERSION +PyTypeObject *PepBuiltinMethod_TypePtr = nullptr; + +static PyTypeObject * +getBuiltinMethodType(void) +{ + // PYSIDE-535: PyPy has a special builtin method that acts almost like PyCFunction. + // + // There is no public declaration for the "builtin method" type. + // We also cannot grep it with a Python script since the import is too early. + // Pick a demo "builtin method" by using the VoidPtr type. + // Create the equivalent of + // "from shiboken6.Shiboken import VoidPtr\n" + // "result = type(VoidPtr(0).toBytes)\n"; + auto *pyVoidP = reinterpret_cast<PyObject *>(SbkVoidPtr_TypeF()); + Shiboken::AutoDecRef arg(Py_BuildValue("i", 0)); + Shiboken::AutoDecRef inst(PyObject_CallFunctionObjArgs(pyVoidP, arg.object(), nullptr)); + Shiboken::AutoDecRef meth(PyObject_GetAttrString(inst, "toBytes")); + auto *result = reinterpret_cast<PyTypeObject *>(PyObject_Type(meth)); + return result; +} +#endif + +/***************************************************************************** + * + * Common newly needed functions + * + */ + +// The introduction of heaptypes converted many type names to the +// dotted form, since PyType_FromSpec uses it to compute the module +// name. This function reverts this effect. +const char * +PepType_GetNameStr(PyTypeObject *type) +{ + const char *ret = type->tp_name; + const char *nodots = std::strrchr(ret, '.'); + if (nodots) + ret = nodots + 1; + return ret; +} + +// PYSIDE-2264: Find the _functools or functools module and retrieve the +// partial function. This can be tampered with, check carefully. +PyObject * +Pep_GetPartialFunction(void) +{ + static bool initialized = false; + static PyObject *result{}; + if (initialized) { + Py_INCREF(result); + return result; + } + auto *functools = PyImport_ImportModule("_functools"); + if (!functools) { + PyErr_Clear(); + functools = PyImport_ImportModule("functools"); + } + if (!functools) + Py_FatalError("functools cannot be found"); + result = PyObject_GetAttrString(functools, "partial"); + if (!result || !PyCallable_Check(result)) + Py_FatalError("partial not found or not a function"); + initialized = true; + return result; +} + +/***************************************************************************** + * + * Newly introduced convenience functions + * + */ +#ifdef Py_LIMITED_API + +PyObject * +PyImport_GetModule(PyObject *name) +{ + PyObject *m; + PyObject *modules = PyImport_GetModuleDict(); + if (modules == NULL) { + PyErr_SetString(PyExc_RuntimeError, "unable to get sys.modules"); + return NULL; + } + Py_INCREF(modules); + if (PyDict_CheckExact(modules)) { + m = PyDict_GetItemWithError(modules, name); /* borrowed */ + Py_XINCREF(m); + } + else { + m = PyObject_GetItem(modules, name); + if (m == NULL && PyErr_ExceptionMatches(PyExc_KeyError)) { + PyErr_Clear(); + } + } + Py_DECREF(modules); + return m; +} + +#endif // Py_LIMITED_API + +// 2020-06-16: For simplicity of creating arbitrary things, this function +// is now made public. + +PyObject * +PepRun_GetResult(const char *command) +{ + /* + * Evaluate a string and return the variable `result` + */ + PyObject *d, *v, *res; + + d = PyDict_New(); + if (d == nullptr + || PyDict_SetItem(d, Shiboken::PyMagicName::builtins(), PyEval_GetBuiltins()) < 0) { + return nullptr; + } + v = PyRun_String(command, Py_file_input, d, d); + res = v ? PyDict_GetItem(d, Shiboken::PyName::result()) : nullptr; + Py_XDECREF(v); + Py_DECREF(d); + return res; +} + +PyTypeObject *PepType_Type_tp_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) +{ + auto ret = PyType_Type.tp_new(metatype, args, kwds); + return reinterpret_cast<PyTypeObject *>(ret); +} + +/***************************************************************************** + * + * Extra support for name mangling + * + */ + +#ifdef Py_LIMITED_API +// We keep these definitions local, because they don't work in Python 2. +# define PyUnicode_GET_LENGTH(op) PyUnicode_GetLength((PyObject *)(op)) +# define PyUnicode_READ_CHAR(u, i) PyUnicode_ReadChar((PyObject *)(u), (i)) +#endif // Py_LIMITED_API + +PyObject * +_Pep_PrivateMangle(PyObject *self, PyObject *name) +{ + /* + * Name mangling: __private becomes _classname__private. + * This function is modelled after _Py_Mangle, but is optimized + * a little for our purpose. + */ + if (PyUnicode_READ_CHAR(name, 0) != '_' || + PyUnicode_READ_CHAR(name, 1) != '_') { + Py_INCREF(name); + return name; + } + size_t nlen = PyUnicode_GET_LENGTH(name); + /* Don't mangle __id__ or names with dots. */ + if ((PyUnicode_READ_CHAR(name, nlen-1) == '_' && + PyUnicode_READ_CHAR(name, nlen-2) == '_') || + PyUnicode_FindChar(name, '.', 0, nlen, 1) != -1) { + Py_INCREF(name); + return name; + } + Shiboken::AutoDecRef privateobj(PyObject_GetAttr( + reinterpret_cast<PyObject *>(Py_TYPE(self)), Shiboken::PyMagicName::name())); + + // PYSIDE-1436: _Py_Mangle is no longer exposed; implement it always. + // The rest of this function is our own implementation of _Py_Mangle. + // Please compare the original function in compile.c . + size_t plen = PyUnicode_GET_LENGTH(privateobj.object()); + /* Strip leading underscores from class name */ + size_t ipriv = 0; + while (PyUnicode_READ_CHAR(privateobj.object(), ipriv) == '_') + ipriv++; + if (ipriv == plen) { + Py_INCREF(name); + return name; /* Don't mangle if class is just underscores */ + } + plen -= ipriv; + + if (plen + nlen >= PY_SSIZE_T_MAX - 1) { + PyErr_SetString(PyExc_OverflowError, + "private identifier too large to be mangled"); + return nullptr; + } + size_t const amount = ipriv + 1 + plen + nlen; + size_t const big_stack = 1000; + wchar_t bigbuf[big_stack]; + wchar_t *resbuf = amount <= big_stack ? bigbuf : (wchar_t *)malloc(sizeof(wchar_t) * amount); + if (!resbuf) + return nullptr; + /* ident = "_" + priv[ipriv:] + ident # i.e. 1+plen+nlen bytes */ + resbuf[0] = '_'; + if (PyUnicode_AsWideChar(privateobj, resbuf + 1, ipriv + plen) < 0) + return nullptr; + if (PyUnicode_AsWideChar(name, resbuf + ipriv + plen + 1, nlen) < 0) + return nullptr; + PyObject *result = PyUnicode_FromWideChar(resbuf + ipriv, 1 + plen + nlen); + if (amount > big_stack) + free(resbuf); + return result; +} + +/***************************************************************************** + * + * Runtime support for Python 3.8 incompatibilities + * + */ + +int PepRuntime_38_flag = 0; + +static void +init_PepRuntime() +{ + // We expect a string of the form "\d\.\d+\." + const char *version = Py_GetVersion(); + if (version[0] < '3') + return; + if (std::atoi(version + 2) >= 8) + PepRuntime_38_flag = 1; +} + +static long _GetPepRuntimeVersion() +{ + auto *version = PySys_GetObject("version_info"); + const auto major = PyLong_AsLong(PyTuple_GetItem(version, 0)); + const auto minor = PyLong_AsLong(PyTuple_GetItem(version, 1)); + const auto micro = PyLong_AsLong(PyTuple_GetItem(version, 2)); + return major << 16 | minor << 8 | micro; +} + +long _PepRuntimeVersion() +{ + static const auto number = _GetPepRuntimeVersion(); + return number; +} + +/***************************************************************************** + * + * PYSIDE-535: Support for PyPy + * + * This has the nice side effect of a more clean implementation, + * and we don't keep the old macro version. + * + */ + +/////////////////////////////////////////////////////////////////////// +// +// PEP 697: Support for embedded type structures. +// +// According to `https://docs.python.org/3/c-api/object.html?highlight=pyobject_gettypedata#c.PyObject_GetTypeData` +// the function `PyObject_GetTypeData` should belong to the Stable API +// since version 3.12.0, but it does not. We use instead some copies +// from Python source code. + +#if !defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030C0000 + +# define PepObject_GetTypeData PyObject_GetTypeData + +SbkObjectTypePrivate *PepType_SOTP(PyTypeObject *type) +{ + // PYSIDE-2676: Use the meta type explicitly. + // A derived type would fail the offset calculation. + static auto *meta = SbkObjectType_TypeF(); + assert(SbkObjectType_Check(type)); + auto *obType = reinterpret_cast<PyObject *>(type); + void *data = PyObject_GetTypeData(obType, meta); + return reinterpret_cast<SbkObjectTypePrivate *>(data); +} + +void PepType_SOTP_delete(PyTypeObject * /*type*/) +{ +} + +#else + +// The following comments are directly copied from Python 3.12 +// + +// Make sure we have maximum alignment, even if the current compiler +// does not support max_align_t. Note that: +// - Autoconf reports alignment of unknown types to 0. +// - 'long double' has maximum alignment on *most* platforms, +// looks like the best we can do for pre-C11 compilers. +// - The value is tested, see test_alignof_max_align_t +# if !defined(ALIGNOF_MAX_ALIGN_T) || ALIGNOF_MAX_ALIGN_T == 0 +# undef ALIGNOF_MAX_ALIGN_T +# define ALIGNOF_MAX_ALIGN_T alignof(long double) +# endif + +/* Align up to the nearest multiple of alignof(max_align_t) + * (like _Py_ALIGN_UP, but for a size rather than pointer) + */ +static Py_ssize_t _align_up(Py_ssize_t size) +{ + return (size + ALIGNOF_MAX_ALIGN_T - 1) & ~(ALIGNOF_MAX_ALIGN_T - 1); +} + +static void *PepObject_GetTypeData(PyObject *obj, PyTypeObject *cls) +{ + assert(PyObject_TypeCheck(obj, cls)); + return reinterpret_cast<char *>(obj) + _align_up(cls->tp_base->tp_basicsize); +} +// +/////////////////////////////////////////////////////////////////////// + +/* + * PyTypeObject extender + */ + +static std::unordered_map<PyTypeObject *, SbkObjectTypePrivate > SOTP_extender{}; +static thread_local PyTypeObject *SOTP_key{}; +static thread_local SbkObjectTypePrivate *SOTP_value{}; + +SbkObjectTypePrivate *PepType_SOTP(PyTypeObject *type) +{ + static auto *meta = SbkObjectType_TypeF(); + static bool use_312 = _PepRuntimeVersion() >= 0x030C00; + assert(SbkObjectType_Check(type)); + if (use_312) { + auto *obType = reinterpret_cast<PyObject *>(type); + void *data = PepObject_GetTypeData(obType, meta); + return reinterpret_cast<SbkObjectTypePrivate *>(data); + } + if (type == SOTP_key) + return SOTP_value; + auto it = SOTP_extender.find(type); + if (it == SOTP_extender.end()) { + it = SOTP_extender.insert({type, {}}).first; + memset(&it->second, 0, sizeof(SbkObjectTypePrivate)); + } + SOTP_key = type; + SOTP_value = &it->second; + return SOTP_value; +} + +void PepType_SOTP_delete(PyTypeObject *type) +{ + static bool use_312 = _PepRuntimeVersion() >= 0x030C00; + assert(SbkObjectType_Check(type)); + if (use_312) + return; + SOTP_extender.erase(type); + SOTP_key = nullptr; +} + +#endif // !defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030C0000 + +/* + * SbkEnumType extender + */ +static std::unordered_map<SbkEnumType *, SbkEnumTypePrivate> SETP_extender{}; +static thread_local SbkEnumType *SETP_key{}; +static thread_local SbkEnumTypePrivate *SETP_value{}; + +SbkEnumTypePrivate *PepType_SETP(SbkEnumType *enumType) +{ + // PYSIDE-2230: This makes no sense at all for Enum types. + if (enumType == SETP_key) + return SETP_value; + auto it = SETP_extender.find(enumType); + if (it == SETP_extender.end()) { + it = SETP_extender.insert({enumType, {}}).first; + memset(&it->second, 0, sizeof(SbkEnumTypePrivate)); + } + SETP_key = enumType; + SETP_value = &it->second; + return SETP_value; +} + +void PepType_SETP_delete(SbkEnumType *enumType) +{ + SETP_extender.erase(enumType); + SETP_key = nullptr; +} + +#ifdef Py_LIMITED_API +static PyObject *emulatePyType_GetDict(PyTypeObject *type) +{ + if (_PepRuntimeVersion() < 0x030C00 || type->tp_dict) { + auto *res = type->tp_dict; + Py_XINCREF(res); + return res; + } + // PYSIDE-2230: Here we are really cheating. We don't know how to + // access an internal dict, and so we simply pretend + // it were an empty dict. This works great for our types. + // This was an unexpectedly simple solution :D + return PyDict_New(); +} +#endif + +// PyType_GetDict: replacement for <static type>.tp_dict, which is +// zero for builtin types since 3.12. +PyObject *PepType_GetDict(PyTypeObject *type) +{ +#if !defined(Py_LIMITED_API) +# if PY_VERSION_HEX >= 0x030C0000 + return PyType_GetDict(type); +# else + // pre 3.12 fallback code, mimicking the addref-behavior. + Py_XINCREF(type->tp_dict); + return type->tp_dict; +# endif +#else + return emulatePyType_GetDict(type); +#endif // Py_LIMITED_API +} + +int PepType_SetDict(PyTypeObject *type, PyObject *dict) +{ + type->tp_dict = dict; + return 0; +} + +// Pre 3.10, PyType_GetSlot() would only work for heap types. +// FIXME: PyType_GetSlot() can be used unconditionally when the +// minimum limited API version is >= 3.10. +void *PepType_GetSlot(PyTypeObject *type, int aSlot) +{ + static const bool is310 = _PepRuntimeVersion() >= 0x030A00; + if (is310 || (type->tp_flags & Py_TPFLAGS_HEAPTYPE) != 0) + return PyType_GetSlot(type, aSlot); + + switch (aSlot) { + case Py_tp_alloc: + return reinterpret_cast<void *>(type->tp_alloc); + case Py_tp_getattro: + return reinterpret_cast<void *>(type->tp_getattro); + case Py_tp_setattro: + return reinterpret_cast<void *>(type->tp_setattro); + case Py_tp_descr_get: + return reinterpret_cast<void *>(type->tp_descr_get); + case Py_tp_descr_set: + return reinterpret_cast<void *>(type->tp_descr_set); + case Py_tp_call: + return reinterpret_cast<void *>(type->tp_call); + case Py_tp_new: + return reinterpret_cast<void *>(type->tp_new); + case Py_tp_init: + return reinterpret_cast<void *>(type->tp_init); + case Py_tp_free: + return reinterpret_cast<void *>(type->tp_free); + } + assert(false); + return nullptr; +} + +/*************************************************************************** + * + * PYSIDE-535: The enum/flag error + * ------------------------------- + * + * This is a fragment of the code which was used to find the enum/flag + * alias error. See the change to `setTypeConverter` in sbkenum.cpp . + * + +Usage: + +python3 -c "from PySide6 import QtCore" 2>&1 | python3 tools/debug_renamer.py | uniq -c | head -10 + + 5 PepType_ExTP:940 x_A SOTP s=96 + 4 PepType_ExTP:940 x_B SETP s=24 + 2 PepType_ExTP:940 x_C PFTP s=16 + 4 PepType_ExTP:940 x_D SETP s=24 + 1 PepType_ExTP:940 x_C SETP s=24 + 2 PepType_ExTP:940 x_E PFTP s=16 + 4 PepType_ExTP:940 x_F SETP s=24 + 1 PepType_ExTP:940 x_E SETP s=24 + 4 PepType_ExTP:940 x_G SETP s=24 + 4 PepType_ExTP:940 x_H SETP s=24 + +static inline void *PepType_ExTP(PyTypeObject *type, size_t size) +{ + static const char *env_p = std::getenv("PFTP"); + if (env_p) { + static PyTypeObject *alias{}; + const char *kind = size == sizeof(SbkObjectTypePrivate) ? "SOTP" : + size == sizeof(SbkEnumTypePrivate) ? "SETP" : + size == sizeof(SbkQFlagsTypePrivate) ? "PFTP" : + "unk."; + fprintf(stderr, "%s:%d %p x %s s=%ld\n", __func__, __LINE__, type, kind, size); + PyObject *kill{}; + if (strlen(env_p) > 0) { + if (size == sizeof(SbkQFlagsTypePrivate)) { + if (alias == nullptr) + alias = type; + } + if (size != sizeof(SbkQFlagsTypePrivate)) { + if (type == alias) + Py_INCREF(kill); + } + } + } + const auto ikey = reinterpret_cast<std::uintptr_t>(type); + if (ikey == cached_key) + return cached_value; + auto it = SOTP_extender.find(ikey); + if (it == SOTP_extender.end()) { + PepType_ExTP_init(type, size); + return PepType_ExTP(type, size); + } + cached_key = ikey; + cached_value = reinterpret_cast<void *>(it->second); + return cached_value; +} +*/ + +/***************************************************************************** + * + * Module Initialization + * + */ + +void +Pep384_Init() +{ + init_PepRuntime(); +#ifdef Py_LIMITED_API + check_PyTypeObject_valid(); + Pep_GetVerboseFlag(); + PepMethod_TypePtr = getMethodType(); + PepFunction_TypePtr = getFunctionType(); + PepStaticMethod_TypePtr = getStaticMethodType(); +#endif // Py_LIMITED_API +#ifdef PYPY_VERSION + PepBuiltinMethod_TypePtr = getBuiltinMethodType(); +#endif +} + +} // extern "C" diff --git a/sources/shiboken6/libshiboken/pep384impl.h b/sources/shiboken6/libshiboken/pep384impl.h new file mode 100644 index 000000000..7188366e2 --- /dev/null +++ b/sources/shiboken6/libshiboken/pep384impl.h @@ -0,0 +1,611 @@ +// Copyright (C) 2018 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 + +#ifndef PEP384IMPL_H +#define PEP384IMPL_H + +extern "C" +{ + +/***************************************************************************** + * + * RESOLVED: memoryobject.h + * + */ + +// Extracted into bufferprocs27.h +#ifdef Py_LIMITED_API +#include "bufferprocs_py37.h" +#endif + +/***************************************************************************** + * + * RESOLVED: object.h + * + */ +#ifdef Py_LIMITED_API +// Why the hell is this useful debugging function not allowed? +// BTW: When used, it breaks on Windows, intentionally! +LIBSHIBOKEN_API void _PyObject_Dump(PyObject *); +#endif + +/* + * There are a few structures that are needed, but cannot be used without + * breaking the API. We use some heuristics to get those fields anyway + * and validate that we really found them, see pep384impl.cpp . + */ + +#ifdef Py_LIMITED_API + +/* + * These are the type object fields that we use. + * We will verify that they never change. + * The unused fields are intentionally named as "void *Xnn" because + * the chance is smaller to forget to validate a field. + * When we need more fields, we replace it back and add it to the + * validation. + */ +typedef struct _typeobject { + PyVarObject ob_base; + const char *tp_name; + Py_ssize_t tp_basicsize; + void *X03; // Py_ssize_t tp_itemsize; +#ifdef PEP384_INTERN + destructor tp_dealloc; +#else + destructor X04; +#endif + void *X05; // Py_ssize_t tp_vectorcall_offset; + void *X06; // getattrfunc tp_getattr; + void *X07; // setattrfunc tp_setattr; + void *X08; // PyAsyncMethods *tp_as_async; +#ifdef PEP384_INTERN + reprfunc tp_repr; +#else + reprfunc X09; +#endif + void *X10; // PyNumberMethods *tp_as_number; + void *X11; // PySequenceMethods *tp_as_sequence; + void *X12; // PyMappingMethods *tp_as_mapping; + void *X13; // hashfunc tp_hash; +#ifdef PEP384_INTERN + ternaryfunc tp_call; +#else + ternaryfunc X14; +#endif + reprfunc tp_str; // Only used for PEP384_INTERN and a shiboken test + getattrofunc tp_getattro; + setattrofunc tp_setattro; + void *X18; // PyBufferProcs *tp_as_buffer; + unsigned long tp_flags; + void *X20; // const char *tp_doc; +#ifdef PEP384_INTERN + traverseproc tp_traverse; + inquiry tp_clear; +#else + traverseproc X21; + inquiry X22; +#endif + void *X23; // richcmpfunc tp_richcompare; + Py_ssize_t tp_weaklistoffset; + void *X25; // getiterfunc tp_iter; +#ifdef PEP384_INTERN + iternextfunc tp_iternext; +#else + iternextfunc X26; +#endif + struct PyMethodDef *tp_methods; + struct PyMemberDef *tp_members; + struct PyGetSetDef *tp_getset; + struct _typeobject *tp_base; +#ifdef PEP384_INTERN + PyObject *tp_dict; + descrgetfunc tp_descr_get; + descrsetfunc tp_descr_set; +#else + void *X31; + descrgetfunc X32; + descrsetfunc X33; +#endif + Py_ssize_t tp_dictoffset; +#ifdef PEP384_INTERN + initproc tp_init; + allocfunc tp_alloc; +#else + initproc X39; + allocfunc X40; +#endif + newfunc tp_new; +#ifdef PEP384_INTERN + freefunc tp_free; + inquiry tp_is_gc; /* For PyObject_IS_GC */ +#else + freefunc X41; + inquiry X42; /* For PyObject_IS_GC */ +#endif + PyObject *tp_bases; + PyObject *tp_mro; /* method resolution order */ + +} PyTypeObject; + +#ifndef PyObject_IS_GC +/* Test if an object has a GC head */ +#define PyObject_IS_GC(o) \ + (PyType_IS_GC(Py_TYPE(o)) \ + && (Py_TYPE(o)->tp_is_gc == NULL || Py_TYPE(o)->tp_is_gc(o))) +#endif + +LIBSHIBOKEN_API PyObject *_PepType_Lookup(PyTypeObject *type, PyObject *name); + +#else // Py_LIMITED_API + +#define _PepType_Lookup(type, name) _PyType_Lookup(type, name) + +#endif // Py_LIMITED_API + +/// PYSIDE-939: We need the runtime version, given major << 16 + minor << 8 + micro +LIBSHIBOKEN_API long _PepRuntimeVersion(); +/***************************************************************************** + * + * PYSIDE-535: Implement a clean type extension for PyPy + * + */ + +struct SbkObjectTypePrivate; + +LIBSHIBOKEN_API SbkObjectTypePrivate *PepType_SOTP(PyTypeObject *type); +LIBSHIBOKEN_API void PepType_SOTP_delete(PyTypeObject *type); + +struct SbkEnumType; +struct SbkEnumTypePrivate; + +LIBSHIBOKEN_API SbkEnumTypePrivate *PepType_SETP(SbkEnumType *type); +LIBSHIBOKEN_API void PepType_SETP_delete(SbkEnumType *enumType); + +struct PySideQFlagsType; +struct SbkQFlagsTypePrivate; + +/*****************************************************************************/ + +// functions used everywhere +LIBSHIBOKEN_API const char *PepType_GetNameStr(PyTypeObject *type); + +LIBSHIBOKEN_API PyObject *Pep_GetPartialFunction(void); + +/***************************************************************************** + * + * RESOLVED: pydebug.h + * + */ +#ifdef Py_LIMITED_API +/* + * We have no direct access to Py_VerboseFlag because debugging is not + * supported. The python developers are partially a bit too rigorous. + * Instead, we compute the value and use a function call macro. + * Was before: extern LIBSHIBOKEN_API int Py_VerboseFlag; + */ +LIBSHIBOKEN_API int Pep_GetFlag(const char *name); +LIBSHIBOKEN_API int Pep_GetVerboseFlag(void); +#endif + +// pyerrors.h +#if defined(Py_LIMITED_API) || PY_VERSION_HEX < 0x030C0000 +LIBSHIBOKEN_API PyObject *PepErr_GetRaisedException(); +LIBSHIBOKEN_API PyObject *PepException_GetArgs(PyObject *ex); +LIBSHIBOKEN_API void PepException_SetArgs(PyObject *ex, PyObject *args); +#else +# define PepErr_GetRaisedException PyErr_GetRaisedException +# define PepException_GetArgs PyException_GetArgs +# define PepException_SetArgs PyException_SetArgs +#endif + +/***************************************************************************** + * + * RESOLVED: unicodeobject.h + * + */ + +/////////////////////////////////////////////////////////////////////// +// +// PYSIDE-813: About The Length Of Unicode Objects +// ----------------------------------------------- +// +// In Python 2 and before Python 3.3, the macro PyUnicode_GET_SIZE +// worked fine and really like a macro. +// +// Meanwhile, the unicode objects have changed their layout very much, +// and the former cheap macro call has become a real function call +// that converts objects and needs PyMemory. +// +// That is not only inefficient, but also requires the GIL! +// This problem was visible by debug Python and qdatastream_test.py . +// It was found while fixing the refcount problem of PYSIDE-813 which +// needed a debug Python. +// + +// PyUnicode_GetSize is deprecated in favor of PyUnicode_GetLength. +#define PepUnicode_GetLength(op) PyUnicode_GetLength((PyObject *)(op)) + +// Unfortunately, we cannot ask this at runtime +// #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030A0000 +// FIXME: Python 3.10: Replace _PepUnicode_AsString by PyUnicode_AsUTF8 +#ifdef Py_LIMITED_API + +LIBSHIBOKEN_API const char *_PepUnicode_AsString(PyObject *); + +enum PepUnicode_Kind { +#if PY_VERSION_HEX < 0x030C0000 + PepUnicode_WCHAR_KIND = 0, +#endif + PepUnicode_1BYTE_KIND = 1, + PepUnicode_2BYTE_KIND = 2, + PepUnicode_4BYTE_KIND = 4 +}; + +LIBSHIBOKEN_API int _PepUnicode_KIND(PyObject *); +LIBSHIBOKEN_API int _PepUnicode_IS_ASCII(PyObject *str); +LIBSHIBOKEN_API int _PepUnicode_IS_COMPACT(PyObject *str); + +LIBSHIBOKEN_API void *_PepUnicode_DATA(PyObject *str); + +#else + +enum PepUnicode_Kind { +#if PY_VERSION_HEX < 0x030C0000 + PepUnicode_WCHAR_KIND = PyUnicode_WCHAR_KIND, +#endif + PepUnicode_1BYTE_KIND = PyUnicode_1BYTE_KIND, + PepUnicode_2BYTE_KIND = PyUnicode_2BYTE_KIND, + PepUnicode_4BYTE_KIND = PyUnicode_4BYTE_KIND +}; + +#define _PepUnicode_AsString PyUnicode_AsUTF8 +#define _PepUnicode_KIND PyUnicode_KIND +#define _PepUnicode_DATA PyUnicode_DATA +#define _PepUnicode_IS_COMPACT PyUnicode_IS_COMPACT +#define _PepUnicode_IS_ASCII PyUnicode_IS_ASCII +#endif + +/***************************************************************************** + * + * RESOLVED: bytesobject.h + * + */ +#ifdef Py_LIMITED_API +#define PyBytes_AS_STRING(op) PyBytes_AsString(op) +#define PyBytes_GET_SIZE(op) PyBytes_Size(op) +#endif + +/***************************************************************************** + * + * RESOLVED: floatobject.h + * + */ +#ifdef Py_LIMITED_API +#define PyFloat_AS_DOUBLE(op) PyFloat_AsDouble(op) +#endif + +/***************************************************************************** + * + * RESOLVED: tupleobject.h + * + */ +#ifdef Py_LIMITED_API +#define PyTuple_GET_ITEM(op, i) PyTuple_GetItem((PyObject *)op, i) +#define PyTuple_SET_ITEM(op, i, v) PyTuple_SetItem(op, i, v) +#define PyTuple_GET_SIZE(op) PyTuple_Size((PyObject *)op) +#endif + +/***************************************************************************** + * + * RESOLVED: listobject.h + * + */ +#ifdef Py_LIMITED_API +#define PyList_GET_ITEM(op, i) PyList_GetItem(op, i) +#define PyList_SET_ITEM(op, i, v) PyList_SetItem(op, i, v) +#define PyList_GET_SIZE(op) PyList_Size(op) +#endif + +/***************************************************************************** + * + * RESOLVED: methodobject.h + * + */ + +#ifdef Py_LIMITED_API + +using PyCFunctionObject = struct _pycfunc; +#define PyCFunction_GET_FUNCTION(func) PyCFunction_GetFunction((PyObject *)func) +#define PyCFunction_GET_SELF(func) PyCFunction_GetSelf((PyObject *)func) +#define PyCFunction_GET_FLAGS(func) PyCFunction_GetFlags((PyObject *)func) +#define PepCFunction_GET_NAMESTR(func) \ + _PepUnicode_AsString(PyObject_GetAttrString((PyObject *)func, "__name__")) +#else +#define PepCFunction_GET_NAMESTR(func) ((func)->m_ml->ml_name) +#endif + +/***************************************************************************** + * + * RESOLVED: pythonrun.h + * + */ +#ifdef Py_LIMITED_API +LIBSHIBOKEN_API PyObject *PyRun_String(const char *, int, PyObject *, PyObject *); +#endif + +/***************************************************************************** + * + * RESOLVED: abstract.h + * + */ +#ifdef Py_LIMITED_API + +// This definition breaks the limited API a little, because it re-enables the +// buffer functions. +// But this is no problem as we check it's validity for every version. + +// PYSIDE-1960 The buffer interface is since Python 3.11 part of the stable +// API and we do not need to check the compatibility by hand anymore. + +typedef struct { + getbufferproc bf_getbuffer; + releasebufferproc bf_releasebuffer; +} PyBufferProcs; + +typedef struct _Pepbuffertype { + PyVarObject ob_base; + void *skip[17]; + PyBufferProcs *tp_as_buffer; +} PepBufferType; + +#define PepType_AS_BUFFER(type) \ + reinterpret_cast<PepBufferType *>(type)->tp_as_buffer + +#define PyObject_CheckBuffer(obj) \ + ((PepType_AS_BUFFER(Py_TYPE(obj)) != NULL) && \ + (PepType_AS_BUFFER(Py_TYPE(obj))->bf_getbuffer != NULL)) + +LIBSHIBOKEN_API int PyObject_GetBuffer(PyObject *ob, Pep_buffer *view, int flags); +LIBSHIBOKEN_API void PyBuffer_Release(Pep_buffer *view); + +#else + +#define Pep_buffer Py_buffer +#define PepType_AS_BUFFER(type) ((type)->tp_as_buffer) + +#endif /* Py_LIMITED_API */ + +/***************************************************************************** + * + * RESOLVED: funcobject.h + * + */ +#ifdef Py_LIMITED_API +typedef struct _func PyFunctionObject; + +extern LIBSHIBOKEN_API PyTypeObject *PepFunction_TypePtr; +LIBSHIBOKEN_API PyObject *PepFunction_Get(PyObject *, const char *); + +#define PyFunction_Check(op) (Py_TYPE(op) == PepFunction_TypePtr) +#define PyFunction_GET_CODE(func) PyFunction_GetCode(func) + +#define PyFunction_GetCode(func) PepFunction_Get((PyObject *)func, "__code__") +#define PepFunction_GetName(func) PepFunction_Get((PyObject *)func, "__name__") +#else +#define PepFunction_TypePtr (&PyFunction_Type) +#define PepFunction_GetName(func) (((PyFunctionObject *)func)->func_name) +#endif + +/***************************************************************************** + * + * RESOLVED: classobject.h + * + */ +#ifdef Py_LIMITED_API + +typedef struct _meth PyMethodObject; + +extern LIBSHIBOKEN_API PyTypeObject *PepMethod_TypePtr; + +LIBSHIBOKEN_API PyObject *PyMethod_New(PyObject *, PyObject *); +LIBSHIBOKEN_API PyObject *PyMethod_Function(PyObject *); +LIBSHIBOKEN_API PyObject *PyMethod_Self(PyObject *); + +#define PyMethod_Check(op) ((op)->ob_type == PepMethod_TypePtr) + +#define PyMethod_GET_SELF(op) PyMethod_Self(op) +#define PyMethod_GET_FUNCTION(op) PyMethod_Function(op) +#endif + +/***************************************************************************** + * + * RESOLVED: code.h + * + */ +#ifdef Py_LIMITED_API +/* Bytecode object */ + +// we have to grab the code object from python +typedef struct _code PepCodeObject; + +LIBSHIBOKEN_API int PepCode_Get(PepCodeObject *co, const char *name); +LIBSHIBOKEN_API int PepCode_Check(PyObject *o); + +# define PepCode_GET_FLAGS(o) PepCode_Get(o, "co_flags") +# define PepCode_GET_ARGCOUNT(o) PepCode_Get(o, "co_argcount") + +LIBSHIBOKEN_API PyObject *PepFunction_GetDefaults(PyObject *function); + +/* Masks for co_flags above */ +# define CO_OPTIMIZED 0x0001 +# define CO_NEWLOCALS 0x0002 +# define CO_VARARGS 0x0004 +# define CO_VARKEYWORDS 0x0008 +# define CO_NESTED 0x0010 +# define CO_GENERATOR 0x0020 + +#else + +# define PepCodeObject PyCodeObject +# define PepCode_GET_FLAGS(o) ((o)->co_flags) +# define PepCode_GET_ARGCOUNT(o) ((o)->co_argcount) +# define PepCode_Check PyCode_Check + +# ifdef PYPY_VERSION + +LIBSHIBOKEN_API PyObject *PepFunction_GetDefaults(PyObject *function); + +# else +# define PepFunction_GetDefaults PyFunction_GetDefaults +# endif +#endif + +/***************************************************************************** + * + * RESOLVED: datetime.h + * + */ +#ifdef Py_LIMITED_API + +LIBSHIBOKEN_API int PyDateTime_Get(PyObject *ob, const char *name); + +#define PyDateTime_GetYear(o) PyDateTime_Get(o, "year") +#define PyDateTime_GetMonth(o) PyDateTime_Get(o, "month") +#define PyDateTime_GetDay(o) PyDateTime_Get(o, "day") +#define PyDateTime_GetHour(o) PyDateTime_Get(o, "hour") +#define PyDateTime_GetMinute(o) PyDateTime_Get(o, "minute") +#define PyDateTime_GetSecond(o) PyDateTime_Get(o, "second") +#define PyDateTime_GetMicrosecond(o) PyDateTime_Get(o, "microsecond") +#define PyDateTime_GetFold(o) PyDateTime_Get(o, "fold") + +#define PyDateTime_GET_YEAR(o) PyDateTime_GetYear(o) +#define PyDateTime_GET_MONTH(o) PyDateTime_GetMonth(o) +#define PyDateTime_GET_DAY(o) PyDateTime_GetDay(o) + +#define PyDateTime_DATE_GET_HOUR(o) PyDateTime_GetHour(o) +#define PyDateTime_DATE_GET_MINUTE(o) PyDateTime_GetMinute(o) +#define PyDateTime_DATE_GET_SECOND(o) PyDateTime_GetSecond(o) +#define PyDateTime_DATE_GET_MICROSECOND(o) PyDateTime_GetMicrosecond(o) +#define PyDateTime_DATE_GET_FOLD(o) PyDateTime_GetFold(o) + +#define PyDateTime_TIME_GET_HOUR(o) PyDateTime_GetHour(o) +#define PyDateTime_TIME_GET_MINUTE(o) PyDateTime_GetMinute(o) +#define PyDateTime_TIME_GET_SECOND(o) PyDateTime_GetSecond(o) +#define PyDateTime_TIME_GET_MICROSECOND(o) PyDateTime_GetMicrosecond(o) +#define PyDateTime_TIME_GET_FOLD(o) PyDateTime_GetFold(o) + +/* Define structure slightly similar to C API. */ +typedef struct { + PyObject *module; + /* type objects */ + PyTypeObject *DateType; + PyTypeObject *DateTimeType; + PyTypeObject *TimeType; + PyTypeObject *DeltaType; + PyTypeObject *TZInfoType; +} datetime_struc; + +LIBSHIBOKEN_API datetime_struc *init_DateTime(void); + +#define PyDateTime_IMPORT PyDateTimeAPI = init_DateTime() + +extern LIBSHIBOKEN_API datetime_struc *PyDateTimeAPI; + +#define PyDate_Check(op) PyObject_TypeCheck(op, PyDateTimeAPI->DateType) +#define PyDateTime_Check(op) PyObject_TypeCheck(op, PyDateTimeAPI->DateTimeType) +#define PyTime_Check(op) PyObject_TypeCheck(op, PyDateTimeAPI->TimeType) + +LIBSHIBOKEN_API PyObject *PyDate_FromDate(int year, int month, int day); +LIBSHIBOKEN_API PyObject *PyDateTime_FromDateAndTime( + int year, int month, int day, int hour, int min, int sec, int usec); +LIBSHIBOKEN_API PyObject *PyTime_FromTime( + int hour, int minute, int second, int usecond); + +#endif /* Py_LIMITED_API */ + +/***************************************************************************** + * + * Extra support for name mangling + * + */ + +// PYSIDE-772: This function supports the fix, but is not meant as public. +LIBSHIBOKEN_API PyObject *_Pep_PrivateMangle(PyObject *self, PyObject *name); + +/***************************************************************************** + * + * Extra support for signature.cpp + * + */ + +#ifdef Py_LIMITED_API +extern LIBSHIBOKEN_API PyTypeObject *PepStaticMethod_TypePtr; +LIBSHIBOKEN_API PyObject *PyStaticMethod_New(PyObject *callable); +#else +#define PepStaticMethod_TypePtr &PyStaticMethod_Type +#endif + +#ifdef PYPY_VERSION +extern LIBSHIBOKEN_API PyTypeObject *PepBuiltinMethod_TypePtr; +#endif + +// Although not PEP specific, we resolve this similar issue, here: +#define PepMethodDescr_TypePtr &PyMethodDescr_Type + +/***************************************************************************** + * + * Newly introduced convenience functions + * + * This is not defined if Py_LIMITED_API is defined. + */ +#ifdef Py_LIMITED_API +LIBSHIBOKEN_API PyObject *PyImport_GetModule(PyObject *name); +#endif // Py_LIMITED_API + +// Evaluate a script and return the variable `result` +LIBSHIBOKEN_API PyObject *PepRun_GetResult(const char *command); + +// Call PyType_Type.tp_new returning a PyType object. +LIBSHIBOKEN_API PyTypeObject *PepType_Type_tp_new(PyTypeObject *metatype, + PyObject *args, + PyObject *kwds); + +/***************************************************************************** + * + * Runtime support for Python 3.8 incompatibilities + * + */ + +#ifndef Py_TPFLAGS_METHOD_DESCRIPTOR +/* Objects behave like an unbound method */ +#define Py_TPFLAGS_METHOD_DESCRIPTOR (1UL << 17) +#endif + +extern LIBSHIBOKEN_API int PepRuntime_38_flag; + +/***************************************************************************** + * + * Runtime support for Python 3.12 incompatibility + * + */ + +LIBSHIBOKEN_API PyObject *PepType_GetDict(PyTypeObject *type); + +// This function does not exist as PyType_SetDict. But because tp_dict +// is no longer considered to be accessible, we treat it as such. +LIBSHIBOKEN_API int PepType_SetDict(PyTypeObject *type, PyObject *dict); + +LIBSHIBOKEN_API void *PepType_GetSlot(PyTypeObject *type, int aSlot); + +/***************************************************************************** + * + * Module Initialization + * + */ + +LIBSHIBOKEN_API void Pep384_Init(void); + +} // extern "C" + +#endif // PEP384IMPL_H diff --git a/sources/shiboken6/libshiboken/pyobjectholder.h b/sources/shiboken6/libshiboken/pyobjectholder.h new file mode 100644 index 000000000..857748c2f --- /dev/null +++ b/sources/shiboken6/libshiboken/pyobjectholder.h @@ -0,0 +1,86 @@ +// Copyright (C) 2024 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 + +#ifndef PYOBJECTHOLDER_H +#define PYOBJECTHOLDER_H + +#include "sbkpython.h" + +#include <cassert> +#include <utility> + +namespace Shiboken +{ + +/// PyObjectHolder holds a PyObject pointer, keeping a reference decrementing +/// its reference counter when destroyed. It makes sure to hold the GIL when +/// releasing. It implements copy/move semantics and is mainly intended as a +/// base class for functors holding a callable which can be passed around and +/// stored in containers or moved from freely. +/// For one-shot functors, release() can be invoked after the call. +class PyObjectHolder +{ +public: + PyObjectHolder() noexcept = default; + + /// PyObjectHolder constructor. + /// \param pyobj A reference to a Python object + explicit PyObjectHolder(PyObject *pyObj) noexcept : m_pyObj(pyObj) + { + assert(pyObj != nullptr); + Py_INCREF(m_pyObj); + } + + PyObjectHolder(const PyObjectHolder &o) noexcept : m_pyObj(o.m_pyObj) + { + Py_XINCREF(m_pyObj); + } + + PyObjectHolder &operator=(const PyObjectHolder &o) noexcept + { + if (this != &o) { + m_pyObj = o.m_pyObj; + Py_XINCREF(m_pyObj); + } + return *this; + } + + PyObjectHolder(PyObjectHolder &&o) noexcept : m_pyObj{std::exchange(o.m_pyObj, nullptr)} {} + + PyObjectHolder &operator=(PyObjectHolder &&o) noexcept + { + m_pyObj = std::exchange(o.m_pyObj, nullptr); + return *this; + } + + /// Decref the python reference + ~PyObjectHolder() { release(); } + + [[nodiscard]] bool isNull() const { return m_pyObj == nullptr; } + [[nodiscard]] operator bool() const { return m_pyObj != nullptr; } + + /// Returns the pointer of the Python object being held. + [[nodiscard]] PyObject *object() const { return m_pyObj; } + [[nodiscard]] operator PyObject *() const { return m_pyObj; } + + [[nodiscard]] PyObject *operator->() { return m_pyObj; } + +protected: + void release() + { + if (m_pyObj != nullptr) { + assert(Py_IsInitialized()); + auto gstate = PyGILState_Ensure(); + Py_DECREF(m_pyObj); + PyGILState_Release(gstate); + m_pyObj = nullptr; + } + } + +private: + PyObject *m_pyObj = nullptr; +}; + +} // namespace Shiboken + +#endif // PYOBJECTHOLDER_H diff --git a/sources/shiboken6/libshiboken/qt_attribution.json b/sources/shiboken6/libshiboken/qt_attribution.json new file mode 100644 index 000000000..36e7f6868 --- /dev/null +++ b/sources/shiboken6/libshiboken/qt_attribution.json @@ -0,0 +1,12 @@ +{ + "Id": "python", + "Name": "Python", + "QDocModule": "QtForPython", + "QtUsage": "Used for Qt for Python in the signature extension.", + "Description": "Qt for Python is an add-on for Python. The libshiboken packages of PySide uses certain parts of the source files (bufferprocs_py37.cpp, bufferprocs_py37.h). See the folder sources/shiboken6/libshiboken .", + "Homepage": "http://www.python.org/", + "Version": "3.7.0", + "License": "PSF LICENSE AGREEMENT FOR PYTHON 3.7.0", + "LicenseFile": "bufferprocs_py37.h", + "Copyright": "© Copyright 2001-2018, Python Software Foundation." +} diff --git a/sources/shiboken6/libshiboken/sbkarrayconverter.cpp b/sources/shiboken6/libshiboken/sbkarrayconverter.cpp new file mode 100644 index 000000000..bcc3fb767 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkarrayconverter.cpp @@ -0,0 +1,246 @@ +// Copyright (C) 2017 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 "sbkarrayconverter.h" +#include "sbkarrayconverter_p.h" +#include "helper.h" +#include "sbkconverter.h" +#include "sbkconverter_p.h" + +#include <longobject.h> +#include <floatobject.h> + +#include <algorithm> + +static SbkArrayConverter *ArrayTypeConverters[Shiboken::Conversions::SBK_ARRAY_IDX_SIZE] [2] = {}; + +namespace Shiboken::Conversions { + +// Check whether Predicate is true for all elements of a sequence +template <class Predicate> +static bool sequenceAllOf(PyObject *pyIn, Predicate p) +{ + const Py_ssize_t size = PySequence_Size(pyIn); + for (Py_ssize_t i = 0; i < size; ++i) { + PyObject *item = PySequence_GetItem(pyIn, i); + const bool ok = p(item); + Py_XDECREF(item); + if (!ok) + return false; + } + return true; +} + +// Convert a sequence to output iterator +template <class T, class Converter> +inline void convertPySequence(PyObject *pyIn, Converter c, T *out) +{ + const Py_ssize_t size = PySequence_Size(pyIn); + for (Py_ssize_t i = 0; i < size; ++i) { + PyObject *item = PySequence_GetItem(pyIn, i); + *out++ = c(item); + Py_XDECREF(item); + } +} + +// Internal, for usage by numpy +SbkArrayConverter *createArrayConverter(IsArrayConvertibleToCppFunc toCppCheckFunc) +{ + auto *result = new SbkArrayConverter; + result->toCppConversions.push_back(toCppCheckFunc); + return result; +} + +static PythonToCppFunc unimplementedArrayCheck(PyObject *, int, int) +{ + return nullptr; +} + +SbkArrayConverter *unimplementedArrayConverter() +{ + static SbkArrayConverter *result = createArrayConverter(unimplementedArrayCheck); + return result; +} + +// Integers + +static inline bool intCheck(PyObject *pyIn) +{ + return PyLong_Check(pyIn); +} + +static short toShort(PyObject *pyIn) { return short(PyLong_AsLong(pyIn)); } + +static void sequenceToCppShortArray(PyObject *pyIn, void *cppOut) +{ + auto *handle = reinterpret_cast<ArrayHandle<short> *>(cppOut); + handle->allocate(PySequence_Size(pyIn)); + convertPySequence(pyIn, toShort, handle->data()); +} + +static inline bool sequenceSizeCheck(PyObject *pyIn, int expectedSize = -1) +{ + if (expectedSize >= 0) { + const int size = int(PySequence_Size(pyIn)); + if (size < expectedSize) { + warning(PyExc_RuntimeWarning, 0, "A sequence of size %d was passed to a function that expects %d.", + size, expectedSize); + return false; + } + } + return true; +} + +static inline bool intArrayCheck(PyObject *pyIn, int expectedSize = -1) +{ + return PySequence_Check(pyIn) && sequenceAllOf(pyIn, intCheck) + && sequenceSizeCheck(pyIn, expectedSize); +} + +static PythonToCppFunc sequenceToCppShortArrayCheck(PyObject *pyIn, int dim1, int /* dim2 */) +{ + return intArrayCheck(pyIn, dim1) ? sequenceToCppShortArray : nullptr; +} + +static short toUnsignedShort(PyObject *pyIn) { return static_cast<unsigned short>(PyLong_AsUnsignedLong(pyIn)); } + +static void sequenceToCppUnsignedShortArray(PyObject *pyIn, void *cppOut) +{ + auto *handle = reinterpret_cast<ArrayHandle<unsigned short> *>(cppOut); + handle->allocate(PySequence_Size(pyIn)); + convertPySequence(pyIn, toUnsignedShort, handle->data()); +} + +static PythonToCppFunc sequenceToCppUnsignedShortArrayCheck(PyObject *pyIn, int dim1, int /* dim2 */) +{ + return intArrayCheck(pyIn, dim1) ? sequenceToCppUnsignedShortArray : nullptr; +} + +static void sequenceToCppIntArray(PyObject *pyIn, void *cppOut) +{ + auto *handle = reinterpret_cast<ArrayHandle<int> *>(cppOut); + handle->allocate(PySequence_Size(pyIn)); + convertPySequence(pyIn, PyLong_AsLong, handle->data()); +} + +static PythonToCppFunc sequenceToCppIntArrayCheck(PyObject *pyIn, int dim1, int /* dim2 */) +{ + return intArrayCheck(pyIn, dim1) ? sequenceToCppIntArray : nullptr; +} + +static void sequenceToCppUnsignedArray(PyObject *pyIn, void *cppOut) +{ + auto *handle = reinterpret_cast<ArrayHandle<unsigned> *>(cppOut); + handle->allocate(PySequence_Size(pyIn)); + convertPySequence(pyIn, PyLong_AsUnsignedLong, handle->data()); +} + +static PythonToCppFunc sequenceToCppUnsignedArrayCheck(PyObject *pyIn, int dim1, int /* dim2 */) +{ + return intArrayCheck(pyIn, dim1) ? sequenceToCppUnsignedArray : nullptr; +} + +static void sequenceToCppLongLongArray(PyObject *pyIn, void *cppOut) +{ + auto *handle = reinterpret_cast<ArrayHandle<long long> *>(cppOut); + handle->allocate(PySequence_Size(pyIn)); + convertPySequence(pyIn, PyLong_AsLongLong, handle->data()); +} + +static PythonToCppFunc sequenceToCppLongLongArrayCheck(PyObject *pyIn, int dim1, int /* dim2 */) +{ + return intArrayCheck(pyIn, dim1) ? sequenceToCppLongLongArray : nullptr; +} + +static void sequenceToCppUnsignedLongLongArray(PyObject *pyIn, void *cppOut) +{ + auto *handle = reinterpret_cast<ArrayHandle<unsigned long long> *>(cppOut); + handle->allocate(PySequence_Size(pyIn)); + convertPySequence(pyIn, PyLong_AsUnsignedLongLong, handle->data()); +} + +static PythonToCppFunc sequenceToCppUnsignedLongLongArrayCheck(PyObject *pyIn, int dim1, int /* dim2 */) +{ + return intArrayCheck(pyIn, dim1) ? sequenceToCppUnsignedLongLongArray : nullptr; +} + +// Float + +static inline bool floatCheck(PyObject *pyIn) { return PyFloat_Check(pyIn); } + +static inline bool floatArrayCheck(PyObject *pyIn, int expectedSize = -1) +{ + return PySequence_Check(pyIn) && sequenceAllOf(pyIn, floatCheck) + && sequenceSizeCheck(pyIn, expectedSize); +} + +static void sequenceToCppDoubleArray(PyObject *pyIn, void *cppOut) +{ + auto *handle = reinterpret_cast<ArrayHandle<double> *>(cppOut); + handle->allocate(PySequence_Size(pyIn)); + convertPySequence(pyIn, PyFloat_AsDouble, handle->data()); +} + +static inline float pyToFloat(PyObject *pyIn) { return float(PyFloat_AsDouble(pyIn)); } + +static void sequenceToCppFloatArray(PyObject *pyIn, void *cppOut) +{ + auto *handle = reinterpret_cast<ArrayHandle<float> *>(cppOut); + handle->allocate(PySequence_Size(pyIn)); + convertPySequence(pyIn, pyToFloat, handle->data()); +} + +static PythonToCppFunc sequenceToCppFloatArrayCheck(PyObject *pyIn, int dim1, int /* dim2 */) +{ + return floatArrayCheck(pyIn, dim1) ? sequenceToCppFloatArray : nullptr; +} + +static PythonToCppFunc sequenceToCppDoubleArrayCheck(PyObject *pyIn, int dim1, int /* dim2 */) +{ + return floatArrayCheck(pyIn, dim1) ? sequenceToCppDoubleArray : nullptr; +} + +#ifdef HAVE_NUMPY +void initNumPyArrayConverters(); +#endif + +void initArrayConverters() +{ + SbkArrayConverter **start = &ArrayTypeConverters[0][0]; + std::fill(start, start + sizeof(ArrayTypeConverters) / sizeof(ArrayTypeConverters[0][0]), nullptr); + // Populate 1-dimensional sequence converters + ArrayTypeConverters[SBK_DOUBLE_ARRAY_IDX][0] = + createArrayConverter(sequenceToCppDoubleArrayCheck); + ArrayTypeConverters[SBK_FLOAT_ARRAY_IDX][0] = + createArrayConverter(sequenceToCppFloatArrayCheck); + ArrayTypeConverters[SBK_SHORT_ARRAY_IDX][0] = + createArrayConverter(sequenceToCppShortArrayCheck); + ArrayTypeConverters[SBK_UNSIGNEDSHORT_ARRAY_IDX][0] = + createArrayConverter(sequenceToCppUnsignedShortArrayCheck); + ArrayTypeConverters[SBK_INT_ARRAY_IDX][0] = + createArrayConverter(sequenceToCppIntArrayCheck); + ArrayTypeConverters[SBK_UNSIGNEDINT_ARRAY_IDX][0] = + createArrayConverter(sequenceToCppUnsignedArrayCheck); + ArrayTypeConverters[SBK_LONGLONG_ARRAY_IDX][0] = + createArrayConverter(sequenceToCppLongLongArrayCheck); + ArrayTypeConverters[SBK_UNSIGNEDLONGLONG_ARRAY_IDX][0] = + createArrayConverter(sequenceToCppUnsignedLongLongArrayCheck); + +#ifdef HAVE_NUMPY + initNumPyArrayConverters(); +#endif +} + +SbkArrayConverter *arrayTypeConverter(int index, int dimension) +{ + SbkArrayConverter *c = ArrayTypeConverters[index][dimension - 1]; + return c ? c : unimplementedArrayConverter(); +} + +// Internal, for usage by numpy +void setArrayTypeConverter(int index, int dimension, SbkArrayConverter *c) +{ + ArrayTypeConverters[index][dimension - 1] = c; +} + +} // namespace Shiboken::Conversions diff --git a/sources/shiboken6/libshiboken/sbkarrayconverter.h b/sources/shiboken6/libshiboken/sbkarrayconverter.h new file mode 100644 index 000000000..f07cb1d70 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkarrayconverter.h @@ -0,0 +1,136 @@ +// Copyright (C) 2017 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 + +#ifndef SBKARRAYCONVERTERS_H +#define SBKARRAYCONVERTERS_H + +#include "sbkpython.h" +#include "shibokenmacros.h" + +extern "C" { +struct SbkArrayConverter; +} + +namespace Shiboken::Conversions { + +enum : int { + SBK_UNIMPLEMENTED_ARRAY_IDX, + SBK_DOUBLE_ARRAY_IDX, + SBK_FLOAT_ARRAY_IDX, + SBK_SHORT_ARRAY_IDX, + SBK_UNSIGNEDSHORT_ARRAY_IDX, + SBK_INT_ARRAY_IDX, + SBK_UNSIGNEDINT_ARRAY_IDX, + SBK_LONGLONG_ARRAY_IDX, + SBK_UNSIGNEDLONGLONG_ARRAY_IDX, + SBK_ARRAY_IDX_SIZE +}; + +/** + * ArrayHandle is the type expected by shiboken6's array converter + * functions. It provides access to array data which it may own + * (in the case of conversions from PySequence) or a flat pointer + * to internal data (in the case of array modules like numpy). + */ + +template <class T> +class ArrayHandle +{ +public: + ArrayHandle(const ArrayHandle &) = delete; + ArrayHandle& operator=(const ArrayHandle &) = delete; + ArrayHandle(ArrayHandle &&) = delete; + ArrayHandle& operator=(ArrayHandle &&) = delete; + + ArrayHandle() = default; + ~ArrayHandle() { destroy(); } + + void allocate(Py_ssize_t size); + void setData(T *d, size_t size); + + size_t size() const { return m_size; } + T *data() const { return m_data; } + operator T *() const { return m_data; } + +private: + void destroy(); + + T *m_data = nullptr; + Py_ssize_t m_size = 0; + bool m_owned = false; +}; + +/** + * Similar to ArrayHandle for fixed size 2 dimensional arrays. + * columns is the size of the last dimension + * It only has a setData() methods since it will be used for numpy only. + */ + +template <class T, int columns> +class Array2Handle +{ +public: + typedef T RowType[columns]; + + Array2Handle() = default; + + operator RowType *() const { return m_rows; } + + void setData(RowType *d) { m_rows = d; } + +private: + RowType *m_rows = nullptr; +}; + +/// Returns the converter for an array type. +LIBSHIBOKEN_API SbkArrayConverter *arrayTypeConverter(int index, int dimension = 1); + +template <class T> +struct ArrayTypeIndex{ + enum : int { index = SBK_UNIMPLEMENTED_ARRAY_IDX }; +}; + +template <> struct ArrayTypeIndex<double> { enum : int { index = SBK_DOUBLE_ARRAY_IDX }; }; +template <> struct ArrayTypeIndex<float> { enum : int { index = SBK_FLOAT_ARRAY_IDX };}; +template <> struct ArrayTypeIndex<short> { enum : int { index = SBK_SHORT_ARRAY_IDX };}; +template <> struct ArrayTypeIndex<unsigned short> { enum : int { index = SBK_UNSIGNEDSHORT_ARRAY_IDX };}; +template <> struct ArrayTypeIndex<int> { enum : int { index = SBK_INT_ARRAY_IDX };}; +template <> struct ArrayTypeIndex<unsigned> { enum : int { index = SBK_UNSIGNEDINT_ARRAY_IDX };}; +template <> struct ArrayTypeIndex<long long> { enum : int { index = SBK_LONGLONG_ARRAY_IDX };}; +template <> struct ArrayTypeIndex<unsigned long long> { enum : int { index = SBK_UNSIGNEDLONGLONG_ARRAY_IDX };}; + +template<typename T> SbkArrayConverter *ArrayTypeConverter(int dimension) +{ return arrayTypeConverter(ArrayTypeIndex<T>::index, dimension); } + +// ArrayHandle methods +template<class T> +void ArrayHandle<T>::allocate(Py_ssize_t size) +{ + destroy(); + m_data = new T[size]; + m_size = size; + m_owned = true; +} + +template<class T> +void ArrayHandle<T>::setData(T *d, size_t size) +{ + destroy(); + m_data = d; + m_size = size; + m_owned = false; +} + +template<class T> +void ArrayHandle<T>::destroy() +{ + if (m_owned) + delete [] m_data; + m_data = nullptr; + m_size = 0; + m_owned = false; +} + +} // namespace Shiboken::Conversions + +#endif // SBKARRAYCONVERTERS_H diff --git a/sources/shiboken6/libshiboken/sbkarrayconverter_p.h b/sources/shiboken6/libshiboken/sbkarrayconverter_p.h new file mode 100644 index 000000000..63d03fb12 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkarrayconverter_p.h @@ -0,0 +1,26 @@ +// Copyright (C) 2017 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 + +#ifndef SBKARRAYCONVERTER_P_H +#define SBKARRAYCONVERTER_P_H + +#include "sbkconverter_p.h" +#include <vector> + +extern "C" +{ + +using IsArrayConvertibleToCppFunc = PythonToCppFunc (*)(PyObject *, int dim1, int dim2); +/** + * \internal + * Private structure of SbkArrayConverter. + */ + +struct SbkArrayConverter +{ + std::vector<IsArrayConvertibleToCppFunc> toCppConversions; +}; + +} // extern "C" + +#endif // SBKARRAYCONVERTER_P_H diff --git a/sources/shiboken6/libshiboken/sbkcontainer.cpp b/sources/shiboken6/libshiboken/sbkcontainer.cpp new file mode 100644 index 000000000..7de1f03e6 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkcontainer.cpp @@ -0,0 +1,19 @@ +// Copyright (C) 2021 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 "sbkcontainer.h" +#include "sbkstaticstrings.h" +#include "autodecref.h" + +namespace Shiboken +{ +bool isOpaqueContainer(PyObject *o) +{ + if (!o) + return false; + Shiboken::AutoDecRef tpDict(PepType_GetDict(o->ob_type)); + return o != nullptr && o != Py_None + && PyDict_Contains(tpDict.object(), Shiboken::PyMagicName::opaque_container()) == 1; + +} +} // Shiboken diff --git a/sources/shiboken6/libshiboken/sbkcontainer.h b/sources/shiboken6/libshiboken/sbkcontainer.h new file mode 100644 index 000000000..8ad5aadc6 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkcontainer.h @@ -0,0 +1,259 @@ +// Copyright (C) 2021 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 + +#ifndef SBK_CONTAINER_H +#define SBK_CONTAINER_H + +#include "sbkpython.h" +#include "shibokenmacros.h" +#include "shibokenbuffer.h" + +#include <algorithm> +#include <iterator> +#include <optional> +#include <utility> + +extern "C" +{ +struct LIBSHIBOKEN_API ShibokenContainer +{ + PyObject_HEAD + void *d; +}; + +} // extern "C" + +// Conversion helper traits for container values (Keep it out of namespace as +// otherwise clashes occur). +template <class Value> +struct ShibokenContainerValueConverter +{ + static bool checkValue(PyObject *pyArg); + static PyObject *convertValueToPython(Value v); + static std::optional<Value> convertValueToCpp(PyObject pyArg); +}; + +// SFINAE test for the presence of reserve() in a sequence container (std::vector/QList) +template <typename T> +class ShibokenContainerHasReserve +{ +private: + using YesType = char[1]; + using NoType = char[2]; + + template <typename C> static YesType& test( decltype(&C::reserve) ) ; + template <typename C> static NoType& test(...); + +public: + enum { value = sizeof(test<T>(nullptr)) == sizeof(YesType) }; +}; + +template <class SequenceContainer> +class ShibokenSequenceContainerPrivate // Helper for sequence type containers +{ +public: + using value_type = typename SequenceContainer::value_type; + using OptionalValue = typename std::optional<value_type>; + + SequenceContainer *m_list{}; + bool m_ownsList = false; + bool m_const = false; + static constexpr const char *msgModifyConstContainer = + "Attempt to modify a constant container."; + + static PyObject *tpNew(PyTypeObject *subtype, PyObject * /* args */, PyObject * /* kwds */) + { + allocfunc allocFunc = reinterpret_cast<allocfunc>(PepType_GetSlot(subtype, Py_tp_alloc)); + auto *me = reinterpret_cast<ShibokenContainer *>(allocFunc(subtype, 0)); + auto *d = new ShibokenSequenceContainerPrivate; + d->m_list = new SequenceContainer; + d->m_ownsList = true; + me->d = d; + return reinterpret_cast<PyObject *>(me); + } + + static PyObject *tpNewInvalid(PyTypeObject * /* subtype */, PyObject * /* args */, PyObject * /* kwds */) + { + return PyErr_Format(PyExc_NotImplementedError, + "Opaque containers of type '%s' cannot be instantiated.", + typeid(SequenceContainer).name()); + } + + static int tpInit(PyObject * /* self */, PyObject * /* args */, PyObject * /* kwds */) + { + return 0; + } + + static void tpFree(void *self) + { + auto *pySelf = reinterpret_cast<PyObject *>(self); + auto *d = get(pySelf); + if (d->m_ownsList) + delete d->m_list; + delete d; + auto freeFunc = reinterpret_cast<freefunc>(PepType_GetSlot(Py_TYPE(pySelf)->tp_base, + Py_tp_free)); + freeFunc(self); + } + + static Py_ssize_t sqLen(PyObject *self) + { + return get(self)->m_list->size(); + } + + static PyObject *sqGetItem(PyObject *self, Py_ssize_t i) + { + auto *d = get(self); + if (i < 0 || i >= Py_ssize_t(d->m_list->size())) + return PyErr_Format(PyExc_IndexError, "index out of bounds"); + auto it = std::cbegin(*d->m_list); + std::advance(it, i); + return ShibokenContainerValueConverter<value_type>::convertValueToPython(*it); + } + + static int sqSetItem(PyObject *self, Py_ssize_t i, PyObject *pyArg) + { + auto *d = get(self); + if (i < 0 || i >= Py_ssize_t(d->m_list->size())) { + PyErr_SetString(PyExc_IndexError, "index out of bounds"); + return -1; + } + auto it = std::begin(*d->m_list); + std::advance(it, i); + OptionalValue value = ShibokenContainerValueConverter<value_type>::convertValueToCpp(pyArg); + if (!value.has_value()) + return -1; + *it = value.value(); + return 0; + } + + static PyObject *push_back(PyObject *self, PyObject *pyArg) + { + auto *d = get(self); + if (!ShibokenContainerValueConverter<value_type>::checkValue(pyArg)) + return PyErr_Format(PyExc_TypeError, "wrong type passed to append."); + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); + + OptionalValue value = ShibokenContainerValueConverter<value_type>::convertValueToCpp(pyArg); + if (!value.has_value()) + return nullptr; + d->m_list->push_back(value.value()); + Py_RETURN_NONE; + } + + static PyObject *push_front(PyObject *self, PyObject *pyArg) + { + auto *d = get(self); + if (!ShibokenContainerValueConverter<value_type>::checkValue(pyArg)) + return PyErr_Format(PyExc_TypeError, "wrong type passed to append."); + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); + + OptionalValue value = ShibokenContainerValueConverter<value_type>::convertValueToCpp(pyArg); + if (!value.has_value()) + return nullptr; + d->m_list->push_front(value.value()); + Py_RETURN_NONE; + } + + static PyObject *clear(PyObject *self) + { + auto *d = get(self); + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); + + d->m_list->clear(); + Py_RETURN_NONE; + } + + static PyObject *pop_back(PyObject *self) + { + auto *d = get(self); + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); + + d->m_list->pop_back(); + Py_RETURN_NONE; + } + + static PyObject *pop_front(PyObject *self) + { + auto *d = get(self); + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); + + d->m_list->pop_front(); + Py_RETURN_NONE; + } + + // Support for containers with reserve/capacity + static PyObject *reserve(PyObject *self, PyObject *pyArg) + { + auto *d = get(self); + if (PyLong_Check(pyArg) == 0) + return PyErr_Format(PyExc_TypeError, "wrong type passed to reserve()."); + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); + + if constexpr (ShibokenContainerHasReserve<SequenceContainer>::value) { + const Py_ssize_t size = PyLong_AsSsize_t(pyArg); + d->m_list->reserve(size); + } else { + return PyErr_Format(PyExc_TypeError, "Container does not support reserve()."); + } + + Py_RETURN_NONE; + } + + static PyObject *capacity(PyObject *self) + { + Py_ssize_t result = -1; + if constexpr (ShibokenContainerHasReserve<SequenceContainer>::value) { + const auto *d = get(self); + result = d->m_list->capacity(); + } + return PyLong_FromSsize_t(result); + } + + static PyObject *data(PyObject *self) + { + PyObject *result = nullptr; + if constexpr (ShibokenContainerHasReserve<SequenceContainer>::value) { + const auto *d = get(self); + auto *data = d->m_list->data(); + const Py_ssize_t size = sizeof(value_type) * d->m_list->size(); + result = Shiboken::Buffer::newObject(data, size, Shiboken::Buffer::ReadWrite); + } else { + PyErr_SetString(PyExc_TypeError, "Container does not support data()."); + } + return result; + } + + static PyObject *constData(PyObject *self) + { + PyObject *result = nullptr; + if constexpr (ShibokenContainerHasReserve<SequenceContainer>::value) { + const auto *d = get(self); + const auto *data = std::as_const(d->m_list)->data(); + const Py_ssize_t size = sizeof(value_type) * d->m_list->size(); + result = Shiboken::Buffer::newObject(data, size); + } else { + PyErr_SetString(PyExc_TypeError, "Container does not support constData()."); + } + return result; + } + + static ShibokenSequenceContainerPrivate *get(PyObject *self) + { + auto *data = reinterpret_cast<ShibokenContainer *>(self); + return reinterpret_cast<ShibokenSequenceContainerPrivate *>(data->d); + } +}; + +namespace Shiboken +{ +LIBSHIBOKEN_API bool isOpaqueContainer(PyObject *o); +} + +#endif // SBK_CONTAINER_H diff --git a/sources/shiboken6/libshiboken/sbkconverter.cpp b/sources/shiboken6/libshiboken/sbkconverter.cpp new file mode 100644 index 000000000..9ab674415 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkconverter.cpp @@ -0,0 +1,910 @@ +// 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 "sbkconverter.h" +#include "sbkconverter_p.h" +#include "sbkarrayconverter_p.h" +#include "sbkmodule.h" +#include "basewrapper_p.h" +#include "bindingmanager.h" +#include "autodecref.h" +#include "helper.h" +#include "voidptr.h" + +#include <string> +#include <cstring> +#include <iostream> +#include <unordered_map> +#include <unordered_set> +#include <map> +#include <set> + +static SbkConverter **PrimitiveTypeConverters; + +using ConvertersMap = std::unordered_map<std::string, SbkConverter *>; +static ConvertersMap converters; + +namespace Shiboken::Conversions { + +void initArrayConverters(); + +void init() +{ + static SbkConverter *primitiveTypeConverters[] = { + Primitive<PY_LONG_LONG>::createConverter(), + Primitive<bool>::createConverter(), + Primitive<char>::createConverter(), + Primitive<const char *>::createConverter(), + Primitive<double>::createConverter(), + Primitive<float>::createConverter(), + Primitive<int>::createConverter(), + Primitive<long>::createConverter(), + Primitive<short>::createConverter(), + Primitive<signed char>::createConverter(), + Primitive<std::string>::createConverter(), + Primitive<std::wstring>::createConverter(), + Primitive<unsigned PY_LONG_LONG>::createConverter(), + Primitive<unsigned char>::createConverter(), + Primitive<unsigned int>::createConverter(), + Primitive<unsigned long>::createConverter(), + Primitive<unsigned short>::createConverter(), + VoidPtr::createConverter(), + Primitive<std::nullptr_t>::createConverter() + }; + PrimitiveTypeConverters = primitiveTypeConverters; + + assert(converters.empty()); + converters["PY_LONG_LONG"] = primitiveTypeConverters[SBK_PY_LONG_LONG_IDX]; + converters["bool"] = primitiveTypeConverters[SBK_BOOL_IDX_1]; + converters["char"] = primitiveTypeConverters[SBK_CHAR_IDX]; + converters["const char *"] = primitiveTypeConverters[SBK_CONSTCHARPTR_IDX]; + converters["double"] = primitiveTypeConverters[SBK_DOUBLE_IDX]; + converters["float"] = primitiveTypeConverters[SBK_FLOAT_IDX]; + converters["int"] = primitiveTypeConverters[SBK_INT_IDX]; + converters["long"] = primitiveTypeConverters[SBK_LONG_IDX]; + converters["short"] = primitiveTypeConverters[SBK_SHORT_IDX]; + converters["signed char"] = primitiveTypeConverters[SBK_SIGNEDCHAR_IDX]; + converters["std::string"] = primitiveTypeConverters[SBK_STD_STRING_IDX]; + converters["std::wstring"] = primitiveTypeConverters[SBK_STD_WSTRING_IDX]; + converters["unsigned PY_LONG_LONG"] = primitiveTypeConverters[SBK_UNSIGNEDPY_LONG_LONG_IDX]; + converters["unsigned char"] = primitiveTypeConverters[SBK_UNSIGNEDCHAR_IDX]; + converters["unsigned int"] = primitiveTypeConverters[SBK_UNSIGNEDINT_IDX]; + converters["unsigned long"] = primitiveTypeConverters[SBK_UNSIGNEDLONG_IDX]; + converters["unsigned short"] = primitiveTypeConverters[SBK_UNSIGNEDSHORT_IDX]; + converters["void*"] = primitiveTypeConverters[SBK_VOIDPTR_IDX]; + converters["std::nullptr_t"] = primitiveTypeConverters[SBK_NULLPTR_T_IDX]; + + initArrayConverters(); +} + +static void dumpPyTypeObject(std::ostream &str, PyTypeObject *t) +{ + str << "\nPython type "; + if (t == nullptr) { + str << "<None>"; + return; + } + str << '"' << t->tp_name << '"'; + if (t->tp_base != nullptr && t->tp_base != &PyBaseObject_Type) + str << '(' << t->tp_base->tp_name << ')'; +} + +static void dumpSbkConverter(std::ostream &str, const SbkConverter *c) +{ + str << "SbkConverter " << static_cast<const void *>(c) << ": "; + if (c->pointerToPython != nullptr) + str << ", C++ pointer->Python"; + if (c->copyToPython != nullptr) + str << ", copy->Python"; + if (c->toCppPointerConversion.second != nullptr) + str << ", Python->C++ pointer"; + if (!c->toCppConversions.empty()) + str << ", " << c->toCppConversions.size() << " Python->C++ conversions"; +} + +// Less than operator for a PyTypeObject for dumping the converter map +static bool pyTypeObjectLessThan(const PyTypeObject *t1, const PyTypeObject *t2) +{ + const bool isNull1 = t1 == nullptr; + const bool isNull2 = t2 == nullptr; + if (isNull1 || isNull2) + return isNull1 && !isNull2; + // Internal types (lower case) first + const bool isInternal1 = std::islower(t1->tp_name[0]); + const bool isInternal2 = std::islower(t2->tp_name[0]); + if (isInternal1 != isInternal2) + return !isInternal2; + return std::strcmp(t1->tp_name, t2->tp_name) < 0; +} + +void dumpConverters() +{ + struct PyTypeObjectLess { + + bool operator()(const PyTypeObject *t1, const PyTypeObject *t2) const { + return pyTypeObjectLessThan(t1, t2); + } + }; + + using StringSet = std::set<std::string>; + using SbkConverterNamesMap = std::unordered_map<SbkConverter *, StringSet>; + using PyTypeObjectConverterMap = std::map<PyTypeObject *, SbkConverterNamesMap, + PyTypeObjectLess>; + + auto &str = std::cerr; + + // Sort the entries by the associated PyTypeObjects and converters + PyTypeObjectConverterMap pyTypeObjectConverterMap; + for (const auto &converter : converters) { + auto *sbkConverter = converter.second; + if (sbkConverter == nullptr) { + str << "Non-existent: \"" << converter.first << "\"\n"; + continue; + } + auto *typeObject = sbkConverter->pythonType; + auto typeIt = pyTypeObjectConverterMap.find(typeObject); + if (typeIt == pyTypeObjectConverterMap.end()) + typeIt = pyTypeObjectConverterMap.insert(std::make_pair(typeObject, + SbkConverterNamesMap{})).first; + SbkConverterNamesMap &sbkConverterMap = typeIt->second; + auto convIt = sbkConverterMap.find(sbkConverter); + if (convIt == sbkConverterMap.end()) + convIt = sbkConverterMap.insert(std::make_pair(sbkConverter, + StringSet{})).first; + convIt->second.insert(converter.first); + } + + for (const auto &tc : pyTypeObjectConverterMap) { + dumpPyTypeObject(str, tc.first); + str << ", " << tc.second.size() << " converter(s):\n"; + for (const auto &cn : tc.second) { + str << " "; + dumpSbkConverter(str, cn.first); + str << ", " << cn.second.size() << " alias(es):"; + int i = 0; + for (const auto &name : cn.second) { + if ((i++ % 5) == 0) + str << "\n "; + str << " \"" << name << '"'; + } + str << '\n'; + } + } + + str << '\n'; +} + +SbkConverter *createConverterObject(PyTypeObject *type, + PythonToCppFunc toCppPointerConvFunc, + IsConvertibleToCppFunc toCppPointerCheckFunc, + CppToPythonFunc pointerToPythonFunc, + CppToPythonFunc copyToPythonFunc) +{ + auto converter = new SbkConverter; + converter->pythonType = type; + // PYSIDE-595: All types are heaptypes now, so provide reference. + Py_XINCREF(type); + + converter->pointerToPython = pointerToPythonFunc; + converter->copyToPython = copyToPythonFunc; + + if (toCppPointerCheckFunc && toCppPointerConvFunc) + converter->toCppPointerConversion = std::make_pair(toCppPointerCheckFunc, toCppPointerConvFunc); + converter->toCppConversions.clear(); + + return converter; +} + +SbkConverter *createConverter(PyTypeObject *type, + PythonToCppFunc toCppPointerConvFunc, + IsConvertibleToCppFunc toCppPointerCheckFunc, + CppToPythonFunc pointerToPythonFunc, + CppToPythonFunc copyToPythonFunc) +{ + SbkConverter *converter = + createConverterObject(type, + toCppPointerConvFunc, toCppPointerCheckFunc, + pointerToPythonFunc, copyToPythonFunc); + PepType_SOTP(type)->converter = converter; + return converter; +} + +SbkConverter *createConverter(PyTypeObject *type, CppToPythonFunc toPythonFunc) +{ + return createConverterObject(type, nullptr, nullptr, nullptr, toPythonFunc); +} + +void deleteConverter(SbkConverter *converter) +{ + if (converter) { + converter->toCppConversions.clear(); + delete converter; + } +} + +void setCppPointerToPythonFunction(SbkConverter *converter, CppToPythonFunc pointerToPythonFunc) +{ + converter->pointerToPython = pointerToPythonFunc; +} + +void setPythonToCppPointerFunctions(SbkConverter *converter, + PythonToCppFunc toCppPointerConvFunc, + IsConvertibleToCppFunc toCppPointerCheckFunc) +{ + converter->toCppPointerConversion = std::make_pair(toCppPointerCheckFunc, toCppPointerConvFunc); +} + +void addPythonToCppValueConversion(SbkConverter *converter, + PythonToCppFunc pythonToCppFunc, + IsConvertibleToCppFunc isConvertibleToCppFunc) +{ + converter->toCppConversions.push_back(std::make_pair(isConvertibleToCppFunc, pythonToCppFunc)); +} + +void addPythonToCppValueConversion(PyTypeObject *type, + PythonToCppFunc pythonToCppFunc, + IsConvertibleToCppFunc isConvertibleToCppFunc) +{ + auto *sotp = PepType_SOTP(type); + addPythonToCppValueConversion(sotp->converter, pythonToCppFunc, isConvertibleToCppFunc); +} + +void addPythonToCppValueConversion(Shiboken::Module::TypeInitStruct typeStruct, + PythonToCppFunc pythonToCppFunc, + IsConvertibleToCppFunc isConvertibleToCppFunc) +{ + addPythonToCppValueConversion(typeStruct.type, pythonToCppFunc, isConvertibleToCppFunc); +} + +PyObject *pointerToPython(PyTypeObject *type, const void *cppIn) +{ + auto *sotp = PepType_SOTP(type); + return pointerToPython(sotp->converter, cppIn); +} + +PyObject *pointerToPython(const SbkConverter *converter, const void *cppIn) +{ + assert(converter); + if (!cppIn) + Py_RETURN_NONE; + if (!converter->pointerToPython) { + warning(PyExc_RuntimeWarning, 0, "pointerToPython(): SbkConverter::pointerToPython is null for \"%s\".", + converter->pythonType->tp_name); + Py_RETURN_NONE; + } + return converter->pointerToPython(cppIn); +} + +PyObject *referenceToPython(PyTypeObject *type, const void *cppIn) +{ + auto *sotp = PepType_SOTP(type); + return referenceToPython(sotp->converter, cppIn); +} + +PyObject *referenceToPython(const SbkConverter *converter, const void *cppIn) +{ + assert(cppIn); + + auto *pyOut = reinterpret_cast<PyObject *>(BindingManager::instance().retrieveWrapper(cppIn)); + if (pyOut) { + Py_INCREF(pyOut); + return pyOut; + } + if (!converter->pointerToPython) { + warning(PyExc_RuntimeWarning, 0, "referenceToPython(): SbkConverter::pointerToPython is null for \"%s\".", + converter->pythonType->tp_name); + Py_RETURN_NONE; + } + return converter->pointerToPython(cppIn); +} + +static inline PyObject *CopyCppToPython(const SbkConverter *converter, const void *cppIn) +{ + if (!cppIn) + Py_RETURN_NONE; + if (!converter->copyToPython) { + warning(PyExc_RuntimeWarning, 0, "CopyCppToPython(): SbkConverter::copyToPython is null for \"%s\".", + converter->pythonType->tp_name); + Py_RETURN_NONE; + } + return converter->copyToPython(cppIn); +} + +PyObject *copyToPython(PyTypeObject *type, const void *cppIn) +{ + auto *sotp = PepType_SOTP(type); + return CopyCppToPython(sotp->converter, cppIn); +} + +PyObject *copyToPython(const SbkConverter *converter, const void *cppIn) +{ + return CopyCppToPython(converter, cppIn); +} + +PythonToCppFunc isPythonToCppPointerConvertible(PyTypeObject *type, PyObject *pyIn) +{ + assert(pyIn); + auto *sotp = PepType_SOTP(type); + return sotp->converter->toCppPointerConversion.first(pyIn); +} + +PythonToCppConversion pythonToCppPointerConversion(PyTypeObject *type, PyObject *pyIn) +{ + if (pyIn == nullptr) + return {}; + if (PythonToCppFunc toCppPtr = isPythonToCppPointerConvertible(type, pyIn)) + return {toCppPtr, PythonToCppConversion::Pointer}; + return {}; +} + +PythonToCppConversion pythonToCppPointerConversion(Module::TypeInitStruct typeStruct, PyObject *pyIn) +{ + return pythonToCppPointerConversion(typeStruct.type, pyIn); +} + +static inline PythonToCppFunc IsPythonToCppConvertible(const SbkConverter *converter, PyObject *pyIn) +{ + assert(pyIn); + for (const ToCppConversion &c : converter->toCppConversions) { + if (PythonToCppFunc toCppFunc = c.first(pyIn)) + return toCppFunc; + } + return nullptr; +} + +PythonToCppFunc isPythonToCppValueConvertible(PyTypeObject *type, PyObject *pyIn) +{ + auto *sotp = PepType_SOTP(type); + return IsPythonToCppConvertible(sotp->converter, pyIn); +} + +PythonToCppConversion pythonToCppValueConversion(PyTypeObject *type, PyObject *pyIn) +{ + if (pyIn == nullptr) + return {}; + if (PythonToCppFunc toCppVal = isPythonToCppValueConvertible(type, pyIn)) + return {toCppVal, PythonToCppConversion::Value}; + return {}; +} + +PythonToCppFunc isPythonToCppConvertible(const SbkConverter *converter, PyObject *pyIn) +{ + return IsPythonToCppConvertible(converter, pyIn); +} + +PythonToCppConversion pythonToCppReferenceConversion(const SbkConverter *converter, PyObject *pyIn) +{ + if (converter->toCppPointerConversion.first) { + if (auto toCppPtr = converter->toCppPointerConversion.first(pyIn)) + return {toCppPtr, PythonToCppConversion::Pointer}; + } + for (const ToCppConversion &c : converter->toCppConversions) { + if (PythonToCppFunc toCppFunc = c.first(pyIn)) + return {toCppFunc, PythonToCppConversion::Value}; + } + return {}; +} + +PythonToCppConversion pythonToCppConversion(const SbkConverter *converter, PyObject *pyIn) +{ + if (auto func = IsPythonToCppConvertible(converter, pyIn)) + return {func, PythonToCppConversion::Value}; + return {}; +} + +PythonToCppFunc isPythonToCppConvertible(const SbkArrayConverter *converter, + int dim1, int dim2, PyObject *pyIn) +{ + assert(pyIn); + for (IsArrayConvertibleToCppFunc f : converter->toCppConversions) { + if (PythonToCppFunc c = f(pyIn, dim1, dim2)) + return c; + } + return nullptr; +} + +LIBSHIBOKEN_API PythonToCppConversion pythonToCppConversion(const SbkArrayConverter *converter, + int dim1, int dim2, PyObject *pyIn) +{ + if (auto func = isPythonToCppConvertible(converter, dim1, dim2, pyIn)) + return {func, PythonToCppConversion::Value}; + return {}; +} + +PythonToCppFunc isPythonToCppReferenceConvertible(PyTypeObject *type, PyObject *pyIn) +{ + if (pyIn != Py_None) { + PythonToCppFunc toCpp = isPythonToCppPointerConvertible(type, pyIn); + if (toCpp) + return toCpp; + } + return isPythonToCppValueConvertible(type, pyIn); +} + +PythonToCppConversion pythonToCppReferenceConversion(PyTypeObject *type, PyObject *pyIn) +{ + if (pyIn == nullptr) + return {}; + if (pyIn != Py_None) { + if (PythonToCppFunc toCppPtr = isPythonToCppPointerConvertible(type, pyIn)) + return {toCppPtr, PythonToCppConversion::Pointer}; + } + if (PythonToCppFunc toCppVal = isPythonToCppValueConvertible(type, pyIn)) + return {toCppVal, PythonToCppConversion::Value}; + return {}; +} + +void nonePythonToCppNullPtr(PyObject *, void *cppOut) +{ + assert(cppOut); + *static_cast<void **>(cppOut) = nullptr; +} + +void *cppPointer(PyTypeObject *desiredType, SbkObject *pyIn) +{ + assert(pyIn); + if (!ObjectType::checkType(desiredType)) + return pyIn; + auto *inType = Py_TYPE(pyIn); + if (ObjectType::hasCast(inType)) + return ObjectType::cast(inType, pyIn, desiredType); + return Object::cppPointer(pyIn, desiredType); +} + +void pythonToCppPointer(PyTypeObject *type, PyObject *pyIn, void *cppOut) +{ + assert(type); + assert(pyIn); + assert(cppOut); + *reinterpret_cast<void **>(cppOut) = pyIn == Py_None + ? nullptr + : cppPointer(type, reinterpret_cast<SbkObject *>(pyIn)); +} + +void pythonToCppPointer(const SbkConverter *converter, PyObject *pyIn, void *cppOut) +{ + assert(converter); + assert(pyIn); + assert(cppOut); + *reinterpret_cast<void **>(cppOut) = pyIn == Py_None + ? nullptr + : cppPointer(converter->pythonType, reinterpret_cast<SbkObject *>(pyIn)); +} + +static void _pythonToCppCopy(const SbkConverter *converter, PyObject *pyIn, void *cppOut) +{ + assert(converter); + assert(pyIn); + assert(cppOut); + PythonToCppFunc toCpp = IsPythonToCppConvertible(converter, pyIn); + if (toCpp) + toCpp(pyIn, cppOut); +} + +void pythonToCppCopy(PyTypeObject *type, PyObject *pyIn, void *cppOut) +{ + assert(type); + auto *sotp = PepType_SOTP(type); + _pythonToCppCopy(sotp->converter, pyIn, cppOut); +} + +void pythonToCppCopy(const SbkConverter *converter, PyObject *pyIn, void *cppOut) +{ + _pythonToCppCopy(converter, pyIn, cppOut); +} + +bool isImplicitConversion(PyTypeObject *type, PythonToCppFunc toCppFunc) +{ + auto *sotp = PepType_SOTP(type); + // This is the Object Type or Value Type conversion that only + // retrieves the C++ pointer held in the Python wrapper. + if (toCppFunc == sotp->converter->toCppPointerConversion.second) + return false; + + // Object Types doesn't have any kind of value conversion, + // only C++ pointer retrieval. + if (sotp->converter->toCppConversions.empty()) + return false; + + // The first conversion of the non-pointer conversion list is + // a Value Type's copy to C++ function, which is not an implicit + // conversion. + // Otherwise it must be one of the implicit conversions. + // Note that we don't check if the Python to C++ conversion is in + // the list of the type's conversions, for it is expected that the + // caller knows what he's doing. + const auto conv = sotp->converter->toCppConversions.cbegin(); + return toCppFunc != (*conv).second; +} + +void registerConverterName(SbkConverter *converter, const char *typeName) +{ + auto iter = converters.find(typeName); + if (iter == converters.end()) + converters.insert(std::make_pair(typeName, converter)); +} + +static std::string getRealTypeName(const std::string &typeName) +{ + auto size = typeName.size(); + if (std::isalnum(typeName[size - 1]) == 0) + return typeName.substr(0, size - 1); + return typeName; +} + +// PYSIDE-2404: Build a negative cache of already failed lookups. +// The resulting list must be reset after each new import, +// because that can change results. Also clear the cache after +// reaching some threashold. +static std::unordered_set<std::string> nonExistingTypeNames{}; + +// Arbitrary size limit to prevent random name overflows. +static constexpr std::size_t negativeCacheLimit = 50; + +static void rememberAsNonexistent(const std::string &typeName) +{ + if (nonExistingTypeNames.size() > negativeCacheLimit) + clearNegativeLazyCache(); + converters.insert(std::make_pair(typeName, nullptr)); + nonExistingTypeNames.insert(typeName); +} + +SbkConverter *getConverter(const char *typeNameC) +{ + std::string typeName = typeNameC; + auto it = converters.find(typeName); + // PYSIDE-2404: This can also contain explicit nullptr as a negative cache. + if (it != converters.end()) + return it->second; + // PYSIDE-2404: Did not find the name. Load the lazy classes + // which have this name and try again. + Shiboken::Module::loadLazyClassesWithName(getRealTypeName(typeName).c_str()); + it = converters.find(typeName); + if (it != converters.end()) + return it->second; + // Cache the negative result. Don't forget to clear the cache for new modules. + rememberAsNonexistent(typeName); + + if (Shiboken::pyVerbose() > 0) { + const std::string message = + std::string("Can't find type resolver for type '") + typeName + "'."; + PyErr_WarnEx(PyExc_RuntimeWarning, message.c_str(), 0); + } + return nullptr; +} + +void clearNegativeLazyCache() +{ + for (const auto &typeName : nonExistingTypeNames) { + auto it = converters.find(typeName); + converters.erase(it); + } + nonExistingTypeNames.clear(); +} + +SbkConverter *primitiveTypeConverter(int index) +{ + return PrimitiveTypeConverters[index]; +} + +bool checkIterableTypes(PyTypeObject *type, PyObject *pyIn) +{ + Shiboken::AutoDecRef it(PyObject_GetIter(pyIn)); + if (it.isNull()) { + PyErr_Clear(); + return false; + } + + while (true) { + Shiboken::AutoDecRef pyItem(PyIter_Next(it.object())); + if (pyItem.isNull()) { + if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_StopIteration)) + PyErr_Clear(); + break; + } + if (!PyObject_TypeCheck(pyItem, type)) + return false; + } + return true; +} + +bool checkSequenceTypes(PyTypeObject *type, PyObject *pyIn) +{ + assert(type); + assert(pyIn); + if (PySequence_Size(pyIn) < 0) { + // clear the error if < 0 which means no length at all + PyErr_Clear(); + return false; + } + const Py_ssize_t size = PySequence_Size(pyIn); + for (Py_ssize_t i = 0; i < size; ++i) { + if (!PyObject_TypeCheck(AutoDecRef(PySequence_GetItem(pyIn, i)), type)) + return false; + } + return true; +} + +bool convertibleIterableTypes(const SbkConverter *converter, PyObject *pyIn) +{ + Shiboken::AutoDecRef it(PyObject_GetIter(pyIn)); + if (it.isNull()) { + PyErr_Clear(); + return false; + } + + while (true) { + Shiboken::AutoDecRef pyItem(PyIter_Next(it.object())); + if (pyItem.isNull()) { + if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_StopIteration)) + PyErr_Clear(); + break; + } + if (!isPythonToCppConvertible(converter, pyItem)) + return false; + } + return true; +} + +bool convertibleSequenceTypes(const SbkConverter *converter, PyObject *pyIn) +{ + assert(converter); + assert(pyIn); + if (!PySequence_Check(pyIn)) + return false; + const Py_ssize_t size = PySequence_Size(pyIn); + for (Py_ssize_t i = 0; i < size; ++i) { + if (!isPythonToCppConvertible(converter, AutoDecRef(PySequence_GetItem(pyIn, i)))) + return false; + } + return true; +} +bool convertibleSequenceTypes(PyTypeObject *type, PyObject *pyIn) +{ + assert(type); + auto *sotp = PepType_SOTP(type); + return convertibleSequenceTypes(sotp->converter, pyIn); +} + +bool convertibleIterableTypes(PyTypeObject *type, PyObject *pyIn) +{ + assert(type); + auto *sotp = PepType_SOTP(type); + return convertibleIterableTypes(sotp->converter, pyIn); +} + +bool checkPairTypes(PyTypeObject *firstType, PyTypeObject *secondType, PyObject *pyIn) +{ + assert(firstType); + assert(secondType); + assert(pyIn); + if (!PySequence_Check(pyIn)) + return false; + if (PySequence_Size(pyIn) != 2) + return false; + if (!PyObject_TypeCheck(AutoDecRef(PySequence_GetItem(pyIn, 0)), firstType)) + return false; + if (!PyObject_TypeCheck(AutoDecRef(PySequence_GetItem(pyIn, 1)), secondType)) + return false; + return true; +} +bool convertiblePairTypes(const SbkConverter *firstConverter, bool firstCheckExact, + const SbkConverter *secondConverter, bool secondCheckExact, + PyObject *pyIn) +{ + assert(firstConverter); + assert(secondConverter); + assert(pyIn); + if (!PySequence_Check(pyIn)) + return false; + if (PySequence_Size(pyIn) != 2) + return false; + AutoDecRef firstItem(PySequence_GetItem(pyIn, 0)); + if (firstCheckExact) { + if (!PyObject_TypeCheck(firstItem, firstConverter->pythonType)) + return false; + } else if (!isPythonToCppConvertible(firstConverter, firstItem)) { + return false; + } + AutoDecRef secondItem(PySequence_GetItem(pyIn, 1)); + if (secondCheckExact) { + if (!PyObject_TypeCheck(secondItem, secondConverter->pythonType)) + return false; + } else if (!isPythonToCppConvertible(secondConverter, secondItem)) { + return false; + } + + return true; +} + +bool checkDictTypes(PyTypeObject *keyType, PyTypeObject *valueType, PyObject *pyIn) +{ + assert(keyType); + assert(valueType); + assert(pyIn); + if (!PyDict_Check(pyIn)) + return false; + + PyObject *key; + PyObject *value; + Py_ssize_t pos = 0; + while (PyDict_Next(pyIn, &pos, &key, &value)) { + if (!PyObject_TypeCheck(key, keyType)) + return false; + if (!PyObject_TypeCheck(value, valueType)) + return false; + } + return true; +} + +bool checkMultiDictTypes(PyTypeObject *keyType, PyTypeObject *valueType, + PyObject *pyIn) +{ + assert(keyType); + assert(valueType); + assert(pyIn); + if (!PyDict_Check(pyIn)) + return false; + + PyObject *key; + PyObject *values; + Py_ssize_t pos = 0; + while (PyDict_Next(pyIn, &pos, &key, &values)) { + if (!PyObject_TypeCheck(key, keyType)) + return false; + if (!PySequence_Check(values)) + return false; + const Py_ssize_t size = PySequence_Size(values); + for (Py_ssize_t i = 0; i < size; ++i) { + AutoDecRef value(PySequence_GetItem(values, i)); + if (!PyObject_TypeCheck(value, valueType)) + return false; + } + } + return true; +} + +bool convertibleDictTypes(const SbkConverter *keyConverter, bool keyCheckExact, const SbkConverter *valueConverter, + bool valueCheckExact, PyObject *pyIn) +{ + assert(keyConverter); + assert(valueConverter); + assert(pyIn); + if (!PyDict_Check(pyIn)) + return false; + PyObject *key; + PyObject *value; + Py_ssize_t pos = 0; + while (PyDict_Next(pyIn, &pos, &key, &value)) { + if (keyCheckExact) { + if (!PyObject_TypeCheck(key, keyConverter->pythonType)) + return false; + } else if (!isPythonToCppConvertible(keyConverter, key)) { + return false; + } + if (valueCheckExact) { + if (!PyObject_TypeCheck(value, valueConverter->pythonType)) + return false; + } else if (!isPythonToCppConvertible(valueConverter, value)) { + return false; + } + } + return true; +} + +bool convertibleMultiDictTypes(const SbkConverter *keyConverter, bool keyCheckExact, + const SbkConverter *valueConverter, + bool valueCheckExact, PyObject *pyIn) +{ + assert(keyConverter); + assert(valueConverter); + assert(pyIn); + if (!PyDict_Check(pyIn)) + return false; + PyObject *key; + PyObject *values; + Py_ssize_t pos = 0; + while (PyDict_Next(pyIn, &pos, &key, &values)) { + if (keyCheckExact) { + if (!PyObject_TypeCheck(key, keyConverter->pythonType)) + return false; + } else if (!isPythonToCppConvertible(keyConverter, key)) { + return false; + } + if (!PySequence_Check(values)) + return false; + const Py_ssize_t size = PySequence_Size(values); + for (Py_ssize_t i = 0; i < size; ++i) { + AutoDecRef value(PySequence_GetItem(values, i)); + if (valueCheckExact) { + if (!PyObject_TypeCheck(value.object(), valueConverter->pythonType)) + return false; + } else if (!isPythonToCppConvertible(valueConverter, value.object())) { + return false; + } + } + } + return true; +} + +PyTypeObject *getPythonTypeObject(const SbkConverter *converter) +{ + if (converter) + return converter->pythonType; + return nullptr; +} + +PyTypeObject *getPythonTypeObject(const char *typeName) +{ + return getPythonTypeObject(getConverter(typeName)); +} + +bool pythonTypeIsValueType(const SbkConverter *converter) +{ + // Unlikely to happen but for multi-inheritance SbkObjs + // the converter is not defined, hence we need a default return. + if (!converter) + return false; + return converter->pointerToPython && converter->copyToPython; +} + +bool pythonTypeIsObjectType(const SbkConverter *converter) +{ + return converter->pointerToPython && !converter->copyToPython; +} + +bool pythonTypeIsWrapperType(const SbkConverter *converter) +{ + return converter->pointerToPython != nullptr; +} + +SpecificConverter::SpecificConverter(const char *typeName) + : m_type(InvalidConversion) +{ + m_converter = getConverter(typeName); + if (!m_converter) + return; + const Py_ssize_t len = strlen(typeName); + char lastChar = typeName[len -1]; + if (lastChar == '&') { + m_type = ReferenceConversion; + } else if (lastChar == '*' || pythonTypeIsObjectType(m_converter)) { + m_type = PointerConversion; + } else { + m_type = CopyConversion; + } +} + +PyObject *SpecificConverter::toPython(const void *cppIn) +{ + switch (m_type) { + case CopyConversion: + return copyToPython(m_converter, cppIn); + case PointerConversion: + return pointerToPython(m_converter, *static_cast<const void * const *>(cppIn)); + case ReferenceConversion: + return referenceToPython(m_converter, cppIn); + default: + PyErr_SetString(PyExc_RuntimeError, "tried to use invalid converter in 'C++ to Python' conversion"); + } + return nullptr; +} + +void SpecificConverter::toCpp(PyObject *pyIn, void *cppOut) +{ + switch (m_type) { + case CopyConversion: + pythonToCppCopy(m_converter, pyIn, cppOut); + break; + case PointerConversion: + pythonToCppPointer(m_converter, pyIn, cppOut); + break; + case ReferenceConversion: + pythonToCppPointer(m_converter, pyIn, &cppOut); + break; + default: + PyErr_SetString(PyExc_RuntimeError, "tried to use invalid converter in 'Python to C++' conversion"); + } +} + +} // namespace Shiboken::Conversions diff --git a/sources/shiboken6/libshiboken/sbkconverter.h b/sources/shiboken6/libshiboken/sbkconverter.h new file mode 100644 index 000000000..0d68f3faf --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkconverter.h @@ -0,0 +1,422 @@ +// 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 + +#ifndef SBK_CONVERTER_H +#define SBK_CONVERTER_H + +#include "sbkpython.h" +#include "sbkmodule.h" +#include "shibokenmacros.h" +#include "sbkenum.h" +#include "basewrapper_p.h" + +#include <limits> +#include <string> + +struct SbkObject; + +/** + * This is a convenience macro identical to Python's PyObject_TypeCheck, + * except that the arguments have swapped places, for the great convenience + * of generator. + */ +#define SbkObject_TypeCheck(tp, ob) \ + (Py_TYPE(ob) == (tp) || PyType_IsSubtype(Py_TYPE(ob), (tp))) + +extern "C" +{ + +/** + * SbkConverter is used to perform type conversions from C++ + * to Python and vice-versa;.and it is also used for type checking. + * SbkConverter is a private structure that must be accessed + * using the functions provided by the converter API. + */ +struct SbkConverter; +struct SbkArrayConverter; + +/** + * Given a void pointer to a C++ object, this function must return + * the proper Python object. It may be either an existing wrapper + * for the C++ object, or a newly create one. Or even the Python + * equivalent of the C++ value passed in the argument. + * + * C++ -> Python + */ +using CppToPythonFunc = PyObject *(*)(const void *); + +/** + * This function converts a Python object to a C++ value, it may be + * a pointer, value, class, container or primitive type, passed via + * a void pointer, that will be cast properly inside the function. + * This function is usually returned by an IsConvertibleToCppFunc + * function, or obtained knowing the type of the Python object input, + * thus it will not check the Python object type, and will expect + * the void pointer to be pointing to a proper variable. + * + * Python -> C++ + */ +using PythonToCppFunc = void (*)(PyObject *,void *); + +/** + * Checks if the Python object passed in the argument is convertible to a + * C++ type defined inside the function, it returns the converter function + * that will transform a Python argument into a C++ value. + * It returns NULL if the Python object is not convertible to the C++ type + * that the function represents. + * + * Python -> C++ ? + */ +using IsConvertibleToCppFunc = PythonToCppFunc (*)(PyObject *); + +} // extern "C" + +namespace Shiboken { +namespace Conversions { + + +class LIBSHIBOKEN_API SpecificConverter +{ +public: + enum Type + { + InvalidConversion, + CopyConversion, + PointerConversion, + ReferenceConversion + }; + + explicit SpecificConverter(const char *typeName); + + inline SbkConverter *converter() { return m_converter; } + inline operator SbkConverter *() const { return m_converter; } + + inline bool isValid() { return m_type != InvalidConversion; } + inline operator bool() const { return m_type != InvalidConversion; } + + inline Type conversionType() { return m_type; } + + PyObject *toPython(const void *cppIn); + void toCpp(PyObject *pyIn, void *cppOut); +private: + SbkConverter *m_converter; + Type m_type; +}; + + +/** + * Creates a converter for a wrapper type. + * \param type A Shiboken.ObjectType that will receive the new converter. + * \param toCppPointerConvFunc Function to retrieve the C++ pointer held by a Python wrapper. + * \param toCppPointerCheckFunc Check and return the retriever function of the C++ pointer held by a Python wrapper. + * \param pointerToPythonFunc Function to convert a C++ object to a Python \p type wrapper, keeping their identity. + * \param copyToPythonFunc Function to convert a C++ object to a Python \p type, copying the object. + * \returns The new converter referred by the wrapper \p type. + */ +LIBSHIBOKEN_API SbkConverter *createConverter(PyTypeObject *type, + PythonToCppFunc toCppPointerConvFunc, + IsConvertibleToCppFunc toCppPointerCheckFunc, + CppToPythonFunc pointerToPythonFunc, + CppToPythonFunc copyToPythonFunc = nullptr); + +/** + * Creates a converter for a non wrapper type (primitive or container type). + * \param type Python type representing to the new converter. + * \param toPythonFunc Function to convert a C++ object to a Python \p type. + * \returns A new type converter. + */ +LIBSHIBOKEN_API SbkConverter *createConverter(PyTypeObject *type, CppToPythonFunc toPythonFunc); + +LIBSHIBOKEN_API void deleteConverter(SbkConverter *converter); + +/// Sets the Python object to C++ pointer conversion function. +LIBSHIBOKEN_API void setCppPointerToPythonFunction(SbkConverter *converter, CppToPythonFunc pointerToPythonFunc); + +/// Sets the C++ pointer to Python object conversion functions. +LIBSHIBOKEN_API void setPythonToCppPointerFunctions(SbkConverter *converter, + PythonToCppFunc toCppPointerConvFunc, + IsConvertibleToCppFunc toCppPointerCheckFunc); + +/** + * Adds a new conversion of a Python object to a C++ value. + * This is used in copy and implicit conversions. + */ +LIBSHIBOKEN_API void addPythonToCppValueConversion(SbkConverter *converter, + PythonToCppFunc pythonToCppFunc, + IsConvertibleToCppFunc isConvertibleToCppFunc); +LIBSHIBOKEN_API void addPythonToCppValueConversion(PyTypeObject *type, + PythonToCppFunc pythonToCppFunc, + IsConvertibleToCppFunc isConvertibleToCppFunc); +LIBSHIBOKEN_API void addPythonToCppValueConversion(Shiboken::Module::TypeInitStruct typeStruct, + PythonToCppFunc pythonToCppFunc, + IsConvertibleToCppFunc isConvertibleToCppFunc); + +// C++ -> Python --------------------------------------------------------------------------- + +/** + * Retrieves the Python wrapper object for the given \p cppIn C++ pointer object. + * This function is used only for Value and Object Types. + * Example usage: + * TYPE *var; + * PyObject *pyVar = pointerToPython(SBKTYPE, &var); + */ +LIBSHIBOKEN_API PyObject *pointerToPython(PyTypeObject *type, const void *cppIn); +LIBSHIBOKEN_API PyObject *pointerToPython(const SbkConverter *converter, const void *cppIn); + +/** + * For the given \p cppIn C++ reference it returns the Python wrapper object, + * always for Object Types, and when they already exist for reference types; + * for when the latter doesn't have an existing wrapper type, the C++ object + * is copied to Python. + * Example usage: + * TYPE &var = SOMETHING; + * PyObject *pyVar = referenceToPython(SBKTYPE, &var); + */ +LIBSHIBOKEN_API PyObject *referenceToPython(PyTypeObject *type, const void *cppIn); +LIBSHIBOKEN_API PyObject *referenceToPython(const SbkConverter *converter, const void *cppIn); + +/** + * Retrieves the Python wrapper object for the given C++ value pointed by \p cppIn. + * This function is used only for Value Types. + * Example usage: + * TYPE var; + * PyObject *pyVar = copyToPython(SBKTYPE, &var); + */ +LIBSHIBOKEN_API PyObject *copyToPython(PyTypeObject *type, const void *cppIn); +LIBSHIBOKEN_API PyObject *copyToPython(const SbkConverter *converter, const void *cppIn); + +// Python -> C++ --------------------------------------------------------------------------- + +struct PythonToCppConversion +{ + enum Type {Invalid, Pointer, Value}; + + operator bool() const { return type != Invalid; } + + void operator()(PyObject *po,void *cpp) const { function(po, cpp); } + + bool isValue() const { return type == Value; } + + PythonToCppFunc function = nullptr; + Type type = Invalid; +}; + +/** + * Returns a Python to C++ conversion function if the Python object is convertible to a C++ pointer. + * It returns NULL if the Python object is not convertible to \p type. + */ +LIBSHIBOKEN_API PythonToCppFunc isPythonToCppPointerConvertible(PyTypeObject *type, PyObject *pyIn); +LIBSHIBOKEN_API PythonToCppConversion pythonToCppPointerConversion(PyTypeObject *type, PyObject *pyIn); +LIBSHIBOKEN_API PythonToCppConversion pythonToCppPointerConversion(Module::TypeInitStruct typeStruct, PyObject *pyIn); + +/** + * Returns a Python to C++ conversion function if the Python object is convertible to a C++ value. + * The resulting converter function will create a copy of the Python object in C++, or implicitly + * convert the object to the expected \p type. + * It returns NULL if the Python object is not convertible to \p type. + */ +LIBSHIBOKEN_API PythonToCppFunc isPythonToCppValueConvertible(PyTypeObject *type, PyObject *pyIn); +LIBSHIBOKEN_API PythonToCppConversion pythonToCppValueConversion(PyTypeObject *type, PyObject *pyIn); + +/** + * Returns a Python to C++ conversion function if the Python object is convertible to a C++ reference. + * The resulting converter function will return the underlying C++ object held by the Python wrapper, + * or a new C++ value if it must be a implicit conversion. + * It returns NULL if the Python object is not convertible to \p type. + */ +LIBSHIBOKEN_API PythonToCppFunc isPythonToCppReferenceConvertible(PyTypeObject *type, PyObject *pyIn); +LIBSHIBOKEN_API PythonToCppConversion pythonToCppReferenceConversion(PyTypeObject *type, PyObject *pyIn); + +/// This is the same as isPythonToCppValueConvertible function. +LIBSHIBOKEN_API PythonToCppFunc isPythonToCppConvertible(const SbkConverter *converter, PyObject *pyIn); +LIBSHIBOKEN_API PythonToCppConversion pythonToCppReferenceConversion(const SbkConverter *converter, PyObject *pyIn); + +LIBSHIBOKEN_API PythonToCppConversion pythonToCppConversion(const SbkConverter *converter, PyObject *pyIn); +LIBSHIBOKEN_API PythonToCppFunc isPythonToCppConvertible(const SbkArrayConverter *converter, + int dim1, int dim2, PyObject *pyIn); +LIBSHIBOKEN_API PythonToCppConversion pythonToCppConversion(const SbkArrayConverter *converter, + int dim1, int dim2, PyObject *pyIn); + +/** + * Returns the C++ pointer for the \p pyIn object cast to the type passed via \p desiredType. + * It differs from Shiboken::Object::cppPointer because it casts the pointer to a proper + * memory offset depending on the desired type. + */ +LIBSHIBOKEN_API void *cppPointer(PyTypeObject *desiredType, SbkObject *pyIn); + +/// Converts a Python object \p pyIn to C++ and stores the result in the C++ pointer passed in \p cppOut. +LIBSHIBOKEN_API void pythonToCppPointer(PyTypeObject *type, PyObject *pyIn, void *cppOut); +LIBSHIBOKEN_API void pythonToCppPointer(const SbkConverter *converter, PyObject *pyIn, void *cppOut); + +/// Converts a Python object \p pyIn to C++, and copies the result in the C++ variable passed in \p cppOut. +LIBSHIBOKEN_API void pythonToCppCopy(PyTypeObject *type, PyObject *pyIn, void *cppOut); +LIBSHIBOKEN_API void pythonToCppCopy(const SbkConverter *converter, PyObject *pyIn, void *cppOut); + +/** + * Helper function returned by generated convertible checking functions + * that returns a C++ NULL when the input Python object is None. + */ +LIBSHIBOKEN_API void nonePythonToCppNullPtr(PyObject *, void *cppOut); + +/** + * Returns true if the \p toCpp function passed is an implicit conversion of Python \p type. + * It is used when C++ expects a reference argument, so it may be the same object received + * from Python, or another created through implicit conversion. + */ +[[deprecated]] LIBSHIBOKEN_API bool isImplicitConversion(PyTypeObject *type, PythonToCppFunc toCpp); + +/// Registers a converter with a type name that may be used to retrieve the converter. +LIBSHIBOKEN_API void registerConverterName(SbkConverter *converter, const char *typeName); + +/// Returns the converter for a given type name, or NULL if it wasn't registered before. +LIBSHIBOKEN_API SbkConverter *getConverter(const char *typeName); + +/// Returns the converter for a primitive type. +LIBSHIBOKEN_API SbkConverter *primitiveTypeConverter(int index); + +/// Returns true if a Python sequence is comprised of objects of the given \p type. +LIBSHIBOKEN_API bool checkSequenceTypes(PyTypeObject *type, PyObject *pyIn); + +/// Returns true if a Python type is iterable and comprised of objects of the +/// given \p type. +LIBSHIBOKEN_API bool checkIterableTypes(PyTypeObject *type, PyObject *pyIn); + +/// Returns true if a Python sequence is comprised of objects of a type convertible to the one represented by the given \p converter. +LIBSHIBOKEN_API bool convertibleSequenceTypes(const SbkConverter *converter, PyObject *pyIn); + +/// Returns true if a Python sequence is comprised of objects of a type convertible to \p type. +LIBSHIBOKEN_API bool convertibleSequenceTypes(PyTypeObject *type, PyObject *pyIn); + +/// Returns true if a Python type is iterable and comprised of objects of a +/// type convertible to the one represented by the given \p converter. +LIBSHIBOKEN_API bool convertibleIterableTypes(const SbkConverter *converter, PyObject *pyIn); + +/// Returns true if a Python type is iterable and comprised of objects of a +/// type convertible to \p type. +LIBSHIBOKEN_API bool convertibleIterableTypes(PyTypeObject *type, PyObject *pyIn); + +/// Returns true if a Python sequence can be converted to a C++ pair. +LIBSHIBOKEN_API bool checkPairTypes(PyTypeObject *firstType, PyTypeObject *secondType, PyObject *pyIn); + +/// Returns true if a Python sequence can be converted to a C++ pair. +LIBSHIBOKEN_API bool convertiblePairTypes(const SbkConverter *firstConverter, bool firstCheckExact, + const SbkConverter *secondConverter, bool secondCheckExact, + PyObject *pyIn); + +/// Returns true if a Python dictionary can be converted to a C++ hash or map. +LIBSHIBOKEN_API bool checkDictTypes(PyTypeObject *keyType, PyTypeObject *valueType, PyObject *pyIn); + +/// Returns true if a Python dictionary can be converted to a C++ multi hash/map. +/// The Python dictionary is expected to contain lists of values +bool checkMultiDictTypes(PyTypeObject *keyType, PyTypeObject *valueType, + PyObject *pyIn); + +/// Returns true if a Python dictionary can be converted to a C++ hash or map. +LIBSHIBOKEN_API bool convertibleDictTypes(const SbkConverter *keyConverter, bool keyCheckExact, + const SbkConverter *valueConverter, bool valueCheckExact, + PyObject *pyIn); + +/// Returns true if a Python dictionary can be converted to a C++ multi hash/map. +/// The Python dictionary is expected to contain lists of values +LIBSHIBOKEN_API bool convertibleMultiDictTypes(const SbkConverter *keyConverter, + bool keyCheckExact, + const SbkConverter *valueConverter, + bool valueCheckExact, + PyObject *pyIn); + +/// Returns the Python type object associated with the given \p converter. +LIBSHIBOKEN_API PyTypeObject *getPythonTypeObject(const SbkConverter *converter); + +/// Returns the Python type object for the given \p typeName. +LIBSHIBOKEN_API PyTypeObject *getPythonTypeObject(const char *typeName); + +/// Returns true if the Python type associated with the converter is a value type. +LIBSHIBOKEN_API bool pythonTypeIsValueType(const SbkConverter *converter); + +/// Returns true if the Python type associated with the converter is an object type. +LIBSHIBOKEN_API bool pythonTypeIsObjectType(const SbkConverter *converter); + +/// Returns true if the Python type associated with the converter is a wrapper type. +LIBSHIBOKEN_API bool pythonTypeIsWrapperType(const SbkConverter *converter); + +#define SBK_PY_LONG_LONG_IDX 0 +// Qt5: name collision in QtCore after QBool is replaced by bool +#define SBK_BOOL_IDX_1 1 +#define SBK_CHAR_IDX 2 +#define SBK_CONSTCHARPTR_IDX 3 +#define SBK_DOUBLE_IDX 4 +#define SBK_FLOAT_IDX 5 +#define SBK_INT_IDX 6 +#define SBK_SIGNEDINT_IDX 6 +#define SBK_LONG_IDX 7 +#define SBK_SHORT_IDX 8 +#define SBK_SIGNEDCHAR_IDX 9 +#define SBK_STD_STRING_IDX 10 +#define SBK_STD_WSTRING_IDX 11 +#define SBK_UNSIGNEDPY_LONG_LONG_IDX 12 +#define SBK_UNSIGNEDCHAR_IDX 13 +#define SBK_UNSIGNEDINT_IDX 14 +#define SBK_UNSIGNEDLONG_IDX 15 +#define SBK_UNSIGNEDSHORT_IDX 16 +#define SBK_VOIDPTR_IDX 17 +#define SBK_NULLPTR_T_IDX 18 + +template<typename T> SbkConverter *PrimitiveTypeConverter() { return nullptr; } +template<> inline SbkConverter *PrimitiveTypeConverter<PY_LONG_LONG>() { return primitiveTypeConverter(SBK_PY_LONG_LONG_IDX); } +template<> inline SbkConverter *PrimitiveTypeConverter<bool>() { return primitiveTypeConverter(SBK_BOOL_IDX_1); } +template<> inline SbkConverter *PrimitiveTypeConverter<char>() { return primitiveTypeConverter(SBK_CHAR_IDX); } +template<> inline SbkConverter *PrimitiveTypeConverter<const char *>() { return primitiveTypeConverter(SBK_CONSTCHARPTR_IDX); } +template<> inline SbkConverter *PrimitiveTypeConverter<double>() { return primitiveTypeConverter(SBK_DOUBLE_IDX); } +template<> inline SbkConverter *PrimitiveTypeConverter<float>() { return primitiveTypeConverter(SBK_FLOAT_IDX); } +template<> inline SbkConverter *PrimitiveTypeConverter<int>() { return primitiveTypeConverter(SBK_INT_IDX); } +template<> inline SbkConverter *PrimitiveTypeConverter<long>() { return primitiveTypeConverter(SBK_LONG_IDX); } +template<> inline SbkConverter *PrimitiveTypeConverter<short>() { return primitiveTypeConverter(SBK_SHORT_IDX); } +template<> inline SbkConverter *PrimitiveTypeConverter<signed char>() { return primitiveTypeConverter(SBK_SIGNEDCHAR_IDX); } +template<> inline SbkConverter *PrimitiveTypeConverter<std::string>() { return primitiveTypeConverter(SBK_STD_STRING_IDX); } +template<> inline SbkConverter *PrimitiveTypeConverter<std::wstring>() { return primitiveTypeConverter(SBK_STD_WSTRING_IDX); } +template<> inline SbkConverter *PrimitiveTypeConverter<unsigned PY_LONG_LONG>() { return primitiveTypeConverter(SBK_UNSIGNEDPY_LONG_LONG_IDX); } +template<> inline SbkConverter *PrimitiveTypeConverter<unsigned char>() { return primitiveTypeConverter(SBK_UNSIGNEDCHAR_IDX); } +template<> inline SbkConverter *PrimitiveTypeConverter<unsigned int>() { return primitiveTypeConverter(SBK_UNSIGNEDINT_IDX); } +template<> inline SbkConverter *PrimitiveTypeConverter<unsigned long>() { return primitiveTypeConverter(SBK_UNSIGNEDLONG_IDX); } +template<> inline SbkConverter *PrimitiveTypeConverter<unsigned short>() { return primitiveTypeConverter(SBK_UNSIGNEDSHORT_IDX); } +template<> inline SbkConverter *PrimitiveTypeConverter<void *>() { return primitiveTypeConverter(SBK_VOIDPTR_IDX); } +template<> inline SbkConverter *PrimitiveTypeConverter<std::nullptr_t>() { return primitiveTypeConverter(SBK_NULLPTR_T_IDX); } + +} // namespace Shiboken::Conversions + +/** +* This function template is used to get the PyTypeObject of a C++ type T. +* All implementations should be provided by template specializations generated by the generator when +* T isn't a C++ primitive type. +* \see SpecialCastFunction +*/ +template<typename T> PyTypeObject *SbkType() { return nullptr; } + +// Below are the template specializations for C++ primitive types. +template<> inline PyTypeObject *SbkType<PY_LONG_LONG>() { return &PyLong_Type; } +template<> inline PyTypeObject *SbkType<bool>() { return &PyBool_Type; } +template<> inline PyTypeObject *SbkType<char>() { return &PyLong_Type; } +template<> inline PyTypeObject *SbkType<double>() { return &PyFloat_Type; } +template<> inline PyTypeObject *SbkType<float>() { return &PyFloat_Type; } +template<> inline PyTypeObject *SbkType<int>() { return &PyLong_Type; } +template<> inline PyTypeObject *SbkType<long>() { return &PyLong_Type; } +template<> inline PyTypeObject *SbkType<short>() { return &PyLong_Type; } +template<> inline PyTypeObject *SbkType<signed char>() { return &PyLong_Type; } +template<> inline PyTypeObject *SbkType<unsigned PY_LONG_LONG>() { return &PyLong_Type; } +template<> inline PyTypeObject *SbkType<unsigned char>() { return &PyLong_Type; } +template<> inline PyTypeObject *SbkType<unsigned int>() { return &PyLong_Type; } +template<> inline PyTypeObject *SbkType<unsigned long>() { return &PyLong_Type; } +template<> inline PyTypeObject *SbkType<unsigned short>() { return &PyLong_Type; } +template<> inline PyTypeObject *SbkType<std::nullptr_t>() { return Py_TYPE(&_Py_NoneStruct); } + +} // namespace Shiboken + +#define SbkChar_Check(X) (PyNumber_Check(X) || Shiboken::String::checkChar(X)) + +struct PySideQFlagsType; +struct SbkQFlagsTypePrivate +{ + SbkConverter *converter; +}; + +#endif // SBK_CONVERTER_H diff --git a/sources/shiboken6/libshiboken/sbkconverter_p.h b/sources/shiboken6/libshiboken/sbkconverter_p.h new file mode 100644 index 000000000..08fc4c8e1 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkconverter_p.h @@ -0,0 +1,542 @@ +// 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 + +#ifndef SBK_CONVERTER_P_H +#define SBK_CONVERTER_P_H + +#include "sbkpython.h" +#include "sbkconverter.h" +#include "sbkcppstring.h" +#include "sbkstring.h" +#include <limits> +#include <typeinfo> +#include <sstream> +#include <iostream> +#include <vector> + +extern "C" +{ + +using ToCppConversion = std::pair<IsConvertibleToCppFunc, PythonToCppFunc>; +using ToCppConversionVector = std::vector<ToCppConversion>; + +/** + * \internal + * Private structure of SbkConverter. + */ +struct SbkConverter +{ + /** + * Python type associated with this converter. If the type is a Shiboken + * wrapper, then it must be a SbkObjectType; otherwise it will be the + * Python type to which the C++ value will be converted (note that the + * C++ type could be produced from various Python types). + */ + PyTypeObject *pythonType; + /** + * This function converts a C++ object to a Python object of the type + * indicated in pythonType. The identity of the C++ object is kept, + * because it looks for an already existing Python wrapper associated + * with the C++ instance. + * It is used to convert C++ pointers and references to Python objects. + */ + CppToPythonFunc pointerToPython; + /** + * This function converts a C++ object to a Python object of the type + * indicated in pythonType. The identity of the object is not kept, + * because a new instance of the C++ object is created. + * It is used to convert objects passed by value, or reference, if said + * reference can't be traced to an object that already has a Python + * wrapper assigned for it. + */ + CppToPythonFunc copyToPython; + /** + * This is a special case of a Python to C++ conversion. It returns + * the underlying C++ pointer of a Python wrapper passed as parameter + * or NULL if the Python object is a None value. + * It comes separated from the other toCppConversions because if you + * have a Python object representing a Value Type the type checking + * for both ValueType and ValueType *would be the same, thus the first + * check would be true and the second would never be reached. + */ + ToCppConversion toCppPointerConversion; + /** + * This is a list of type checking functions that return the + * proper Python to C++ conversion function, for the given Python + * object. + * For Object Types, that never have implicit conversions, this + * list is always empty. + */ + ToCppConversionVector toCppConversions; +}; + +} // extern "C" + +template<typename T, typename MaxLimitType, bool isSigned> +struct OverFlowCheckerBase { + static void formatOverFlowMessage(const MaxLimitType &value, + const std::string *valueAsString = nullptr) + { + std::ostringstream str; + str << "libshiboken: Overflow: Value "; + if (valueAsString != nullptr && !valueAsString->empty()) + str << *valueAsString; + else + str << value; + str << " exceeds limits of type " + << " [" << (isSigned ? "signed" : "unsigned") + << "] \"" << typeid(T).name() + << "\" (" << sizeof(T) << "bytes)."; + const std::string message = str.str(); + PyErr_WarnEx(PyExc_RuntimeWarning, message.c_str(), 0); + } + + // Checks if an overflow occurred inside Python code. + // Precondition: use after calls like PyLong_AsLongLong or PyLong_AsUnsignedLongLong. + // Postcondition: if error ocurred, sets the string reference to the string representation of + // the passed value. + static bool checkForInternalPyOverflow(PyObject *pyIn, std::string &valueAsString) + { + if (PyErr_Occurred()) { + PyErr_Print(); + PyObject *stringRepresentation = PyObject_Str(pyIn); + const char *cString = Shiboken::String::toCString(stringRepresentation); + valueAsString.assign(cString); + Py_DECREF(stringRepresentation); + return true; + } + return false; + } +}; + +// Helper template for checking if a value overflows when cast to type T. +// The MaxLimitType size is usually >= than type T size, so that it can still represent values that +// can't be stored in T (unless the types are of course the same). +// TLDR: MaxLimitType is either long long or unsigned long long. +template<typename T, typename MaxLimitType = PY_LONG_LONG, + bool isSigned = std::numeric_limits<T>::is_signed > +struct OverFlowChecker; + +template<typename T, typename MaxLimitType> +struct OverFlowChecker<T, MaxLimitType, true> : + public OverFlowCheckerBase<T, MaxLimitType, true> { + static bool check(const MaxLimitType &value, PyObject *pyIn) + { + std::string valueAsString; + const bool isOverflow = + OverFlowChecker::checkForInternalPyOverflow(pyIn, valueAsString) + || value < std::numeric_limits<T>::min() + || value > std::numeric_limits<T>::max(); + if (isOverflow) + OverFlowChecker::formatOverFlowMessage(value, &valueAsString); + return isOverflow; + } +}; + +template<typename T, typename MaxLimitType> +struct OverFlowChecker<T, MaxLimitType, false> + : public OverFlowCheckerBase<T, MaxLimitType, false> { + static bool check(const MaxLimitType &value, PyObject *pyIn) + { + std::string valueAsString; + const bool isOverflow = + OverFlowChecker::checkForInternalPyOverflow(pyIn, valueAsString) + || value < 0 + || static_cast<unsigned long long>(value) > std::numeric_limits<T>::max(); + if (isOverflow) + OverFlowChecker::formatOverFlowMessage(value, &valueAsString); + return isOverflow; + } +}; +template<> +struct OverFlowChecker<PY_LONG_LONG, PY_LONG_LONG, true> : + public OverFlowCheckerBase<PY_LONG_LONG, PY_LONG_LONG, true> { + static bool check(const PY_LONG_LONG &value, PyObject *pyIn) { + std::string valueAsString; + const bool isOverflow = checkForInternalPyOverflow(pyIn, valueAsString); + if (isOverflow) + OverFlowChecker::formatOverFlowMessage(value, &valueAsString); + return isOverflow; + + } +}; +template<> +struct OverFlowChecker<double, PY_LONG_LONG, true> { + static bool check(const double &, PyObject *) { return false; } +}; +template<> +struct OverFlowChecker<float, PY_LONG_LONG, true> : + public OverFlowCheckerBase<float, PY_LONG_LONG, true> { + static bool check(const double &value, PyObject *) + { + const bool result = value < std::numeric_limits<float>::min() + || value > std::numeric_limits<float>::max(); + if (result) + formatOverFlowMessage(value); + return result; + } +}; + +// Basic primitive type converters --------------------------------------------------------- + +template <typename T> struct Primitive {}; + +template <typename T> +struct OnePrimitive +{ + static PyObject *toPython(const void *) { return nullptr; } + static PythonToCppFunc isConvertible(PyObject *) { return nullptr; } + static void toCpp(PyObject *, void *) {} + static SbkConverter *createConverter() + { + SbkConverter *converter = Shiboken::Conversions::createConverter(Shiboken::SbkType<T>(), + Primitive<T>::toPython); + Shiboken::Conversions::addPythonToCppValueConversion(converter, + Primitive<T>::toCpp, + Primitive<T>::isConvertible); + return converter; + } +}; +template <typename T> +struct TwoPrimitive : OnePrimitive<T> +{ + static PythonToCppFunc isOtherConvertible(PyObject *) { return nullptr; } + static void otherToCpp(PyObject *, void *) {} + static SbkConverter *createConverter() + { + SbkConverter *converter = OnePrimitive<T>::createConverter(); + Shiboken::Conversions::addPythonToCppValueConversion(converter, Primitive<T>::otherToCpp, Primitive<T>::isOtherConvertible); + return converter; + } +}; + +// Integers -------------------------------------------------------------------------------- + +template <typename INT> +struct IntPrimitive : TwoPrimitive<INT> +{ + static PyObject *toPython(const void *cppIn) + { + return PyLong_FromLong(*reinterpret_cast<const INT *>(cppIn)); + } + static void toCpp(PyObject *pyIn, void *cppOut) + { + double result = PyFloat_AS_DOUBLE(pyIn); + // If cast to long directly it could overflow silently. + if (OverFlowChecker<INT>::check(result, pyIn)) + PyErr_SetObject(PyExc_OverflowError, nullptr); + *reinterpret_cast<INT * >(cppOut) = static_cast<INT>(result); + } + static PythonToCppFunc isConvertible(PyObject *pyIn) + { + if (PyFloat_Check(pyIn)) + return toCpp; + return nullptr; + } + static void otherToCpp(PyObject *pyIn, void *cppOut) + { + PY_LONG_LONG result = PyLong_AsLongLong(pyIn); + if (OverFlowChecker<INT>::check(result, pyIn)) + PyErr_SetObject(PyExc_OverflowError, nullptr); + *reinterpret_cast<INT * >(cppOut) = static_cast<INT>(result); + } + static PythonToCppFunc isOtherConvertible(PyObject *pyIn) + { + if (PyNumber_Check(pyIn)) + return otherToCpp; + return nullptr; + } +}; +template <> struct Primitive<int> : IntPrimitive<int> {}; +template <> struct Primitive<long> : IntPrimitive<long> {}; +template <> struct Primitive<short> : IntPrimitive<short> {}; +template <> struct Primitive<unsigned short> : IntPrimitive<unsigned short> {}; + +// Unsigned Long Integers ------------------------------------------------------------------ + +template <typename LONG> +struct UnsignedLongPrimitive : IntPrimitive<LONG> +{ + static PyObject *toPython(const void *cppIn) + { + return PyLong_FromUnsignedLong(*reinterpret_cast<const LONG *>(cppIn)); + } +}; +template <> struct Primitive<unsigned int> : UnsignedLongPrimitive<unsigned int> {}; +template <> struct Primitive<unsigned long> : UnsignedLongPrimitive<unsigned long> {}; + +// Big integers ---------------------------------------------------------------------------- + +template <> +struct Primitive<PY_LONG_LONG> : OnePrimitive<PY_LONG_LONG> +{ + static PyObject *toPython(const void *cppIn) + { + return PyLong_FromLongLong(*reinterpret_cast<const PY_LONG_LONG *>(cppIn)); + } + static void toCpp(PyObject *pyIn, void *cppOut) + { + PY_LONG_LONG result = PyLong_AsLongLong(pyIn); + if (OverFlowChecker<PY_LONG_LONG>::check(result, pyIn)) + PyErr_SetObject(PyExc_OverflowError, nullptr); + *reinterpret_cast<PY_LONG_LONG * >(cppOut) = result; + } + static PythonToCppFunc isConvertible(PyObject *pyIn) + { + if (PyNumber_Check(pyIn)) + return toCpp; + return nullptr; + } +}; + +template <> +struct Primitive<unsigned PY_LONG_LONG> : OnePrimitive<unsigned PY_LONG_LONG> +{ + static PyObject *toPython(const void *cppIn) + { + return PyLong_FromUnsignedLongLong(*static_cast<const unsigned PY_LONG_LONG *>(cppIn)); + } + static void toCpp(PyObject *pyIn, void *cppOut) + { + if (PyLong_Check(pyIn)) { + unsigned PY_LONG_LONG result = PyLong_AsUnsignedLongLong(pyIn); + if (OverFlowChecker<unsigned PY_LONG_LONG, unsigned PY_LONG_LONG>::check(result, pyIn)) + PyErr_SetObject(PyExc_OverflowError, nullptr); + *reinterpret_cast<unsigned PY_LONG_LONG * >(cppOut) = result; + } + else { + PyErr_SetString(PyExc_TypeError, "Invalid type for unsigned long long conversion"); + } + } + static PythonToCppFunc isConvertible(PyObject *pyIn) + { + if (PyNumber_Check(pyIn)) + return toCpp; + return nullptr; + } +}; + +// Floating point -------------------------------------------------------------------------- + +template <typename FLOAT> +struct FloatPrimitive : TwoPrimitive<FLOAT> +{ + static PyObject *toPython(const void *cppIn) + { + return PyFloat_FromDouble(*reinterpret_cast<const FLOAT *>(cppIn)); + } + static void toCpp(PyObject *pyIn, void *cppOut) + { + *reinterpret_cast<FLOAT *>(cppOut) = FLOAT(PyLong_AsDouble(pyIn)); + } + static PythonToCppFunc isConvertible(PyObject *pyIn) + { + if (PyLong_Check(pyIn) || PyLong_Check(pyIn)) + return toCpp; + return nullptr; + } + static void otherToCpp(PyObject *pyIn, void *cppOut) + { + *reinterpret_cast<FLOAT *>(cppOut) = FLOAT(PyFloat_AsDouble(pyIn)); + } + static PythonToCppFunc isOtherConvertible(PyObject *pyIn) + { + if (PyNumber_Check(pyIn)) + return otherToCpp; + return nullptr; + } +}; +template <> struct Primitive<float> : FloatPrimitive<float> {}; +template <> struct Primitive<double> : FloatPrimitive<double> {}; + +// Boolean --------------------------------------------------------------------------------- + +template <> +struct Primitive<bool> : OnePrimitive<bool> +{ + static PyObject *toPython(const void *cppIn) + { + return PyBool_FromLong(*reinterpret_cast<const bool *>(cppIn)); + } + static PythonToCppFunc isConvertible(PyObject *pyIn) + { + if (PyNumber_Check(pyIn)) + return toCpp; + return nullptr; + } + static void toCpp(PyObject *pyIn, void *cppOut) + { + *reinterpret_cast<bool *>(cppOut) = PyLong_AS_LONG(pyIn) != 0; + } +}; + +// Characters ------------------------------------------------------------------------------ + +template <typename CHAR> +struct CharPrimitive : IntPrimitive<CHAR> +{ + static void toCpp(PyObject *pyIn, void *cppOut) + { + *reinterpret_cast<CHAR *>(cppOut) = CHAR(Shiboken::String::toCString(pyIn)[0]); + } + static PythonToCppFunc isConvertible(PyObject *pyIn) + { + if (Shiboken::String::checkChar(pyIn)) + return toCpp; + return nullptr; + } + static void otherToCpp(PyObject *pyIn, void *cppOut) + { + PY_LONG_LONG result = PyLong_AsLongLong(pyIn); + if (OverFlowChecker<CHAR>::check(result, pyIn)) + PyErr_SetObject(PyExc_OverflowError, nullptr); + *reinterpret_cast<CHAR *>(cppOut) = CHAR(result); + } + static PythonToCppFunc isOtherConvertible(PyObject *pyIn) + { + if (PyNumber_Check(pyIn)) + return otherToCpp; + return nullptr; + } + static SbkConverter *createConverter() + { + SbkConverter *converter = IntPrimitive<CHAR>::createConverter(); + Shiboken::Conversions::addPythonToCppValueConversion(converter, CharPrimitive<CHAR>::otherToCpp, CharPrimitive<CHAR>::isOtherConvertible); + return converter; + } + +}; +template <> struct Primitive<signed char> : CharPrimitive<signed char> {}; +template <> struct Primitive<unsigned char> : CharPrimitive<unsigned char> {}; +template <> struct Primitive<char> : CharPrimitive<char> { + using CharPrimitive<char>::toPython; + static PyObject *toPython(const void *cppIn) { + return Shiboken::String::fromCString(reinterpret_cast<const char *>(cppIn), 1); + } +}; + + + +// Strings --------------------------------------------------------------------------------- + +template <> +struct Primitive<const char *> : TwoPrimitive<const char *> +{ + static PyObject *toPython(const void *cppIn) + { + if (!cppIn) + Py_RETURN_NONE; + return Shiboken::String::fromCString(reinterpret_cast<const char *>(cppIn)); + } + static void toCpp(PyObject *, void *cppOut) + { + *reinterpret_cast<const char **>(cppOut) = nullptr; + } + static PythonToCppFunc isConvertible(PyObject *pyIn) + { + if (pyIn == Py_None) + return toCpp; + return nullptr; + } + static void otherToCpp(PyObject *pyIn, void *cppOut) + { + *reinterpret_cast<const char **>(cppOut) = Shiboken::String::toCString(pyIn); + } + static PythonToCppFunc isOtherConvertible(PyObject *pyIn) + { + if (Shiboken::String::check(pyIn)) + return otherToCpp; + return nullptr; + } +}; + +template <> +struct Primitive<std::string> : TwoPrimitive<std::string> +{ + static PyObject *toPython(const void *cppIn) + { + return Shiboken::String::fromCppString(*reinterpret_cast<const std::string *>(cppIn)); + } + static void toCpp(PyObject *, void *cppOut) + { + reinterpret_cast<std::string *>(cppOut)->clear(); + } + static PythonToCppFunc isConvertible(PyObject *pyIn) + { + if (pyIn == Py_None) + return toCpp; + return nullptr; + } + static void otherToCpp(PyObject *pyIn, void *cppOut) + { + Shiboken::String::toCppString(pyIn, reinterpret_cast<std::string *>(cppOut)); + } + static PythonToCppFunc isOtherConvertible(PyObject *pyIn) + { + if (Shiboken::String::check(pyIn)) + return otherToCpp; + return nullptr; + } +}; + +template <> +struct Primitive<std::wstring> : TwoPrimitive<std::wstring> +{ + static PyObject *toPython(const void *cppIn) + { + return Shiboken::String::fromCppWString(*reinterpret_cast<const std::wstring *>(cppIn)); + } + static void toCpp(PyObject *, void *cppOut) + { + reinterpret_cast<std::wstring *>(cppOut)->clear(); + } + static PythonToCppFunc isConvertible(PyObject *pyIn) + { + return pyIn == Py_None ? toCpp : nullptr; + } + static void otherToCpp(PyObject *pyIn, void *cppOut) + { + Shiboken::String::toCppWString(pyIn, reinterpret_cast<std::wstring *>(cppOut)); + } + static PythonToCppFunc isOtherConvertible(PyObject *pyIn) + { + return PyUnicode_Check(pyIn) ? otherToCpp : nullptr; + } +}; + +// nullptr_t +template <> +struct Primitive<std::nullptr_t> : OnePrimitive<std::nullptr_t> +{ + static PyObject *toPython(const void * /* cppIn */) + { + Py_RETURN_NONE; + } + static void toCpp(PyObject *, void *cppOut) + { + *reinterpret_cast<std::nullptr_t *>(cppOut) = nullptr; + } + static PythonToCppFunc isConvertible(PyObject *pyIn) + { + if (pyIn == Py_None) + return toCpp; + return nullptr; + } +}; + +namespace Shiboken::Conversions { + +SbkConverter *createConverterObject(PyTypeObject *type, + PythonToCppFunc toCppPointerConvFunc, + IsConvertibleToCppFunc toCppPointerCheckFunc, + CppToPythonFunc pointerToPythonFunc, + CppToPythonFunc copyToPythonFunc); + +LIBSHIBOKEN_API void dumpConverters(); + +/// Interface for sbkmodule which must reset cache when new module is loaded. +LIBSHIBOKEN_API void clearNegativeLazyCache(); + +} // namespace Shiboken::Conversions + +#endif // SBK_CONVERTER_P_H diff --git a/sources/shiboken6/libshiboken/sbkcppstring.cpp b/sources/shiboken6/libshiboken/sbkcppstring.cpp new file mode 100644 index 000000000..8e8324f5e --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkcppstring.cpp @@ -0,0 +1,54 @@ +// Copyright (C) 2021 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 "sbkcppstring.h" +#include "autodecref.h" + +namespace Shiboken::String +{ + +PyObject *fromCppString(const std::string &value) +{ + return PyUnicode_FromStringAndSize(value.data(), value.size()); +} + +PyObject *fromCppStringView(std::string_view value) +{ + return PyUnicode_FromStringAndSize(value.data(), value.size()); +} + +PyObject *fromCppWString(const std::wstring &value) +{ + return PyUnicode_FromWideChar(value.data(), value.size()); +} + +void toCppString(PyObject *str, std::string *value) +{ + value->clear(); + + if (str == Py_None) + return; + + if (PyUnicode_Check(str)) { + if (PyUnicode_GetLength(str) > 0) + value->assign(_PepUnicode_AsString(str)); + return; + } + + if (PyBytes_Check(str)) + value->assign(PyBytes_AS_STRING(str)); +} + +void toCppWString(PyObject *str, std::wstring *value) +{ + value->clear(); + + if (str == Py_None || PyUnicode_Check(str) == 0 || PyUnicode_GetLength(str) == 0) + return; + + wchar_t *w = PyUnicode_AsWideCharString(str, nullptr); + value->assign(w); + PyMem_Free(w); +} + +} // namespace Shiboken::String diff --git a/sources/shiboken6/libshiboken/sbkcppstring.h b/sources/shiboken6/libshiboken/sbkcppstring.h new file mode 100644 index 000000000..7ffe11c75 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkcppstring.h @@ -0,0 +1,22 @@ +// Copyright (C) 2021 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 + +#ifndef SBKCPPSTRING_H +#define SBKCPPSTRING_H + +#include "sbkpython.h" +#include "shibokenmacros.h" + +#include <string> +#include <string_view> + +namespace Shiboken::String +{ + LIBSHIBOKEN_API PyObject *fromCppString(const std::string &value); + LIBSHIBOKEN_API PyObject *fromCppStringView(std::string_view value); + LIBSHIBOKEN_API PyObject *fromCppWString(const std::wstring &value); + LIBSHIBOKEN_API void toCppString(PyObject *str, std::string *value); + LIBSHIBOKEN_API void toCppWString(PyObject *str, std::wstring *value); +} // namespace Shiboken::String + +#endif // SBKCPPSTRING_H diff --git a/sources/shiboken6/libshiboken/sbkcpptonumpy.cpp b/sources/shiboken6/libshiboken/sbkcpptonumpy.cpp new file mode 100644 index 000000000..7637efa70 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkcpptonumpy.cpp @@ -0,0 +1,67 @@ +// Copyright (C) 2022 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 + +// included by sbknumpy.cpp + +namespace Shiboken::Numpy { + +#ifdef HAVE_NUMPY + +// Helper to create a 1-dimensional numpy array +template <class Type> +static PyObject *_createArray1(Py_ssize_t size, int numpyType, const Type *data) +{ + const npy_intp dims[1] = {size}; + PyObject *result = PyArray_EMPTY(1, dims, numpyType, 0); + auto *array = reinterpret_cast<PyArrayObject *>(result); + auto *rawTargetData = PyArray_DATA(array); + auto *targetData = reinterpret_cast<Type *>(rawTargetData); + std::copy(data, data + size, targetData); + return result; +} + +PyObject *createByteArray1(Py_ssize_t size, const uint8_t *data) +{ + return _createArray1(size, NPY_BYTE, data); +} + +PyObject *createDoubleArray1(Py_ssize_t size, const double *data) +{ + return _createArray1(size, NPY_DOUBLE, data); +} + +PyObject *createFloatArray1(Py_ssize_t size, const float *data) +{ + return _createArray1(size, NPY_FLOAT, data); +} + +PyObject *createIntArray1(Py_ssize_t size, const int *data) +{ + return _createArray1(size, NPY_INT, data); +} + +#else // HAVE_NUMPY + +PyObject *createByteArray1(Py_ssize_t, const uint8_t *) +{ + return Py_None; +} + +PyObject *createDoubleArray1(Py_ssize_t, const double *) +{ + Py_RETURN_NONE; +} + +PyObject *createFloatArray1(Py_ssize_t, const float *) +{ + Py_RETURN_NONE; +} + +PyObject *createIntArray1(Py_ssize_t, const int *) +{ + Py_RETURN_NONE; +} + +#endif // !HAVE_NUMPY + +} //namespace Shiboken::Numpy diff --git a/sources/shiboken6/libshiboken/sbkcpptonumpy.h b/sources/shiboken6/libshiboken/sbkcpptonumpy.h new file mode 100644 index 000000000..8b9b0cfd2 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkcpptonumpy.h @@ -0,0 +1,41 @@ +// Copyright (C) 2022 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 + +#ifndef SBKCPPTONUMPY_H +#define SBKCPPTONUMPY_H + +#include <sbkpython.h> +#include <shibokenmacros.h> + +#include <cstdint> + +namespace Shiboken::Numpy +{ + +/// Create a one-dimensional numpy array of type uint8/NPY_BYTE +/// \param size Size +/// \param data Data +/// \return PyArrayObject +LIBSHIBOKEN_API PyObject *createByteArray1(Py_ssize_t size, const uint8_t *data); + +/// Create a one-dimensional numpy array of type double/NPY_DOUBLE +/// \param size Size +/// \param data Data +/// \return PyArrayObject +LIBSHIBOKEN_API PyObject *createDoubleArray1(Py_ssize_t size, const double *data); + +/// Create a one-dimensional numpy array of type float/NPY_FLOAT +/// \param size Size +/// \param data Data +/// \return PyArrayObject +LIBSHIBOKEN_API PyObject *createFloatArray1(Py_ssize_t size, const float *data); + +/// Create a one-dimensional numpy array of type int/NPY_INT +/// \param size Size +/// \param data Data +/// \return PyArrayObject +LIBSHIBOKEN_API PyObject *createIntArray1(Py_ssize_t size, const int *data); + +} //namespace Shiboken::Numpy + +#endif // SBKCPPTONUMPY_H diff --git a/sources/shiboken6/libshiboken/sbkenum.cpp b/sources/shiboken6/libshiboken/sbkenum.cpp new file mode 100644 index 000000000..4c0597bda --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkenum.cpp @@ -0,0 +1,455 @@ +// Copyright (C) 2018 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 "sbkenum.h" +#include "sbkstring.h" +#include "helper.h" +#include "sbkstaticstrings.h" +#include "sbkstaticstrings_p.h" +#include "sbkconverter.h" +#include "basewrapper.h" +#include "autodecref.h" +#include "sbkpython.h" +#include "signature.h" + +#include <cstring> +#include <vector> +#include <sstream> + +using namespace Shiboken; + +extern "C" +{ + +struct SbkEnumType +{ + PyTypeObject type; +}; + +// Initialization +static bool _init_enum() +{ + AutoDecRef shibo(PyImport_ImportModule("shiboken6.Shiboken")); + return !shibo.isNull(); +} + +static PyObject *PyEnumModule{}; +static PyObject *PyEnumMeta{}; +static PyObject *PyEnum{}; +static PyObject *PyIntEnum{}; +static PyObject *PyFlag{}; +static PyObject *PyIntFlag{}; +static PyObject *PyFlag_KEEP{}; + +bool PyEnumMeta_Check(PyObject *ob) +{ + return Py_TYPE(ob) == reinterpret_cast<PyTypeObject *>(PyEnumMeta); +} + +PyTypeObject *getPyEnumMeta() +{ + if (PyEnumMeta) + return reinterpret_cast<PyTypeObject *>(PyEnumMeta); + + static auto *mod = PyImport_ImportModule("enum"); + if (mod) { + PyEnumModule = mod; + PyEnumMeta = PyObject_GetAttrString(mod, "EnumMeta"); + if (PyEnumMeta && PyType_Check(PyEnumMeta)) + PyEnum = PyObject_GetAttrString(mod, "Enum"); + if (PyEnum && PyType_Check(PyEnum)) + PyIntEnum = PyObject_GetAttrString(mod, "IntEnum"); + if (PyIntEnum && PyType_Check(PyIntEnum)) + PyFlag = PyObject_GetAttrString(mod, "Flag"); + if (PyFlag && PyType_Check(PyFlag)) + PyIntFlag = PyObject_GetAttrString(mod, "IntFlag"); + if (PyIntFlag && PyType_Check(PyIntFlag)) { + // KEEP is defined from Python 3.11 on. + PyFlag_KEEP = PyObject_GetAttrString(mod, "KEEP"); + PyErr_Clear(); + return reinterpret_cast<PyTypeObject *>(PyEnumMeta); + } + } + Py_FatalError("Python module 'enum' not found"); + return nullptr; +} + +void init_enum() +{ + static bool isInitialized = false; + if (isInitialized) + return; + if (!(isInitialized || _init_enum())) + Py_FatalError("could not init enum"); + + // PYSIDE-1735: Determine whether we should use the old or the new enum implementation. + static PyObject *option = PySys_GetObject("pyside6_option_python_enum"); + if (!option || !PyLong_Check(option)) { + PyErr_Clear(); + option = PyLong_FromLong(1); + } + int ignoreOver{}; + Enum::enumOption = PyLong_AsLongAndOverflow(option, &ignoreOver); + getPyEnumMeta(); + isInitialized = true; +} + +// PYSIDE-1735: Helper function supporting QEnum +int enumIsFlag(PyObject *ob_type) +{ + init_enum(); + + auto *metatype = Py_TYPE(ob_type); + if (metatype != reinterpret_cast<PyTypeObject *>(PyEnumMeta)) + return -1; + auto *mro = reinterpret_cast<PyTypeObject *>(ob_type)->tp_mro; + const Py_ssize_t n = PyTuple_GET_SIZE(mro); + for (Py_ssize_t idx = 0; idx < n; ++idx) { + auto *sub_type = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx)); + if (sub_type == reinterpret_cast<PyTypeObject *>(PyFlag)) + return 1; + } + return 0; +} + +/////////////////////////////////////////////////////////////////////// +// +// Support for Missing Values +// ========================== +// +// Qt enums sometimes use undefined values in enums. +// The enum module handles this by the option "KEEP" for Flag and +// IntFlag. The handling of missing enum values is still strict. +// +// We changed that (also for compatibility with some competitor) +// and provide a `_missing_` function that creates the missing value. +// +// The idea: +// --------- +// We cannot modify the already created class. +// But we can create a one-element class with the new value and +// pretend that this is the already existing class. +// +// We create each constant only once and keep the result in a dict +// "_sbk_missing_". This is similar to a competitor's "_sip_missing_". +// + +static PyObject *missing_func(PyObject * /* self */ , PyObject *args) +{ + // In order to relax matters to be more compatible with C++, we need + // to create a pseudo-member with that value. + static auto *const _sbk_missing = Shiboken::String::createStaticString("_sbk_missing_"); + static auto *const _name = Shiboken::String::createStaticString("__name__"); + static auto *const _mro = Shiboken::String::createStaticString("__mro__"); + static auto *const _class = Shiboken::String::createStaticString("__class__"); + + PyObject *klass{}, *value{}; + if (!PyArg_UnpackTuple(args, "missing", 2, 2, &klass, &value)) + Py_RETURN_NONE; + if (!PyLong_Check(value)) + Py_RETURN_NONE; + auto *type = reinterpret_cast<PyTypeObject *>(klass); + AutoDecRef tpDict(PepType_GetDict(type)); + auto *sbk_missing = PyDict_GetItem(tpDict.object(), _sbk_missing); + if (!sbk_missing) { + sbk_missing = PyDict_New(); + PyDict_SetItem(tpDict.object(), _sbk_missing, sbk_missing); + } + // See if the value is already in the dict. + AutoDecRef val_str(PyObject_CallMethod(value, "__str__", nullptr)); + auto *ret = PyDict_GetItem(sbk_missing, val_str); + if (ret) { + Py_INCREF(ret); + return ret; + } + // No, we must create a new object and insert it into the dict. + AutoDecRef cls_name(PyObject_GetAttr(klass, _name)); + AutoDecRef mro(PyObject_GetAttr(klass, _mro)); + auto *baseClass(PyTuple_GetItem(mro, 1)); + AutoDecRef param(PyDict_New()); + PyDict_SetItem(param, val_str, value); + AutoDecRef fake(PyObject_CallFunctionObjArgs(baseClass, cls_name.object(), param.object(), + nullptr)); + ret = PyObject_GetAttr(fake, val_str); + PyDict_SetItem(sbk_missing, val_str, ret); + // Now the real fake: Pretend that the type is our original type! + PyObject_SetAttr(ret, _class, klass); + return ret; +} + +static struct PyMethodDef dummy_methods[] = { + {"_missing_", reinterpret_cast<PyCFunction>(missing_func), METH_VARARGS|METH_STATIC, nullptr}, + {nullptr, nullptr, 0, nullptr} +}; + +static PyType_Slot dummy_slots[] = { + {Py_tp_base, reinterpret_cast<void *>(&PyType_Type)}, + {Py_tp_methods, reinterpret_cast<void *>(dummy_methods)}, + {0, nullptr} +}; + +static PyType_Spec dummy_spec = { + "1:builtins.EnumType", + 0, + 0, + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, + dummy_slots, +}; + +static PyObject *create_missing_func(PyObject *klass) +{ + // When creating the class, memorize it in the missing function by + // a partial function argument. + static auto *const type = SbkType_FromSpec(&dummy_spec); + static auto *const obType = reinterpret_cast<PyObject *>(type); + static auto *const _missing = Shiboken::String::createStaticString("_missing_"); + static auto *const func = PyObject_GetAttr(obType, _missing); + static auto *const partial = Pep_GetPartialFunction(); + return PyObject_CallFunctionObjArgs(partial, func, klass, nullptr); +} +// +//////////////////////////////////////////////////////////////////////// + +} // extern "C" + +namespace Shiboken::Enum { + +int enumOption{}; + +bool check(PyObject *pyObj) +{ + init_enum(); + + static PyTypeObject *meta = getPyEnumMeta(); + return Py_TYPE(Py_TYPE(pyObj)) == reinterpret_cast<PyTypeObject *>(meta); +} + +PyObject *getEnumItemFromValue(PyTypeObject *enumType, EnumValueType itemValue) +{ + init_enum(); + + auto *obEnumType = reinterpret_cast<PyObject *>(enumType); + AutoDecRef val2members(PyObject_GetAttrString(obEnumType, "_value2member_map_")); + if (val2members.isNull()) { + PyErr_Clear(); + return nullptr; + } + AutoDecRef ob_value(PyLong_FromLongLong(itemValue)); + auto *result = PyDict_GetItem(val2members, ob_value); + Py_XINCREF(result); + return result; +} + +PyObject *newItem(PyTypeObject *enumType, EnumValueType itemValue, + const char *itemName) +{ + init_enum(); + + auto *obEnumType = reinterpret_cast<PyObject *>(enumType); + if (!itemName) + return PyObject_CallFunction(obEnumType, "L", itemValue); + + static PyObject *const _member_map_ = String::createStaticString("_member_map_"); + AutoDecRef tpDict(PepType_GetDict(enumType)); + auto *member_map = PyDict_GetItem(tpDict.object(), _member_map_); + if (!(member_map && PyDict_Check(member_map))) + return nullptr; + auto *result = PyDict_GetItemString(member_map, itemName); + Py_XINCREF(result); + return result; +} + +EnumValueType getValue(PyObject *enumItem) +{ + init_enum(); + + assert(Enum::check(enumItem)); + + AutoDecRef pyValue(PyObject_GetAttrString(enumItem, "value")); + return PyLong_AsLongLong(pyValue); +} + +void setTypeConverter(PyTypeObject *type, SbkConverter *converter) +{ + auto *enumType = reinterpret_cast<SbkEnumType *>(type); + PepType_SETP(enumType)->converter = converter; +} + +static PyTypeObject *createEnumForPython(PyObject *scopeOrModule, + const char *fullName, + PyObject *pyEnumItems) +{ + const char *colon = strchr(fullName, ':'); + assert(colon); + int package_level = atoi(fullName); + const char *mod = colon + 1; + + const char *qual = mod; + for (int idx = package_level; idx > 0; --idx) { + const char *dot = strchr(qual, '.'); + if (!dot) + break; + qual = dot + 1; + } + int mlen = qual - mod - 1; + AutoDecRef module(Shiboken::String::fromCString(mod, mlen)); + AutoDecRef qualname(Shiboken::String::fromCString(qual)); + const char *dot = strrchr(qual, '.'); + AutoDecRef name(Shiboken::String::fromCString(dot ? dot + 1 : qual)); + + static PyObject *enumName = String::createStaticString("IntEnum"); + if (PyType_Check(scopeOrModule)) { + // For global objects, we have no good solution, yet where to put the int info. + auto type = reinterpret_cast<PyTypeObject *>(scopeOrModule); + auto *sotp = PepType_SOTP(type); + if (!sotp->enumFlagsDict) + initEnumFlagsDict(type); + enumName = PyDict_GetItem(sotp->enumTypeDict, name); + } + + SBK_UNUSED(getPyEnumMeta()); // enforce PyEnumModule creation + assert(PyEnumModule != nullptr); + AutoDecRef PyEnumType(PyObject_GetAttr(PyEnumModule, enumName)); + assert(PyEnumType.object()); + bool isFlag = PyObject_IsSubclass(PyEnumType, PyFlag); + + // See if we should use the Int versions of the types, again + bool useIntInheritance = Enum::enumOption & Enum::ENOPT_INHERIT_INT; + if (useIntInheritance) { + auto *surrogate = PyObject_IsSubclass(PyEnumType, PyFlag) ? PyIntFlag : PyIntEnum; + Py_INCREF(surrogate); + PyEnumType.reset(surrogate); + } + + // Walk the enumItemStrings and create a Python enum type. + auto *pyName = name.object(); + + // We now create the new type. Since Python 3.11, we need to pass in + // `boundary=KEEP` because the default STRICT crashes on us. + // See QDir.Filter.Drives | QDir.Filter.Files + AutoDecRef callArgs(Py_BuildValue("(OO)", pyName, pyEnumItems)); + AutoDecRef callDict(PyDict_New()); + static PyObject *boundary = String::createStaticString("boundary"); + if (PyFlag_KEEP) + PyDict_SetItem(callDict, boundary, PyFlag_KEEP); + auto *obNewType = PyObject_Call(PyEnumType, callArgs, callDict); + if (!obNewType || PyObject_SetAttr(scopeOrModule, pyName, obNewType) < 0) + return nullptr; + + // For compatibility with Qt enums, provide a permissive missing method for (Int)?Enum. + if (!isFlag) { + bool supportMissing = !(Enum::enumOption & Enum::ENOPT_NO_MISSING); + if (supportMissing) { + AutoDecRef enum_missing(create_missing_func(obNewType)); + PyObject_SetAttrString(obNewType, "_missing_", enum_missing); + } + } + + auto *newType = reinterpret_cast<PyTypeObject *>(obNewType); + PyObject_SetAttr(obNewType, PyMagicName::qualname(), qualname); + PyObject_SetAttr(obNewType, PyMagicName::module(), module); + + // See if we should re-introduce shortcuts in the enclosing object. + const bool useGlobalShortcut = (Enum::enumOption & Enum::ENOPT_GLOBAL_SHORTCUT) != 0; + const bool useScopedShortcut = (Enum::enumOption & Enum::ENOPT_SCOPED_SHORTCUT) != 0; + if (useGlobalShortcut || useScopedShortcut) { + // We have to use the iterator protokol because the values dict is a mappingproxy. + AutoDecRef values(PyObject_GetAttr(obNewType, PyMagicName::members())); + AutoDecRef mapIterator(PyObject_GetIter(values)); + AutoDecRef mapKey{}; + bool isModule = PyModule_Check(scopeOrModule); + while ((mapKey.reset(PyIter_Next(mapIterator))), mapKey.object()) { + if ((useGlobalShortcut && isModule) || (useScopedShortcut && !isModule)) { + AutoDecRef value(PyObject_GetItem(values, mapKey)); + if (PyObject_SetAttr(scopeOrModule, mapKey, value) < 0) + return nullptr; + } + } + } + + return newType; +} + +template <typename IntT> +static PyObject *toPyObject(IntT v) +{ + if constexpr (sizeof(IntT) == 8) { + if constexpr (std::is_unsigned_v<IntT>) + return PyLong_FromUnsignedLongLong(v); + return PyLong_FromLongLong(v); + } + if constexpr (std::is_unsigned_v<IntT>) + return PyLong_FromUnsignedLong(v); + return PyLong_FromLong(v); +} + +template <typename IntT> +static PyTypeObject *createPythonEnumHelper(PyObject *module, + const char *fullName, const char *enumItemStrings[], const IntT enumValues[]) +{ + AutoDecRef args(PyList_New(0)); + auto *pyEnumItems = args.object(); + for (size_t idx = 0; enumItemStrings[idx] != nullptr; ++idx) { + const char *kv = enumItemStrings[idx]; + auto *key = PyUnicode_FromString(kv); + auto *value = toPyObject(enumValues[idx]); + auto *key_value = PyTuple_New(2); + PyTuple_SET_ITEM(key_value, 0, key); + PyTuple_SET_ITEM(key_value, 1, value); + PyList_Append(pyEnumItems, key_value); + } + return createEnumForPython(module, fullName, pyEnumItems); +} + +// Now we have to concretize these functions explicitly, +// otherwise templates will not work across modules. + +PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const int64_t enumValues[]) +{ + return createPythonEnumHelper(module, fullName, enumItemStrings, enumValues); +} + +PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const uint64_t enumValues[]) +{ + return createPythonEnumHelper(module, fullName, enumItemStrings, enumValues); +} + +PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const int32_t enumValues[]) +{ + return createPythonEnumHelper(module, fullName, enumItemStrings, enumValues); +} + +PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const uint32_t enumValues[]) +{ + return createPythonEnumHelper(module, fullName, enumItemStrings, enumValues); +} + +PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const int16_t enumValues[]) +{ + return createPythonEnumHelper(module, fullName, enumItemStrings, enumValues); +} + +PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const uint16_t enumValues[]) +{ + return createPythonEnumHelper(module, fullName, enumItemStrings, enumValues); +} + +PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const int8_t enumValues[]) +{ + return createPythonEnumHelper(module, fullName, enumItemStrings, enumValues); +} + +PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const uint8_t enumValues[]) +{ + return createPythonEnumHelper(module, fullName, enumItemStrings, enumValues); +} + +} // namespace Shiboken::Enum diff --git a/sources/shiboken6/libshiboken/sbkenum.h b/sources/shiboken6/libshiboken/sbkenum.h new file mode 100644 index 000000000..e19ca4b4c --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkenum.h @@ -0,0 +1,102 @@ +// 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 + +#ifndef SBKENUM_H +#define SBKENUM_H + +#include "sbkpython.h" +#include "shibokenmacros.h" + +extern "C" +{ + +LIBSHIBOKEN_API bool PyEnumMeta_Check(PyObject *ob); + +/// exposed for the signature module +LIBSHIBOKEN_API void init_enum(); + +struct SbkConverter; +struct SbkEnumType; + +struct SbkEnumTypePrivate +{ + SbkConverter *converter; +}; + +/// PYSIDE-1735: Pass on the Python enum/flag information. +LIBSHIBOKEN_API void initEnumFlagsDict(PyTypeObject *type); + +/// PYSIDE-1735: Make sure that we can import the Python enum implementation. +LIBSHIBOKEN_API PyTypeObject *getPyEnumMeta(); +/// PYSIDE-1735: Helper function supporting QEnum +LIBSHIBOKEN_API int enumIsFlag(PyObject *ob_enum); + +} + +namespace Shiboken::Enum { + +enum : int { + ENOPT_OLD_ENUM = 0x00, // PySide 6.6: no longer supported + ENOPT_NEW_ENUM = 0x01, + ENOPT_INHERIT_INT = 0x02, + ENOPT_GLOBAL_SHORTCUT = 0x04, + ENOPT_SCOPED_SHORTCUT = 0x08, + ENOPT_NO_FAKESHORTCUT = 0x10, + ENOPT_NO_FAKERENAMES = 0x20, + ENOPT_NO_ZERODEFAULT = 0x40, + ENOPT_NO_MISSING = 0x80, +}; + +LIBSHIBOKEN_API extern int enumOption; + +using EnumValueType = long long; + +LIBSHIBOKEN_API bool check(PyObject *obj); + +LIBSHIBOKEN_API PyObject *newItem(PyTypeObject *enumType, EnumValueType itemValue, + const char *itemName = nullptr); + +LIBSHIBOKEN_API EnumValueType getValue(PyObject *enumItem); +LIBSHIBOKEN_API PyObject *getEnumItemFromValue(PyTypeObject *enumType, + EnumValueType itemValue); + +/// Sets the enum/flag's type converter. +LIBSHIBOKEN_API void setTypeConverter(PyTypeObject *type, SbkConverter *converter); + +/// Creating Python enums for different types. +LIBSHIBOKEN_API PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const int64_t enumValues[]); + +LIBSHIBOKEN_API PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const uint64_t enumValues[]); + +LIBSHIBOKEN_API PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const int32_t enumValues[]); + +LIBSHIBOKEN_API PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const uint32_t enumValues[]); + +LIBSHIBOKEN_API PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const int16_t enumValues[]); + +LIBSHIBOKEN_API PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const uint16_t enumValues[]); + +LIBSHIBOKEN_API PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const int8_t enumValues[]); + +LIBSHIBOKEN_API PyTypeObject *createPythonEnum(PyObject *module, + const char *fullName, const char *enumItemStrings[], const uint8_t enumValues[]); + +/// This template removes duplication by inlining necessary type casts. +template <typename IntT> +inline PyTypeObject *createPythonEnum(PyTypeObject *scope, + const char *fullName, const char *enumItemStrings[], const IntT enumValues[]) +{ + auto *obScope = reinterpret_cast<PyObject *>(scope); + return createPythonEnum(obScope, fullName, enumItemStrings, enumValues); +} + +} // namespace Shiboken::Enum + +#endif // SKB_PYENUM_H diff --git a/sources/shiboken6/libshiboken/sbkerrors.cpp b/sources/shiboken6/libshiboken/sbkerrors.cpp new file mode 100644 index 000000000..84c080f8d --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkerrors.cpp @@ -0,0 +1,215 @@ +// Copyright (C) 2022 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 "sbkerrors.h" +#include "sbkstring.h" +#include "helper.h" +#include "gilstate.h" + +#include <cstdio> +#include <string> + +using namespace std::literals::string_literals; + +namespace Shiboken +{ + +// PYSIDE-2335: Track down if we can reach a Python error handler. +// _pythonContextStack has always the current state of handler status +// in its lowest bit. +// Blocking calls like exec or run need to use `setBlocking`. +static thread_local std::size_t _pythonContextStack{}; + +PythonContextMarker::PythonContextMarker() +{ + // Shift history up and set lowest bit. + _pythonContextStack = (_pythonContextStack * 2) + 1; +} + +PythonContextMarker::~PythonContextMarker() +{ + // Shift history down. + _pythonContextStack /= 2; +} + +void PythonContextMarker::setBlocking() +{ + // Clear lowest bit. + _pythonContextStack = _pythonContextStack / 2 * 2; +} + +namespace Errors +{ + +void setInstantiateAbstractClass(const char *name) +{ + PyErr_Format(PyExc_NotImplementedError, + "'%s' represents a C++ abstract class and cannot be instantiated", name); +} + +void setInstantiateAbstractClassDisabledWrapper(const char *name) +{ + PyErr_Format(PyExc_NotImplementedError, + "Abstract class '%s' cannot be instantiated since the wrapper has been disabled.", + name); +} + +void setInvalidTypeDeletion(const char *name) +{ + PyErr_Format(PyExc_TypeError, "'%s' may not be deleted", name); +} + +void setOperatorNotImplemented() +{ + PyErr_SetString(PyExc_NotImplementedError, "operator not implemented."); +} + +void setPureVirtualMethodError(const char *name) +{ + PyErr_Format(PyExc_NotImplementedError, "pure virtual method '%s' not implemented.", name); +} + +void setPrivateMethod(const char *name) +{ + PyErr_Format(PyExc_TypeError, "%s is a private method.\", ", name); +} + +void setReverseOperatorNotImplemented() +{ + PyErr_SetString(PyExc_NotImplementedError, "reverse operator not implemented."); +} + +void setSequenceTypeError(const char *expectedType) +{ + PyErr_Format(PyExc_TypeError, + "attributed value with wrong type, '%s' or other convertible type expected", + expectedType); +} + +void setSetterTypeError(const char *name, const char *expectedType) +{ + PyErr_Format(PyExc_TypeError, + "wrong type attributed to '%s', '%s' or convertible type expected", + name, expectedType); +} + +void setWrongContainerType() +{ + PyErr_SetString(PyExc_TypeError, "Wrong type passed to container conversion."); +} + +// Prepend something to an exception message provided it is a single string +// argument. +static bool prependToExceptionMessage(PyObject *exc, const char *context) +{ + Shiboken::AutoDecRef args(PepException_GetArgs(exc)); + if (args.isNull() || PyTuple_Check(args.object()) == 0 || PyTuple_Size(args) != 1) + return false; + auto *oldMessage = PyTuple_GetItem(args, 0); + if (oldMessage == nullptr || PyUnicode_CheckExact(oldMessage) == 0) + return false; + auto *newMessage = PyUnicode_FromFormat("%s%U", context, oldMessage); + PepException_SetArgs(exc, PyTuple_Pack(1, newMessage)); + return true; +} + +struct ErrorStore { + PyObject *type; + PyObject *exc; + PyObject *traceback; +}; + +static thread_local ErrorStore savedError{}; + +static bool hasPythonContext() +{ + return _pythonContextStack & 1; +} + +void storeErrorOrPrint() +{ + // This error happened in a function with no way to return an error state. + // Therefore, we handle the error when we are error checking, anyway. + // But we do that only when we know that an error handler can pick it up. + if (hasPythonContext()) + PyErr_Fetch(&savedError.type, &savedError.exc, &savedError.traceback); + else + PyErr_Print(); +} + +// Like storeErrorOrPrint() with additional context info that is prepended +// to the exception message or printed. +static void storeErrorOrPrintWithContext(const char *context) +{ + if (hasPythonContext()) { + PyErr_Fetch(&savedError.type, &savedError.exc, &savedError.traceback); + prependToExceptionMessage(savedError.exc, context); + } else { + std::fputs(context, stderr); + PyErr_Print(); + } +} + +void storePythonOverrideErrorOrPrint(const char *className, const char *funcName) +{ + const std::string context = "Error calling Python override of "s + + className + "::"s + funcName + "(): "s; + storeErrorOrPrintWithContext(context.c_str()); +} + +PyObject *occurred() +{ + if (savedError.type) { + PyErr_Restore(savedError.type, savedError.exc, savedError.traceback); + savedError.type = nullptr; + } + return PyErr_Occurred(); +} + +} // namespace Errors + +namespace Warnings +{ +void warnInvalidReturnValue(const char *className, const char *functionName, + const char *expectedType, const char *actualType) +{ + Shiboken::warning(PyExc_RuntimeWarning, 2, + "Invalid return value in function '%s.%s', expected %s, got %s.", + className, functionName, expectedType, actualType); +} + +void warnDeprecated(const char *functionName) +{ + Shiboken::warning(PyExc_DeprecationWarning, 1, + "Function: '%s' is marked as deprecated, please check " + "the documentation for more information.", + functionName); +} + +void warnDeprecated(const char *className, const char *functionName) +{ + Shiboken::warning(PyExc_DeprecationWarning, 1, + "Function: '%s.%s' is marked as deprecated, please check " + "the documentation for more information.", + className, functionName); +} + +void warnDeprecatedEnum(const char *enumName) +{ + Shiboken::warning(PyExc_DeprecationWarning, 1, + "Enum: '%s' is marked as deprecated, please check " + "the documentation for more information.", + enumName); +} + +void warnDeprecatedEnumValue(const char *enumName, const char *valueName) +{ + Shiboken::warning(PyExc_DeprecationWarning, 1, + "Enum value '%s.%s' is marked as deprecated, please check " + "the documentation for more information.", + enumName, valueName); + +} + +} // namespace Warnings +} // namespace Shiboken diff --git a/sources/shiboken6/libshiboken/sbkerrors.h b/sources/shiboken6/libshiboken/sbkerrors.h new file mode 100644 index 000000000..18ce701e7 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkerrors.h @@ -0,0 +1,78 @@ +// Copyright (C) 2022 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 + +#ifndef SBKERRORS_H +#define SBKERRORS_H + +#include "sbkpython.h" +#include "shibokenmacros.h" + +/// Craving for C++20 and std::source_location::current() +#if defined(_MSC_VER) +# define SBK_FUNC_INFO __FUNCSIG__ +#elif defined(__GNUC__) +# define SBK_FUNC_INFO __PRETTY_FUNCTION__ +#else +# define SBK_FUNC_INFO __FUNCTION__ +#endif + +namespace Shiboken +{ + +struct LIBSHIBOKEN_API PythonContextMarker +{ +public: + PythonContextMarker(const PythonContextMarker &) = delete; + PythonContextMarker(PythonContextMarker &&) = delete; + PythonContextMarker &operator=(const PythonContextMarker &) = delete; + PythonContextMarker &operator=(PythonContextMarker &&) = delete; + + explicit PythonContextMarker(); + ~PythonContextMarker(); + void setBlocking(); +}; + +namespace Errors +{ + +LIBSHIBOKEN_API void setInstantiateAbstractClass(const char *name); +LIBSHIBOKEN_API void setInstantiateAbstractClassDisabledWrapper(const char *name); +LIBSHIBOKEN_API void setInvalidTypeDeletion(const char *name); +LIBSHIBOKEN_API void setOperatorNotImplemented(); +LIBSHIBOKEN_API void setPureVirtualMethodError(const char *name); +LIBSHIBOKEN_API void setPrivateMethod(const char *name); +LIBSHIBOKEN_API void setReverseOperatorNotImplemented(); +LIBSHIBOKEN_API void setSequenceTypeError(const char *expectedType); +LIBSHIBOKEN_API void setSetterTypeError(const char *name, const char *expectedType); +LIBSHIBOKEN_API void setWrongContainerType(); + +/// Report an error ASAP: Instead of printing, store for later re-raise. +/// This replaces `PyErr_Print`, which cannot report errors as exception. +/// To be used in contexts where raising errors is impossible. +LIBSHIBOKEN_API void storeErrorOrPrint(); + +/// Call storeErrorOrPrint() and print the context to report +/// errors when calling Python overrides of virtual functions. +LIBSHIBOKEN_API void storePythonOverrideErrorOrPrint(const char *className, const char *funcName); + +/// Handle an error as in PyErr_Occurred(), but also check for errors which +/// were captured by `storeErrorOrPrint`. +/// To be used in normal error checks. +LIBSHIBOKEN_API PyObject *occurred(); + +} // namespace Errors + +namespace Warnings +{ +/// Warn about invalid return value of overwritten virtual +LIBSHIBOKEN_API void warnInvalidReturnValue(const char *className, const char *functionName, + const char *expectedType, const char *actualType); +LIBSHIBOKEN_API void warnDeprecated(const char *functionName); +LIBSHIBOKEN_API void warnDeprecated(const char *className, const char *functionName); +LIBSHIBOKEN_API void warnDeprecatedEnum(const char *enumName); +LIBSHIBOKEN_API void warnDeprecatedEnumValue(const char *enumName, const char *valueName); +} // namespace Warnings + +} // namespace Shiboken + +#endif // SBKERRORS_H 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" diff --git a/sources/shiboken6/libshiboken/sbkfeature_base.h b/sources/shiboken6/libshiboken/sbkfeature_base.h new file mode 100644 index 000000000..290884062 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkfeature_base.h @@ -0,0 +1,18 @@ +// 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 + +#ifndef SBKFEATURE_BASE_H +#define SBKFEATURE_BASE_H + +extern "C" +{ + +LIBSHIBOKEN_API int currentSelectId(PyTypeObject *type); +LIBSHIBOKEN_API PyObject *mangled_type_getattro(PyTypeObject *type, PyObject *name); +LIBSHIBOKEN_API PyObject *Sbk_TypeGet___dict__(PyTypeObject *type, void *context); +LIBSHIBOKEN_API PyObject *SbkObject_GenericGetAttr(PyObject *obj, PyObject *name); +LIBSHIBOKEN_API int SbkObject_GenericSetAttr(PyObject *obj, PyObject *name, PyObject *value); + +} // extern "C" + +#endif // SBKFEATURE_BASE_H diff --git a/sources/shiboken6/libshiboken/sbkmodule.cpp b/sources/shiboken6/libshiboken/sbkmodule.cpp new file mode 100644 index 000000000..f0136148b --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkmodule.cpp @@ -0,0 +1,526 @@ +// 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 "sbkmodule.h" +#include "autodecref.h" +#include "basewrapper.h" +#include "bindingmanager.h" +#include "sbkstring.h" +#include "sbkcppstring.h" +#include "sbkconverter_p.h" + +#include <unordered_map> +#include <unordered_set> +#include <vector> +#include <cstring> + +/// This hash maps module objects to arrays of converters. +using ModuleConvertersMap = std::unordered_map<PyObject *, SbkConverter **> ; + +/// This hash maps module objects to arrays of Python types. +using ModuleTypesMap = std::unordered_map<PyObject *, Shiboken::Module::TypeInitStruct *> ; + +struct TypeCreationStruct +{ + Shiboken::Module::TypeCreationFunction func; + std::vector<std::string> subtypeNames; +}; + +/// This hash maps type names to type creation structs. +using NameToTypeFunctionMap = std::unordered_map<std::string, TypeCreationStruct> ; + +/// This hash maps module objects to maps of names to functions. +using ModuleToFuncsMap = std::unordered_map<PyObject *, NameToTypeFunctionMap> ; + +/// All types produced in imported modules are mapped here. +static ModuleTypesMap moduleTypes; +static ModuleConvertersMap moduleConverters; +static ModuleToFuncsMap moduleToFuncs; + +namespace Shiboken +{ +namespace Module +{ + +// PYSIDE-2404: Replacing the arguments generated by cpythonTypeNameExt +// by a function call. +LIBSHIBOKEN_API PyTypeObject *get(TypeInitStruct &typeStruct) +{ + if (typeStruct.type != nullptr) + return typeStruct.type; + + static PyObject *sysModules = PyImport_GetModuleDict(); + + // The slow path for initialization. + // We get the type by following the chain from the module. + // As soon as types[index] gets filled, we can stop. + + std::string_view names(typeStruct.fullName); + const bool usePySide = names.compare(0, 8, "PySide6.") == 0; + auto dotPos = usePySide ? names.find('.', 8) : names.find('.'); + auto startPos = dotPos + 1; + AutoDecRef modName(String::fromCppStringView(names.substr(0, dotPos))); + auto *modOrType = PyDict_GetItem(sysModules, modName); + if (modOrType == nullptr) { + PyErr_Format(PyExc_SystemError, "Module \"%U\" should already be in sys.modules", + modName.object()); + return nullptr; + } + + do { + dotPos = names.find('.', startPos); + auto typeName = dotPos != std::string::npos + ? names.substr(startPos, dotPos - startPos) + : names.substr(startPos); + startPos = dotPos + 1; + AutoDecRef obTypeName(String::fromCppStringView(typeName)); + modOrType = PyObject_GetAttr(modOrType, obTypeName); + } while (typeStruct.type == nullptr && dotPos != std::string::npos); + + return typeStruct.type; +} + +static void incarnateHelper(PyObject *module, const std::string_view names, + const NameToTypeFunctionMap &nameToFunc) +{ + auto dotPos = names.find('.'); + std::string::size_type startPos = 0; + auto *modOrType{module}; + while (dotPos != std::string::npos) { + auto typeName = names.substr(startPos, dotPos - startPos); + AutoDecRef obTypeName(String::fromCppStringView(typeName)); + modOrType = PyObject_GetAttr(modOrType, obTypeName); + startPos = dotPos + 1; + dotPos = names.find('.', startPos); + } + // now we have the type to create. + auto funcIter = nameToFunc.find(std::string(names)); + // - call this function that returns a PyTypeObject + auto tcStruct = funcIter->second; + auto initFunc = tcStruct.func; + PyTypeObject *type = initFunc(modOrType); + auto name = names.substr(startPos); + PyObject_SetAttrString(modOrType, name.data(), reinterpret_cast<PyObject *>(type)); +} + +static void incarnateSubtypes(PyObject *module, + const std::vector<std::string> &nameList, + NameToTypeFunctionMap &nameToFunc) +{ + for (auto const & tableIter : nameList) { + std::string_view names(tableIter); + incarnateHelper(module, names, nameToFunc); + } +} + +static PyTypeObject *incarnateType(PyObject *module, const char *name, + NameToTypeFunctionMap &nameToFunc) +{ + // - locate the name and retrieve the generating function + 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 + auto tcStruct = funcIter->second; + auto initFunc = tcStruct.func; + auto *modOrType{module}; + + // PYSIDE-2404: Make sure that no switching happens during type creation. + auto saveFeature = initSelectableFeature(nullptr); + PyTypeObject *type = initFunc(modOrType); + if (!tcStruct.subtypeNames.empty()) + incarnateSubtypes(module, tcStruct.subtypeNames, nameToFunc); + initSelectableFeature(saveFeature); + + // - assign this object to the name in the module + auto *res = reinterpret_cast<PyObject *>(type); + Py_INCREF(res); + PyModule_AddObject(module, name, res); // steals reference + // - remove the entry, if not by something cleared. + if (!nameToFunc.empty()) + nameToFunc.erase(funcIter); + // - return the PyTypeObject. + return type; +} + +// PYSIDE-2404: Make sure that the mentioned classes really exist. +// Used in `Pyside::typeName`. Because the result will be cached by +// the creation of the type(s), this is efficient. +void loadLazyClassesWithName(const char *name) +{ + for (auto const & tableIter : moduleToFuncs) { + auto nameToFunc = tableIter.second; + auto funcIter = nameToFunc.find(name); + if (funcIter != nameToFunc.end()) { + // attribute exists in the lazy types. + auto *module = tableIter.first; + incarnateType(module, name, nameToFunc); + } + } +} + +// PYSIDE-2404: Completely load all not yet loaded classes. +// This is needed to resolve a star import. +void resolveLazyClasses(PyObject *module) +{ + // - locate the module in the moduleTofuncs mapping + auto tableIter = moduleToFuncs.find(module); + if (tableIter == moduleToFuncs.end()) + return; + + // - see if there are still unloaded elements + auto &nameToFunc = tableIter->second; + + // - incarnate all types. + while (!nameToFunc.empty()) { + auto it = nameToFunc.begin(); + auto attrNameStr = it->first; + incarnateType(module, attrNameStr.c_str(), nameToFunc); + } +} + +// 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) +{ + // - 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); + // - 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(name); + auto &nameToFunc = tableIter->second; + // - create the real type and handle subtypes + auto *type = incarnateType(module, attrNameStr, nameToFunc); + auto *ret = reinterpret_cast<PyObject *>(type); + // - 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__"); + // The dir function must replace all of the builtin function. + PyObject *module{}; + if (!PyArg_ParseTuple(args, "O", &module)) + return nullptr; + + auto tableIter = moduleToFuncs.find(module); + assert(tableIter != moduleToFuncs.end()); + Shiboken::AutoDecRef dict(PyObject_GetAttr(module, _dict)); + auto *ret = PyDict_Keys(dict); + // Now add all elements that were not yet in the dict. + auto &nameToFunc = tableIter->second; + for (const auto &funcIter : nameToFunc) { + const char *name = funcIter.first.c_str(); + Shiboken::AutoDecRef pyName(PyUnicode_FromString(name)); + PyList_Append(ret, pyName); + } + return ret; +} + +static PyMethodDef module_methods[] = { + {"__dir__", (PyCFunction)_module_dir_template, METH_VARARGS, nullptr}, + {nullptr, nullptr, 0, nullptr} +}; + +// Python 3.8 - 3.12 +static int const LOAD_CONST_312 = 100; +static int const IMPORT_NAME_312 = 108; + +static bool isImportStar(PyObject *module) +{ + // Find out whether we have a star import. This must work even + // when we have no import support from feature. + static PyObject *const _f_code = Shiboken::String::createStaticString("f_code"); + static PyObject *const _f_lasti = Shiboken::String::createStaticString("f_lasti"); + static PyObject *const _f_back = Shiboken::String::createStaticString("f_back"); + static PyObject *const _co_code = Shiboken::String::createStaticString("co_code"); + static PyObject *const _co_consts = Shiboken::String::createStaticString("co_consts"); + static PyObject *const _co_names = Shiboken::String::createStaticString("co_names"); + + auto *obFrame = reinterpret_cast<PyObject *>(PyEval_GetFrame()); + if (obFrame == nullptr) + return true; // better assume worst-case. + + Py_INCREF(obFrame); + AutoDecRef dec_frame(obFrame); + + // Calculate the offset of the running import_name opcode on the stack. + // Right before that there must be a load_const with the tuple `("*",)`. + while (dec_frame.object() != Py_None) { + AutoDecRef dec_f_code(PyObject_GetAttr(dec_frame, _f_code)); + AutoDecRef dec_co_code(PyObject_GetAttr(dec_f_code, _co_code)); + AutoDecRef dec_f_lasti(PyObject_GetAttr(dec_frame, _f_lasti)); + Py_ssize_t f_lasti = PyLong_AsSsize_t(dec_f_lasti); + Py_ssize_t code_len; + char *co_code{}; + PyBytes_AsStringAndSize(dec_co_code, &co_code, &code_len); + uint8_t opcode2 = co_code[f_lasti]; + uint8_t opcode1 = co_code[f_lasti - 2]; + if (opcode1 == LOAD_CONST_312 && opcode2 == IMPORT_NAME_312) { + uint8_t oparg1 = co_code[f_lasti - 1]; + uint8_t oparg2 = co_code[f_lasti + 1]; + AutoDecRef dec_co_consts(PyObject_GetAttr(dec_f_code, _co_consts)); + auto *fromlist = PyTuple_GetItem(dec_co_consts, oparg1); + if (PyTuple_Check(fromlist) && PyTuple_Size(fromlist) == 1 + && Shiboken::String::toCString(PyTuple_GetItem(fromlist, 0))[0] == '*') { + AutoDecRef dec_co_names(PyObject_GetAttr(dec_f_code, _co_names)); + const char *name = String::toCString(PyTuple_GetItem(dec_co_names, oparg2)); + const char *modName = PyModule_GetName(module); + if (std::strcmp(name, modName) == 0) + return true; + } + } + dec_frame.reset(PyObject_GetAttr(dec_frame, _f_back)); + } + return false; +} + +// PYSIDE-2404: These modules produce ambiguous names which we cannot handle, yet. +static std::unordered_set<std::string> dontLazyLoad{ + "testbinding" +}; + +static const std::unordered_set<std::string> knownModules{ + "shiboken6.Shiboken", + "minimal", + "other", + "sample", + "smart", + "scriptableapplication", + "testbinding" +}; + +static bool canNotLazyLoad(PyObject *module) +{ + const char *modName = PyModule_GetName(module); + + // There are no more things that must be disabled :-D + return dontLazyLoad.find(modName) != dontLazyLoad.end(); +} + +static bool shouldLazyLoad(PyObject *module) +{ + const char *modName = PyModule_GetName(module); + + if (knownModules.find(modName) != knownModules.end()) + return true; + return std::strncmp(modName, "PySide6.", 8) == 0; +} + +static int lazyLoadDefault() +{ +#ifndef PYPY_VERSION + int result = 1; +#else + int result = 0; +#endif + if (auto *flag = getenv("PYSIDE6_OPTION_LAZY")) + result = std::atoi(flag); + return result; +} + +void checkIfShouldLoadImmediately(PyObject *module, const std::string &name, + const NameToTypeFunctionMap &nameToFunc) +{ + static const int value = lazyLoadDefault(); + + // PYSIDE-2404: Lazy Loading + // + // Options: + // 0 - switch lazy loading off. + // 1 - lazy loading for all known modules. + // 3 - lazy loading for any module. + // + // By default we lazy load all known modules (option = 1). + if (value == 0 // completely disabled + || canNotLazyLoad(module) // for some reason we cannot lazy load + || (value == 1 && !shouldLazyLoad(module)) // not a known module + ) { + incarnateHelper(module, name, nameToFunc); + } +} + +void AddTypeCreationFunction(PyObject *module, + const char *name, + TypeCreationFunction func) +{ + // - locate the module in the moduleTofuncs mapping + auto tableIter = moduleToFuncs.find(module); + assert(tableIter != moduleToFuncs.end()); + // - Assign the name/generating function tcStruct. + auto &nameToFunc = tableIter->second; + TypeCreationStruct tcStruct{func, {}}; + auto nit = nameToFunc.find(name); + if (nit == nameToFunc.end()) + nameToFunc.insert(std::make_pair(name, tcStruct)); + else + nit->second = tcStruct; + + checkIfShouldLoadImmediately(module, name, nameToFunc); +} + +void AddTypeCreationFunction(PyObject *module, + const char *containerName, + TypeCreationFunction func, + const char *namePath) +{ + // - locate the module in the moduleTofuncs mapping + auto tableIter = moduleToFuncs.find(module); + assert(tableIter != moduleToFuncs.end()); + // - Assign the name/generating function tcStruct. + auto &nameToFunc = tableIter->second; + auto nit = nameToFunc.find(containerName); + + // - insert namePath into the subtype vector of the main type. + nit->second.subtypeNames.push_back(namePath); + // - insert it also as its own entry. + nit = nameToFunc.find(namePath); + TypeCreationStruct tcStruct{func, {}}; + if (nit == nameToFunc.end()) + nameToFunc.insert(std::make_pair(namePath, tcStruct)); + else + nit->second = tcStruct; + + checkIfShouldLoadImmediately(module, namePath, nameToFunc); +} + +PyObject *import(const char *moduleName) +{ + PyObject *sysModules = PyImport_GetModuleDict(); + PyObject *module = PyDict_GetItemString(sysModules, moduleName); + if (module != nullptr) + Py_INCREF(module); + else + module = PyImport_ImportModule(moduleName); + + if (module == nullptr) + PyErr_Format(PyExc_ImportError, "could not import module '%s'", moduleName); + + return module; +} + +// PYSIDE-2404: Redirecting import for "import *" support. +// +// The first import will be handled by the isImportStar function. +// But the same module might be imported twice, which would give no +// introspection due to module caching. + +static PyObject *origImportFunc{}; + +static PyObject *lazy_import(PyObject * /* self */, PyObject *args, PyObject *kwds) +{ + auto *ret = PyObject_Call(origImportFunc, args, kwds); + if (ret != nullptr) { + // PYSIDE-2404: Support star import when lazy loading. + if (PyTuple_Size(args) >= 4) { + auto *fromlist = PyTuple_GetItem(args, 3); + if (PyTuple_Check(fromlist) && PyTuple_Size(fromlist) == 1 + && Shiboken::String::toCString(PyTuple_GetItem(fromlist, 0))[0] == '*') + Shiboken::Module::resolveLazyClasses(ret); + } + } + return ret; +} + +static PyMethodDef lazy_methods[] = { + {"__lazy_import__", (PyCFunction)lazy_import, METH_VARARGS | METH_KEYWORDS, nullptr}, + {nullptr, nullptr, 0, nullptr} +}; + +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{}; + + Shiboken::init(); + auto *module = PyModule_Create(reinterpret_cast<PyModuleDef *>(moduleData)); + + // 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)); + + // A star import must be done unconditionally. Use the complete name. + if (isImportStar(module)) + dontLazyLoad.insert(PyModule_GetName(module)); + + if (!lazy_init) { + // Install the getattr patch. + origModuleGetattro = PyModule_Type.tp_getattro; + PyModule_Type.tp_getattro = PyModule_lazyGetAttro; + // Add the lazy import redirection, keeping a reference. + origImportFunc = PyDict_GetItemString(builtins, "__import__"); + Py_INCREF(origImportFunc); + AutoDecRef 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); + // Clear the non-existing name cache because we have a new module. + Shiboken::Conversions::clearNegativeLazyCache(); + return module; +} + +void registerTypes(PyObject *module, TypeInitStruct *types) +{ + auto iter = moduleTypes.find(module); + if (iter == moduleTypes.end()) + moduleTypes.insert(std::make_pair(module, types)); +} + +TypeInitStruct *getTypes(PyObject *module) +{ + auto iter = moduleTypes.find(module); + return (iter == moduleTypes.end()) ? 0 : iter->second; +} + +void registerTypeConverters(PyObject *module, SbkConverter **converters) +{ + auto iter = moduleConverters.find(module); + if (iter == moduleConverters.end()) + moduleConverters.insert(std::make_pair(module, converters)); +} + +SbkConverter **getTypeConverters(PyObject *module) +{ + auto iter = moduleConverters.find(module); + return (iter == moduleConverters.end()) ? 0 : iter->second; +} + +} } // namespace Shiboken::Module diff --git a/sources/shiboken6/libshiboken/sbkmodule.h b/sources/shiboken6/libshiboken/sbkmodule.h new file mode 100644 index 000000000..2c407e09d --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkmodule.h @@ -0,0 +1,89 @@ +// 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 + +#ifndef SBK_MODULE_H +#define SBK_MODULE_H + +#include "sbkpython.h" +#include "shibokenmacros.h" + +extern "C" +{ +struct SbkConverter; +} + +namespace Shiboken::Module { + +struct TypeInitStruct +{ + PyTypeObject *type; + const char *fullName; +}; + +/// PYSIDE-2404: Replacing the arguments in cpythonTypeNameExt by a function. +LIBSHIBOKEN_API PyTypeObject *get(TypeInitStruct &typeStruct); + +/// PYSIDE-2404: Make sure that mentioned classes really exist. +LIBSHIBOKEN_API void loadLazyClassesWithName(const char *name); + +/// PYSIDE-2404: incarnate all classes for star imports. +LIBSHIBOKEN_API void resolveLazyClasses(PyObject *module); + +/** + * Imports and returns the module named \p moduleName, or a NULL pointer in case of failure. + * If the module is already imported, it increments its reference count before returning it. + * \returns the module specified in \p moduleName or NULL if an error occurs. + */ +LIBSHIBOKEN_API PyObject *import(const char *moduleName); + +/** + * Creates a new Python module named \p moduleName using the information passed in \p moduleData. + * In fact, \p moduleData expects a "PyMethodDef *" object, but that's for Python 2. A "void*" + * was preferred to make this work with future Python 3 support. + * \returns a newly created module. + */ +LIBSHIBOKEN_API PyObject *create(const char *moduleName, void *moduleData); + +using TypeCreationFunction = PyTypeObject *(*)(PyObject *module); + +/// Adds a type creation function to the module. +LIBSHIBOKEN_API void AddTypeCreationFunction(PyObject *module, + const char *name, + TypeCreationFunction func); + +LIBSHIBOKEN_API void AddTypeCreationFunction(PyObject *module, + const char *name, + TypeCreationFunction func, + const char *containerName); + +/** + * Registers the list of types created by \p module. + * \param module Module where the types were created. + * \param types Array of PyTypeObject *objects representing the types created on \p module. + */ +LIBSHIBOKEN_API void registerTypes(PyObject *module, TypeInitStruct *types); + +/** + * Retrieves the array of types. + * \param module Module where the types were created. + * \returns A pointer to the PyTypeObject *array of types. + */ +LIBSHIBOKEN_API TypeInitStruct *getTypes(PyObject *module); + +/** + * Registers the list of converters created by \p module for non-wrapper types. + * \param module Module where the converters were created. + * \param converters Array of SbkConverter *objects representing the converters created on \p module. + */ +LIBSHIBOKEN_API void registerTypeConverters(PyObject *module, SbkConverter **converters); + +/** + * Retrieves the array of converters. + * \param module Module where the converters were created. + * \returns A pointer to the SbkConverter *array of converters. + */ +LIBSHIBOKEN_API SbkConverter **getTypeConverters(PyObject *module); + +} // namespace Shiboken::Module + +#endif // SBK_MODULE_H diff --git a/sources/shiboken6/libshiboken/sbknumpy.cpp b/sources/shiboken6/libshiboken/sbknumpy.cpp new file mode 100644 index 000000000..b6422e73f --- /dev/null +++ b/sources/shiboken6/libshiboken/sbknumpy.cpp @@ -0,0 +1,57 @@ +// Copyright (C) 2021 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 + + +#ifdef HAVE_NUMPY +// Include numpy first to get the proper PyArray_Check +# include <numpy/arrayobject.h> +#endif + +#include "helper.h" +#include "sbknumpycheck.h" +#include "sbkcpptonumpy.h" +#include "sbknumpyview.h" + +#include <algorithm> + +namespace Shiboken::Numpy +{ + +#ifdef HAVE_NUMPY +static void initNumPy() +{ + // PYSIDE-2404: Delay-initialize numpy from check() as it causes a + // significant startup delay (~770 allocations in memray) + static bool initialized = false; + if (initialized) + return; + initialized = true; + // Expanded from macro "import_array" in __multiarray_api.h + // Make sure to read about the magic defines PY_ARRAY_UNIQUE_SYMBOL etc., + // when changing this or spreading the code over several source files. + if (_import_array() < 0) + PyErr_Print(); +} +#endif // HAVE_NUMPY + +bool check(PyObject *pyIn) +{ +#ifdef HAVE_NUMPY + initNumPy(); + return PyArray_Check(pyIn); +#else + SBK_UNUSED(pyIn); + return false; +#endif +} + +} //namespace Shiboken::Numpy + +// Include all sources files using numpy so that they are in the same +// translation unit (see comment at initNumPyArrayConverters()). + +#include "sbknumpyview.cpp" +#include "sbkcpptonumpy.cpp" +#ifdef HAVE_NUMPY +# include "sbknumpyarrayconverter.cpp" +#endif diff --git a/sources/shiboken6/libshiboken/sbknumpyarrayconverter.cpp b/sources/shiboken6/libshiboken/sbknumpyarrayconverter.cpp new file mode 100644 index 000000000..835a97524 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbknumpyarrayconverter.cpp @@ -0,0 +1,270 @@ +// Copyright (C) 2017 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 + +// included by sbknumpy.cpp + +#include "sbkarrayconverter.h" +#include "helper.h" +#include "sbkconverter.h" +#include "sbkconverter_p.h" +#include "sbkarrayconverter_p.h" + +#include <algorithm> +#include <iostream> +#include <cstdint> + +enum { debugNumPy = 0 }; + +struct TypeCharMapping +{ + NPY_TYPES type; + const char *name; +}; + +static const TypeCharMapping typeCharMappings[] = { +{NPY_BYTE, "NPY_BYTE"}, +{NPY_UBYTE, "NPY_UBYTE"}, +{NPY_SHORT, "NPY_SHORT"}, +{NPY_USHORT, "NPY_USHORT"}, +{NPY_INT, "NPY_INT"}, +{NPY_UINT, "NPY_UINT"}, +{NPY_LONG, "NPY_LONG"}, +{NPY_ULONG, "NPY_ULONG"}, +{NPY_LONGLONG, "NPY_LONGLONG"}, +{NPY_ULONGLONG, "NPY_ULONGLONG"}, +{NPY_FLOAT, "NPY_FLOAT"}, +{NPY_DOUBLE, "NPY_DOUBLE"} +}; + +const char *npTypeName(npy_intp t) +{ + const TypeCharMapping *end = typeCharMappings + sizeof(typeCharMappings) / sizeof(typeCharMappings[0]); + const TypeCharMapping *result = + std::find_if(typeCharMappings, end, + [t] (const TypeCharMapping &m) { return m.type == t; }); + return result != end ? result->name : nullptr; +} + +std::ostream &operator<<(std::ostream &str, PyArrayObject *o) +{ + str << "PyArrayObject("; + if (o) { + const npy_intp npType = PyArray_TYPE(o); + if (const char *name = npTypeName(npType)) + str << name; + else + str << "type=" << npType; + const int nDim = PyArray_NDIM(o); + const npy_intp *dims = PyArray_DIMS(o); + for (int d = 0; d < nDim; ++d) + str << '[' << dims[d] << ']'; + str << ", "; + const int flags = PyArray_FLAGS(o); + if ((flags & NPY_ARRAY_C_CONTIGUOUS) != 0) + str << " NPY_ARRAY_C_CONTIGUOUS"; + if ((flags & NPY_ARRAY_F_CONTIGUOUS) != 0) + str << " NPY_ARRAY_F_CONTIGUOUS"; + if ((flags & NPY_ARRAY_OWNDATA) != 0) + str << " NPY_ARRAY_OWNDATA"; + if ((flags & NPY_ARRAY_FORCECAST) != 0) + str << " NPY_ARRAY_FORCECAST"; + if ((flags & NPY_ARRAY_ENSURECOPY) != 0) + str << " NPY_ARRAY_ENSURECOPY"; + if ((flags & NPY_ARRAY_ENSUREARRAY) != 0) + str << " NPY_ARRAY_ENSUREARRAY"; + if ((flags & NPY_ARRAY_ELEMENTSTRIDES) != 0) + str << " NPY_ARRAY_ELEMENTSTRIDES"; + if ((flags & NPY_ARRAY_ALIGNED) != 0) + str << " NPY_ARRAY_ALIGNED"; + if ((flags & NPY_ARRAY_NOTSWAPPED) != 0) + str << " NPY_ARRAY_NOTSWAPPED"; + if ((flags & NPY_ARRAY_WRITEABLE) != 0) + str << " NPY_ARRAY_WRITEABLE"; +#if NPY_VERSION >= 0x00000010 // NPY_1_23_API_VERSION + if ((flags & NPY_ARRAY_WRITEBACKIFCOPY) != 0) + str << " NPY_ARRAY_WRITEBACKIFCOPY"; +#else + if ((flags & NPY_ARRAY_UPDATEIFCOPY) != 0) + str << " NPY_ARRAY_UPDATEIFCOPY"; +#endif + } else { + str << '0'; + } + str << ')'; + return str; +} + +namespace Shiboken::Conversions { + +// Internals from sbkarrayconverter.cpp +SbkArrayConverter *createArrayConverter(IsArrayConvertibleToCppFunc toCppCheckFunc); +void setArrayTypeConverter(int index, int dimension, SbkArrayConverter *c); +SbkArrayConverter *unimplementedArrayConverter(); + +template <int dimension> +static bool isPrimitiveArray(PyObject *pyIn, int expectedNpType) +{ + Shiboken::Numpy::initNumPy(); + if (!PyArray_Check(pyIn)) + return false; + auto *pya = reinterpret_cast<PyArrayObject *>(pyIn); + if (debugNumPy) { + std::cerr << __FUNCTION__ << "(expectedNpType=" << expectedNpType; + if (const char *name = npTypeName(expectedNpType)) + std::cerr << " (" << name << ')'; + std::cerr << ' ' << pya << '\n'; + } + + const int dim = PyArray_NDIM(pya); + if (dim != dimension) { + warning(PyExc_RuntimeWarning, 0, + "%d dimensional numpy array passed to a function expecting a %d dimensional array.", + dim, dimension); + return false; + } + if ((PyArray_FLAGS(pya) & NPY_ARRAY_C_CONTIGUOUS) == 0) { + warning(PyExc_RuntimeWarning, 0, + "Cannot handle numpy arrays that do not have NPY_ARRAY_C_CONTIGUOUS set."); + return false; + } + const int actualNpType = PyArray_TYPE(pya); + if (actualNpType != expectedNpType) { + const char *actualName = npTypeName(actualNpType); + const char *expectedName = npTypeName(expectedNpType); + warning(PyExc_RuntimeWarning, 0, + "A numpy array of type %d (%s) was passed to a function expecting type %d (%s).", + actualNpType, actualName ? actualName : "", + expectedNpType, expectedName ? expectedName : ""); + return false; + } + return true; +} + +static inline bool primitiveArrayCheck1(PyObject *pyIn, int expectedNpType, int expectedSize) +{ + if (!isPrimitiveArray<1>(pyIn, expectedNpType)) + return false; + if (expectedSize >= 0) { + auto *pya = reinterpret_cast<PyArrayObject *>(pyIn); + const int size = int(PyArray_DIMS(pya)[0]); + if (size < expectedSize) { + warning(PyExc_RuntimeWarning, 0, "A numpy array of size %d was passed to a function expects %d.", + size, expectedSize); + return false; + } + } + return true; +} + +// Convert one-dimensional array +template <class T> +static void convertArray1(PyObject *pyIn, void *cppOut) +{ + auto *handle = reinterpret_cast<ArrayHandle<T> *>(cppOut); + auto *pya = reinterpret_cast<PyArrayObject *>(pyIn); + const npy_intp size = PyArray_DIMS(pya)[0]; + if (debugNumPy) + std::cerr << __FUNCTION__ << ' ' << size << '\n'; + handle->setData(reinterpret_cast<T *>(PyArray_DATA(pya)), size_t(size)); +} + +// Convert 2 dimensional array +template <class T> +static void convertArray2(PyObject *pyIn, void *cppOut) +{ + typedef typename Array2Handle<T, 1>::RowType RowType; + auto *handle = reinterpret_cast<Array2Handle<T, 1> *>(cppOut); + auto *pya = reinterpret_cast<PyArrayObject *>(pyIn); + handle->setData(reinterpret_cast<RowType *>(PyArray_DATA(pya))); +} + +template <class T, int NumPyType> +static PythonToCppFunc checkArray1(PyObject *pyIn, int dim1, int /* dim2 */) +{ + return primitiveArrayCheck1(pyIn, NumPyType, dim1) ? convertArray1<T> : nullptr; +} + +static inline bool primitiveArrayCheck2(PyObject *pyIn, int expectedNpType, int expectedDim1, int expectedDim2) +{ + if (!isPrimitiveArray<2>(pyIn, expectedNpType)) + return false; + if (expectedDim2 >= 0) { + auto *pya = reinterpret_cast<PyArrayObject *>(pyIn); + const int dim1 = int(PyArray_DIMS(pya)[0]); + const int dim2 = int(PyArray_DIMS(pya)[1]); + if (dim1 != expectedDim1 || dim2 != expectedDim2) { + warning(PyExc_RuntimeWarning, 0, "A numpy array of size %dx%d was passed to a function that expects %dx%d.", + dim1, dim2, expectedDim1, expectedDim2); + return false; + } + } + return true; +} + +template <class T, int NumPyType> +static PythonToCppFunc checkArray2(PyObject *pyIn, int dim1, int dim2) +{ + return primitiveArrayCheck2(pyIn, NumPyType, dim1, dim2) ? convertArray2<T> : nullptr; +} + +template <class T> +static void setOrExtendArrayConverter(int dimension, IsArrayConvertibleToCppFunc toCppCheckFunc) +{ + // PYSIDE-2404/FIXME: When adding a C++ -> Python conversion, be sure + // to delay-initialize numpy in the converter (similar to the + // initialization in check() for the Python -> C++ conversion). + SbkArrayConverter *arrayConverter = ArrayTypeConverter<T>(dimension); + if (arrayConverter == unimplementedArrayConverter()) { + arrayConverter = createArrayConverter(toCppCheckFunc); + setArrayTypeConverter(ArrayTypeIndex<T>::index, dimension, arrayConverter); + } else { + arrayConverter->toCppConversions.push_back(toCppCheckFunc); + } +} + +// Extend the converters for primitive type one-dimensional arrays by NumPy ones. +template <class T, int NumPyType> +static inline void extendArrayConverter1() +{ + setOrExtendArrayConverter<T>(1, checkArray1<T, NumPyType>); +} + +// Extend the converters for primitive type one-dimensional arrays by NumPy ones. +template <class T, int NumPyType> +static inline void extendArrayConverter2() +{ + setOrExtendArrayConverter<T>(2, checkArray2<T, NumPyType>); +} + +void initNumPyArrayConverters() +{ + // Extend the converters for primitive types by NumPy ones. + extendArrayConverter1<short, NPY_SHORT>(); + extendArrayConverter2<short, NPY_SHORT>(); + extendArrayConverter1<unsigned short, NPY_SHORT>(); + extendArrayConverter2<unsigned short, NPY_SHORT>(); + extendArrayConverter1<int, NPY_INT>(); + extendArrayConverter2<int, NPY_INT>(); + extendArrayConverter1<unsigned int, NPY_UINT>(); + extendArrayConverter2<unsigned int, NPY_UINT>(); + extendArrayConverter1<long long, NPY_LONGLONG>(); + extendArrayConverter2<long long, NPY_LONGLONG>(); + extendArrayConverter1<unsigned long long, NPY_ULONGLONG>(); + if (sizeof(long) == 8) { // UNIX/LP64: ints typically come as long + extendArrayConverter1<long long, NPY_LONG>(); + extendArrayConverter2<long long, NPY_LONG>(); + extendArrayConverter1<unsigned long long, NPY_ULONG>(); + extendArrayConverter2<unsigned long long, NPY_ULONG>(); + } else if (sizeof(long) == sizeof(int)) { + extendArrayConverter1<int, NPY_LONG>(); + extendArrayConverter1<unsigned, NPY_ULONG>(); + extendArrayConverter2<int, NPY_LONG>(); + extendArrayConverter2<unsigned, NPY_ULONG>(); + } + extendArrayConverter1<float, NPY_FLOAT>(); + extendArrayConverter2<float, NPY_FLOAT>(); + extendArrayConverter1<double, NPY_DOUBLE>(); + extendArrayConverter2<double, NPY_DOUBLE>(); +} + +} // namespace Shiboken::Conversions diff --git a/sources/shiboken6/libshiboken/sbknumpycheck.h b/sources/shiboken6/libshiboken/sbknumpycheck.h new file mode 100644 index 000000000..cfe65372c --- /dev/null +++ b/sources/shiboken6/libshiboken/sbknumpycheck.h @@ -0,0 +1,30 @@ +// Copyright (C) 2021 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 + +#ifndef SBKNUMPYCHECK_H +#define SBKNUMPYCHECK_H + +#include <sbkpython.h> +#include <shibokenmacros.h> + + +// This header provides a PyArray_Check() definition that can be used to avoid +// having to include the numpy headers. When using numpy headers, make sure +// to include this header after them to skip the definition. Also remember +// that import_array() must then be called to initialize numpy. + +namespace Shiboken::Numpy +{ + +/// Check whether the object is a PyArrayObject +/// \param pyIn object +/// \return Whether it is a PyArrayObject +LIBSHIBOKEN_API bool check(PyObject *pyIn); + +} //namespace Shiboken::Numpy + +#ifndef PyArray_Check +# define PyArray_Check(op) Shiboken::Numpy::check(op) +#endif + +#endif // SBKNUMPYCHECK_H diff --git a/sources/shiboken6/libshiboken/sbknumpyview.cpp b/sources/shiboken6/libshiboken/sbknumpyview.cpp new file mode 100644 index 000000000..bafbf8038 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbknumpyview.cpp @@ -0,0 +1,265 @@ +// Copyright (C) 2021 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 + +// included by sbknumpy.cpp + +#include "helper.h" +#include <iostream> +#include <iomanip> +#include <optional> + +#ifdef HAVE_NUMPY + +namespace Shiboken { +namespace Numpy { + +static std::optional<View::Type> viewTypeFromNumPy(int npt) +{ + switch (npt) { + case NPY_SHORT: + return View::Int16; + case NPY_USHORT: + return View::Unsigned16; + case NPY_INT: + return View::Int; + case NPY_UINT: + return View::Unsigned; + case NPY_LONG: + if constexpr (sizeof(long) == sizeof(int)) + return View::Int; + if constexpr (sizeof(long) == sizeof(int64_t)) + return View::Int64; + break; + case NPY_ULONG: + if constexpr (sizeof(long) == sizeof(int)) + return View::Unsigned; + if constexpr (sizeof(long) == sizeof(int64_t)) + return View::Unsigned64; + break; + case NPY_LONGLONG: + if constexpr (sizeof(long long) == 8) + return View::Int64; + break; + case NPY_ULONGLONG: + if constexpr (sizeof(long long) == 8) + return View::Unsigned64; + break; + case NPY_FLOAT: + return View::Float; + case NPY_DOUBLE: + return View::Double; + default: + break; + } + return {}; +} + +View View::fromPyObject(PyObject *pyIn) +{ + if (pyIn == nullptr || PyArray_Check(pyIn) == 0) + return {}; + auto *ar = reinterpret_cast<PyArrayObject *>(pyIn); + if ((PyArray_FLAGS(ar) & NPY_ARRAY_C_CONTIGUOUS) == 0) + return {}; + const int ndim = PyArray_NDIM(ar); + if (ndim > 2) + return {}; + + const auto typeO = viewTypeFromNumPy(PyArray_TYPE(ar)); + if (!typeO.has_value()) + return {}; + + View result; + result.ndim = ndim; + result.type = typeO.value(); + result.data = PyArray_DATA(ar); + result.dimensions[0] = PyArray_DIMS(ar)[0]; + result.stride[0] = PyArray_STRIDES(ar)[0]; + if (ndim > 1) { + result.dimensions[1] = PyArray_DIMS(ar)[1]; + result.stride[1] = PyArray_STRIDES(ar)[1]; + } else { + result.dimensions[1] = result.stride[1] = 0; + } + return result; +} + +} // namespace Numpy + +template <class T> +static void debugArray(std::ostream &str, const T *data, int n) +{ + static const int maxData = 10; + str << " = "; + auto *end = data + std::min(n, maxData); + for (auto *d = data; d != end; ++d) { + if (d != data) + str << ", "; + str << *d; + } + if (n > maxData) + str << "..."; +} + +std::ostream &operator<<(std::ostream &str, const debugPyArrayObject &a) +{ + str << "PyArrayObject("; + if (a.m_object == nullptr) { + str << '0'; + } else if (PyArray_Check(a.m_object) != 0) { + auto *ar = reinterpret_cast<PyArrayObject *>(a.m_object); + const int ndim = PyArray_NDIM(ar); + const int type = PyArray_TYPE(ar); + const int flags = PyArray_FLAGS(ar); + str << "ndim=" << ndim << " ["; + for (int d = 0; d < ndim; ++d) { + if (d) + str << ", "; + str << PyArray_DIMS(ar)[d]; + } + str << "], type="; + switch (type) { + case NPY_SHORT: + str << "short"; + break; + case NPY_USHORT: + str << "ushort"; + break; + case NPY_INT: + str << "int32"; + break; + case NPY_UINT: + str << "uint32"; + break; + case NPY_LONG: + str << "long"; + break; + case NPY_ULONG: + str << "ulong"; + break; + case NPY_LONGLONG: + str << "long long"; + break; + case NPY_ULONGLONG: + str << "ulong long"; + break; + case NPY_FLOAT: + str << "float"; + break; + case NPY_DOUBLE: + str << "double"; + break; + default: + str << '(' << type << ')'; + break; + } + str << ", flags=0x" << std::hex << flags << std::dec; + if ((flags & NPY_ARRAY_C_CONTIGUOUS) != 0) + str << " [C-contiguous]"; + if ((flags & NPY_ARRAY_F_CONTIGUOUS) != 0) + str << " [Fortran-contiguous]"; + if ((flags & NPY_ARRAY_ALIGNED) != 0) + str << " [aligned]"; + if ((flags & NPY_ARRAY_OWNDATA) != 0) + str << " [owndata]"; + if ((flags & NPY_ARRAY_WRITEABLE) != 0) + str << " [writeable]"; + + if (const int dim0 = PyArray_DIMS(ar)[0]) { + auto *data = PyArray_DATA(ar); + switch (type) { + case NPY_SHORT: + debugArray(str, reinterpret_cast<const short *>(data), dim0); + break; + case NPY_USHORT: + debugArray(str, reinterpret_cast<const unsigned short *>(data), dim0); + break; + case NPY_INT: + debugArray(str, reinterpret_cast<const int *>(data), dim0); + break; + case NPY_UINT: + debugArray(str, reinterpret_cast<const unsigned *>(data), dim0); + break; + case NPY_LONG: + debugArray(str, reinterpret_cast<const long *>(data), dim0); + break; + case NPY_ULONG: + debugArray(str, reinterpret_cast<const unsigned long*>(data), dim0); + break; + case NPY_LONGLONG: + debugArray(str, reinterpret_cast<const long long *>(data), dim0); + break; + case NPY_ULONGLONG: + debugArray(str, reinterpret_cast<const unsigned long long *>(data), dim0); + break; + case NPY_FLOAT: + debugArray(str, reinterpret_cast<const float *>(data), dim0); + break; + case NPY_DOUBLE: + debugArray(str, reinterpret_cast<const double *>(data), dim0); + break; + } + } + } else { + str << "Invalid"; + } + str << ')'; + return str; +} + +} //namespace Shiboken + +#else // HAVE_NUMPY + +namespace Shiboken::Numpy +{ + +View View::fromPyObject(PyObject *) +{ + return {}; +} + +std::ostream &operator<<(std::ostream &str, const debugPyArrayObject &) +{ + str << "Unimplemented function " << __FUNCTION__ << ", (numpy was not found)."; + return str; +} + +} //namespace Shiboken::Numpy + +#endif // !HAVE_NUMPY + +namespace Shiboken::Numpy +{ + +bool View::sameLayout(const View &rhs) const +{ + return rhs && *this && ndim == rhs.ndim && type == rhs.type; +} + +bool View::sameSize(const View &rhs) const +{ + return sameLayout(rhs) + && dimensions[0] == rhs.dimensions[0] && dimensions[1] == rhs.dimensions[1]; +} + +std::ostream &operator<<(std::ostream &str, const View &v) +{ + str << "Shiboken::Numpy::View("; + if (v) { + str << "type=" << v.type << ", ndim=" << v.ndim << " [" + << v.dimensions[0]; + if (v.ndim > 1) + str << ", " << v.dimensions[1]; + str << "], stride=[" << v.stride[0]; + if (v.ndim > 1) + str << ", " << v.stride[1]; + str << "], data=" << v.data; + } else { + str << "invalid"; + } + str << ')'; + return str; +} + +} //namespace Shiboken::Numpy diff --git a/sources/shiboken6/libshiboken/sbknumpyview.h b/sources/shiboken6/libshiboken/sbknumpyview.h new file mode 100644 index 000000000..918913b78 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbknumpyview.h @@ -0,0 +1,47 @@ +// Copyright (C) 2022 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 + +#ifndef SBKNUMPYVIEW_H +#define SBKNUMPYVIEW_H + +#include <sbkpython.h> +#include <shibokenmacros.h> + +#include <iosfwd> + +namespace Shiboken::Numpy +{ + +/// Check whether the object is a PyArrayObject +/// \param pyIn object +/// \return Whether it is a PyArrayObject +LIBSHIBOKEN_API bool check(PyObject *pyIn); + +/// A simple view of an up to 2 dimensional, C-contiguous array of a standard +/// type. It can be passed to compilation units that do not include the +/// numpy headers. +struct LIBSHIBOKEN_API View +{ + enum Type { Int, Unsigned, Float, Double, Int16, Unsigned16, Int64, Unsigned64 }; + + static View fromPyObject(PyObject *pyIn); + + operator bool() const { return ndim > 0; } + + /// Return whether rhs is of the same type and dimensionality + bool sameLayout(const View &rhs) const; + /// Return whether rhs is of the same type dimensionality and size + bool sameSize(const View &rhs) const; + + int ndim = 0; + Py_ssize_t dimensions[2]; + Py_ssize_t stride[2]; + void *data = nullptr; + Type type = Int; +}; + +LIBSHIBOKEN_API std::ostream &operator<<(std::ostream &, const View &v); + +} //namespace Shiboken::Numpy + +#endif // SBKNUMPYVIEW_H diff --git a/sources/shiboken6/libshiboken/sbkpython.h b/sources/shiboken6/libshiboken/sbkpython.h new file mode 100644 index 000000000..e62fa13ae --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkpython.h @@ -0,0 +1,75 @@ +// Copyright (C) 2018 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 + +#ifndef SBKPYTHON_H +#define SBKPYTHON_H + +#include "sbkversion.h" + +// Qt's "slots" macro collides with the "slots" member variables +// used in some Python structs. For compilers that support push_macro, +// temporarily undefine it. +#if defined(slots) && (defined(__GNUC__) || defined(_MSC_VER) || defined(__clang__)) +# pragma push_macro("slots") +# undef slots +/* + * Python 2 has function _Py_Mangle directly in Python.h . + * This creates wrong language binding unless we define 'extern "C"' here. + */ +extern "C" { +/* + * Python 2 uses the "register" keyword, which is deprecated in C++ 11 + * and forbidden in C++17. + */ +# if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-register" +# endif + +# include <Python.h> + +# if defined(__clang__) +# pragma clang diagnostic pop +# endif +} +# include <structmember.h> +// Now we have the usual variables from Python.h . +# include "shibokenmacros.h" +// "pep384impl.h" may nowhere be included but in this file. +# include "pep384impl.h" +# pragma pop_macro("slots") + +#else + +extern "C" { +/* + * Python 2 uses the "register" keyword, which is deprecated in C++ 11 + * and forbidden in C++17. + */ +# if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-register" +# endif + +# include <Python.h> + +# if defined(__clang__) +# pragma clang diagnostic pop +# endif +} +# include <structmember.h> +// Now we have the usual variables from Python.h . +# include "shibokenmacros.h" +// "pep384impl.h" may nowhere be included but in this file. +# include "pep384impl.h" +#endif + +// In Python 3, Py_TPFLAGS_DEFAULT contains Py_TPFLAGS_HAVE_VERSION_TAG, +// which will trigger the attribute cache, which is not intended in Qt for Python. +// Use a customized Py_TPFLAGS_DEFAULT by defining Py_TPFLAGS_HAVE_VERSION_TAG = 0. +#undef Py_TPFLAGS_HAVE_VERSION_TAG +#define Py_TPFLAGS_HAVE_VERSION_TAG (0) + +using SbkObjectType [[deprecated]] = PyTypeObject; // FIXME PYSIDE 7 remove + +#endif diff --git a/sources/shiboken6/libshiboken/sbksmartpointer.cpp b/sources/shiboken6/libshiboken/sbksmartpointer.cpp new file mode 100644 index 000000000..ee28f7db8 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbksmartpointer.cpp @@ -0,0 +1,58 @@ +// Copyright (C) 2023 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 "sbksmartpointer.h" +#include "sbkstring.h" +#include "autodecref.h" + +#include <unordered_set> + +namespace Shiboken::SmartPointer +{ + +PyObject *repr(PyObject *pointer, PyObject *pointee) +{ + Shiboken::AutoDecRef pointerRepr(Shiboken::String::repr(pointer)); + if (pointer == nullptr) + return pointerRepr.release(); + + Shiboken::AutoDecRef pointeeRepr(pointee != nullptr + ? PyObject_Repr(pointee) + : Shiboken::String::repr(pointee)); + + return PyUnicode_FromFormat("%U (%U)", pointerRepr.object(), pointeeRepr.object()); +} + +// __dir__ for a smart pointer. Add the __dir__ entries of the pointee to the list. +PyObject *dir(PyObject *pointer, PyObject *pointee) +{ + if (pointer == nullptr) + return PyList_New(0); + // Get the pointer's dir entries. Note: PyObject_Dir() cannot be called on + // self, will crash. Work around by using the type dict keys. + AutoDecRef tpDict(PepType_GetDict(Py_TYPE(pointer))); + auto *result = PyMapping_Keys(tpDict); + + if (pointee != nullptr && pointee != Py_None) { + // Add the entries of the pointee that do not exist in the pointer's list. + // Since Python internally caches strings; we can use a set of PyObject *. + std::unordered_set<PyObject *> knownStrings; + for (Py_ssize_t i = 0, size = PySequence_Size(result); i < size; ++i) { + Shiboken::AutoDecRef item(PySequence_GetItem(result, i)); + knownStrings.insert(item.object()); + } + const auto knownEnd = knownStrings.end(); + + Shiboken::AutoDecRef pointeeDir(PyObject_Dir(pointee)); + for (Py_ssize_t i = 0, size = PySequence_Size(pointeeDir.object()); i < size; ++i) { + Shiboken::AutoDecRef item(PySequence_GetItem(pointeeDir, i)); + if (knownStrings.find(item.object()) == knownEnd) + PyList_Append(result, item.object()); + } + } + + PyList_Sort(result); + return result; +} + +} // namespace Shiboken::SmartPointer diff --git a/sources/shiboken6/libshiboken/sbksmartpointer.h b/sources/shiboken6/libshiboken/sbksmartpointer.h new file mode 100644 index 000000000..5e2022722 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbksmartpointer.h @@ -0,0 +1,18 @@ +// Copyright (C) 2023 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 + +#ifndef SBK_SBKSMARTPOINTER_H +#define SBK_SBKSMARTPOINTER_H + +#include "sbkpython.h" +#include "shibokenmacros.h" + +namespace Shiboken::SmartPointer +{ + +LIBSHIBOKEN_API PyObject *repr(PyObject *pointer, PyObject *pointee); +LIBSHIBOKEN_API PyObject *dir(PyObject *pointer, PyObject *pointee); + +} // namespace Shiboken::SmartPointer + +#endif // SBK_SBKSMARTPOINTER_H diff --git a/sources/shiboken6/libshiboken/sbkstaticstrings.cpp b/sources/shiboken6/libshiboken/sbkstaticstrings.cpp new file mode 100644 index 000000000..023de0ea4 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkstaticstrings.cpp @@ -0,0 +1,88 @@ +// Copyright (C) 2019 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 "sbkstaticstrings.h" +#include "sbkstaticstrings_p.h" +#include "sbkstring.h" + +#define STATIC_STRING_IMPL(funcName, value) \ +PyObject *funcName() \ +{ \ + static PyObject *const s = Shiboken::String::createStaticString(value); \ + return s; \ +} + +namespace Shiboken +{ +namespace PyName { +// exported: +STATIC_STRING_IMPL(dumps, "dumps") +STATIC_STRING_IMPL(fget, "fget") +STATIC_STRING_IMPL(fset, "fset") +STATIC_STRING_IMPL(im_func, "im_func") +STATIC_STRING_IMPL(im_self, "im_self") +STATIC_STRING_IMPL(loads, "loads") +STATIC_STRING_IMPL(multi, "multi") +STATIC_STRING_IMPL(name, "name") +STATIC_STRING_IMPL(orig_dict, "orig_dict") +STATIC_STRING_IMPL(qApp, "qApp") +STATIC_STRING_IMPL(result, "result") +STATIC_STRING_IMPL(select_id, "select_id") +STATIC_STRING_IMPL(value, "value") +STATIC_STRING_IMPL(values, "values") +STATIC_STRING_IMPL(qtStaticMetaObject, "staticMetaObject") + +// Internal: +STATIC_STRING_IMPL(classmethod, "classmethod") +STATIC_STRING_IMPL(co_name, "co_name") +STATIC_STRING_IMPL(compile, "compile"); +STATIC_STRING_IMPL(f_code, "f_code") +STATIC_STRING_IMPL(f_lineno, "f_lineno") +STATIC_STRING_IMPL(function, "function") +STATIC_STRING_IMPL(marshal, "marshal") +STATIC_STRING_IMPL(method, "method") +STATIC_STRING_IMPL(mro, "mro") +STATIC_STRING_IMPL(overload, "overload") +STATIC_STRING_IMPL(staticmethod, "staticmethod") +} // namespace PyName + +namespace PyMagicName { +// exported: +STATIC_STRING_IMPL(class_, "__class__") +STATIC_STRING_IMPL(dict, "__dict__") +STATIC_STRING_IMPL(doc, "__doc__") +STATIC_STRING_IMPL(ecf, "__ecf__") +STATIC_STRING_IMPL(file, "__file__") +STATIC_STRING_IMPL(get, "__get__") +STATIC_STRING_IMPL(members, "__members__") +STATIC_STRING_IMPL(module, "__module__") +STATIC_STRING_IMPL(name, "__name__") +STATIC_STRING_IMPL(property_methods, "__property_methods__") +STATIC_STRING_IMPL(qualname, "__qualname__") +STATIC_STRING_IMPL(self, "__self__") +STATIC_STRING_IMPL(select_i, "__self__") +STATIC_STRING_IMPL(code, "__code__") +STATIC_STRING_IMPL(rlshift, "__rlshift__") +STATIC_STRING_IMPL(rrshift, "__rrshift__") + +// Internal: +STATIC_STRING_IMPL(base, "__base__") +STATIC_STRING_IMPL(bases, "__bases__") +STATIC_STRING_IMPL(builtins, "__builtins__") +STATIC_STRING_IMPL(dictoffset, "__dictoffset__") +STATIC_STRING_IMPL(func, "__func__") +STATIC_STRING_IMPL(func_kind, "__func_kind__") +STATIC_STRING_IMPL(iter, "__iter__") +STATIC_STRING_IMPL(mro, "__mro__") +STATIC_STRING_IMPL(new_, "__new__") +STATIC_STRING_IMPL(objclass, "__objclass__") +STATIC_STRING_IMPL(weakrefoffset, "__weakrefoffset__") +STATIC_STRING_IMPL(opaque_container, "__opaque_container__") +} // namespace PyMagicName + +namespace Messages +{ +STATIC_STRING_IMPL(unknownException, "An unknown exception was caught") +} + +} // namespace Shiboken diff --git a/sources/shiboken6/libshiboken/sbkstaticstrings.h b/sources/shiboken6/libshiboken/sbkstaticstrings.h new file mode 100644 index 000000000..017790ee3 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkstaticstrings.h @@ -0,0 +1,61 @@ +// Copyright (C) 2019 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 + +#ifndef SBKSTATICSTRINGS_H +#define SBKSTATICSTRINGS_H + +#include "sbkpython.h" +#include "shibokenmacros.h" + +namespace Shiboken +{ +// Some often-used strings +namespace PyName +{ +LIBSHIBOKEN_API PyObject *co_name(); +LIBSHIBOKEN_API PyObject *dumps(); +LIBSHIBOKEN_API PyObject *fget(); +LIBSHIBOKEN_API PyObject *fset(); +LIBSHIBOKEN_API PyObject *f_code(); +LIBSHIBOKEN_API PyObject *f_lineno(); +LIBSHIBOKEN_API PyObject *im_func(); +LIBSHIBOKEN_API PyObject *im_self(); +LIBSHIBOKEN_API PyObject *loads(); +LIBSHIBOKEN_API PyObject *multi(); +LIBSHIBOKEN_API PyObject *name(); +LIBSHIBOKEN_API PyObject *orig_dict(); +LIBSHIBOKEN_API PyObject *result(); +LIBSHIBOKEN_API PyObject *select_id(); +LIBSHIBOKEN_API PyObject *value(); +LIBSHIBOKEN_API PyObject *values(); +LIBSHIBOKEN_API PyObject *qtStaticMetaObject(); +} // namespace PyName + +namespace PyMagicName +{ +LIBSHIBOKEN_API PyObject *class_(); +LIBSHIBOKEN_API PyObject *dict(); +LIBSHIBOKEN_API PyObject *doc(); +LIBSHIBOKEN_API PyObject *ecf(); +LIBSHIBOKEN_API PyObject *file(); +LIBSHIBOKEN_API PyObject *func(); +LIBSHIBOKEN_API PyObject *get(); +LIBSHIBOKEN_API PyObject *members(); +LIBSHIBOKEN_API PyObject *module(); +LIBSHIBOKEN_API PyObject *name(); +LIBSHIBOKEN_API PyObject *property_methods(); +LIBSHIBOKEN_API PyObject *qualname(); +LIBSHIBOKEN_API PyObject *self(); +LIBSHIBOKEN_API PyObject *opaque_container(); +LIBSHIBOKEN_API PyObject *code(); +LIBSHIBOKEN_API PyObject *rlshift(); +LIBSHIBOKEN_API PyObject *rrshift(); +} // namespace PyMagicName + +namespace Messages +{ +LIBSHIBOKEN_API PyObject *unknownException(); +} // Messages +} // namespace Shiboken + +#endif // SBKSTATICSTRINGS_H diff --git a/sources/shiboken6/libshiboken/sbkstaticstrings_p.h b/sources/shiboken6/libshiboken/sbkstaticstrings_p.h new file mode 100644 index 000000000..2a337bf7e --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkstaticstrings_p.h @@ -0,0 +1,37 @@ +// Copyright (C) 2019 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 "sbkpython.h" +#include "shibokenmacros.h" + +namespace Shiboken +{ +namespace PyName +{ +PyObject *classmethod(); +PyObject *compile(); +PyObject *function(); +PyObject *marshal(); +PyObject *method(); +PyObject *mro(); +PyObject *overload(); +PyObject *qApp(); +PyObject *staticmethod(); +} // namespace PyName +namespace PyMagicName +{ +PyObject *base(); +PyObject *bases(); +PyObject *builtins(); +PyObject *code(); +PyObject *dictoffset(); +PyObject *func_kind(); +PyObject *iter(); +PyObject *module(); +PyObject *mro(); +PyObject *new_(); +PyObject *objclass(); +PyObject *signature(); +PyObject *weakrefoffset(); +} // namespace PyMagicName +} // namespace Shiboken diff --git a/sources/shiboken6/libshiboken/sbkstring.cpp b/sources/shiboken6/libshiboken/sbkstring.cpp new file mode 100644 index 000000000..b5e87ca5a --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkstring.cpp @@ -0,0 +1,254 @@ +// Copyright (C) 2019 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 "sbkstring.h" +#include "sbkenum.h" +#include "sbkstaticstrings_p.h" +#include "autodecref.h" + +namespace Shiboken::String +{ + +// PYSIDE-795: Redirecting PySequence to Iterable +bool checkIterable(PyObject *obj) +{ + return PyObject_HasAttr(obj, Shiboken::PyMagicName::iter()); +} + +bool checkIterableArgument(PyObject *obj) +{ + return checkIterable(obj) && !Shiboken::Enum::check(obj); +} + +static PyObject *initPathLike() +{ + PyObject *PathLike{}; + auto osmodule = PyImport_ImportModule("os"); + if (osmodule == nullptr + || (PathLike = PyObject_GetAttrString(osmodule, "PathLike")) == nullptr) { + PyErr_Print(); + Py_FatalError("cannot import os.PathLike"); + } + return PathLike; +} + +// PYSIDE-1499: Migrate to pathlib.Path and support __fspath__ in PySide +bool checkPath(PyObject *path) +{ + // Let normal strings through, unchanged. + if (PyUnicode_Check(path) || PyBytes_Check(path)) + return true; + // Without the Limited API, we could look up an `__fspath__` class attribute. + // But we use `isinstance(os.PathLike)`, instead. + static PyObject *PathLike = initPathLike(); + return PyObject_IsInstance(path, PathLike); +} + +bool checkType(PyTypeObject *type) +{ + return type == &PyUnicode_Type; +} + +bool check(PyObject *obj) +{ + return obj == Py_None || PyUnicode_Check(obj); +} + +bool checkChar(PyObject *pyobj) +{ + return check(pyobj) && (len(pyobj) == 1); +} + +bool isConvertible(PyObject *obj) +{ + return check(obj); +} + +PyObject *fromCString(const char *value) +{ + return PyUnicode_FromString(value); +} + +PyObject *fromCString(const char *value, int len) +{ + return PyUnicode_FromStringAndSize(value, len); +} + +const char *toCString(PyObject *str) +{ + if (str == Py_None) + return nullptr; + if (PyUnicode_Check(str)) + return _PepUnicode_AsString(str); + if (PyBytes_Check(str)) + return PyBytes_AS_STRING(str); + return nullptr; +} + +const char *toCString(PyObject *str, Py_ssize_t *len) +{ + if (str == Py_None) { + *len = 0; + return nullptr; + } + if (PyUnicode_Check(str)) { + // We need to encode the unicode string into utf8 to know the size of returned char *. + Shiboken::AutoDecRef uniStr(PyUnicode_AsUTF8String(str)); + *len = PyBytes_GET_SIZE(uniStr.object()); + // Return unicode from str instead of uniStr, because the lifetime of the returned pointer + // depends on the lifetime of str. + return _PepUnicode_AsString(str); + } + if (PyBytes_Check(str)) { + *len = PyBytes_GET_SIZE(str); + return PyBytes_AS_STRING(str); + } + return nullptr; +} + +bool concat(PyObject **val1, PyObject *val2) +{ + if (PyUnicode_Check(*val1) && PyUnicode_Check(val2)) { + PyObject *result = PyUnicode_Concat(*val1, val2); + Py_DECREF(*val1); + *val1 = result; + return true; + } + + if (PyBytes_Check(*val1) && PyBytes_Check(val2)) { + PyBytes_Concat(val1, val2); + return true; + } + + return false; +} + +PyObject *fromFormat(const char *format, ...) +{ + va_list argp; + va_start(argp, format); + PyObject *result = nullptr; + result = PyUnicode_FromFormatV(format, argp); + va_end(argp); + return result; +} + +PyObject *fromStringAndSize(const char *str, Py_ssize_t size) +{ + return PyUnicode_FromStringAndSize(str, size); +} + +int compare(PyObject *val1, const char *val2) +{ + if (PyUnicode_Check(val1)) + return PyUnicode_CompareWithASCIIString(val1, val2); + return 0; + +} + +Py_ssize_t len(PyObject *str) +{ + if (str == Py_None) + return 0; + + if (PyUnicode_Check(str)) + return PepUnicode_GetLength(str); + + if (PyBytes_Check(str)) + return PyBytes_GET_SIZE(str); + return 0; +} + +/////////////////////////////////////////////////////////////////////// +// +// Implementation of efficient Python strings +// ------------------------------------------ +// +// Instead of repetitively executing +// +// PyObject *attr = PyObject_GetAttrString(obj, "__name__"); +// +// a helper of the form +// +// PyObject *name() +// { +// static PyObject *const s = Shiboken::String::createStaticString("__name__"); +// return result; +// } +// +// can now be implemented, which registers the string into a static set avoiding +// repetitive string creation. The resulting code looks like: +// +// PyObject *attr = PyObject_GetAttr(obj, name()); +// + +PyObject *createStaticString(const char *str) +{ + return PyUnicode_InternFromString(str); +} + +/////////////////////////////////////////////////////////////////////// +// +// PYSIDE-1019: Helper function for snake_case vs. camelCase names +// --------------------------------------------------------------- +// +// When renaming dict entries, `BindingManager::getOverride` must +// use adapted names. +// +// This might become more complex when we need to register +// exceptions from this rule. +// + +PyObject *getSnakeCaseName(const char *name, bool lower) +{ + /* + * Convert `camelCase` to `snake_case`. + * Gives up when there are two consecutive upper chars. + * + * Also functions beginning with `gl` followed by upper case stay + * unchanged since that are the special OpenGL functions. + */ + if (!lower + || strlen(name) < 3 + || (name[0] == 'g' && name[1] == 'l' && isupper(name[2]))) + return createStaticString(name); + + char new_name[200 + 1] = {}; + const char *p = name; + char *q = new_name; + for (; *p && q - new_name < 200; ++p, ++q) { + if (isupper(*p)) { + if (p != name && isupper(*(p - 1))) + return createStaticString(name); + *q = '_'; + ++q; + *q = tolower(*p); + } + else { + *q = *p; + } + } + return createStaticString(new_name); +} + +PyObject *getSnakeCaseName(PyObject *name, bool lower) +{ + // This is all static strings, not refcounted. + if (lower) + return getSnakeCaseName(toCString(name), lower); + return name; +} + +// Return a generic representation of a PyObject as does PyObject_Repr(). +// Note: PyObject_Repr() may not be called on self from __repr__() as this +// causes a recursion. +PyObject *repr(PyObject *o) +{ + if (o == nullptr) + return PyUnicode_FromString("<NULL>"); + if (o == Py_None) + return PyUnicode_FromString("None"); + return PyUnicode_FromFormat("<%s object at %p>", Py_TYPE(o)->tp_name, o); +} + +} // namespace Shiboken::String diff --git a/sources/shiboken6/libshiboken/sbkstring.h b/sources/shiboken6/libshiboken/sbkstring.h new file mode 100644 index 000000000..ebc5428c7 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkstring.h @@ -0,0 +1,42 @@ +// Copyright (C) 2019 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 + +#ifndef SBKSTRING_H +#define SBKSTRING_H + +#include "sbkpython.h" +#include "shibokenmacros.h" + +namespace Shiboken +{ +namespace String +{ + LIBSHIBOKEN_API bool check(PyObject *obj); + LIBSHIBOKEN_API bool checkIterable(PyObject *obj); + /// Check for iterable function arguments (excluding enumerations) + LIBSHIBOKEN_API bool checkIterableArgument(PyObject *obj); + LIBSHIBOKEN_API bool checkPath(PyObject *path); + LIBSHIBOKEN_API bool checkType(PyTypeObject *obj); + LIBSHIBOKEN_API bool checkChar(PyObject *obj); + LIBSHIBOKEN_API bool isConvertible(PyObject *obj); + LIBSHIBOKEN_API PyObject *fromCString(const char *value); + LIBSHIBOKEN_API PyObject *fromCString(const char *value, int len); + LIBSHIBOKEN_API const char *toCString(PyObject *str); + LIBSHIBOKEN_API const char *toCString(PyObject *str, Py_ssize_t *len); + LIBSHIBOKEN_API bool concat(PyObject **val1, PyObject *val2); + LIBSHIBOKEN_API PyObject *fromFormat(const char *format, ...); + LIBSHIBOKEN_API PyObject *fromStringAndSize(const char *str, Py_ssize_t size); + LIBSHIBOKEN_API int compare(PyObject *val1, const char *val2); + LIBSHIBOKEN_API Py_ssize_t len(PyObject *str); + LIBSHIBOKEN_API PyObject *createStaticString(const char *str); + LIBSHIBOKEN_API PyObject *getSnakeCaseName(const char *name, bool lower); + LIBSHIBOKEN_API PyObject *getSnakeCaseName(PyObject *name, bool lower); + LIBSHIBOKEN_API PyObject *repr(PyObject *o); + +} // namespace String +} // namespace Shiboken + + +#endif + + diff --git a/sources/shiboken6/libshiboken/sbktypefactory.cpp b/sources/shiboken6/libshiboken/sbktypefactory.cpp new file mode 100644 index 000000000..079548eed --- /dev/null +++ b/sources/shiboken6/libshiboken/sbktypefactory.cpp @@ -0,0 +1,407 @@ +// Copyright (C) 2021 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 "sbktypefactory.h" +#include "shiboken.h" + +extern "C" +{ + +using Shiboken::AutoDecRef; + +PyTypeObject *SbkType_FromSpec(PyType_Spec *spec) +{ + return SbkType_FromSpec_BMDWB(spec, nullptr, nullptr, 0, 0, nullptr); +} + +PyTypeObject *SbkType_FromSpecWithMeta(PyType_Spec *spec, PyTypeObject *meta) +{ + return SbkType_FromSpec_BMDWB(spec, nullptr, meta, 0, 0, nullptr); +} + +PyTypeObject *SbkType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases) +{ + return SbkType_FromSpec_BMDWB(spec, bases, nullptr, 0, 0, nullptr); +} + +PyTypeObject *SbkType_FromSpecBasesMeta(PyType_Spec *spec, PyObject *bases, PyTypeObject *meta) +{ + return SbkType_FromSpec_BMDWB(spec, bases, meta, 0, 0, nullptr); +} + +#ifdef PYPY_VERSION + +static PyObject *_PyType_FromSpecWithBases(PyType_Spec *, PyObject *); + +#else + +#define _PyType_FromSpecWithBases PyType_FromSpecWithBases + +#endif // PYPY_VERSION + +// PYSIDE-2230: Not so temporary fix for Python 3.12. +// A tp_new is no longer allowed in a meta class. +// Hopefully, the Python devs will supply the missing support. +// It turned out that they will not fix that, as expected. +// Note: Python 3.12 is the first version that grabs the metaclass from base classes. +static PyObject *_PyType_FromSpecWithBasesHack(PyType_Spec *spec, + PyObject *bases, + PyTypeObject *meta) +{ + PyTypeObject *keepMeta{}; + newfunc keepNew{}; + AutoDecRef basesPatch{}; + + if (bases) { + if (bases == Py_None) { + // PYSIDE-2230: This is the SbkObject entry which has no base to provide + // the metaclass. We patch it in by modifying `object`s class. + assert(meta); + auto *base = reinterpret_cast<PyObject *>(&PyBaseObject_Type); + base->ob_type = meta; + basesPatch.reset(Py_BuildValue("(O)", &PyBaseObject_Type)); + bases = basesPatch.object(); + } + + Py_ssize_t n = PyTuple_GET_SIZE(bases); + for (auto idx = 0; idx < n; ++idx) { + PyTypeObject *base = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(bases, idx)); + PyTypeObject *meta = Py_TYPE(base); + if (meta->tp_new != PyType_Type.tp_new) { + // make sure there is no second meta class + assert(keepMeta == nullptr); + keepMeta = meta; + keepNew = meta->tp_new; + meta->tp_new = PyType_Type.tp_new; + } + } + } + +#if !defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030C0000 + auto *ret = PyType_FromMetaclass(meta, nullptr /*module*/, spec, bases); +#else + auto *ret = _PyType_FromSpecWithBases(spec, bases); +#endif + + if (keepMeta) + keepMeta->tp_new = keepNew; + if (basesPatch.object()) { + // undo the metaclass patch. + auto *base = PyTuple_GET_ITEM(basesPatch.object(), 0); + base->ob_type = &PyType_Type; + } + return ret; +} + +PyTypeObject *SbkType_FromSpec_BMDWB(PyType_Spec *spec, + PyObject *bases, + PyTypeObject *meta, + int dictoffset, + int weaklistoffset, + PyBufferProcs *bufferprocs) +{ + // PYSIDE-1286: Generate correct __module__ and __qualname__ + // The name field can now be extended by an "n:" prefix which is + // the number of modules in the name. The default is 1. + // + // Example: + // "2:mainmod.submod.mainclass.subclass" + // results in + // __module__ : "mainmod.submod" + // __qualname__ : "mainclass.subclass" + // __name__ : "subclass" + + PyType_Spec new_spec = *spec; + const char *colon = strchr(spec->name, ':'); + assert(colon); + int package_level = atoi(spec->name); + const char *mod = new_spec.name = colon + 1; + + PyObject *obType = _PyType_FromSpecWithBasesHack(&new_spec, bases, meta); + if (obType == nullptr) + return nullptr; + + const char *qual = mod; + for (int idx = package_level; idx > 0; --idx) { + const char *dot = strchr(qual, '.'); + if (!dot) + break; + qual = dot + 1; + } + int mlen = qual - mod - 1; + AutoDecRef module(Shiboken::String::fromCString(mod, mlen)); + AutoDecRef qualname(Shiboken::String::fromCString(qual)); + + auto *type = reinterpret_cast<PyTypeObject *>(obType); + + if (meta) { + PyTypeObject *hold = Py_TYPE(type); + obType->ob_type = meta; + Py_INCREF(Py_TYPE(type)); + if (hold->tp_flags & Py_TPFLAGS_HEAPTYPE) + Py_DECREF(hold); + } + + if (dictoffset) + type->tp_dictoffset = dictoffset; + if (weaklistoffset) + type->tp_weaklistoffset = weaklistoffset; + if (bufferprocs) + PepType_AS_BUFFER(type) = bufferprocs; + +#ifdef PYPY_VERSION + // PYSIDE-535: Careful: Using PyObject_SetAttr would have the side-effect of calling + // PyType_Ready too early. (at least in PyPy, which caused pretty long debugging.) + auto *ht = reinterpret_cast<PyHeapTypeObject *>(type); + ht->ht_qualname = qualname; + AutoDecRef tpDict(PepType_GetDict(type)); + if (PyDict_SetItem(tpDict.object(), Shiboken::PyMagicName::qualname(), qualname)) + return nullptr; + if (PyDict_SetItem(tpDict.object(), Shiboken::PyMagicName::module(), module)) + return nullptr; + PyType_Ready(type); +#else + if (PyObject_SetAttr(obType, Shiboken::PyMagicName::module(), module) < 0) + return nullptr; + if (PyObject_SetAttr(obType, Shiboken::PyMagicName::qualname(), qualname) < 0) + return nullptr; + PyType_Modified(type); +#endif + return type; +} + +#ifdef PYPY_VERSION + +///////////////////////////////////////////////////////////////////////////// +// +// Reimplementation of `PyType_FromSpecWithBases` +// +// This is almost the original code from Python 3.7 with a few changes. +// Especially the call to `PyType_Ready` is deferred until the needed +// post-actions are carried out in `SbkType_FromSpec_BMDWBD`. +// +// FIXME remove ASAP. +// Version is not clear, yet. Current version == 7.3.6 +// + +static const short slotoffsets[] = { + -1, /* invalid slot */ +/* Generated by typeslots.py */ +0, +0, +offsetof(PyHeapTypeObject, as_mapping.mp_ass_subscript), +offsetof(PyHeapTypeObject, as_mapping.mp_length), +offsetof(PyHeapTypeObject, as_mapping.mp_subscript), +offsetof(PyHeapTypeObject, as_number.nb_absolute), +offsetof(PyHeapTypeObject, as_number.nb_add), +offsetof(PyHeapTypeObject, as_number.nb_and), +offsetof(PyHeapTypeObject, as_number.nb_bool), +offsetof(PyHeapTypeObject, as_number.nb_divmod), +offsetof(PyHeapTypeObject, as_number.nb_float), +offsetof(PyHeapTypeObject, as_number.nb_floor_divide), +offsetof(PyHeapTypeObject, as_number.nb_index), +offsetof(PyHeapTypeObject, as_number.nb_inplace_add), +offsetof(PyHeapTypeObject, as_number.nb_inplace_and), +offsetof(PyHeapTypeObject, as_number.nb_inplace_floor_divide), +offsetof(PyHeapTypeObject, as_number.nb_inplace_lshift), +offsetof(PyHeapTypeObject, as_number.nb_inplace_multiply), +offsetof(PyHeapTypeObject, as_number.nb_inplace_or), +offsetof(PyHeapTypeObject, as_number.nb_inplace_power), +offsetof(PyHeapTypeObject, as_number.nb_inplace_remainder), +offsetof(PyHeapTypeObject, as_number.nb_inplace_rshift), +offsetof(PyHeapTypeObject, as_number.nb_inplace_subtract), +offsetof(PyHeapTypeObject, as_number.nb_inplace_true_divide), +offsetof(PyHeapTypeObject, as_number.nb_inplace_xor), +offsetof(PyHeapTypeObject, as_number.nb_int), +offsetof(PyHeapTypeObject, as_number.nb_invert), +offsetof(PyHeapTypeObject, as_number.nb_lshift), +offsetof(PyHeapTypeObject, as_number.nb_multiply), +offsetof(PyHeapTypeObject, as_number.nb_negative), +offsetof(PyHeapTypeObject, as_number.nb_or), +offsetof(PyHeapTypeObject, as_number.nb_positive), +offsetof(PyHeapTypeObject, as_number.nb_power), +offsetof(PyHeapTypeObject, as_number.nb_remainder), +offsetof(PyHeapTypeObject, as_number.nb_rshift), +offsetof(PyHeapTypeObject, as_number.nb_subtract), +offsetof(PyHeapTypeObject, as_number.nb_true_divide), +offsetof(PyHeapTypeObject, as_number.nb_xor), +offsetof(PyHeapTypeObject, as_sequence.sq_ass_item), +offsetof(PyHeapTypeObject, as_sequence.sq_concat), +offsetof(PyHeapTypeObject, as_sequence.sq_contains), +offsetof(PyHeapTypeObject, as_sequence.sq_inplace_concat), +offsetof(PyHeapTypeObject, as_sequence.sq_inplace_repeat), +offsetof(PyHeapTypeObject, as_sequence.sq_item), +offsetof(PyHeapTypeObject, as_sequence.sq_length), +offsetof(PyHeapTypeObject, as_sequence.sq_repeat), +offsetof(PyHeapTypeObject, ht_type.tp_alloc), +offsetof(PyHeapTypeObject, ht_type.tp_base), +offsetof(PyHeapTypeObject, ht_type.tp_bases), +offsetof(PyHeapTypeObject, ht_type.tp_call), +offsetof(PyHeapTypeObject, ht_type.tp_clear), +offsetof(PyHeapTypeObject, ht_type.tp_dealloc), +offsetof(PyHeapTypeObject, ht_type.tp_del), +offsetof(PyHeapTypeObject, ht_type.tp_descr_get), +offsetof(PyHeapTypeObject, ht_type.tp_descr_set), +offsetof(PyHeapTypeObject, ht_type.tp_doc), +offsetof(PyHeapTypeObject, ht_type.tp_getattr), +offsetof(PyHeapTypeObject, ht_type.tp_getattro), +offsetof(PyHeapTypeObject, ht_type.tp_hash), +offsetof(PyHeapTypeObject, ht_type.tp_init), +offsetof(PyHeapTypeObject, ht_type.tp_is_gc), +offsetof(PyHeapTypeObject, ht_type.tp_iter), +offsetof(PyHeapTypeObject, ht_type.tp_iternext), +offsetof(PyHeapTypeObject, ht_type.tp_methods), +offsetof(PyHeapTypeObject, ht_type.tp_new), +offsetof(PyHeapTypeObject, ht_type.tp_repr), +offsetof(PyHeapTypeObject, ht_type.tp_richcompare), +offsetof(PyHeapTypeObject, ht_type.tp_setattr), +offsetof(PyHeapTypeObject, ht_type.tp_setattro), +offsetof(PyHeapTypeObject, ht_type.tp_str), +offsetof(PyHeapTypeObject, ht_type.tp_traverse), +offsetof(PyHeapTypeObject, ht_type.tp_members), +offsetof(PyHeapTypeObject, ht_type.tp_getset), +offsetof(PyHeapTypeObject, ht_type.tp_free), +offsetof(PyHeapTypeObject, as_number.nb_matrix_multiply), +offsetof(PyHeapTypeObject, as_number.nb_inplace_matrix_multiply), +offsetof(PyHeapTypeObject, as_async.am_await), +offsetof(PyHeapTypeObject, as_async.am_aiter), +offsetof(PyHeapTypeObject, as_async.am_anext), +offsetof(PyHeapTypeObject, ht_type.tp_finalize), +}; + +static PyTypeObject * +best_base(PyObject *bases) +{ + // We always have only one base + return reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(bases, 0)); +} + +static PyObject * +_PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases) +{ + PyHeapTypeObject *res = reinterpret_cast<PyHeapTypeObject *>( + PyType_GenericAlloc(&PyType_Type, 0)); + PyTypeObject *type, *base; + PyObject *modname; + char *s; + char *res_start = reinterpret_cast<char *>(res); + PyType_Slot *slot; + + if (res == nullptr) + return nullptr; + + if (spec->name == nullptr) { + PyErr_SetString(PyExc_SystemError, + "Type spec does not define the name field."); + goto fail; + } + + /* Set the type name and qualname */ + s = strrchr(const_cast<char *>(spec->name), '.'); + if (s == nullptr) + s = (char*)spec->name; + else + s++; + + type = &res->ht_type; + /* The flags must be initialized early, before the GC traverses us */ + type->tp_flags = spec->flags | Py_TPFLAGS_HEAPTYPE; + res->ht_name = PyUnicode_FromString(s); + if (!res->ht_name) + goto fail; + res->ht_qualname = res->ht_name; + Py_INCREF(res->ht_qualname); + type->tp_name = spec->name; + + /* Adjust for empty tuple bases */ + if (!bases) { + base = &PyBaseObject_Type; + /* See whether Py_tp_base(s) was specified */ + for (slot = spec->slots; slot->slot; slot++) { + if (slot->slot == Py_tp_base) + base = reinterpret_cast<PyTypeObject *>(slot->pfunc); + else if (slot->slot == Py_tp_bases) { + bases = reinterpret_cast<PyObject *>(slot->pfunc); + Py_INCREF(bases); + } + } + if (!bases) + bases = PyTuple_Pack(1, base); + if (!bases) + goto fail; + } + else + Py_INCREF(bases); + + /* Calculate best base, and check that all bases are type objects */ + base = best_base(bases); + if (base == nullptr) { + goto fail; + } + + /* Initialize essential fields */ + type->tp_as_async = &res->as_async; + type->tp_as_number = &res->as_number; + type->tp_as_sequence = &res->as_sequence; + type->tp_as_mapping = &res->as_mapping; + type->tp_as_buffer = &res->as_buffer; + /* Set tp_base and tp_bases */ + type->tp_bases = bases; + bases = nullptr; + Py_INCREF(base); + type->tp_base = base; + + type->tp_basicsize = spec->basicsize; + type->tp_itemsize = spec->itemsize; + + for (slot = spec->slots; slot->slot; slot++) { + if (slot->slot == Py_tp_base || slot->slot == Py_tp_bases) + /* Processed above */ + continue; + *reinterpret_cast<void **>(res_start + slotoffsets[slot->slot]) = slot->pfunc; + + /* need to make a copy of the docstring slot, which usually + points to a static string literal */ + if (slot->slot == Py_tp_doc) { + const char *old_doc = reinterpret_cast<char *>(slot->pfunc); + //_PyType_DocWithoutSignature(type->tp_name, slot->pfunc); + size_t len = strlen(old_doc)+1; + char *tp_doc = reinterpret_cast<char *>(PyObject_MALLOC(len)); + if (tp_doc == nullptr) { + type->tp_doc = nullptr; + PyErr_NoMemory(); + goto fail; + } + memcpy(tp_doc, old_doc, len); + type->tp_doc = tp_doc; + } + } + if (type->tp_dealloc == nullptr) { + /* It's a heap type, so needs the heap types' dealloc. + subtype_dealloc will call the base type's tp_dealloc, if + necessary. */ + type->tp_dealloc = _PyPy_subtype_dealloc; + } + + /// Here is the only change needed: Do not finalize type creation. + // if (PyType_Ready(type) < 0) + // goto fail; + PepType_SetDict(type, PyDict_New()); + /// This is not found in PyPy: + // if (type->tp_dictoffset) { + // res->ht_cached_keys = _PyDict_NewKeysForClass(); + // } + + /* Set type.__module__ */ + /// Removed __module__ handling, already implemented. + + return (PyObject*)res; + + fail: + Py_DECREF(res); + return nullptr; +} + +#endif // PYPY_VERSION + +} //extern "C" diff --git a/sources/shiboken6/libshiboken/sbktypefactory.h b/sources/shiboken6/libshiboken/sbktypefactory.h new file mode 100644 index 000000000..81cb32d41 --- /dev/null +++ b/sources/shiboken6/libshiboken/sbktypefactory.h @@ -0,0 +1,26 @@ +// Copyright (C) 2021 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 + +#ifndef SBKTYPEFACTORY_H +#define SBKTYPEFACTORY_H + +#include "sbkpython.h" + +extern "C" +{ + +// PYSIDE-535: Encapsulation of PyType_FromSpec special-cased for PyPy +LIBSHIBOKEN_API PyTypeObject *SbkType_FromSpec(PyType_Spec *); +LIBSHIBOKEN_API PyTypeObject *SbkType_FromSpecWithMeta(PyType_Spec *, PyTypeObject *); +LIBSHIBOKEN_API PyTypeObject *SbkType_FromSpecWithBases(PyType_Spec *, PyObject *); +LIBSHIBOKEN_API PyTypeObject *SbkType_FromSpecBasesMeta(PyType_Spec *, PyObject *, PyTypeObject *); +LIBSHIBOKEN_API PyTypeObject *SbkType_FromSpec_BMDWB(PyType_Spec *spec, + PyObject *bases, + PyTypeObject *meta, + int dictoffset, + int weaklistoffset, + PyBufferProcs *bufferprocs); + +} //extern "C" + +#endif // SBKTYPEFACTORY_H diff --git a/sources/shiboken6/libshiboken/sbkversion.h.in b/sources/shiboken6/libshiboken/sbkversion.h.in new file mode 100644 index 000000000..5c0b38fdb --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkversion.h.in @@ -0,0 +1,17 @@ +// 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 + +#ifndef SBKVERSION_H +#define SBKVERSION_H + +#define SHIBOKEN_VERSION "@shiboken_MAJOR_VERSION@.@shiboken_MINOR_VERSION@.@shiboken_MICRO_VERSION@" +#define SHIBOKEN_MAJOR_VERSION @shiboken_MAJOR_VERSION@ +#define SHIBOKEN_MINOR_VERSION @shiboken_MINOR_VERSION@ +#define SHIBOKEN_MICRO_VERSION @shiboken_MICRO_VERSION@ +#define SHIBOKEN_RELEASE_LEVEL "final" +#define SHIBOKEN_SERIAL 0 +#define PYTHON_VERSION_MAJOR @Python_VERSION_MAJOR@ +#define PYTHON_VERSION_MINOR @Python_VERSION_MINOR@ +#define PYTHON_VERSION_PATCH @Python_VERSION_PATCH@ + +#endif diff --git a/sources/shiboken6/libshiboken/sbkwindows.h b/sources/shiboken6/libshiboken/sbkwindows.h new file mode 100644 index 000000000..9e753fa5e --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkwindows.h @@ -0,0 +1,17 @@ +// Copyright (C) 2022 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 + +#ifndef SBKWINDOWS_H +#define SBKWINDOWS_H + +#ifdef _WIN32 +# ifndef NOMINMAX +# define NOMINMAX +# endif +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include <windows.h> +#endif + +#endif // SBKWINDOWS_H diff --git a/sources/shiboken6/libshiboken/shiboken.h b/sources/shiboken6/libshiboken/shiboken.h new file mode 100644 index 000000000..fcf777ae0 --- /dev/null +++ b/sources/shiboken6/libshiboken/shiboken.h @@ -0,0 +1,27 @@ +// 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 + +#ifndef SHIBOKEN_H +#define SHIBOKEN_H + +#include "sbkpython.h" +#include "autodecref.h" +#include "basewrapper.h" +#include "bindingmanager.h" +#include "gilstate.h" +#include "threadstatesaver.h" +#include "helper.h" +#include "pyobjectholder.h" +#include "sbkarrayconverter.h" +#include "sbkconverter.h" +#include "sbkenum.h" +#include "sbkerrors.h" +#include "sbkmodule.h" +#include "sbkstring.h" +#include "sbkstaticstrings.h" +#include "shibokenmacros.h" +#include "shibokenbuffer.h" +#include "signature.h" + +#endif // SHIBOKEN_H + diff --git a/sources/shiboken6/libshiboken/shibokenbuffer.cpp b/sources/shiboken6/libshiboken/shibokenbuffer.cpp new file mode 100644 index 000000000..d04613895 --- /dev/null +++ b/sources/shiboken6/libshiboken/shibokenbuffer.cpp @@ -0,0 +1,70 @@ +// 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 "shibokenbuffer.h" +#include <cstdlib> +#include <cstring> + +bool Shiboken::Buffer::checkType(PyObject *pyObj) +{ + return PyObject_CheckBuffer(pyObj) != 0; +} + +void *Shiboken::Buffer::getPointer(PyObject *pyObj, Py_ssize_t *size) +{ + Py_buffer view; + if (PyObject_GetBuffer(pyObj, &view, PyBUF_ND) == 0) { + if (size) + *size = view.len; + PyBuffer_Release(&view); + return view.buf; + } + return nullptr; +} + +void *Shiboken::Buffer::copyData(PyObject *pyObj, Py_ssize_t *sizeIn) +{ + void *result = nullptr; + Py_ssize_t size = 0; + + Py_buffer view; + if (PyObject_GetBuffer(pyObj, &view, PyBUF_ND) == 0) { + size = view.len; + if (size) { + result = std::malloc(size); + if (result != nullptr) + std::memcpy(result, view.buf, size); + else + size = 0; + } + PyBuffer_Release(&view); + } + + if (sizeIn != nullptr) + *sizeIn = size; + return result; +} + +PyObject *Shiboken::Buffer::newObject(void *memory, Py_ssize_t size, Type type) +{ + if (size == 0) + Py_RETURN_NONE; + Py_buffer view; + memset(&view, 0, sizeof(Py_buffer)); + view.buf = memory; + view.len = size; + view.readonly = type == Shiboken::Buffer::ReadOnly; + view.ndim = 1; + view.itemsize = sizeof(char); + Py_ssize_t shape[] = { size }; + view.shape = shape; + // Pep384: This is way too complicated and impossible with the limited api: + //return PyMemoryView_FromBuffer(&view); + return PyMemoryView_FromMemory(reinterpret_cast<char *>(view.buf), + size, type == ReadOnly ? PyBUF_READ : PyBUF_WRITE); +} + +PyObject *Shiboken::Buffer::newObject(const void *memory, Py_ssize_t size) +{ + return newObject(const_cast<void *>(memory), size, ReadOnly); +} diff --git a/sources/shiboken6/libshiboken/shibokenbuffer.h b/sources/shiboken6/libshiboken/shibokenbuffer.h new file mode 100644 index 000000000..6b17eb6eb --- /dev/null +++ b/sources/shiboken6/libshiboken/shibokenbuffer.h @@ -0,0 +1,57 @@ +// 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 + +#ifndef SHIBOKEN_BUFFER_H +#define SHIBOKEN_BUFFER_H + +#include "sbkpython.h" +#include "shibokenmacros.h" + +namespace Shiboken +{ + +namespace Buffer +{ + enum Type { + ReadOnly, + WriteOnly, + ReadWrite + }; + + /** + * Creates a new Python buffer pointing to a contiguous memory block at + * \p memory of size \p size. + */ + LIBSHIBOKEN_API PyObject *newObject(void *memory, Py_ssize_t size, Type type); + + /** + * Creates a new <b>read only</b> Python buffer pointing to a contiguous memory block at + * \p memory of size \p size. + */ + LIBSHIBOKEN_API PyObject *newObject(const void *memory, Py_ssize_t size); + + /** + * Check if is ok to use \p pyObj as argument in all function under Shiboken::Buffer namespace. + */ + LIBSHIBOKEN_API bool checkType(PyObject *pyObj); + + /** + * Returns a pointer to the memory pointed by the buffer \p pyObj, \p size is filled with the buffer + * size if not null. + * + * If the \p pyObj is a non-contiguous buffer a Python error is set. + */ + LIBSHIBOKEN_API void *getPointer(PyObject *pyObj, Py_ssize_t *size = nullptr); + + /** + * Returns a copy of the buffer data which should be free'd. + * + * If the \p pyObj is a non-contiguous buffer a Python error is set. + * nullptr is returned for empty buffers. + */ + LIBSHIBOKEN_API void *copyData(PyObject *pyObj, Py_ssize_t *size = nullptr); + +} // namespace Buffer +} // namespace Shiboken + +#endif diff --git a/sources/shiboken6/libshiboken/shibokenmacros.h b/sources/shiboken6/libshiboken/shibokenmacros.h new file mode 100644 index 000000000..3c083c5bb --- /dev/null +++ b/sources/shiboken6/libshiboken/shibokenmacros.h @@ -0,0 +1,26 @@ +// Copyright (C) 2020 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 + +#ifndef SHIBOKENMACROS_H +#define SHIBOKENMACROS_H + +// LIBSHIBOKEN_API macro is used for the public API symbols. +#if defined _WIN32 +# define LIBSHIBOKEN_EXPORT __declspec(dllexport) +# ifdef _MSC_VER +# define LIBSHIBOKEN_IMPORT __declspec(dllimport) +# else +# define LIBSHIBOKEN_IMPORT +# endif +#else +# define LIBSHIBOKEN_EXPORT __attribute__ ((visibility("default"))) +# define LIBSHIBOKEN_IMPORT +#endif + +#ifdef BUILD_LIBSHIBOKEN +# define LIBSHIBOKEN_API LIBSHIBOKEN_EXPORT +#else +# define LIBSHIBOKEN_API LIBSHIBOKEN_IMPORT +#endif + +#endif // SHIBOKENMACROS_H diff --git a/sources/shiboken6/libshiboken/signature.h b/sources/shiboken6/libshiboken/signature.h new file mode 100644 index 000000000..e0130b5a6 --- /dev/null +++ b/sources/shiboken6/libshiboken/signature.h @@ -0,0 +1,21 @@ +// Copyright (C) 2018 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 + +#ifndef SIGNATURE_H +#define SIGNATURE_H + +#include "shibokenmacros.h" +#include "sbkpython.h" + +extern "C" +{ + +LIBSHIBOKEN_API int InitSignatureStrings(PyTypeObject *, const char *[]); +LIBSHIBOKEN_API void FinishSignatureInitialization(PyObject *, const char *[]); +LIBSHIBOKEN_API void SetError_Argument(PyObject *, const char *, PyObject *); +LIBSHIBOKEN_API PyObject *Sbk_TypeGet___doc__(PyObject *); +LIBSHIBOKEN_API PyObject *GetFeatureDict(); + +} // extern "C" + +#endif // SIGNATURE_H diff --git a/sources/shiboken6/libshiboken/signature/signature.cpp b/sources/shiboken6/libshiboken/signature/signature.cpp new file mode 100644 index 000000000..3255cb56d --- /dev/null +++ b/sources/shiboken6/libshiboken/signature/signature.cpp @@ -0,0 +1,640 @@ +// Copyright (C) 2020 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 + +//////////////////////////////////////////////////////////////////////////// +// +// signature.cpp +// ------------- +// +// This is the main file of the signature module. +// It contains the most important functions and avoids confusion +// by moving many helper functions elsewhere. +// +// General documentation can be found in `signature_doc.rst`. +// + +#include "signature.h" +#include "signature_p.h" + +#include "basewrapper.h" +#include "autodecref.h" +#include "sbkstring.h" +#include "sbkstaticstrings.h" +#include "sbkstaticstrings_p.h" +#include "sbkfeature_base.h" + +#include <structmember.h> + +using namespace Shiboken; + +extern "C" +{ + +static PyObject *CreateSignature(PyObject *props, PyObject *key) +{ + /* + * Here is the new function to create all signatures. It simply calls + * into Python and creates a signature object directly. + * This is so much simpler than using all the attributes explicitly + * to support '_signature_is_functionlike()'. + */ + return PyObject_CallFunction(pyside_globals->create_signature_func, + "(OO)", props, key); +} + +PyObject *GetClassOrModOf(PyObject *ob) +{ + /* + * Return the type or module of a function or type. + * The purpose is finally to use the name of the object. + */ + if (PyType_Check(ob)) { + // PySide-928: The type case must do refcounting like the others as well. + Py_INCREF(ob); + return ob; + } +#ifdef PYPY_VERSION + // PYSIDE-535: PyPy has a special builtin method that acts almost like PyCFunction. + if (Py_TYPE(ob) == PepBuiltinMethod_TypePtr) + return _get_class_of_bm(ob); +#endif + if (PyType_IsSubtype(Py_TYPE(ob), &PyCFunction_Type)) + return _get_class_of_cf(ob); + if (Py_TYPE(ob) == PepStaticMethod_TypePtr) + return _get_class_of_sm(ob); + if (Py_TYPE(ob) == PepMethodDescr_TypePtr) + return _get_class_of_descr(ob); + if (Py_TYPE(ob) == &PyWrapperDescr_Type) + return _get_class_of_descr(ob); + Py_FatalError("unexpected type in GetClassOrModOf"); + return nullptr; +} + +PyObject *GetTypeKey(PyObject *ob) +{ + assert(PyType_Check(ob) || PyModule_Check(ob)); + /* + * Obtain a unique key using the module name and the type name. + * + * PYSIDE-1286: We use correct __module__ and __qualname__, now. + */ + AutoDecRef module_name(PyObject_GetAttr(ob, PyMagicName::module())); + if (module_name.isNull()) { + // We have no module_name because this is a module ;-) + PyErr_Clear(); + module_name.reset(PyObject_GetAttr(ob, PyMagicName::name())); + return Py_BuildValue("O", module_name.object()); + } + AutoDecRef class_name(PyObject_GetAttr(ob, PyMagicName::qualname())); + if (class_name.isNull()) { + Py_FatalError("Signature: missing class name in GetTypeKey"); + return nullptr; + } + return Py_BuildValue("(OO)", module_name.object(), class_name.object()); +} + +static PyObject *empty_dict = nullptr; + +PyObject *TypeKey_to_PropsDict(PyObject *type_key) +{ + PyObject *dict = PyDict_GetItem(pyside_globals->arg_dict, type_key); + if (dict == nullptr) { + if (empty_dict == nullptr) + empty_dict = PyDict_New(); + dict = empty_dict; + } + if (!PyDict_Check(dict)) + dict = PySide_BuildSignatureProps(type_key); + return dict; +} + +static PyObject *_GetSignature_Cached(PyObject *props, PyObject *func_kind, PyObject *modifier) +{ + // Special case: We want to know the func_kind. + if (modifier) { + PyUnicode_InternInPlace(&modifier); + if (modifier == PyMagicName::func_kind()) + return Py_BuildValue("O", func_kind); + } + + AutoDecRef key(modifier == nullptr ? Py_BuildValue("O", func_kind) + : Py_BuildValue("(OO)", func_kind, modifier)); + PyObject *value = PyDict_GetItem(props, key); + if (value == nullptr) { + // we need to compute a signature object + value = CreateSignature(props, key); + if (value != nullptr) { + if (PyDict_SetItem(props, key, value) < 0) + // this is an error + return nullptr; + } + else { + // key not found + Py_RETURN_NONE; + } + } + return Py_INCREF(value), value; +} + +#ifdef PYPY_VERSION +PyObject *GetSignature_Method(PyObject *obfunc, PyObject *modifier) +{ + AutoDecRef obtype_mod(GetClassOrModOf(obfunc)); + AutoDecRef type_key(GetTypeKey(obtype_mod)); + if (type_key.isNull()) + Py_RETURN_NONE; + PyObject *dict = TypeKey_to_PropsDict(type_key); + if (dict == nullptr) + return nullptr; + AutoDecRef func_name(PyObject_GetAttr(obfunc, PyMagicName::name())); + PyObject *props = !func_name.isNull() ? PyDict_GetItem(dict, func_name) : nullptr; + if (props == nullptr) + Py_RETURN_NONE; + return _GetSignature_Cached(props, PyName::method(), modifier); +} +#endif + +PyObject *GetSignature_Function(PyObject *obfunc, PyObject *modifier) +{ + // make sure that we look into PyCFunction, only... + if (Py_TYPE(obfunc) == PepFunction_TypePtr) + Py_RETURN_NONE; + AutoDecRef obtype_mod(GetClassOrModOf(obfunc)); + AutoDecRef type_key(GetTypeKey(obtype_mod)); + if (type_key.isNull()) + Py_RETURN_NONE; + PyObject *dict = TypeKey_to_PropsDict(type_key); + if (dict == nullptr) + return nullptr; + AutoDecRef func_name(PyObject_GetAttr(obfunc, PyMagicName::name())); + PyObject *props = !func_name.isNull() ? PyDict_GetItem(dict, func_name) : nullptr; + if (props == nullptr) + Py_RETURN_NONE; + + int flags = PyCFunction_GET_FLAGS(obfunc); + PyObject *func_kind; + if (PyModule_Check(obtype_mod.object())) + func_kind = PyName::function(); + else if (flags & METH_CLASS) + func_kind = PyName::classmethod(); + else if (flags & METH_STATIC) + func_kind = PyName::staticmethod(); + else + func_kind = PyName::method(); + return _GetSignature_Cached(props, func_kind, modifier); +} + +PyObject *GetSignature_Wrapper(PyObject *ob, PyObject *modifier) +{ + AutoDecRef func_name(PyObject_GetAttr(ob, PyMagicName::name())); + AutoDecRef objclass(PyObject_GetAttr(ob, PyMagicName::objclass())); + AutoDecRef class_key(GetTypeKey(objclass)); + if (func_name.isNull() || objclass.isNull() || class_key.isNull()) + return nullptr; + PyObject *dict = TypeKey_to_PropsDict(class_key); + if (dict == nullptr) + return nullptr; + PyObject *props = PyDict_GetItem(dict, func_name); + if (props == nullptr) { + // handle `__init__` like the class itself + if (PyUnicode_CompareWithASCIIString(func_name, "__init__") == 0) + return GetSignature_TypeMod(objclass, modifier); + Py_RETURN_NONE; + } + return _GetSignature_Cached(props, PyName::method(), modifier); +} + +PyObject *GetSignature_TypeMod(PyObject *ob, PyObject *modifier) +{ + AutoDecRef ob_name(PyObject_GetAttr(ob, PyMagicName::name())); + AutoDecRef ob_key(GetTypeKey(ob)); + + PyObject *dict = TypeKey_to_PropsDict(ob_key); + if (dict == nullptr) + return nullptr; + PyObject *props = PyDict_GetItem(dict, ob_name); + if (props == nullptr) + Py_RETURN_NONE; + return _GetSignature_Cached(props, PyName::method(), modifier); +} + +//////////////////////////////////////////////////////////////////////////// +// +// get_signature -- providing a superior interface +// +// Additional to the interface via `__signature__`, we also provide +// a general function, which allows for different signature layouts. +// The `modifier` argument is a string that is passed in from `loader.py`. +// Configuration what the modifiers mean is completely in Python. +// +// PYSIDE-2101: The __signature__ attribute is gone due to rlcompleter. +// + +PyObject *get_signature_intern(PyObject *ob, PyObject *modifier) +{ +#ifdef PYPY_VERSION + // PYSIDE-535: PyPy has a special builtin method that acts almost like PyCFunction. + if (Py_TYPE(ob) == PepBuiltinMethod_TypePtr) { + return pyside_bm_get___signature__(ob, modifier); + } +#endif + if (PyType_IsSubtype(Py_TYPE(ob), &PyCFunction_Type)) + return pyside_cf_get___signature__(ob, modifier); + if (Py_TYPE(ob) == PepStaticMethod_TypePtr) + return pyside_sm_get___signature__(ob, modifier); + if (Py_TYPE(ob) == PepMethodDescr_TypePtr) + return pyside_md_get___signature__(ob, modifier); + if (PyType_Check(ob)) + return pyside_tp_get___signature__(ob, modifier); + if (Py_TYPE(ob) == &PyWrapperDescr_Type) + return pyside_wd_get___signature__(ob, modifier); + // For classmethods we use the simple wrapper description implementation. + if (Py_TYPE(ob) == &PyClassMethodDescr_Type) + return pyside_wd_get___signature__(ob, modifier); + return nullptr; +} + +static PyObject *get_signature(PyObject * /* self */, PyObject *args) +{ + PyObject *ob; + PyObject *modifier = nullptr; + + if (!PyArg_ParseTuple(args, "O|O", &ob, &modifier)) + return nullptr; + if (Py_TYPE(ob) == PepFunction_TypePtr) + Py_RETURN_NONE; + PyObject *ret = get_signature_intern(ob, modifier); + if (ret != nullptr) + return ret; + Py_RETURN_NONE; +} + +//////////////////////////////////////////////////////////////////////////// +// +// feature_import -- special handling for `from __feature__ import ...` +// +// The actual function is implemented in Python. +// When no features are involved, we redirect to the original import. +// This avoids an extra function level in tracebacks that is irritating. +// + +static PyObject *feature_import(PyObject * /* self */, PyObject *args, PyObject *kwds) +{ + PyObject *ret = PyObject_Call(pyside_globals->feature_import_func, args, kwds); + if (ret != Py_None) + return ret; + // feature_import did not handle it, so call the normal import. + Py_DECREF(ret); + static PyObject *builtins = PyEval_GetBuiltins(); + PyObject *import_func = PyDict_GetItemString(builtins, "__orig_import__"); + if (import_func == nullptr) { + Py_FatalError("builtins has no \"__orig_import__\" function"); + } + 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[] = { + {"__feature_import__", (PyCFunction)feature_import, METH_VARARGS | METH_KEYWORDS, nullptr}, + {"get_signature", (PyCFunction)get_signature, METH_VARARGS, + "get the signature, passing an optional string parameter"}, + {nullptr, nullptr, 0, nullptr} +}; + +//////////////////////////////////////////////////////////////////////////// +// +// Argument Handling +// ----------------- +// +// * PySide_BuildSignatureArgs +// +// Called during class or module initialization. +// The signature strings from the C modules are stored in a dict for +// later use. +// +// * PySide_BuildSignatureProps +// +// Called on demand during signature retieval. This function calls all the way +// through `parser.py` and prepares all properties for the functions of the class. +// The parsed properties can then be used to create signature objects. +// + +static int PySide_BuildSignatureArgs(PyObject *obtype_mod, const char *signatures[]) +{ + AutoDecRef type_key(GetTypeKey(obtype_mod)); + /* + * PYSIDE-996: Avoid string overflow in MSVC, which has a limit of + * 2**15 unicode characters (64 K memory). + * Instead of one huge string, we take a ssize_t that is the + * address of a string array. It will not be turned into a real + * string list until really used by Python. This is quite optimal. + */ + AutoDecRef numkey(Py_BuildValue("n", signatures)); + if (type_key.isNull() || numkey.isNull() + || PyDict_SetItem(pyside_globals->arg_dict, type_key, numkey) < 0) + return -1; + /* + * We record also a mapping from type key to type/module. This helps to + * lazily initialize the Py_LIMITED_API in name_key_to_func(). + */ + return PyDict_SetItem(pyside_globals->map_dict, type_key, obtype_mod) == 0 ? 0 : -1; +} + +PyObject *PySide_BuildSignatureProps(PyObject *type_key) +{ + /* + * Here is the second part of the function. + * This part will be called on-demand when needed by some attribute. + * We simply pick up the arguments that we stored here and replace + * them by the function result. + */ + if (type_key == nullptr) + return nullptr; + PyObject *numkey = PyDict_GetItem(pyside_globals->arg_dict, type_key); + AutoDecRef strings(_address_to_stringlist(numkey)); + if (strings.isNull()) + return nullptr; + AutoDecRef arg_tup(Py_BuildValue("(OO)", type_key, strings.object())); + if (arg_tup.isNull()) + return nullptr; + PyObject *dict = PyObject_CallObject(pyside_globals->pyside_type_init_func, arg_tup); + if (dict == nullptr) { + if (PyErr_Occurred()) + return nullptr; + // No error: return an empty dict. + if (empty_dict == nullptr) + empty_dict = PyDict_New(); + return empty_dict; + } + // PYSIDE-1019: Build snake case versions of the functions. + if (insert_snake_case_variants(dict) < 0) + return nullptr; + // We replace the arguments by the result dict. + if (PyDict_SetItem(pyside_globals->arg_dict, type_key, dict) < 0) + return nullptr; + return dict; +} +// +//////////////////////////////////////////////////////////////////////////// + +#ifdef PYPY_VERSION +static bool get_lldebug_flag() +{ + auto *dic = PySys_GetObject("pypy_translation_info"); + int lldebug = PyObject_IsTrue(PyDict_GetItemString(dic, "translation.lldebug")); + int lldebug0 = PyObject_IsTrue(PyDict_GetItemString(dic, "translation.lldebug0")); + return lldebug || lldebug0; +} + +#endif + +static int PySide_FinishSignatures(PyObject *module, const char *signatures[]) +{ +#ifdef PYPY_VERSION + static const bool have_problem = get_lldebug_flag(); + if (have_problem) + return 0; // crash with lldebug at `PyDict_Next` +#endif + /* + * Initialization of module functions and resolving of static methods. + */ + const char *name = PyModule_GetName(module); + if (name == nullptr) + return -1; + + // we abuse the call for types, since they both have a __name__ attribute. + if (PySide_BuildSignatureArgs(module, signatures) < 0) + return -1; + + /* + * Note: This function crashed when called from PySide_BuildSignatureArgs. + * Probably this was an import timing problem. + * + * Pep384: We need to switch this always on since we have no access + * to the PyCFunction attributes. Therefore I simplified things + * and always use our own mapping. + */ + PyObject *key, *func, *obdict = PyModule_GetDict(module); + Py_ssize_t pos = 0; + + while (PyDict_Next(obdict, &pos, &key, &func)) + if (PyCFunction_Check(func)) + if (PyDict_SetItem(pyside_globals->map_dict, func, module) < 0) + return -1; + // The finish_import function will not work the first time since phase 2 + // was not yet run. But that is ok, because the first import is always for + // the shiboken module (or a test module). + if (pyside_globals->finish_import_func == nullptr) { + assert(strncmp(name, "PySide6.", 8) != 0); + return 0; + } + AutoDecRef ret(PyObject_CallFunction( + pyside_globals->finish_import_func, "(O)", module)); + return ret.isNull() ? -1 : 0; +} + +//////////////////////////////////////////////////////////////////////////// +// +// External functions interface +// +// These are exactly the supported functions from `signature.h`. +// + +int InitSignatureStrings(PyTypeObject *type, const char *signatures[]) +{ + // PYSIDE-2404: This function now also builds the mapping for static methods. + // It was one missing spot to let Lazy import work. + init_shibokensupport_module(); + auto *ob_type = reinterpret_cast<PyObject *>(type); + int ret = PySide_BuildSignatureArgs(ob_type, signatures); + if (ret < 0 || _build_func_to_type(ob_type) < 0) { + PyErr_Print(); + PyErr_SetNone(PyExc_ImportError); + } + return ret; +} + +void FinishSignatureInitialization(PyObject *module, const char *signatures[]) +{ + /* + * This function is called at the very end of a module initialization. + * We now patch certain types to support the __signature__ attribute, + * initialize module functions and resolve static methods. + * + * Still, it is not possible to call init phase 2 from here, + * because the import is still running. Do it from Python! + */ + init_shibokensupport_module(); + +#ifndef PYPY_VERSION + static const bool patch_types = true; +#else + // PYSIDE-535: On PyPy we cannot patch builtin types. This can be + // re-implemented later. For now, we use `get_signature`, instead. + static const bool patch_types = false; +#endif + + if ((patch_types && PySide_PatchTypes() < 0) + || PySide_FinishSignatures(module, signatures) < 0) { + PyErr_Print(); + PyErr_SetNone(PyExc_ImportError); + } +} + +static PyObject *adjustFuncName(const char *func_name) +{ + /* + * PYSIDE-1019: Modify the function name expression according to feature. + * + * - snake_case + * The function name must be converted. + * - full_property + * The property name must be used and "fset" appended. + * + * modname.subname.classsname.propname.fset + * + * Class properties must use the expression + * + * modname.subname.classsname.__dict__['propname'].fset + * + * Note that fget is impossible because there are no parameters. + */ + static const char mapping_name[] = "shibokensupport.signature.mapping"; + static PyObject *sys_modules = PySys_GetObject("modules"); + static PyObject *mapping = PyDict_GetItemString(sys_modules, mapping_name); + static PyObject *ns = PyModule_GetDict(mapping); + + char _path[200 + 1] = {}; + const char *_name = strrchr(func_name, '.'); + strncat(_path, func_name, _name - func_name); + ++_name; + + // This is a very cheap call into `mapping.py`. + PyObject *update_mapping = PyDict_GetItemString(ns, "update_mapping"); + AutoDecRef res(PyObject_CallFunctionObjArgs(update_mapping, nullptr)); + if (res.isNull()) + return nullptr; + + // Run `eval` on the type string to get the object. + // PYSIDE-1710: If the eval does not work, return the given string. + AutoDecRef obtype(PyRun_String(_path, Py_eval_input, ns, ns)); + if (obtype.isNull()) + return String::fromCString(func_name); + + if (PyModule_Check(obtype.object())) { + // This is a plain function. Return the unmangled name. + return String::fromCString(func_name); + } + assert(PyType_Check(obtype)); // This was not true for __init__! + + // Find the feature flags + auto type = reinterpret_cast<PyTypeObject *>(obtype.object()); + AutoDecRef dict(PepType_GetDict(type)); + int id = currentSelectId(type); + id = id < 0 ? 0 : id; // if undefined, set to zero + auto lower = id & 0x01; + auto is_prop = id & 0x02; + bool is_class_prop = false; + + // Compute all needed info. + PyObject *name = String::getSnakeCaseName(_name, lower); + PyObject *prop_name{}; + if (is_prop) { + PyObject *prop_methods = PyDict_GetItem(dict, PyMagicName::property_methods()); + prop_name = PyDict_GetItem(prop_methods, name); + if (prop_name != nullptr) { + PyObject *prop = PyDict_GetItem(dict, prop_name); + is_class_prop = Py_TYPE(prop) != &PyProperty_Type; + } + } + + // Finally, generate the correct path expression. + char _buf[250 + 1] = {}; + if (prop_name) { + auto _prop_name = String::toCString(prop_name); + if (is_class_prop) + snprintf(_buf, sizeof(_buf), "%s.__dict__['%s'].fset", _path, _prop_name); + else + snprintf(_buf, sizeof(_buf), "%s.%s.fset", _path, _prop_name); + } + else { + auto _name = String::toCString(name); + snprintf(_buf, sizeof(_buf), "%s.%s", _path, _name); + } + return String::fromCString(_buf); +} + +void SetError_Argument(PyObject *args, const char *func_name, PyObject *info) +{ + init_shibokensupport_module(); + /* + * This function replaces the type error construction with extra + * overloads parameter in favor of using the signature module. + * Error messages are rare, so we do it completely in Python. + */ + + // PYSIDE-1305: Handle errors set by fillQtProperties. + if (PyErr_Occurred()) { + PyObject *e, *v, *t; + // Note: These references are all borrowed. + PyErr_Fetch(&e, &v, &t); + Py_DECREF(e); + info = v; + Py_XDECREF(t); + } + // PYSIDE-1019: Modify the function name expression according to feature. + AutoDecRef new_func_name(adjustFuncName(func_name)); + if (new_func_name.isNull()) { + PyErr_Print(); + Py_FatalError("seterror_argument failed to call update_mapping"); + } + if (info == nullptr) + info = Py_None; + AutoDecRef res(PyObject_CallFunctionObjArgs(pyside_globals->seterror_argument_func, + args, new_func_name.object(), info, nullptr)); + if (res.isNull()) { + PyErr_Print(); + Py_FatalError("seterror_argument did not receive a result"); + } + PyObject *err, *msg; + if (!PyArg_UnpackTuple(res, func_name, 2, 2, &err, &msg)) { + PyErr_Print(); + Py_FatalError("unexpected failure in seterror_argument"); + } + PyErr_SetObject(err, msg); +} + +/* + * Support for the metatype SbkObjectType_Type's tp_getset. + * + * This was not necessary for __signature__, because PyType_Type inherited it. + * But the __doc__ attribute existed already by inheritance, and calling + * PyType_Modified() is not supported. So we added the getsets explicitly + * to the metatype. + * + * PYSIDE-2101: The __signature__ attribute is gone due to rlcompleter. + */ + +PyObject *Sbk_TypeGet___doc__(PyObject *ob) +{ + init_shibokensupport_module(); + return pyside_tp_get___doc__(ob); +} + +PyObject *GetFeatureDict() +{ + init_shibokensupport_module(); + return pyside_globals->feature_dict; +} + +} //extern "C" diff --git a/sources/shiboken6/libshiboken/signature/signature_extend.cpp b/sources/shiboken6/libshiboken/signature/signature_extend.cpp new file mode 100644 index 000000000..7292f8216 --- /dev/null +++ b/sources/shiboken6/libshiboken/signature/signature_extend.cpp @@ -0,0 +1,230 @@ +// Copyright (C) 2020 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 + +//////////////////////////////////////////////////////////////////////////// +// +// signature_extend.cpp +// -------------------- +// +// This file contains the additions and changes to the following +// Python types: +// +// PyMethodDescr_Type +// PyCFunction_Type +// PyStaticMethod_Type +// (*) PyType_Type +// PyWrapperDescr_Type +// +// Their `tp_getset` fields are modified to support the `__signature__` +// attribute and additions to the `__doc__` attribute. +// +// PYSIDE-535: PyType_Type patching is removed, +// Shiboken.ObjectType and Shiboken.EnumMeta have new getsets, instead. + +#include "autodecref.h" +#include "sbkstring.h" +#include "sbkstaticstrings.h" +#include "sbkstaticstrings_p.h" + +#include "signature_p.h" + +using namespace Shiboken; + +extern "C" { + +using signaturefunc = PyObject *(*)(PyObject *, PyObject *); + +static PyObject *_get_written_signature(signaturefunc sf, PyObject *ob, PyObject *modifier) +{ + /* + * Be a writable Attribute, but have a computed value. + * + * If a signature has not been written, call the signature function. + * If it has been written, return the written value. + * After __del__ was called, the function value re-appears. + * + * Note: This serves also for the new version that does not allow any + * assignment if we have a computed value. We only need to check if + * a computed value exists and then forbid writing. + * See pyside_set___signature + */ + PyObject *ret = PyDict_GetItem(pyside_globals->value_dict, ob); + if (ret == nullptr) + return ob == nullptr ? nullptr : sf(ob, modifier); + Py_INCREF(ret); + return ret; +} + +#ifdef PYPY_VERSION +PyObject *pyside_bm_get___signature__(PyObject *func, PyObject *modifier) +{ + return _get_written_signature(GetSignature_Method, func, modifier); +} +#endif + +PyObject *pyside_cf_get___signature__(PyObject *func, PyObject *modifier) +{ + return _get_written_signature(GetSignature_Function, func, modifier); +} + +PyObject *pyside_sm_get___signature__(PyObject *sm, PyObject *modifier) +{ + AutoDecRef func(PyObject_GetAttr(sm, PyMagicName::func())); + return _get_written_signature(GetSignature_Function, func, modifier); +} + +PyObject *pyside_md_get___signature__(PyObject *ob_md, PyObject *modifier) +{ + AutoDecRef func(name_key_to_func(ob_md)); + if (func.object() == Py_None) + Py_RETURN_NONE; + if (func.isNull()) + Py_FatalError("missing mapping in MethodDescriptor"); + return pyside_cf_get___signature__(func, modifier); +} + +PyObject *pyside_wd_get___signature__(PyObject *ob, PyObject *modifier) +{ + return _get_written_signature(GetSignature_Wrapper, ob, modifier); +} + +PyObject *pyside_tp_get___signature__(PyObject *obtype_mod, PyObject *modifier) +{ + return _get_written_signature(GetSignature_TypeMod, obtype_mod, modifier); +} + +//////////////////////////////////////////////////////////////////////////// +// +// Augmenting builtin types with a __signature__ attribute. +// +// This is a harmless change to Python, similar like __text_signature__. +// We could avoid it, but then we would need to copy quite some module +// initialization functions which are pretty version- and word size +// dependent. I think this little patch is the lesser of the two evils. +// +// Please note that in fact we are modifying 'type', the metaclass of all +// objects, because we add new functionality. +// +// Addendum 2019-01-12: We now also compute a docstring from the signature. +// + +// keep the original __doc__ functions +static PyObject *old_cf_doc_descr = nullptr; +static PyObject *old_sm_doc_descr = nullptr; +static PyObject *old_md_doc_descr = nullptr; +static PyObject *old_tp_doc_descr = nullptr; +static PyObject *old_wd_doc_descr = nullptr; + +static int handle_doc_in_progress = 0; + +static PyObject *handle_doc(PyObject *ob, PyObject *old_descr) +{ + AutoDecRef ob_type_mod(GetClassOrModOf(ob)); + const char *name; + bool isModule = PyModule_Check(ob_type_mod.object()); + if (isModule) + name = PyModule_GetName(ob_type_mod.object()); + else + name = reinterpret_cast<PyTypeObject *>(ob_type_mod.object())->tp_name; + PyObject *res{}; + + if (handle_doc_in_progress || name == nullptr + || (isModule && strncmp(name, "PySide6.", 8) != 0)) { + res = PyObject_CallMethodObjArgs(old_descr, PyMagicName::get(), ob, nullptr); + } else { + handle_doc_in_progress++; + res = PyObject_CallFunction(pyside_globals->make_helptext_func, "(O)", ob); + handle_doc_in_progress--; + } + + if (res) + return res; + + PyErr_Clear(); + Py_RETURN_NONE; +} + +static PyObject *pyside_cf_get___doc__(PyObject *cf) +{ + return handle_doc(cf, old_cf_doc_descr); +} + +static PyObject *pyside_sm_get___doc__(PyObject *sm) +{ + return handle_doc(sm, old_sm_doc_descr); +} + +static PyObject *pyside_md_get___doc__(PyObject *md) +{ + return handle_doc(md, old_md_doc_descr); +} + +PyObject *pyside_tp_get___doc__(PyObject *tp) +{ + return handle_doc(tp, old_tp_doc_descr); +} + +static PyObject *pyside_wd_get___doc__(PyObject *wd) +{ + return handle_doc(wd, old_wd_doc_descr); +} + +// PYSIDE-535: We cannot patch types easily in PyPy. +// Let's use the `get_signature` function, instead. +static PyGetSetDef new_PyCFunction_getsets[] = { + {const_cast<char *>("__doc__"), reinterpret_cast<getter>(pyside_cf_get___doc__), + nullptr, nullptr, nullptr}, + {nullptr, nullptr, nullptr, nullptr, nullptr} +}; + +static PyGetSetDef new_PyStaticMethod_getsets[] = { + {const_cast<char *>("__doc__"), reinterpret_cast<getter>(pyside_sm_get___doc__), + nullptr, nullptr, nullptr}, + {nullptr, nullptr, nullptr, nullptr, nullptr} +}; + +static PyGetSetDef new_PyMethodDescr_getsets[] = { + {const_cast<char *>("__doc__"), reinterpret_cast<getter>(pyside_md_get___doc__), + nullptr, nullptr, nullptr}, + {nullptr, nullptr, nullptr, nullptr, nullptr} +}; + +static PyGetSetDef new_PyWrapperDescr_getsets[] = { + {const_cast<char *>("__doc__"), reinterpret_cast<getter>(pyside_wd_get___doc__), + nullptr, nullptr, nullptr}, + {nullptr, nullptr, nullptr, nullptr, nullptr} +}; + +int PySide_PatchTypes(void) +{ + static int init_done = 0; + + if (!init_done) { + AutoDecRef meth_descr(PyObject_GetAttrString( + reinterpret_cast<PyObject *>(&PyUnicode_Type), "split")); + AutoDecRef wrap_descr(PyObject_GetAttrString( + reinterpret_cast<PyObject *>(Py_TYPE(Py_True)), "__add__")); + // abbreviations for readability + auto md_gs = new_PyMethodDescr_getsets; + auto md_doc = &old_md_doc_descr; + auto cf_gs = new_PyCFunction_getsets; + auto cf_doc = &old_cf_doc_descr; + auto sm_gs = new_PyStaticMethod_getsets; + auto sm_doc = &old_sm_doc_descr; + auto wd_gs = new_PyWrapperDescr_getsets; + auto wd_doc = &old_wd_doc_descr; + + if (meth_descr.isNull() || wrap_descr.isNull() + || PyType_Ready(Py_TYPE(meth_descr)) < 0 + || add_more_getsets(PepMethodDescr_TypePtr, md_gs, md_doc) < 0 + || add_more_getsets(&PyCFunction_Type, cf_gs, cf_doc) < 0 + || add_more_getsets(PepStaticMethod_TypePtr, sm_gs, sm_doc) < 0 + || add_more_getsets(Py_TYPE(wrap_descr), wd_gs, wd_doc) < 0 + ) + return -1; + init_done = 1; + } + return 0; +} + +} // extern "C" diff --git a/sources/shiboken6/libshiboken/signature/signature_globals.cpp b/sources/shiboken6/libshiboken/signature/signature_globals.cpp new file mode 100644 index 000000000..3a79a12d5 --- /dev/null +++ b/sources/shiboken6/libshiboken/signature/signature_globals.cpp @@ -0,0 +1,264 @@ +// Copyright (C) 2020 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 + +//////////////////////////////////////////////////////////////////////////// +// +// signature_global.cpp +// +// This file contains the global data structures and init code. +// + +#include "autodecref.h" +#include "sbkstring.h" +#include "sbkstaticstrings.h" +#include "sbkstaticstrings_p.h" +#include "sbkenum.h" + +#include "signature_p.h" + +using namespace Shiboken; + +extern "C" { + +static const char *PySide_CompressedSignaturePackage[] = { +#include "embed/signature_inc.h" + }; + +static const unsigned char PySide_SignatureLoader[] = { +#include "embed/signature_bootstrap_inc.h" + }; + +static safe_globals_struc *init_phase_1() +{ + do { + auto *p = reinterpret_cast<safe_globals_struc *> + (malloc(sizeof(safe_globals_struc))); + if (p == nullptr) + break; + /* + * Initializing module signature_bootstrap. + * Since we now have an embedding script, we can do this without any + * Python strings in the C code. + */ +#if defined(Py_LIMITED_API) || defined(SHIBOKEN_NO_EMBEDDING_PYC) + // We must work for multiple versions or we are cross-building for a different + // Python version interpreter, so use source code. +#else + AutoDecRef marshal_module(PyImport_Import(PyName::marshal())); // builtin + AutoDecRef loads(PyObject_GetAttr(marshal_module, PyName::loads())); + if (loads.isNull()) + break; +#endif + char *bytes_cast = reinterpret_cast<char *>( + const_cast<unsigned char *>(PySide_SignatureLoader)); + AutoDecRef bytes(PyBytes_FromStringAndSize(bytes_cast, sizeof(PySide_SignatureLoader))); + if (bytes.isNull()) + break; +#if defined(Py_LIMITED_API) || defined(SHIBOKEN_NO_EMBEDDING_PYC) + PyObject *builtins = PyEval_GetBuiltins(); + PyObject *compile = PyDict_GetItem(builtins, PyName::compile()); + if (compile == nullptr) + break; + AutoDecRef code_obj(PyObject_CallFunction(compile, "Oss", + bytes.object(), "signature_bootstrap.py", "exec")); +#else + AutoDecRef code_obj(PyObject_CallFunctionObjArgs( + loads, bytes.object(), nullptr)); +#endif + if (code_obj.isNull()) + break; + p->helper_module = PyImport_ExecCodeModule("signature_bootstrap", code_obj); + if (p->helper_module == nullptr) + break; + // Initialize the module + PyObject *mdict = PyModule_GetDict(p->helper_module); + if (PyDict_SetItem(mdict, PyMagicName::builtins(), PyEval_GetBuiltins()) < 0) + break; + + /********************************************************************* + * + * Attention! + * ---------- + * + * We are unpacking an embedded ZIP file with more signature modules. + * They will be loaded later with the zipimporter. + * The file `signature_bootstrap.py` does the unpacking and starts the + * loader. See `init_phase_2`. + * + * Due to MSVC's limitation to 64k strings, we needed to assemble pieces. + */ + auto **block_ptr = reinterpret_cast<const char **>(PySide_CompressedSignaturePackage); + PyObject *piece{}; + AutoDecRef zipped_string_sequence(PyList_New(0)); + for (; **block_ptr != 0; ++block_ptr) { + // we avoid the string/unicode dilemma by not using PyString_XXX: + piece = Py_BuildValue("s", *block_ptr); + if (piece == nullptr || PyList_Append(zipped_string_sequence, piece) < 0) + break; + } + if (PyDict_SetItemString(mdict, "zipstring_sequence", zipped_string_sequence) < 0) + break; + + // build a dict for diverse mappings + p->map_dict = PyDict_New(); + + // build a dict for the prepared arguments + p->arg_dict = PyDict_New(); + if (PyObject_SetAttrString(p->helper_module, "pyside_arg_dict", p->arg_dict) < 0) + break; + + // build a dict for assigned signature values + p->value_dict = PyDict_New(); + + // PYSIDE-1019: build a __feature__ dict + p->feature_dict = PyDict_New(); + if (PyObject_SetAttrString(p->helper_module, "pyside_feature_dict", p->feature_dict) < 0) + break; + + // This function will be disabled until phase 2 is done. + p->finish_import_func = nullptr; + + return p; + + } while (0); + + PyErr_Print(); + Py_FatalError("could not initialize part 1"); + return nullptr; +} + +static int init_phase_2(safe_globals_struc *p, PyMethodDef *methods) +{ + do { + PyMethodDef *ml; + + // The single function to be called, but maybe more to come. + for (ml = methods; ml->ml_name != nullptr; ml++) { + PyObject *v = PyCFunction_NewEx(ml, nullptr, nullptr); + if (v == nullptr + || PyObject_SetAttrString(p->helper_module, ml->ml_name, v) != 0) + break; + Py_DECREF(v); + } + // The first entry is __feature_import__, add documentation. + PyObject *builtins = PyEval_GetBuiltins(); + PyObject *imp_func = PyDict_GetItemString(builtins, "__import__"); + PyObject *imp_doc = PyObject_GetAttrString(imp_func, "__doc__"); + signature_methods[0].ml_doc = String::toCString(imp_doc); + + PyObject *bootstrap_func = PyObject_GetAttrString(p->helper_module, "bootstrap"); + if (bootstrap_func == nullptr) + break; + + /********************************************************************* + * + * Attention! + * ---------- + * + * This is the entry point where everything in folder + * `shibokensupport` becomes initialized. It starts with + * `signature_bootstrap.py` and continues from there to `loader.py`. + * + * The return value of the bootstrap function is the loader module. + */ + PyObject *loader = PyObject_CallFunctionObjArgs(bootstrap_func, nullptr); + if (loader == nullptr) + break; + + // now the loader should be initialized + p->pyside_type_init_func = PyObject_GetAttrString(loader, "pyside_type_init"); + if (p->pyside_type_init_func == nullptr) + break; + p->create_signature_func = PyObject_GetAttrString(loader, "create_signature"); + if (p->create_signature_func == nullptr) + break; + p->seterror_argument_func = PyObject_GetAttrString(loader, "seterror_argument"); + if (p->seterror_argument_func == nullptr) + break; + p->make_helptext_func = PyObject_GetAttrString(loader, "make_helptext"); + if (p->make_helptext_func == nullptr) + break; + p->finish_import_func = PyObject_GetAttrString(loader, "finish_import"); + if (p->finish_import_func == nullptr) + break; + 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; + + // We call stuff like the feature initialization late, + // after all the function pointers are in place. + PyObject *post_init_func = PyObject_GetAttrString(loader, "post_init"); + if (post_init_func == nullptr) + break; + PyObject *ret = PyObject_CallFunctionObjArgs(post_init_func, nullptr); + if (ret == nullptr) + break; + + return 0; + + } while (0); + + PyErr_Print(); + Py_FatalError("could not initialize part 2"); + return -1; +} + +#ifndef _WIN32 +//////////////////////////////////////////////////////////////////////////// +// a stack trace for linux-like platforms +#include <cstdio> +#if defined(__GLIBC__) +# include <execinfo.h> +#endif +#include <signal.h> +#include <cstdlib> +#include <unistd.h> + +static void handler(int sig) { +#if defined(__GLIBC__) + void *array[30]; + size_t size; + + // get void *'s for all entries on the stack + size = backtrace(array, 30); + + // print out all the frames to stderr +#endif + std::fprintf(stderr, "Error: signal %d:\n", sig); +#if defined(__GLIBC__) + backtrace_symbols_fd(array, size, STDERR_FILENO); +#endif + exit(1); +} + +//////////////////////////////////////////////////////////////////////////// +#endif // _WIN32 + +safe_globals_struc *pyside_globals = nullptr; + +void init_shibokensupport_module(void) +{ + static int init_done = 0; + + if (!init_done) { + pyside_globals = init_phase_1(); + if (pyside_globals != nullptr) + init_done = 1; + +#ifndef _WIN32 + // We enable the stack trace in CI, only. + const char *testEnv = getenv("QTEST_ENVIRONMENT"); + if (testEnv && strstr(testEnv, "ci")) + signal(SIGSEGV, handler); // install our handler +#endif // _WIN32 + + init_phase_2(pyside_globals, signature_methods); + // Enum must be initialized when signatures exist, not earlier. + init_enum(); + } +} + +} // extern "C" diff --git a/sources/shiboken6/libshiboken/signature/signature_helper.cpp b/sources/shiboken6/libshiboken/signature/signature_helper.cpp new file mode 100644 index 000000000..cf84cfa13 --- /dev/null +++ b/sources/shiboken6/libshiboken/signature/signature_helper.cpp @@ -0,0 +1,389 @@ +// Copyright (C) 2020 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 + +//////////////////////////////////////////////////////////////////////////// +// +// signature_helper.cpp +// -------------------- +// +// This file contains assoerted helper functions that are needed, +// but it is not helpful to see them all the time. +// + +#include "autodecref.h" +#include "sbkstring.h" +#include "sbkstaticstrings.h" +#include "sbkstaticstrings_p.h" + +#include "signature_p.h" + +using namespace Shiboken; + +extern "C" { + +static int _fixup_getset(PyTypeObject *type, const char *name, PyGetSetDef *new_gsp) +{ + /* + * This function pre-fills all fields of the new gsp. We then + * insert the changed values. + */ + PyGetSetDef *gsp = type->tp_getset; + if (gsp != nullptr) { + for (; gsp->name != nullptr; gsp++) { + if (strcmp(gsp->name, name) == 0) { + new_gsp->set = gsp->set; + new_gsp->doc = gsp->doc; + new_gsp->closure = gsp->closure; + return 1; // success + } + } + } + PyMemberDef *md = type->tp_members; + if (md != nullptr) + for (; md->name != nullptr; md++) + if (strcmp(md->name, name) == 0) + return 1; + return 0; +} + +int add_more_getsets(PyTypeObject *type, PyGetSetDef *gsp, PyObject **doc_descr) +{ + /* + * This function is used to assign a new `__signature__` attribute, + * and also to override a `__doc__` or `__name__` attribute. + * + * PYSIDE-2101: The __signature__ attribute is gone due to rlcompleter. + */ + assert(PyType_Check(type)); + PyType_Ready(type); + AutoDecRef tpDict(PepType_GetDict(type)); + auto *dict = tpDict.object(); + for (; gsp->name != nullptr; gsp++) { + PyObject *have_descr = PyDict_GetItemString(dict, gsp->name); + if (have_descr != nullptr) { + Py_INCREF(have_descr); + if (strcmp(gsp->name, "__doc__") == 0) + *doc_descr = have_descr; + else + assert(false); + if (!_fixup_getset(type, gsp->name, gsp)) + continue; + } + AutoDecRef descr(PyDescr_NewGetSet(type, gsp)); + if (descr.isNull()) + return -1; + // PYSIDE-535: We cannot set the attribute. For simplicity, we use + // get_signature in PyPy, instead. This can be re-implemented + // later by deriving extra heap types. + if (PyDict_SetItemString(dict, gsp->name, descr) < 0) + return -1; + } + PyType_Modified(type); + return 0; +} + +static PyObject *get_funcname(PyObject *ob) +{ + PyObject *func = ob; + if (Py_TYPE(ob) == PepStaticMethod_TypePtr) + func = PyObject_GetAttr(ob, PyMagicName::func()); + else + Py_INCREF(func); + PyObject *func_name = PyObject_GetAttr(func, PyMagicName::name()); + Py_DECREF(func); + if (func_name == nullptr) + Py_FatalError("unexpected name problem in compute_name_key"); + return func_name; +} + +static PyObject *compute_name_key(PyObject *ob) +{ + if (PyType_Check(ob)) + return GetTypeKey(ob); + AutoDecRef func_name(get_funcname(ob)); + AutoDecRef type_key(GetTypeKey(GetClassOrModOf(ob))); + return Py_BuildValue("(OO)", type_key.object(), func_name.object()); +} + +static PyObject *_func_with_new_name(PyTypeObject *type, + PyMethodDef *meth, + const char *new_name) +{ + /* + * Create a function with a lower case name. + * Note: This is similar to feature_select's methodWithNewName, + * but does not create a descriptor. + * XXX Maybe we can get rid of this, completely? + */ + auto obtype = reinterpret_cast<PyObject *>(type); + int len = strlen(new_name); + auto name = new char[len + 1]; + strcpy(name, new_name); + auto new_meth = new PyMethodDef; + new_meth->ml_name = name; + new_meth->ml_meth = meth->ml_meth; + new_meth->ml_flags = meth->ml_flags; + new_meth->ml_doc = meth->ml_doc; + return PyCFunction_NewEx(new_meth, obtype, nullptr); +} + +static int build_name_key_to_func(PyObject *obtype) +{ + auto *type = reinterpret_cast<PyTypeObject *>(obtype); + PyMethodDef *meth = type->tp_methods; + + if (meth == nullptr) + return 0; + + AutoDecRef type_key(GetTypeKey(obtype)); + for (; meth->ml_name != nullptr; meth++) { + AutoDecRef func(PyCFunction_NewEx(meth, obtype, nullptr)); + AutoDecRef func_name(get_funcname(func)); + AutoDecRef name_key(Py_BuildValue("(OO)", type_key.object(), func_name.object())); + if (func.isNull() || name_key.isNull() + || PyDict_SetItem(pyside_globals->map_dict, name_key, func) < 0) + return -1; + } + // PYSIDE-1019: Now we repeat the same for snake case names. + meth = type->tp_methods; + for (; meth->ml_name != nullptr; meth++) { + const char *name = String::toCString(String::getSnakeCaseName(meth->ml_name, true)); + AutoDecRef func(_func_with_new_name(type, meth, name)); + AutoDecRef func_name(get_funcname(func)); + AutoDecRef name_key(Py_BuildValue("(OO)", type_key.object(), func_name.object())); + if (func.isNull() || name_key.isNull() + || PyDict_SetItem(pyside_globals->map_dict, name_key, func) < 0) + return -1; + } + return 0; +} + +PyObject *name_key_to_func(PyObject *ob) +{ + /* + * We build a mapping from name_key to function. + * This could also be computed directly, but the Limited API + * makes this impossible. So we always build our own mapping. + */ + AutoDecRef name_key(compute_name_key(ob)); + if (name_key.isNull()) + Py_RETURN_NONE; + + PyObject *ret = PyDict_GetItem(pyside_globals->map_dict, name_key); + if (ret == nullptr) { + // do a lazy initialization + AutoDecRef type_key(GetTypeKey(GetClassOrModOf(ob))); + PyObject *type = PyDict_GetItem(pyside_globals->map_dict, + type_key); + if (type == nullptr) + Py_RETURN_NONE; + assert(PyType_Check(type)); + if (build_name_key_to_func(type) < 0) + return nullptr; + ret = PyDict_GetItem(pyside_globals->map_dict, name_key); + } + Py_XINCREF(ret); + return ret; +} + +static PyObject *_build_new_entry(PyObject *new_name, PyObject *value) +{ + PyObject *new_value = PyDict_Copy(value); + PyObject *multi = PyDict_GetItem(value, PyName::multi()); + if (multi != nullptr && Py_TYPE(multi) == &PyList_Type) { + Py_ssize_t len = PyList_Size(multi); + AutoDecRef list(PyList_New(len)); + if (list.isNull()) + return nullptr; + for (int idx = 0; idx < len; ++idx) { + auto multi_entry = PyList_GetItem(multi, idx); + auto dup = PyDict_Copy(multi_entry); + if (PyDict_SetItem(dup, PyName::name(), new_name) < 0) + return nullptr; + if (PyList_SetItem(list, idx, dup) < 0) + return nullptr; + } + if (PyDict_SetItem(new_value, PyName::multi(), list) < 0) + return nullptr; + } else { + if (PyDict_SetItem(new_value, PyName::name(), new_name) < 0) + return nullptr; + } + return new_value; +} + +int insert_snake_case_variants(PyObject *dict) +{ + AutoDecRef snake_dict(PyDict_New()); + PyObject *key, *value; + Py_ssize_t pos = 0; + while (PyDict_Next(dict, &pos, &key, &value)) { + AutoDecRef name(String::getSnakeCaseName(key, true)); + AutoDecRef new_value(_build_new_entry(name, value)); + if (PyDict_SetItem(snake_dict, name, new_value) < 0) + return -1; + } + return PyDict_Merge(dict, snake_dict, 0); +} + +#ifdef PYPY_VERSION +PyObject *_get_class_of_bm(PyObject *ob_bm) +{ + AutoDecRef self(PyObject_GetAttr(ob_bm, PyMagicName::self())); + auto *klass = PyObject_GetAttr(self, PyMagicName::class_()); + return klass; +} +#endif + +PyObject *_get_class_of_cf(PyObject *ob_cf) +{ + PyObject *selftype = PyCFunction_GET_SELF(ob_cf); + if (selftype == nullptr) { + selftype = PyDict_GetItem(pyside_globals->map_dict, ob_cf); + if (selftype == nullptr) { + // This must be an overloaded function that we handled special. + AutoDecRef special(Py_BuildValue("(OO)", ob_cf, PyName::overload())); + selftype = PyDict_GetItem(pyside_globals->map_dict, special); + if (selftype == nullptr) { + // This is probably a module function. We will return type(None). + selftype = Py_None; + } + } + } + + PyObject *obtype_mod = (PyType_Check(selftype) || PyModule_Check(selftype)) + ? selftype + : reinterpret_cast<PyObject *>(Py_TYPE(selftype)); + Py_INCREF(obtype_mod); + return obtype_mod; +} + +PyObject *_get_class_of_sm(PyObject *ob_sm) +{ + AutoDecRef func(PyObject_GetAttr(ob_sm, PyMagicName::func())); + return _get_class_of_cf(func); +} + +PyObject *_get_class_of_descr(PyObject *ob) +{ + return PyObject_GetAttr(ob, PyMagicName::objclass()); +} + +PyObject *_address_to_stringlist(PyObject *numkey) +{ + /* + * This is a tiny optimization that saves initialization time. + * Instead of creating all Python strings during the call to + * `PySide_BuildSignatureArgs`, we store the address of the stringlist. + * When needed in `PySide_BuildSignatureProps`, the strings are + * finally materialized. + */ + Py_ssize_t address = PyNumber_AsSsize_t(numkey, PyExc_ValueError); + if (address == -1 && PyErr_Occurred()) + return nullptr; + char **sig_strings = reinterpret_cast<char **>(address); + PyObject *res_list = PyList_New(0); + if (res_list == nullptr) + return nullptr; + for (; *sig_strings != nullptr; ++sig_strings) { + char *sig_str = *sig_strings; + AutoDecRef pystr(Py_BuildValue("s", sig_str)); + if (pystr.isNull() || PyList_Append(res_list, pystr) < 0) + return nullptr; + } + return res_list; +} + +int _build_func_to_type(PyObject *obtype) +{ + /* + * There is no general way to directly get the type of a static method. + * On Python 3, the type is hidden in an unused pointer in the + * PyCFunction structure, but the Limited API does not allow to access + * this, either. + * + * In the end, it was easier to avoid such tricks and build an explicit + * mapping from function to type. + * + * We walk through the method list of the type + * and record the mapping from static method to this type in a dict. + * We also check for hidden methods, see below. + */ + auto *type = reinterpret_cast<PyTypeObject *>(obtype); + AutoDecRef tpDict(PepType_GetDict(type)); + auto *dict = tpDict.object(); + + // PYSIDE-2404: Get the original dict for late initialization. + // The dict might have been switched before signature init. + static const auto *pyTypeType_tp_dict = PepType_GetDict(&PyType_Type); + if (Py_TYPE(dict) != Py_TYPE(pyTypeType_tp_dict)) { + tpDict.reset(PyObject_GetAttr(dict, PyName::orig_dict())); + dict = tpDict.object(); + } + + PyMethodDef *meth = type->tp_methods; + + if (meth == nullptr) + return 0; + + for (; meth->ml_name != nullptr; meth++) { + /* + * It is possible that a method is overwritten by another + * attribute with the same name. This case was obviously provoked + * explicitly in "testbinding.TestObject.staticMethodDouble", + * where instead of the method a "PySide6.QtCore.Signal" object + * was in the dict. + * This overlap is also found in regular PySide under + * "PySide6.QtCore.QProcess.error" where again a signal object is + * returned. These hidden methods will be opened for the + * signature module by adding them under the name + * "{name}.overload". + */ + PyObject *descr = PyDict_GetItemString(dict, meth->ml_name); + PyObject *look_attr = meth->ml_flags & METH_STATIC ? PyMagicName::func() + : PyMagicName::name(); + int check_name = meth->ml_flags & METH_STATIC ? 0 : 1; + if (descr == nullptr) + return -1; + + // We first check all methods if one is hidden by something else. + AutoDecRef look(PyObject_GetAttr(descr, look_attr)); + AutoDecRef given(Py_BuildValue("s", meth->ml_name)); + if (look.isNull() + || (check_name && PyObject_RichCompareBool(look, given, Py_EQ) != 1)) { + PyErr_Clear(); + AutoDecRef cfunc(PyCFunction_NewEx( + meth, reinterpret_cast<PyObject *>(type), nullptr)); + if (cfunc.isNull()) + return -1; + if (meth->ml_flags & METH_STATIC) + descr = PyStaticMethod_New(cfunc); + else + descr = PyDescr_NewMethod(type, meth); + if (descr == nullptr) + return -1; + char mangled_name[200]; + strcpy(mangled_name, meth->ml_name); + strcat(mangled_name, ".overload"); + if (PyDict_SetItemString(dict, mangled_name, descr) < 0) + return -1; + if (meth->ml_flags & METH_STATIC) { + // This is the special case where a static method is hidden. + AutoDecRef special(Py_BuildValue("(Os)", cfunc.object(), "overload")); + if (PyDict_SetItem(pyside_globals->map_dict, special, obtype) < 0) + return -1; + } + if (PyDict_SetItemString(pyside_globals->map_dict, mangled_name, obtype) < 0) + return -1; + continue; + } + // Then we insert the mapping for static methods. + if (meth->ml_flags & METH_STATIC) { + if (PyDict_SetItem(pyside_globals->map_dict, look, obtype) < 0) + return -1; + } + } + return 0; +} + +} // extern "C" diff --git a/sources/shiboken6/libshiboken/signature_p.h b/sources/shiboken6/libshiboken/signature_p.h new file mode 100644 index 000000000..d0c4ee537 --- /dev/null +++ b/sources/shiboken6/libshiboken/signature_p.h @@ -0,0 +1,78 @@ +// Copyright (C) 2020 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 + +#ifndef SIGNATURE_IMPL_H +#define SIGNATURE_IMPL_H + +#include "signature.h" + +extern "C" { + +// signature_globals.cpp + +struct safe_globals_struc { + // init part 1: get arg_dict + PyObject *helper_module; + PyObject *arg_dict; + PyObject *map_dict; + PyObject *value_dict; // for writing signatures + PyObject *feature_dict; // registry for PySide.support.__feature__ + // init part 2: run module + PyObject *pyside_type_init_func; + PyObject *create_signature_func; + PyObject *seterror_argument_func; + PyObject *make_helptext_func; + PyObject *finish_import_func; + PyObject *feature_import_func; + PyObject *feature_imported_func; +}; + +extern safe_globals_struc *pyside_globals; +extern PyMethodDef signature_methods[]; + +void init_shibokensupport_module(void); + +// signature.cpp + +PyObject *GetTypeKey(PyObject *ob); + +PyObject *GetSignature_Function(PyObject *, PyObject *); +PyObject *GetSignature_TypeMod(PyObject *, PyObject *); +PyObject *GetSignature_Wrapper(PyObject *, PyObject *); + +LIBSHIBOKEN_API PyObject *get_signature_intern(PyObject *ob, PyObject *modifier); +PyObject *PySide_BuildSignatureProps(PyObject *class_mod); +PyObject *GetClassOrModOf(PyObject *ob); + +// signature_extend.cpp +PyObject *pyside_cf_get___signature__(PyObject *func, PyObject *modifier); +PyObject *pyside_sm_get___signature__(PyObject *sm, PyObject *modifier); +PyObject *pyside_md_get___signature__(PyObject *ob_md, PyObject *modifier); +PyObject *pyside_wd_get___signature__(PyObject *ob, PyObject *modifier); +PyObject *pyside_tp_get___signature__(PyObject *obtype_mod, PyObject *modifier); + +int PySide_PatchTypes(void); +PyObject *pyside_tp_get___doc__(PyObject *tp); + +// signature_helper.cpp + +int add_more_getsets(PyTypeObject *type, PyGetSetDef *gsp, PyObject **doc_descr); +PyObject *name_key_to_func(PyObject *ob); +int insert_snake_case_variants(PyObject *dict); +PyObject *_get_class_of_cf(PyObject *ob_cf); +PyObject *_get_class_of_sm(PyObject *ob_sm); +PyObject *_get_class_of_descr(PyObject *ob); +PyObject *_address_to_stringlist(PyObject *numkey); +int _build_func_to_type(PyObject *obtype); +int _finish_nested_classes(PyObject *dict); + +#ifdef PYPY_VERSION +// PyPy has a special builtin method. +PyObject *GetSignature_Method(PyObject *, PyObject *); +PyObject *pyside_bm_get___signature__(PyObject *func, PyObject *modifier); +PyObject *_get_class_of_bm(PyObject *ob_cf); +#endif + +} // extern "C" + +#endif // SIGNATURE_IMPL_H diff --git a/sources/shiboken6/libshiboken/threadstatesaver.cpp b/sources/shiboken6/libshiboken/threadstatesaver.cpp new file mode 100644 index 000000000..9f74ed442 --- /dev/null +++ b/sources/shiboken6/libshiboken/threadstatesaver.cpp @@ -0,0 +1,31 @@ +// 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 "threadstatesaver.h" + +namespace Shiboken +{ + +ThreadStateSaver::ThreadStateSaver() = default; + +ThreadStateSaver::~ThreadStateSaver() +{ + restore(); +} + +void ThreadStateSaver::save() +{ + if (Py_IsInitialized()) + m_threadState = PyEval_SaveThread(); +} + +void ThreadStateSaver::restore() +{ + if (m_threadState) { + PyEval_RestoreThread(m_threadState); + m_threadState = nullptr; + } +} + +} // namespace Shiboken + diff --git a/sources/shiboken6/libshiboken/threadstatesaver.h b/sources/shiboken6/libshiboken/threadstatesaver.h new file mode 100644 index 000000000..4289f6726 --- /dev/null +++ b/sources/shiboken6/libshiboken/threadstatesaver.h @@ -0,0 +1,32 @@ +// 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 + +#ifndef THREADSTATESAVER_H +#define THREADSTATESAVER_H + +#include "sbkpython.h" +#include <shibokenmacros.h> + +namespace Shiboken +{ + +class LIBSHIBOKEN_API ThreadStateSaver +{ +public: + ThreadStateSaver(const ThreadStateSaver &) = delete; + ThreadStateSaver(ThreadStateSaver &&) = delete; + ThreadStateSaver &operator=(const ThreadStateSaver &) = delete; + ThreadStateSaver &operator=(ThreadStateSaver &&) = delete; + + ThreadStateSaver(); + ~ThreadStateSaver(); + void save(); + void restore(); +private: + PyThreadState *m_threadState = nullptr; +}; + +} // namespace Shiboken + +#endif // THREADSTATESAVER_H + diff --git a/sources/shiboken6/libshiboken/voidptr.cpp b/sources/shiboken6/libshiboken/voidptr.cpp new file mode 100644 index 000000000..8bb3f6ac8 --- /dev/null +++ b/sources/shiboken6/libshiboken/voidptr.cpp @@ -0,0 +1,434 @@ +// Copyright (C) 2017 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 "voidptr.h" +#include "pep384ext.h" +#include "sbkconverter.h" +#include "basewrapper.h" +#include "basewrapper_p.h" + +extern "C" +{ + +// Void pointer object definition. +struct SbkVoidPtrObject { + PyObject_HEAD + void *cptr; + Py_ssize_t size; + bool isWritable; +}; + +PyObject *SbkVoidPtrObject_new(PyTypeObject *type, PyObject * /* args */, PyObject * /* kwds */) +{ + // PYSIDE-560: It is much safer to first call a function and then do a + // type cast than to do everything in one line. The bad construct looked + // like this, actual call forgotten: + // SbkVoidPtrObject *self = + // reinterpret_cast<SbkVoidPtrObject *>(type->tp_alloc); + auto *self = PepExt_TypeCallAlloc<SbkVoidPtrObject>(type, 0); + + if (self != nullptr) { + self->cptr = nullptr; + self->size = -1; + self->isWritable = false; + } + + return reinterpret_cast<PyObject *>(self); +} + +#define SbkVoidPtr_Check(op) (Py_TYPE(op) == SbkVoidPtr_TypeF()) + + +int SbkVoidPtrObject_init(PyObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *addressObject; + Py_ssize_t size = -1; + int isWritable = 0; + auto *sbkSelf = reinterpret_cast<SbkVoidPtrObject *>(self); + + static const char *kwlist[] = {"address", "size", "writeable", nullptr}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|ni", const_cast<char **>(kwlist), + &addressObject, &size, &isWritable)) + return -1; + + // Void pointer. + if (SbkVoidPtr_Check(addressObject)) { + auto *sbkOther = reinterpret_cast<SbkVoidPtrObject *>(addressObject); + sbkSelf->cptr = sbkOther->cptr; + sbkSelf->size = sbkOther->size; + sbkSelf->isWritable = sbkOther->isWritable; + } + // Python buffer interface. + else if (PyObject_CheckBuffer(addressObject)) { + Py_buffer bufferView; + + // Bail out if the object can't provide a simple contiguous buffer. + if (PyObject_GetBuffer(addressObject, &bufferView, PyBUF_SIMPLE) < 0) + return 0; + + sbkSelf->cptr = bufferView.buf; + sbkSelf->size = bufferView.len > 0 ? bufferView.len : size; + sbkSelf->isWritable = bufferView.readonly <= 0; + + // Release the buffer. + PyBuffer_Release(&bufferView); + } + // Shiboken::Object wrapper. + else if (Shiboken::Object::checkType(addressObject)) { + auto *sbkOther = reinterpret_cast<SbkObject *>(addressObject); + sbkSelf->cptr = sbkOther->d->cptr[0]; + sbkSelf->size = size; + sbkSelf->isWritable = isWritable > 0; + } + // An integer representing an address. + else { + if (addressObject == Py_None) { + sbkSelf->cptr = nullptr; + sbkSelf->size = 0; + sbkSelf->isWritable = false; + } + + else { + void *cptr = PyLong_AsVoidPtr(addressObject); + if (PyErr_Occurred()) { + PyErr_SetString(PyExc_TypeError, + "Creating a VoidPtr object requires an address of a C++ object, " + "a wrapped Shiboken Object type, " + "an object implementing the Python Buffer interface, " + "or another VoidPtr object."); + return -1; + } + sbkSelf->cptr = cptr; + sbkSelf->size = size; + sbkSelf->isWritable = isWritable > 0; + } + } + + return 0; +} + +PyObject *SbkVoidPtrObject_richcmp(PyObject *obj1, PyObject *obj2, int op) +{ + PyObject *result = Py_False; + void *cptr1 = nullptr; + void *cptr2 = nullptr; + bool validObjects = true; + + if (SbkVoidPtr_Check(obj1)) + cptr1 = reinterpret_cast<SbkVoidPtrObject *>(obj1)->cptr; + else + validObjects = false; + + if (SbkVoidPtr_Check(obj2)) + cptr2 = reinterpret_cast<SbkVoidPtrObject *>(obj2)->cptr; + else + validObjects = false; + + if (validObjects) { + switch (op) { + case Py_EQ: + if (cptr1 == cptr2) + result = Py_True; + break; + case Py_NE: + if (cptr1 != cptr2) + result = Py_True; + break; + case Py_LT: + case Py_LE: + case Py_GT: + case Py_GE: + break; + } + } + + Py_INCREF(result); + return result; +} + +PyObject *SbkVoidPtrObject_int(PyObject *v) +{ + auto *sbkObject = reinterpret_cast<SbkVoidPtrObject *>(v); + return PyLong_FromVoidPtr(sbkObject->cptr); +} + +PyObject *toBytes(PyObject *self, PyObject * /* args */) +{ + auto *sbkObject = reinterpret_cast<SbkVoidPtrObject *>(self); + if (sbkObject->size < 0) + return PyErr_Format(PyExc_IndexError, "VoidPtr does not have a size set."); + + PyObject *bytes = PyBytes_FromStringAndSize(reinterpret_cast<const char *>(sbkObject->cptr), + sbkObject->size); + Py_XINCREF(bytes); + return bytes; +} + +static struct PyMethodDef SbkVoidPtrObject_methods[] = { + {"toBytes", toBytes, METH_NOARGS, nullptr}, + {nullptr, nullptr, 0, nullptr} +}; + +static Py_ssize_t SbkVoidPtrObject_length(PyObject *v) +{ + auto *sbkObject = reinterpret_cast<SbkVoidPtrObject *>(v); + if (sbkObject->size < 0) { + PyErr_SetString(PyExc_IndexError, "VoidPtr does not have a size set."); + return -1; + } + + return sbkObject->size; +} + +static const char trueString[] = "True" ; +static const char falseString[] = "False" ; + +PyObject *SbkVoidPtrObject_repr(PyObject *v) +{ + + + auto *sbkObject = reinterpret_cast<SbkVoidPtrObject *>(v); + PyObject *s = PyUnicode_FromFormat("%s(%p, %zd, %s)", + Py_TYPE(sbkObject)->tp_name, + sbkObject->cptr, + sbkObject->size, + sbkObject->isWritable ? trueString : falseString); + Py_XINCREF(s); + return s; +} + +PyObject *SbkVoidPtrObject_str(PyObject *v) +{ + auto *sbkObject = reinterpret_cast<SbkVoidPtrObject *>(v); + PyObject *s = PyUnicode_FromFormat("%s(Address %p, Size %zd, isWritable %s)", + Py_TYPE(sbkObject)->tp_name, + sbkObject->cptr, + sbkObject->size, + sbkObject->isWritable ? trueString : falseString); + Py_XINCREF(s); + return s; +} + + +static int SbkVoidPtrObject_getbuffer(PyObject *obj, Py_buffer *view, int flags) +{ + if (view == nullptr) + return -1; + + auto *sbkObject = reinterpret_cast<SbkVoidPtrObject *>(obj); + if (sbkObject->size < 0) + return -1; + + int readonly = sbkObject->isWritable ? 0 : 1; + if (((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) && + (readonly == 1)) { + PyErr_SetString(PyExc_BufferError, + "Object is not writable."); + return -1; + } + + view->obj = obj; + if (obj) + Py_XINCREF(obj); + view->buf = sbkObject->cptr; + view->len = sbkObject->size; + view->readonly = readonly; + view->itemsize = 1; + view->format = nullptr; + if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) + view->format = const_cast<char *>("B"); + view->ndim = 1; + view->shape = nullptr; + if ((flags & PyBUF_ND) == PyBUF_ND) + view->shape = &(view->len); + view->strides = nullptr; + if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) + view->strides = &(view->itemsize); + view->suboffsets = nullptr; + view->internal = nullptr; + return 0; +} + +static PyBufferProcs SbkVoidPtrObjectBufferProc = { + (getbufferproc)SbkVoidPtrObject_getbuffer, // bf_getbuffer + (releasebufferproc)nullptr // bf_releasebuffer +}; + +static PyTypeObject *createVoidPtrType() +{ + PyType_Slot SbkVoidPtrType_slots[] = { + {Py_tp_repr, reinterpret_cast<void *>(SbkVoidPtrObject_repr)}, + {Py_nb_int, reinterpret_cast<void *>(SbkVoidPtrObject_int)}, + {Py_sq_length, reinterpret_cast<void *>(SbkVoidPtrObject_length)}, + {Py_tp_str, reinterpret_cast<void *>(SbkVoidPtrObject_str)}, + {Py_tp_richcompare, reinterpret_cast<void *>(SbkVoidPtrObject_richcmp)}, + {Py_tp_init, reinterpret_cast<void *>(SbkVoidPtrObject_init)}, + {Py_tp_new, reinterpret_cast<void *>(SbkVoidPtrObject_new)}, + {Py_tp_dealloc, reinterpret_cast<void *>(Sbk_object_dealloc)}, + {Py_tp_methods, reinterpret_cast<void *>(SbkVoidPtrObject_methods)}, + {0, nullptr} + }; + + PyType_Spec SbkVoidPtrType_spec = { + "2:shiboken6.Shiboken.VoidPtr", + sizeof(SbkVoidPtrObject), + 0, + Py_TPFLAGS_DEFAULT, + SbkVoidPtrType_slots, + }; + + return SbkType_FromSpec_BMDWB(&SbkVoidPtrType_spec, + nullptr, nullptr, 0, 0, + &SbkVoidPtrObjectBufferProc); +} + +PyTypeObject *SbkVoidPtr_TypeF(void) +{ + static auto *type = createVoidPtrType(); + return type; +} + +} // extern "C" + +namespace VoidPtr { + +static int voidPointerInitialized = false; + +void init() +{ + if (PyType_Ready(SbkVoidPtr_TypeF()) < 0) + Py_FatalError("[libshiboken] Failed to initialize Shiboken.VoidPtr type."); + else + voidPointerInitialized = true; +} + +void addVoidPtrToModule(PyObject *module) +{ + if (voidPointerInitialized) { + Py_INCREF(SbkVoidPtr_TypeF()); + PyModule_AddObject(module, PepType_GetNameStr(SbkVoidPtr_TypeF()), + reinterpret_cast<PyObject *>(SbkVoidPtr_TypeF())); + } +} + +static PyObject *createVoidPtr(void *cppIn, Py_ssize_t size = 0, bool isWritable = false) +{ + if (!cppIn) + Py_RETURN_NONE; + + SbkVoidPtrObject *result = PyObject_New(SbkVoidPtrObject, SbkVoidPtr_TypeF()); + if (!result) + Py_RETURN_NONE; + + result->cptr = cppIn; + result->size = size; + result->isWritable = isWritable; + + return reinterpret_cast<PyObject *>(result); +} + +static PyObject *toPython(const void *cppIn) +{ + return createVoidPtr(const_cast<void *>(cppIn)); +} + +static void VoidPtrToCpp(PyObject *pyIn, void *cppOut) +{ + auto *sbkIn = reinterpret_cast<SbkVoidPtrObject *>(pyIn); + *reinterpret_cast<void **>(cppOut) = sbkIn->cptr; +} + +static PythonToCppFunc VoidPtrToCppIsConvertible(PyObject *pyIn) +{ + return SbkVoidPtr_Check(pyIn) ? VoidPtrToCpp : nullptr; +} + +static void SbkObjectToCpp(PyObject *pyIn, void *cppOut) +{ + auto *sbkIn = reinterpret_cast<SbkObject *>(pyIn); + *reinterpret_cast<void **>(cppOut) = sbkIn->d->cptr[0]; +} + +static PythonToCppFunc SbkObjectToCppIsConvertible(PyObject *pyIn) +{ + return Shiboken::Object::checkType(pyIn) ? SbkObjectToCpp : nullptr; +} + +static void PythonBufferToCpp(PyObject *pyIn, void *cppOut) +{ + if (PyObject_CheckBuffer(pyIn)) { + Py_buffer bufferView; + + // Bail out if the object can't provide a simple contiguous buffer. + if (PyObject_GetBuffer(pyIn, &bufferView, PyBUF_SIMPLE) < 0) + return; + + *reinterpret_cast<void **>(cppOut) = bufferView.buf; + + // Release the buffer. + PyBuffer_Release(&bufferView); + } +} + +static PythonToCppFunc PythonBufferToCppIsConvertible(PyObject *pyIn) +{ + if (PyObject_CheckBuffer(pyIn)) { + Py_buffer bufferView; + + // Bail out if the object can't provide a simple contiguous buffer. + if (PyObject_GetBuffer(pyIn, &bufferView, PyBUF_SIMPLE) < 0) + return nullptr; + + // Release the buffer. + PyBuffer_Release(&bufferView); + + return PythonBufferToCpp; + } + return nullptr; +} + +SbkConverter *createConverter() +{ + SbkConverter *converter = Shiboken::Conversions::createConverter(SbkVoidPtr_TypeF(), toPython); + Shiboken::Conversions::addPythonToCppValueConversion(converter, + VoidPtrToCpp, + VoidPtrToCppIsConvertible); + Shiboken::Conversions::addPythonToCppValueConversion(converter, + SbkObjectToCpp, + SbkObjectToCppIsConvertible); + Shiboken::Conversions::addPythonToCppValueConversion(converter, + PythonBufferToCpp, + PythonBufferToCppIsConvertible); + return converter; +} + +void setSize(PyObject *voidPtr, Py_ssize_t size) +{ + assert(voidPtr->ob_type == SbkVoidPtr_TypeF()); + auto *voidPtrObj = reinterpret_cast<SbkVoidPtrObject *>(voidPtr); + voidPtrObj->size = size; +} + +Py_ssize_t getSize(PyObject *voidPtr) +{ + assert(voidPtr->ob_type == SbkVoidPtr_TypeF()); + auto *voidPtrObj = reinterpret_cast<SbkVoidPtrObject *>(voidPtr); + return voidPtrObj->size; +} + +bool isWritable(PyObject *voidPtr) +{ + assert(voidPtr->ob_type == SbkVoidPtr_TypeF()); + auto *voidPtrObj = reinterpret_cast<SbkVoidPtrObject *>(voidPtr); + return voidPtrObj->isWritable; +} + +void setWritable(PyObject *voidPtr, bool isWritable) +{ + assert(voidPtr->ob_type == SbkVoidPtr_TypeF()); + auto *voidPtrObj = reinterpret_cast<SbkVoidPtrObject *>(voidPtr); + voidPtrObj->isWritable = isWritable; +} + +} // namespace VoidPtr diff --git a/sources/shiboken6/libshiboken/voidptr.h b/sources/shiboken6/libshiboken/voidptr.h new file mode 100644 index 000000000..8360bf9c7 --- /dev/null +++ b/sources/shiboken6/libshiboken/voidptr.h @@ -0,0 +1,33 @@ +// Copyright (C) 2017 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 + +#ifndef VOIDPTR_H +#define VOIDPTR_H + +#include "sbkpython.h" +#include "shibokenmacros.h" +#include "sbkconverter.h" + +extern "C" +{ + +// Void pointer type declaration. +extern LIBSHIBOKEN_API PyTypeObject *SbkVoidPtr_TypeF(void); + +} // extern "C" + +namespace VoidPtr +{ + +void init(); +SbkConverter *createConverter(); +LIBSHIBOKEN_API void addVoidPtrToModule(PyObject *module); + +LIBSHIBOKEN_API void setSize(PyObject *voidPtr, Py_ssize_t size); +LIBSHIBOKEN_API Py_ssize_t getSize(PyObject *voidPtr); +LIBSHIBOKEN_API bool isWritable(PyObject *voidPtr); +LIBSHIBOKEN_API void setWritable(PyObject *voidPtr, bool isWritable); +} + + +#endif // VOIDPTR_H |