From 3cc57505e530c64b7d783e5ac15d3e66d233bbbb Mon Sep 17 00:00:00 2001 From: Hai Zhu <35182391+cocolato@users.noreply.github.com> Date: Sun, 21 Dec 2025 01:27:34 +0800 Subject: [PATCH 1/3] gh-142834: pdb commands command should use last available breakpoint (#142835) --- Doc/library/pdb.rst | 3 +- Lib/pdb.py | 9 +++- Lib/test/test_pdb.py | 43 +++++++++++++++++++ ...-12-16-15-32-41.gh-issue-142834.g7mHw_.rst | 1 + 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-12-16-15-32-41.gh-issue-142834.g7mHw_.rst diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index 0bbdc42535290a..8ab3e7ec9ef9d2 100644 --- a/Doc/library/pdb.rst +++ b/Doc/library/pdb.rst @@ -520,7 +520,8 @@ can be overridden by the local file. To remove all commands from a breakpoint, type ``commands`` and follow it immediately with ``end``; that is, give no commands. - With no *bpnumber* argument, ``commands`` refers to the last breakpoint set. + With no *bpnumber* argument, ``commands`` refers to the most recently set + breakpoint that still exists. You can use breakpoint commands to start your program up again. Simply use the :pdbcmd:`continue` command, or :pdbcmd:`step`, diff --git a/Lib/pdb.py b/Lib/pdb.py index c1a5db080dc7ef..4a6bc17e91cf0c 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -1315,7 +1315,14 @@ def do_commands(self, arg): reached. """ if not arg: - bnum = len(bdb.Breakpoint.bpbynumber) - 1 + for bp in reversed(bdb.Breakpoint.bpbynumber): + if bp is None: + continue + bnum = bp.number + break + else: + self.error('cannot set commands: no existing breakpoint') + return else: try: bnum = int(arg) diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 5cba34ff8bad10..4352aa6abfeabb 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -3478,6 +3478,49 @@ def test_pdb_issue_gh_65052(): (Pdb) continue """ +def test_pdb_commands_last_breakpoint(): + """See GH-142834 + + >>> def test_function(): + ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + ... foo = 1 + ... bar = 2 + + >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE + ... 'break 4', + ... 'break 3', + ... 'clear 2', + ... 'commands', + ... 'p "success"', + ... 'end', + ... 'continue', + ... 'clear 1', + ... 'commands', + ... 'continue', + ... ]): + ... test_function() + > (2)test_function() + -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + (Pdb) break 4 + Breakpoint 1 at :4 + (Pdb) break 3 + Breakpoint 2 at :3 + (Pdb) clear 2 + Deleted breakpoint 2 at :3 + (Pdb) commands + (com) p "success" + (com) end + (Pdb) continue + 'success' + > (4)test_function() + -> bar = 2 + (Pdb) clear 1 + Deleted breakpoint 1 at :4 + (Pdb) commands + *** cannot set commands: no existing breakpoint + (Pdb) continue + """ + @support.force_not_colorized_test_class @support.requires_subprocess() diff --git a/Misc/NEWS.d/next/Library/2025-12-16-15-32-41.gh-issue-142834.g7mHw_.rst b/Misc/NEWS.d/next/Library/2025-12-16-15-32-41.gh-issue-142834.g7mHw_.rst new file mode 100644 index 00000000000000..8cde592e7c937d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-16-15-32-41.gh-issue-142834.g7mHw_.rst @@ -0,0 +1 @@ +Change the :mod:`pdb` ``commands`` command to use the last available breakpoint instead of failing when the most recently created breakpoint was deleted. From 7607712b61b7ab35c08e189155c0e161f9ad74b0 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Sat, 20 Dec 2025 14:42:12 -0500 Subject: [PATCH 2/3] gh-120321: Avoid `-Wunreachable-code` warning on Clang (gh-143022) --- Objects/genobject.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Objects/genobject.c b/Objects/genobject.c index 1e59d89f5ce85f..020af903a3f828 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -422,7 +422,8 @@ gen_close(PyObject *self, PyObject *args) int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state); do { if (frame_state == FRAME_CREATED) { - if (!_Py_GEN_TRY_SET_FRAME_STATE(gen, frame_state, FRAME_CLEARED)) { + // && (1) to avoid -Wunreachable-code warning on Clang + if (!_Py_GEN_TRY_SET_FRAME_STATE(gen, frame_state, FRAME_CLEARED) && (1)) { continue; } gen_clear_frame(gen); From 2b4feee648b7af0ccca8dee167fdd21cfb0bd23a Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Sat, 20 Dec 2025 15:37:31 -0500 Subject: [PATCH 3/3] gh-122581: Use parser mutex in default build for subinterpreters (gh-142959) --- Include/internal/pycore_parser.h | 16 ---------------- Include/internal/pycore_runtime_structs.h | 2 -- Parser/pegen.c | 19 +++++++++---------- 3 files changed, 9 insertions(+), 28 deletions(-) diff --git a/Include/internal/pycore_parser.h b/Include/internal/pycore_parser.h index 2c46f59ab7da9f..b89d02035db71f 100644 --- a/Include/internal/pycore_parser.h +++ b/Include/internal/pycore_parser.h @@ -14,10 +14,8 @@ extern "C" { #include "pycore_pyarena.h" // PyArena _Py_DECLARE_STR(empty, "") -#if defined(Py_DEBUG) && defined(Py_GIL_DISABLED) #define _parser_runtime_state_INIT \ { \ - .mutex = {0}, \ .dummy_name = { \ .kind = Name_kind, \ .v.Name.id = &_Py_STR(empty), \ @@ -28,20 +26,6 @@ _Py_DECLARE_STR(empty, "") .end_col_offset = 0, \ }, \ } -#else -#define _parser_runtime_state_INIT \ - { \ - .dummy_name = { \ - .kind = Name_kind, \ - .v.Name.id = &_Py_STR(empty), \ - .v.Name.ctx = Load, \ - .lineno = 1, \ - .col_offset = 0, \ - .end_lineno = 1, \ - .end_col_offset = 0, \ - }, \ - } -#endif extern struct _mod* _PyParser_ASTFromString( const char *str, diff --git a/Include/internal/pycore_runtime_structs.h b/Include/internal/pycore_runtime_structs.h index 995f49e78dcda3..92387031ad7465 100644 --- a/Include/internal/pycore_runtime_structs.h +++ b/Include/internal/pycore_runtime_structs.h @@ -77,9 +77,7 @@ struct _fileutils_state { struct _parser_runtime_state { #ifdef Py_DEBUG long memo_statistics[_PYPEGEN_NSTATISTICS]; -#ifdef Py_GIL_DISABLED PyMutex mutex; -#endif #else int _not_used; #endif diff --git a/Parser/pegen.c b/Parser/pegen.c index a38e973b3f64c6..7ecc55eee13775 100644 --- a/Parser/pegen.c +++ b/Parser/pegen.c @@ -3,9 +3,8 @@ #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_parser.h" // _PYPEGEN_NSTATISTICS #include "pycore_pyerrors.h" // PyExc_IncompleteInputError -#include "pycore_runtime.h" // _PyRuntime +#include "pycore_runtime.h" // _PyRuntime #include "pycore_unicodeobject.h" // _PyUnicode_InternImmortal -#include "pycore_pyatomic_ft_wrappers.h" #include #include "lexer/lexer.h" @@ -303,11 +302,11 @@ _PyPegen_fill_token(Parser *p) void _PyPegen_clear_memo_statistics(void) { - FT_MUTEX_LOCK(&_PyRuntime.parser.mutex); + PyMutex_Lock(&_PyRuntime.parser.mutex); for (int i = 0; i < NSTATISTICS; i++) { memo_statistics[i] = 0; } - FT_MUTEX_UNLOCK(&_PyRuntime.parser.mutex); + PyMutex_Unlock(&_PyRuntime.parser.mutex); } PyObject * @@ -318,22 +317,22 @@ _PyPegen_get_memo_statistics(void) return NULL; } - FT_MUTEX_LOCK(&_PyRuntime.parser.mutex); + PyMutex_Lock(&_PyRuntime.parser.mutex); for (int i = 0; i < NSTATISTICS; i++) { PyObject *value = PyLong_FromLong(memo_statistics[i]); if (value == NULL) { - FT_MUTEX_UNLOCK(&_PyRuntime.parser.mutex); + PyMutex_Unlock(&_PyRuntime.parser.mutex); Py_DECREF(ret); return NULL; } // PyList_SetItem borrows a reference to value. if (PyList_SetItem(ret, i, value) < 0) { - FT_MUTEX_UNLOCK(&_PyRuntime.parser.mutex); + PyMutex_Unlock(&_PyRuntime.parser.mutex); Py_DECREF(ret); return NULL; } } - FT_MUTEX_UNLOCK(&_PyRuntime.parser.mutex); + PyMutex_Unlock(&_PyRuntime.parser.mutex); return ret; } #endif @@ -359,9 +358,9 @@ _PyPegen_is_memoized(Parser *p, int type, void *pres) if (count <= 0) { count = 1; } - FT_MUTEX_LOCK(&_PyRuntime.parser.mutex); + PyMutex_Lock(&_PyRuntime.parser.mutex); memo_statistics[type] += count; - FT_MUTEX_UNLOCK(&_PyRuntime.parser.mutex); + PyMutex_Unlock(&_PyRuntime.parser.mutex); } #endif p->mark = m->mark;