Skip to content

Incorrect tracing of "if" inside async-for #93061

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
nedbat opened this issue May 21, 2022 · 1 comment
Closed

Incorrect tracing of "if" inside async-for #93061

nedbat opened this issue May 21, 2022 · 1 comment
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error

Comments

@nedbat
Copy link
Member

nedbat commented May 21, 2022

Tracing an if-statement in an async-for seems to trace statements that aren't executed. It was correct in 3.9, became wrong in 3.10, and remains wrong in 3.11.

import linecache, sys

def trace(frame, event, arg):
    if frame.f_code.co_filename == globals().get("__file__"):
        lineno = frame.f_lineno
        line = linecache.getline(__file__, lineno).rstrip()
        print("{} {}: {}".format(event[:4], lineno, line))
    return trace

print(sys.version)
sys.settrace(trace)

import asyncio

class CoverMe:
    def __init__(self, number):
        self._number = number

    async def _async_range(self):
        for n in range(self._number):
            yield n

    async def do_something(self):
        accumulated = 0
        async for n in self._async_range():
            accumulated += n
            print(f"{accumulated=}")
            if accumulated > 10:
                break

        return accumulated

async def main():
    print(await CoverMe(10).do_something())

asyncio.run(main())

Running this code with 3.11.0b1 gives:

3.11.0b1 (main, May 12 2022, 06:47:33) [Clang 12.0.0 (clang-1200.0.32.29)]
call 15: class CoverMe:
line 15: class CoverMe:
line 16:     def __init__(self, number):
line 19:     async def _async_range(self):
line 23:     async def do_something(self):
retu 23:     async def do_something(self):
call 33: async def main():
line 34:     print(await CoverMe(10).do_something())
call 16:     def __init__(self, number):
line 17:         self._number = number
retu 17:         self._number = number
call 23:     async def do_something(self):
line 24:         accumulated = 0
line 25:         async for n in self._async_range():
call 19:     async def _async_range(self):
line 20:         for n in range(self._number):
line 21:             yield n
retu 21:             yield n
exce 25:         async for n in self._async_range():
line 26:             accumulated += n
line 27:             print(f"{accumulated=}")
accumulated=0
line 28:             if accumulated > 10:
line 29:                 break                              <<<<
line 25:         async for n in self._async_range():
call 21:             yield n
line 20:         for n in range(self._number):
line 21:             yield n
retu 21:             yield n
exce 25:         async for n in self._async_range():
line 26:             accumulated += n
line 27:             print(f"{accumulated=}")
accumulated=1
line 28:             if accumulated > 10:
line 29:                 break                              <<<<
line 25:         async for n in self._async_range():
call 21:             yield n
line 20:         for n in range(self._number):
line 21:             yield n
retu 21:             yield n
exce 25:         async for n in self._async_range():
line 26:             accumulated += n
line 27:             print(f"{accumulated=}")
accumulated=3
line 28:             if accumulated > 10:
line 29:                 break                              <<<<
line 25:         async for n in self._async_range():
call 21:             yield n
line 20:         for n in range(self._number):
line 21:             yield n
retu 21:             yield n
exce 25:         async for n in self._async_range():
line 26:             accumulated += n
line 27:             print(f"{accumulated=}")
accumulated=6
line 28:             if accumulated > 10:
line 29:                 break                              <<<<
line 25:         async for n in self._async_range():
call 21:             yield n
line 20:         for n in range(self._number):
line 21:             yield n
retu 21:             yield n
exce 25:         async for n in self._async_range():
line 26:             accumulated += n
line 27:             print(f"{accumulated=}")
accumulated=10
line 28:             if accumulated > 10:
line 29:                 break                              <<<<
line 25:         async for n in self._async_range():
call 21:             yield n
line 20:         for n in range(self._number):
line 21:             yield n
retu 21:             yield n
exce 25:         async for n in self._async_range():
line 26:             accumulated += n
line 27:             print(f"{accumulated=}")
accumulated=15
line 28:             if accumulated > 10:
line 29:                 break
line 31:         return accumulated
retu 31:         return accumulated
exce 34:     print(await CoverMe(10).do_something())
15
retu 34:     print(await CoverMe(10).do_something())
call 21:             yield n
exce 21:             yield n
retu 21:             yield n

The lines marked with <<<< show a break statement being traced when it is not executed.

@sweeneyde
Copy link
Member

I think this should be fixed now in 3.10, 3.11, and main, but feel free to re-open if I've missed something.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

3 participants