Author: Armin Rigo <[email protected]>
Branch: cpyext-avoid-roundtrip
Changeset: r92716:0d92b4ee9763
Date: 2017-10-11 12:18 +0200
http://bitbucket.org/pypy/pypy/changeset/0d92b4ee9763/

Log:    (antocuni, ronan, arigo)

        Add hack_for_result_often_existing_obj(), used in a few functions
        which officially return a new reference, but where the caller might
        be bogus code that expects the new reference to stay alive even
        after a Py_DecRef() (which is "often" the case in CPython)

diff --git a/pypy/module/cpyext/mapping.py b/pypy/module/cpyext/mapping.py
--- a/pypy/module/cpyext/mapping.py
+++ b/pypy/module/cpyext/mapping.py
@@ -1,7 +1,8 @@
 from rpython.rtyper.lltypesystem import lltype, rffi
 from pypy.module.cpyext.api import (
     cpython_api, CANNOT_FAIL, CONST_STRING, Py_ssize_t)
-from pypy.module.cpyext.pyobject import PyObject
+from pypy.module.cpyext.pyobject import (
+    PyObject, hack_for_result_often_existing_obj)
 
 
 @cpython_api([PyObject], rffi.INT_real, error=CANNOT_FAIL)
@@ -37,12 +38,13 @@
     the Python expression o.items()."""
     return space.call_method(w_obj, "items")
 
-@cpython_api([PyObject, CONST_STRING], PyObject)
+@cpython_api([PyObject, CONST_STRING], PyObject, result_is_ll=True)
 def PyMapping_GetItemString(space, w_obj, key):
     """Return element of o corresponding to the object key or NULL on failure.
     This is the equivalent of the Python expression o[key]."""
     w_key = space.newtext(rffi.charp2str(key))
-    return space.getitem(w_obj, w_key)
+    w_res = space.getitem(w_obj, w_key)
+    return hack_for_result_often_existing_obj(space, w_res)
 
 @cpython_api([PyObject, CONST_STRING, PyObject], rffi.INT_real, error=-1)
 def PyMapping_SetItemString(space, w_obj, key, w_value):
diff --git a/pypy/module/cpyext/object.py b/pypy/module/cpyext/object.py
--- a/pypy/module/cpyext/object.py
+++ b/pypy/module/cpyext/object.py
@@ -6,7 +6,7 @@
     Py_GE, CONST_STRING, FILEP, fwrite, c_only)
 from pypy.module.cpyext.pyobject import (
     PyObject, PyObjectP, from_ref, incref, decref,
-    get_typedescr)
+    get_typedescr, hack_for_result_often_existing_obj)
 from pypy.module.cpyext.typeobject import PyTypeObjectPtr
 from pypy.module.cpyext.pyerrors import PyErr_NoMemory, PyErr_BadInternalCall
 from pypy.objspace.std.typeobject import W_TypeObject
@@ -69,20 +69,22 @@
 def PyObject_Not(space, w_obj):
     return not space.is_true(w_obj)
 
-@cpython_api([PyObject, PyObject], PyObject)
+@cpython_api([PyObject, PyObject], PyObject, result_is_ll=True)
 def PyObject_GetAttr(space, w_obj, w_name):
     """Retrieve an attribute named attr_name from object o. Returns the 
attribute
     value on success, or NULL on failure.  This is the equivalent of the Python
     expression o.attr_name."""
-    return space.getattr(w_obj, w_name)
+    w_res = space.getattr(w_obj, w_name)
+    return hack_for_result_often_existing_obj(space, w_res)
 
-@cpython_api([PyObject, CONST_STRING], PyObject)
+@cpython_api([PyObject, CONST_STRING], PyObject, result_is_ll=True)
 def PyObject_GetAttrString(space, w_obj, name_ptr):
     """Retrieve an attribute named attr_name from object o. Returns the 
attribute
     value on success, or NULL on failure. This is the equivalent of the Python
     expression o.attr_name."""
     name = rffi.charp2str(name_ptr)
-    return space.getattr(w_obj, space.newtext(name))
+    w_res = space.getattr(w_obj, space.newtext(name))
+    return hack_for_result_often_existing_obj(space, w_res)
 
 @cpython_api([PyObject, PyObject], rffi.INT_real, error=CANNOT_FAIL)
 def PyObject_HasAttr(space, w_obj, w_name):
@@ -141,11 +143,12 @@
     and 0 otherwise.  This function always succeeds."""
     return int(space.is_true(space.callable(w_obj)))
 
-@cpython_api([PyObject, PyObject], PyObject)
+@cpython_api([PyObject, PyObject], PyObject, result_is_ll=True)
 def PyObject_GetItem(space, w_obj, w_key):
     """Return element of o corresponding to the object key or NULL on failure.
     This is the equivalent of the Python expression o[key]."""
-    return space.getitem(w_obj, w_key)
+    w_res = space.getitem(w_obj, w_key)
+    return hack_for_result_often_existing_obj(space, w_res)
 
 @cpython_api([PyObject, PyObject, PyObject], rffi.INT_real, error=-1)
 def PyObject_SetItem(space, w_obj, w_key, w_value):
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
@@ -286,6 +286,13 @@
         keepalive_until_here(w_obj)
     return pyobj
 
+def hack_for_result_often_existing_obj(space, w_obj):
+    # Equivalent to get_pyobj_and_incref() and not to make_ref():
+    # it builds a PyObject from a W_Root, but ensures that the result
+    # gets attached to the original W_Root.  This is needed to work around
+    # some obscure abuses: https://github.com/numpy/numpy/issues/9850
+    return get_pyobj_and_incref(space, w_obj)
+
 def make_ref(space, w_obj, w_userdata=None, immortal=False):
     """Turn the W_Root into a corresponding PyObject.  You should
     decref the returned PyObject later.  Note that it is often the
diff --git a/pypy/module/cpyext/test/test_mapping.py 
b/pypy/module/cpyext/test/test_mapping.py
--- a/pypy/module/cpyext/test/test_mapping.py
+++ b/pypy/module/cpyext/test/test_mapping.py
@@ -1,5 +1,8 @@
 from pypy.module.cpyext.test.test_api import BaseApiTest
+from pypy.module.cpyext.test.test_cpyext import AppTestCpythonExtensionBase
 from rpython.rtyper.lltypesystem import lltype, rffi
+from pypy.module.cpyext.pyobject import get_w_obj_and_decref
+
 
 class TestMapping(BaseApiTest):
     def test_check(self, space, api):
@@ -25,8 +28,8 @@
         w_d = space.newdict()
         key = rffi.str2charp("key")
         api.PyMapping_SetItemString(w_d, key, space.wrap(42))
-        assert 42 == space.unwrap(
-            api.PyMapping_GetItemString(w_d, key))
+        assert 42 == space.unwrap(get_w_obj_and_decref(space,
+            api.PyMapping_GetItemString(w_d, key)))
         rffi.free_charp(key)
 
     def test_haskey(self, space, api):
@@ -38,3 +41,23 @@
 
         assert api.PyMapping_HasKey(w_d, w_d) == 0
         # and no error is set
+
+
+class AppTestMapping(AppTestCpythonExtensionBase):
+
+    def test_getitemstring_returns_new_but_borrowed_ref(self):
+        module = self.import_extension('foo', [
+           ("test_mapping", "METH_O",
+            '''
+                PyObject *value = PyMapping_GetItemString(args, "a");
+                /* officially, "value" can have a refcount equal to one,
+                   but some code out there assumes that it has a refcnt
+                   of at least two --- which is bogus --- because it
+                   is generally kept alive by the container. */
+                PyObject *refcnt = PyInt_FromLong(value->ob_refcnt);
+                Py_DECREF(value);
+                return refcnt;
+            '''),])
+        d = {"a": 42}
+        res = module.test_mapping(d)
+        assert res > 1
diff --git a/pypy/module/cpyext/test/test_object.py 
b/pypy/module/cpyext/test/test_object.py
--- a/pypy/module/cpyext/test/test_object.py
+++ b/pypy/module/cpyext/test/test_object.py
@@ -3,6 +3,7 @@
 from pypy.module.cpyext.test.test_api import BaseApiTest, raises_w
 from pypy.module.cpyext.test.test_cpyext import AppTestCpythonExtensionBase
 from rpython.rtyper.lltypesystem import rffi, lltype
+from pypy.module.cpyext.pyobject import get_w_obj_and_decref
 from pypy.module.cpyext.api import (
     Py_LT, Py_LE, Py_NE, Py_EQ, Py_GE, Py_GT)
 from pypy.module.cpyext.object import (
@@ -70,7 +71,8 @@
     def test_getattr(self, space):
         charp1 = rffi.str2charp("__len__")
         charp2 = rffi.str2charp("not_real")
-        assert PyObject_GetAttrString(space, space.wrap(""), charp1)
+        assert get_w_obj_and_decref(space,
+            PyObject_GetAttrString(space, space.wrap(""), charp1))
 
         with raises_w(space, AttributeError):
             PyObject_GetAttrString(space, space.wrap(""), charp2)
@@ -79,17 +81,20 @@
         rffi.free_charp(charp1)
         rffi.free_charp(charp2)
 
-        assert PyObject_GetAttr(space, space.wrap(""), space.wrap("__len__"))
+        assert get_w_obj_and_decref(space,
+            PyObject_GetAttr(space, space.wrap(""), space.wrap("__len__")))
         with raises_w(space, AttributeError):
             PyObject_DelAttr(space, space.wrap(""), space.wrap("__len__"))
 
     def test_getitem(self, space, api):
         w_t = space.wrap((1, 2, 3, 4, 5))
-        assert space.unwrap(api.PyObject_GetItem(w_t, space.wrap(3))) == 4
+        assert space.unwrap(get_w_obj_and_decref(space,
+            api.PyObject_GetItem(w_t, space.wrap(3)))) == 4
 
         w_d = space.newdict()
         space.setitem(w_d, space.wrap("a key!"), space.wrap(72))
-        assert space.unwrap(api.PyObject_GetItem(w_d, space.wrap("a key!"))) 
== 72
+        assert space.unwrap(get_w_obj_and_decref(space,
+            api.PyObject_GetItem(w_d, space.wrap("a key!")))) == 72
 
         assert api.PyObject_SetItem(w_d, space.wrap("key"), space.w_None) == 0
         assert space.getitem(w_d, space.wrap("key")) is space.w_None
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to