From e9af01057305337cd8f7e6130a05ab7c005d6102 Mon Sep 17 00:00:00 2001 From: fitermay Date: Wed, 10 Jan 2018 12:27:14 -0500 Subject: [PATCH] Make set next statement work properly for frame eval debugger (PY-26101) --- .../pydev/_pydevd_bundle/pydevd_frame.py | 2 +- .../pydevd_frame_evaluator.pyx | 7 +++- .../pydevd_frame_tracing.py | 38 ++++++++++++++++--- python/helpers/pydev/pydevd.py | 29 +++++++------- 4 files changed, 51 insertions(+), 25 deletions(-) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py b/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py index 5df7448637d6..76e3d8f52082 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py @@ -560,7 +560,7 @@ class PyDBFrame: breakpoint = breakpoints_for_file[line] new_frame = frame stop = True - if step_cmd == CMD_STEP_OVER and stop_frame is frame and (is_line or is_return): + if (step_cmd == CMD_STEP_OVER or step_cmd == CMD_SET_NEXT_STATEMENT) and stop_frame is frame and (is_line or is_return): stop = False #we don't stop on breakpoint if we have to stop by step-over (it will be processed later) elif plugin_manager is not None and main_debugger.has_plugin_line_breaks: result = plugin_manager.get_breakpoint(main_debugger, self, frame, event, self._args) diff --git a/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_evaluator.pyx b/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_evaluator.pyx index 2bc0dcf01627..7894d855ec67 100644 --- a/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_evaluator.pyx +++ b/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_evaluator.pyx @@ -3,7 +3,7 @@ from _pydev_imps._pydev_saved_modules import threading from _pydevd_bundle.pydevd_additional_thread_info import PyDBAdditionalThreadInfo from _pydevd_bundle.pydevd_comm import get_global_debugger from _pydevd_bundle.pydevd_dont_trace_files import DONT_TRACE -from _pydevd_frame_eval.pydevd_frame_tracing import pydev_trace_code_wrapper, update_globals_dict, dummy_tracing_holder +from _pydevd_frame_eval.pydevd_frame_tracing import create_code_wrapper, update_globals_dict, dummy_tracing_holder from _pydevd_frame_eval.pydevd_modify_bytecode import insert_code from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame, NORM_PATHS_AND_BASE_CONTAINER @@ -107,13 +107,16 @@ cdef PyObject* get_bytecode_while_frame_eval(PyFrameObject *frame_obj, int exc): code_object = frame.f_code if breakpoints: breakpoints_to_update = [] + injected_code_size = 0 for offset, line in dis.findlinestarts(code_object): if line in breakpoints: breakpoint = breakpoints[line] if code_object not in breakpoint.code_objects: # This check is needed for generator functions, because after each yield the new frame is created # but the former code object is used - success, new_code = insert_code(frame.f_code, pydev_trace_code_wrapper.__code__, line) + injected_code = create_code_wrapper(offset + injected_code_size) + success, new_code = insert_code(frame.f_code, injected_code , line) + injected_code_size += len(injected_code.co_code) if success: breakpoints_to_update.append(breakpoint) Py_INCREF(new_code) diff --git a/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py b/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py index 62610b8cc160..40980f1c8819 100644 --- a/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py +++ b/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py @@ -1,12 +1,14 @@ +from types import CodeType + import sys from _pydev_bundle import pydev_log from _pydev_imps._pydev_saved_modules import threading -from _pydevd_bundle.pydevd_comm import get_global_debugger, CMD_SET_BREAK +from _pydevd_bundle.pydevd_comm import get_global_debugger, CMD_SET_BREAK, CMD_SET_NEXT_STATEMENT from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame, NORM_PATHS_AND_BASE_CONTAINER from _pydevd_bundle.pydevd_frame import handle_breakpoint_condition, handle_breakpoint_expression - +import opcode class DummyTracingHolder: dummy_trace_func = None @@ -54,7 +56,7 @@ def _pydev_stop_at_break(): frame = sys._getframe(1) t = threading.currentThread() if t.additional_info.is_tracing: - return + return False if t.additional_info.pydev_step_cmd == -1 and frame.f_trace in (None, dummy_tracing_holder.dummy_trace_func): # do not handle breakpoints while stepping, because they're handled by old tracing function @@ -74,16 +76,40 @@ def _pydev_stop_at_break(): except KeyError: pydev_log.debug("Couldn't find breakpoint in the file {} on line {}".format(frame.f_code.co_filename, line)) t.additional_info.is_tracing = False - return + return False if breakpoint and handle_breakpoint(frame, t, debugger, breakpoint): pydev_log.debug("Suspending at breakpoint in file: {} on line {}".format(frame.f_code.co_filename, line)) debugger.set_suspend(t, CMD_SET_BREAK) debugger.do_wait_suspend(t, frame, 'line', None, "frame_eval") - t.additional_info.is_tracing = False + return t.additional_info.pydev_step_cmd == CMD_SET_NEXT_STATEMENT + return False def pydev_trace_code_wrapper(): # import this module again, because it's inserted inside user's code global _pydev_stop_at_break - _pydev_stop_at_break() + return _pydev_stop_at_break() + + + +def create_code_wrapper(offset): + co = pydev_trace_code_wrapper.__code__ + # 0 + offset LOAD_GLOBAL 0 (_pydev_stop_at_break) + # 2 + offset CALL_FUNCTION 0 + # 4 + offset POP_JUMP_IF_TRUE offset + byte_code = [116, 0, 131, 0] + if offset > 0xFF: + byte_code += [opcode.EXTENDED_ARG, offset >> 8] + byte_code += [115, offset & 0xFF] + + #below code is just function trailer and gets removed + byte_code += [100, 0, 83, 0] + return CodeType( + co.co_argcount, co.co_kwonlyargcount, co.co_nlocals, + co.co_stacksize, + co.co_flags, + bytes(byte_code), + co.co_consts, co.co_names, co.co_varnames, co.co_filename, + co.co_name, co.co_firstlineno, co.co_lnotab, co.co_freevars, + co.co_cellvars) \ No newline at end of file diff --git a/python/helpers/pydev/pydevd.py b/python/helpers/pydev/pydevd.py index bca5a552849c..838e834220da 100644 --- a/python/helpers/pydev/pydevd.py +++ b/python/helpers/pydev/pydevd.py @@ -750,14 +750,10 @@ class PyDB: if curr_func_name == func_name: line = next_line - if frame.f_lineno == line: - stop = True - else: - if frame.f_trace is None: - frame.f_trace = self.trace_dispatch - frame.f_lineno = line - frame.f_trace = None - stop = True + if frame.f_trace is None: + frame.f_trace = self.trace_dispatch + frame.f_lineno = line + stop = True else: response_msg = "jump is available only within the bottom frame" return stop, old_line, response_msg @@ -856,14 +852,15 @@ class PyDB: info.pydev_message = '' if stop: - info.pydev_state = STATE_RUN - # `f_line` should be assigned within a tracing function, so, we can't assign it here - # for the frame evaluation debugger. For tracing debugger it will be assigned, but we should - # revert the previous value, because both debuggers should behave the same way - try: - self.set_next_statement(frame, event, info.pydev_func_name, old_line) - except: - pass + cmd = self.cmd_factory.make_thread_run_message(get_thread_id(thread), info.pydev_step_cmd) + self.writer.add_command(cmd) + if suspend_type == "trace": + info.pydev_state = STATE_SUSPEND + thread.stop_reason= CMD_SET_NEXT_STATEMENT + self.do_wait_suspend(thread, frame, event, arg, "trace") + else: + info.pydev_step_stop = frame + return else: info.pydev_step_cmd = -1 info.pydev_state = STATE_SUSPEND