diff --git a/coverage/ctracer/tracer.c b/coverage/ctracer/tracer.c index 8a9e0a5ed..a05e0f8ef 100644 --- a/coverage/ctracer/tracer.c +++ b/coverage/ctracer/tracer.c @@ -710,7 +710,10 @@ CTracer_handle_return(CTracer *self, PyFrameObject *frame) real_return = TRUE; } else { - real_return = (code_bytes[lasti + 2] != RESUME); +#if ENV_LASTI_IS_YIELD + lasti += 2; +#endif + real_return = (code_bytes[lasti] != RESUME); } #else /* Need to distinguish between RETURN_VALUE and YIELD_VALUE. Read diff --git a/coverage/ctracer/util.h b/coverage/ctracer/util.h index 1e99313dc..aaa0c98e5 100644 --- a/coverage/ctracer/util.h +++ b/coverage/ctracer/util.h @@ -59,6 +59,11 @@ #define MyCode_FreeCode(code) #endif +// Where does frame.f_lasti point when yielding from a generator? +// It used to point at the YIELD, now it points at the RESUME. +// https://github.com/python/cpython/issues/113728 +#define ENV_LASTI_IS_YIELD (PY_VERSION_HEX < 0x030D0000) + /* The values returned to indicate ok or error. */ #define RET_OK 0 #define RET_ERROR -1 diff --git a/coverage/env.py b/coverage/env.py index dd0146039..b6b9caca3 100644 --- a/coverage/env.py +++ b/coverage/env.py @@ -117,6 +117,11 @@ class PYBEHAVIOR: # PEP669 Low Impact Monitoring: https://peps.python.org/pep-0669/ pep669 = bool(getattr(sys, "monitoring", None)) + # Where does frame.f_lasti point when yielding from a generator? + # It used to point at the YIELD, now it points at the RESUME. + # https://github.com/python/cpython/issues/113728 + lasti_is_yield = (PYVERSION < (3, 13)) + # Coverage.py specifics, about testing scenarios. See tests/testenv.py also. diff --git a/coverage/pytracer.py b/coverage/pytracer.py index 9bdb9c331..f527a4040 100644 --- a/coverage/pytracer.py +++ b/coverage/pytracer.py @@ -21,6 +21,7 @@ ) # We need the YIELD_VALUE opcode below, in a comparison-friendly form. +# PYVERSIONS: RESUME is new in Python3.11 RESUME = dis.opmap.get("RESUME") RETURN_VALUE = dis.opmap["RETURN_VALUE"] if RESUME is None: @@ -137,7 +138,8 @@ def _trace( if THIS_FILE in frame.f_code.co_filename: return None - #self.log(":", frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name + "()", event) + # f = frame; code = f.f_code + # self.log(":", f"{code.co_filename} {f.f_lineno} {code.co_name}()", event) if (self.stopped and sys.gettrace() == self._cached_bound_method_trace): # pylint: disable=comparison-with-callable # The PyTrace.stop() method has been called, possibly by another @@ -255,8 +257,10 @@ def _trace( # A return from the end of a code object is a real return. real_return = True else: - # it's a real return. - real_return = (code[lasti + 2] != RESUME) + # It is a real return if we aren't going to resume next. + if env.PYBEHAVIOR.lasti_is_yield: + lasti += 2 + real_return = (code[lasti] != RESUME) else: if code[lasti] == RETURN_VALUE: real_return = True