From a60e4beefa665952948a462b6ba852fbeae7231f Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 23 Feb 2022 19:20:35 +0100 Subject: [PATCH 1/2] build: MyFrame_lasti() uses PyObject_GetAttrString() Reimplement the MyFrame_lasti() function using PyObject_GetAttrString(frame, "f_lasti") to avoid using the internal C API on Python 3.11. Add assertions in CTracer_handle_call() and CTracer_handle_return() to ensure that f_lasti is the in expected bounds. --- coverage/ctracer/tracer.c | 12 ++++++++---- coverage/ctracer/util.h | 28 +++++++++++++++------------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/coverage/ctracer/tracer.c b/coverage/ctracer/tracer.c index fc88ef85f..68cce5a6a 100644 --- a/coverage/ctracer/tracer.c +++ b/coverage/ctracer/tracer.c @@ -525,16 +525,18 @@ CTracer_handle_call(CTracer *self, PyFrameObject *frame) */ BOOL real_call = FALSE; + Py_ssize_t lasti = MyFrame_lasti(frame); #ifdef RESUME // 3.11.0a4 /* * The current opcode is guaranteed to be RESUME. The argument * determines what kind of resume it is. */ PyObject * pCode = MyFrame_GetCode(frame)->co_code; - real_call = (PyBytes_AS_STRING(pCode)[MyFrame_lasti(frame) + 1] == 0); + assert(0 <= lasti >= 0 && (lasti + 1) < PyBytes_GET_SIZE(pCode)); + real_call = (PyBytes_AS_STRING(pCode)[lasti + 1] == 0); #else // f_lasti is -1 for a true call, and a real byte offset for a generator re-entry. - real_call = (MyFrame_lasti(frame) < 0); + real_call = (lasti < 0); #endif if (real_call) { @@ -699,11 +701,12 @@ CTracer_handle_return(CTracer *self, PyFrameObject *frame) if (self->tracing_arcs && self->pcur_entry->file_data) { BOOL real_return = FALSE; PyObject * pCode = MyFrame_GetCode(frame)->co_code; - int lasti = MyFrame_lasti(frame); + Py_ssize_t lasti = MyFrame_lasti(frame); Py_ssize_t code_size = PyBytes_GET_SIZE(pCode); unsigned char * code_bytes = (unsigned char *)PyBytes_AS_STRING(pCode); #ifdef RESUME - if (lasti == code_size - 2) { + assert(0 <= lasti); + if ((lasti + 2) == code_size) { real_return = TRUE; } else { @@ -718,6 +721,7 @@ CTracer_handle_return(CTracer *self, PyFrameObject *frame) BOOL is_yield = FALSE; BOOL is_yield_from = FALSE; + assert(0 <= lasti); if (lasti < code_size) { is_yield = (code_bytes[lasti] == YIELD_VALUE); if (lasti + 2 < code_size) { diff --git a/coverage/ctracer/util.h b/coverage/ctracer/util.h index 58fa1d490..dfa7752f8 100644 --- a/coverage/ctracer/util.h +++ b/coverage/ctracer/util.h @@ -12,19 +12,21 @@ #undef COLLECT_STATS /* Collect counters: stats are printed when tracer is stopped. */ #undef DO_NOTHING /* Define this to make the tracer do nothing. */ -#if PY_VERSION_HEX >= 0x030B00A0 -// 3.11 moved f_lasti into an internal structure. This is totally the wrong way -// to make this work, but it's all I've got until https://bugs.python.org/issue40421 -// is resolved. -#include -#define MyFrame_lasti(f) ((f)->f_frame->f_lasti * 2) -#elif PY_VERSION_HEX >= 0x030A00A7 -// The f_lasti field changed meaning in 3.10.0a7. It had been bytes, but -// now is instructions, so we need to adjust it to use it as a byte index. -#define MyFrame_lasti(f) ((f)->f_lasti * 2) -#else -#define MyFrame_lasti(f) ((f)->f_lasti) -#endif +static inline Py_ssize_t MyFrame_lasti(PyFrameObject *frame) +{ + PyObject *obj = PyObject_GetAttrString((PyObject*)frame, "f_lasti"); + if (obj == NULL) { + PyErr_Clear(); + return -1; + } + // f_lasti is an int, but using Py_ssize_t is more convenient in coverage + Py_ssize_t lasti = PyLong_AsSsize_t(obj); + if (lasti == -1 && PyErr_Occurred()) { + PyErr_Clear(); + return -1; + } + return lasti; +} // Access f_code should be done through a helper starting in 3.9. #if PY_VERSION_HEX >= 0x03090000 From 609448156000b3d83bdc820691b893e5b8ddaf0c Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 23 Feb 2022 19:32:32 +0100 Subject: [PATCH 2/2] Fix Python 3.8 support --- coverage/ctracer/util.h | 1 + 1 file changed, 1 insertion(+) diff --git a/coverage/ctracer/util.h b/coverage/ctracer/util.h index dfa7752f8..56815747f 100644 --- a/coverage/ctracer/util.h +++ b/coverage/ctracer/util.h @@ -5,6 +5,7 @@ #define _COVERAGE_UTIL_H #include +#include "frameobject.h" // PyFrameObject /* Compile-time debugging helpers */ #undef WHAT_LOG /* Define to log the WHAT params in the trace function. */