| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -71,6 +71,8 @@ whose size is determined when the object is allocated. | |||
| 71 | 71 | * | |
| 72 | 72 | * Statically allocated objects might be shared between | |
| 73 | 73 | * interpreters, so must be marked as immortal. | |
| 74 | + * | ||
| 75 | + * Before changing this, see the check in PyModuleDef_Init(). | ||
| 74 | 76 | */ | |
| 75 | 77 | #if defined(Py_GIL_DISABLED) | |
| 76 | 78 | #define PyObject_HEAD_INIT(type) \ | |
@@ -634,6 +636,7 @@ given type object has a specified feature. | |||
| 634 | 636 | ||
| 635 | 637 | // Flag values for ob_flags (16 bits available, if SIZEOF_VOID_P > 4). | |
| 636 | 638 | #define _Py_IMMORTAL_FLAGS (1 << 0) | |
| 639 | + #define _Py_LEGACY_ABI_CHECK_FLAG (1 << 1) /* see PyModuleDef_Init() */ | ||
| 637 | 640 | #define _Py_STATICALLY_ALLOCATED_FLAG (1 << 2) | |
| 638 | 641 | #if defined(Py_GIL_DISABLED) && defined(Py_DEBUG) | |
| 639 | 642 | #define _Py_TYPE_REVEALED_FLAG (1 << 3) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,2 @@ | |||
| 1 | + Display and raise an exception if an extension compiled for | ||
| 2 | + non-free-threaded Python is loaded in a free-threaded interpreter. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -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); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments