diff --git a/plugins/org.python.pydev.core/pysrc/_pydevd_sys_monitoring/pydevd_sys_monitoring.py b/plugins/org.python.pydev.core/pysrc/_pydevd_sys_monitoring/pydevd_sys_monitoring.py index b6dbd68678..178a27ca95 100644 --- a/plugins/org.python.pydev.core/pysrc/_pydevd_sys_monitoring/pydevd_sys_monitoring.py +++ b/plugins/org.python.pydev.core/pysrc/_pydevd_sys_monitoring/pydevd_sys_monitoring.py @@ -350,6 +350,10 @@ def _enable_code_tracing(thread, code, frame): elif step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE): if _is_same_frame(info, info.pydev_step_stop, frame): _enable_line_tracing(code) + + # Wee need to enable return tracing because if we have a return during a step over + # we need to stop too. + _enable_return_tracing(code) elif py_db.show_return_values and _is_same_frame(info, info.pydev_step_stop, frame.f_back): # Show return values on step over. _enable_return_tracing(code) @@ -388,10 +392,40 @@ def _return_event(code, instruction, retval): frame = sys._getframe(1) step_cmd = info.pydev_step_cmd - if step_cmd in (CMD_STEP_RETURN, CMD_STEP_RETURN_MY_CODE): + if step_cmd in (CMD_STEP_RETURN, CMD_STEP_RETURN_MY_CODE) and _is_same_frame(info, info.pydev_step_stop, frame): + _stop_on_return(py_db, thread_info, info, step_cmd, frame, retval) + return + + if step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE) and _is_same_frame(info, info.pydev_step_stop, frame): + # This isn't in the sys.settrace version: on a step over, if we return and the return is valid, show + # as a step return instead of going back to step into mode (but if the back frame is not valid, then + # go to step into mode). + f_back = frame.f_back + if f_back is not None: + func_code_info = get_func_code_info(f_back.f_code) + if func_code_info is not None and not func_code_info.always_skip_code: + if not func_code_info.filtered_out: + _stop_on_return(py_db, thread_info, info, step_cmd, frame, retval) + return + + if step_cmd in (CMD_STEP_OVER, CMD_STEP_RETURN, CMD_STEP_OVER_MY_CODE, CMD_STEP_RETURN_MY_CODE, CMD_SMART_STEP_INTO): + # If we are in single step mode and something causes us to exit the current frame, we need to make sure we break + # eventually. Force the step mode to step into and the step stop frame to None. + # I.e.: F6 in the end of a function should stop in the next possible position (instead of forcing the user + # to make a step in or step over at that location). + # Note: this is especially troublesome when we're skipping code with the + # @DontTrace comment. stop_frame = info.pydev_step_stop - if _is_same_frame(info, stop_frame, frame): - _stop_on_return(py_db, thread_info, info, step_cmd, frame, retval) + if stop_frame is frame and not info.pydev_use_scoped_step_frame: + if step_cmd in (CMD_STEP_OVER, CMD_STEP_RETURN, CMD_SMART_STEP_INTO): + info.pydev_step_cmd = CMD_STEP_INTO + else: + info.pydev_step_cmd = CMD_STEP_INTO_MY_CODE + info.pydev_step_stop = None + f = stop_frame.f_back + while f is not None: + enable_code_tracing(thread_info.thread, code, frame) + f = f.f_back def _stop_on_return(py_db, thread_info, info, step_cmd, frame, retval): diff --git a/plugins/org.python.pydev.core/pysrc/tests_python/test_debugger.py b/plugins/org.python.pydev.core/pysrc/tests_python/test_debugger.py index 6159080819..a58b90ab95 100644 --- a/plugins/org.python.pydev.core/pysrc/tests_python/test_debugger.py +++ b/plugins/org.python.pydev.core/pysrc/tests_python/test_debugger.py @@ -20,7 +20,7 @@ REASON_STEP_OVER_MY_CODE, REASON_STEP_INTO, CMD_THREAD_KILL, IS_PYPY, REASON_STOP_ON_START, CMD_SMART_STEP_INTO, CMD_GET_VARIABLE) from _pydevd_bundle.pydevd_constants import IS_WINDOWS, IS_PY38_OR_GREATER, \ - IS_MAC + IS_MAC, USE_SYS_MONITORING from _pydevd_bundle.pydevd_comm_constants import CMD_RELOAD_CODE, CMD_INPUT_REQUESTED, \ CMD_RUN_CUSTOM_OPERATION import json @@ -542,8 +542,16 @@ def test_case_11(case_setup): writer.write_step_over(hit.thread_id) - hit = writer.wait_for_breakpoint_hit(REASON_STEP_OVER, line=12) # Reverts to step in - assert hit.name == 'Method2' + if USE_SYS_MONITORING: + hit = writer.wait_for_breakpoint_hit(REASON_STEP_OVER, line=11) # Reverts to step return + assert hit.name == 'Method2' + + writer.write_step_over(hit.thread_id) + hit = writer.wait_for_breakpoint_hit(REASON_STEP_OVER, line=12) + assert hit.name == 'Method2' + else: + hit = writer.wait_for_breakpoint_hit(REASON_STEP_OVER, line=12) # Reverts to step in + assert hit.name == 'Method2' writer.write_step_over(hit.thread_id) @@ -551,8 +559,17 @@ def test_case_11(case_setup): assert hit.name == 'Method2' writer.write_step_over(hit.thread_id) - hit = writer.wait_for_breakpoint_hit(REASON_STEP_OVER, line=18) # Reverts to step in - assert hit.name == '' + + if USE_SYS_MONITORING: + hit = writer.wait_for_breakpoint_hit(REASON_STEP_OVER, line=17) # Reverts to step return + assert hit.name == '' + + writer.write_step_over(hit.thread_id) + hit = writer.wait_for_breakpoint_hit(REASON_STEP_OVER, line=18) + assert hit.name == '' + else: + hit = writer.wait_for_breakpoint_hit(REASON_STEP_OVER, line=18) # Reverts to step in + assert hit.name == '' # Finish with a step over writer.write_step_over(hit.thread_id) @@ -593,7 +610,7 @@ def test_case_12(case_setup): @pytest.mark.skipif(IS_IRONPYTHON, reason='Failing on IronPython (needs to be investigated).') -def test_case_13(case_setup): +def test_case_property_trace_enable_disable(case_setup): with case_setup.test_file('_debugger_case13.py') as writer: def _ignore_stderr_line(line):