|
| 1 | +""" |
| 2 | +The code in this module is a backport of cPython changes in Pdb |
| 3 | +that were introduced in Python 3.13 by gh-83151: Make closure work on pdb |
| 4 | +https://github.com/python/cpython/pull/111094. |
| 5 | +This file should be removed once IPython drops supports for Python 3.12. |
| 6 | +
|
| 7 | +The only changes are: |
| 8 | +- reformatting by darker (black) formatter |
| 9 | +- addition of type-ignore comments to satisfy mypy |
| 10 | +
|
| 11 | +Copyright (c) 2001 Python Software Foundation; All Rights Reserved |
| 12 | +
|
| 13 | +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 |
| 14 | +-------------------------------------------- |
| 15 | +
|
| 16 | +1. This LICENSE AGREEMENT is between the Python Software Foundation |
| 17 | +("PSF"), and the Individual or Organization ("Licensee") accessing and |
| 18 | +otherwise using this software ("Python") in source or binary form and |
| 19 | +its associated documentation. |
| 20 | +
|
| 21 | +2. Subject to the terms and conditions of this License Agreement, PSF hereby |
| 22 | +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, |
| 23 | +analyze, test, perform and/or display publicly, prepare derivative works, |
| 24 | +distribute, and otherwise use Python alone or in any derivative version, |
| 25 | +provided, however, that PSF's License Agreement and PSF's notice of copyright, |
| 26 | +i.e., "Copyright (c) 2001 Python Software Foundation; All Rights Reserved" |
| 27 | +are retained in Python alone or in any derivative version prepared by Licensee. |
| 28 | +
|
| 29 | +3. In the event Licensee prepares a derivative work that is based on |
| 30 | +or incorporates Python or any part thereof, and wants to make |
| 31 | +the derivative work available to others as provided herein, then |
| 32 | +Licensee hereby agrees to include in any such work a brief summary of |
| 33 | +the changes made to Python. |
| 34 | +
|
| 35 | +4. PSF is making Python available to Licensee on an "AS IS" |
| 36 | +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR |
| 37 | +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND |
| 38 | +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS |
| 39 | +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT |
| 40 | +INFRINGE ANY THIRD PARTY RIGHTS. |
| 41 | +
|
| 42 | +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON |
| 43 | +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS |
| 44 | +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, |
| 45 | +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. |
| 46 | +
|
| 47 | +6. This License Agreement will automatically terminate upon a material |
| 48 | +breach of its terms and conditions. |
| 49 | +
|
| 50 | +7. Nothing in this License Agreement shall be deemed to create any |
| 51 | +relationship of agency, partnership, or joint venture between PSF and |
| 52 | +Licensee. This License Agreement does not grant permission to use PSF |
| 53 | +trademarks or trade name in a trademark sense to endorse or promote |
| 54 | +products or services of Licensee, or any third party. |
| 55 | +
|
| 56 | +8. By copying, installing or otherwise using Python, Licensee |
| 57 | +agrees to be bound by the terms and conditions of this License |
| 58 | +Agreement. |
| 59 | +""" |
| 60 | + |
| 61 | +import sys |
| 62 | +import types |
| 63 | +import codeop |
| 64 | +import textwrap |
| 65 | +from types import CodeType |
| 66 | + |
| 67 | + |
| 68 | +class PdbClosureBackport: |
| 69 | + def _exec_in_closure(self, source, globals, locals): # type: ignore[no-untyped-def] |
| 70 | + """Run source code in closure so code object created within source |
| 71 | + can find variables in locals correctly |
| 72 | + returns True if the source is executed, False otherwise |
| 73 | + """ |
| 74 | + |
| 75 | + # Determine if the source should be executed in closure. Only when the |
| 76 | + # source compiled to multiple code objects, we should use this feature. |
| 77 | + # Otherwise, we can just raise an exception and normal exec will be used. |
| 78 | + |
| 79 | + code = compile(source, "<string>", "exec") |
| 80 | + if not any(isinstance(const, CodeType) for const in code.co_consts): |
| 81 | + return False |
| 82 | + |
| 83 | + # locals could be a proxy which does not support pop |
| 84 | + # copy it first to avoid modifying the original locals |
| 85 | + locals_copy = dict(locals) |
| 86 | + |
| 87 | + locals_copy["__pdb_eval__"] = {"result": None, "write_back": {}} |
| 88 | + |
| 89 | + # If the source is an expression, we need to print its value |
| 90 | + try: |
| 91 | + compile(source, "<string>", "eval") |
| 92 | + except SyntaxError: |
| 93 | + pass |
| 94 | + else: |
| 95 | + source = "__pdb_eval__['result'] = " + source |
| 96 | + |
| 97 | + # Add write-back to update the locals |
| 98 | + source = ( |
| 99 | + "try:\n" |
| 100 | + + textwrap.indent(source, " ") |
| 101 | + + "\n" |
| 102 | + + "finally:\n" |
| 103 | + + " __pdb_eval__['write_back'] = locals()" |
| 104 | + ) |
| 105 | + |
| 106 | + # Build a closure source code with freevars from locals like: |
| 107 | + # def __pdb_outer(): |
| 108 | + # var = None |
| 109 | + # def __pdb_scope(): # This is the code object we want to execute |
| 110 | + # nonlocal var |
| 111 | + # <source> |
| 112 | + # return __pdb_scope.__code__ |
| 113 | + source_with_closure = ( |
| 114 | + "def __pdb_outer():\n" |
| 115 | + + "\n".join(f" {var} = None" for var in locals_copy) |
| 116 | + + "\n" |
| 117 | + + " def __pdb_scope():\n" |
| 118 | + + "\n".join(f" nonlocal {var}" for var in locals_copy) |
| 119 | + + "\n" |
| 120 | + + textwrap.indent(source, " ") |
| 121 | + + "\n" |
| 122 | + + " return __pdb_scope.__code__" |
| 123 | + ) |
| 124 | + |
| 125 | + # Get the code object of __pdb_scope() |
| 126 | + # The exec fills locals_copy with the __pdb_outer() function and we can call |
| 127 | + # that to get the code object of __pdb_scope() |
| 128 | + ns = {} |
| 129 | + try: |
| 130 | + exec(source_with_closure, {}, ns) |
| 131 | + except Exception: |
| 132 | + return False |
| 133 | + code = ns["__pdb_outer"]() |
| 134 | + |
| 135 | + cells = tuple(types.CellType(locals_copy.get(var)) for var in code.co_freevars) |
| 136 | + |
| 137 | + try: |
| 138 | + exec(code, globals, locals_copy, closure=cells) |
| 139 | + except Exception: |
| 140 | + return False |
| 141 | + |
| 142 | + # get the data we need from the statement |
| 143 | + pdb_eval = locals_copy["__pdb_eval__"] |
| 144 | + |
| 145 | + # __pdb_eval__ should not be updated back to locals |
| 146 | + pdb_eval["write_back"].pop("__pdb_eval__") |
| 147 | + |
| 148 | + # Write all local variables back to locals |
| 149 | + locals.update(pdb_eval["write_back"]) |
| 150 | + eval_result = pdb_eval["result"] |
| 151 | + if eval_result is not None: |
| 152 | + print(repr(eval_result)) |
| 153 | + |
| 154 | + return True |
| 155 | + |
| 156 | + def default(self, line): # type: ignore[no-untyped-def] |
| 157 | + if line[:1] == "!": |
| 158 | + line = line[1:].strip() |
| 159 | + locals = self.curframe_locals |
| 160 | + globals = self.curframe.f_globals |
| 161 | + try: |
| 162 | + buffer = line |
| 163 | + if ( |
| 164 | + code := codeop.compile_command(line + "\n", "<stdin>", "single") |
| 165 | + ) is None: |
| 166 | + # Multi-line mode |
| 167 | + with self._disable_command_completion(): |
| 168 | + buffer = line |
| 169 | + continue_prompt = "... " |
| 170 | + while ( |
| 171 | + code := codeop.compile_command(buffer, "<stdin>", "single") |
| 172 | + ) is None: |
| 173 | + if self.use_rawinput: |
| 174 | + try: |
| 175 | + line = input(continue_prompt) |
| 176 | + except (EOFError, KeyboardInterrupt): |
| 177 | + self.lastcmd = "" |
| 178 | + print("\n") |
| 179 | + return |
| 180 | + else: |
| 181 | + self.stdout.write(continue_prompt) |
| 182 | + self.stdout.flush() |
| 183 | + line = self.stdin.readline() |
| 184 | + if not len(line): |
| 185 | + self.lastcmd = "" |
| 186 | + self.stdout.write("\n") |
| 187 | + self.stdout.flush() |
| 188 | + return |
| 189 | + else: |
| 190 | + line = line.rstrip("\r\n") |
| 191 | + buffer += "\n" + line |
| 192 | + save_stdout = sys.stdout |
| 193 | + save_stdin = sys.stdin |
| 194 | + save_displayhook = sys.displayhook |
| 195 | + try: |
| 196 | + sys.stdin = self.stdin |
| 197 | + sys.stdout = self.stdout |
| 198 | + sys.displayhook = self.displayhook |
| 199 | + if not self._exec_in_closure(buffer, globals, locals): |
| 200 | + exec(code, globals, locals) |
| 201 | + finally: |
| 202 | + sys.stdout = save_stdout |
| 203 | + sys.stdin = save_stdin |
| 204 | + sys.displayhook = save_displayhook |
| 205 | + except: |
| 206 | + self._error_exc() |
0 commit comments