Skip to content

Commit

Permalink
fix: adapt to a 3.13 change in frame.f_lasti
Browse files Browse the repository at this point in the history
During a yield from a generator, frame.f_lasti used to point to the
YIELD bytecode.  In 3.13, it now points to the RESUME that follows it.
python/cpython#113728
  • Loading branch information
nedbat committed Jan 12, 2024
1 parent f9e202a commit 159f67f
Show file tree
Hide file tree
Showing 4 changed files with 21 additions and 4 deletions.
5 changes: 4 additions & 1 deletion coverage/ctracer/tracer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions coverage/ctracer/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions coverage/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
10 changes: 7 additions & 3 deletions coverage/pytracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 159f67f

Please sign in to comment.