Skip to content

Commit

Permalink
gh-104922: Make PY_SSIZE_T_CLEAN not mandatory again (#105051)
Browse files Browse the repository at this point in the history
  • Loading branch information
methane committed May 31, 2023
1 parent f90d3f6 commit adccff3
Show file tree
Hide file tree
Showing 15 changed files with 106 additions and 398 deletions.
18 changes: 9 additions & 9 deletions Doc/c-api/arg.rst
Expand Up @@ -27,9 +27,18 @@ unit; the entry in (round) parentheses is the Python object type that matches
the format unit; and the entry in [square] brackets is the type of the C
variable(s) whose address should be passed.

.. _arg-parsing-string-and-buffers:

Strings and buffers
-------------------

.. note::

On Python 3.12 and older, the macro :c:macro:`!PY_SSIZE_T_CLEAN` must be
defined before including :file:`Python.h` to use all ``#`` variants of
formats (``s#``, ``y#``, etc.) explained below.
This is not necessary on Python 3.13 and later.

These formats allow accessing an object as a contiguous chunk of memory.
You don't have to provide raw storage for the returned unicode or bytes
area.
Expand Down Expand Up @@ -68,15 +77,6 @@ There are three ways strings and buffers can be converted to C:
whether the input object is immutable (e.g. whether it would honor a request
for a writable buffer, or whether another thread can mutate the data).

.. note::

For all ``#`` variants of formats (``s#``, ``y#``, etc.), the macro
:c:macro:`PY_SSIZE_T_CLEAN` must be defined before including
:file:`Python.h`. On Python 3.9 and older, the type of the length argument
is :c:type:`Py_ssize_t` if the :c:macro:`PY_SSIZE_T_CLEAN` macro is defined,
or int otherwise.


``s`` (:class:`str`) [const char \*]
Convert a Unicode object to a C pointer to a character string.
A pointer to an existing string is stored in the character pointer
Expand Down
6 changes: 3 additions & 3 deletions Doc/extending/extending.rst
Expand Up @@ -70,7 +70,7 @@ the module and a copyright notice if you like).
headers are included.

It is recommended to always define ``PY_SSIZE_T_CLEAN`` before including
``Python.h``. See :ref:`parsetuple` for a description of this macro.
``Python.h``. See :ref:`arg-parsing-string-and-buffers` for a description of this macro.

All user-visible symbols defined by :file:`Python.h` have a prefix of ``Py`` or
``PY``, except those defined in standard header files. For convenience, and
Expand Down Expand Up @@ -649,7 +649,7 @@ Note that any Python object references which are provided to the caller are

Some example calls::

#define PY_SSIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */
#define PY_SSIZE_T_CLEAN
#include <Python.h>

::
Expand Down Expand Up @@ -745,7 +745,7 @@ it returns false and raises an appropriate exception.
Here is an example module which uses keywords, based on an example by Geoff
Philbrick (philbrick@hks.com)::

#define PY_SSIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */
#define PY_SSIZE_T_CLEAN
#include <Python.h>

static PyObject *
Expand Down
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.13.rst
Expand Up @@ -292,6 +292,13 @@ C API Changes
New Features
------------

* You no longer have to define the ``PY_SSIZE_T_CLEAN`` macro before including
:file:`Python.h` when using ``#`` formats in
:ref:`format codes <arg-parsing-string-and-buffers>`.
APIs accepting the format codes always use ``Py_ssize_t`` for ``#`` formats.
(Contributed by Inada Naoki in :gh:`104922`.)


Porting to Python 3.13
----------------------

Expand Down
15 changes: 0 additions & 15 deletions Include/abstract.h
Expand Up @@ -135,12 +135,6 @@ extern "C" {
This function always succeeds. */


#ifdef PY_SSIZE_T_CLEAN
# define PyObject_CallFunction _PyObject_CallFunction_SizeT
# define PyObject_CallMethod _PyObject_CallMethod_SizeT
#endif


#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03090000
/* Call a callable Python object without any arguments */
PyAPI_FUNC(PyObject *) PyObject_CallNoArgs(PyObject *func);
Expand Down Expand Up @@ -195,15 +189,6 @@ PyAPI_FUNC(PyObject *) PyObject_CallMethod(PyObject *obj,
const char *name,
const char *format, ...);

PyAPI_FUNC(PyObject *) _PyObject_CallFunction_SizeT(PyObject *callable,
const char *format,
...);

PyAPI_FUNC(PyObject *) _PyObject_CallMethod_SizeT(PyObject *obj,
const char *name,
const char *format,
...);

/* Call a callable Python object 'callable' with a variable number of C
arguments. The C arguments are provided as PyObject* values, terminated
by a NULL.
Expand Down
9 changes: 0 additions & 9 deletions Include/cpython/abstract.h
Expand Up @@ -4,10 +4,6 @@

/* === Object Protocol ================================================== */

#ifdef PY_SSIZE_T_CLEAN
# define _PyObject_CallMethodId _PyObject_CallMethodId_SizeT
#endif

/* Convert keyword arguments from the FASTCALL (stack: C array, kwnames: tuple)
format to a Python dictionary ("kwargs" dict).
Expand Down Expand Up @@ -113,11 +109,6 @@ PyAPI_FUNC(PyObject *) _PyObject_CallMethodId(PyObject *obj,
_Py_Identifier *name,
const char *format, ...);

PyAPI_FUNC(PyObject *) _PyObject_CallMethodId_SizeT(PyObject *obj,
_Py_Identifier *name,
const char *format,
...);

PyAPI_FUNC(PyObject *) _PyObject_CallMethodIdObjArgs(
PyObject *obj,
_Py_Identifier *name,
Expand Down
21 changes: 0 additions & 21 deletions Include/cpython/modsupport.h
Expand Up @@ -2,20 +2,6 @@
# error "this header file must not be included directly"
#endif

/* If PY_SSIZE_T_CLEAN is defined, each functions treats #-specifier
to mean Py_ssize_t */
#ifdef PY_SSIZE_T_CLEAN
#define _Py_VaBuildStack _Py_VaBuildStack_SizeT
#else
PyAPI_FUNC(PyObject *) _Py_VaBuildValue_SizeT(const char *, va_list);
PyAPI_FUNC(PyObject **) _Py_VaBuildStack_SizeT(
PyObject **small_stack,
Py_ssize_t small_stack_len,
const char *format,
va_list va,
Py_ssize_t *p_nargs);
#endif

PyAPI_FUNC(int) _PyArg_UnpackStack(
PyObject *const *args,
Py_ssize_t nargs,
Expand Down Expand Up @@ -63,13 +49,6 @@ typedef struct _PyArg_Parser {
struct _PyArg_Parser *next;
} _PyArg_Parser;

#ifdef PY_SSIZE_T_CLEAN
#define _PyArg_ParseTupleAndKeywordsFast _PyArg_ParseTupleAndKeywordsFast_SizeT
#define _PyArg_ParseStack _PyArg_ParseStack_SizeT
#define _PyArg_ParseStackAndKeywords _PyArg_ParseStackAndKeywords_SizeT
#define _PyArg_VaParseTupleAndKeywordsFast _PyArg_VaParseTupleAndKeywordsFast_SizeT
#endif

PyAPI_FUNC(int) _PyArg_ParseTupleAndKeywordsFast(PyObject *, PyObject *,
struct _PyArg_Parser *, ...);
PyAPI_FUNC(int) _PyArg_ParseStack(
Expand Down
23 changes: 3 additions & 20 deletions Include/modsupport.h
Expand Up @@ -9,34 +9,17 @@ extern "C" {

#include <stdarg.h> // va_list

/* If PY_SSIZE_T_CLEAN is defined, each functions treats #-specifier
to mean Py_ssize_t */
#ifdef PY_SSIZE_T_CLEAN
#define PyArg_Parse _PyArg_Parse_SizeT
#define PyArg_ParseTuple _PyArg_ParseTuple_SizeT
#define PyArg_ParseTupleAndKeywords _PyArg_ParseTupleAndKeywords_SizeT
#define PyArg_VaParse _PyArg_VaParse_SizeT
#define PyArg_VaParseTupleAndKeywords _PyArg_VaParseTupleAndKeywords_SizeT
#define Py_BuildValue _Py_BuildValue_SizeT
#define Py_VaBuildValue _Py_VaBuildValue_SizeT
#endif

/* Due to a glitch in 3.2, the _SizeT versions weren't exported from the DLL. */
#if !defined(PY_SSIZE_T_CLEAN) || !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03030000
PyAPI_FUNC(int) PyArg_Parse(PyObject *, const char *, ...);
PyAPI_FUNC(int) PyArg_ParseTuple(PyObject *, const char *, ...);
PyAPI_FUNC(int) PyArg_ParseTupleAndKeywords(PyObject *, PyObject *,
const char *, char **, ...);
const char *, char **, ...);
PyAPI_FUNC(int) PyArg_VaParse(PyObject *, const char *, va_list);
PyAPI_FUNC(int) PyArg_VaParseTupleAndKeywords(PyObject *, PyObject *,
const char *, char **, va_list);
#endif
const char *, char **, va_list);

PyAPI_FUNC(int) PyArg_ValidateKeywordArguments(PyObject *);
PyAPI_FUNC(int) PyArg_UnpackTuple(PyObject *, const char *, Py_ssize_t, Py_ssize_t, ...);
PyAPI_FUNC(PyObject *) Py_BuildValue(const char *, ...);
PyAPI_FUNC(PyObject *) _Py_BuildValue_SizeT(const char *, ...);


PyAPI_FUNC(PyObject *) Py_VaBuildValue(const char *, va_list);

// Add an attribute with name 'name' and value 'obj' to the module 'mod.
Expand Down
17 changes: 0 additions & 17 deletions Lib/test/test_capi/test_getargs.py
Expand Up @@ -901,23 +901,6 @@ def test_s_hash(self):
self.assertRaises(TypeError, getargs_s_hash, memoryview(b'memoryview'))
self.assertRaises(TypeError, getargs_s_hash, None)

def test_s_hash_int(self):
# "s#" without PY_SSIZE_T_CLEAN defined.
from _testcapi import getargs_s_hash_int
from _testcapi import getargs_s_hash_int2
buf = bytearray([1, 2])
self.assertRaises(SystemError, getargs_s_hash_int, buf, "abc")
self.assertRaises(SystemError, getargs_s_hash_int, buf, x=42)
self.assertRaises(SystemError, getargs_s_hash_int, buf, x="abc")
self.assertRaises(SystemError, getargs_s_hash_int2, buf, ("abc",))
self.assertRaises(SystemError, getargs_s_hash_int2, buf, x=42)
self.assertRaises(SystemError, getargs_s_hash_int2, buf, x="abc")
buf.append(3) # still mutable -- not locked by a buffer export
# getargs_s_hash_int(buf) may not raise SystemError because skipitem()
# is not called. But it is an implementation detail.
# getargs_s_hash_int(buf)
# getargs_s_hash_int2(buf)

def test_z(self):
from _testcapi import getargs_z
self.assertEqual(getargs_z('abc\xe9'), b'abc\xc3\xa9')
Expand Down
@@ -0,0 +1,3 @@
``PY_SSIZE_T_CLEAN`` is no longer required to use ``'#'`` formats in APIs
like :c:func:`PyArg_ParseTuple` and :c:func:`Py_BuildValue`. They uses
``Py_ssize_t`` for ``'#'`` regardless ``PY_SSIZE_T_CLEAN``.
40 changes: 0 additions & 40 deletions Modules/_testcapi/getargs.c
Expand Up @@ -816,44 +816,6 @@ test_s_code(PyObject *self, PyObject *Py_UNUSED(ignored))
Py_RETURN_NONE;
}

#undef PyArg_ParseTupleAndKeywords
PyAPI_FUNC(int) PyArg_ParseTupleAndKeywords(PyObject *, PyObject *,
const char *, char **, ...);

static PyObject *
getargs_s_hash_int(PyObject *self, PyObject *args, PyObject *kwargs)
{
static char *keywords[] = {"", "", "x", NULL};
Py_buffer buf = {NULL};
const char *s;
int len;
int i = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "w*|s#i", keywords,
&buf, &s, &len, &i))
{
return NULL;
}
PyBuffer_Release(&buf);
Py_RETURN_NONE;
}

