]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-101101: Unstable C API tier (PEP 689) (GH-101102)
authorPetr Viktorin <encukou@gmail.com>
Tue, 28 Feb 2023 08:31:01 +0000 (09:31 +0100)
committerGitHub <noreply@github.com>
Tue, 28 Feb 2023 08:31:01 +0000 (09:31 +0100)
18 files changed:
Doc/c-api/code.rst
Doc/c-api/stable.rst
Doc/tools/extensions/c_annotations.py
Doc/whatsnew/3.12.rst
Include/README.rst
Include/cpython/ceval.h
Include/cpython/code.h
Include/pyport.h
Lib/test/test_code.py
Misc/NEWS.d/next/C API/2022-04-21-17-25-22.gh-issue-91744.FgvaMi.rst [new file with mode: 0644]
Modules/Setup.stdlib.in
Modules/_testcapi/code.c [new file with mode: 0644]
Modules/_testcapi/parts.h
Modules/_testcapimodule.c
Objects/codeobject.c
PCbuild/_testcapi.vcxproj
PCbuild/_testcapi.vcxproj.filters
Python/ceval.c

index ae75d68901d7bafcc6dd2072662f31ccf311ae78..062ef3a1fea93cdd279caadb90cd535e65b5ce56 100644 (file)
@@ -33,28 +33,47 @@ bound into a function.
 
    Return the number of free variables in *co*.
 
-.. c:function:: PyCodeObject* PyCode_New(int argcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, int firstlineno, PyObject *linetable, PyObject *exceptiontable)
+.. c:function:: PyCodeObject* PyUnstable_Code_New(int argcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, int firstlineno, PyObject *linetable, PyObject *exceptiontable)
 
    Return a new code object.  If you need a dummy code object to create a frame,
-   use :c:func:`PyCode_NewEmpty` instead.  Calling :c:func:`PyCode_New` directly
-   will bind you to a precise Python version since the definition of the bytecode
-   changes often. The many arguments of this function are inter-dependent in complex
+   use :c:func:`PyCode_NewEmpty` instead.
+
+   Since the definition of the bytecode changes often, calling
+   :c:func:`PyCode_New` directly can bind you to a precise Python version.
+
+   The many arguments of this function are inter-dependent in complex
    ways, meaning that subtle changes to values are likely to result in incorrect
    execution or VM crashes. Use this function only with extreme care.
 
    .. versionchanged:: 3.11
       Added ``exceptiontable`` parameter.
 
-.. c:function:: PyCodeObject* PyCode_NewWithPosOnlyArgs(int argcount, int posonlyargcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, int firstlineno, PyObject *linetable, PyObject *exceptiontable)
+   .. index:: single: PyCode_New
+
+   .. versionchanged:: 3.12
+
+      Renamed from ``PyCode_New`` as part of :ref:`unstable-c-api`.
+      The old name is deprecated, but will remain available until the
+      signature changes again.
+
+.. c:function:: PyCodeObject* PyUnstable_Code_NewWithPosOnlyArgs(int argcount, int posonlyargcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, int firstlineno, PyObject *linetable, PyObject *exceptiontable)
 
    Similar to :c:func:`PyCode_New`, but with an extra "posonlyargcount" for positional-only arguments.
    The same caveats that apply to ``PyCode_New`` also apply to this function.
 
-   .. versionadded:: 3.8
+   .. index:: single: PyCode_NewWithPosOnlyArgs
+
+   .. versionadded:: 3.8 as ``PyCode_NewWithPosOnlyArgs``
 
    .. versionchanged:: 3.11
       Added ``exceptiontable`` parameter.
 
+   .. versionchanged:: 3.12
+
+      Renamed to ``PyUnstable_Code_NewWithPosOnlyArgs``.
+      The old name is deprecated, but will remain available until the
+      signature changes again.
+
 .. c:function:: PyCodeObject* PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
 
    Return a new empty code object with the specified filename,
@@ -165,3 +184,70 @@ bound into a function.
    :c:func:`PyErr_WriteUnraisable`. Otherwise it should return ``0``.
 
    .. versionadded:: 3.12
