@@ -52,6 +52,59 @@ _PyModule_IsExtension(PyObject *obj)
52
52
PyObject *
53
53
PyModuleDef_Init (PyModuleDef * def )
54
54
{
55
+ #ifdef Py_GIL_DISABLED
56
+ // Check that this def does not come from a non-free-threading ABI.
57
+ //
58
+ // This is meant as a "sanity check"; users should never rely on it.
59
+ // In particular, if we run out of ob_flags bits, or otherwise need to
60
+ // change some of the internals, this check can go away. Still, it
61
+ // would be nice to keep it for the free-threading transition.
62
+ //
63
+ // A PyModuleDef must be initialized with PyModuleDef_HEAD_INIT,
64
+ // which (via PyObject_HEAD_INIT) sets _Py_STATICALLY_ALLOCATED_FLAG
65
+ // and not _Py_LEGACY_ABI_CHECK_FLAG. For PyModuleDef, these flags never
66
+ // change.
67
+ // This means that the lower nibble of a valid PyModuleDef's ob_flags is
68
+ // always `_10_` (in binary; `_` is don't care).
69
+ //
70
+ // So, a check for these bits won't reject valid PyModuleDef.
71
+ // Rejecting incompatible extensions is slightly less important; here's
72
+ // how that works:
73
+ //
74
+ // In the pre-free-threading stable ABI, PyModuleDef_HEAD_INIT is big
75
+ // enough to overlap with free-threading ABI's ob_flags, is all zeros
76
+ // except for the refcount field.
77
+ // The refcount field can be:
78
+ // - 1 (3.11 and below)
79
+ // - UINT_MAX >> 2 (32-bit 3.12 & 3.13)
80
+ // - UINT_MAX (64-bit 3.12 & 3.13)
81
+ // - 7L << 28 (3.14)
82
+ //
83
+ // This means that the lower nibble of *any byte* in PyModuleDef_HEAD_INIT
84
+ // is not `_10_` -- it can be:
85
+ // - 0b0000
86
+ // - 0b0001
87
+ // - 0b0011 (from UINT_MAX >> 2)
88
+ // - 0b0111 (from 7L << 28)
89
+ // - 0b1111 (e.g. from UINT_MAX)
90
+ // (The values may change at runtime as the PyModuleDef is used, but
91
+ // PyModuleDef_Init is required before using the def as a Python object,
92
+ // so we check at least once with the initial values.
93
+ uint16_t flags = ((PyObject * )def )-> ob_flags ;
94
+ uint16_t bits = _Py_STATICALLY_ALLOCATED_FLAG | _Py_LEGACY_ABI_CHECK_FLAG ;
95
+ if ((flags & bits ) != _Py_STATICALLY_ALLOCATED_FLAG ) {
96
+ const char * message = "invalid PyModuleDef, extension possibly "
97
+ "compiled for non-free-threaded Python" ;
98
+ // Write the error as unraisable: if the extension tries calling
99
+ // any API, it's likely to segfault and lose the exception.
100
+ PyErr_SetString (PyExc_SystemError , message );
101
+ PyErr_WriteUnraisable (NULL );
102
+ // But also raise the exception normally -- this is technically
103
+ // a recoverable state.
104
+ PyErr_SetString (PyExc_SystemError , message );
105
+ return NULL ;
106
+ }
107
+ #endif
55
108
assert (PyModuleDef_Type .tp_flags & Py_TPFLAGS_READY );
56
109
if (def -> m_base .m_index == 0 ) {
57
110
Py_SET_REFCNT (def , 1 );
0 commit comments