diff options
Diffstat (limited to 'sources/shiboken6/libshiboken/bindingmanager.cpp')
-rw-r--r-- | sources/shiboken6/libshiboken/bindingmanager.cpp | 443 |
1 files changed, 295 insertions, 148 deletions
diff --git a/sources/shiboken6/libshiboken/bindingmanager.cpp b/sources/shiboken6/libshiboken/bindingmanager.cpp index 20b331bde..83c927ae5 100644 --- a/sources/shiboken6/libshiboken/bindingmanager.cpp +++ b/sources/shiboken6/libshiboken/bindingmanager.cpp @@ -1,157 +1,188 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 *>; -class Graph +template <class NodeType> +class BaseGraph { public: - using NodeList = std::vector<PyTypeObject *>; - using Edges = std::unordered_map<PyTypeObject *, NodeList>; + using NodeList = std::vector<NodeType>; + using NodeSet = std::unordered_set<NodeType>; + + using Edges = std::unordered_map<NodeType, NodeList>; Edges m_edges; - Graph() = default; + BaseGraph() = default; - void addEdge(PyTypeObject *from, PyTypeObject *to) + void addEdge(NodeType from, NodeType to) { m_edges[from].push_back(to); } -#ifndef NDEBUG - void dumpDotGraph() + NodeSet nodeSet() const { - std::ofstream file("/tmp/shiboken_graph.dot"); - - file << "digraph D {\n"; - - for (auto i = m_edges.begin(), end = m_edges.end(); i != end; ++i) { - auto *node1 = i->first; - const NodeList &nodeList = i->second; - for (const PyTypeObject *o : nodeList) { - auto *node2 = o; - file << '"' << node2->tp_name << "\" -> \"" - << node1->tp_name << "\"\n"; - } + NodeSet result; + for (const auto &p : m_edges) { + result.insert(p.first); + for (const auto node2 : p.second) + result.insert(node2); } - file << "}\n"; + return result; } -#endif +}; - PyTypeObject *identifyType(void **cptr, PyTypeObject *type, PyTypeObject *baseType) const +class Graph : public BaseGraph<GraphNode> +{ +public: + using TypeCptrPair = BindingManager::TypeCptrPair; + + TypeCptrPair identifyType(void *cptr, PyTypeObject *type, PyTypeObject *baseType) const { - auto edgesIt = m_edges.find(type); - if (edgesIt != m_edges.end()) { - const NodeList &adjNodes = m_edges.find(type)->second; - for (PyTypeObject *node : adjNodes) { - PyTypeObject *newType = identifyType(cptr, node, baseType); - if (newType) - return newType; - } - } - void *typeFound = nullptr; - auto *sotp = PepType_SOTP(type); - if (sotp->type_discovery) - typeFound = sotp->type_discovery(*cptr, baseType); - if (typeFound) { - // This "typeFound != type" is needed for backwards compatibility with old modules using a newer version of - // libshiboken because old versions of type_discovery function used to return a PyTypeObject *instead of - // a possible variation of the C++ instance pointer (*cptr). - if (typeFound != type) - *cptr = typeFound; - return type; - } - return nullptr; + return identifyType(cptr, GraphNode(type->tp_name), type, baseType); } -}; + bool dumpTypeGraph(const char *fileName) const; -#ifndef NDEBUG -static void showWrapperMap(const WrapperMap &wrapperMap) +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 { - if (Py_VerboseFlag > 0) { - fprintf(stderr, "-------------------------------\n"); - fprintf(stderr, "WrapperMap: %p (size: %d)\n", &wrapperMap, (int) wrapperMap.size()); - for (auto it = wrapperMap.begin(), end = wrapperMap.end(); it != end; ++it) { - const SbkObject *sbkObj = it->second; - fprintf(stderr, "key: %p, value: %p (%s, refcnt: %d)\n", it->first, - static_cast<const void *>(sbkObj), - (Py_TYPE(sbkObj))->tp_name, - int(reinterpret_cast<const PyObject *>(sbkObj)->ob_refcnt)); + 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; } - fprintf(stderr, "-------------------------------\n"); } + + 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; } -#endif 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 destroying; - BindingManagerPrivate() : destroying(false) {} - bool releaseWrapper(void *cptr, SbkObject *wrapper); - void assignWrapper(SbkObject *wrapper, const void *cptr); + 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); }; -bool BindingManager::BindingManagerPrivate::releaseWrapper(void *cptr, SbkObject *wrapper) +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. @@ -164,14 +195,41 @@ bool BindingManager::BindingManagerPrivate::releaseWrapper(void *cptr, SbkObject return false; } -void BindingManager::BindingManagerPrivate::assignWrapper(SbkObject *wrapper, const void *cptr) +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; @@ -187,12 +245,14 @@ BindingManager::~BindingManager() debugRemoveFreeHook(); #endif #ifndef NDEBUG - showWrapperMap(m_d->wrapperMapper); + 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)); } @@ -208,6 +268,7 @@ BindingManager &BindingManager::instance() { 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(); } @@ -221,15 +282,7 @@ void BindingManager::registerWrapper(SbkObject *pyObj, void *cptr) if (d->mi_init && !d->mi_offsets) d->mi_offsets = d->mi_init(cptr); - m_d->assignWrapper(pyObj, cptr); - if (d->mi_offsets) { - int *offset = d->mi_offsets; - while (*offset != -1) { - if (*offset > 0) - m_d->assignWrapper(pyObj, reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(cptr) + *offset)); - offset++; - } - } + m_d->assignWrapper(pyObj, cptr, d->mi_offsets); } void BindingManager::releaseWrapper(SbkObject *sbkObj) @@ -239,17 +292,10 @@ void BindingManager::releaseWrapper(SbkObject *sbkObj) 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) { - auto *cptr = reinterpret_cast<unsigned char *>(cptrs[i]); - m_d->releaseWrapper(cptr, sbkObj); - if (d && d->mi_offsets) { - int *offset = d->mi_offsets; - while (*offset != -1) { - if (*offset > 0) - m_d->releaseWrapper(reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(cptr) + *offset), sbkObj); - offset++; - } - } + if (cptrs[i] != nullptr) + m_d->releaseWrapper(cptrs[i], sbkObj, mi_offsets); } sbkObj->d->validCppObject = false; } @@ -268,6 +314,7 @@ void BindingManager::addToDeletionInMainThread(const DestructorEntry &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; @@ -281,7 +328,7 @@ PyObject *BindingManager::getOverride(const void *cptr, 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 || reinterpret_cast<const PyObject *>(wrapper)->ob_refcnt == 0) + if (!wrapper || Py_REFCNT(reinterpret_cast<const PyObject *>(wrapper)) == 0) return nullptr; // PYSIDE-1626: Touch the type to initiate switching early. @@ -289,11 +336,6 @@ PyObject *BindingManager::getOverride(const void *cptr, int flag = currentSelectId(Py_TYPE(wrapper)); int propFlag = isdigit(methodName[0]) ? methodName[0] - '0' : 0; - if ((flag & 0x02) != 0 && (propFlag & 3) != 0) { - // PYSIDE-1019: Handle overriding with properties. - // They cannot be overridden (make that sure by the metaclass). - return nullptr; - } bool is_snake = flag & 0x01; PyObject *pyMethodName = nameCache[is_snake]; // borrowed if (pyMethodName == nullptr) { @@ -303,12 +345,13 @@ PyObject *BindingManager::getOverride(const void *cptr, nameCache[is_snake] = pyMethodName; } - if (wrapper->ob_dict) { - PyObject *method = PyDict_GetItem(wrapper->ob_dict, pyMethodName); - if (method) { - Py_INCREF(method); - return method; - } + 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); @@ -318,6 +361,7 @@ PyObject *BindingManager::getOverride(const void *cptr, // 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); @@ -326,7 +370,8 @@ PyObject *BindingManager::getOverride(const void *cptr, method = nullptr; } } else if (PyObject_HasAttr(method, PyName::im_self()) - && PyObject_HasAttr(method, PyName::im_func())) { + && 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); @@ -346,41 +391,59 @@ PyObject *BindingManager::getOverride(const void *cptr, } if (method != nullptr) { - PyObject *defaultMethod; + 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)); - if (parent->tp_dict) { - defaultMethod = PyDict_GetItem(parent->tp_dict, pyMethodName); - if (defaultMethod && function != defaultMethod) - return method; + 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(PyTypeObject *parent, PyTypeObject *child) +void BindingManager::addClassInheritance(Module::TypeInitStruct *parent, + Module::TypeInitStruct *child) { - m_d->classHierarchy.addEdge(parent, 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) { - PyTypeObject *identifiedType = m_d->classHierarchy.identifyType(cptr, type, type); - return identifiedType ? identifiedType : 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) @@ -392,10 +455,94 @@ std::set<PyObject *> BindingManager::getAllPyObjects() void BindingManager::visitAllPyObjects(ObjectVisitor visitor, void *data) { WrapperMap copy = m_d->wrapperMapper; - for (auto it = copy.begin(); it != copy.end(); ++it) { - if (hasWrapper(it->first)) - visitor(it->second, data); + 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 |