+
+
+Extra information
+-----------------
+
+To support low-level extensions to frame evaluation, such as external
+just-in-time compilers, it is possible to attach arbitrary extra data to
+code objects.
+
+These functions are part of the unstable C API tier:
+this functionality is a CPython implementation detail, and the API
+may change without deprecation warnings.
+
+.. c:function:: Py_ssize_t PyUnstable_Eval_RequestCodeExtraIndex(freefunc free)
+
+   Return a new an opaque index value used to adding data to code objects.
+
+   You generally call this function once (per interpreter) and use the result
+   with ``PyCode_GetExtra`` and ``PyCode_SetExtra`` to manipulate
+   data on individual code objects.
+
+   If *free* is not ``NULL``: when a code object is deallocated,
+   *free* will be called on non-``NULL`` data stored under the new index.
+   Use :c:func:`Py_DecRef` when storing :c:type:`PyObject`.
+
+   .. index:: single: _PyEval_RequestCodeExtraIndex
+
+   .. versionadded:: 3.6 as ``_PyEval_RequestCodeExtraIndex``
+
+   .. versionchanged:: 3.12
+
+     Renamed to ``PyUnstable_Eval_RequestCodeExtraIndex``.
+     The old private name is deprecated, but will be available until the API
+     changes.
+
+.. c:function:: int PyUnstable_Code_GetExtra(PyObject *code, Py_ssize_t index, void **extra)
+
+   Set *extra* to the extra data stored under the given index.
+   Return 0 on success. Set an exception and return -1 on failure.
+
+   If no data was set under the index, set *extra* to ``NULL`` and return
+   0 without setting an exception.
+
+   .. index:: single: _PyCode_GetExtra
+
+   .. versionadded:: 3.6 as ``_PyCode_GetExtra``
+
+   .. versionchanged:: 3.12
+
+     Renamed to ``PyUnstable_Code_GetExtra``.
+     The old private name is deprecated, but will be available until the API
+     changes.
+
+.. c:function:: int PyUnstable_Code_SetExtra(PyObject *code, Py_ssize_t index, void *extra)
+
+   Set the extra data stored under the given index to *extra*.
+   Return 0 on success. Set an exception and return -1 on failure.
+
+   .. index:: single: _PyCode_SetExtra
+
+   .. versionadded:: 3.6 as ``_PyCode_SetExtra``
+
+   .. versionchanged:: 3.12
+
+     Renamed to ``PyUnstable_Code_SetExtra``.
+     The old private name is deprecated, but will be available until the API
+     changes.
index 4ae20e93e36785e3415703961aca9d400fa69090..3721fc0697f5cd830965ebfda2ace0b318f3922f 100644 (file)
@@ -6,9 +6,9 @@
 C API Stability
 ***************
 
-Python's C API is covered by the Backwards Compatibility Policy, :pep:`387`.
-While the C API will change with every minor release (e.g. from 3.9 to 3.10),
-most changes will be source-compatible, typically by only adding new API.
+Unless documented otherwise, Python's C API is covered by the Backwards
+Compatibility Policy, :pep:`387`.
+Most changes to it are source-compatible (typically by only adding new API).
 Changing existing API or removing API is only done after a deprecation period
 or to fix serious issues.
 
@@ -18,8 +18,38 @@ way; see :ref:`stable-abi-platform` below).
 So, code compiled for Python 3.10.0 will work on 3.10.8 and vice versa,
 but will need to be compiled separately for 3.9.x and 3.10.x.
 
+There are two tiers of C API with different stability exepectations:
+
+- *Unstable API*, may change in minor versions without a deprecation period.
+  It is marked by the ``PyUnstable`` prefix in names.
+- *Limited API*, is compatible across several minor releases.
+  When :c:macro:`Py_LIMITED_API` is defined, only this subset is exposed
+  from ``Python.h``.
+
+These are discussed in more detail below.
+
 Names prefixed by an underscore, such as ``_Py_InternalState``,
 are private API that can change without notice even in patch releases.
