Skip to content

Commit

Permalink
Merge branch 'main' into pythongh-127794
Browse files Browse the repository at this point in the history
  • Loading branch information
srinivasreddy authored Dec 11, 2024
2 parents d04c9a2 + ce76b54 commit bcab963
Show file tree
Hide file tree
Showing 13 changed files with 141 additions and 48 deletions.
9 changes: 9 additions & 0 deletions Doc/c-api/init.rst
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,15 @@ Initializing and finalizing the interpreter
customized Python that always runs in isolated mode using
:c:func:`Py_RunMain`.
.. c:function:: int PyUnstable_AtExit(PyInterpreterState *interp, void (*func)(void *), void *data)
Register an :mod:`atexit` callback for the target interpreter *interp*.
This is similar to :c:func:`Py_AtExit`, but takes an explicit interpreter and
data pointer for the callback.
The :term:`GIL` must be held for *interp*.
.. versionadded:: 3.13
Process-wide parameters
=======================
Expand Down
4 changes: 4 additions & 0 deletions Doc/c-api/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -426,3 +426,7 @@ Process Control
function registered last is called first. Each cleanup function will be called
at most once. Since Python's internal finalization will have completed before
the cleanup function, no Python APIs should be called by *func*.
.. seealso::
:c:func:`PyUnstable_AtExit` for passing a ``void *data`` argument.
6 changes: 6 additions & 0 deletions Include/cpython/tracemalloc.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#ifndef Py_LIMITED_API
#ifndef Py_TRACEMALLOC_H
#define Py_TRACEMALLOC_H
#ifdef __cplusplus
extern "C" {
#endif

/* Track an allocated memory block in the tracemalloc module.
Return 0 on success, return -1 on error (failed to allocate memory to store
Expand All @@ -22,5 +25,8 @@ PyAPI_FUNC(int) PyTraceMalloc_Untrack(
unsigned int domain,
uintptr_t ptr);

#ifdef __cplusplus
}
#endif
#endif // !Py_TRACEMALLOC_H
#endif // !Py_LIMITED_API
1 change: 0 additions & 1 deletion Include/internal/pycore_atexit.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ typedef struct {

struct atexit_state {
atexit_callback *ll_callbacks;
atexit_callback *last_ll_callback;

// XXX The rest of the state could be moved to the atexit module state
// and a low-level callback added for it during module exec.
Expand Down
4 changes: 2 additions & 2 deletions Lib/pickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -1387,7 +1387,7 @@ def load_int(self):
elif data == TRUE[1:]:
val = True
else:
val = int(data, 0)
val = int(data)
self.append(val)
dispatch[INT[0]] = load_int

Expand All @@ -1407,7 +1407,7 @@ def load_long(self):
val = self.readline()[:-1]
if val and val[-1] == b'L'[0]:
val = val[:-1]
self.append(int(val, 0))
self.append(int(val))
dispatch[LONG[0]] = load_long

def load_long1(self):
Expand Down
20 changes: 20 additions & 0 deletions Lib/test/pickletester.py
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,26 @@ def test_constants(self):
self.assertIs(self.loads(b'I01\n.'), True)
self.assertIs(self.loads(b'I00\n.'), False)

def test_zero_padded_integers(self):
self.assertEqual(self.loads(b'I010\n.'), 10)
self.assertEqual(self.loads(b'I-010\n.'), -10)
self.assertEqual(self.loads(b'I0010\n.'), 10)
self.assertEqual(self.loads(b'I-0010\n.'), -10)
self.assertEqual(self.loads(b'L010\n.'), 10)
self.assertEqual(self.loads(b'L-010\n.'), -10)
self.assertEqual(self.loads(b'L0010\n.'), 10)
self.assertEqual(self.loads(b'L-0010\n.'), -10)
self.assertEqual(self.loads(b'L010L\n.'), 10)
self.assertEqual(self.loads(b'L-010L\n.'), -10)

def test_nondecimal_integers(self):
self.assertRaises(ValueError, self.loads, b'I0b10\n.')
self.assertRaises(ValueError, self.loads, b'I0o10\n.')
self.assertRaises(ValueError, self.loads, b'I0x10\n.')
self.assertRaises(ValueError, self.loads, b'L0b10L\n.')
self.assertRaises(ValueError, self.loads, b'L0o10L\n.')
self.assertRaises(ValueError, self.loads, b'L0x10L\n.')

def test_empty_bytestring(self):
# issue 11286
empty = self.loads(b'\x80\x03U\x00q\x00.', encoding='koi8-r')
Expand Down
37 changes: 37 additions & 0 deletions Lib/test/test_pickletools.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,43 @@ def test_persid(self):
highest protocol among opcodes = 0
''')

def test_constants(self):
self.check_dis(b"(NI00\nI01\n\x89\x88t.", '''\
0: ( MARK
1: N NONE
2: I INT False
6: I INT True
10: \\x89 NEWFALSE
11: \\x88 NEWTRUE
12: t TUPLE (MARK at 0)
13: . STOP
highest protocol among opcodes = 2
''')

def test_integers(self):
self.check_dis(b"(I0\nI1\nI10\nI011\nL12\nL13L\nL014\nL015L\nt.", '''\
0: ( MARK
1: I INT 0
4: I INT 1
7: I INT 10
11: I INT 11
16: L LONG 12
20: L LONG 13
25: L LONG 14
30: L LONG 15
36: t TUPLE (MARK at 0)
37: . STOP
highest protocol among opcodes = 0
''')

def test_nondecimal_integers(self):
self.check_dis_error(b'I0b10\n.', '', 'invalid literal for int')
self.check_dis_error(b'I0o10\n.', '', 'invalid literal for int')
self.check_dis_error(b'I0x10\n.', '', 'invalid literal for int')
self.check_dis_error(b'L0b10L\n.', '', 'invalid literal for int')
self.check_dis_error(b'L0o10L\n.', '', 'invalid literal for int')
self.check_dis_error(b'L0x10L\n.', '', 'invalid literal for int')


class MiscTestCase(unittest.TestCase):
def test__all__(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix loss of callbacks after more than one call to
:c:func:`PyUnstable_AtExit`.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix LONG and INT opcodes to only use base 10 for string to integer conversion in :mod:`pickle`.
11 changes: 4 additions & 7 deletions Modules/_pickle.c
Original file line number Diff line number Diff line change
Expand Up @@ -5211,16 +5211,14 @@ load_int(PickleState *state, UnpicklerObject *self)
return bad_readline(state);

errno = 0;
/* XXX: Should the base argument of strtol() be explicitly set to 10?
XXX(avassalotti): Should this uses PyOS_strtol()? */
x = strtol(s, &endptr, 0);
/* XXX(avassalotti): Should this uses PyOS_strtol()? */
x = strtol(s, &endptr, 10);

if (errno || (*endptr != '\n' && *endptr != '\0')) {
/* Hm, maybe we've got something long. Let's try reading
* it as a Python int object. */
errno = 0;
/* XXX: Same thing about the base here. */
value = PyLong_FromString(s, NULL, 0);
value = PyLong_FromString(s, NULL, 10);
if (value == NULL) {
PyErr_SetString(PyExc_ValueError,
"could not convert string to int");
Expand Down Expand Up @@ -5370,8 +5368,7 @@ load_long(PickleState *state, UnpicklerObject *self)
the 'L' to be present. */
if (s[len-2] == 'L')
s[len-2] = '\0';
/* XXX: Should the base argument explicitly set to 10? */
value = PyLong_FromString(s, NULL, 0);
value = PyLong_FromString(s, NULL, 10);
if (value == NULL)
return -1;

Expand Down
48 changes: 48 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -3353,6 +3353,53 @@ type_freeze(PyObject *module, PyObject *args)
Py_RETURN_NONE;
}

struct atexit_data {
int called;
PyThreadState *tstate;
PyInterpreterState *interp;
};

static void
atexit_callback(void *data)
{
struct atexit_data *at_data = (struct atexit_data *)data;
// Ensure that the callback is from the same interpreter
assert(PyThreadState_Get() == at_data->tstate);
assert(PyInterpreterState_Get() == at_data->interp);
++at_data->called;
}

static PyObject *
test_atexit(PyObject *self, PyObject *Py_UNUSED(args))
{
PyThreadState *oldts = PyThreadState_Swap(NULL);
PyThreadState *tstate = Py_NewInterpreter();

struct atexit_data data = {0};
data.tstate = PyThreadState_Get();
data.interp = PyInterpreterState_Get();

int amount = 10;
for (int i = 0; i < amount; ++i)
{
int res = PyUnstable_AtExit(tstate->interp, atexit_callback, (void *)&data);
if (res < 0) {
Py_EndInterpreter(tstate);
PyThreadState_Swap(oldts);
PyErr_SetString(PyExc_RuntimeError, "atexit callback failed");
return NULL;
}
}

Py_EndInterpreter(tstate);
PyThreadState_Swap(oldts);

if (data.called != amount) {
PyErr_SetString(PyExc_RuntimeError, "atexit callback not called");
return NULL;
}
Py_RETURN_NONE;
}

static PyMethodDef TestMethods[] = {
{"set_errno", set_errno, METH_VARARGS},
Expand Down Expand Up @@ -3495,6 +3542,7 @@ static PyMethodDef TestMethods[] = {
{"test_critical_sections", test_critical_sections, METH_NOARGS},
{"finalize_thread_hang", finalize_thread_hang, METH_O, NULL},
{"type_freeze", type_freeze, METH_VARARGS},
{"test_atexit", test_atexit, METH_NOARGS},
{NULL, NULL} /* sentinel */
};

Expand Down
34 changes: 0 additions & 34 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -1236,39 +1236,6 @@ unicode_transformdecimalandspacetoascii(PyObject *self, PyObject *arg)
return _PyUnicode_TransformDecimalAndSpaceToASCII(arg);
}


struct atexit_data {
int called;
};

static void
callback(void *data)
{
((struct atexit_data *)data)->called += 1;
}

static PyObject *
test_atexit(PyObject *self, PyObject *Py_UNUSED(args))
{
PyThreadState *oldts = PyThreadState_Swap(NULL);
PyThreadState *tstate = Py_NewInterpreter();

struct atexit_data data = {0};
int res = PyUnstable_AtExit(tstate->interp, callback, (void *)&data);
Py_EndInterpreter(tstate);
PyThreadState_Swap(oldts);
if (res < 0) {
return NULL;
}

if (data.called == 0) {
PyErr_SetString(PyExc_RuntimeError, "atexit callback not called");
return NULL;
}
Py_RETURN_NONE;
}


static PyObject *
test_pyobject_is_freed(const char *test_name, PyObject *op)
{
Expand Down Expand Up @@ -2128,7 +2095,6 @@ static PyMethodDef module_functions[] = {
{"_PyTraceMalloc_GetTraceback", tracemalloc_get_traceback, METH_VARARGS},
{"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL},
{"_PyUnicode_TransformDecimalAndSpaceToASCII", unicode_transformdecimalandspacetoascii, METH_O},
{"test_atexit", test_atexit, METH_NOARGS},
{"check_pyobject_forbidden_bytes_is_freed",
check_pyobject_forbidden_bytes_is_freed, METH_NOARGS},
{"check_pyobject_freed_is_freed", check_pyobject_freed_is_freed, METH_NOARGS},
Expand Down
12 changes: 8 additions & 4 deletions Modules/atexitmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ int
PyUnstable_AtExit(PyInterpreterState *interp,
atexit_datacallbackfunc func, void *data)
{
assert(interp == _PyInterpreterState_GET());
PyThreadState *tstate = _PyThreadState_GET();
_Py_EnsureTstateNotNULL(tstate);
assert(tstate->interp == interp);

atexit_callback *callback = PyMem_Malloc(sizeof(atexit_callback));
if (callback == NULL) {
PyErr_NoMemory();
Expand All @@ -38,12 +41,13 @@ PyUnstable_AtExit(PyInterpreterState *interp,
callback->next = NULL;

struct atexit_state *state = &interp->atexit;
if (state->ll_callbacks == NULL) {
atexit_callback *top = state->ll_callbacks;
if (top == NULL) {
state->ll_callbacks = callback;
state->last_ll_callback = callback;
}
else {
state->last_ll_callback->next = callback;
callback->next = top;
state->ll_callbacks = callback;
}
return 0;
}
Expand Down

0 comments on commit bcab963

Please sign in to comment.