Skip to content

Commit

Permalink
WIP: pythongh-106593: PyResource API
Browse files Browse the repository at this point in the history
* Add PyUnicode_AsUTF8Resource()
* Add PyBytes_AsStringResource()
* Add PySequence_AsObjectArray()
* Add Include/pyresource.h
* Add PyResource_Close() to the stable ABI
* compute_abstract_methods(): Replace PySequence_Fast()
  with PySequence_AsObjectArray()
  • Loading branch information
vstinner committed Jul 17, 2023
1 parent b2b261a commit 8af4606
Show file tree
Hide file tree
Showing 17 changed files with 176 additions and 16 deletions.
2 changes: 2 additions & 0 deletions Doc/data/stable_abi.dat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Include/Python.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "pymacro.h"
#include "pymath.h"
#include "pymem.h"
#include "pyresource.h"
#include "pytypedefs.h"
#include "pybuffer.h"
#include "object.h"
Expand Down
6 changes: 6 additions & 0 deletions Include/abstract.h
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,12 @@ PyAPI_FUNC(PyObject *) PySequence_List(PyObject *o);
TypeError exception with 'm' as the message text. */
PyAPI_FUNC(PyObject *) PySequence_Fast(PyObject *o, const char* m);

PyAPI_FUNC(int) PySequence_AsObjectArray(
PyObject *,
PyResource *res,
PyObject ***parray,
Py_ssize_t *psize);

