Skip to content

Commit 8a45947

Browse files
committed
python-ecosys/debugpy: Add local variable modification while debugging.
Signed-off-by: Jos Verlinde <jos_verlinde@hotmail.com> (cherry picked from commit 215300dad99c2dab2adbf8d48e7044508b17b3e9) Signed-off-by: Jos Verlinde <jos_verlinde@hotmail.com>
1 parent 2f8e9b8 commit 8a45947

2 files changed

Lines changed: 57 additions & 22 deletions

File tree

python-ecosys/debugpy/debugpy/common/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
CMD_STACK_TRACE = const("stackTrace")
3434
CMD_SCOPES = const("scopes")
3535
CMD_VARIABLES = const("variables")
36+
CMD_SET_VARIABLE = const("setVariable")
3637
CMD_EVALUATE = const("evaluate")
3738
CMD_DISCONNECT = const("disconnect")
3839
CMD_CONFIGURATION_DONE = const("configurationDone")

python-ecosys/debugpy/debugpy/server/pdb_adapter.py

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -721,45 +721,79 @@ def _lightweight_serialize(self, value):
721721
return f"<{type_name} object>"
722722

723723
def set_variable(self, variables_ref: int, name: str, value: str) -> dict[str, str | int]:
724-
"""Set a variable to a new value and return the updated variable info."""
724+
"""Set a variable to a new value and return the updated variable info.
725+
726+
This function can modify both global and local variables when using a MicroPython
727+
build with settrace and local variable modification support (sys._set_local_var).
728+
729+
For global variables: Works reliably on all MicroPython builds.
730+
For local variables: Requires MicroPython build with C-level local variable support.
731+
"""
725732
# Handle complex variable references (not supported for setting)
726733
if variables_ref >= VARREF_COMPLEX_BASE:
727734
raise Exception("Cannot set variables in complex object expansions")
728735

729736
frame_id = variables_ref // 1000
730737
scope_type = variables_ref % 1000
731738

732-
if frame_id not in self.variables_cache:
733-
raise Exception("Invalid frame reference")
734-
735-
frame = self.variables_cache[frame_id]
739+
# Only allow setting variables in the topmost frame (frame_id = 0)
740+
if frame_id != 0:
741+
raise Exception("Variable modification is only allowed in the topmost frame")
736742

737-
# Determine the variable dictionary to modify
738-
if scope_type == VARREF_LOCALS or scope_type == VARREF_LOCALS_SPECIAL:
739-
var_dict = frame.f_locals if hasattr(frame, "f_locals") else {}
740-
elif scope_type == VARREF_GLOBALS or scope_type == VARREF_GLOBALS_SPECIAL:
741-
var_dict = frame.f_globals if hasattr(frame, "f_globals") else {}
742-
else:
743-
raise Exception("Invalid scope reference")
743+
# Use the current frame for modification
744+
frame = self.current_frame
745+
if frame is None:
746+
raise Exception("No current frame available")
744747

745-
# Check if variable exists
746-
if name not in var_dict:
747-
raise Exception(f"Variable '{name}' not found in the specified scope")
748+
# Get the appropriate variable contexts
749+
globals_dict = frame.f_globals if hasattr(frame, "f_globals") else {}
750+
locals_dict = frame.f_locals if hasattr(frame, "f_locals") else {}
748751

749752
try:
750-
# Evaluate the new value in the context of the frame
751-
globals_dict = frame.f_globals if hasattr(frame, "f_globals") else {}
752-
locals_dict = frame.f_locals if hasattr(frame, "f_locals") else {}
753-
754-
# Try to evaluate the value as a Python expression
753+
# Try to evaluate the new value as a Python expression
755754
try:
756755
new_value = eval(value, globals_dict, locals_dict)
757756
except:
758757
# If evaluation fails, treat as string literal
759758
new_value = value
760759

761-
# Set the variable
762-
var_dict[name] = new_value
760+
if scope_type == VARREF_GLOBALS or scope_type == VARREF_GLOBALS_SPECIAL:
761+
# Check if variable exists in globals
762+
if name not in globals_dict:
763+
raise Exception(f"Global variable '{name}' not found")
764+
765+
# For global variables, direct assignment works reliably
766+
globals_dict[name] = new_value
767+
self._debug_print(f"[PDB] Successfully set global variable '{name}' = {new_value}")
768+
769+
elif scope_type == VARREF_LOCALS or scope_type == VARREF_LOCALS_SPECIAL:
770+
# Check if variable exists in locals
771+
if name not in locals_dict:
772+
raise Exception(f"Local variable '{name}' not found")
773+
774+
# Try to use the frame._set_local method to set local variables
775+
try:
776+
if hasattr(frame, '_set_local'):
777+
# Use the frame._set_local method (CPython-compatible API)
778+
frame._set_local(name, new_value)
779+
self._debug_print(f"[PDB] Successfully set local variable '{name}' = {new_value}")
780+
else:
781+
# Fallback error if the method is not available
782+
raise Exception(
783+
f"Cannot modify local variable '{name}'. "
784+
f"This MicroPython build doesn't support local variable modification. "
785+
f"Please use a MicroPython build with settrace and local variable support."
786+
)
787+
except Exception as inner_e:
788+
# If frame.set_local fails, provide detailed error
789+
raise Exception(
790+
f"Failed to modify local variable '{name}': {str(inner_e)}. "
791+
f"Local variables in MicroPython are stored in internal code_state->state[] slots. "
792+
f"Consider using global variables for reliable modification during debugging."
793+
)
794+
795+
else:
796+
raise Exception("Invalid scope reference")
763797

764798
# Return the updated variable info
765799
return self._get_variable_info(name, new_value)

0 commit comments

Comments
 (0)