""" The code in this module is a backport of cPython changes in Pdb that were introduced in Python 3.13 by gh-83151: Make closure work on pdb https://github.com/python/cpython/pull/111094. This file should be removed once IPython drops supports for Python 3.12. The only changes are: - reformatting by darker (black) formatter - addition of type-ignore comments to satisfy mypy Copyright (c) 2001 Python Software Foundation; All Rights Reserved PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 -------------------------------------------- 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001 Python Software Foundation; All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python. 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License Agreement. """ import sys import types import codeop import textwrap from types import CodeType class PdbClosureBackport: def _exec_in_closure(self, source, globals, locals): # type: ignore[no-untyped-def] """Run source code in closure so code object created within source can find variables in locals correctly returns True if the source is executed, False otherwise """ # Determine if the source should be executed in closure. Only when the # source compiled to multiple code objects, we should use this feature. # Otherwise, we can just raise an exception and normal exec will be used. code = compile(source, "", "exec") if not any(isinstance(const, CodeType) for const in code.co_consts): return False # locals could be a proxy which does not support pop # copy it first to avoid modifying the original locals locals_copy = dict(locals) locals_copy["__pdb_eval__"] = {"result": None, "write_back": {}} # If the source is an expression, we need to print its value try: compile(source, "", "eval") except SyntaxError: pass else: source = "__pdb_eval__['result'] = " + source # Add write-back to update the locals source = ( "try:\n" + textwrap.indent(source, " ") + "\n" + "finally:\n" + " __pdb_eval__['write_back'] = locals()" ) # Build a closure source code with freevars from locals like: # def __pdb_outer(): # var = None # def __pdb_scope(): # This is the code object we want to execute # nonlocal var # # return __pdb_scope.__code__ source_with_closure = ( "def __pdb_outer():\n" + "\n".join(f" {var} = None" for var in locals_copy) + "\n" + " def __pdb_scope():\n" + "\n".join(f" nonlocal {var}" for var in locals_copy) + "\n" + textwrap.indent(source, " ") + "\n" + " return __pdb_scope.__code__" ) # Get the code object of __pdb_scope() # The exec fills locals_copy with the __pdb_outer() function and we can call # that to get the code object of __pdb_scope() ns = {} try: exec(source_with_closure, {}, ns) except Exception: return False code = ns["__pdb_outer"]() cells = tuple(types.CellType(locals_copy.get(var)) for var in code.co_freevars) try: exec(code, globals, locals_copy, closure=cells) except Exception: return False # get the data we need from the statement pdb_eval = locals_copy["__pdb_eval__"] # __pdb_eval__ should not be updated back to locals pdb_eval["write_back"].pop("__pdb_eval__") # Write all local variables back to locals locals.update(pdb_eval["write_back"]) eval_result = pdb_eval["result"] if eval_result is not None: print(repr(eval_result)) return True def default(self, line): # type: ignore[no-untyped-def] if line[:1] == "!": line = line[1:].strip() locals = self.curframe_locals globals = self.curframe.f_globals try: buffer = line if ( code := codeop.compile_command(line + "\n", "", "single") ) is None: # Multi-line mode with self._disable_command_completion(): buffer = line continue_prompt = "... " while ( code := codeop.compile_command(buffer, "", "single") ) is None: if self.use_rawinput: try: line = input(continue_prompt) except (EOFError, KeyboardInterrupt): self.lastcmd = "" print("\n") return else: self.stdout.write(continue_prompt) self.stdout.flush() line = self.stdin.readline() if not len(line): self.lastcmd = "" self.stdout.write("\n") self.stdout.flush() return else: line = line.rstrip("\r\n") buffer += "\n" + line save_stdout = sys.stdout save_stdin = sys.stdin save_displayhook = sys.displayhook try: sys.stdin = self.stdin sys.stdout = self.stdout sys.displayhook = self.displayhook if not self._exec_in_closure(buffer, globals, locals): exec(code, globals, locals) finally: sys.stdout = save_stdout sys.stdin = save_stdin sys.displayhook = save_displayhook except: self._error_exc()