Python-checkins
Threads by month
- ----- 2025 -----
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2006 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2005 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2004 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2003 -----
- December
- November
- October
- September
- August
January 2025
- 1 participants
- 705 discussions
Jan. 21, 2025
https://github.com/python/cpython/commit/bf150f61ad85dd609f412f1c87dec6245f…
commit: bf150f61ad85dd609f412f1c87dec6245f484f0d
branch: main
author: Petr Viktorin <encukou(a)gmail.com>
committer: encukou <encukou(a)gmail.com>
date: 2025-01-21T11:28:34+01:00
summary:
gh-126349: test_turtle: Add cleanup to avoid reference leaks (GH-129079)
files:
M Lib/test/test_turtle.py
diff --git a/Lib/test/test_turtle.py b/Lib/test/test_turtle.py
index de6508ff8c791d..d02cac284a909a 100644
--- a/Lib/test/test_turtle.py
+++ b/Lib/test/test_turtle.py
@@ -570,6 +570,9 @@ def setUp(self):
with patch_screen():
self.turtle = turtle.Turtle()
+ # Reset the Screen singleton to avoid reference leaks
+ self.addCleanup(setattr, turtle.Turtle, '_screen', None)
+
def test_begin_end_fill(self):
self.assertFalse(self.turtle.filling())
self.turtle.begin_fill()
1
0
GH-128563: Add new frame owner type for interpreter entry frames (GH-129078)
by markshannon Jan. 21, 2025
by markshannon Jan. 21, 2025
Jan. 21, 2025
https://github.com/python/cpython/commit/f5b6356a11bde64ac1e08478dd5ee7c47d…
commit: f5b6356a11bde64ac1e08478dd5ee7c47da653d8
branch: main
author: Mark Shannon <mark(a)hotpy.org>
committer: markshannon <mark(a)hotpy.org>
date: 2025-01-21T10:15:02Z
summary:
GH-128563: Add new frame owner type for interpreter entry frames (GH-129078)
Add new frame owner type for interpreter entry frames
files:
M Include/internal/pycore_frame.h
M Modules/_testexternalinspection.c
M Objects/frameobject.c
M Python/bytecodes.c
M Python/ceval.c
M Python/ceval_macros.h
M Python/executor_cases.c.h
M Python/frame.c
M Python/gc.c
M Python/generated_cases.c.h
M Python/instrumentation.c
M Python/traceback.c
M Tools/gdb/libpython.py
diff --git a/Include/internal/pycore_frame.h b/Include/internal/pycore_frame.h
index 96ae4dd22ecb43..77a922630cde08 100644
--- a/Include/internal/pycore_frame.h
+++ b/Include/internal/pycore_frame.h
@@ -56,7 +56,8 @@ enum _frameowner {
FRAME_OWNED_BY_THREAD = 0,
FRAME_OWNED_BY_GENERATOR = 1,
FRAME_OWNED_BY_FRAME_OBJECT = 2,
- FRAME_OWNED_BY_CSTACK = 3,
+ FRAME_OWNED_BY_INTERPRETER = 3,
+ FRAME_OWNED_BY_CSTACK = 4,
};
typedef struct _PyInterpreterFrame {
@@ -264,7 +265,7 @@ _PyFrame_SetStackPointer(_PyInterpreterFrame *frame, _PyStackRef *stack_pointer)
static inline bool
_PyFrame_IsIncomplete(_PyInterpreterFrame *frame)
{
- if (frame->owner == FRAME_OWNED_BY_CSTACK) {
+ if (frame->owner >= FRAME_OWNED_BY_INTERPRETER) {
return true;
}
return frame->owner != FRAME_OWNED_BY_GENERATOR &&
diff --git a/Modules/_testexternalinspection.c b/Modules/_testexternalinspection.c
index 0807d1e47b6736..8a92d5cdd894be 100644
--- a/Modules/_testexternalinspection.c
+++ b/Modules/_testexternalinspection.c
@@ -508,7 +508,7 @@ parse_frame_object(
return -1;
}
- if (owner == FRAME_OWNED_BY_CSTACK) {
+ if (owner >= FRAME_OWNED_BY_INTERPRETER) {
return 0;
}
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index bf5067bba58f71..d6b4065e1302bc 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -2133,7 +2133,7 @@ _PyFrame_IsEntryFrame(PyFrameObject *frame)
assert(frame != NULL);
_PyInterpreterFrame *f = frame->f_frame;
assert(!_PyFrame_IsIncomplete(f));
- return f->previous && f->previous->owner == FRAME_OWNED_BY_CSTACK;
+ return f->previous && f->previous->owner == FRAME_OWNED_BY_INTERPRETER;
}
PyCodeObject *
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index b2745c3400ffc9..8bda4501df5f74 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -1090,7 +1090,7 @@ dummy_func(
}
tier1 inst(INTERPRETER_EXIT, (retval --)) {
- assert(frame == &entry_frame);
+ assert(frame->owner == FRAME_OWNED_BY_INTERPRETER);
assert(_PyFrame_IsIncomplete(frame));
/* Restore previous frame and return. */
tstate->current_frame = frame->previous;
@@ -1105,9 +1105,7 @@ dummy_func(
// retval is popped from the stack, but res
// is pushed to a different frame, the callers' frame.
inst(RETURN_VALUE, (retval -- res)) {
- #if TIER_ONE
- assert(frame != &entry_frame);
- #endif
+ assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
_PyStackRef temp = retval;
DEAD(retval);
SAVE_STACK();
@@ -1205,7 +1203,7 @@ dummy_func(
PyObject *receiver_o = PyStackRef_AsPyObjectBorrow(receiver);
PyObject *retval_o;
- assert(frame != &entry_frame);
+ assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
if ((tstate->interp->eval_frame == NULL) &&
(Py_TYPE(receiver_o) == &PyGen_Type || Py_TYPE(receiver_o) == &PyCoro_Type) &&
((PyGenObject *)receiver_o)->gi_frame_state < FRAME_EXECUTING)
@@ -1278,9 +1276,7 @@ dummy_func(
// NOTE: It's important that YIELD_VALUE never raises an exception!
// The compiler treats any exception raised here as a failed close()
// or throw() call.
- #if TIER_ONE
- assert(frame != &entry_frame);
- #endif
+ assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
frame->instr_ptr++;
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame);
assert(FRAME_SUSPENDED_YIELD_FROM == FRAME_SUSPENDED + 1);
diff --git a/Python/ceval.c b/Python/ceval.c
index 28b0b4c6de39a7..813d980acf5aab 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -178,7 +178,7 @@ lltrace_instruction(_PyInterpreterFrame *frame,
int opcode,
int oparg)
{
- if (frame->owner == FRAME_OWNED_BY_CSTACK) {
+ if (frame->owner >= FRAME_OWNED_BY_INTERPRETER) {
return;
}
dump_stack(frame, stack_pointer);
@@ -229,12 +229,12 @@ lltrace_resume_frame(_PyInterpreterFrame *frame)
}
static int
-maybe_lltrace_resume_frame(_PyInterpreterFrame *frame, _PyInterpreterFrame *skip_frame, PyObject *globals)
+maybe_lltrace_resume_frame(_PyInterpreterFrame *frame, PyObject *globals)
{
if (globals == NULL) {
return 0;
}
- if (frame == skip_frame) {
+ if (frame->owner >= FRAME_OWNED_BY_INTERPRETER) {
return 0;
}
int r = PyDict_Contains(globals, &_Py_ID(__lltrace__));
@@ -818,7 +818,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
entry_frame.f_executable = PyStackRef_None;
entry_frame.instr_ptr = (_Py_CODEUNIT *)_Py_INTERPRETER_TRAMPOLINE_INSTRUCTIONS + 1;
entry_frame.stackpointer = entry_frame.localsplus;
- entry_frame.owner = FRAME_OWNED_BY_CSTACK;
+ entry_frame.owner = FRAME_OWNED_BY_INTERPRETER;
entry_frame.visited = 0;
entry_frame.return_offset = 0;
/* Push frame */
@@ -880,7 +880,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
stack_pointer = _PyFrame_GetStackPointer(frame);
#ifdef LLTRACE
- lltrace = maybe_lltrace_resume_frame(frame, &entry_frame, GLOBALS());
+ lltrace = maybe_lltrace_resume_frame(frame, GLOBALS());
if (lltrace < 0) {
goto exit_unwind;
}
diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h
index c37e1cf3afa60e..6a982ee2ca5d9a 100644
--- a/Python/ceval_macros.h
+++ b/Python/ceval_macros.h
@@ -89,7 +89,7 @@
#if LLTRACE
#define LLTRACE_RESUME_FRAME() \
do { \
- lltrace = maybe_lltrace_resume_frame(frame, &entry_frame, GLOBALS()); \
+ lltrace = maybe_lltrace_resume_frame(frame, GLOBALS()); \
if (lltrace < 0) { \
goto exit_unwind; \
} \
@@ -238,7 +238,7 @@ GETITEM(PyObject *v, Py_ssize_t i) {
#endif
#define WITHIN_STACK_BOUNDS() \
- (frame == &entry_frame || (STACK_LEVEL() >= 0 && STACK_LEVEL() <= STACK_SIZE()))
+ (frame->owner == FRAME_OWNED_BY_INTERPRETER || (STACK_LEVEL() >= 0 && STACK_LEVEL() <= STACK_SIZE()))
/* Data access macros */
#define FRAME_CO_CONSTS (_PyFrame_GetCode(frame)->co_consts)
diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h
index 049073162b6c5f..0d6719a5c40bdd 100644
--- a/Python/executor_cases.c.h
+++ b/Python/executor_cases.c.h
@@ -1441,9 +1441,7 @@
_PyStackRef retval;
_PyStackRef res;
retval = stack_pointer[-1];
- #if TIER_ONE
- assert(frame != &entry_frame);
- #endif
+ assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
_PyStackRef temp = retval;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
@@ -1579,9 +1577,7 @@
// NOTE: It's important that YIELD_VALUE never raises an exception!
// The compiler treats any exception raised here as a failed close()
// or throw() call.
- #if TIER_ONE
- assert(frame != &entry_frame);
- #endif
+ assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
frame->instr_ptr++;
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame);
assert(FRAME_SUSPENDED_YIELD_FROM == FRAME_SUSPENDED + 1);
diff --git a/Python/frame.c b/Python/frame.c
index 6eb32bcce0b799..68ac2acbaee342 100644
--- a/Python/frame.c
+++ b/Python/frame.c
@@ -48,7 +48,7 @@ _PyFrame_MakeAndSetFrameObject(_PyInterpreterFrame *frame)
static void
take_ownership(PyFrameObject *f, _PyInterpreterFrame *frame)
{
- assert(frame->owner != FRAME_OWNED_BY_CSTACK);
+ assert(frame->owner < FRAME_OWNED_BY_INTERPRETER);
assert(frame->owner != FRAME_OWNED_BY_FRAME_OBJECT);
Py_ssize_t size = ((char*)frame->stackpointer) - (char *)frame;
memcpy((_PyInterpreterFrame *)f->_f_frame_data, frame, size);
@@ -69,7 +69,7 @@ take_ownership(PyFrameObject *f, _PyInterpreterFrame *frame)
_PyInterpreterFrame *prev = _PyFrame_GetFirstComplete(frame->previous);
frame->previous = NULL;
if (prev) {
- assert(prev->owner != FRAME_OWNED_BY_CSTACK);
+ assert(prev->owner < FRAME_OWNED_BY_INTERPRETER);
/* Link PyFrameObjects.f_back and remove link through _PyInterpreterFrame.previous */
PyFrameObject *back = _PyFrame_GetFrameObject(prev);
if (back == NULL) {
diff --git a/Python/gc.c b/Python/gc.c
index 5b9588c8741b97..3fe0b7f814544d 100644
--- a/Python/gc.c
+++ b/Python/gc.c
@@ -1476,7 +1476,7 @@ mark_stacks(PyInterpreterState *interp, PyGC_Head *visited, int visited_space, b
while (ts) {
_PyInterpreterFrame *frame = ts->current_frame;
while (frame) {
- if (frame->owner == FRAME_OWNED_BY_CSTACK) {
+ if (frame->owner >= FRAME_OWNED_BY_INTERPRETER) {
frame = frame->previous;
continue;
}
diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h
index f25c9a97176a52..a8dbcabde538d5 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -5046,9 +5046,7 @@
// _RETURN_VALUE
{
retval = val;
- #if TIER_ONE
- assert(frame != &entry_frame);
- #endif
+ assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
_PyStackRef temp = retval;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
@@ -5100,9 +5098,7 @@
// NOTE: It's important that YIELD_VALUE never raises an exception!
// The compiler treats any exception raised here as a failed close()
// or throw() call.
- #if TIER_ONE
- assert(frame != &entry_frame);
- #endif
+ assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
frame->instr_ptr++;
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame);
assert(FRAME_SUSPENDED_YIELD_FROM == FRAME_SUSPENDED + 1);
@@ -5145,7 +5141,7 @@
INSTRUCTION_STATS(INTERPRETER_EXIT);
_PyStackRef retval;
retval = stack_pointer[-1];
- assert(frame == &entry_frame);
+ assert(frame->owner == FRAME_OWNED_BY_INTERPRETER);
assert(_PyFrame_IsIncomplete(frame));
/* Restore previous frame and return. */
tstate->current_frame = frame->previous;
@@ -7309,9 +7305,7 @@
_PyStackRef retval;
_PyStackRef res;
retval = stack_pointer[-1];
- #if TIER_ONE
- assert(frame != &entry_frame);
- #endif
+ assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
_PyStackRef temp = retval;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
@@ -7364,7 +7358,7 @@
v = stack_pointer[-1];
PyObject *receiver_o = PyStackRef_AsPyObjectBorrow(receiver);
PyObject *retval_o;
- assert(frame != &entry_frame);
+ assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
if ((tstate->interp->eval_frame == NULL) &&
(Py_TYPE(receiver_o) == &PyGen_Type || Py_TYPE(receiver_o) == &PyCoro_Type) &&
((PyGenObject *)receiver_o)->gi_frame_state < FRAME_EXECUTING)
@@ -8486,9 +8480,7 @@
// NOTE: It's important that YIELD_VALUE never raises an exception!
// The compiler treats any exception raised here as a failed close()
// or throw() call.
- #if TIER_ONE
- assert(frame != &entry_frame);
- #endif
+ assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
frame->instr_ptr++;
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame);
assert(FRAME_SUSPENDED_YIELD_FROM == FRAME_SUSPENDED + 1);
diff --git a/Python/instrumentation.c b/Python/instrumentation.c
index 8968ff2c691272..d20195d426fc00 100644
--- a/Python/instrumentation.c
+++ b/Python/instrumentation.c
@@ -1946,7 +1946,7 @@ instrument_all_executing_code_objects(PyInterpreterState *interp) {
while (ts) {
_PyInterpreterFrame *frame = ts->current_frame;
while (frame) {
- if (frame->owner != FRAME_OWNED_BY_CSTACK) {
+ if (frame->owner < FRAME_OWNED_BY_INTERPRETER) {
if (instrument_lock_held(_PyFrame_GetCode(frame), interp)) {
return -1;
}
diff --git a/Python/traceback.c b/Python/traceback.c
index e819909b6045c3..62387f12392265 100644
--- a/Python/traceback.c
+++ b/Python/traceback.c
@@ -890,7 +890,7 @@ _Py_DumpASCII(int fd, PyObject *text)
static void
dump_frame(int fd, _PyInterpreterFrame *frame)
{
- assert(frame->owner != FRAME_OWNED_BY_CSTACK);
+ assert(frame->owner < FRAME_OWNED_BY_INTERPRETER);
PyCodeObject *code =_PyFrame_GetCode(frame);
PUTS(fd, " File ");
@@ -965,7 +965,7 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header)
unsigned int depth = 0;
while (1) {
- if (frame->owner == FRAME_OWNED_BY_CSTACK) {
+ if (frame->owner == FRAME_OWNED_BY_INTERPRETER) {
/* Trampoline frame */
frame = frame->previous;
if (frame == NULL) {
@@ -973,7 +973,7 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header)
}
/* Can't have more than one shim frame in a row */
- assert(frame->owner != FRAME_OWNED_BY_CSTACK);
+ assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
}
if (MAX_FRAME_DEPTH <= depth) {
diff --git a/Tools/gdb/libpython.py b/Tools/gdb/libpython.py
index 698ecbd3b549aa..e0d92e21dc42b3 100755
--- a/Tools/gdb/libpython.py
+++ b/Tools/gdb/libpython.py
@@ -99,7 +99,7 @@ def interp_frame_has_tlbc_index():
Py_TPFLAGS_TYPE_SUBCLASS = (1 << 31)
#From pycore_frame.h
-FRAME_OWNED_BY_CSTACK = 3
+FRAME_OWNED_BY_INTERPRETER = 3
MAX_OUTPUT_LEN=1024
@@ -1113,7 +1113,7 @@ def _f_lasti(self):
return int(instr_ptr - first_instr)
def is_shim(self):
- return self._f_special("owner", int) == FRAME_OWNED_BY_CSTACK
+ return self._f_special("owner", int) == FRAME_OWNED_BY_INTERPRETER
def previous(self):
return self._f_special("previous", PyFramePtr)
1
0
gh-128156: Guard use of `ffi_type_complex_double` on macOS system libffi (GH-128680)
by encukou Jan. 21, 2025
by encukou Jan. 21, 2025
Jan. 21, 2025
https://github.com/python/cpython/commit/d3b1bb228c951f8245f36ee28d9b175786…
commit: d3b1bb228c951f8245f36ee28d9b175786522730
branch: main
author: Petr Viktorin <encukou(a)gmail.com>
committer: encukou <encukou(a)gmail.com>
date: 2025-01-21T10:59:18+01:00
summary:
gh-128156: Guard use of `ffi_type_complex_double` on macOS system libffi (GH-128680)
* Determine ffi complex support at runtime
* Also, generate SIMPLE_TYPE_CHARS once at runtime
files:
A Misc/NEWS.d/next/Library/2025-01-09-16-20-34.gh-issue-128156.GfObBq.rst
M Doc/library/ctypes.rst
M Lib/test/test_ctypes/test_c_simple_type_meta.py
M Modules/_ctypes/_ctypes.c
M Modules/_ctypes/cfield.c
M Modules/_ctypes/ctypes.h
M Tools/c-analyzer/cpython/globals-to-fix.tsv
diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst
index 164d706e7738e2..615138302e1379 100644
--- a/Doc/library/ctypes.rst
+++ b/Doc/library/ctypes.rst
@@ -266,8 +266,8 @@ Fundamental data types
(1)
The constructor accepts any object with a truth value.
-Additionally, if IEC 60559 compatible complex arithmetic (Annex G) is supported, the following
-complex types are available:
+Additionally, if IEC 60559 compatible complex arithmetic (Annex G) is supported
+in both C and ``libffi``, the following complex types are available:
+----------------------------------+---------------------------------+-----------------+
| ctypes type | C type | Python type |
diff --git a/Lib/test/test_ctypes/test_c_simple_type_meta.py b/Lib/test/test_ctypes/test_c_simple_type_meta.py
index e8f347a0d0c57b..b446fd5c77dde2 100644
--- a/Lib/test/test_ctypes/test_c_simple_type_meta.py
+++ b/Lib/test/test_ctypes/test_c_simple_type_meta.py
@@ -1,4 +1,5 @@
import unittest
+from test.support import MS_WINDOWS
import ctypes
from ctypes import POINTER, c_void_p
@@ -150,3 +151,20 @@ class Sub(CtBase):
self.assertIsInstance(POINTER(Sub), p_meta)
self.assertIsSubclass(POINTER(Sub), Sub)
+
+ def test_bad_type_message(self):
+ """Verify the error message that lists all available type codes"""
+ # (The string is generated at runtime, so this checks the underlying
+ # set of types as well as correct construction of the string.)
+ with self.assertRaises(AttributeError) as cm:
+ class F(metaclass=PyCSimpleType):
+ _type_ = "\0"
+ message = str(cm.exception)
+ expected_type_chars = list('cbBhHiIlLdCEFfuzZqQPXOv?g')
+ if not hasattr(ctypes, 'c_float_complex'):
+ expected_type_chars.remove('C')
+ expected_type_chars.remove('E')
+ expected_type_chars.remove('F')
+ if not MS_WINDOWS:
+ expected_type_chars.remove('X')
+ self.assertIn("'" + ''.join(expected_type_chars) + "'", message)
diff --git a/Misc/NEWS.d/next/Library/2025-01-09-16-20-34.gh-issue-128156.GfObBq.rst b/Misc/NEWS.d/next/Library/2025-01-09-16-20-34.gh-issue-128156.GfObBq.rst
new file mode 100644
index 00000000000000..ec6a55040ae6cb
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-01-09-16-20-34.gh-issue-128156.GfObBq.rst
@@ -0,0 +1,3 @@
+When using macOS system ``libffi``, support for complex types in
+:mod:`ctypes` is now checked at runtime (macOS 10.15 or newer). The types
+must also be available at build time.
diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index c4d130a5ec1d52..5d37610cc1c970 100644
--- a/Modules/_ctypes/_ctypes.c
+++ b/Modules/_ctypes/_ctypes.c
@@ -1776,11 +1776,6 @@ class _ctypes.c_void_p "PyObject *" "clinic_state_sub()->PyCSimpleType_Type"
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=dd4d9646c56f43a9]*/
-#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
-static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdCEFfuzZqQPXOv?g";
-#else
-static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdfuzZqQPXOv?g";
-#endif
/*[clinic input]
_ctypes.c_wchar_p.from_param as c_wchar_p_from_param
@@ -2252,17 +2247,13 @@ PyCSimpleType_init(PyObject *self, PyObject *args, PyObject *kwds)
"which must be a string of length 1");
goto error;
}
- if (!strchr(SIMPLE_TYPE_CHARS, *proto_str)) {
+ fmt = _ctypes_get_fielddesc(proto_str);
+ if (!fmt) {
PyErr_Format(PyExc_AttributeError,
"class must define a '_type_' attribute which must be\n"
- "a single character string containing one of '%s'.",
- SIMPLE_TYPE_CHARS);
- goto error;
- }
- fmt = _ctypes_get_fielddesc(proto_str);
- if (fmt == NULL) {
- PyErr_Format(PyExc_ValueError,
- "_type_ '%s' not supported", proto_str);
+ "a single character string containing one of the\n"
+ "supported types: '%s'.",
+ _ctypes_get_simple_type_chars());
goto error;
}
diff --git a/Modules/_ctypes/cfield.c b/Modules/_ctypes/cfield.c
index dcac9da75360a4..e045d206f05580 100644
--- a/Modules/_ctypes/cfield.c
+++ b/Modules/_ctypes/cfield.c
@@ -1255,6 +1255,10 @@ for code in 'sbBcdCEFgfhHiIlLqQPzuUZXvO':
// always contains NULLs:
struct fielddesc fmt_nil;
+
+ // Result of _ctypes_get_simple_type_chars. Initialized just after
+ // the rest of formattable, so we stash it here.
+ char simple_type_chars[26];
};
static struct formattable formattable;
@@ -1315,8 +1319,8 @@ _Py_COMP_DIAG_PUSH
/* Delayed initialization. Windows cannot statically reference dynamically
loaded addresses from DLLs. */
-void
-_ctypes_init_fielddesc(void)
+static void
+_ctypes_init_fielddesc_locked(void)
{
/* Fixed-width integers */
@@ -1432,9 +1436,11 @@ for base_code, base_c_type in [
TABLE_ENTRY_SW(d, &ffi_type_double);
#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
- TABLE_ENTRY(C, &ffi_type_complex_double);
- TABLE_ENTRY(E, &ffi_type_complex_float);
- TABLE_ENTRY(F, &ffi_type_complex_longdouble);
+ if (Py_FFI_COMPLEX_AVAILABLE) {
+ TABLE_ENTRY(C, &ffi_type_complex_double);
+ TABLE_ENTRY(E, &ffi_type_complex_float);
+ TABLE_ENTRY(F, &ffi_type_complex_longdouble);
+ }
#endif
TABLE_ENTRY(g, &ffi_type_longdouble);
TABLE_ENTRY_SW(f, &ffi_type_float);
@@ -1466,21 +1472,75 @@ for base_code, base_c_type in [
formattable.fmt_bool.code = '?';
formattable.fmt_bool.setfunc = bool_set;
formattable.fmt_bool.getfunc = bool_get;
+
+/*[python input]
+all_chars = "cbBhHiIlLdCEFfuzZqQPXOv?g"
+print(f' assert(sizeof(formattable.simple_type_chars) == {len(all_chars)+1});')
+print(f' int i = 0;')
+for char in all_chars:
+ ident_char = {'?': 'bool'}.get(char, char)
+ print(f" if (formattable.fmt_{ident_char}.code) "
+ + f"formattable.simple_type_chars[i++] = '{char}';")
+print(f" formattable.simple_type_chars[i] = 0;")
+[python start generated code]*/
+ assert(sizeof(formattable.simple_type_chars) == 26);
+ int i = 0;
+ if (formattable.fmt_c.code) formattable.simple_type_chars[i++] = 'c';
+ if (formattable.fmt_b.code) formattable.simple_type_chars[i++] = 'b';
+ if (formattable.fmt_B.code) formattable.simple_type_chars[i++] = 'B';
+ if (formattable.fmt_h.code) formattable.simple_type_chars[i++] = 'h';
+ if (formattable.fmt_H.code) formattable.simple_type_chars[i++] = 'H';
+ if (formattable.fmt_i.code) formattable.simple_type_chars[i++] = 'i';
+ if (formattable.fmt_I.code) formattable.simple_type_chars[i++] = 'I';
+ if (formattable.fmt_l.code) formattable.simple_type_chars[i++] = 'l';
+ if (formattable.fmt_L.code) formattable.simple_type_chars[i++] = 'L';
+ if (formattable.fmt_d.code) formattable.simple_type_chars[i++] = 'd';
+ if (formattable.fmt_C.code) formattable.simple_type_chars[i++] = 'C';
+ if (formattable.fmt_E.code) formattable.simple_type_chars[i++] = 'E';
+ if (formattable.fmt_F.code) formattable.simple_type_chars[i++] = 'F';
+ if (formattable.fmt_f.code) formattable.simple_type_chars[i++] = 'f';
+ if (formattable.fmt_u.code) formattable.simple_type_chars[i++] = 'u';
+ if (formattable.fmt_z.code) formattable.simple_type_chars[i++] = 'z';
+ if (formattable.fmt_Z.code) formattable.simple_type_chars[i++] = 'Z';
+ if (formattable.fmt_q.code) formattable.simple_type_chars[i++] = 'q';
+ if (formattable.fmt_Q.code) formattable.simple_type_chars[i++] = 'Q';
+ if (formattable.fmt_P.code) formattable.simple_type_chars[i++] = 'P';
+ if (formattable.fmt_X.code) formattable.simple_type_chars[i++] = 'X';
+ if (formattable.fmt_O.code) formattable.simple_type_chars[i++] = 'O';
+ if (formattable.fmt_v.code) formattable.simple_type_chars[i++] = 'v';
+ if (formattable.fmt_bool.code) formattable.simple_type_chars[i++] = '?';
+ if (formattable.fmt_g.code) formattable.simple_type_chars[i++] = 'g';
+ formattable.simple_type_chars[i] = 0;
+/*[python end generated code: output=e6e5098a02f4b606 input=72031a625eac00c1]*/
+
}
#undef FIXINT_FIELDDESC_FOR
_Py_COMP_DIAG_POP
-struct fielddesc *
-_ctypes_get_fielddesc(const char *fmt)
+static void
+_ctypes_init_fielddesc(void)
{
static bool initialized = false;
static PyMutex mutex = {0};
PyMutex_Lock(&mutex);
if (!initialized) {
- _ctypes_init_fielddesc();
+ _ctypes_init_fielddesc_locked();
initialized = true;
}
PyMutex_Unlock(&mutex);
+}
+
+char *
+_ctypes_get_simple_type_chars(void) {
+ _ctypes_init_fielddesc();
+ return formattable.simple_type_chars;
+}
+
+struct fielddesc *
+_ctypes_get_fielddesc(const char *fmt)
+{
+ _ctypes_init_fielddesc();
+
struct fielddesc *result = NULL;
switch(fmt[0]) {
/*[python input]
diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h
index cc09639e21f7c2..9e8097feae2c17 100644
--- a/Modules/_ctypes/ctypes.h
+++ b/Modules/_ctypes/ctypes.h
@@ -5,8 +5,17 @@
#include "pycore_moduleobject.h" // _PyModule_GetState()
#include "pycore_typeobject.h" // _PyType_GetModuleState()
+// Do we support C99 complex types in ffi?
+// For Apple's libffi, this must be determined at runtime (see gh-128156).
#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
# include "../_complex.h" // complex
+# if USING_APPLE_OS_LIBFFI && defined(__has_builtin) && __has_builtin(__builtin_available)
+# define Py_FFI_COMPLEX_AVAILABLE __builtin_available(macOS 10.15, *)
+# else
+# define Py_FFI_COMPLEX_AVAILABLE 1
+# endif
+#else
+# define Py_FFI_COMPLEX_AVAILABLE 0
#endif
#ifndef MS_WIN32
@@ -255,6 +264,9 @@ struct fielddesc {
GETFUNC getfunc_swapped;
};
+// Get all single-character type codes (for use in error messages)
+extern char *_ctypes_get_simple_type_chars(void);
+
typedef struct CFieldObject {
PyObject_HEAD
Py_ssize_t offset;
diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv
index a74779803228c2..54954cfb5f83ff 100644
--- a/Tools/c-analyzer/cpython/globals-to-fix.tsv
+++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv
@@ -407,7 +407,8 @@ Modules/_tkinter.c - trbInCmd -
## other
Include/datetime.h - PyDateTimeAPI -
-Modules/_ctypes/cfield.c _ctypes_get_fielddesc initialized -
+Modules/_ctypes/cfield.c _ctypes_init_fielddesc initialized -
+Modules/_ctypes/cfield.c - formattable -
Modules/_ctypes/malloc_closure.c - _pagesize -
Modules/_cursesmodule.c - curses_module_loaded -
Modules/_cursesmodule.c - curses_initscr_called -
@@ -422,7 +423,6 @@ Modules/readline.c - libedit_history_start -
##-----------------------
## state
-Modules/_ctypes/cfield.c - formattable -
Modules/_ctypes/malloc_closure.c - free_list -
Modules/_curses_panel.c - lop -
Modules/_ssl/debughelpers.c _PySSL_keylog_callback lock -
1
0
Jan. 21, 2025
https://github.com/python/cpython/commit/0b66037993bfb1f233f18876988d4aab5f…
commit: 0b66037993bfb1f233f18876988d4aab5f44896f
branch: 3.12
author: Miss Islington (bot) <31488909+miss-islington(a)users.noreply.github.com>
committer: encukou <encukou(a)gmail.com>
date: 2025-01-21T10:40:38+01:00
summary:
[3.12] Docs: fix typo in `Doc/howto/mro.rst` (GH-129095) (GH-129121)
(cherry picked from commit da310d209a6af45e5e1ea48a873f1d14be471a09)
Co-authored-by: smelnikov <13030121+smelnikov(a)users.noreply.github.com>
files:
M Doc/howto/mro.rst
diff --git a/Doc/howto/mro.rst b/Doc/howto/mro.rst
index 46db516e16dae4..0872bedcd3a2d3 100644
--- a/Doc/howto/mro.rst
+++ b/Doc/howto/mro.rst
@@ -398,7 +398,7 @@ with inheritance diagram
We see that class G inherits from F and E, with F *before* E: therefore
we would expect the attribute *G.remember2buy* to be inherited by
-*F.rembermer2buy* and not by *E.remember2buy*: nevertheless Python 2.2
+*F.remember2buy* and not by *E.remember2buy*: nevertheless Python 2.2
gives
>>> G.remember2buy # doctest: +SKIP
1
0
Jan. 21, 2025
https://github.com/python/cpython/commit/8ab29d95a2a45df84370127a7e4b00b8d6…
commit: 8ab29d95a2a45df84370127a7e4b00b8d6b1b53f
branch: 3.13
author: Miss Islington (bot) <31488909+miss-islington(a)users.noreply.github.com>
committer: encukou <encukou(a)gmail.com>
date: 2025-01-21T10:40:31+01:00
summary:
[3.13] Docs: fix typo in `Doc/howto/mro.rst` (GH-129095) (GH-129122)
(cherry picked from commit da310d209a6af45e5e1ea48a873f1d14be471a09)
Co-authored-by: smelnikov <13030121+smelnikov(a)users.noreply.github.com>
files:
M Doc/howto/mro.rst
diff --git a/Doc/howto/mro.rst b/Doc/howto/mro.rst
index 46db516e16dae4..0872bedcd3a2d3 100644
--- a/Doc/howto/mro.rst
+++ b/Doc/howto/mro.rst
@@ -398,7 +398,7 @@ with inheritance diagram
We see that class G inherits from F and E, with F *before* E: therefore
we would expect the attribute *G.remember2buy* to be inherited by
-*F.rembermer2buy* and not by *E.remember2buy*: nevertheless Python 2.2
+*F.remember2buy* and not by *E.remember2buy*: nevertheless Python 2.2
gives
>>> G.remember2buy # doctest: +SKIP
1
0
GH-127953: Make line number lookup O(1) regardless of the size of the code object (GH-128350)
by markshannon Jan. 21, 2025
by markshannon Jan. 21, 2025
Jan. 21, 2025
https://github.com/python/cpython/commit/7239da75592081b6e8d0917a2cd2bf1990…
commit: 7239da75592081b6e8d0917a2cd2bf19907c8165
branch: main
author: Mark Shannon <mark(a)hotpy.org>
committer: markshannon <mark(a)hotpy.org>
date: 2025-01-21T09:33:23Z
summary:
GH-127953: Make line number lookup O(1) regardless of the size of the code object (GH-128350)
files:
A Misc/NEWS.d/next/Core_and_Builtins/2024-12-30-15-49-31.gh-issue-127953.B4_6L9.rst
M Include/cpython/code.h
M Objects/codeobject.c
M Python/bytecodes.c
M Python/generated_cases.c.h
M Python/instrumentation.c
diff --git a/Include/cpython/code.h b/Include/cpython/code.h
index cb6261ddde941b..2bd3e08631f0ad 100644
--- a/Include/cpython/code.h
+++ b/Include/cpython/code.h
@@ -35,11 +35,12 @@ typedef struct {
} _PyCoCached;
/* Ancillary data structure used for instrumentation.
- Line instrumentation creates an array of
- these. One entry per code unit.*/
+ Line instrumentation creates this with sufficient
+ space for one entry per code unit. The total size
+ of the data will be `bytes_per_entry * Py_SIZE(code)` */
typedef struct {
- uint8_t original_opcode;
- int8_t line_delta;
+ uint8_t bytes_per_entry;
+ uint8_t data[1];
} _PyCoLineInstrumentationData;
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-12-30-15-49-31.gh-issue-127953.B4_6L9.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-12-30-15-49-31.gh-issue-127953.B4_6L9.rst
new file mode 100644
index 00000000000000..f19afcd90b16ea
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-12-30-15-49-31.gh-issue-127953.B4_6L9.rst
@@ -0,0 +1,2 @@
+The time to handle a ``LINE`` event in sys.monitoring (and sys.settrace) is
+now independent of the number of lines in the code object.
diff --git a/Objects/codeobject.c b/Objects/codeobject.c
index 91332edbfd3061..7eea61968bc4d9 100644
--- a/Objects/codeobject.c
+++ b/Objects/codeobject.c
@@ -979,6 +979,9 @@ PyCode_Addr2Line(PyCodeObject *co, int addrq)
if (addrq < 0) {
return co->co_firstlineno;
}
+ if (co->_co_monitoring && co->_co_monitoring->lines) {
+ return _Py_Instrumentation_GetLine(co, addrq/sizeof(_Py_CODEUNIT));
+ }
assert(addrq >= 0 && addrq < _PyCode_NBYTES(co));
PyCodeAddressRange bounds;
_PyCode_InitAddressRange(co, &bounds);
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index 398f1d564fad4a..b2745c3400ffc9 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -4816,7 +4816,8 @@ dummy_func(
int original_opcode = 0;
if (tstate->tracing) {
PyCodeObject *code = _PyFrame_GetCode(frame);
- original_opcode = code->_co_monitoring->lines[(int)(this_instr - _PyFrame_GetBytecode(frame))].original_opcode;
+ int index = (int)(this_instr - _PyFrame_GetBytecode(frame));
+ original_opcode = code->_co_monitoring->lines->data[index*code->_co_monitoring->lines->bytes_per_entry];
next_instr = this_instr;
} else {
original_opcode = _Py_call_instrumentation_line(
diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h
index ec3397e1cf6e5f..f25c9a97176a52 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -4809,8 +4809,9 @@
if (tstate->tracing) {
PyCodeObject *code = _PyFrame_GetCode(frame);
_PyFrame_SetStackPointer(frame, stack_pointer);
- original_opcode = code->_co_monitoring->lines[(int)(this_instr - _PyFrame_GetBytecode(frame))].original_opcode;
+ int index = (int)(this_instr - _PyFrame_GetBytecode(frame));
stack_pointer = _PyFrame_GetStackPointer(frame);
+ original_opcode = code->_co_monitoring->lines->data[index*code->_co_monitoring->lines->bytes_per_entry];
next_instr = this_instr;
} else {
_PyFrame_SetStackPointer(frame, stack_pointer);
diff --git a/Python/instrumentation.c b/Python/instrumentation.c
index b24e2887f42ecc..8968ff2c691272 100644
--- a/Python/instrumentation.c
+++ b/Python/instrumentation.c
@@ -53,7 +53,7 @@
if (bc == NULL) { \
continue; \
} \
- (func)((_Py_CODEUNIT *)bc, __VA_ARGS__); \
+ (func)(code, (_Py_CODEUNIT *)bc, __VA_ARGS__); \
} \
} while (0)
@@ -62,7 +62,7 @@
#define LOCK_CODE(code)
#define UNLOCK_CODE()
#define MODIFY_BYTECODE(code, func, ...) \
- (func)(_PyCode_CODE(code), __VA_ARGS__)
+ (func)(code, _PyCode_CODE(code), __VA_ARGS__)
#endif
@@ -290,48 +290,36 @@ get_events(_Py_GlobalMonitors *m, int tool_id)
return result;
}
-/* Line delta.
- * 8 bit value.
- * if line_delta == -128:
- * line = None # represented as -1
- * elif line_delta == -127 or line_delta == -126:
- * line = PyCode_Addr2Line(code, offset * sizeof(_Py_CODEUNIT));
+/* Module code can have line 0, even though modules start at line 1,
+ * so -1 is a legal delta. */
+#define NO_LINE (-2)
+
+/* Returns the line delta. Defined as:
+ * if line is None:
+ * line_delta = NO_LINE
* else:
- * line = first_line + (offset >> OFFSET_SHIFT) + line_delta;
+ * line_delta = line - first_line
*/
-
-#define NO_LINE -128
-#define COMPUTED_LINE_LINENO_CHANGE -127
-#define COMPUTED_LINE -126
-
-#define OFFSET_SHIFT 4
-
-static int8_t
-compute_line_delta(PyCodeObject *code, int offset, int line)
+static int
+compute_line_delta(PyCodeObject *code, int line)
{
if (line < 0) {
+ assert(line == -1);
return NO_LINE;
}
- int delta = line - code->co_firstlineno - (offset >> OFFSET_SHIFT);
- if (delta <= INT8_MAX && delta > COMPUTED_LINE) {
- return delta;
- }
- return COMPUTED_LINE;
+ int delta = line - code->co_firstlineno;
+ assert(delta > NO_LINE);
+ return delta;
}
static int
-compute_line(PyCodeObject *code, int offset, int8_t line_delta)
+compute_line(PyCodeObject *code, int line_delta)
{
- if (line_delta > COMPUTED_LINE) {
- return code->co_firstlineno + (offset >> OFFSET_SHIFT) + line_delta;
- }
if (line_delta == NO_LINE) {
-
return -1;
}
- assert(line_delta == COMPUTED_LINE || line_delta == COMPUTED_LINE_LINENO_CHANGE);
- /* Look it up */
- return PyCode_Addr2Line(code, offset * sizeof(_Py_CODEUNIT));
+ assert(line_delta > NO_LINE);
+ return code->co_firstlineno + line_delta;
}
int
@@ -343,6 +331,57 @@ _PyInstruction_GetLength(PyCodeObject *code, int offset)
return 1 + _PyOpcode_Caches[inst.op.code];
}
+static inline uint8_t
+get_original_opcode(_PyCoLineInstrumentationData *line_data, int index)
+{
+ return line_data->data[index*line_data->bytes_per_entry];
+}
+
+static inline uint8_t *
+get_original_opcode_ptr(_PyCoLineInstrumentationData *line_data, int index)
+{
+ return &line_data->data[index*line_data->bytes_per_entry];
+}
+
+static inline void
+set_original_opcode(_PyCoLineInstrumentationData *line_data, int index, uint8_t opcode)
+{
+ line_data->data[index*line_data->bytes_per_entry] = opcode;
+}
+
+static inline int
+get_line_delta(_PyCoLineInstrumentationData *line_data, int index)
+{
+ uint8_t *ptr = &line_data->data[index*line_data->bytes_per_entry+1];
+ assert(line_data->bytes_per_entry >= 2);
+ uint32_t value = *ptr;
+ for (int idx = 2; idx < line_data->bytes_per_entry; idx++) {
+ ptr++;
+ int shift = (idx-1)*8;
+ value |= ((uint32_t)(*ptr)) << shift;
+ }
+ assert(value < INT_MAX);
+ /* NO_LINE is stored as zero. */
+ return ((int)value) + NO_LINE;
+}
+
+static inline void
+set_line_delta(_PyCoLineInstrumentationData *line_data, int index, int line_delta)
+{
+ /* Store line_delta + 2 as we need -2 to represent no line number */
+ assert(line_delta >= NO_LINE);
+ uint32_t adjusted = line_delta - NO_LINE;
+ uint8_t *ptr = &line_data->data[index*line_data->bytes_per_entry+1];
+ assert(adjusted < (1ULL << ((line_data->bytes_per_entry-1)*8)));
+ assert(line_data->bytes_per_entry >= 2);
+ *ptr = adjusted & 0xff;
+ for (int idx = 2; idx < line_data->bytes_per_entry; idx++) {
+ ptr++;
+ adjusted >>= 8;
+ *ptr = adjusted & 0xff;
+ }
+}
+
#ifdef INSTRUMENT_DEBUG
static void
@@ -362,11 +401,15 @@ dump_instrumentation_data_lines(PyCodeObject *code, _PyCoLineInstrumentationData
if (lines == NULL) {
fprintf(out, ", lines = NULL");
}
- else if (lines[i].original_opcode == 0) {
- fprintf(out, ", lines = {original_opcode = No LINE (0), line_delta = %d)", lines[i].line_delta);
- }
else {
- fprintf(out, ", lines = {original_opcode = %s, line_delta = %d)", _PyOpcode_OpName[lines[i].original_opcode], lines[i].line_delta);
+ int opcode = get_original_opcode(lines, i);
+ int line_delta = get_line_delta(lines, i);
+ if (opcode == 0) {
+ fprintf(out, ", lines = {original_opcode = No LINE (0), line_delta = %d)", line_delta);
+ }
+ else {
+ fprintf(out, ", lines = {original_opcode = %s, line_delta = %d)", _PyOpcode_OpName[opcode], line_delta);
+ }
}
}
@@ -416,6 +459,12 @@ dump_local_monitors(const char *prefix, _Py_LocalMonitors monitors, FILE*out)
}
}
+/** NOTE:
+ * Do not use PyCode_Addr2Line to determine the line number in instrumentation,
+ * as `PyCode_Addr2Line` uses the monitoring data if it is available.
+ */
+
+
/* No error checking -- Don't use this for anything but experimental debugging */
static void
dump_instrumentation_data(PyCodeObject *code, int star, FILE*out)
@@ -433,6 +482,8 @@ dump_instrumentation_data(PyCodeObject *code, int star, FILE*out)
dump_local_monitors("Active", data->active_monitors, out);
int code_len = (int)Py_SIZE(code);
bool starred = false;
+ PyCodeAddressRange range;
+ _PyCode_InitAddressRange(code, &range);
for (int i = 0; i < code_len; i += _PyInstruction_GetLength(code, i)) {
_Py_CODEUNIT *instr = &_PyCode_CODE(code)[i];
int opcode = instr->op.code;
@@ -440,7 +491,7 @@ dump_instrumentation_data(PyCodeObject *code, int star, FILE*out)
fprintf(out, "** ");
starred = true;
}
- fprintf(out, "Offset: %d, line: %d %s: ", i, PyCode_Addr2Line(code, i*2), _PyOpcode_OpName[opcode]);
+ fprintf(out, "Offset: %d, line: %d %s: ", i, _PyCode_CheckLineNumber(i*2, &range), _PyOpcode_OpName[opcode]);
dump_instrumentation_data_tools(code, data->tools, i, out);
dump_instrumentation_data_lines(code, data->lines, i, out);
dump_instrumentation_data_line_tools(code, data->line_tools, i, out);
@@ -505,10 +556,12 @@ sanity_check_instrumentation(PyCodeObject *code)
code->_co_monitoring->active_monitors,
active_monitors));
int code_len = (int)Py_SIZE(code);
+ PyCodeAddressRange range;
+ _PyCode_InitAddressRange(co, &range);
for (int i = 0; i < code_len;) {
_Py_CODEUNIT *instr = &_PyCode_CODE(code)[i];
int opcode = instr->op.code;
- int base_opcode = _Py_GetBaseCodeUnit(code, offset).op.code;
+ int base_opcode = _Py_GetBaseCodeUnit(code, i).op.code;
CHECK(valid_opcode(opcode));
CHECK(valid_opcode(base_opcode));
if (opcode == INSTRUMENTED_INSTRUCTION) {
@@ -519,8 +572,8 @@ sanity_check_instrumentation(PyCodeObject *code)
}
if (opcode == INSTRUMENTED_LINE) {
CHECK(data->lines);
- CHECK(valid_opcode(data->lines[i].original_opcode));
- opcode = data->lines[i].original_opcode;
+ opcode = get_original_opcode(data->lines, i);
+ CHECK(valid_opcode(opcode));
CHECK(opcode != END_FOR);
CHECK(opcode != RESUME);
CHECK(opcode != RESUME_CHECK);
@@ -535,7 +588,7 @@ sanity_check_instrumentation(PyCodeObject *code)
* *and* we are executing a INSTRUMENTED_LINE instruction
* that has de-instrumented itself, then we will execute
* an invalid INSTRUMENTED_INSTRUCTION */
- CHECK(data->lines[i].original_opcode != INSTRUMENTED_INSTRUCTION);
+ CHECK(get_original_opcode(data->lines, i) != INSTRUMENTED_INSTRUCTION);
}
if (opcode == INSTRUMENTED_INSTRUCTION) {
CHECK(data->per_instruction_opcodes[i] != 0);
@@ -550,9 +603,9 @@ sanity_check_instrumentation(PyCodeObject *code)
}
CHECK(active_monitors.tools[event] != 0);
}
- if (data->lines && base_opcode != END_FOR) {
- int line1 = compute_line(code, i, data->lines[i].line_delta);
- int line2 = PyCode_Addr2Line(code, i*sizeof(_Py_CODEUNIT));
+ if (data->lines && get_original_opcode(data->lines, i)) {
+ int line1 = compute_line(code, get_line_delta(data->lines, i));
+ int line2 = _PyCode_CheckLineNumber(i*sizeof(_Py_CODEUNIT), &range);
CHECK(line1 == line2);
}
CHECK(valid_opcode(opcode));
@@ -602,7 +655,7 @@ _Py_GetBaseCodeUnit(PyCodeObject *code, int i)
return inst;
}
if (opcode == INSTRUMENTED_LINE) {
- opcode = code->_co_monitoring->lines[i].original_opcode;
+ opcode = get_original_opcode(code->_co_monitoring->lines, i);
}
if (opcode == INSTRUMENTED_INSTRUCTION) {
opcode = code->_co_monitoring->per_instruction_opcodes[i];
@@ -621,7 +674,7 @@ _Py_GetBaseCodeUnit(PyCodeObject *code, int i)
}
static void
-de_instrument(_Py_CODEUNIT *bytecode, _PyCoMonitoringData *monitoring, int i,
+de_instrument(PyCodeObject *code, _Py_CODEUNIT *bytecode, _PyCoMonitoringData *monitoring, int i,
int event)
{
assert(event != PY_MONITORING_EVENT_INSTRUCTION);
@@ -632,7 +685,7 @@ de_instrument(_Py_CODEUNIT *bytecode, _PyCoMonitoringData *monitoring, int i,
int opcode = *opcode_ptr;
assert(opcode != ENTER_EXECUTOR);
if (opcode == INSTRUMENTED_LINE) {
- opcode_ptr = &monitoring->lines[i].original_opcode;
+ opcode_ptr = get_original_opcode_ptr(monitoring->lines, i);
opcode = *opcode_ptr;
}
if (opcode == INSTRUMENTED_INSTRUCTION) {
@@ -652,7 +705,7 @@ de_instrument(_Py_CODEUNIT *bytecode, _PyCoMonitoringData *monitoring, int i,
}
static void
-de_instrument_line(_Py_CODEUNIT *bytecode, _PyCoMonitoringData *monitoring,
+de_instrument_line(PyCodeObject *code, _Py_CODEUNIT *bytecode, _PyCoMonitoringData *monitoring,
int i)
{
_Py_CODEUNIT *instr = &bytecode[i];
@@ -660,10 +713,10 @@ de_instrument_line(_Py_CODEUNIT *bytecode, _PyCoMonitoringData *monitoring,
if (opcode != INSTRUMENTED_LINE) {
return;
}
- _PyCoLineInstrumentationData *lines = &monitoring->lines[i];
- int original_opcode = lines->original_opcode;
+ _PyCoLineInstrumentationData *lines = monitoring->lines;
+ int original_opcode = get_original_opcode(lines, i);
if (original_opcode == INSTRUMENTED_INSTRUCTION) {
- lines->original_opcode = monitoring->per_instruction_opcodes[i];
+ set_original_opcode(lines, i, monitoring->per_instruction_opcodes[i]);
}
CHECK(original_opcode != 0);
CHECK(original_opcode == _PyOpcode_Deopt[original_opcode]);
@@ -676,14 +729,14 @@ de_instrument_line(_Py_CODEUNIT *bytecode, _PyCoMonitoringData *monitoring,
}
static void
-de_instrument_per_instruction(_Py_CODEUNIT *bytecode,
+de_instrument_per_instruction(PyCodeObject *code, _Py_CODEUNIT *bytecode,
_PyCoMonitoringData *monitoring, int i)
{
_Py_CODEUNIT *instr = &bytecode[i];
uint8_t *opcode_ptr = &instr->op.code;
int opcode = *opcode_ptr;
if (opcode == INSTRUMENTED_LINE) {
- opcode_ptr = &monitoring->lines[i].original_opcode;
+ opcode_ptr = get_original_opcode_ptr(monitoring->lines, i);
opcode = *opcode_ptr;
}
if (opcode != INSTRUMENTED_INSTRUCTION) {
@@ -702,14 +755,13 @@ de_instrument_per_instruction(_Py_CODEUNIT *bytecode,
}
static void
-instrument(_Py_CODEUNIT *bytecode, _PyCoMonitoringData *monitoring, int i)
+instrument(PyCodeObject *code, _Py_CODEUNIT *bytecode, _PyCoMonitoringData *monitoring, int i)
{
_Py_CODEUNIT *instr = &bytecode[i];
uint8_t *opcode_ptr = &instr->op.code;
int opcode =*opcode_ptr;
if (opcode == INSTRUMENTED_LINE) {
- _PyCoLineInstrumentationData *lines = &monitoring->lines[i];
- opcode_ptr = &lines->original_opcode;
+ opcode_ptr = get_original_opcode_ptr(monitoring->lines, i);
opcode = *opcode_ptr;
}
if (opcode == INSTRUMENTED_INSTRUCTION) {
@@ -732,29 +784,27 @@ instrument(_Py_CODEUNIT *bytecode, _PyCoMonitoringData *monitoring, int i)
}
static void
-instrument_line(_Py_CODEUNIT *bytecode, _PyCoMonitoringData *monitoring, int i)
+instrument_line(PyCodeObject *code, _Py_CODEUNIT *bytecode, _PyCoMonitoringData *monitoring, int i)
{
uint8_t *opcode_ptr = &bytecode[i].op.code;
int opcode = *opcode_ptr;
if (opcode == INSTRUMENTED_LINE) {
return;
}
- _PyCoLineInstrumentationData *lines = &monitoring->lines[i];
- lines->original_opcode = _PyOpcode_Deopt[opcode];
- CHECK(lines->original_opcode > 0);
+ set_original_opcode(monitoring->lines, i, _PyOpcode_Deopt[opcode]);
+ CHECK(get_line_delta(monitoring->lines, i) > NO_LINE);
FT_ATOMIC_STORE_UINT8_RELAXED(*opcode_ptr, INSTRUMENTED_LINE);
}
static void
-instrument_per_instruction(_Py_CODEUNIT *bytecode,
+instrument_per_instruction(PyCodeObject *code, _Py_CODEUNIT *bytecode,
_PyCoMonitoringData *monitoring, int i)
{
_Py_CODEUNIT *instr = &bytecode[i];
uint8_t *opcode_ptr = &instr->op.code;
int opcode = *opcode_ptr;
if (opcode == INSTRUMENTED_LINE) {
- _PyCoLineInstrumentationData *lines = &monitoring->lines[i];
- opcode_ptr = &lines->original_opcode;
+ opcode_ptr = get_original_opcode_ptr(monitoring->lines, i);
opcode = *opcode_ptr;
}
if (opcode == INSTRUMENTED_INSTRUCTION) {
@@ -1231,18 +1281,16 @@ _Py_call_instrumentation_exc2(
call_instrumentation_vector_protected(tstate, event, frame, instr, 4, args);
}
-
int
_Py_Instrumentation_GetLine(PyCodeObject *code, int index)
{
_PyCoMonitoringData *monitoring = code->_co_monitoring;
assert(monitoring != NULL);
assert(monitoring->lines != NULL);
- assert(index >= code->_co_firsttraceable);
assert(index < Py_SIZE(code));
- _PyCoLineInstrumentationData *line_data = &monitoring->lines[index];
- int8_t line_delta = line_data->line_delta;
- int line = compute_line(code, index, line_delta);
+ _PyCoLineInstrumentationData *line_data = monitoring->lines;
+ int line_delta = get_line_delta(line_data, index);
+ int line = compute_line(code, line_delta);
return line;
}
@@ -1256,29 +1304,20 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame,
int i = (int)(instr - bytecode);
_PyCoMonitoringData *monitoring = code->_co_monitoring;
- _PyCoLineInstrumentationData *line_data = &monitoring->lines[i];
+ _PyCoLineInstrumentationData *line_data = monitoring->lines;
PyInterpreterState *interp = tstate->interp;
- int8_t line_delta = line_data->line_delta;
- int line = 0;
-
- if (line_delta == COMPUTED_LINE_LINENO_CHANGE) {
- // We know the line number must have changed, don't need to calculate
- // the line number for now because we might not need it.
- line = -1;
- } else {
- line = compute_line(code, i, line_delta);
- assert(line >= 0);
- assert(prev != NULL);
- int prev_index = (int)(prev - bytecode);
- int prev_line = _Py_Instrumentation_GetLine(code, prev_index);
- if (prev_line == line) {
- int prev_opcode = bytecode[prev_index].op.code;
- /* RESUME and INSTRUMENTED_RESUME are needed for the operation of
- * instrumentation, so must never be hidden by an INSTRUMENTED_LINE.
- */
- if (prev_opcode != RESUME && prev_opcode != INSTRUMENTED_RESUME) {
- goto done;
- }
+ int line = _Py_Instrumentation_GetLine(code, i);
+ assert(line >= 0);
+ assert(prev != NULL);
+ int prev_index = (int)(prev - bytecode);
+ int prev_line = _Py_Instrumentation_GetLine(code, prev_index);
+ if (prev_line == line) {
+ int prev_opcode = bytecode[prev_index].op.code;
+ /* RESUME and INSTRUMENTED_RESUME are needed for the operation of
+ * instrumentation, so must never be hidden by an INSTRUMENTED_LINE.
+ */
+ if (prev_opcode != RESUME && prev_opcode != INSTRUMENTED_RESUME) {
+ goto done;
}
}
@@ -1303,12 +1342,6 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame,
tstate->tracing++;
/* Call c_tracefunc directly, having set the line number. */
Py_INCREF(frame_obj);
- if (line == -1 && line_delta > COMPUTED_LINE) {
- /* Only assign f_lineno if it's easy to calculate, otherwise
- * do lazy calculation by setting the f_lineno to 0.
- */
- line = compute_line(code, i, line_delta);
- }
frame_obj->f_lineno = line;
int err = tstate->c_tracefunc(tstate->c_traceobj, frame_obj, PyTrace_LINE, Py_None);
frame_obj->f_lineno = 0;
@@ -1325,11 +1358,6 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame,
if (tools == 0) {
goto done;
}
-
- if (line == -1) {
- /* Need to calculate the line number now for monitoring events */
- line = compute_line(code, i, line_delta);
- }
PyObject *line_obj = PyLong_FromLong(line);
if (line_obj == NULL) {
return -1;
@@ -1361,7 +1389,7 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame,
Py_DECREF(line_obj);
uint8_t original_opcode;
done:
- original_opcode = line_data->original_opcode;
+ original_opcode = get_original_opcode(line_data, i);
assert(original_opcode != 0);
assert(original_opcode != INSTRUMENTED_LINE);
assert(_PyOpcode_Deopt[original_opcode] == original_opcode);
@@ -1433,7 +1461,7 @@ initialize_tools(PyCodeObject *code)
int opcode = instr->op.code;
assert(opcode != ENTER_EXECUTOR);
if (opcode == INSTRUMENTED_LINE) {
- opcode = code->_co_monitoring->lines[i].original_opcode;
+ opcode = get_original_opcode(code->_co_monitoring->lines, i);
}
if (opcode == INSTRUMENTED_INSTRUCTION) {
opcode = code->_co_monitoring->per_instruction_opcodes[i];
@@ -1476,64 +1504,58 @@ initialize_tools(PyCodeObject *code)
}
}
-#define NO_LINE -128
-
static void
-initialize_lines(PyCodeObject *code)
+initialize_lines(PyCodeObject *code, int bytes_per_entry)
{
ASSERT_WORLD_STOPPED_OR_LOCKED(code);
_PyCoLineInstrumentationData *line_data = code->_co_monitoring->lines;
assert(line_data != NULL);
+ line_data->bytes_per_entry = bytes_per_entry;
int code_len = (int)Py_SIZE(code);
PyCodeAddressRange range;
_PyCode_InitAddressRange(code, &range);
- for (int i = 0; i < code->_co_firsttraceable && i < code_len; i++) {
- line_data[i].original_opcode = 0;
- line_data[i].line_delta = -127;
- }
int current_line = -1;
- for (int i = code->_co_firsttraceable; i < code_len; ) {
+ for (int i = 0; i < code_len; ) {
int opcode = _Py_GetBaseCodeUnit(code, i).op.code;
int line = _PyCode_CheckLineNumber(i*(int)sizeof(_Py_CODEUNIT), &range);
- line_data[i].line_delta = compute_line_delta(code, i, line);
+ set_line_delta(line_data, i, compute_line_delta(code, line));
int length = _PyInstruction_GetLength(code, i);
- switch (opcode) {
- case END_ASYNC_FOR:
- case END_FOR:
- case END_SEND:
- case RESUME:
+ if (i < code->_co_firsttraceable) {
+ set_original_opcode(line_data, i, 0);
+ }
+ else {
+ switch (opcode) {
+ case END_ASYNC_FOR:
+ case END_FOR:
+ case END_SEND:
+ case RESUME:
case POP_ITER:
- /* END_FOR cannot start a line, as it is skipped by FOR_ITER
- * END_SEND cannot start a line, as it is skipped by SEND
- * RESUME and POP_ITER must not be instrumented with INSTRUMENT_LINE */
- line_data[i].original_opcode = 0;
- break;
- default:
- /* Set original_opcode to the opcode iff the instruction
- * starts a line, and thus should be instrumented.
- * This saves having to perform this check every time the
- * we turn instrumentation on or off, and serves as a sanity
- * check when debugging.
- */
- if (line != current_line && line >= 0) {
- line_data[i].original_opcode = opcode;
- if (line_data[i].line_delta == COMPUTED_LINE) {
- /* Label this line as a line with a line number change
- * which could help the monitoring callback to quickly
- * identify the line number change.
- */
- line_data[i].line_delta = COMPUTED_LINE_LINENO_CHANGE;
+ /* END_FOR cannot start a line, as it is skipped by FOR_ITER
+ * END_SEND cannot start a line, as it is skipped by SEND
+ * RESUME and POP_ITER must not be instrumented with INSTRUMENTED_LINE */
+ set_original_opcode(line_data, i, 0);
+ break;
+ default:
+ /* Set original_opcode to the opcode iff the instruction
+ * starts a line, and thus should be instrumented.
+ * This saves having to perform this check every time the
+ * we turn instrumentation on or off, and serves as a sanity
+ * check when debugging.
+ */
+ if (line != current_line && line >= 0) {
+ set_original_opcode(line_data, i, opcode);
+ CHECK(get_line_delta(line_data, i) != NO_LINE);
}
- }
- else {
- line_data[i].original_opcode = 0;
- }
- current_line = line;
+ else {
+ set_original_opcode(line_data, i, 0);
+ }
+ current_line = line;
+ }
}
for (int j = 1; j < length; j++) {
- line_data[i+j].original_opcode = 0;
- line_data[i+j].line_delta = NO_LINE;
+ set_original_opcode(line_data, i+j, 0);
+ set_line_delta(line_data, i+j, NO_LINE);
}
i += length;
}
@@ -1577,15 +1599,10 @@ initialize_lines(PyCodeObject *code)
continue;
}
assert(target >= 0);
- if (line_data[target].line_delta != NO_LINE) {
+ if (get_line_delta(line_data, target) != NO_LINE) {
int opcode = _Py_GetBaseCodeUnit(code, target).op.code;
if (opcode != POP_ITER) {
- line_data[target].original_opcode = opcode;
- if (line_data[target].line_delta == COMPUTED_LINE_LINENO_CHANGE) {
- // If the line is a jump target, we are not sure if the line
- // number changes, so we set it to COMPUTED_LINE.
- line_data[target].line_delta = COMPUTED_LINE;
- }
+ set_original_opcode(line_data, target, opcode);
}
}
}
@@ -1608,9 +1625,8 @@ initialize_lines(PyCodeObject *code)
* END_ASYNC_FOR is a bit special as it marks the end of
* an `async for` loop, which should not generate its own
* line event. */
- if (line_data[handler].line_delta != NO_LINE &&
- original_opcode != END_ASYNC_FOR) {
- line_data[handler].original_opcode = original_opcode;
+ if (get_line_delta(line_data, handler) != NO_LINE && original_opcode != END_ASYNC_FOR) {
+ set_original_opcode(line_data, handler, original_opcode);
}
}
}
@@ -1683,12 +1699,39 @@ update_instrumentation_data(PyCodeObject *code, PyInterpreterState *interp)
}
if (all_events.tools[PY_MONITORING_EVENT_LINE]) {
if (code->_co_monitoring->lines == NULL) {
- code->_co_monitoring->lines = PyMem_Malloc(code_len * sizeof(_PyCoLineInstrumentationData));
+ PyCodeAddressRange range;
+ _PyCode_InitAddressRange(code, &range);
+ int max_line = code->co_firstlineno + 1;
+ _PyCode_InitAddressRange(code, &range);
+ for (int i = code->_co_firsttraceable; i < code_len; ) {
+ int line = _PyCode_CheckLineNumber(i*(int)sizeof(_Py_CODEUNIT), &range);
+ if (line > max_line) {
+ max_line = line;
+ }
+ int length = _PyInstruction_GetLength(code, i);
+ i += length;
+ }
+ int bytes_per_entry;
+ int max_delta = max_line - code->co_firstlineno;
+ /* We store delta+2 in the table, so 253 is max for one byte */
+ if (max_delta < 256+NO_LINE) {
+ bytes_per_entry = 2;
+ }
+ else if (max_delta < (1 << 16)+NO_LINE) {
+ bytes_per_entry = 3;
+ }
+ else if (max_delta < (1 << 24)+NO_LINE) {
+ bytes_per_entry = 4;
+ }
+ else {
+ bytes_per_entry = 5;
+ }
+ code->_co_monitoring->lines = PyMem_Malloc(1 + code_len * bytes_per_entry);
if (code->_co_monitoring->lines == NULL) {
PyErr_NoMemory();
return -1;
}
- initialize_lines(code);
+ initialize_lines(code, bytes_per_entry);
}
if (multitools && code->_co_monitoring->line_tools == NULL) {
code->_co_monitoring->line_tools = PyMem_Malloc(code_len);
@@ -1803,7 +1846,7 @@ force_instrument_lock_held(PyCodeObject *code, PyInterpreterState *interp)
if (removed_line_tools) {
_PyCoLineInstrumentationData *line_data = code->_co_monitoring->lines;
for (int i = code->_co_firsttraceable; i < code_len;) {
- if (line_data[i].original_opcode) {
+ if (get_original_opcode(line_data, i)) {
remove_line_tools(code, i, removed_line_tools);
}
i += _PyInstruction_GetLength(code, i);
@@ -1830,7 +1873,7 @@ force_instrument_lock_held(PyCodeObject *code, PyInterpreterState *interp)
if (new_line_tools) {
_PyCoLineInstrumentationData *line_data = code->_co_monitoring->lines;
for (int i = code->_co_firsttraceable; i < code_len;) {
- if (line_data[i].original_opcode) {
+ if (get_original_opcode(line_data, i)) {
add_line_tools(code, i, new_line_tools);
}
i += _PyInstruction_GetLength(code, i);
1
0
gh-71339: Use new assertion methods in test_import and test_importlib (GH-129052)
by serhiy-storchaka Jan. 21, 2025
by serhiy-storchaka Jan. 21, 2025
Jan. 21, 2025
https://github.com/python/cpython/commit/f7cc7d296c2cbb33d3f0bde4ace82e8569…
commit: f7cc7d296c2cbb33d3f0bde4ace82e8569f7dbc3
branch: main
author: Serhiy Storchaka <storchaka(a)gmail.com>
committer: serhiy-storchaka <storchaka(a)gmail.com>
date: 2025-01-21T11:24:19+02:00
summary:
gh-71339: Use new assertion methods in test_import and test_importlib (GH-129052)
files:
M Lib/test/test_import/__init__.py
M Lib/test/test_importlib/extension/test_path_hook.py
M Lib/test/test_importlib/frozen/test_loader.py
M Lib/test/test_importlib/import_/test_caching.py
M Lib/test/test_importlib/import_/test_fromlist.py
M Lib/test/test_importlib/import_/test_meta_path.py
M Lib/test/test_importlib/import_/test_path.py
M Lib/test/test_importlib/import_/test_relative_imports.py
M Lib/test/test_importlib/resources/test_path.py
M Lib/test/test_importlib/source/test_finder.py
M Lib/test/test_importlib/source/test_path_hook.py
M Lib/test/test_importlib/test_abc.py
M Lib/test/test_importlib/test_api.py
M Lib/test/test_importlib/test_lazy.py
M Lib/test/test_importlib/test_namespace_pkgs.py
M Lib/test/test_importlib/test_pkg_import.py
M Lib/test/test_importlib/test_spec.py
M Lib/test/test_importlib/test_util.py
diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py
index 1e706023c795b6..654b9f5bd7ab50 100644
--- a/Lib/test/test_import/__init__.py
+++ b/Lib/test/test_import/__init__.py
@@ -539,7 +539,7 @@ def test_import_name_binding(self):
import test as x
import test.support
self.assertIs(x, test, x.__name__)
- self.assertTrue(hasattr(test.support, "__file__"))
+ self.assertHasAttr(test.support, "__file__")
# import x.y.z as w binds z as w
import test.support as y
@@ -610,7 +610,7 @@ def test_file_to_source(self):
sys.path.insert(0, os.curdir)
try:
mod = __import__(TESTFN)
- self.assertTrue(mod.__file__.endswith('.py'))
+ self.assertEndsWith(mod.__file__, '.py')
os.remove(source)
del sys.modules[TESTFN]
make_legacy_pyc(source)
@@ -1443,7 +1443,7 @@ def test_UNC_path(self):
self.fail("could not import 'test_unc_path' from %r: %r"
% (unc, e))
self.assertEqual(mod.testdata, 'test_unc_path')
- self.assertTrue(mod.__file__.startswith(unc), mod.__file__)
+ self.assertStartsWith(mod.__file__, unc)
unload("test_unc_path")
@@ -1456,7 +1456,7 @@ def tearDown(self):
def test_relimport_star(self):
# This will import * from .test_import.
from .. import relimport
- self.assertTrue(hasattr(relimport, "RelativeImportTests"))
+ self.assertHasAttr(relimport, "RelativeImportTests")
def test_issue3221(self):
# Note for mergers: the 'absolute' tests from the 2.x branch
@@ -1786,7 +1786,7 @@ def test_frozen_importlib_is_bootstrap(self):
self.assertIs(mod, _bootstrap)
self.assertEqual(mod.__name__, 'importlib._bootstrap')
self.assertEqual(mod.__package__, 'importlib')
- self.assertTrue(mod.__file__.endswith('_bootstrap.py'), mod.__file__)
+ self.assertEndsWith(mod.__file__, '_bootstrap.py')
def test_frozen_importlib_external_is_bootstrap_external(self):
from importlib import _bootstrap_external
@@ -1794,7 +1794,7 @@ def test_frozen_importlib_external_is_bootstrap_external(self):
self.assertIs(mod, _bootstrap_external)
self.assertEqual(mod.__name__, 'importlib._bootstrap_external')
self.assertEqual(mod.__package__, 'importlib')
- self.assertTrue(mod.__file__.endswith('_bootstrap_external.py'), mod.__file__)
+ self.assertEndsWith(mod.__file__, '_bootstrap_external.py')
def test_there_can_be_only_one(self):
# Issue #15386 revealed a tricky loophole in the bootstrapping
@@ -2800,7 +2800,7 @@ def check_common(self, loaded):
self.assertEqual(mod.__file__, self.FILE)
self.assertEqual(mod.__spec__.origin, self.ORIGIN)
if not isolated:
- self.assertTrue(issubclass(mod.error, Exception))
+ self.assertIsSubclass(mod.error, Exception)
self.assertEqual(mod.int_const, 1969)
self.assertEqual(mod.str_const, 'something different')
self.assertIsInstance(mod._module_initialized, float)
diff --git a/Lib/test/test_importlib/extension/test_path_hook.py b/Lib/test/test_importlib/extension/test_path_hook.py
index 314a635c77e082..941dcd5432ce46 100644
--- a/Lib/test/test_importlib/extension/test_path_hook.py
+++ b/Lib/test/test_importlib/extension/test_path_hook.py
@@ -21,7 +21,7 @@ def hook(self, entry):
def test_success(self):
# Path hook should handle a directory where a known extension module
# exists.
- self.assertTrue(hasattr(self.hook(util.EXTENSIONS.path), 'find_spec'))
+ self.assertHasAttr(self.hook(util.EXTENSIONS.path), 'find_spec')
(Frozen_PathHooksTests,
diff --git a/Lib/test/test_importlib/frozen/test_loader.py b/Lib/test/test_importlib/frozen/test_loader.py
index 1112c0664ad477..c808bb73291a7c 100644
--- a/Lib/test/test_importlib/frozen/test_loader.py
+++ b/Lib/test/test_importlib/frozen/test_loader.py
@@ -61,7 +61,7 @@ def exec_module(self, name, origname=None):
module.main()
self.assertTrue(module.initialized)
- self.assertTrue(hasattr(module, '__spec__'))
+ self.assertHasAttr(module, '__spec__')
self.assertEqual(module.__spec__.origin, 'frozen')
return module, stdout.getvalue()
@@ -72,7 +72,7 @@ def test_module(self):
for attr, value in check.items():
self.assertEqual(getattr(module, attr), value)
self.assertEqual(output, 'Hello world!\n')
- self.assertTrue(hasattr(module, '__spec__'))
+ self.assertHasAttr(module, '__spec__')
self.assertEqual(module.__spec__.loader_state.origname, name)
def test_package(self):
@@ -136,7 +136,7 @@ def test_get_code(self):
exec(code, mod.__dict__)
with captured_stdout() as stdout:
mod.main()
- self.assertTrue(hasattr(mod, 'initialized'))
+ self.assertHasAttr(mod, 'initialized')
self.assertEqual(stdout.getvalue(), 'Hello world!\n')
def test_get_source(self):
diff --git a/Lib/test/test_importlib/import_/test_caching.py b/Lib/test/test_importlib/import_/test_caching.py
index aedf0fd4f9db02..718e7d041b0860 100644
--- a/Lib/test/test_importlib/import_/test_caching.py
+++ b/Lib/test/test_importlib/import_/test_caching.py
@@ -78,7 +78,7 @@ def test_using_cache_for_assigning_to_attribute(self):
with self.create_mock('pkg.__init__', 'pkg.module') as importer:
with util.import_state(meta_path=[importer]):
module = self.__import__('pkg.module')
- self.assertTrue(hasattr(module, 'module'))
+ self.assertHasAttr(module, 'module')
self.assertEqual(id(module.module),
id(sys.modules['pkg.module']))
@@ -88,7 +88,7 @@ def test_using_cache_for_fromlist(self):
with self.create_mock('pkg.__init__', 'pkg.module') as importer:
with util.import_state(meta_path=[importer]):
module = self.__import__('pkg', fromlist=['module'])
- self.assertTrue(hasattr(module, 'module'))
+ self.assertHasAttr(module, 'module')
self.assertEqual(id(module.module),
id(sys.modules['pkg.module']))
diff --git a/Lib/test/test_importlib/import_/test_fromlist.py b/Lib/test/test_importlib/import_/test_fromlist.py
index 4b4b9bc3f5e04a..feccc7be09a98c 100644
--- a/Lib/test/test_importlib/import_/test_fromlist.py
+++ b/Lib/test/test_importlib/import_/test_fromlist.py
@@ -63,7 +63,7 @@ def test_nonexistent_object(self):
with util.import_state(meta_path=[importer]):
module = self.__import__('module', fromlist=['non_existent'])
self.assertEqual(module.__name__, 'module')
- self.assertFalse(hasattr(module, 'non_existent'))
+ self.assertNotHasAttr(module, 'non_existent')
def test_module_from_package(self):
# [module]
@@ -71,7 +71,7 @@ def test_module_from_package(self):
with util.import_state(meta_path=[importer]):
module = self.__import__('pkg', fromlist=['module'])
self.assertEqual(module.__name__, 'pkg')
- self.assertTrue(hasattr(module, 'module'))
+ self.assertHasAttr(module, 'module')
self.assertEqual(module.module.__name__, 'pkg.module')
def test_nonexistent_from_package(self):
@@ -79,7 +79,7 @@ def test_nonexistent_from_package(self):
with util.import_state(meta_path=[importer]):
module = self.__import__('pkg', fromlist=['non_existent'])
self.assertEqual(module.__name__, 'pkg')
- self.assertFalse(hasattr(module, 'non_existent'))
+ self.assertNotHasAttr(module, 'non_existent')
def test_module_from_package_triggers_ModuleNotFoundError(self):
# If a submodule causes an ModuleNotFoundError because it tries
@@ -107,7 +107,7 @@ def basic_star_test(self, fromlist=['*']):
mock['pkg'].__all__ = ['module']
module = self.__import__('pkg', fromlist=fromlist)
self.assertEqual(module.__name__, 'pkg')
- self.assertTrue(hasattr(module, 'module'))
+ self.assertHasAttr(module, 'module')
self.assertEqual(module.module.__name__, 'pkg.module')
def test_using_star(self):
@@ -125,8 +125,8 @@ def test_star_with_others(self):
mock['pkg'].__all__ = ['module1']
module = self.__import__('pkg', fromlist=['module2', '*'])
self.assertEqual(module.__name__, 'pkg')
- self.assertTrue(hasattr(module, 'module1'))
- self.assertTrue(hasattr(module, 'module2'))
+ self.assertHasAttr(module, 'module1')
+ self.assertHasAttr(module, 'module2')
self.assertEqual(module.module1.__name__, 'pkg.module1')
self.assertEqual(module.module2.__name__, 'pkg.module2')
@@ -136,7 +136,7 @@ def test_nonexistent_in_all(self):
importer['pkg'].__all__ = ['non_existent']
module = self.__import__('pkg', fromlist=['*'])
self.assertEqual(module.__name__, 'pkg')
- self.assertFalse(hasattr(module, 'non_existent'))
+ self.assertNotHasAttr(module, 'non_existent')
def test_star_in_all(self):
with util.mock_spec('pkg.__init__') as importer:
@@ -144,7 +144,7 @@ def test_star_in_all(self):
importer['pkg'].__all__ = ['*']
module = self.__import__('pkg', fromlist=['*'])
self.assertEqual(module.__name__, 'pkg')
- self.assertFalse(hasattr(module, '*'))
+ self.assertNotHasAttr(module, '*')
def test_invalid_type(self):
with util.mock_spec('pkg.__init__') as importer:
diff --git a/Lib/test/test_importlib/import_/test_meta_path.py b/Lib/test/test_importlib/import_/test_meta_path.py
index 8689017ba43112..4c00f60681acf1 100644
--- a/Lib/test/test_importlib/import_/test_meta_path.py
+++ b/Lib/test/test_importlib/import_/test_meta_path.py
@@ -43,7 +43,7 @@ def test_empty(self):
self.assertIsNone(importlib._bootstrap._find_spec('nothing',
None))
self.assertEqual(len(w), 1)
- self.assertTrue(issubclass(w[-1].category, ImportWarning))
+ self.assertIsSubclass(w[-1].category, ImportWarning)
(Frozen_CallingOrder,
diff --git a/Lib/test/test_importlib/import_/test_path.py b/Lib/test/test_importlib/import_/test_path.py
index 89b52fbd1e1aff..bcd5ad6e76005a 100644
--- a/Lib/test/test_importlib/import_/test_path.py
+++ b/Lib/test/test_importlib/import_/test_path.py
@@ -80,7 +80,7 @@ def test_empty_path_hooks(self):
self.assertIsNone(self.find('os'))
self.assertIsNone(sys.path_importer_cache[path_entry])
self.assertEqual(len(w), 1)
- self.assertTrue(issubclass(w[-1].category, ImportWarning))
+ self.assertIsSubclass(w[-1].category, ImportWarning)
def test_path_importer_cache_empty_string(self):
# The empty string should create a finder using the cwd.
diff --git a/Lib/test/test_importlib/import_/test_relative_imports.py b/Lib/test/test_importlib/import_/test_relative_imports.py
index 99c24f1fd9487c..e535d119763148 100644
--- a/Lib/test/test_importlib/import_/test_relative_imports.py
+++ b/Lib/test/test_importlib/import_/test_relative_imports.py
@@ -81,7 +81,7 @@ def callback(global_):
self.__import__('pkg') # For __import__().
module = self.__import__('', global_, fromlist=['mod2'], level=1)
self.assertEqual(module.__name__, 'pkg')
- self.assertTrue(hasattr(module, 'mod2'))
+ self.assertHasAttr(module, 'mod2')
self.assertEqual(module.mod2.attr, 'pkg.mod2')
self.relative_import_test(create, globals_, callback)
@@ -107,7 +107,7 @@ def callback(global_):
module = self.__import__('', global_, fromlist=['module'],
level=1)
self.assertEqual(module.__name__, 'pkg')
- self.assertTrue(hasattr(module, 'module'))
+ self.assertHasAttr(module, 'module')
self.assertEqual(module.module.attr, 'pkg.module')
self.relative_import_test(create, globals_, callback)
@@ -131,7 +131,7 @@ def callback(global_):
module = self.__import__('', global_, fromlist=['subpkg2'],
level=2)
self.assertEqual(module.__name__, 'pkg')
- self.assertTrue(hasattr(module, 'subpkg2'))
+ self.assertHasAttr(module, 'subpkg2')
self.assertEqual(module.subpkg2.attr, 'pkg.subpkg2.__init__')
self.relative_import_test(create, globals_, callback)
diff --git a/Lib/test/test_importlib/resources/test_path.py b/Lib/test/test_importlib/resources/test_path.py
index 378dc7a2baeb23..903911f57b3306 100644
--- a/Lib/test/test_importlib/resources/test_path.py
+++ b/Lib/test/test_importlib/resources/test_path.py
@@ -20,7 +20,7 @@ def test_reading(self):
target = resources.files(self.data) / 'utf-8.file'
with resources.as_file(target) as path:
self.assertIsInstance(path, pathlib.Path)
- self.assertTrue(path.name.endswith("utf-8.file"), repr(path))
+ self.assertEndsWith(path.name, "utf-8.file")
self.assertEqual('Hello, UTF-8 world!\n', path.read_text(encoding='utf-8'))
diff --git a/Lib/test/test_importlib/source/test_finder.py b/Lib/test/test_importlib/source/test_finder.py
index 8c06c4da1f5cba..4de736a6bf3b2d 100644
--- a/Lib/test/test_importlib/source/test_finder.py
+++ b/Lib/test/test_importlib/source/test_finder.py
@@ -73,7 +73,7 @@ def run_test(self, test, create=None, *, compile_=None, unlink=None):
if error.errno != errno.ENOENT:
raise
loader = self.import_(mapping['.root'], test)
- self.assertTrue(hasattr(loader, 'load_module'))
+ self.assertHasAttr(loader, 'load_module')
return loader
def test_module(self):
@@ -100,7 +100,7 @@ def test_module_in_package(self):
with util.create_modules('pkg.__init__', 'pkg.sub') as mapping:
pkg_dir = os.path.dirname(mapping['pkg.__init__'])
loader = self.import_(pkg_dir, 'pkg.sub')
- self.assertTrue(hasattr(loader, 'load_module'))
+ self.assertHasAttr(loader, 'load_module')
# [sub package]
def test_package_in_package(self):
@@ -108,7 +108,7 @@ def test_package_in_package(self):
with context as mapping:
pkg_dir = os.path.dirname(mapping['pkg.__init__'])
loader = self.import_(pkg_dir, 'pkg.sub')
- self.assertTrue(hasattr(loader, 'load_module'))
+ self.assertHasAttr(loader, 'load_module')
# [package over modules]
def test_package_over_module(self):
@@ -129,7 +129,7 @@ def test_empty_string_for_dir(self):
file.write("# test file for importlib")
try:
loader = self._find(finder, 'mod', loader_only=True)
- self.assertTrue(hasattr(loader, 'load_module'))
+ self.assertHasAttr(loader, 'load_module')
finally:
os.unlink('mod.py')
diff --git a/Lib/test/test_importlib/source/test_path_hook.py b/Lib/test/test_importlib/source/test_path_hook.py
index f274330e0b333b..6e1c23e6a9842b 100644
--- a/Lib/test/test_importlib/source/test_path_hook.py
+++ b/Lib/test/test_importlib/source/test_path_hook.py
@@ -15,12 +15,12 @@ def path_hook(self):
def test_success(self):
with util.create_modules('dummy') as mapping:
- self.assertTrue(hasattr(self.path_hook()(mapping['.root']),
- 'find_spec'))
+ self.assertHasAttr(self.path_hook()(mapping['.root']),
+ 'find_spec')
def test_empty_string(self):
# The empty string represents the cwd.
- self.assertTrue(hasattr(self.path_hook()(''), 'find_spec'))
+ self.assertHasAttr(self.path_hook()(''), 'find_spec')
(Frozen_PathHookTest,
diff --git a/Lib/test/test_importlib/test_abc.py b/Lib/test/test_importlib/test_abc.py
index 92a77e079e57e7..b1ab52f966ffdb 100644
--- a/Lib/test/test_importlib/test_abc.py
+++ b/Lib/test/test_importlib/test_abc.py
@@ -43,14 +43,12 @@ def setUp(self):
def test_subclasses(self):
# Test that the expected subclasses inherit.
for subclass in self.subclasses:
- self.assertTrue(issubclass(subclass, self.__test),
- "{0} is not a subclass of {1}".format(subclass, self.__test))
+ self.assertIsSubclass(subclass, self.__test)
def test_superclasses(self):
# Test that the class inherits from the expected superclasses.
for superclass in self.superclasses:
- self.assertTrue(issubclass(self.__test, superclass),
- "{0} is not a superclass of {1}".format(superclass, self.__test))
+ self.assertIsSubclass(self.__test, superclass)
class MetaPathFinder(InheritanceTests):
@@ -424,14 +422,14 @@ def test_source_to_code_source(self):
# Since compile() can handle strings, so should source_to_code().
source = 'attr = 42'
module = self.source_to_module(source)
- self.assertTrue(hasattr(module, 'attr'))
+ self.assertHasAttr(module, 'attr')
self.assertEqual(module.attr, 42)
def test_source_to_code_bytes(self):
# Since compile() can handle bytes, so should source_to_code().
source = b'attr = 42'
module = self.source_to_module(source)
- self.assertTrue(hasattr(module, 'attr'))
+ self.assertHasAttr(module, 'attr')
self.assertEqual(module.attr, 42)
def test_source_to_code_path(self):
@@ -765,7 +763,7 @@ def test_package_settings(self):
warnings.simplefilter('ignore', DeprecationWarning)
module = self.loader.load_module(self.name)
self.verify_module(module)
- self.assertFalse(hasattr(module, '__path__'))
+ self.assertNotHasAttr(module, '__path__')
def test_get_source_encoding(self):
# Source is considered encoded in UTF-8 by default unless otherwise
diff --git a/Lib/test/test_importlib/test_api.py b/Lib/test/test_importlib/test_api.py
index 6035b2ca72efb9..1bc531a2fe34e7 100644
--- a/Lib/test/test_importlib/test_api.py
+++ b/Lib/test/test_importlib/test_api.py
@@ -430,8 +430,7 @@ def test_everyone_has___loader__(self):
for name, module in sys.modules.items():
if isinstance(module, types.ModuleType):
with self.subTest(name=name):
- self.assertTrue(hasattr(module, '__loader__'),
- '{!r} lacks a __loader__ attribute'.format(name))
+ self.assertHasAttr(module, '__loader__')
if self.machinery.BuiltinImporter.find_spec(name):
self.assertIsNot(module.__loader__, None)
elif self.machinery.FrozenImporter.find_spec(name):
@@ -441,7 +440,7 @@ def test_everyone_has___spec__(self):
for name, module in sys.modules.items():
if isinstance(module, types.ModuleType):
with self.subTest(name=name):
- self.assertTrue(hasattr(module, '__spec__'))
+ self.assertHasAttr(module, '__spec__')
if self.machinery.BuiltinImporter.find_spec(name):
self.assertIsNot(module.__spec__, None)
elif self.machinery.FrozenImporter.find_spec(name):
diff --git a/Lib/test/test_importlib/test_lazy.py b/Lib/test/test_importlib/test_lazy.py
index 5c6e0303528906..e48fad8898f0ef 100644
--- a/Lib/test/test_importlib/test_lazy.py
+++ b/Lib/test/test_importlib/test_lazy.py
@@ -125,12 +125,12 @@ def test_delete_eventual_attr(self):
# Deleting an attribute should stay deleted.
module = self.new_module()
del module.attr
- self.assertFalse(hasattr(module, 'attr'))
+ self.assertNotHasAttr(module, 'attr')
def test_delete_preexisting_attr(self):
module = self.new_module()
del module.__name__
- self.assertFalse(hasattr(module, '__name__'))
+ self.assertNotHasAttr(module, '__name__')
def test_module_substitution_error(self):
with test_util.uncache(TestingImporter.module_name):
diff --git a/Lib/test/test_importlib/test_namespace_pkgs.py b/Lib/test/test_importlib/test_namespace_pkgs.py
index cbbdada3b010a7..6ca0978f9bca69 100644
--- a/Lib/test/test_importlib/test_namespace_pkgs.py
+++ b/Lib/test/test_importlib/test_namespace_pkgs.py
@@ -80,7 +80,7 @@ def test_cant_import_other(self):
def test_simple_repr(self):
import foo.one
- self.assertTrue(repr(foo).startswith("<module 'foo' (namespace) from ["))
+ self.assertStartsWith(repr(foo), "<module 'foo' (namespace) from [")
class DynamicPathNamespacePackage(NamespacePackageTest):
@@ -301,7 +301,7 @@ def test_missing_directory(self):
def test_missing_directory2(self):
import foo
- self.assertFalse(hasattr(foo, 'one'))
+ self.assertNotHasAttr(foo, 'one')
def test_present_directory(self):
import bar.two
diff --git a/Lib/test/test_importlib/test_pkg_import.py b/Lib/test/test_importlib/test_pkg_import.py
index 66f5f8bc253a85..5ffae6222bacb8 100644
--- a/Lib/test/test_importlib/test_pkg_import.py
+++ b/Lib/test/test_importlib/test_pkg_import.py
@@ -55,7 +55,7 @@ def test_package_import__semantics(self):
except SyntaxError: pass
else: raise RuntimeError('Failed to induce SyntaxError') # self.fail()?
self.assertNotIn(self.module_name, sys.modules)
- self.assertFalse(hasattr(sys.modules[self.package_name], 'foo'))
+ self.assertNotHasAttr(sys.modules[self.package_name], 'foo')
# ...make up a variable name that isn't bound in __builtins__
var = 'a'
diff --git a/Lib/test/test_importlib/test_spec.py b/Lib/test/test_importlib/test_spec.py
index 02318926f35eee..aebeabaf83f75d 100644
--- a/Lib/test/test_importlib/test_spec.py
+++ b/Lib/test/test_importlib/test_spec.py
@@ -237,7 +237,7 @@ def test_exec(self):
self.spec.loader = NewLoader()
module = self.util.module_from_spec(self.spec)
sys.modules[self.name] = module
- self.assertFalse(hasattr(module, 'eggs'))
+ self.assertNotHasAttr(module, 'eggs')
self.bootstrap._exec(self.spec, module)
self.assertEqual(module.eggs, 1)
@@ -348,9 +348,9 @@ def test_reload_init_module_attrs(self):
self.assertIs(loaded.__loader__, self.spec.loader)
self.assertEqual(loaded.__package__, self.spec.parent)
self.assertIs(loaded.__spec__, self.spec)
- self.assertFalse(hasattr(loaded, '__path__'))
- self.assertFalse(hasattr(loaded, '__file__'))
- self.assertFalse(hasattr(loaded, '__cached__'))
+ self.assertNotHasAttr(loaded, '__path__')
+ self.assertNotHasAttr(loaded, '__file__')
+ self.assertNotHasAttr(loaded, '__cached__')
(Frozen_ModuleSpecMethodsTests,
diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py
index 0bdd1b4b82e544..6332548291987b 100644
--- a/Lib/test/test_importlib/test_util.py
+++ b/Lib/test/test_importlib/test_util.py
@@ -321,7 +321,7 @@ def test_length(self):
def test_incorporates_rn(self):
# The magic number uses \r\n to come out wrong when splitting on lines.
- self.assertTrue(self.util.MAGIC_NUMBER.endswith(b'\r\n'))
+ self.assertEndsWith(self.util.MAGIC_NUMBER, b'\r\n')
(Frozen_MagicNumberTests,
1
0
https://github.com/python/cpython/commit/da310d209a6af45e5e1ea48a873f1d14be…
commit: da310d209a6af45e5e1ea48a873f1d14be471a09
branch: main
author: smelnikov <13030121+smelnikov(a)users.noreply.github.com>
committer: encukou <encukou(a)gmail.com>
date: 2025-01-21T10:22:13+01:00
summary:
Docs: fix typo in `Doc/howto/mro.rst` (GH-129095)
files:
M Doc/howto/mro.rst
diff --git a/Doc/howto/mro.rst b/Doc/howto/mro.rst
index 46db516e16dae4..0872bedcd3a2d3 100644
--- a/Doc/howto/mro.rst
+++ b/Doc/howto/mro.rst
@@ -398,7 +398,7 @@ with inheritance diagram
We see that class G inherits from F and E, with F *before* E: therefore
we would expect the attribute *G.remember2buy* to be inherited by
-*F.rembermer2buy* and not by *E.remember2buy*: nevertheless Python 2.2
+*F.remember2buy* and not by *E.remember2buy*: nevertheless Python 2.2
gives
>>> G.remember2buy # doctest: +SKIP
1
0
gh-128595: Fix `test__colorize` unexpected keyword argument 'file' on buildbots (#129070)
by hugovk Jan. 21, 2025
by hugovk Jan. 21, 2025
Jan. 21, 2025
https://github.com/python/cpython/commit/417f7a9ef754eae6d19be23279e9a9a948…
commit: 417f7a9ef754eae6d19be23279e9a9a948f6fecb
branch: main
author: Hugo van Kemenade <1324225+hugovk(a)users.noreply.github.com>
committer: hugovk <1324225+hugovk(a)users.noreply.github.com>
date: 2025-01-21T11:20:54+02:00
summary:
gh-128595: Fix `test__colorize` unexpected keyword argument 'file' on buildbots (#129070)
files:
M Lib/test/test__colorize.py
diff --git a/Lib/test/test__colorize.py b/Lib/test/test__colorize.py
index 1871775fa205a2..77e74fa3e23c2c 100644
--- a/Lib/test/test__colorize.py
+++ b/Lib/test/test__colorize.py
@@ -9,7 +9,7 @@
def setUpModule():
- _colorize.can_colorize = lambda: False
+ _colorize.can_colorize = lambda *args, **kwargs: False
def tearDownModule():
@@ -21,6 +21,7 @@ class TestColorizeFunction(unittest.TestCase):
def test_colorized_detection_checks_for_environment_variables(self):
flags = unittest.mock.MagicMock(ignore_environment=False)
with (unittest.mock.patch("os.isatty") as isatty_mock,
+ unittest.mock.patch("sys.stdout") as stdout_mock,
unittest.mock.patch("sys.stderr") as stderr_mock,
unittest.mock.patch("sys.flags", flags),
unittest.mock.patch("_colorize.can_colorize", ORIGINAL_CAN_COLORIZE),
@@ -29,6 +30,8 @@ def test_colorized_detection_checks_for_environment_variables(self):
contextlib.nullcontext()) as vt_mock):
isatty_mock.return_value = True
+ stdout_mock.fileno.return_value = 1
+ stdout_mock.isatty.return_value = True
stderr_mock.fileno.return_value = 2
stderr_mock.isatty.return_value = True
with unittest.mock.patch("os.environ", {'TERM': 'dumb'}):
@@ -61,6 +64,7 @@ def test_colorized_detection_checks_for_environment_variables(self):
self.assertEqual(_colorize.can_colorize(), True)
isatty_mock.return_value = False
+ stdout_mock.isatty.return_value = False
stderr_mock.isatty.return_value = False
self.assertEqual(_colorize.can_colorize(), False)
1
0
[3.12] gh-128588: fix refcycles in eager task creation and remove eager tasks optimization that missed and introduced incorrect cancellations (#129063) (#128586)
by kumaraditya303 Jan. 21, 2025
by kumaraditya303 Jan. 21, 2025
Jan. 21, 2025
https://github.com/python/cpython/commit/bc214545f9f6b0288330dd8afe0a33a280…
commit: bc214545f9f6b0288330dd8afe0a33a280406df5
branch: 3.12
author: Thomas Grainger <tagrain(a)gmail.com>
committer: kumaraditya303 <kumaraditya(a)python.org>
date: 2025-01-21T11:11:39+05:30
summary:
[3.12] gh-128588: fix refcycles in eager task creation and remove eager tasks optimization that missed and introduced incorrect cancellations (#129063) (#128586)
Co-authored-by: Kumar Aditya <kumaraditya(a)python.org>
files:
A Misc/NEWS.d/next/Library/2025-01-06-18-41-08.gh-issue-128552.fV-f8j.rst
A Misc/NEWS.d/next/Library/2025-01-20-13-12-39.gh-issue-128550.AJ5TOL.rst
M Lib/asyncio/base_events.py
M Lib/asyncio/taskgroups.py
M Lib/test/test_asyncio/test_taskgroups.py
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
index 9cbed30449688e..136c1631822427 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -466,7 +466,12 @@ def create_task(self, coro, *, name=None, context=None):
tasks._set_task_name(task, name)
- return task
+ try:
+ return task
+ finally:
+ # gh-128552: prevent a refcycle of
+ # task.exception().__traceback__->BaseEventLoop.create_task->task
+ del task
def set_task_factory(self, factory):
"""Set a task factory that will be used by loop.create_task().
diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py
index aada3ffa8e0f29..b2b953b09385ce 100644
--- a/Lib/asyncio/taskgroups.py
+++ b/Lib/asyncio/taskgroups.py
@@ -185,15 +185,20 @@ def create_task(self, coro, *, name=None, context=None):
else:
task = self._loop.create_task(coro, context=context)
tasks._set_task_name(task, name)
- # optimization: Immediately call the done callback if the task is
+
+ # Always schedule the done callback even if the task is
# already done (e.g. if the coro was able to complete eagerly),
- # and skip scheduling a done callback
- if task.done():
- self._on_task_done(task)
- else:
- self._tasks.add(task)
- task.add_done_callback(self._on_task_done)
- return task
+ # otherwise if the task completes with an exception then it will cancel
+ # the current task too early. gh-128550, gh-128588
+
+ self._tasks.add(task)
+ task.add_done_callback(self._on_task_done)
+ try:
+ return task
+ finally:
+ # gh-128552: prevent a refcycle of
+ # task.exception().__traceback__->TaskGroup.create_task->task
+ del task
# Since Python 3.8 Tasks propagate all exceptions correctly,
# except for KeyboardInterrupt and SystemExit which are
diff --git a/Lib/test/test_asyncio/test_taskgroups.py b/Lib/test/test_asyncio/test_taskgroups.py
index 236bfaaccf88fa..75efa912108281 100644
--- a/Lib/test/test_asyncio/test_taskgroups.py
+++ b/Lib/test/test_asyncio/test_taskgroups.py
@@ -1,6 +1,8 @@
# Adapted with permission from the EdgeDB project;
# license: PSFL.
+import weakref
+import sys
import gc
import asyncio
import contextvars
@@ -27,7 +29,25 @@ def get_error_types(eg):
return {type(exc) for exc in eg.exceptions}
-class TestTaskGroup(unittest.IsolatedAsyncioTestCase):
+def set_gc_state(enabled):
+ was_enabled = gc.isenabled()
+ if enabled:
+ gc.enable()
+ else:
+ gc.disable()
+ return was_enabled
+
+
+(a)contextlib.contextmanager
+def disable_gc():
+ was_enabled = set_gc_state(enabled=False)
+ try:
+ yield
+ finally:
+ set_gc_state(enabled=was_enabled)
+
+
+class BaseTestTaskGroup:
async def test_taskgroup_01(self):
@@ -880,6 +900,30 @@ async def coro_fn():
self.assertIsInstance(exc, _Done)
self.assertListEqual(gc.get_referrers(exc), [])
+
+ async def test_exception_refcycles_parent_task_wr(self):
+ """Test that TaskGroup deletes self._parent_task and create_task() deletes task"""
+ tg = asyncio.TaskGroup()
+ exc = None
+
+ class _Done(Exception):
+ pass
+
+ async def coro_fn():
+ async with tg:
+ raise _Done
+
+ with disable_gc():
+ try:
+ async with asyncio.TaskGroup() as tg2:
+ task_wr = weakref.ref(tg2.create_task(coro_fn()))
+ except* _Done as excs:
+ exc = excs.exceptions[0].exceptions[0]
+
+ self.assertIsNone(task_wr())
+ self.assertIsInstance(exc, _Done)
+ self.assertListEqual(gc.get_referrers(exc), [])
+
async def test_exception_refcycles_propagate_cancellation_error(self):
"""Test that TaskGroup deletes propagate_cancellation_error"""
tg = asyncio.TaskGroup()
@@ -912,6 +956,81 @@ class MyKeyboardInterrupt(KeyboardInterrupt):
self.assertIsNotNone(exc)
self.assertListEqual(gc.get_referrers(exc), [])
+ async def test_cancels_task_if_created_during_creation(self):
+ # regression test for gh-128550
+ ran = False
+ class MyError(Exception):
+ pass
+
+ exc = None
+ try:
+ async with asyncio.TaskGroup() as tg:
+ async def third_task():
+ raise MyError("third task failed")
+
+ async def second_task():
+ nonlocal ran
+ tg.create_task(third_task())
+ with self.assertRaises(asyncio.CancelledError):
+ await asyncio.sleep(0) # eager tasks cancel here
+ await asyncio.sleep(0) # lazy tasks cancel here
+ ran = True
+
+ tg.create_task(second_task())
+ except* MyError as excs:
+ exc = excs.exceptions[0]
+
+ self.assertTrue(ran)
+ self.assertIsInstance(exc, MyError)
+
+ async def test_cancellation_does_not_leak_out_of_tg(self):
+ class MyError(Exception):
+ pass
+
+ async def throw_error():
+ raise MyError
+
+ try:
+ async with asyncio.TaskGroup() as tg:
+ tg.create_task(throw_error())
+ except* MyError:
+ pass
+ else:
+ self.fail("should have raised one MyError in group")
+
+ # if this test fails this current task will be cancelled
+ # outside the task group and inside unittest internals
+ # we yield to the event loop with sleep(0) so that
+ # cancellation happens here and error is more understandable
+ await asyncio.sleep(0)
+
+
+if sys.platform == "win32":
+ EventLoop = asyncio.ProactorEventLoop
+else:
+ EventLoop = asyncio.SelectorEventLoop
+
+
+class IsolatedAsyncioTestCase(unittest.IsolatedAsyncioTestCase):
+ loop_factory = None
+
+ def _setupAsyncioRunner(self):
+ assert self._asyncioRunner is None, 'asyncio runner is already initialized'
+ runner = asyncio.Runner(debug=True, loop_factory=self.loop_factory)
+ self._asyncioRunner = runner
+
+
+class TestTaskGroup(BaseTestTaskGroup, IsolatedAsyncioTestCase):
+ loop_factory = EventLoop
+
+
+class TestEagerTaskTaskGroup(BaseTestTaskGroup, IsolatedAsyncioTestCase):
+ @staticmethod
+ def loop_factory():
+ loop = EventLoop()
+ loop.set_task_factory(asyncio.eager_task_factory)
+ return loop
+
if __name__ == "__main__":
unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2025-01-06-18-41-08.gh-issue-128552.fV-f8j.rst b/Misc/NEWS.d/next/Library/2025-01-06-18-41-08.gh-issue-128552.fV-f8j.rst
new file mode 100644
index 00000000000000..83816f775da9c5
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-01-06-18-41-08.gh-issue-128552.fV-f8j.rst
@@ -0,0 +1 @@
+Fix cyclic garbage introduced by :meth:`asyncio.loop.create_task` and :meth:`asyncio.TaskGroup.create_task` holding a reference to the created task if it is eager.
diff --git a/Misc/NEWS.d/next/Library/2025-01-20-13-12-39.gh-issue-128550.AJ5TOL.rst b/Misc/NEWS.d/next/Library/2025-01-20-13-12-39.gh-issue-128550.AJ5TOL.rst
new file mode 100644
index 00000000000000..f59feac795ea18
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-01-20-13-12-39.gh-issue-128550.AJ5TOL.rst
@@ -0,0 +1 @@
+Removed an incorrect optimization relating to eager tasks in :class:`asyncio.TaskGroup` that resulted in cancellations being missed.
1
0