[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