+If you need to use this API, consider reaching out to
+`CPython developers <https://discuss.python.org/c/core-dev/c-api/30>`_
+to discuss adding public API for your use case.
+
+.. _unstable-c-api:
+
+Unstable C API
+==============
+
+.. index:: single: PyUnstable
+
+Any API named with the ``PyUnstable`` prefix exposes CPython implementation
+details, and may change in every minor release (e.g. from 3.9 to 3.10) without
+any deprecation warnings.
+However, it will not change in a bugfix release (e.g. from 3.10.0 to 3.10.1).
+
+It is generally intended for specialized, low-level tools like debuggers.
+
+Projects that use this API are expected to follow
+CPython development and spend extra effort adjusting to changes.
 
 
 Stable Application Binary Interface
index 9defb24a0331ef53cfb980d16c21ffd2d60c1cce..e5dc82cf0dc2d9d97f9b43d6773b14835292a34f 100644 (file)
@@ -143,6 +143,22 @@ class Annotations:
                         ' (Only some members are part of the stable ABI.)')
                 node.insert(0, emph_node)
 
+            # Unstable API annotation.
+            if name.startswith('PyUnstable'):
+                warn_node = nodes.admonition(
+                    classes=['unstable-c-api', 'warning'])
+                message = 'This is '
+                emph_node = nodes.emphasis(message, message)
+                ref_node = addnodes.pending_xref(
+                    'Unstable API', refdomain="std",
+                    reftarget='unstable-c-api',
+                    reftype='ref', refexplicit="False")
+                ref_node += nodes.Text('Unstable API')
+                emph_node += ref_node
+                emph_node += nodes.Text('. It may change without warning in minor releases.')
+                warn_node += emph_node
+                node.insert(0, warn_node)
+
             # Return value annotation
             if objtype != 'function':
                 continue
index 1a25ec6b70613b4dbb55fefcb39aedf8b8fe7872..c0c021c679147f82895837ad2ef5a14e393013f7 100644 (file)
@@ -810,6 +810,29 @@ C API Changes
 New Features
 ------------
 
+
+* :pep:`697`: Introduced the :ref:`Unstable C API tier <unstable-c-api>`,
+  intended for low-level tools like debuggers and JIT compilers.
+  This API may change in each minor release of CPython without deprecation
+  warnings.
+  Its contents are marked by the ``PyUnstable_`` prefix in names.
+
+  Code object constructors:
+
+  - ``PyUnstable_Code_New()`` (renamed from ``PyCode_New``)
+  - ``PyUnstable_Code_NewWithPosOnlyArgs()`` (renamed from ``PyCode_NewWithPosOnlyArgs``)
+
+  Extra storage for code objects (:pep:`523`):
+
+  - ``PyUnstable_Eval_RequestCodeExtraIndex()`` (renamed from ``_PyEval_RequestCodeExtraIndex``)
+  - ``PyUnstable_Code_GetExtra()`` (renamed from ``_PyCode_GetExtra``)
+  - ``PyUnstable_Code_SetExtra()`` (renamed from ``_PyCode_SetExtra``)
+
+  The original names will continue to be available until the respective
+  API changes.
+
+  (Contributed by Petr Viktorin in :gh:`101101`.)
+
 * Added the new limited C API function :c:func:`PyType_FromMetaclass`,
   which generalizes the existing :c:func:`PyType_FromModuleAndSpec` using
   an additional metaclass argument.
index f52e690eac9a91cabec5e250e6ff3e47e107cd42..531f09692f783f52eb7f4ff33e4f1f94077426d3 100644 (file)
@@ -1,11 +1,13 @@
 The Python C API
 ================
 
-The C API is divided into three sections:
+The C API is divided into these sections:
 
 1. ``Include/``: Limited API
 2. ``Include/cpython/``: CPython implementation details
-3. ``Include/internal/``: The internal API
+3. ``Include/cpython/``, names with the ``PyUnstable_`` prefix: API that can
+   change between minor releases
+4. ``Include/internal/``, and any name with ``_`` prefix: The internal API
 
 Information on changing the C API is available `in the developer guide`_
 
