Author: Armin Rigo <ar...@tunes.org> Branch: Changeset: r91446:789fb549e0af Date: 2017-05-30 13:37 +0200 http://bitbucket.org/pypy/pypy/changeset/789fb549e0af/
Log: merge heads diff --git a/pypy/doc/cpython_differences.rst b/pypy/doc/cpython_differences.rst --- a/pypy/doc/cpython_differences.rst +++ b/pypy/doc/cpython_differences.rst @@ -357,6 +357,26 @@ .. __: https://bitbucket.org/pypy/pypy/issue/1974/different-behaviour-for-collections-of +C-API Differences +----------------- + +The external C-API has been reimplemented in PyPy as an internal cpyext module. +We support most of the documented C-API, but sometimes internal C-abstractions +leak out on CPython and are abused, perhaps even unknowingly. For instance, +assignment to a ``PyTupleObject`` is not supported after the tuple is +used internally, even by another C-API function call. On CPython this will +succeed as long as the refcount is 1. On PyPy this will always raise a +``SystemError('PyTuple_SetItem called on tuple after use of tuple")`` +exception (explicitly listed here for search engines). + +Another similar problem is assignment of a new function pointer to any of the +``tp_as_*`` structures after calling ``PyType_Ready``. For instance, overriding +``tp_as_number.nb_int`` with a different function after calling ``PyType_Ready`` +on CPython will result in the old function being called for ``x.__int__()`` +(via class ``__dict__`` lookup) and the new function being called for ``int(x)`` +(via slot lookup). On PyPy we will always call the __new__ function, not the +old, this quirky behaviour is unfortunately necessary to fully support NumPy. + Performance Differences ------------------------- 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 @@ -161,3 +161,47 @@ assert list(a) == range(100, 400, 100) assert list(a) == range(100, 400, 100) assert list(a) == range(100, 400, 100) + + def test_setitem(self): + module = self.import_extension('foo', [ + ("set_after_use", "METH_O", + """ + PyObject *t2, *tuple = PyTuple_New(1); + PyObject * one = PyLong_FromLong(1); + int res; + Py_INCREF(one); + res = PyTuple_SetItem(tuple, 0, one); + if (res != 0) + { + Py_DECREF(tuple); + return NULL; + } + Py_INCREF(args); + res = PyTuple_SetItem(tuple, 0, args); + if (res != 0) + { + Py_DECREF(tuple); + return NULL; + } + /* Do something that uses the tuple, but does not incref */ + t2 = PyTuple_GetSlice(tuple, 0, 1); + Py_DECREF(t2); + Py_INCREF(one); + res = PyTuple_SetItem(tuple, 0, one); + Py_DECREF(tuple); + if (res != 0) + { + Py_DECREF(one); + return NULL; + } + Py_INCREF(Py_None); + return Py_None; + """), + ]) + import sys + s = 'abc' + if '__pypy__' in sys.builtin_module_names: + raises(SystemError, module.set_after_use, s) + else: + module.set_after_use(s) + 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 @@ -6,7 +6,7 @@ PyVarObjectFields, cpython_struct, bootstrap_function, slot_function) from pypy.module.cpyext.pyobject import ( PyObject, PyObjectP, make_ref, from_ref, decref, incref, - track_reference, make_typedescr, get_typedescr) + track_reference, make_typedescr, get_typedescr, pyobj_has_w_obj) from pypy.module.cpyext.pyerrors import PyErr_BadInternalCall from pypy.objspace.std.tupleobject import W_TupleObject @@ -132,19 +132,20 @@ @cpython_api([PyObject, Py_ssize_t, PyObject], rffi.INT_real, error=-1) def PyTuple_SetItem(space, ref, index, py_obj): - # XXX this will not complain when changing tuples that have - # already been realized as a W_TupleObject, but won't update the - # W_TupleObject if not tuple_check_ref(space, ref): decref(space, py_obj) PyErr_BadInternalCall(space) - ref = rffi.cast(PyTupleObject, ref) - size = ref.c_ob_size + tupleobj = rffi.cast(PyTupleObject, ref) + size = tupleobj.c_ob_size if index < 0 or index >= size: decref(space, py_obj) raise oefmt(space.w_IndexError, "tuple assignment index out of range") - old_ref = ref.c_ob_item[index] - ref.c_ob_item[index] = py_obj # consumes a reference + old_ref = tupleobj.c_ob_item[index] + if pyobj_has_w_obj(ref): + # similar but not quite equal to ref.c_ob_refcnt != 1 on CPython + raise oefmt(space.w_SystemError, "PyTuple_SetItem called on tuple after" + " use of tuple") + tupleobj.c_ob_item[index] = py_obj # consumes a reference if old_ref: decref(space, old_ref) return 0 _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit