[pypy-commit] pypy cpyext-tp_dictoffset: test, fix setting tp_dictoffset as indicator for mutable attributes (cython)

mattip pypy.commits at gmail.com
Mon Sep 11 11:45:27 EDT 2017


Author: Matti Picus <matti.picus at gmail.com>
Branch: cpyext-tp_dictoffset
Changeset: r92369:56afd2a1b1e9
Date: 2017-09-09 23:58 +0300
http://bitbucket.org/pypy/pypy/changeset/56afd2a1b1e9/

Log:	test, fix setting tp_dictoffset as indicator for mutable attributes
	(cython)

diff --git a/pypy/module/cpyext/api.py b/pypy/module/cpyext/api.py
--- a/pypy/module/cpyext/api.py
+++ b/pypy/module/cpyext/api.py
@@ -1206,6 +1206,8 @@
     py_obj.c_ob_type = rffi.cast(PyTypeObjectPtr,
                                  make_ref(space, w_type))
     typedescr.attach(space, py_obj, w_obj)
+    # prevent PyObject_SetAttr on built-ins
+    rffi.cast(PyTypeObjectPtr, py_obj).c_tp_dictoffset = 0
     attached_objs.append(i)
 
 
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)
 from pypy.module.cpyext.pyobject import (
     PyObject, PyObjectP, from_ref, Py_IncRef, Py_DecRef,
-    get_typedescr)
+    get_typedescr, as_pyobj)
 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
@@ -122,12 +122,20 @@
 
 @cpython_api([PyObject, PyObject, PyObject], rffi.INT_real, error=-1)
 def PyObject_SetAttr(space, w_obj, w_name, w_value):
+    pyobj = as_pyobj(space, w_obj) # assumes w_obj is kept alive for the call
+    if (pyobj.c_ob_type.c_tp_dictoffset == 0):
+        raise oefmt(space.w_AttributeError,
+             "object has no attribute %s", space.text_w(w_name))
     operation.setattr(space, w_obj, w_name, w_value)
     return 0
 
 @cpython_api([PyObject, CONST_STRING, PyObject], rffi.INT_real, error=-1)
 def PyObject_SetAttrString(space, w_obj, name_ptr, w_value):
     w_name = space.newtext(rffi.charp2str(name_ptr))
+    pyobj = as_pyobj(space, w_obj) # assumes w_obj is kept alive for the call
+    if (pyobj.c_ob_type.c_tp_dictoffset == 0):
+        raise oefmt(space.w_AttributeError,
+             "object has no attribute %s", space.text_w(w_name))
     operation.setattr(space, w_obj, w_name, w_value)
     return 0
 
@@ -135,6 +143,10 @@
 def PyObject_DelAttr(space, w_obj, w_name):
     """Delete attribute named attr_name, for object o. Returns -1 on failure.
     This is the equivalent of the Python statement del o.attr_name."""
+    pyobj = as_pyobj(space, w_obj) # assumes w_obj is kept alive for the call
+    if (pyobj.c_ob_type.c_tp_dictoffset == 0):
+        raise oefmt(space.w_AttributeError,
+             "object has no attribute %s", space.text_w(w_name))
     space.delattr(w_obj, w_name)
     return 0
 
@@ -143,6 +155,10 @@
     """Delete attribute named attr_name, for object o. Returns -1 on failure.
     This is the equivalent of the Python statement del o.attr_name."""
     w_name = space.newtext(rffi.charp2str(name_ptr))
+    pyobj = as_pyobj(space, w_obj) # assumes w_obj is kept alive for the call
+    if (pyobj.c_ob_type.c_tp_dictoffset == 0):
+        raise oefmt(space.w_AttributeError,
+             "object has no attribute %s", space.text_w(w_name))
     space.delattr(w_obj, w_name)
     return 0
 
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
@@ -276,7 +276,7 @@
                  PyObject *method;
                  if (!args->ob_type->tp_dict)
                  {
-                     PyErr_SetNone(PyExc_ValueError);
+                     PyErr_SetString(PyExc_ValueError, "no tp_dict");
                      return NULL;
                  }
                  method = PyDict_GetItemString(
@@ -291,25 +291,54 @@
                 Py_INCREF(value);
                 return value;
              '''),
+            ('set_attr', "METH_VARARGS",
+             '''
+                PyObject * v, *name, *w;
+                int ret;
+                if (!PyArg_ParseTuple(args, "OOO", &v, &name, &w))
+                    return NULL;
+                ret = PyObject_SetAttr(v, name, w);
+                if (ret != 0)
+                    return NULL;
+                Py_RETURN_NONE;
+             '''),   
+            ('get_tp_dictoffset', "METH_O",
+             '''
+                return PyLong_FromLong(args->ob_type->tp_dictoffset);
+             '''),
             ])
         obj = foo.new()
+        assert module.get_tp_dictoffset(obj) == 0
         assert module.read_tp_dict(obj) == foo.fooType.copy
         d = module.get_type_dict(obj)
         assert type(d) is dict
         d["_some_attribute"] = 1
         assert type(obj)._some_attribute == 1
+        assert module.get_tp_dictoffset(obj) == 0
+        del d["_some_attribute"]
+        assert module.get_tp_dictoffset(obj) == 0
+
+        def method(self):
+            return 42
+
+        exc = raises(AttributeError, module.set_attr, obj, 'meth', method)
+        assert 'object has no attribute' in str(exc.value)
+
+        class A(object):
+            pass
+        obj = A()
+        assert module.get_tp_dictoffset(obj) > 0
+        module.set_attr(obj, 'meth', method)
+        assert obj.meth(obj) == 42
+        d = module.get_type_dict(obj)
+        assert type(d) is dict
+        d["_some_attribute"] = 1
+        assert type(obj)._some_attribute == 1
         del d["_some_attribute"]
 
-        class A(object):
-            pass
-        obj = A()
-        d = module.get_type_dict(obj)
-        assert type(d) is dict
-        d["_some_attribute"] = 1
-        assert type(obj)._some_attribute == 1
-        del d["_some_attribute"]
-
-        d = module.get_type_dict(1)
+        a = 1
+        d = module.get_type_dict(a)
+        assert module.get_tp_dictoffset(a) == 0
         assert type(d) is dict
         try:
             d["_some_attribute"] = 1
@@ -318,6 +347,7 @@
         else:
             assert int._some_attribute == 1
             del d["_some_attribute"]
+        assert module.get_tp_dictoffset(a) == 0
 
     def test_custom_allocation(self):
         foo = self.import_module("foo")
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
@@ -815,6 +815,7 @@
         if pto.c_tp_base != base_object_pto or flags & Py_TPFLAGS_HEAPTYPE:
                 pto.c_tp_new = pto.c_tp_base.c_tp_new
         Py_DecRef(space, base_object_pyo)
+    pto.c_tp_dictoffset = rffi.offsetof(lltype.typeOf(pto).TO, 'c_tp_dictoffset')
     pto.c_tp_flags |= Py_TPFLAGS_READY
     return pto
 


More information about the pypy-commit mailing list