Author: Matti Picus <matti.pi...@gmail.com> Branch: py3.5 Changeset: r94285:e3384cb52177 Date: 2018-04-10 00:30 +0300 http://bitbucket.org/pypy/pypy/changeset/e3384cb52177/
Log: merge default into py3.5 diff --git a/pypy/doc/release-v6.0.0.rst b/pypy/doc/release-v6.0.0.rst --- a/pypy/doc/release-v6.0.0.rst +++ b/pypy/doc/release-v6.0.0.rst @@ -8,26 +8,26 @@ the dual release. This release is a feature release following our previous 5.10 incremental -release in late December 2017, with many improvements in the C-API -compatability layer and other improvements in speed and CPython compatibility. -Since the changes affect the included python development header files, all -c-extension modules must be recompiled for this version. +release in late December 2017. Our C-API compatability layer ``cpyext`` is +now much faster (see the `blog post`_) as well as more complete. We have made +many other improvements in speed and CPython compatibility. Since the changes +affect the included python development header files, all c-extension modules must +be recompiled for this version. -The Windows PyPy3.5 release is still considered beta-quality. There are issues -with unicode handling especially around system calls and c-extensions. +The Windows PyPy3.5 release is still considered beta-quality. There are open +issues with unicode handling especially around system calls and c-extensions. -The Matplotlib TkAgg backend now works with PyPy. PyGame and pygtk also now can -work with PyPy. +The Matplotlib TkAgg backend now works with PyPy, as do pygame and pygobject. As always, this release is 100% compatible with the previous one and fixed several issues and bugs raised by the growing community of PyPy users. We strongly recommend updating. +We updated the cffi module included in PyPy to version 1.11.5 + The utf8 branch that changes internal representation of unicode to utf8 did not -make it into the release. We also began working on a Python3.6 implementation, -help is welcome. - -We updated the cffi module included in PyPy to version 1.11.5 +make it into the release, so there is still more goodness coming. We also +began working on a Python3.6 implementation, help is welcome. You can download the v6.0 releases here: @@ -46,6 +46,7 @@ .. _`RPython`: https://rpython.readthedocs.org .. _`modules`: project-ideas.html#make-more-python-modules-pypy-friendly .. _`help`: project-ideas.html +.. _`blog post`: https://morepypy.blogspot.it/2017/10/cape-of-good-hope-for-pypy-hello-from.html What is PyPy? ============= diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst --- a/pypy/doc/whatsnew-head.rst +++ b/pypy/doc/whatsnew-head.rst @@ -5,4 +5,8 @@ .. this is a revision shortly after release-pypy-6.0.0 .. startrev: 2e04adf1b89f +.. branch: cpyext-subclass-setattr +Fix for python-level classes that inherit from C-API types, previously the +`w_obj` was not necessarily preserved throughout the lifetime of the `pyobj` +which led to cases where instance attributes were lost. Fixes issue #2793 diff --git a/pypy/interpreter/test/test_typedef.py b/pypy/interpreter/test/test_typedef.py --- a/pypy/interpreter/test/test_typedef.py +++ b/pypy/interpreter/test/test_typedef.py @@ -423,3 +423,10 @@ def test_get_with_none_arg(self): raises(TypeError, type.__dict__['__mro__'].__get__, None) raises(TypeError, type.__dict__['__mro__'].__get__, None, None) + + def test_builtin_readonly_property(self): + import sys + x = lambda: 5 + e = raises(AttributeError, 'x.func_globals = {}') + if '__pypy__' in sys.builtin_module_names: + assert str(e.value) == "readonly attribute 'func_globals'" diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py --- a/pypy/interpreter/typedef.py +++ b/pypy/interpreter/typedef.py @@ -313,12 +313,18 @@ self.reqcls, Arguments(space, [w_obj, space.newtext(self.name)])) + def readonly_attribute(self, space): # overwritten in cpyext + if self.name == '<generic property>': + raise oefmt(space.w_AttributeError, "readonly attribute") + else: + raise oefmt(space.w_AttributeError, "readonly attribute '%s'", self.name) + def descr_property_set(self, space, w_obj, w_value): """property.__set__(obj, value) Change the value of the property of the given obj.""" fset = self.fset if fset is None: - raise oefmt(space.w_AttributeError, "readonly attribute") + raise self.readonly_attribute(space) try: fset(self, space, w_obj, w_value) except DescrMismatch: diff --git a/pypy/module/cpyext/pyobject.py b/pypy/module/cpyext/pyobject.py --- a/pypy/module/cpyext/pyobject.py +++ b/pypy/module/cpyext/pyobject.py @@ -60,10 +60,10 @@ def _cpyext_attach_pyobj(self, space, py_obj): self._cpy_ref = py_obj - rawrefcount.create_link_pyobj(self, py_obj) + rawrefcount.create_link_pypy(self, py_obj) cls._cpyext_attach_pyobj = _cpyext_attach_pyobj -add_direct_pyobj_storage(W_BaseCPyObject) +add_direct_pyobj_storage(W_BaseCPyObject) add_direct_pyobj_storage(W_TypeObject) add_direct_pyobj_storage(W_NoneObject) add_direct_pyobj_storage(W_BoolObject) @@ -415,3 +415,14 @@ @cpython_api([rffi.VOIDP], lltype.Signed, error=CANNOT_FAIL) def _Py_HashPointer(space, ptr): return rffi.cast(lltype.Signed, ptr) + +@cpython_api([PyObject], lltype.Void) +def Py_IncRef(space, obj): + # used only ifdef PYPY_DEBUG_REFCOUNT + if obj: + incref(space, obj) + +@cpython_api([PyObject], lltype.Void) +def Py_DecRef(space, obj): + # used only ifdef PYPY_DEBUG_REFCOUNT + decref(space, obj) diff --git a/pypy/module/cpyext/src/object.c b/pypy/module/cpyext/src/object.c --- a/pypy/module/cpyext/src/object.c +++ b/pypy/module/cpyext/src/object.c @@ -5,18 +5,6 @@ extern void _PyPy_Free(void *ptr); extern void *_PyPy_Malloc(Py_ssize_t size); -void -Py_IncRef(PyObject *o) -{ - Py_XINCREF(o); -} - -void -Py_DecRef(PyObject *o) -{ - Py_XDECREF(o); -} - /* * The actual value of this variable will be the address of * pyobject.w_marker_deallocating, and will be set by diff --git a/pypy/module/cpyext/test/array.c b/pypy/module/cpyext/test/array.c --- a/pypy/module/cpyext/test/array.c +++ b/pypy/module/cpyext/test/array.c @@ -2936,6 +2936,87 @@ return PyLong_FromLong(obj1->ob_type->tp_dealloc == obj2->ob_type->tp_dealloc); } +static PyObject * +subclass_with_attribute(PyObject *self, PyObject* args) { + /* what happens when we use tp_alloc to create the subclass, then + * assign to the w_obj via python, then get the GC to collect? + * The w_obj should not be collected!! + */ + PyObject * obj, *sub, *attrib, *funcname, *attribname, *collect, *res, *tup; + PyTypeObject * subtype; + int i; + if (!PyArg_ParseTuple(args, "OOOO", &obj, &funcname, &attribname, &collect)) { + return NULL; + } + if (!PyType_Check(obj)) { + PyErr_SetString(PyExc_TypeError, + "expected type object"); + return NULL; + } + subtype = (PyTypeObject*)obj; + sub = subtype->tp_alloc(subtype, 0); + if (!sub) { + return NULL; + } + attrib = PyObject_GetAttr(sub, funcname); + if (!attrib || (attrib == Py_None) ) { + PyErr_SetString(PyExc_ValueError, + "could not find function to call"); + Py_XDECREF(attrib); + Py_DECREF(sub); + return NULL; + } + tup = PyTuple_New(0); + /* + #ifdef PYPY_VERSION + printf("calling addattrib pypylink %lu \n", sub->ob_pypy_link); + #endif + */ + res = PyObject_Call(attrib, tup, NULL); + /* + #ifdef PYPY_VERSION + printf("after addattrib pypylink %lu \n", sub->ob_pypy_link); + #endif + */ + Py_DECREF(attrib); + if (res == NULL) { + Py_DECREF(tup); + Py_DECREF(sub); + return NULL; + } + Py_DECREF(res); + for(i=0; i<10; i++) { + /* + #ifdef PYPY_VERSION + printf("starting loop iteration %d refcnt %lu pypylink %lu \n", i, + sub->ob_refcnt, sub->ob_pypy_link); + #else + printf("starting loop iteration %d refcnt %lu\n", i, sub->ob_refcnt); + #endif + */ + attrib = PyObject_GetAttr(sub, attribname); + if (!attrib || (attrib == Py_None)) { + PyErr_SetString(PyExc_ValueError, + "could not find attrib on object"); + Py_XDECREF(attrib); + Py_DECREF(tup); + Py_DECREF(sub); + return NULL; + } + Py_XDECREF(attrib); + res = PyObject_Call(collect, tup, NULL); + if (res == NULL) { + Py_DECREF(tup); + Py_DECREF(sub); + return NULL; + } + Py_DECREF(res); + } + Py_DECREF(tup); + Py_DECREF(sub); + Py_RETURN_NONE; +} + /*********************** Install Module **************************/ static PyMethodDef a_methods[] = { @@ -2948,6 +3029,7 @@ {"write_buffer_len", write_buffer_len, METH_O, NULL}, {"same_dealloc", (PyCFunction)same_dealloc, METH_VARARGS, NULL}, {"getitem", (PyCFunction)getitem, METH_VARARGS, NULL}, + {"subclass_with_attribute", (PyCFunction)subclass_with_attribute, METH_VARARGS, NULL}, {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/pypy/module/cpyext/test/test_arraymodule.py b/pypy/module/cpyext/test/test_arraymodule.py --- a/pypy/module/cpyext/test/test_arraymodule.py +++ b/pypy/module/cpyext/test/test_arraymodule.py @@ -179,3 +179,15 @@ # array_subscr does) raises(IndexError, module.getitem, a, -5) + def test_subclass_with_attribute(self): + module = self.import_module(name='array') + class Sub(module.array): + def addattrib(self): + print('called addattrib') + self.attrib = True + import gc + module.subclass_with_attribute(Sub, "addattrib", "attrib", gc.collect) + if self.runappdirect: + assert Sub.__module__ == 'pypy.module.cpyext.test.test_arraymodule' + assert str(Sub) == "<class 'pypy.module.cpyext.test.test_arraymodule.Sub'>" + diff --git a/pypy/module/cpyext/test/test_tupleobject.py b/pypy/module/cpyext/test/test_tupleobject.py --- a/pypy/module/cpyext/test/test_tupleobject.py +++ b/pypy/module/cpyext/test/test_tupleobject.py @@ -67,6 +67,18 @@ assert space.int_w(space.getitem(w_tuple, space.wrap(i))) == 42 + i decref(space, ar[0]) + py_tuple = state.ccall("PyTuple_New", 1) + ar[0] = py_tuple + api._PyTuple_Resize(ar, 1) + assert api.PyTuple_Size(ar[0]) == 1 + decref(space, ar[0]) + + py_tuple = state.ccall("PyTuple_New", 1) + ar[0] = py_tuple + api._PyTuple_Resize(ar, 5) + assert api.PyTuple_Size(ar[0]) == 5 + decref(space, ar[0]) + lltype.free(ar, flavor='raw') def test_setitem(self, space, api): diff --git a/pypy/module/cpyext/test/test_typeobject.py b/pypy/module/cpyext/test/test_typeobject.py --- a/pypy/module/cpyext/test/test_typeobject.py +++ b/pypy/module/cpyext/test/test_typeobject.py @@ -5,7 +5,7 @@ from pypy.module.cpyext.test.test_api import BaseApiTest from pypy.module.cpyext.api import generic_cpy_call from pypy.module.cpyext.pyobject import make_ref, from_ref, decref, as_pyobj -from pypy.module.cpyext.typeobject import cts, PyTypeObjectPtr +from pypy.module.cpyext.typeobject import cts, PyTypeObjectPtr, W_PyCTypeObject @@ -410,33 +410,42 @@ def test_type_dict(self): foo = self.import_module("foo") module = self.import_extension('test', [ - ("hack_tp_dict", "METH_O", + ("hack_tp_dict", "METH_VARARGS", ''' - PyTypeObject *type = args->ob_type; + PyTypeObject *type, *obj; PyObject *a1 = PyLong_FromLong(1); PyObject *a2 = PyLong_FromLong(2); PyObject *value; + PyObject * key; + if (!PyArg_ParseTuple(args, "OO", &obj, &key)) + return NULL; + type = obj->ob_type; - if (PyDict_SetItemString(type->tp_dict, "a", + if (PyDict_SetItem(type->tp_dict, key, a1) < 0) return NULL; Py_DECREF(a1); PyType_Modified(type); - value = PyObject_GetAttrString((PyObject *)type, "a"); + value = PyObject_GetAttr((PyObject *)type, key); Py_DECREF(value); - if (PyDict_SetItemString(type->tp_dict, "a", + if (PyDict_SetItem(type->tp_dict, key, a2) < 0) return NULL; Py_DECREF(a2); PyType_Modified(type); - value = PyObject_GetAttrString((PyObject *)type, "a"); + value = PyObject_GetAttr((PyObject *)type, key); return value; ''' ) ]) obj = foo.new() - assert module.hack_tp_dict(obj) == 2 + assert module.hack_tp_dict(obj, "a") == 2 + class Sub(foo.fooType): + pass + obj = Sub() + assert module.hack_tp_dict(obj, "b") == 2 + def test_tp_descr_get(self): module = self.import_extension('foo', [ @@ -572,6 +581,23 @@ def test_typeslots(self, space): assert cts.macros['Py_tp_doc'] == 56 + def test_subclass_not_PyCTypeObject(self, space, api): + pyobj = make_ref(space, api.PyLong_Type) + py_type = rffi.cast(PyTypeObjectPtr, pyobj) + w_pyclass = W_PyCTypeObject(space, py_type) + w_class = space.appexec([w_pyclass], """(base): + class Sub(base): + def addattrib(self, value): + self.attrib = value + return Sub + """) + assert w_pyclass in w_class.mro_w + assert isinstance(w_pyclass, W_PyCTypeObject) + assert not isinstance(w_class, W_PyCTypeObject) + assert w_pyclass.is_cpytype() + # XXX document the current status, not clear if this is desirable + assert w_class.is_cpytype() + class AppTestSlots(AppTestCpythonExtensionBase): def setup_class(cls): @@ -1558,6 +1584,64 @@ pass C(42) # assert is not aborting + def test_getset(self): + module = self.import_extension('foo', [ + ("get_instance", "METH_NOARGS", + ''' + return PyObject_New(PyObject, &Foo_Type); + ''' + ), ("get_number", "METH_NOARGS", + ''' + return PyInt_FromLong(my_global_number); + ''' + )], prologue=''' + #if PY_MAJOR_VERSION > 2 + #define PyInt_FromLong PyLong_FromLong + #endif + static long my_global_number; + static PyTypeObject Foo_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "foo.foo", + }; + static PyObject *bar_get(PyObject *foo, void *closure) + { + return PyInt_FromLong(1000 + (long)closure); + } + static PyObject *baz_get(PyObject *foo, void *closure) + { + return PyInt_FromLong(2000 + (long)closure); + } + static int baz_set(PyObject *foo, PyObject *x, void *closure) + { + if (x != NULL) + my_global_number = 3000 + (long)closure + PyInt_AsLong(x); + else + my_global_number = 4000 + (long)closure; + return 0; + } + static PyGetSetDef foo_getset[] = { + { "bar", bar_get, NULL, "mybardoc", (void *)42 }, + { "baz", baz_get, baz_set, "mybazdoc", (void *)43 }, + { NULL } + }; + ''', more_init = ''' + Foo_Type.tp_getset = foo_getset; + Foo_Type.tp_flags = Py_TPFLAGS_DEFAULT; + if (PyType_Ready(&Foo_Type) < 0) INITERROR; + ''') + foo = module.get_instance() + assert foo.bar == 1042 + assert foo.bar == 1042 + assert foo.baz == 2043 + foo.baz = 50000 + assert module.get_number() == 53043 + e = raises(AttributeError, "foo.bar = 0") + assert str(e.value).startswith("attribute 'bar' of '") + assert str(e.value).endswith("foo' objects is not writable") + del foo.baz + assert module.get_number() == 4043 + raises(AttributeError, "del foo.bar") + class AppTestHashable(AppTestCpythonExtensionBase): def test_unhashable(self): diff --git a/pypy/module/cpyext/tupleobject.py b/pypy/module/cpyext/tupleobject.py --- a/pypy/module/cpyext/tupleobject.py +++ b/pypy/module/cpyext/tupleobject.py @@ -187,6 +187,8 @@ PyErr_BadInternalCall(space) oldref = rffi.cast(PyTupleObject, ref) oldsize = oldref.c_ob_size + if oldsize == newsize: + return 0 ptup = state.ccall("PyTuple_New", newsize) if not ptup: state.check_and_raise_exception(always=True) @@ -199,8 +201,9 @@ to_cp = newsize for i in range(to_cp): ob = oldref.c_ob_item[i] - incref(space, ob) - newref.c_ob_item[i] = ob + if ob: + incref(space, ob) + newref.c_ob_item[i] = ob except: decref(space, p_ref[0]) p_ref[0] = lltype.nullptr(PyObject.TO) diff --git a/pypy/module/cpyext/typeobject.py b/pypy/module/cpyext/typeobject.py --- a/pypy/module/cpyext/typeobject.py +++ b/pypy/module/cpyext/typeobject.py @@ -1,5 +1,5 @@ from rpython.rlib.unroll import unrolling_iterable -from rpython.rlib import jit +from rpython.rlib import jit, rawrefcount from rpython.rlib.objectmodel import specialize, we_are_translated from rpython.rtyper.lltypesystem import rffi, lltype @@ -57,19 +57,26 @@ class W_GetSetPropertyEx(GetSetProperty): def __init__(self, getset, w_type): self.getset = getset - self.name = rffi.charp2str(getset.c_name) self.w_type = w_type - doc = set = get = None + doc = fset = fget = fdel = None if doc: # XXX dead code? doc = rffi.charp2str(getset.c_doc) if getset.c_get: - get = GettersAndSetters.getter.im_func + fget = GettersAndSetters.getter.im_func if getset.c_set: - set = GettersAndSetters.setter.im_func - GetSetProperty.__init__(self, get, set, None, doc, + fset = GettersAndSetters.setter.im_func + fdel = GettersAndSetters.deleter.im_func + GetSetProperty.__init__(self, fget, fset, fdel, doc, cls=None, use_closure=True, tag="cpyext_1") + self.name = rffi.charp2str(getset.c_name) + + def readonly_attribute(self, space): # overwritten + raise oefmt(space.w_AttributeError, + "attribute '%s' of '%N' objects is not writable", + self.name, self.w_type) + def PyDescr_NewGetSet(space, getset, w_type): return W_GetSetPropertyEx(getset, w_type) @@ -455,6 +462,16 @@ state = space.fromcache(State) state.check_and_raise_exception() + def deleter(self, space, w_self): + assert isinstance(self, W_GetSetPropertyEx) + check_descr(space, w_self, self.w_type) + res = generic_cpy_call( + space, self.getset.c_set, w_self, None, + self.getset.c_closure) + if rffi.cast(lltype.Signed, res) < 0: + state = space.fromcache(State) + state.check_and_raise_exception() + def member_getter(self, space, w_self): assert isinstance(self, W_MemberDescr) check_descr(space, w_self, self.w_type) @@ -518,6 +535,10 @@ self.w_doc = space.newtext_or_none(extract_doc(rawdoc, name)) self.text_signature = extract_txtsig(rawdoc, name) + def _cpyext_attach_pyobj(self, space, py_obj): + self._cpy_ref = py_obj + rawrefcount.create_link_pyobj(self, py_obj) + @bootstrap_function def init_typeobject(space): make_typedescr(space.w_type.layout.typedef, @@ -667,7 +688,6 @@ try: w_obj = _type_realize(space, py_obj) finally: - name = rffi.charp2str(cts.cast('char*', pto.c_tp_name)) pto.c_tp_flags &= ~Py_TPFLAGS_READYING pto.c_tp_flags |= Py_TPFLAGS_READY return w_obj @@ -762,7 +782,6 @@ base = pto.c_tp_base base_pyo = rffi.cast(PyObject, pto.c_tp_base) if base and not base.c_tp_flags & Py_TPFLAGS_READY: - name = rffi.charp2str(cts.cast('char*', base.c_tp_name)) type_realize(space, base_pyo) if base and not pto.c_ob_type: # will be filled later pto.c_ob_type = base.c_ob_type _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit