diff options
Diffstat (limited to 'libs/python/src/object/class.cpp')
-rw-r--r-- | libs/python/src/object/class.cpp | 764 |
1 files changed, 764 insertions, 0 deletions
diff --git a/libs/python/src/object/class.cpp b/libs/python/src/object/class.cpp new file mode 100644 index 000000000..aeef688e2 --- /dev/null +++ b/libs/python/src/object/class.cpp @@ -0,0 +1,764 @@ +// Copyright David Abrahams 2001. +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include <boost/python/detail/prefix.hpp> +#include <boost/mpl/lambda.hpp> // #including this first is an intel6 workaround + +#include <boost/python/object/class.hpp> +#include <boost/python/object/instance.hpp> +#include <boost/python/object/class_detail.hpp> +#include <boost/python/scope.hpp> +#include <boost/python/converter/registry.hpp> +#include <boost/python/object/find_instance.hpp> +#include <boost/python/object/pickle_support.hpp> +#include <boost/python/detail/map_entry.hpp> +#include <boost/python/object.hpp> +#include <boost/python/object_protocol.hpp> +#include <boost/detail/binary_search.hpp> +#include <boost/python/self.hpp> +#include <boost/python/dict.hpp> +#include <boost/python/str.hpp> +#include <boost/python/ssize_t.hpp> +#include <functional> +#include <vector> +#include <cstddef> +#include <new> +#include <structmember.h> + +namespace boost { namespace python { + +# ifdef BOOST_PYTHON_SELF_IS_CLASS +namespace self_ns +{ + self_t self; +} +# endif + +instance_holder::instance_holder() + : m_next(0) +{ +} + +instance_holder::~instance_holder() +{ +} + +extern "C" +{ + // This is copied from typeobject.c in the Python sources. Even though + // class_metatype_object doesn't set Py_TPFLAGS_HAVE_GC, that bit gets + // filled in by the base class initialization process in + // PyType_Ready(). However, tp_is_gc is *not* copied from the base + // type, making it assume that classes are GC-able even if (like + // class_type_object) they're statically allocated. + static int + type_is_gc(PyTypeObject *python_type) + { + return python_type->tp_flags & Py_TPFLAGS_HEAPTYPE; + } + + // This is also copied from the Python sources. We can't implement + // static_data as a subclass property effectively without it. + typedef struct { + PyObject_HEAD + PyObject *prop_get; + PyObject *prop_set; + PyObject *prop_del; + PyObject *prop_doc; + int getter_doc; + } propertyobject; + + // Copied from Python source and removed the part for setting docstring, + // since we don't have a setter for __doc__ and trying to set it will + // cause the init fail. + static int property_init(PyObject *self, PyObject *args, PyObject *kwds) + { + PyObject *get = NULL, *set = NULL, *del = NULL, *doc = NULL; + static const char *kwlist[] = {"fget", "fset", "fdel", "doc", 0}; + propertyobject *prop = (propertyobject *)self; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOO:property", + const_cast<char **>(kwlist), &get, &set, &del, &doc)) + return -1; + + if (get == Py_None) + get = NULL; + if (set == Py_None) + set = NULL; + if (del == Py_None) + del = NULL; + + Py_XINCREF(get); + Py_XINCREF(set); + Py_XINCREF(del); + Py_XINCREF(doc); + + prop->prop_get = get; + prop->prop_set = set; + prop->prop_del = del; + prop->prop_doc = doc; + prop->getter_doc = 0; + + return 0; + } + + + static PyObject * + static_data_descr_get(PyObject *self, PyObject * /*obj*/, PyObject * /*type*/) + { + propertyobject *gs = (propertyobject *)self; + + return PyObject_CallFunction(gs->prop_get, const_cast<char*>("()")); + } + + static int + static_data_descr_set(PyObject *self, PyObject * /*obj*/, PyObject *value) + { + propertyobject *gs = (propertyobject *)self; + PyObject *func, *res; + + if (value == NULL) + func = gs->prop_del; + else + func = gs->prop_set; + if (func == NULL) { + PyErr_SetString(PyExc_AttributeError, + value == NULL ? + "can't delete attribute" : + "can't set attribute"); + return -1; + } + if (value == NULL) + res = PyObject_CallFunction(func, const_cast<char*>("()")); + else + res = PyObject_CallFunction(func, const_cast<char*>("(O)"), value); + if (res == NULL) + return -1; + Py_DECREF(res); + return 0; + } +} + +static PyTypeObject static_data_object = { + PyVarObject_HEAD_INIT(NULL, 0) + const_cast<char*>("Boost.Python.StaticProperty"), + sizeof(propertyobject), + 0, + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT // | Py_TPFLAGS_HAVE_GC + | Py_TPFLAGS_BASETYPE, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, //&PyProperty_Type, /* tp_base */ + 0, /* tp_dict */ + static_data_descr_get, /* tp_descr_get */ + static_data_descr_set, /* tp_descr_set */ + 0, /* tp_dictoffset */ + property_init, /* tp_init */ + 0, /* tp_alloc */ + 0, // filled in with type_new /* tp_new */ + 0, // filled in with __PyObject_GC_Del /* tp_free */ + 0, /* tp_is_gc */ + 0, /* tp_bases */ + 0, /* tp_mro */ + 0, /* tp_cache */ + 0, /* tp_subclasses */ + 0, /* tp_weaklist */ +#if PYTHON_API_VERSION >= 1012 + 0 /* tp_del */ +#endif +}; + +namespace objects +{ +#if PY_VERSION_HEX < 0x03000000 + // XXX Not sure why this run into compiling error in Python 3 + extern "C" + { + // This declaration needed due to broken Python 2.2 headers + extern DL_IMPORT(PyTypeObject) PyProperty_Type; + } +#endif + + BOOST_PYTHON_DECL PyObject* static_data() + { + if (static_data_object.tp_dict == 0) + { + Py_TYPE(&static_data_object) = &PyType_Type; + static_data_object.tp_base = &PyProperty_Type; + if (PyType_Ready(&static_data_object)) + return 0; + } + return upcast<PyObject>(&static_data_object); + } +} + +extern "C" +{ + // Ordinarily, descriptors have a certain assymetry: you can use + // them to read attributes off the class object they adorn, but + // writing the same attribute on the class object always replaces + // the descriptor in the class __dict__. In order to properly + // represent C++ static data members, we need to allow them to be + // written through the class instance. This function of the + // metaclass makes it possible. + static int + class_setattro(PyObject *obj, PyObject *name, PyObject* value) + { + // Must use "private" Python implementation detail + // _PyType_Lookup instead of PyObject_GetAttr because the + // latter will always end up calling the descr_get function on + // any descriptor it finds; we need the unadulterated + // descriptor here. + PyObject* a = _PyType_Lookup(downcast<PyTypeObject>(obj), name); + + // a is a borrowed reference or 0 + + // If we found a static data descriptor, call it directly to + // force it to set the static data member + if (a != 0 && PyObject_IsInstance(a, objects::static_data())) + return Py_TYPE(a)->tp_descr_set(a, obj, value); + else + return PyType_Type.tp_setattro(obj, name, value); + } +} + +static PyTypeObject class_metatype_object = { + PyVarObject_HEAD_INIT(NULL, 0) + const_cast<char*>("Boost.Python.class"), + PyType_Type.tp_basicsize, + 0, + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + class_setattro, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT // | Py_TPFLAGS_HAVE_GC + | Py_TPFLAGS_BASETYPE, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, //&PyType_Type, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, // filled in with type_new /* tp_new */ + 0, // filled in with __PyObject_GC_Del /* tp_free */ + (inquiry)type_is_gc, /* tp_is_gc */ + 0, /* tp_bases */ + 0, /* tp_mro */ + 0, /* tp_cache */ + 0, /* tp_subclasses */ + 0, /* tp_weaklist */ +#if PYTHON_API_VERSION >= 1012 + 0 /* tp_del */ +#endif +}; + +// Install the instance data for a C++ object into a Python instance +// object. +void instance_holder::install(PyObject* self) throw() +{ + assert(PyType_IsSubtype(Py_TYPE(Py_TYPE(self)), &class_metatype_object)); + m_next = ((objects::instance<>*)self)->objects; + ((objects::instance<>*)self)->objects = this; +} + + +namespace objects +{ +// Get the metatype object for all extension classes. + BOOST_PYTHON_DECL type_handle class_metatype() + { + if (class_metatype_object.tp_dict == 0) + { + Py_TYPE(&class_metatype_object) = &PyType_Type; + class_metatype_object.tp_base = &PyType_Type; + if (PyType_Ready(&class_metatype_object)) + return type_handle(); + } + return type_handle(borrowed(&class_metatype_object)); + } + extern "C" + { + static void instance_dealloc(PyObject* inst) + { + instance<>* kill_me = (instance<>*)inst; + + for (instance_holder* p = kill_me->objects, *next; p != 0; p = next) + { + next = p->next(); + p->~instance_holder(); + instance_holder::deallocate(inst, dynamic_cast<void*>(p)); + } + + // Python 2.2.1 won't add weak references automatically when + // tp_itemsize > 0, so we need to manage that + // ourselves. Accordingly, we also have to clean up the + // weakrefs ourselves. + if (kill_me->weakrefs != NULL) + PyObject_ClearWeakRefs(inst); + + Py_XDECREF(kill_me->dict); + + Py_TYPE(inst)->tp_free(inst); + } + + static PyObject * + instance_new(PyTypeObject* type_, PyObject* /*args*/, PyObject* /*kw*/) + { + // Attempt to find the __instance_size__ attribute. If not present, no problem. + PyObject* d = type_->tp_dict; + PyObject* instance_size_obj = PyObject_GetAttrString(d, const_cast<char*>("__instance_size__")); + + ssize_t instance_size = instance_size_obj ? +#if PY_VERSION_HEX >= 0x03000000 + PyLong_AsSsize_t(instance_size_obj) : 0; +#else + PyInt_AsLong(instance_size_obj) : 0; +#endif + + if (instance_size < 0) + instance_size = 0; + + PyErr_Clear(); // Clear any errors that may have occurred. + + instance<>* result = (instance<>*)type_->tp_alloc(type_, instance_size); + if (result) + { + // Guido says we can use ob_size for any purpose we + // like, so we'll store the total size of the object + // there. A negative number indicates that the extra + // instance memory is not yet allocated to any holders. +#if PY_VERSION_HEX >= 0x02060000 + Py_SIZE(result) = +#else + result->ob_size = +#endif + -(static_cast<int>(offsetof(instance<>,storage) + instance_size)); + } + return (PyObject*)result; + } + + static PyObject* instance_get_dict(PyObject* op, void*) + { + instance<>* inst = downcast<instance<> >(op); + if (inst->dict == 0) + inst->dict = PyDict_New(); + return python::xincref(inst->dict); + } + + static int instance_set_dict(PyObject* op, PyObject* dict, void*) + { + instance<>* inst = downcast<instance<> >(op); + python::xdecref(inst->dict); + inst->dict = python::incref(dict); + return 0; + } + + } + + + static PyGetSetDef instance_getsets[] = { + {const_cast<char*>("__dict__"), instance_get_dict, instance_set_dict, NULL, 0}, + {0, 0, 0, 0, 0} + }; + + + static PyMemberDef instance_members[] = { + {const_cast<char*>("__weakref__"), T_OBJECT, offsetof(instance<>, weakrefs), 0, 0}, + {0, 0, 0, 0, 0} + }; + + static PyTypeObject class_type_object = { + PyVarObject_HEAD_INIT(NULL, 0) + const_cast<char*>("Boost.Python.instance"), + offsetof(instance<>,storage), /* tp_basicsize */ + 1, /* tp_itemsize */ + instance_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT // | Py_TPFLAGS_HAVE_GC + | Py_TPFLAGS_BASETYPE, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + offsetof(instance<>,weakrefs), /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + instance_members, /* tp_members */ + instance_getsets, /* tp_getset */ + 0, //&PyBaseObject_Type, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + offsetof(instance<>,dict), /* tp_dictoffset */ + 0, /* tp_init */ + PyType_GenericAlloc, /* tp_alloc */ + instance_new, /* tp_new */ + 0, /* tp_free */ + 0, /* tp_is_gc */ + 0, /* tp_bases */ + 0, /* tp_mro */ + 0, /* tp_cache */ + 0, /* tp_subclasses */ + 0, /* tp_weaklist */ +#if PYTHON_API_VERSION >= 1012 + 0 /* tp_del */ +#endif + }; + + BOOST_PYTHON_DECL type_handle class_type() + { + if (class_type_object.tp_dict == 0) + { + Py_TYPE(&class_type_object) = incref(class_metatype().get()); + class_type_object.tp_base = &PyBaseObject_Type; + if (PyType_Ready(&class_type_object)) + return type_handle(); +// class_type_object.tp_setattro = class_setattro; + } + return type_handle(borrowed(&class_type_object)); + } + + BOOST_PYTHON_DECL void* + find_instance_impl(PyObject* inst, type_info type, bool null_shared_ptr_only) + { + if (!Py_TYPE(Py_TYPE(inst)) || + !PyType_IsSubtype(Py_TYPE(Py_TYPE(inst)), &class_metatype_object)) + return 0; + + instance<>* self = reinterpret_cast<instance<>*>(inst); + + for (instance_holder* match = self->objects; match != 0; match = match->next()) + { + void* const found = match->holds(type, null_shared_ptr_only); + if (found) + return found; + } + return 0; + } + + object module_prefix() + { + return object( + PyObject_IsInstance(scope().ptr(), upcast<PyObject>(&PyModule_Type)) + ? object(scope().attr("__name__")) + : api::getattr(scope(), "__module__", str()) + ); + } + + namespace + { + // Find a registered class object corresponding to id. Return a + // null handle if no such class is registered. + inline type_handle query_class(type_info id) + { + converter::registration const* p = converter::registry::query(id); + return type_handle( + python::borrowed( + python::allow_null(p ? p->m_class_object : 0)) + ); + } + + // Find a registered class corresponding to id. If not found, + // throw an appropriate exception. + type_handle get_class(type_info id) + { + type_handle result(query_class(id)); + + if (result.get() == 0) + { + object report("extension class wrapper for base class "); + report = report + id.name() + " has not been created yet"; + PyErr_SetObject(PyExc_RuntimeError, report.ptr()); + throw_error_already_set(); + } + return result; + } + + // class_base constructor + // + // name - the name of the new Python class + // + // num_types - one more than the number of declared bases + // + // types - array of python::type_info, the first item + // corresponding to the class being created, and the + // rest corresponding to its declared bases. + // + inline object + new_class(char const* name, std::size_t num_types, type_info const* const types, char const* doc) + { + assert(num_types >= 1); + + // Build a tuple of the base Python type objects. If no bases + // were declared, we'll use our class_type() as the single base + // class. + ssize_t const num_bases = (std::max)(num_types - 1, static_cast<std::size_t>(1)); + handle<> bases(PyTuple_New(num_bases)); + + for (ssize_t i = 1; i <= num_bases; ++i) + { + type_handle c = (i >= static_cast<ssize_t>(num_types)) ? class_type() : get_class(types[i]); + // PyTuple_SET_ITEM steals this reference + PyTuple_SET_ITEM(bases.get(), static_cast<ssize_t>(i - 1), upcast<PyObject>(c.release())); + } + + // Call the class metatype to create a new class + dict d; + + object m = module_prefix(); + if (m) d["__module__"] = m; + + if (doc != 0) + d["__doc__"] = doc; + + object result = object(class_metatype())(name, bases, d); + assert(PyType_IsSubtype(Py_TYPE(result.ptr()), &PyType_Type)); + + if (scope().ptr() != Py_None) + scope().attr(name) = result; + + // For pickle. Will lead to informative error messages if pickling + // is not enabled. + result.attr("__reduce__") = object(make_instance_reduce_function()); + + return result; + } + } + + class_base::class_base( + char const* name, std::size_t num_types, type_info const* const types, char const* doc) + : object(new_class(name, num_types, types, doc)) + { + // Insert the new class object in the registry + converter::registration& converters = const_cast<converter::registration&>( + converter::registry::lookup(types[0])); + + // Class object is leaked, for now + converters.m_class_object = (PyTypeObject*)incref(this->ptr()); + } + + BOOST_PYTHON_DECL void copy_class_object(type_info const& src, type_info const& dst) + { + converter::registration& dst_converters + = const_cast<converter::registration&>(converter::registry::lookup(dst)); + + converter::registration const& src_converters = converter::registry::lookup(src); + + dst_converters.m_class_object = src_converters.m_class_object; + } + + void class_base::set_instance_size(std::size_t instance_size) + { + this->attr("__instance_size__") = instance_size; + } + + void class_base::add_property( + char const* name, object const& fget, char const* docstr) + { + object property( + (python::detail::new_reference) + PyObject_CallFunction((PyObject*)&PyProperty_Type, const_cast<char*>("Osss"), fget.ptr(), 0, 0, docstr)); + + this->setattr(name, property); + } + + void class_base::add_property( + char const* name, object const& fget, object const& fset, char const* docstr) + { + object property( + (python::detail::new_reference) + PyObject_CallFunction((PyObject*)&PyProperty_Type, const_cast<char*>("OOss"), fget.ptr(), fset.ptr(), 0, docstr)); + + this->setattr(name, property); + } + + void class_base::add_static_property(char const* name, object const& fget) + { + object property( + (python::detail::new_reference) + PyObject_CallFunction(static_data(), const_cast<char*>("O"), fget.ptr()) + ); + + this->setattr(name, property); + } + + void class_base::add_static_property(char const* name, object const& fget, object const& fset) + { + object property( + (python::detail::new_reference) + PyObject_CallFunction(static_data(), const_cast<char*>("OO"), fget.ptr(), fset.ptr())); + + this->setattr(name, property); + } + + void class_base::setattr(char const* name, object const& x) + { + if (PyObject_SetAttrString(this->ptr(), const_cast<char*>(name), x.ptr()) < 0) + throw_error_already_set(); + } + + namespace + { + extern "C" PyObject* no_init(PyObject*, PyObject*) + { + ::PyErr_SetString(::PyExc_RuntimeError, const_cast<char*>("This class cannot be instantiated from Python")); + return NULL; + } + static ::PyMethodDef no_init_def = { + const_cast<char*>("__init__"), no_init, METH_VARARGS, + const_cast<char*>("Raises an exception\n" + "This class cannot be instantiated from Python\n") + }; + } + + void class_base::def_no_init() + { + handle<> f(::PyCFunction_New(&no_init_def, 0)); + this->setattr("__init__", object(f)); + } + + void class_base::enable_pickling_(bool getstate_manages_dict) + { + setattr("__safe_for_unpickling__", object(true)); + + if (getstate_manages_dict) + { + setattr("__getstate_manages_dict__", object(true)); + } + } + + namespace + { + PyObject* callable_check(PyObject* callable) + { + if (PyCallable_Check(expect_non_null(callable))) + return callable; + + ::PyErr_Format( + PyExc_TypeError + , const_cast<char*>("staticmethod expects callable object; got an object of type %s, which is not callable") + , Py_TYPE(callable)->tp_name + ); + + throw_error_already_set(); + return 0; + } + } + + void class_base::make_method_static(const char * method_name) + { + PyTypeObject* self = downcast<PyTypeObject>(this->ptr()); + dict d((handle<>(borrowed(self->tp_dict)))); + + object method(d[method_name]); + + this->attr(method_name) = object( + handle<>( + PyStaticMethod_New((callable_check)(method.ptr()) ) + )); + } + + BOOST_PYTHON_DECL type_handle registered_class_object(type_info id) + { + return query_class(id); + } +} // namespace objects + + +void* instance_holder::allocate(PyObject* self_, std::size_t holder_offset, std::size_t holder_size) +{ + assert(PyType_IsSubtype(Py_TYPE(Py_TYPE(self_)), &class_metatype_object)); + objects::instance<>* self = (objects::instance<>*)self_; + + int total_size_needed = holder_offset + holder_size; + + if (-Py_SIZE(self) >= total_size_needed) + { + // holder_offset should at least point into the variable-sized part + assert(holder_offset >= offsetof(objects::instance<>,storage)); + + // Record the fact that the storage is occupied, noting where it starts + Py_SIZE(self) = holder_offset; + return (char*)self + holder_offset; + } + else + { + void* const result = PyMem_Malloc(holder_size); + if (result == 0) + throw std::bad_alloc(); + return result; + } +} + +void instance_holder::deallocate(PyObject* self_, void* storage) throw() +{ + assert(PyType_IsSubtype(Py_TYPE(Py_TYPE(self_)), &class_metatype_object)); + objects::instance<>* self = (objects::instance<>*)self_; + if (storage != (char*)self + Py_SIZE(self)) + { + PyMem_Free(storage); + } +} + +}} // namespace boost::python |