static PyObject *
getargs_s_hash_int2(PyObject *self, PyObject *args, PyObject *kwargs)
{
static char *keywords[] = {"", "", "x", NULL};
Py_buffer buf = {NULL};
const char *s;
int len;
int i = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "w*|(s#)i", keywords,
&buf, &s, &len, &i))
{
return NULL;
}
PyBuffer_Release(&buf);
Py_RETURN_NONE;
}

static PyObject *
gh_99240_clear_args(PyObject *self, PyObject *args)
{
Expand Down Expand Up @@ -906,8 +868,6 @@ static PyMethodDef test_methods[] = {
{"getargs_positional_only_and_keywords", _PyCFunction_CAST(getargs_positional_only_and_keywords), METH_VARARGS|METH_KEYWORDS},
{"getargs_s", getargs_s, METH_VARARGS},
{"getargs_s_hash", getargs_s_hash, METH_VARARGS},
{"getargs_s_hash_int", _PyCFunction_CAST(getargs_s_hash_int), METH_VARARGS|METH_KEYWORDS},
{"getargs_s_hash_int2", _PyCFunction_CAST(getargs_s_hash_int2), METH_VARARGS|METH_KEYWORDS},
{"getargs_s_star", getargs_s_star, METH_VARARGS},
{"getargs_tuple", getargs_tuple, METH_VARARGS},
{"getargs_u", getargs_u, METH_VARARGS},
Expand Down
47 changes: 0 additions & 47 deletions Modules/_testcapimodule.c
Expand Up @@ -3266,8 +3266,6 @@ test_atexit(PyObject *self, PyObject *Py_UNUSED(args))
}


static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *);

