diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_save_locals.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_save_locals.py index fa1a12520..c6bc37542 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_save_locals.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_save_locals.py @@ -70,6 +70,8 @@ def save_locals_ctypes_impl(frame): save_locals_impl = make_save_locals_impl() +_SENTINEL = [] # Any mutable will do. + def update_globals_and_locals(updated_globals, initial_globals, frame): # We don't have the locals and passed all in globals, so, we have to @@ -83,8 +85,11 @@ def update_globals_and_locals(updated_globals, initial_globals, frame): # one that enabled creating and using variables during the same evaluation. assert updated_globals is not None f_locals = None + + removed = set(initial_globals).difference(updated_globals) + for key, val in updated_globals.items(): - if initial_globals.get(key) is not val: + if val is not initial_globals.get(key, _SENTINEL): if f_locals is None: # Note: we call f_locals only once because each time # we call it the values may be reset. @@ -92,5 +97,17 @@ def update_globals_and_locals(updated_globals, initial_globals, frame): f_locals[key] = val + if removed: + if f_locals is None: + # Note: we call f_locals only once because each time + # we call it the values may be reset. + f_locals = frame.f_locals + + for key in removed: + try: + del f_locals[key] + except KeyError: + pass + if f_locals is not None: save_locals(frame) diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py index 2634714aa..cdbe3085f 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py @@ -523,6 +523,10 @@ def method(): updated_globals = {} updated_globals.update(frame.f_globals) updated_globals.update(frame.f_locals) + if 'globals' not in updated_globals: + # If the user explicitly uses 'globals()' then we provide the + # frame globals (unless he has shadowed it already). + updated_globals['globals'] = lambda: frame.f_globals initial_globals = updated_globals.copy() diff --git a/src/debugpy/_vendored/pydevd/tests_python/test_evaluate_expression.py b/src/debugpy/_vendored/pydevd/tests_python/test_evaluate_expression.py index adc4ba261..123fb6394 100644 --- a/src/debugpy/_vendored/pydevd/tests_python/test_evaluate_expression.py +++ b/src/debugpy/_vendored/pydevd/tests_python/test_evaluate_expression.py @@ -122,6 +122,47 @@ def check(frame): assert 'email' not in sys._getframe().f_globals +def test_evaluate_expression_access_globals(disable_critical_log): + from _pydevd_bundle.pydevd_vars import evaluate_expression + + def check(frame): + eval_txt = '''globals()['global_variable'] = 22''' + evaluate_expression(None, frame, eval_txt, is_exec=True) + assert 'global_variable' not in frame.f_locals + assert 'global_variable' in frame.f_globals + + check(next(iter(obtain_frame()))) + assert 'global_variable' in sys._getframe().f_globals + assert 'global_variable' not in sys._getframe().f_locals + + +def test_evaluate_expression_create_none(disable_critical_log): + from _pydevd_bundle.pydevd_vars import evaluate_expression + + def check(frame): + eval_txt = 'x = None' + evaluate_expression(None, frame, eval_txt, is_exec=True) + assert 'x' in frame.f_locals + assert 'x' not in frame.f_globals + + check(next(iter(obtain_frame()))) + + +def test_evaluate_expression_delete_var(disable_critical_log): + from _pydevd_bundle.pydevd_vars import evaluate_expression + + def check(frame): + eval_txt = 'x = 22' + evaluate_expression(None, frame, eval_txt, is_exec=True) + assert 'x' in frame.f_locals + + eval_txt = 'del x' + evaluate_expression(None, frame, eval_txt, is_exec=True) + assert 'x' not in frame.f_locals + + check(next(iter(obtain_frame()))) + + def test_evaluate_expression_5(disable_critical_log): from _pydevd_bundle.pydevd_vars import evaluate_expression