Skip to content

Commit bfac1b2

Browse files
committed
Add nb_add/sq_concat to slots fuzzer, fix issue found by the fuzzer
1 parent c7cdac5 commit bfac1b2

File tree

3 files changed

+54
-7
lines changed

3 files changed

+54
-7
lines changed

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@
6565
static PyObject* get_tp_descr_get(PyObject* unused, PyObject* object) {
6666
return PyLong_FromVoidPtr(Py_TYPE(object)->tp_descr_get);
6767
}
68+
static PyObject* get_nb_add(PyObject* unused, PyObject* object) {
69+
return PyLong_FromVoidPtr(Py_TYPE(object)->tp_as_number == NULL ? NULL : Py_TYPE(object)->tp_as_number->nb_add);
70+
}
6871
""",
6972
tp_methods=
7073
'{"get_tp_attr", (PyCFunction)get_tp_attr, METH_O | METH_STATIC, ""},' +
@@ -74,6 +77,7 @@
7477
'{"get_nb_bool", (PyCFunction)get_nb_bool, METH_O | METH_STATIC, ""},' +
7578
'{"get_tp_as_number", (PyCFunction)get_tp_as_number, METH_O | METH_STATIC, ""},' +
7679
'{"get_sq_concat", (PyCFunction)get_sq_concat, METH_O | METH_STATIC, ""},' +
80+
'{"get_nb_add", (PyCFunction)get_nb_add, METH_O | METH_STATIC, ""},' +
7781
'{"get_tp_descr_get", (PyCFunction)get_tp_descr_get, METH_O | METH_STATIC, ""}')
7882

7983

@@ -565,4 +569,27 @@ class ManagedSub(NbAddOnlyHeapType):
565569
class ManagedSub2(NbAddOnlyHeapType):
566570
def __add__(self, other): return NotImplemented
567571

568-
assert SlotsGetter.get_sq_concat(ManagedSub2()) == 0
572+
assert SlotsGetter.get_sq_concat(ManagedSub2()) == 0
573+
574+
575+
def test_nb_add_sq_concat_static_managed_heap_inheritance():
576+
NbAddSqConcatStaticType = CPyExtType("NbAddSqConcatStaticType",
577+
code = 'PyObject* my_nb_add(PyObject* self, PyObject *other) { return PyLong_FromLong(42); }',
578+
nb_add = 'my_nb_add',
579+
sq_concat = 'my_nb_add')
580+
581+
# fixup_slot_dispatchers should figure out that __add__ and __radd__ descriptors wrap the same
582+
# native call and set nb_add to it instead of the Python dispatcher C function
583+
class IntermediateManagedDummy(NbAddSqConcatStaticType):
584+
pass
585+
586+
# This should inherit my_nb_add as nb_add
587+
SqConcatInheritingNbAdd = CPyExtHeapType("SqConcatInheritingNbAdd",
588+
bases=(IntermediateManagedDummy,),
589+
code = 'PyObject* my_sq_concat(PyObject* self, PyObject *other) { return PyLong_FromLong(10); }',
590+
slots=['{Py_sq_concat, &my_sq_concat}'])
591+
592+
# Doing SqConcatInheritingNbAdd() + NbAddSqConcatStaticType() triggers segfault in CPython debug build
593+
# (not in the addition itself, but later in weakref processing). This test is derived from a fuzzer test
594+
# case, where it did not segfault...
595+
assert SlotsGetter.get_nb_add(NbAddSqConcatStaticType()) == SlotsGetter.get_nb_add(SqConcatInheritingNbAdd())

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlot.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,19 @@ public static TpSlotNative createHPySlot(Object callable) {
223223
}
224224

225225
public final boolean isSameCallable(TpSlotNative other, InteropLibrary interop) {
226-
// Interop is going to be quite slow (in interpreter)
227-
// What else? Compare asPointer() values?
228-
return this == other || this.callable == other.callable || interop.isIdentical(callable, other.callable, interop);
226+
if (this == other || this.callable == other.callable) {
227+
return true;
228+
}
229+
// NFISymbols do not implement isIdentical interop message, so we compare the pointers
230+
// Interop is going to be quite slow (in interpreter), should we eagerly request the
231+
// pointer in the ctor?
232+
interop.toNative(callable);
233+
interop.toNative(other.callable);
234+
try {
235+
return interop.asPointer(callable) == interop.asPointer(other.callable);
236+
} catch (UnsupportedMessageException e) {
237+
throw CompilerDirectives.shouldNotReachHere(e);
238+
}
229239
}
230240

231241
/**

scripts/slot_fuzzer.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
import sys
6666
import re
6767
import traceback
68-
def slots_tester(Klass):
68+
def slots_tester(Klass, other_klasses):
6969
def test(fun, name):
7070
try:
7171
print(f'{name}:', end='')
@@ -148,6 +148,11 @@ def del_descr(self): del self.descr
148148
test_dunder(obj, '__getitem__', -1)
149149
test_dunder(obj, '__getitem__', 'hello')
150150
151+
other_objs = [K() for K in other_klasses] + [42, 3.14, 'string', (1,2,3)]
152+
for obj2 in other_objs:
153+
obj1 = Klass()
154+
test(lambda: obj1 + obj2, f"{Klass} + {type(obj2)}")
155+
test(lambda: obj2 + obj1, f"{type(obj2)} + {Klass}")
151156
152157
'''
153158

@@ -222,8 +227,10 @@ def tp_decl(self, name_prefix):
222227

223228
SLOTS = [
224229
Slot('tp_as_number', 'nb_bool', 'int $name$(PyObject* self)', ['1', '0', None]),
230+
Slot('tp_as_number', 'nb_add', 'PyObject* $name$(PyObject* self, PyObject *other)', ['Py_NewRef(self)', 'PyLong_FromLong(0)', None]),
225231
Slot('tp_as_sequence', 'sq_length', 'Py_ssize_t $name$(PyObject* self)', ['0', '1', '42', None]),
226-
Slot('tp_as_sequence', 'sq_item', 'PyObject* $name$(PyObject* self, Py_ssize_t index)', ['0', 'PyLong_FromSsize_t(index + 1)', None]),
232+
Slot('tp_as_sequence', 'sq_item', 'PyObject* $name$(PyObject* self, Py_ssize_t index)', ['Py_NewRef(self)', 'PyLong_FromSsize_t(index + 1)', None]),
233+
Slot('tp_as_sequence', 'sq_concat', 'PyObject* $name$(PyObject* self, PyObject *other)', ['Py_NewRef(self)', 'PyLong_FromLong(10)', None]),
227234
Slot('tp_as_mapping', 'mp_length', 'Py_ssize_t $name$(PyObject* self)', ['0', '1', '42', None]),
228235
Slot('tp_as_mapping', 'mp_subscript', 'PyObject* $name$(PyObject* self, PyObject* key)', ['Py_RETURN_FALSE', 'Py_NewRef(key)', None]),
229236
Slot(NO_GROUP, 'tp_getattr', 'PyObject* $name$(PyObject* self, char *name)', ['Py_RETURN_NONE', 'Py_RETURN_FALSE', 'Py_NewRef(self)', None,
@@ -275,6 +282,8 @@ def tp_decl(self, name_prefix):
275282

276283
MAGIC = {
277284
'__bool__(self)': ['True', 'False', None],
285+
'__add__(self)': ['self', '"__add__result"', "NotImplemented", None],
286+
'__radd__(self)': ['self', '"__add__result"', "NotImplemented", None],
278287
'__len__(self)': ['0', '1', '42', None],
279288
'__getattribute__(self,name)': ['name', '42', 'global_dict1[name]', None],
280289
'__getattr__(self,name)': ['name+"abc"', 'False', 'global_dict1[name]', None],
@@ -517,9 +526,10 @@ def choose_random(l):
517526

518527
py_source += '\n\n# ===========\n'
519528
py_source += '# Tests:\n\n'
529+
py_source += 'all_classes = ' + ','.join(classes) + '\n\n'
520530
for klass in classes:
521531
py_source += f'print("\\n\\n\\nTesting {klass}\\n")\n'
522-
py_source += f'slots_tester({klass})\n'
532+
py_source += f'slots_tester({klass}, all_classes)\n'
523533

524534
py_filename = f"test{test_case_idx}.py"
525535
write_all(os.path.join(output_dir, py_filename), py_source)

0 commit comments

Comments
 (0)