static PyMethodDef TestMethods[] = {
{"set_errno", set_errno, METH_VARARGS},
{"test_config", test_config, METH_NOARGS},
Expand Down Expand Up @@ -3297,7 +3295,6 @@ static PyMethodDef TestMethods[] = {
{"getbuffer_with_null_view", getbuffer_with_null_view, METH_O},
{"PyBuffer_SizeFromFormat", test_PyBuffer_SizeFromFormat, METH_VARARGS},
{"test_buildvalue_N", test_buildvalue_N, METH_NOARGS},
{"test_buildvalue_issue38913", test_buildvalue_issue38913, METH_NOARGS},
{"test_get_statictype_slots", test_get_statictype_slots, METH_NOARGS},
{"test_get_type_name", test_get_type_name, METH_NOARGS},
{"test_get_type_qualname", test_get_type_qualname, METH_NOARGS},
Expand Down Expand Up @@ -4067,47 +4064,3 @@ PyInit__testcapi(void)
PyState_AddModule(m, &_testcapimodule);
return m;
}

/* Test the C API exposed when PY_SSIZE_T_CLEAN is not defined */

#undef Py_BuildValue
PyAPI_FUNC(PyObject *) Py_BuildValue(const char *, ...);

static PyObject *
test_buildvalue_issue38913(PyObject *self, PyObject *Py_UNUSED(ignored))
{
PyObject *res;
const char str[] = "string";
const Py_UNICODE unicode[] = L"unicode";
assert(!PyErr_Occurred());

res = Py_BuildValue("(s#O)", str, 1, Py_None);
assert(res == NULL);
if (!PyErr_ExceptionMatches(PyExc_SystemError)) {
return NULL;
}
PyErr_Clear();

res = Py_BuildValue("(z#O)", str, 1, Py_None);
assert(res == NULL);
if (!PyErr_ExceptionMatches(PyExc_SystemError)) {
return NULL;
}
PyErr_Clear();

res = Py_BuildValue("(y#O)", str, 1, Py_None);
assert(res == NULL);
if (!PyErr_ExceptionMatches(PyExc_SystemError)) {
return NULL;
}
PyErr_Clear();

res = Py_BuildValue("(u#O)", unicode, 1, Py_None);
assert(res == NULL);
if (!PyErr_ExceptionMatches(PyExc_SystemError)) {
return NULL;
}
PyErr_Clear();

Py_RETURN_NONE;
}

0 comments on commit adccff3

Please sign in to comment.