Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Update the code for pep 667
  • Loading branch information
gaogaotiantian committed May 5, 2024
commit fe5a6f6ae6c4f0ba91b409ecdcbaf78d285f70e5
109 changes: 68 additions & 41 deletions Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,58 +627,87 @@ def _disable_command_completion(self):
def _exec_in_closure(self, source, globals, locals):
""" 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, "<string>", "exec")
for const in code.co_consts:
if isinstance(const, CodeType):
break
else:
return False
Comment thread
gaogaotiantian marked this conversation as resolved.
Outdated

# 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": {}
}
Comment thread
brandtbucher marked this conversation as resolved.

# If the source is an expression, we need to print its value
try:
compile(source, "<string>", "eval")
source = "__pdb_eval_result__ = " + source
locals["__pdb_eval_result__"] = None
except SyntaxError:
pass
Comment thread
brandtbucher marked this conversation as resolved.
else:
source = "__pdb_eval__['result'] = " + source

# Add write-back to update the locals
locals["__pdb_write_back__"] = {}
source = ("try:\n" +
textwrap.indent(source, " ") + "\n" +
"finally:\n" +
" for key, val in locals().items():\n __pdb_write_back__[key] = val")
" __pdb_eval__['write_back'].update(locals())")
Comment thread
gaogaotiantian marked this conversation as resolved.
Outdated

local_vars = list(locals_copy.keys())
Comment thread
gaogaotiantian marked this conversation as resolved.
Outdated

# Build a closure source code with freevars from locals like:
# def outer():
Comment thread
gaogaotiantian marked this conversation as resolved.
Outdated
# var = None
# def __pdb_scope(): # This is the code object we want to execute
# nonlocal var
# <source>
# return __pdb_scope.__code__
source_with_closure = ("def __pdb_outer():\n" +
"\n".join(f" {var} = None" for var in local_vars) + "\n" +
" def __pdb_scope():\n" +
"\n".join(f" nonlocal {var}" for var in local_vars) + "\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()
code = exec(source_with_closure, {}, locals_copy)
Comment thread
gaogaotiantian marked this conversation as resolved.
Outdated
code = locals_copy.pop("__pdb_outer")()

cells = tuple(types.CellType(locals_copy.get(var)) for var in code.co_freevars)

try:
local_vars = list(locals.keys())

# Build a closure source code with freevars from locals like:
# def outer():
# var = None
# def __pdb_scope(): # This is the code object we want to execute
# nonlocal var
# <source>
source_with_closure = ("def outer():\n" +
"\n".join(f" {var} = None" for var in local_vars) + "\n" +
" def __pdb_scope():\n" +
"\n".join(f" nonlocal {var}" for var in local_vars) + "\n" +
textwrap.indent(source, " ")
)

# Compile the instrumented source code, and get the code object of __pdb_scope()
# This simulates executing the original source code with locals as cellvars
# co_consts[0] -> outer;
# outer.co_consts[1] -> __pdb_scope because outer.co_consts[0] is None
code = compile(source_with_closure, "<string>", "exec").co_consts[0].co_consts[1]
cells = tuple(types.CellType(locals.get(var)) for var in code.co_freevars)
exec(code, globals, locals, closure=cells)

# Write all local variables back to locals
for var, value in locals["__pdb_write_back__"].items():
locals[var] = value
locals.pop("__pdb_write_back__", None)

if (ret := locals.get("__pdb_eval_result__")) is not None:
locals.pop("__pdb_eval_result__", None)
print(repr(ret))
finally:
locals.pop("__pdb_eval_result__", None)
locals.pop("__pdb_write_back__", None)
exec(code, globals, locals_copy, closure=cells)
except Exception:
return False
Comment thread
brandtbucher marked this conversation as resolved.

# get the data we need from the statement
pdb_eval = locals_copy.pop("__pdb_eval__")
Comment thread
gaogaotiantian marked this conversation as resolved.
Outdated

# __pdb_eval__ should not be updated back to locals
pdb_eval["write_back"].pop("__pdb_eval__")
Comment thread
brandtbucher marked this conversation as resolved.

# 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):
if line[:1] == '!': line = line[1:].strip()
Expand Down Expand Up @@ -718,9 +747,7 @@ def default(self, line):
sys.stdin = self.stdin
sys.stdout = self.stdout
sys.displayhook = self.displayhook
try:
self._exec_in_closure(buffer, globals, locals)
except Exception:
if not self._exec_in_closure(buffer, globals, locals):
exec(code, globals, locals)
finally:
sys.stdout = save_stdout
Expand Down
1 change: 0 additions & 1 deletion Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@ framelocalsproxy_setitem(PyObject *self, PyObject *key, PyObject *value)
} else if (value != oldvalue) {
Py_XSETREF(fast[i], Py_NewRef(value));
}
Py_XDECREF(value);
return 0;
}
}
Expand Down