Skip to content

Commit 55449f5

Browse files
committed
[GR-45323] [GR-45310] [GR-45265] Backport fixes for native extensions and tox
PullRequest: graalpython/2716
2 parents 30e17ed + 88bef6c commit 55449f5

File tree

4 files changed

+173
-60
lines changed

4 files changed

+173
-60
lines changed

graalpython/com.oracle.graal.python.cext/src/typeobject.c

Lines changed: 94 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -178,34 +178,19 @@ void PyType_Modified(PyTypeObject* type) {
178178
static void inherit_special(PyTypeObject *type, PyTypeObject *base) {
179179

180180
/* Copying basicsize is connected to the GC flags */
181-
unsigned long flags = PyTypeObject_tp_flags(type);
182-
unsigned long base_flags = PyTypeObject_tp_flags(base);
181+
unsigned long flags = PyTypeObject_tp_flags(type);
182+
unsigned long base_flags = PyTypeObject_tp_flags(base);
183183
if (!(flags & Py_TPFLAGS_HAVE_GC) &&
184184
(base_flags & Py_TPFLAGS_HAVE_GC) &&
185185
(!PyTypeObject_tp_traverse(type) && !PyTypeObject_tp_clear(type))) {
186-
flags |= Py_TPFLAGS_HAVE_GC;
186+
187+
flags |= Py_TPFLAGS_HAVE_GC;
187188
if (PyTypeObject_tp_traverse(type) == NULL)
188189
set_PyTypeObject_tp_traverse(type, PyTypeObject_tp_traverse(base));
189190
if (PyTypeObject_tp_clear(type) == NULL)
190-
set_PyTypeObject_tp_clear(type, PyTypeObject_tp_clear(base));
191-
}
192-
{
193-
/* The condition below could use some explanation.
194-
It appears that tp_new is not inherited for static types
195-
whose base class is 'object'; this seems to be a precaution
196-
so that old extension types don't suddenly become
197-
callable (object.__new__ wouldn't insure the invariants
198-
that the extension type's own factory function ensures).
199-
Heap types, of course, are under our control, so they do
200-
inherit tp_new; static extension types that specify some
201-
other built-in type as the default also
202-
inherit object.__new__. */
203-
if (base != &PyBaseObject_Type ||
204-
(flags & Py_TPFLAGS_HEAPTYPE)) {
205-
if (PyTypeObject_tp_new(type) == NULL)
206-
set_PyTypeObject_tp_new(type, PyTypeObject_tp_new(base)) ;
207-
}
191+
set_PyTypeObject_tp_clear(type, PyTypeObject_tp_clear(base));
208192
}
193+
209194
/* Copy other non-function slots */
210195

211196
#undef COPYVAL
@@ -238,7 +223,7 @@ static void inherit_special(PyTypeObject *type, PyTypeObject *base) {
238223
flags |= _Py_TPFLAGS_MATCH_SELF;
239224

240225
if (flags != PyTypeObject_tp_flags(type)) {
241-
set_PyTypeObject_tp_flags(type, flags);
226+
set_PyTypeObject_tp_flags(type, flags);
242227
}
243228
}
244229

@@ -372,13 +357,55 @@ static void add_slot(PyTypeObject* cls, PyObject* type_dict, char* name, void* m
372357
}
373358
}
374359

375-
#define ADD_MEMBER(__javacls__, __tpdict__, __mname__, __mtype__, __moffset__, __mflags__, __mdoc__) \
360+
#define ADD_MEMBER(__javacls__, __tpdict__, __mname__, __mtype__, __moffset__, __mflags__, __mdoc__) \
376361
add_member((__javacls__), (__tpdict__), (__mname__), (__mtype__), (__moffset__), (__mflags__), (__mdoc__))
377362

378363

379-
#define ADD_GETSET(__javacls__, __tpdict__, __name__, __getter__, __setter__, __doc__, __closure__) \
364+
#define ADD_GETSET(__javacls__, __tpdict__, __name__, __getter__, __setter__, __doc__, __closure__) \
380365
add_getset((__javacls__), (__tpdict__), (__name__), (__getter__), (__setter__), (__doc__), (__closure__))
381366

367+
// Set tp_new and the "__new__" key in the type dictionary.
368+
// Use the Py_TPFLAGS_DISALLOW_INSTANTIATION flag.
369+
static int
370+
type_ready_set_new(PyTypeObject *type, PyObject *dict, PyTypeObject *base)
371+
{
372+
/* The condition below could use some explanation.
373+
374+
It appears that tp_new is not inherited for static types whose base
375+
class is 'object'; this seems to be a precaution so that old extension
376+
types don't suddenly become callable (object.__new__ wouldn't insure the
377+
invariants that the extension type's own factory function ensures).
378+
379+
Heap types, of course, are under our control, so they do inherit tp_new;
380+
static extension types that specify some other built-in type as the
381+
default also inherit object.__new__. */
382+
newfunc tp_new = PyTypeObject_tp_new(type);
383+
unsigned long tp_flags = PyTypeObject_tp_flags(type);
384+
if (tp_new == NULL
385+
&& base == &PyBaseObject_Type
386+
&& !(tp_flags & Py_TPFLAGS_HEAPTYPE))
387+
{
388+
set_PyTypeObject_tp_flags(type, tp_flags |= Py_TPFLAGS_DISALLOW_INSTANTIATION);
389+
}
390+
391+
if (!(type->tp_flags & Py_TPFLAGS_DISALLOW_INSTANTIATION)) {
392+
if (tp_new != NULL) {
393+
// If "__new__" key does not exists in the type dictionary,
394+
// set it to tp_new_wrapper().
395+
add_slot(type, dict, "__new__", tp_new, METH_KEYWORDS | METH_VARARGS, JWRAPPER_NEW, NULL);
396+
}
397+
else {
398+
// tp_new is NULL: inherit tp_new from base
399+
set_PyTypeObject_tp_new(type, PyTypeObject_tp_new(base)) ;
400+
}
401+
}
402+
else {
403+
// Py_TPFLAGS_DISALLOW_INSTANTIATION sets tp_new to NULL
404+
// not supported yet
405+
// set_PyTypeObject_tp_new(type, NULL) ;
406+
}
407+
return 0;
408+
}
382409

383410
int PyType_Ready(PyTypeObject* cls) {
384411
#define RETURN_ERROR(__type__) \
@@ -388,7 +415,7 @@ int PyType_Ready(PyTypeObject* cls) {
388415
return -1; \
389416
} while(0)
390417

391-
#define ADD_IF_MISSING(attr, def) if (!(attr)) { attr = def; }
418+
#define ADD_IF_MISSING(OBJ, SLOT, VAL) if (!(PyTypeObject_##SLOT(OBJ))) { set_PyTypeObject_##SLOT((OBJ), (VAL)); }
392419
#define ADD_SLOT_CONV(__name__, __meth__, __flags__, __signature__) add_slot(cls, dict, (__name__), (__meth__), (__flags__), (__signature__), NULL)
393420

394421
Py_ssize_t n;
@@ -507,23 +534,15 @@ int PyType_Ready(PyTypeObject* cls) {
507534
PyObject* mro = GraalPyTruffle_Compute_Mro(cls, truffleString(cls->tp_name));
508535
set_PyTypeObject_tp_mro(cls, mro);
509536

510-
/* Inherit special flags from dominant base */
511-
if (base != NULL)
512-
inherit_special(cls, base);
537+
/* set new */
538+
type_ready_set_new(cls, dict, base);
513539

514-
/* Initialize tp_dict properly */
515-
bases = mro;
516-
assert(bases != NULL);
517-
assert(PyTuple_Check(bases));
518-
n = PyTuple_GET_SIZE(bases);
519-
for (i = 1; i < n; i++) {
520-
PyObject *b = PyTuple_GET_ITEM(bases, i);
521-
if (PyType_Check(b))
522-
inherit_slots(cls, (PyTypeObject *)b);
523-
}
540+
/* fill dict */
524541

525-
ADD_IF_MISSING(cls->tp_alloc, PyType_GenericAlloc);
526-
ADD_IF_MISSING(cls->tp_new, PyType_GenericNew);
542+
/*
543+
* NOTE: ADD_SLOT_CONV won't overwrite existing attributes, so the order is crucial and must
544+
* reflect CPython's 'slotdefs' array.
545+
*/
527546

528547
// add special methods defined directly on the type structs
529548
ADD_SLOT_CONV("__dealloc__", cls->tp_dealloc, -1, JWRAPPER_DIRECT);
@@ -563,25 +582,14 @@ int PyType_Ready(PyTypeObject* cls) {
563582
ADD_SLOT_CONV("__set__", cls->tp_descr_set, -3, JWRAPPER_DESCR_SET);
564583
ADD_SLOT_CONV("__init__", cls->tp_init, METH_KEYWORDS | METH_VARARGS, JWRAPPER_INITPROC);
565584
ADD_SLOT_CONV("__alloc__", cls->tp_alloc, -2, JWRAPPER_ALLOC);
566-
ADD_SLOT_CONV("__new__", cls->tp_new, METH_KEYWORDS | METH_VARARGS, JWRAPPER_NEW);
585+
/* Note: '__new__' was added here previously but we don't do it similar to CPython.
586+
They also skip it because the appropriate 'slotdef' doesn't have a wrapper.
587+
Adding '__new__' is done by function 'type_ready_set_new'. */
567588
ADD_SLOT_CONV("__free__", cls->tp_free, -1, JWRAPPER_DIRECT);
568589
ADD_SLOT_CONV("__del__", cls->tp_del, -1, JWRAPPER_DIRECT);
569590
ADD_SLOT_CONV("__finalize__", cls->tp_finalize, -1, JWRAPPER_DIRECT);
570591

571-
PySequenceMethods* sequences = PyTypeObject_tp_as_sequence(cls);
572-
if (sequences) {
573-
// sequence functions first, so that the number functions take precendence
574-
ADD_SLOT_CONV("__len__", sequences->sq_length, -1, JWRAPPER_LENFUNC);
575-
ADD_SLOT_CONV("__add__", sequences->sq_concat, -2, JWRAPPER_BINARYFUNC);
576-
ADD_SLOT_CONV("__mul__", sequences->sq_repeat, -2, JWRAPPER_SSIZE_ARG);
577-
ADD_SLOT_CONV("__getitem__", sequences->sq_item, -2, JWRAPPER_GETITEM);
578-
ADD_SLOT_CONV("__setitem__", sequences->sq_ass_item, -3, JWRAPPER_SETITEM);
579-
ADD_SLOT_CONV("__delitem__", sequences->sq_ass_item, -3, JWRAPPER_DELITEM);
580-
ADD_SLOT_CONV("__contains__", sequences->sq_contains, -2, JWRAPPER_OBJOBJPROC);
581-
ADD_SLOT_CONV("__iadd__", sequences->sq_inplace_concat, -2, JWRAPPER_BINARYFUNC);
582-
ADD_SLOT_CONV("__imul__", sequences->sq_inplace_repeat, -2, JWRAPPER_SSIZE_ARG);
583-
}
584-
592+
// 'tp_as_number' takes precedence over 'tp_as_mapping' and 'tp_as_sequence' !
585593
PyNumberMethods* numbers = PyTypeObject_tp_as_number(cls);
586594
if (numbers) {
587595
ADD_SLOT_CONV("__add__", numbers->nb_add, -2, JWRAPPER_BINARYFUNC_L);
@@ -635,6 +643,7 @@ int PyType_Ready(PyTypeObject* cls) {
635643
ADD_SLOT_CONV("__imatmul__", numbers->nb_inplace_matrix_multiply, -2, JWRAPPER_BINARYFUNC_L);
636644
}
637645

646+
// 'tp_as_mapping' takes precedence over 'tp_as_sequence' !
638647
PyMappingMethods* mappings = PyTypeObject_tp_as_mapping(cls);
639648
if (mappings) {
640649
ADD_SLOT_CONV("__len__", mappings->mp_length, -1, JWRAPPER_LENFUNC);
@@ -643,6 +652,20 @@ int PyType_Ready(PyTypeObject* cls) {
643652
ADD_SLOT_CONV("__delitem__", mappings->mp_ass_subscript, -3, JWRAPPER_MP_DELITEM);
644653
}
645654

655+
PySequenceMethods* sequences = PyTypeObject_tp_as_sequence(cls);
656+
if (sequences) {
657+
// sequence functions first, so that the number functions take precendence
658+
ADD_SLOT_CONV("__len__", sequences->sq_length, -1, JWRAPPER_LENFUNC);
659+
ADD_SLOT_CONV("__add__", sequences->sq_concat, -2, JWRAPPER_BINARYFUNC);
660+
ADD_SLOT_CONV("__mul__", sequences->sq_repeat, -2, JWRAPPER_SSIZE_ARG);
661+
ADD_SLOT_CONV("__getitem__", sequences->sq_item, -2, JWRAPPER_GETITEM);
662+
ADD_SLOT_CONV("__setitem__", sequences->sq_ass_item, -3, JWRAPPER_SETITEM);
663+
ADD_SLOT_CONV("__delitem__", sequences->sq_ass_item, -3, JWRAPPER_DELITEM);
664+
ADD_SLOT_CONV("__contains__", sequences->sq_contains, -2, JWRAPPER_OBJOBJPROC);
665+
ADD_SLOT_CONV("__iadd__", sequences->sq_inplace_concat, -2, JWRAPPER_BINARYFUNC);
666+
ADD_SLOT_CONV("__imul__", sequences->sq_inplace_repeat, -2, JWRAPPER_SSIZE_ARG);
667+
}
668+
646669
PyAsyncMethods* async = PyTypeObject_tp_as_async(cls);
647670
if (async) {
648671
ADD_SLOT_CONV("__await__", async->am_await, -1, JWRAPPER_DIRECT);
@@ -655,6 +678,22 @@ int PyType_Ready(PyTypeObject* cls) {
655678
// TODO ...
656679
}
657680

681+
/* Inherit slots */
682+
if (base != NULL)
683+
inherit_special(cls, base);
684+
bases = mro;
685+
assert(bases != NULL);
686+
assert(PyTuple_Check(bases));
687+
n = PyTuple_GET_SIZE(bases);
688+
for (i = 1; i < n; i++) {
689+
PyObject *b = PyTuple_GET_ITEM(bases, i);
690+
if (PyType_Check(b))
691+
inherit_slots(cls, (PyTypeObject *)b);
692+
}
693+
694+
ADD_IF_MISSING(cls, tp_alloc, PyType_GenericAlloc);
695+
ADD_IF_MISSING(cls, tp_new, PyType_GenericNew);
696+
658697
// process inherited slots
659698
// CPython doesn't do that in 'PyType_Ready' but we must because a native type can inherit
660699
// dynamic slots from a managed Python class. Since the managed Python class may be created

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,53 @@ def test_doc(self):
731731
assert len(obj.some_member.__doc__) == len(expected_doc)
732732
assert obj.some_member.__doc__ == expected_doc
733733

734+
def test_multiple_inheritance_with_native(self):
735+
_A = CPyExtType("_A","")
736+
class B:
737+
def __getattr__(self, name):
738+
return name
739+
class X(_A, B):
740+
b = 2
741+
x = X()
742+
assert x.foo == "foo"
743+
744+
def test_slot_precedence(self):
745+
MapAndSeq = CPyExtType("MapAndSeq",
746+
'''
747+
static PyObject * mas_nb_add(PyObject *self, PyObject *other) {
748+
return PyUnicode_FromString("mas_nb_add");
749+
}
750+
static Py_ssize_t mas_sq_length(PyObject *self) {
751+
return 111;
752+
}
753+
static PyObject *mas_sq_item(PyObject *self, Py_ssize_t idx) {
754+
return PyUnicode_FromString("sq_item");
755+
}
756+
static PyObject * mas_sq_concat(PyObject *self, PyObject *other) {
757+
return PyUnicode_FromString("mas_sq_concat");
758+
}
759+
static Py_ssize_t mas_mp_length(PyObject *self) {
760+
return 222;
761+
}
762+
static PyObject * mas_mp_subscript(PyObject *self, PyObject *key) {
763+
return PyUnicode_FromString("mp_subscript");
764+
}
765+
''',
766+
nb_add='mas_nb_add',
767+
sq_length='mas_sq_length',
768+
sq_item='mas_sq_item',
769+
sq_concat='mas_sq_concat',
770+
mp_length='mas_mp_length',
771+
mp_subscript='mas_mp_subscript',
772+
)
773+
obj = MapAndSeq()
774+
# Note: len(obj) uses 'PyObject_Lenght' which does not use the attribute but first tries
775+
# 'sq_length' and falls back to 'mp_length'. Therefore, we just look at '__len__' here.
776+
assert obj.__len__() == 222
777+
assert obj['hello'] == 'mp_subscript'
778+
assert obj + 'hello' == 'mas_nb_add'
779+
780+
734781
class CBytes:
735782
def __bytes__(self):
736783
return b'abc'

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/PosixModuleBuiltins.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1900,6 +1900,30 @@ boolean access(PosixPath path, int mode, int dirFd, boolean effectiveIds, boolea
19001900
}
19011901
}
19021902

1903+
@Builtin(name = "fchmod", minNumOfPositionalArgs = 2, parameterNames = {"fd", "mode"})
1904+
@ArgumentClinic(name = "fd", conversion = ClinicConversion.Int)
1905+
@ArgumentClinic(name = "mode", conversion = ClinicConversion.Int)
1906+
@GenerateNodeFactory
1907+
abstract static class FChmodNode extends PythonBinaryClinicBuiltinNode {
1908+
@Override
1909+
protected ArgumentClinicProvider getArgumentClinic() {
1910+
return PosixModuleBuiltinsClinicProviders.FChmodNodeClinicProviderGen.INSTANCE;
1911+
}
1912+
1913+
@Specialization
1914+
PNone fchmod(VirtualFrame frame, int fd, int mode,
1915+
@Cached SysModuleBuiltins.AuditNode auditNode,
1916+
@CachedLibrary("getPosixSupport()") PosixSupportLibrary posixLib) {
1917+
auditNode.audit("os.chmod", fd, mode, -1);
1918+
try {
1919+
posixLib.fchmod(getPosixSupport(), fd, mode);
1920+
} catch (PosixException e) {
1921+
throw raiseOSErrorFromPosixException(frame, e, fd);
1922+
}
1923+
return PNone.NONE;
1924+
}
1925+
}
1926+
19031927
@Builtin(name = "chmod", minNumOfPositionalArgs = 2, parameterNames = {"path", "mode"}, varArgsMarker = true, keywordOnlyNames = {"dir_fd", "follow_symlinks"})
19041928
@ArgumentClinic(name = "path", conversionClass = PathConversionNode.class, args = {"false", "true"})
19051929
@ArgumentClinic(name = "mode", conversion = ClinicConversion.Int)

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextTypeBuiltins.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
import com.oracle.graal.python.builtins.objects.cext.common.CExtContext;
8181
import com.oracle.graal.python.builtins.objects.cext.common.CExtContext.Store;
8282
import com.oracle.graal.python.builtins.objects.common.DynamicObjectStorage;
83+
import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageGetItem;
8384
import com.oracle.graal.python.builtins.objects.dict.DictBuiltins;
8485
import com.oracle.graal.python.builtins.objects.dict.PDict;
8586
import com.oracle.graal.python.builtins.objects.function.PBuiltinFunction;
@@ -292,12 +293,14 @@ abstract static class PyTruffleType_AddSlot extends CApi7BuiltinNode {
292293
@Specialization
293294
@TruffleBoundary
294295
static int addSlot(Object clazz, PDict tpDict, TruffleString memberName, Object cfunc, int flags, int wrapper, Object memberDoc) {
295-
// create wrapper descriptor
296-
Object wrapperDescriptor = CreateFunctionNodeGen.getUncached().execute(memberName, cfunc, wrapper, clazz, flags, PythonObjectFactory.getUncached());
297-
WriteAttributeToDynamicObjectNode.getUncached().execute(wrapperDescriptor, SpecialAttributeNames.T___DOC__, memberDoc);
296+
if (!HashingStorageGetItem.hasKeyUncached(tpDict.getDictStorage(), memberName)) {
297+
// create wrapper descriptor
298+
Object wrapperDescriptor = CreateFunctionNodeGen.getUncached().execute(memberName, cfunc, wrapper, clazz, flags, PythonObjectFactory.getUncached());
299+
WriteAttributeToDynamicObjectNode.getUncached().execute(wrapperDescriptor, SpecialAttributeNames.T___DOC__, memberDoc);
298300

299-
// add wrapper descriptor to tp_dict
300-
PyDictSetItem.executeUncached(tpDict, memberName, wrapperDescriptor);
301+
// add wrapper descriptor to tp_dict
302+
PyDictSetItem.executeUncached(tpDict, memberName, wrapperDescriptor);
303+
}
301304
return 0;
302305
}
303306
}

0 commit comments

Comments
 (0)