/* Return the size of the sequence 'o', assuming that 'o' was returned by
PySequence_Fast and is not NULL. */
#define PySequence_Fast_GET_SIZE(o) \
Expand Down
1 change: 1 addition & 0 deletions Include/bytesobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ PyAPI_FUNC(PyObject *) PyBytes_FromFormat(const char*, ...)
Py_GCC_ATTRIBUTE((format(printf, 1, 2)));
PyAPI_FUNC(Py_ssize_t) PyBytes_Size(PyObject *);
PyAPI_FUNC(char *) PyBytes_AsString(PyObject *);
PyAPI_FUNC(char *) PyBytes_AsStringResource(PyObject *, PyResource *res);
PyAPI_FUNC(PyObject *) PyBytes_Repr(PyObject *, int);
PyAPI_FUNC(void) PyBytes_Concat(PyObject **, PyObject *);
PyAPI_FUNC(void) PyBytes_ConcatAndDel(PyObject **, PyObject *);
Expand Down
4 changes: 4 additions & 0 deletions Include/cpython/unicodeobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,10 @@ PyAPI_FUNC(PyObject*) PyUnicode_FromKindAndData(

PyAPI_FUNC(const char *) PyUnicode_AsUTF8(PyObject *unicode);

PyAPI_FUNC(const char *) PyUnicode_AsUTF8(PyObject *unicode);

PyAPI_FUNC(const char *) PyUnicode_AsUTF8Resource(PyObject *unicode, PyResource *res);

#define _PyUnicode_AsString PyUnicode_AsUTF8

/* === Characters Type APIs =============================================== */
Expand Down
21 changes: 21 additions & 0 deletions Include/pyresource.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#ifndef Py_RESOURCE_H
#define Py_RESOURCE_H
#ifdef __cplusplus
extern "C" {
#endif

typedef struct {
void (*close_func) (void *data);
void *data;
} PyResource;

PyAPI_FUNC(void) PyResource_Close(PyResource *res);

#ifdef Py_BUILD_CORE
PyAPI_FUNC(void) _PyResource_DECREF(void *data);
#endif

#ifdef __cplusplus
}
#endif
#endif // !Py_RESOURCE_H
1 change: 1 addition & 0 deletions Lib/test/test_stable_abi_ctypes.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -1657,6 +1657,7 @@ PYTHON_HEADERS= \
$(srcdir)/Include/pymath.h \
$(srcdir)/Include/pymem.h \
$(srcdir)/Include/pyport.h \
$(srcdir)/Include/pyresource.h \
$(srcdir)/Include/pystate.h \
$(srcdir)/Include/pystats.h \
$(srcdir)/Include/pystrcmp.h \
Expand Down
5 changes: 5 additions & 0 deletions Misc/stable_abi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2444,3 +2444,8 @@
added = '3.13'
[function.PyMapping_GetOptionalItemString]
added = '3.13'
[struct.PyResource]
added = '3.13'
struct_abi_kind = 'full-abi'
[function.PyResource_Close]
added = '3.13'
44 changes: 28 additions & 16 deletions Modules/_abc.c
Original file line number Diff line number Diff line change
Expand Up @@ -317,34 +317,46 @@ compute_abstract_methods(PyObject *self)
}
assert(PyList_Check(items));
for (Py_ssize_t pos = 0; pos < PyList_GET_SIZE(items); pos++) {
PyObject *it = PySequence_Fast(
PyList_GET_ITEM(items, pos),
"items() returned non-iterable");
if (!it) {
PyObject *seq = PyList_GET_ITEM(items, pos);
PyResource res;
PyObject **items;
Py_ssize_t nitem;
if (PySequence_AsObjectArray(seq, &res, &items, &nitem) < 0) {
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
PyErr_SetString(PyExc_TypeError, "items() returned non-iterable");
}
goto error;
}
if (PySequence_Fast_GET_SIZE(it) != 2) {
if (nitem != 2) {
PyErr_SetString(PyExc_TypeError,
"items() returned item which size is not 2");
Py_DECREF(it);
PyResource_Close(&res);
goto error;
}

// borrowed
PyObject *key = PySequence_Fast_GET_ITEM(it, 0);
PyObject *value = PySequence_Fast_GET_ITEM(it, 1);
// items or it may be cleared while accessing __abstractmethod__
// So we need to keep strong reference for key
Py_INCREF(key);
PyObject *key = Py_NewRef(items[0]);
PyObject *value = Py_NewRef(items[1]);
PyResource_Close(&res);

int is_abstract = _PyObject_IsAbstract(value);
if (is_abstract < 0 ||
(is_abstract && PySet_Add(abstracts, key) < 0)) {
Py_DECREF(it);
Py_DECREF(key);
goto error;
int err;
if (is_abstract < 0) {
err = 1;
}
else if (is_abstract) {
err = PySet_Add(abstracts, key) < 0;
}
else {
err = 0;
}
Py_DECREF(key);
Py_DECREF(it);
Py_DECREF(value);

if (err) {
goto error;
}
}

/* Stage 2: inherited abstract methods. */
Expand Down
66 changes: 66 additions & 0 deletions Objects/abstract.c
Original file line number Diff line number Diff line change
Expand Up @@ -2205,6 +2205,72 @@ PySequence_Fast(PyObject *v, const char *m)
return v;
}

static void pysequence_decref_track(void *data)
{
PyObject *obj = _PyObject_CAST(data);
_PyObject_GC_TRACK(obj);
Py_DECREF(obj);
}

int
PySequence_AsObjectArray(PyObject *v, PyResource *res,
PyObject ***parray, Py_ssize_t *psize)
{
if (v == NULL) {
null_error();
goto error;
}

if (PyTuple_CheckExact(v)) {
if (_PyObject_GC_IS_TRACKED(v)) {
// Don't track the tuple while it's being used
_PyObject_GC_UNTRACK(v);
res->close_func = pysequence_decref_track;
}
else {
res->close_func = _PyResource_DECREF;
}
res->data = Py_NewRef(v);
*parray = _PyTuple_ITEMS(v);
*psize = PyTuple_GET_SIZE(v);
return 0;
}

if (PyList_CheckExact(v)) {
// Don't track the list while it's being used
_PyObject_GC_UNTRACK(v);
res->close_func = pysequence_decref_track;
res->data = Py_NewRef(v);
*parray = _PyList_ITEMS(v);
*psize = PyList_GET_SIZE(v);
return 0;
}

PyObject *it = PyObject_GetIter(v);
if (it == NULL) {
goto error;
}

v = PySequence_List(it);
Py_DECREF(it);
if (v == NULL) {
goto error;
}

// Don't track the list while it's being used
_PyObject_GC_UNTRACK(v);
res->close_func = pysequence_decref_track;
res->data = v;
*parray = _PyList_ITEMS(v);
*psize = PyList_GET_SIZE(v);
return 0;

error:
*parray = NULL;
*psize = 0;
return -1;
}

/* Iterate over seq. Result depends on the operation:
PY_ITERSEARCH_COUNT: -1 if error, else # of times obj appears in seq.
PY_ITERSEARCH_INDEX: 0-based index of first occurrence of obj in seq;
Expand Down
13 changes: 13 additions & 0 deletions Objects/bytesobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1220,6 +1220,19 @@ PyBytes_AsString(PyObject *op)
return ((PyBytesObject *)op)->ob_sval;
}

char *
PyBytes_AsStringResource(PyObject *op, PyResource *res)
{
if (!PyBytes_Check(op)) {
PyErr_Format(PyExc_TypeError,
"expected bytes, %.200s found", Py_TYPE(op)->tp_name);
return NULL;
}
res->close_func = _PyResource_DECREF;
res->data = Py_NewRef(op);
return ((PyBytesObject *)op)->ob_sval;
}

int
PyBytes_AsStringAndSize(PyObject *obj,
char **s,
Expand Down
14 changes: 14 additions & 0 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -2678,6 +2678,20 @@ int Py_IsFalse(PyObject *x)
return Py_Is(x, Py_False);
}

void _PyResource_DECREF(void *data)
{
PyObject *obj = _PyObject_CAST(data);
Py_DECREF(obj);
}

void PyResource_Close(PyResource *res)
{
if (res->close_func == NULL) {
return;
}
res->close_func(res->data);
}

#ifdef __cplusplus
}
#endif
8 changes: 8 additions & 0 deletions Objects/unicodeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3806,6 +3806,14 @@ PyUnicode_AsUTF8(PyObject *unicode)
return PyUnicode_AsUTF8AndSize(unicode, NULL);
}

const char *
PyUnicode_AsUTF8Resource(PyObject *unicode, PyResource *res)
{
res->close_func = _PyResource_DECREF;
res->data = Py_NewRef(unicode);
return PyUnicode_AsUTF8AndSize(unicode, NULL);
}

/*
PyUnicode_GetSize() has been deprecated since Python 3.3
because it returned length of Py_UNICODE.
Expand Down
1 change: 1 addition & 0 deletions PC/python3dll.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions PCbuild/pythoncore.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@
<ClInclude Include="..\Include\pymath.h" />
<ClInclude Include="..\Include\pymem.h" />
<ClInclude Include="..\Include\pyport.h" />
<ClInclude Include="..\Include\pyresource.h" />
<ClInclude Include="..\Include\pystate.h" />
<ClInclude Include="..\Include\pystats.h" />
<ClInclude Include="..\Include\pystrcmp.h" />
Expand Down
3 changes: 3 additions & 0 deletions PCbuild/pythoncore.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@
<ClInclude Include="..\Include\pyport.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="..\Include\pyresource.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="..\Include\pystate.h">
<Filter>Include</Filter>
</ClInclude>
Expand Down

0 comments on commit 8af4606

Please sign in to comment.