index 74665c9fa10580bcf7d48e54a9988795cbb76bd5..0fbbee10c2edce0bc4faaa8860be7f7bab7e4ebc 100644 (file)
@@ -22,7 +22,12 @@ PyAPI_FUNC(PyObject *) _PyEval_EvalFrameDefault(PyThreadState *tstate, struct _P
 PyAPI_FUNC(void) _PyEval_SetSwitchInterval(unsigned long microseconds);
 PyAPI_FUNC(unsigned long) _PyEval_GetSwitchInterval(void);
 
-PyAPI_FUNC(Py_ssize_t) _PyEval_RequestCodeExtraIndex(freefunc);
+PyAPI_FUNC(Py_ssize_t) PyUnstable_Eval_RequestCodeExtraIndex(freefunc);
+// Old name -- remove when this API changes:
+_Py_DEPRECATED_EXTERNALLY(3.12) static inline Py_ssize_t
+_PyEval_RequestCodeExtraIndex(freefunc f) {
+    return PyUnstable_Eval_RequestCodeExtraIndex(f);
+}
 
 PyAPI_FUNC(int) _PyEval_SliceIndex(PyObject *, Py_ssize_t *);
 PyAPI_FUNC(int) _PyEval_SliceIndexNotNone(PyObject *, Py_ssize_t *);
index fba9296aedc99dcb2fae9605757e7c1b24f553a9..0e4bd8a58c165b36c2ab8ca4b29d606809cf345f 100644 (file)
@@ -178,19 +178,40 @@ static inline int PyCode_GetFirstFree(PyCodeObject *op) {
 #define _PyCode_CODE(CO) _Py_RVALUE((_Py_CODEUNIT *)(CO)->co_code_adaptive)
 #define _PyCode_NBYTES(CO) (Py_SIZE(CO) * (Py_ssize_t)sizeof(_Py_CODEUNIT))
 
-/* Public interface */
-PyAPI_FUNC(PyCodeObject *) PyCode_New(
+/* Unstable public interface */
+PyAPI_FUNC(PyCodeObject *) PyUnstable_Code_New(
         int, int, int, int, int, PyObject *, PyObject *,
         PyObject *, PyObject *, PyObject *, PyObject *,
         PyObject *, PyObject *, PyObject *, int, PyObject *,
         PyObject *);
 
-PyAPI_FUNC(PyCodeObject *) PyCode_NewWithPosOnlyArgs(
+PyAPI_FUNC(PyCodeObject *) PyUnstable_Code_NewWithPosOnlyArgs(
         int, int, int, int, int, int, PyObject *, PyObject *,
         PyObject *, PyObject *, PyObject *, PyObject *,
         PyObject *, PyObject *, PyObject *, int, PyObject *,
         PyObject *);
         /* same as struct above */
+// Old names -- remove when this API changes:
+_Py_DEPRECATED_EXTERNALLY(3.12) static inline PyCodeObject *
+PyCode_New(
+        int a, int b, int c, int d, int e, PyObject *f, PyObject *g,
+        PyObject *h, PyObject *i, PyObject *j, PyObject *k,
+        PyObject *l, PyObject *m, PyObject *n, int o, PyObject *p,
+        PyObject *q)
+{
+    return PyUnstable_Code_New(
+        a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q);
+}
+_Py_DEPRECATED_EXTERNALLY(3.12) static inline PyCodeObject *
+PyCode_NewWithPosOnlyArgs(
+        int a, int poac, int b, int c, int d, int e, PyObject *f, PyObject *g,
+        PyObject *h, PyObject *i, PyObject *j, PyObject *k,
+        PyObject *l, PyObject *m, PyObject *n, int o, PyObject *p,
+        PyObject *q)
+{
+    return PyUnstable_Code_NewWithPosOnlyArgs(
+        a, poac, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q);
+}
 
 /* Creates a new empty code object with the specified source location. */
 PyAPI_FUNC(PyCodeObject *)
@@ -269,11 +290,21 @@ PyAPI_FUNC(PyObject*) _PyCode_ConstantKey(PyObject *obj);
 PyAPI_FUNC(PyObject*) PyCode_Optimize(PyObject *code, PyObject* consts,
                                       PyObject *names, PyObject *lnotab);
 
-
-PyAPI_FUNC(int) _PyCode_GetExtra(PyObject *code, Py_ssize_t index,
-                                 void **extra);
-PyAPI_FUNC(int) _PyCode_SetExtra(PyObject *code, Py_ssize_t index,
-                                 void *extra);
+PyAPI_FUNC(int) PyUnstable_Code_GetExtra(
+    PyObject *code, Py_ssize_t index, void **extra);
+PyAPI_FUNC(int) PyUnstable_Code_SetExtra(
+    PyObject *code, Py_ssize_t index, void *extra);
+// Old names -- remove when this API changes:
+_Py_DEPRECATED_EXTERNALLY(3.12) static inline int
+_PyCode_GetExtra(PyObject *code, Py_ssize_t index, void **extra)
+{
+    return PyUnstable_Code_GetExtra(code, index, extra);
+}
+_Py_DEPRECATED_EXTERNALLY(3.12) static inline int
+_PyCode_SetExtra(PyObject *code, Py_ssize_t index, void *extra)
+{
+    return PyUnstable_Code_SetExtra(code, index, extra);
+}
 
 /* Equivalent to getattr(code, 'co_code') in Python.
    Returns a strong reference to a bytes object. */
index 40092c2f81ad484fb8f88a3c374696ef4724129a..eef0fe1bfd71d8501bb9bde5a4cf3a689028a308 100644 (file)
@@ -323,6 +323,15 @@ extern "C" {
 #define Py_DEPRECATED(VERSION_UNUSED)
 #endif
 
+// _Py_DEPRECATED_EXTERNALLY(version)
+// Deprecated outside CPython core.
+#ifdef Py_BUILD_CORE
+#define _Py_DEPRECATED_EXTERNALLY(VERSION_UNUSED)
+#else
+#define _Py_DEPRECATED_EXTERNALLY(version) Py_DEPRECATED(version)
+#endif
+
+
 #if defined(__clang__)
 #define _Py_COMP_DIAG_PUSH _Pragma("clang diagnostic push")
 #define _Py_COMP_DIAG_IGNORE_DEPR_DECLS \
index 9c2ac83e1b69e35c273d417790d6413b9e4957db..0cd1fb3f9728e58ab313190a6a2d2d91e76c348f 100644 (file)
@@ -752,15 +752,15 @@ if check_impl_detail(cpython=True) and ctypes is not None:
     py = ctypes.pythonapi
     freefunc = ctypes.CFUNCTYPE(None,ctypes.c_voidp)
 
-    RequestCodeExtraIndex = py._PyEval_RequestCodeExtraIndex
+    RequestCodeExtraIndex = py.PyUnstable_Eval_RequestCodeExtraIndex
     RequestCodeExtraIndex.argtypes = (freefunc,)
     RequestCodeExtraIndex.restype = ctypes.c_ssize_t
 
-    SetExtra = py._PyCode_SetExtra
+    SetExtra = py.PyUnstable_Code_SetExtra
     SetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t, ctypes.c_voidp)
     SetExtra.restype = ctypes.c_int
 
-    GetExtra = py._PyCode_GetExtra
+    GetExtra = py.PyUnstable_Code_GetExtra
     GetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t,
                          ctypes.POINTER(ctypes.c_voidp))
     GetExtra.restype = ctypes.c_int
diff --git a/Misc/NEWS.d/next/C API/2022-04-21-17-25-22.gh-issue-91744.FgvaMi.rst b/Misc/NEWS.d/next/C API/2022-04-21-17-25-22.gh-issue-91744.FgvaMi.rst
new file mode 100644 (file)
index 0000000..20db25d
--- /dev/null
@@ -0,0 +1,3 @@
+Introduced the *Unstable C API tier*, marking APi that is allowed to change
+in minor releases without a deprecation period.
+See :pep:`689` for details.
index 7551e5b349430e19ce3916914df6852da7870bc7..b12290d436cbeb53c2f93030804fedbe329c8ad7 100644 (file)
 @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
 @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
 @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c
-@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c _testcapi/exceptions.c
+@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c
 @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
 
 # Some testing modules MUST be built as shared libraries.
diff --git a/Modules/_testcapi/code.c b/Modules/_testcapi/code.c
new file mode 100644 (file)
index 0000000..588dc67
--- /dev/null
@@ -0,0 +1,115 @@
+#include "parts.h"
+
+static Py_ssize_t
+get_code_extra_index(PyInterpreterState* interp) {
+    Py_ssize_t result = -1;
+
+    static const char *key = "_testcapi.frame_evaluation.code_index";
+
+    PyObject *interp_dict = PyInterpreterState_GetDict(interp); // borrowed
+    assert(interp_dict);  // real users would handle missing dict... somehow
+
+    PyObject *index_obj = PyDict_GetItemString(interp_dict, key); // borrowed
+    Py_ssize_t index = 0;
+    if (!index_obj) {
+        if (PyErr_Occurred()) {
+            goto finally;
+        }
+        index = PyUnstable_Eval_RequestCodeExtraIndex(NULL);
+        if (index < 0 || PyErr_Occurred()) {
+            goto finally;
+        }
+        index_obj = PyLong_FromSsize_t(index); // strong ref
+        if (!index_obj) {
+            goto finally;
+        }
+        int res = PyDict_SetItemString(interp_dict, key, index_obj);
+        Py_DECREF(index_obj);
+        if (res < 0) {
+            goto finally;
+        }
+    }
+    else {
+        index = PyLong_AsSsize_t(index_obj);
+        if (index == -1 && PyErr_Occurred()) {
+            goto finally;
+        }
+    }
+
+    result = index;
+finally:
+    return result;
+}
+
+static PyObject *
+test_code_extra(PyObject* self, PyObject *Py_UNUSED(callable))
+{
+    PyObject *result = NULL;
+    PyObject *test_module = NULL;
+    PyObject *test_func = NULL;
+
+    // Get or initialize interpreter-specific code object storage index
+    PyInterpreterState *interp = PyInterpreterState_Get();
+    if (!interp) {
+        return NULL;
+    }
+    Py_ssize_t code_extra_index = get_code_extra_index(interp);
+    if (PyErr_Occurred()) {
+        goto finally;
+    }
+
+    // Get a function to test with
+    // This can be any Python function. Use `test.test_misc.testfunction`.
+    test_module = PyImport_ImportModule("test.test_capi.test_misc");
+    if (!test_module) {
+        goto finally;
+    }
+    test_func = PyObject_GetAttrString(test_module, "testfunction");
+    if (!test_func) {
+        goto finally;
+    }
+    PyObject *test_func_code = PyFunction_GetCode(test_func);  // borrowed
+    if (!test_func_code) {
+        goto finally;
+    }
+
+    // Check the value is initially NULL
+    void *extra;
+    int res = PyUnstable_Code_GetExtra(test_func_code, code_extra_index, &extra);
+    if (res < 0) {
+        goto finally;
+    }
+    assert (extra == NULL);
+
+    // Set another code extra value
+    res = PyUnstable_Code_SetExtra(test_func_code, code_extra_index, (void*)(uintptr_t)77);
+    if (res < 0) {
+        goto finally;
+    }
+    // Assert it was set correctly
+    res = PyUnstable_Code_GetExtra(test_func_code, code_extra_index, &extra);
+    if (res < 0) {
+        goto finally;
+    }
+    assert ((uintptr_t)extra == 77);
+
+    result = Py_NewRef(Py_None);
+finally:
+    Py_XDECREF(test_module);
+    Py_XDECREF(test_func);
+    return result;
+}
+
+static PyMethodDef TestMethods[] = {
+    {"test_code_extra", test_code_extra, METH_NOARGS},
+    {NULL},
+};
+
+int
+_PyTestCapi_Init_Code(PyObject *m) {
+    if (PyModule_AddFunctions(m, TestMethods) < 0) {
+        return -1;
+    }
+
+    return 0;
+}
index 1689f186b833f60a3540d2ec5cfc449991f02043..c8f31dc8e39faec28a62f2edd4052b43d1cdddaa 100644 (file)
@@ -37,6 +37,7 @@ int _PyTestCapi_Init_Long(PyObject *module);
 int _PyTestCapi_Init_Float(PyObject *module);
 int _PyTestCapi_Init_Structmember(PyObject *module);
 int _PyTestCapi_Init_Exceptions(PyObject *module);
+int _PyTestCapi_Init_Code(PyObject *module);
 
 #ifdef LIMITED_API_AVAILABLE
 int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
index fc716a3564d39adee966d7fbb4e072db3764893f..10e507d6b481de04f6a3ab83ab642bbf800202b9 100644 (file)
@@ -4083,6 +4083,9 @@ PyInit__testcapi(void)
     if (_PyTestCapi_Init_Exceptions(m) < 0) {
         return NULL;
     }
+    if (_PyTestCapi_Init_Code(m) < 0) {
+        return NULL;
+    }
 
 #ifndef LIMITED_API_AVAILABLE
     PyModule_AddObjectRef(m, "LIMITED_API_AVAILABLE", Py_False);
index a03b14edea8d4c156d73a2980ce65cc03a587094..175bd57568f8f6524f8239608921158281cb9e7a 100644 (file)
@@ -567,7 +567,8 @@ _PyCode_New(struct _PyCodeConstructor *con)
  ******************/
 
 PyCodeObject *
-PyCode_NewWithPosOnlyArgs(int argcount, int posonlyargcount, int kwonlyargcount,
+PyUnstable_Code_NewWithPosOnlyArgs(
+                          int argcount, int posonlyargcount, int kwonlyargcount,
                           int nlocals, int stacksize, int flags,
                           PyObject *code, PyObject *consts, PyObject *names,
                           PyObject *varnames, PyObject *freevars, PyObject *cellvars,
@@ -691,7 +692,7 @@ error:
 }
 
 PyCodeObject *
-PyCode_New(int argcount, int kwonlyargcount,
+PyUnstable_Code_New(int argcount, int kwonlyargcount,
            int nlocals, int stacksize, int flags,
            PyObject *code, PyObject *consts, PyObject *names,
            PyObject *varnames, PyObject *freevars, PyObject *cellvars,
@@ -1371,7 +1372,7 @@ typedef struct {
 
 
 int
-_PyCode_GetExtra(PyObject *code, Py_ssize_t index, void **extra)
+PyUnstable_Code_GetExtra(PyObject *code, Py_ssize_t index, void **extra)
 {
     if (!PyCode_Check(code)) {
         PyErr_BadInternalCall();
@@ -1392,7 +1393,7 @@ _PyCode_GetExtra(PyObject *code, Py_ssize_t index, void **extra)
 
 
 int
-_PyCode_SetExtra(PyObject *code, Py_ssize_t index, void *extra)
+PyUnstable_Code_SetExtra(PyObject *code, Py_ssize_t index, void *extra)
 {
     PyInterpreterState *interp = _PyInterpreterState_GET();
 
index 742eb3ed2d90569a51c84fad127778a7a11d523d..4cc184bfc1ac829791156a2eb12c242e8172c932 100644 (file)
     <ClCompile Include="..\Modules\_testcapi\long.c" />
     <ClCompile Include="..\Modules\_testcapi\structmember.c" />
     <ClCompile Include="..\Modules\_testcapi\exceptions.c" />
+    <ClCompile Include="..\Modules\_testcapi\code.c" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\PC\python_nt.rc" />
index ab5afc150c32f5ce98281b7b025ab8ccfee987a8..fbdaf04ce37cb14d43ce268c1c8b9a3ed8dbf1a9 100644 (file)
@@ -54,6 +54,9 @@
     <ClCompile Include="..\Modules\_testcapi\exceptions.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\Modules\_testcapi\code.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\PC\python_nt.rc">
index b382d2109b93b7f1f6778bc62f8167ed58a3e1ee..5540c93d5e3dd7429d86dcaa75ddcfb9a911fe2b 100644 (file)
@@ -2988,7 +2988,7 @@ format_awaitable_error(PyThreadState *tstate, PyTypeObject *type, int oparg)
 
 
 Py_ssize_t
-_PyEval_RequestCodeExtraIndex(freefunc free)
+PyUnstable_Eval_RequestCodeExtraIndex(freefunc free)
 {
     PyInterpreterState *interp = _PyInterpreterState_GET();
     Py_ssize_t new_index;