From 46d0c7edf701abc8f8f7883634bc72e44bbfb4bc Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Fri, 12 Apr 2019 19:32:51 -0700 Subject: [PATCH] Move stack levels to pydevd (#1351) --- .../pydevd/_pydevd_bundle/pydevd_api.py | 4 +- .../pydevd/_pydevd_bundle/pydevd_comm.py | 6 ++- .../pydevd_net_command_factory_json.py | 11 +++++- .../pydevd_net_command_factory_xml.py | 2 +- .../pydevd_process_net_command_json.py | 4 +- .../resources/_debugger_case_deep_stacks.py | 16 ++++++++ .../pydevd/tests_python/test_debugger_json.py | 35 +++++++++++++++++ src/ptvsd/wrapper.py | 39 +++++-------------- 8 files changed, 80 insertions(+), 37 deletions(-) create mode 100644 src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_deep_stacks.py diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_api.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_api.py index 5ffc2c36..4d54b516 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_api.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_api.py @@ -117,10 +117,10 @@ class PyDevdAPI(object): py_db.post_method_as_internal_command( thread_id, internal_get_completions, seq, thread_id, frame_id, act_tok, line=line, column=column) - def request_stack(self, py_db, seq, thread_id, fmt=None, timeout=.5): + def request_stack(self, py_db, seq, thread_id, fmt=None, timeout=.5, start_frame=0, levels=0): # If it's already suspended, get it right away. internal_get_thread_stack = InternalGetThreadStack( - seq, thread_id, py_db, set_additional_thread_info, fmt=fmt, timeout=timeout) + seq, thread_id, py_db, set_additional_thread_info, fmt=fmt, timeout=timeout, start_frame=start_frame, levels=levels) if internal_get_thread_stack.can_be_executed_by(get_current_thread_id(threading.current_thread())): internal_get_thread_stack.do_it(py_db) else: diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py index 639bed2b..a8ab5962 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py @@ -550,13 +550,15 @@ class InternalGetThreadStack(InternalThreadCommand): stopped in a breakpoint). ''' - def __init__(self, seq, thread_id, py_db, set_additional_thread_info, fmt, timeout=.5): + def __init__(self, seq, thread_id, py_db, set_additional_thread_info, fmt, timeout=.5, start_frame=0, levels=0): InternalThreadCommand.__init__(self, thread_id) self._py_db = weakref.ref(py_db) self._timeout = time.time() + timeout self.seq = seq self._cmd = None self._fmt = fmt + self._start_frame = start_frame + self._levels = levels # Note: receives set_additional_thread_info to avoid a circular import # in this module. @@ -574,7 +576,7 @@ class InternalGetThreadStack(InternalThreadCommand): frame = additional_info.get_topmost_frame(t) try: self._cmd = py_db.cmd_factory.make_get_thread_stack_message( - py_db, self.seq, self.thread_id, frame, self._fmt, must_be_suspended=not timed_out) + py_db, self.seq, self.thread_id, frame, self._fmt, must_be_suspended=not timed_out, start_frame=self._start_frame, levels=self._levels) finally: frame = None t = None diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py index 23dd7525..b9bb52cf 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py @@ -158,7 +158,7 @@ class NetCommandFactoryJson(NetCommandFactory): return frame_name @overrides(NetCommandFactory.make_get_thread_stack_message) - def make_get_thread_stack_message(self, py_db, seq, thread_id, topmost_frame, fmt, must_be_suspended=False): + def make_get_thread_stack_message(self, py_db, seq, thread_id, topmost_frame, fmt, must_be_suspended=False, start_frame=0, levels=0): frames = [] module_events = [] if topmost_frame is not None: @@ -204,11 +204,18 @@ class NetCommandFactoryJson(NetCommandFactory): for module_event in module_events: py_db.writer.add_command(module_event) + total_frames = len(frames) + stack_frames = frames + if bool(levels): + start = start_frame + end = min(start + levels, total_frames) + stack_frames = frames[start:end] + response = pydevd_schema.StackTraceResponse( request_seq=seq, success=True, command='stackTrace', - body=pydevd_schema.StackTraceResponseBody(stackFrames=frames, totalFrames=len(frames))) + body=pydevd_schema.StackTraceResponseBody(stackFrames=stack_frames, totalFrames=total_frames)) return NetCommand(CMD_RETURN, 0, response, is_json=True) @overrides(NetCommandFactory.make_io_message) diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py index 76103566..e1173030 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py @@ -87,7 +87,7 @@ class NetCommandFactory(object): except: return self.make_error_message(seq, get_exception_traceback_str()) - def make_get_thread_stack_message(self, py_db, seq, thread_id, topmost_frame, fmt, must_be_suspended=False): + def make_get_thread_stack_message(self, py_db, seq, thread_id, topmost_frame, fmt, must_be_suspended=False, start_frame=0, levels=0): """ Returns thread stack as XML. diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py index b7cab17c..c4f4cd7b 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py @@ -481,11 +481,13 @@ class _PyDevJsonCommandProcessor(object): # : :type stack_trace_arguments: StackTraceArguments stack_trace_arguments = request.arguments thread_id = stack_trace_arguments.threadId + start_frame = stack_trace_arguments.startFrame + levels = stack_trace_arguments.levels fmt = stack_trace_arguments.format if hasattr(fmt, 'to_dict'): fmt = fmt.to_dict() - self.api.request_stack(py_db, request.seq, thread_id, fmt) + self.api.request_stack(py_db, request.seq, thread_id, fmt=fmt, start_frame=start_frame, levels=levels) def on_exceptioninfo_request(self, py_db, request): ''' diff --git a/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_deep_stacks.py b/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_deep_stacks.py new file mode 100644 index 00000000..8d06e16e --- /dev/null +++ b/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_deep_stacks.py @@ -0,0 +1,16 @@ +def method1(n): + if n <= 0: + return 0 # Break here + method2(n - 1) + + +def method2(n): + method1(n - 1) + + +if __name__ == '__main__': + try: + method1(100) + except: + pass # Don't let it print the exception (just deal with caught exceptions). + print('TEST SUCEEDED!') diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py index 7a4c706c..897b2c31 100644 --- a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py +++ b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py @@ -1182,6 +1182,41 @@ def test_exception_details(case_setup, max_frames): writer.finished_ok = True +def test_stack_levels(case_setup): + with case_setup.test_file('_debugger_case_deep_stacks.py') as writer: + json_facade = JsonFacade(writer) + + writer.write_set_protocol('http_json') + writer.write_add_breakpoint(writer.get_line_index_with_content('Break here')) + + json_facade.write_make_initial_run() + hit = writer.wait_for_breakpoint_hit() + + # get full stack + stack_trace_request = json_facade.write_request( + pydevd_schema.StackTraceRequest(pydevd_schema.StackTraceArguments(threadId=hit.thread_id))) + stack_trace_response = json_facade.wait_for_response(stack_trace_request) + full_stack_frames = stack_trace_response.body.stackFrames + total_frames = stack_trace_response.body.totalFrames + + startFrame = 0 + levels = 20 + received_frames = [] + while startFrame < total_frames: + stack_trace_request = json_facade.write_request( + pydevd_schema.StackTraceRequest(pydevd_schema.StackTraceArguments( + threadId=hit.thread_id, + startFrame=startFrame, + levels=20))) + stack_trace_response = json_facade.wait_for_response(stack_trace_request) + received_frames += stack_trace_response.body.stackFrames + startFrame += levels + + assert full_stack_frames == received_frames + + writer.write_run_thread(hit.thread_id) + + writer.finished_ok = True @pytest.mark.skipif(IS_JYTHON, reason='No goto on Jython.') def test_goto(case_setup): diff --git a/src/ptvsd/wrapper.py b/src/ptvsd/wrapper.py index f0a82f72..d8cb590c 100644 --- a/src/ptvsd/wrapper.py +++ b/src/ptvsd/wrapper.py @@ -1412,7 +1412,15 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor): def _forward_request_to_pydevd(self, request, args): translate_thread_id = args.get('threadId') is not None if translate_thread_id: - pyd_tid = self.thread_map.to_pydevd(int(args['threadId'])) + try: + vsc_tid = int(args['threadId']) + pyd_tid = self.thread_map.to_pydevd(vsc_tid) + except KeyError: + # Unknown thread, nothing much we can do about it here + self.send_error_response( + request, + 'Thread {} not found'.format(vsc_tid)) + return pydevd_request = copy.deepcopy(request) del pydevd_request['seq'] # A new seq should be created for pydevd. @@ -1480,35 +1488,8 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor): else: self.send_error_response(request, resp_args.get('message', 'Error retrieving source')) - @async_handler def on_stackTrace(self, request, args): - vsc_tid = int(args['threadId']) - - try: - pyd_tid = self.thread_map.to_pydevd(vsc_tid) - except KeyError: - # Unknown thread, nothing much we can do about it here - self.send_error_response( - request, - 'Thread {} not found'.format(vsc_tid)) - return - pydevd_request = copy.deepcopy(request) - del pydevd_request['seq'] # A new seq should be created for pydevd. - # Translate threadId for pydevd. - pydevd_request['arguments']['threadId'] = pyd_tid - _, _, resp_args = yield self.pydevd_request( - pydevd_comm.CMD_GET_THREAD_STACK, - pydevd_request, - is_json=True) - - levels = int(args.get('levels', 0)) - stackFrames = resp_args['body']['stackFrames'] - if levels > 0: - start = int(args.get('startFrame', 0)) - end = min(start + levels, len(stackFrames)) - stackFrames = resp_args['body']['stackFrames'][start:end] - totalFrames = resp_args['body']['totalFrames'] - self.send_response(request, stackFrames=stackFrames, totalFrames=totalFrames) + self._forward_request_to_pydevd(request, args) def on_scopes(self, request, args): self._forward_request_to_pydevd(request, args)