Skip to content

Commit 327ed27

Browse files
committed
Fix: __setattr__, __setitem__, __set__ external fun wrappers should return None
1 parent 8811218 commit 327ed27

File tree

4 files changed

+69
-22
lines changed

4 files changed

+69
-22
lines changed

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_object.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,6 +1242,9 @@ def call_set():
12421242
del obj.foo
12431243
assert obj.foo == 'unset'
12441244

1245+
obj = TestSetter()
1246+
assert TestSetter.foo.__set__(obj, 42) is None
1247+
12451248
def test_member_kind_precedence(self):
12461249
TestWithConflictingMember1 = CPyExtType(
12471250
"TestWithConflictingMember1",

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_tp_slots.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,22 @@ class MyStr(str):
197197
assert repr(x) == 'hello'
198198

199199

200+
def test_setattr_wrapper():
201+
TestSetAttrWrapperReturn = CPyExtType("TestSetAttrWrapperReturn",
202+
"int myset(PyObject* self, PyObject* key, PyObject* value) { return 0; }",
203+
tp_setattro="(setattrofunc) myset")
204+
x = TestSetAttrWrapperReturn()
205+
assert x.__setattr__("bar", 42) is None
206+
207+
208+
def test_sq_ass_item_wrapper():
209+
TestSqAssItemWrapperReturn = CPyExtType("TestSqAssItemWrapperReturn",
210+
"static int myassitem(PyObject *self, Py_ssize_t i, PyObject *v) { return 0; }",
211+
sq_ass_item="myassitem")
212+
x = TestSqAssItemWrapperReturn()
213+
assert x.__setitem__(42, 42) is None
214+
215+
200216
def test_concat_vs_add():
201217
# Inheritance of the Py_sq_concat slot:
202218
SqAdd = CPyExtHeapType("SqAdd",

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/ExternalFunctionNodes.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -364,9 +364,9 @@ public enum PExternalFunctionWrapper implements NativeCExtSymbol {
364364
// METH_METHOD
365365
ALLOC(10, PyObjectTransfer, PyObject, Py_ssize_t),
366366
GETATTR(11, PyObjectTransfer, PyObject, CharPtrAsTruffleString),
367-
SETATTR(12, Int, PyObject, CharPtrAsTruffleString, PyObject),
367+
SETATTR(12, InitResult, PyObject, CharPtrAsTruffleString, PyObject),
368368
RICHCMP(13, PyObjectTransfer, PyObject, PyObject, Int),
369-
SETITEM(14, Int, PyObject, Py_ssize_t, PyObject),
369+
SETITEM(14, InitResult, PyObject, Py_ssize_t, PyObject),
370370
UNARYFUNC(15, PyObjectTransfer, PyObject),
371371
BINARYFUNC(16, PyObjectTransfer, PyObject, PyObject),
372372
BINARYFUNC_L(17, PyObjectTransfer, PyObject, PyObject),
@@ -384,7 +384,7 @@ public enum PExternalFunctionWrapper implements NativeCExtSymbol {
384384
DELITEM(29, defaults(1), Int, PyObject, Py_ssize_t, PyObject),
385385
GETITEM(30, PyObjectTransfer, PyObject, Py_ssize_t),
386386
GETTER(31, PyObjectTransfer, PyObject, Pointer),
387-
SETTER(32, Int, PyObject, PyObject, Pointer),
387+
SETTER(32, InitResult, PyObject, PyObject, Pointer),
388388
INITPROC(33, InitResult, PyObject, PyObject, PyObject),
389389
HASHFUNC(34, PrimitiveResult64, PyObject),
390390
CALL(35, PyObjectTransfer, PyObject, PyObject, PyObject),

scripts/slot_fuzzer.py

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ def test(fun, name):
7777
traceback.print_exc()
7878
else:
7979
print(type(e))
80+
81+
def test_dunder(obj, fun_name, *args):
82+
# avoid going through tp_getattr/o, which may be overridden to something funky
83+
args_str = ','.join([repr(x) for x in args])
84+
test(lambda: Klass.__dict__[fun_name](obj, *args), f"{fun_name} via class dict")
85+
test(lambda: getattr(obj, fun_name)(*args), f"{fun_name}")
8086
8187
def test_dunder(obj, fun_name, *args):
8288
# avoid going through tp_getattr/o, which may be overridden to something funky
@@ -93,8 +99,8 @@ def write_attr(obj, attr, value):
9399
obj = Klass()
94100
test(lambda: bool(obj), "bool(obj)")
95101
test(lambda: len(obj), "len(obj)")
96-
test(lambda: obj.__bool__(), "obj.__bool__()")
97-
test(lambda: obj.__len__(), "obj.__len__()")
102+
test_dunder(obj, '__bool__')
103+
test_dunder(obj, '__len__')
98104
test(lambda: obj.foo, "obj.foo")
99105
test(lambda: obj.bar, "obj.bar")
100106
@@ -107,25 +113,26 @@ def write_attr(obj, attr, value):
107113
test(lambda: obj.foo, "obj.foo")
108114
test(lambda: obj.bar, "obj.bar")
109115
110-
test(lambda: obj.__bool__(), "obj.__bool__()")
111-
test(lambda: obj.__len__(), "obj.__len__()")
112-
test(lambda: obj.__getattr__('bar'), "obj.__getattr__('bar')")
113-
test(lambda: obj.__getattribute__('bar'), "obj.__getattribute__('bar')")
114-
test(lambda: obj.__setattr__('foo', 11), "obj.__setattr__('foo', 11)")
115-
test(lambda: obj.__getattr__('foo'), "obj.__getattr__('foo')")
116-
test(lambda: obj.__delattr__('foo'), "obj.__delattr__('foo')")
117-
test(lambda: obj.__getattr__('foo'), "obj.__getattr__('foo')")
116+
test_dunder(obj, '__bool__')
117+
test_dunder(obj, '__len__')
118+
test_dunder(obj, '__getattr__', 'bar')
119+
test_dunder(obj, '__getattribute__', 'bar')
120+
test_dunder(obj, '__setattr__', 'foo', 11)
121+
test_dunder(obj, '__getattr__', 'foo')
122+
test_dunder(obj, '__delattr__', 'foo')
123+
test_dunder(obj, '__getattr__', 'foo')
124+
test(lambda: obj.foo, "obj.foo")
118125
119126
class Dummy1:
120127
pass
121128
122129
obj = Klass()
123130
owner = Dummy1()
124-
test(lambda: obj.__get__(owner), "obj.__get__(owner)")
125-
test(lambda: obj.__set__(owner, 42), "obj.__set__(owner, 42)")
126-
test(lambda: obj.__get__(owner), "obj.__get__(owner)")
127-
test(lambda: obj.__delete__(owner), "obj.__delete__(owner)")
128-
test(lambda: obj.__get__(owner), "obj.__get__(owner)")
131+
test_dunder(obj, '__get__', owner)
132+
test_dunder(obj, '__set__', owner, 42)
133+
test_dunder(obj, '__get__', owner)
134+
test_dunder(obj, '__delete__', owner)
135+
test_dunder(obj, '__get__', owner)
129136
130137
class WithDescr:
131138
descr = Klass()
@@ -215,7 +222,7 @@ def tp_decl(self, name_prefix):
215222
Slot('tp_as_number', 'nb_bool', 'int $name$(PyObject* self)', ['1', '0', None]),
216223
Slot('tp_as_sequence', 'sq_length', 'Py_ssize_t $name$(PyObject* self)', ['0', '1', '42', None]),
217224
Slot('tp_as_mapping', 'mp_length', 'Py_ssize_t $name$(PyObject* self)', ['0', '1', '42', None]),
218-
Slot(NO_GROUP, 'tp_getattr', 'PyObject* $name$(PyObject* self, char *name)', ['Py_RETURN_NONE', 'Py_RETURN_TRUE', 'Py_NewRef(self)', None,
225+
Slot(NO_GROUP, 'tp_getattr', 'PyObject* $name$(PyObject* self, char *name)', ['Py_RETURN_NONE', 'Py_RETURN_FALSE', 'Py_NewRef(self)', None,
219226
'''
220227
if (global_stash1 == NULL) Py_RETURN_NONE;
221228
Py_IncRef(global_stash1);
@@ -227,7 +234,14 @@ def tp_decl(self, name_prefix):
227234
Py_IncRef(global_stash1);
228235
return global_stash1;
229236
''']),
230-
Slot(NO_GROUP, 'tp_setattro', 'PyObject* $name$(PyObject* self, PyObject *name, PyObject *value)', ['0', None,
237+
Slot(NO_GROUP, 'tp_setattro', 'int $name$(PyObject* self, PyObject *name, PyObject *value)', ['0', None,
238+
'''
239+
Py_IncRef(value);
240+
Py_XDECREF(global_stash1);
241+
global_stash1 = value;
242+
return 0;
243+
''']),
244+
Slot(NO_GROUP, 'tp_setattr', 'int $name$(PyObject* self, char *name, PyObject *value)', ['0', None,
231245
'''
232246
Py_IncRef(value);
233247
Py_XDECREF(global_stash1);
@@ -253,10 +267,24 @@ def tp_decl(self, name_prefix):
253267
MAGIC = {
254268
'__bool__(self)': ['True', 'False', None],
255269
'__len__(self)': ['0', '1', '42', None],
256-
'__getattribute__(self,name)': ['name', '42', None],
257-
'__getattr__(self,name)': ['name+"abc"', 'False', None],
270+
'__getattribute__(self,name)': ['name', '42', 'self._dict[name]', None],
271+
'__getattr__(self,name)': ['name+"abc"', 'False', 'self._dict[name]', None],
258272
'__get__(self,obj,objtype=None)': ['obj', 'True', 'self.descr_value', None],
259273
'__set__(self,obj,value)': ['self.descr_value = value\nreturn None', None],
274+
'__setattr__(self,name,value)': [None,
275+
'''
276+
if not self._dict:
277+
self._dict = dict()
278+
self._dict[name] = value
279+
return None
280+
'''],
281+
'__delattr__(self,name,value)': [None,
282+
'''
283+
if not self._dict:
284+
self._dict = dict()
285+
del self._dict[name]
286+
return None
287+
'''],
260288
}
261289

262290

0 commit comments

Comments
 (0)