Show the full stack trace on user unhandled exceptions. Fixes #399

This commit is contained in:
Fabio Zadrozny 2020-09-27 08:07:14 -03:00 committed by Pavel Minaev
parent d68a17170c
commit 3d472df2d6
16 changed files with 5105 additions and 4999 deletions

View file

@ -1182,6 +1182,7 @@ def build_exception_info_response(dbg, thread_id, request_seq, set_additional_th
frames = []
exc_type = None
exc_desc = None
current_paused_frame_name = ''
if topmost_frame is not None:
try:
frames_list = dbg.suspended_frames_manager.get_frames_list(thread_id)
@ -1189,8 +1190,8 @@ def build_exception_info_response(dbg, thread_id, request_seq, set_additional_th
exc_type = frames_list.exc_type
exc_desc = frames_list.exc_desc
trace_obj = frames_list.trace_obj
for frame_id, frame, method_name, original_filename, filename_in_utf8, lineno, _applied_mapping in iter_visible_frames_info(
dbg, frames_list):
for frame_id, frame, method_name, original_filename, filename_in_utf8, lineno, _applied_mapping, show_as_current_frame in \
iter_visible_frames_info(dbg, frames_list):
line_text = linecache.getline(original_filename, lineno)
@ -1198,6 +1199,10 @@ def build_exception_info_response(dbg, thread_id, request_seq, set_additional_th
if not getattr(frame, 'IS_PLUGIN_FRAME', False):
if dbg.is_files_filter_enabled and dbg.apply_files_filter(frame, original_filename, False):
continue
if show_as_current_frame:
current_paused_frame_name = method_name
method_name += ' (Current frame)'
frames.append((filename_in_utf8, lineno, method_name, line_text))
finally:
topmost_frame = None
@ -1222,6 +1227,9 @@ def build_exception_info_response(dbg, thread_id, request_seq, set_additional_th
except:
pass
if current_paused_frame_name:
name += ' (note: full exception trace is shown but execution is paused at: %s)' % (current_paused_frame_name,)
stack_str = ''.join(traceback.format_list(frames[-max_frames:]))
# This is an extra bit of data used by Visual Studio

View file

@ -78,6 +78,8 @@ CMD_THREAD_RESUME_SINGLE_NOTIFICATION = 158
CMD_STEP_OVER_MY_CODE = 159
CMD_STEP_RETURN_MY_CODE = 160
CMD_SET_PY_EXCEPTION_JSON = 161
CMD_REDIRECT_OUTPUT = 200
CMD_GET_NEXT_STATEMENT_TARGETS = 201
CMD_SET_PROJECT_ROOTS = 202
@ -171,6 +173,8 @@ ID_TO_MEANING = {
'159': 'CMD_STEP_OVER_MY_CODE',
'160': 'CMD_STEP_RETURN_MY_CODE',
'161': 'CMD_SET_PY_EXCEPTION_JSON',
'200': 'CMD_REDIRECT_OUTPUT',
'201': 'CMD_GET_NEXT_STATEMENT_TARGETS',
'202': 'CMD_SET_PROJECT_ROOTS',

View file

@ -313,6 +313,10 @@ PYDEVD_UNBLOCK_THREADS_TIMEOUT = as_float_in_env('PYDEVD_UNBLOCK_THREADS_TIMEOUT
# on how the thread interruption works (there are some caveats related to it).
PYDEVD_INTERRUPT_THREAD_TIMEOUT = as_float_in_env('PYDEVD_INTERRUPT_THREAD_TIMEOUT', -1)
EXCEPTION_TYPE_UNHANDLED = 'UNHANDLED'
EXCEPTION_TYPE_USER_UNHANDLED = 'USER_UNHANDLED'
EXCEPTION_TYPE_HANDLED = 'HANDLED'
if SHOW_DEBUG_INFO_ENV:
# show debug info before the debugger start
DebugInfoHolder.DEBUG_RECORD_SOCKET_READS = True

File diff suppressed because it is too large Load diff

View file

@ -137,7 +137,8 @@ import re
from _pydev_bundle import pydev_log
from _pydevd_bundle import pydevd_dont_trace
from _pydevd_bundle.pydevd_constants import (dict_iter_values, IS_PY3K, RETURN_VALUES_DICT, NO_FTRACE)
from _pydevd_bundle.pydevd_constants import (dict_iter_values, IS_PY3K, RETURN_VALUES_DICT, NO_FTRACE,
EXCEPTION_TYPE_HANDLED, EXCEPTION_TYPE_USER_UNHANDLED)
from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, just_raised, remove_exception_from_frame, ignore_exception_trace
from _pydevd_bundle.pydevd_utils import get_clsname_for_code
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame
@ -286,7 +287,7 @@ cdef class PyDBFrame:
should_stop, frame = self.should_stop_on_exception(frame, event, arg)
if should_stop:
if self.handle_exception(frame, event, arg):
if self.handle_exception(frame, event, arg, EXCEPTION_TYPE_HANDLED):
return self.trace_dispatch
elif event == 'return':
@ -336,79 +337,85 @@ cdef class PyDBFrame:
pydev_log.exception()
if not should_stop:
was_just_raised = trace.tb_next is None
# Apply checks that don't need the exception breakpoint (where we shouldn't ever stop).
if exception == SystemExit and main_debugger.ignore_system_exit_code(value):
pass
# It was not handled by any plugin, lets check exception breakpoints.
check_excs = []
exc_break_caught = main_debugger.get_exception_breakpoint(
exception, main_debugger.break_on_caught_exceptions)
if exc_break_caught is not None:
check_excs.append((exc_break_caught, False))
elif exception in (GeneratorExit, StopIteration):
# These exceptions are control-flow related (they work as a generator
# pause), so, we shouldn't stop on them.
pass
exc_break_user = main_debugger.get_exception_breakpoint(
exception, main_debugger.break_on_user_uncaught_exceptions)
if exc_break_user is not None:
check_excs.append((exc_break_user, True))
elif ignore_exception_trace(trace):
pass
for exc_break, is_user_uncaught in check_excs:
# Initially mark that it should stop and then go into exclusions.
should_stop = True
else:
was_just_raised = trace.tb_next is None
if exception is SystemExit and main_debugger.ignore_system_exit_code(value):
should_stop = False
# It was not handled by any plugin, lets check exception breakpoints.
check_excs = []
elif exception in (GeneratorExit, StopIteration):
# These exceptions are control-flow related (they work as a generator
# pause), so, we shouldn't stop on them.
should_stop = False
# Note: check user unhandled before regular exceptions.
exc_break_user = main_debugger.get_exception_breakpoint(
exception, main_debugger.break_on_user_uncaught_exceptions)
if exc_break_user is not None:
check_excs.append((exc_break_user, True))
elif main_debugger.exclude_exception_by_filter(exc_break, trace):
pydev_log.debug("Ignore exception %s in library %s -- (%s)" % (exception, frame.f_code.co_filename, frame.f_code.co_name))
should_stop = False
exc_break_caught = main_debugger.get_exception_breakpoint(
exception, main_debugger.break_on_caught_exceptions)
if exc_break_caught is not None:
check_excs.append((exc_break_caught, False))
elif ignore_exception_trace(trace):
should_stop = False
for exc_break, is_user_uncaught in check_excs:
# Initially mark that it should stop and then go into exclusions.
should_stop = True
elif exc_break.condition is not None and \
not main_debugger.handle_breakpoint_condition(info, exc_break, frame):
should_stop = False
if main_debugger.exclude_exception_by_filter(exc_break, trace):
pydev_log.debug("Ignore exception %s in library %s -- (%s)" % (exception, frame.f_code.co_filename, frame.f_code.co_name))
should_stop = False
elif was_just_raised and main_debugger.skip_on_exceptions_thrown_in_same_context:
# Option: Don't break if an exception is caught in the same function from which it is thrown
should_stop = False
elif exc_break.condition is not None and \
not main_debugger.handle_breakpoint_condition(info, exc_break, frame):
should_stop = False
elif exc_break.notify_on_first_raise_only and main_debugger.skip_on_exceptions_thrown_in_same_context \
and not was_just_raised and not just_raised(trace.tb_next):
# In this case we never stop if it was just raised, so, to know if it was the first we
# need to check if we're in the 2nd method.
should_stop = False # I.e.: we stop only when we're at the caller of a method that throws an exception
elif is_user_uncaught:
# Note: we don't stop here, we just collect the exc_info to use later on...
should_stop = False
if not main_debugger.apply_files_filter(frame, frame.f_code.co_filename, True) \
and (frame.f_back is None or main_debugger.apply_files_filter(frame.f_back, frame.f_back.f_code.co_filename, True)):
# User uncaught means that we're currently in user code but the code
# up the stack is library code.
exc_info = self.exc_info
if not exc_info:
exc_info = (arg, frame.f_lineno, set([frame.f_lineno]))
else:
lines = exc_info[2]
lines.add(frame.f_lineno)
exc_info = (arg, frame.f_lineno, lines)
self.exc_info = exc_info
else:
# I.e.: these are only checked if we're not dealing with user uncaught exceptions.
if exc_break.notify_on_first_raise_only and main_debugger.skip_on_exceptions_thrown_in_same_context \
and not was_just_raised and not just_raised(trace.tb_next):
# In this case we never stop if it was just raised, so, to know if it was the first we
# need to check if we're in the 2nd method.
should_stop = False # I.e.: we stop only when we're at the caller of a method that throws an exception
elif exc_break.notify_on_first_raise_only and not main_debugger.skip_on_exceptions_thrown_in_same_context \
and not was_just_raised:
should_stop = False # I.e.: we stop only when it was just raised
elif exc_break.notify_on_first_raise_only and not main_debugger.skip_on_exceptions_thrown_in_same_context \
and not was_just_raised:
should_stop = False # I.e.: we stop only when it was just raised
elif is_user_uncaught:
should_stop = False
if not main_debugger.apply_files_filter(frame, frame.f_code.co_filename, True) \
and (frame.f_back is None or main_debugger.apply_files_filter(frame.f_back, frame.f_back.f_code.co_filename, True)):
# User uncaught means that we're currently in user code but the code
# up the stack is library code.
exc_info = self.exc_info
if not exc_info:
exc_info = (arg, frame.f_lineno, set([frame.f_lineno]))
else:
lines = exc_info[2]
lines.add(frame.f_lineno)
exc_info = (arg, frame.f_lineno, lines)
self.exc_info = exc_info
elif was_just_raised and main_debugger.skip_on_exceptions_thrown_in_same_context:
# Option: Don't break if an exception is caught in the same function from which it is thrown
should_stop = False
if should_stop:
exception_breakpoint = exc_break
try:
info.pydev_message = exc_break.qname
except:
info.pydev_message = exc_break.qname.encode('utf-8')
break
if should_stop:
exception_breakpoint = exc_break
try:
info.pydev_message = exc_break.qname
except:
info.pydev_message = exc_break.qname.encode('utf-8')
break
if should_stop:
# Always add exception to frame (must remove later after we proceed).
@ -422,11 +429,11 @@ cdef class PyDBFrame:
def handle_user_exception(self, frame):
exc_info = self.exc_info
if exc_info:
return self.handle_exception(frame, 'exception', exc_info[0])
return self.handle_exception(frame, 'exception', exc_info[0], EXCEPTION_TYPE_USER_UNHANDLED)
return False
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
cpdef handle_exception(self, frame, str event, arg):
cpdef handle_exception(self, frame, str event, arg, str exception_type):
cdef bint stopped;
cdef tuple abs_real_path_and_base;
cdef str absolute_filename;
@ -438,7 +445,7 @@ cdef class PyDBFrame:
cdef object trace_obj;
cdef object main_debugger;
# ELSE
# def handle_exception(self, frame, event, arg):
# def handle_exception(self, frame, event, arg, exception_type):
# ENDIF
stopped = False
try:
@ -532,7 +539,7 @@ cdef class PyDBFrame:
stopped = True
main_debugger.send_caught_exception_stack(thread, arg, id(frame))
self.set_suspend(thread, 137)
self.do_wait_suspend(thread, frame, event, arg)
self.do_wait_suspend(thread, frame, event, arg, exception_type=exception_type)
main_debugger.send_caught_exception_stack_proceeded(thread)
except:
pydev_log.exception()
@ -746,7 +753,7 @@ cdef class PyDBFrame:
if has_exception_breakpoints:
should_stop, frame = self.should_stop_on_exception(frame, event, arg)
if should_stop:
if self.handle_exception(frame, event, arg):
if self.handle_exception(frame, event, arg, EXCEPTION_TYPE_HANDLED):
return self.trace_dispatch
return self.trace_dispatch
@ -796,7 +803,7 @@ cdef class PyDBFrame:
if has_exception_breakpoints:
should_stop, frame = self.should_stop_on_exception(frame, event, arg)
if should_stop:
if self.handle_exception(frame, event, arg):
if self.handle_exception(frame, event, arg, EXCEPTION_TYPE_HANDLED):
return self.trace_dispatch
is_line = False
is_return = False

View file

@ -4,7 +4,8 @@ import re
from _pydev_bundle import pydev_log
from _pydevd_bundle import pydevd_dont_trace
from _pydevd_bundle.pydevd_constants import (dict_iter_values, IS_PY3K, RETURN_VALUES_DICT, NO_FTRACE)
from _pydevd_bundle.pydevd_constants import (dict_iter_values, IS_PY3K, RETURN_VALUES_DICT, NO_FTRACE,
EXCEPTION_TYPE_HANDLED, EXCEPTION_TYPE_USER_UNHANDLED)
from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, just_raised, remove_exception_from_frame, ignore_exception_trace
from _pydevd_bundle.pydevd_utils import get_clsname_for_code
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame
@ -165,7 +166,7 @@ class PyDBFrame:
should_stop, frame = self.should_stop_on_exception(frame, event, arg)
if should_stop:
if self.handle_exception(frame, event, arg):
if self.handle_exception(frame, event, arg, EXCEPTION_TYPE_HANDLED):
return self.trace_dispatch
elif event == 'return':
@ -215,79 +216,85 @@ class PyDBFrame:
pydev_log.exception()
if not should_stop:
was_just_raised = trace.tb_next is None
# Apply checks that don't need the exception breakpoint (where we shouldn't ever stop).
if exception == SystemExit and main_debugger.ignore_system_exit_code(value):
pass
# It was not handled by any plugin, lets check exception breakpoints.
check_excs = []
exc_break_caught = main_debugger.get_exception_breakpoint(
exception, main_debugger.break_on_caught_exceptions)
if exc_break_caught is not None:
check_excs.append((exc_break_caught, False))
elif exception in (GeneratorExit, StopIteration):
# These exceptions are control-flow related (they work as a generator
# pause), so, we shouldn't stop on them.
pass
exc_break_user = main_debugger.get_exception_breakpoint(
exception, main_debugger.break_on_user_uncaught_exceptions)
if exc_break_user is not None:
check_excs.append((exc_break_user, True))
elif ignore_exception_trace(trace):
pass
for exc_break, is_user_uncaught in check_excs:
# Initially mark that it should stop and then go into exclusions.
should_stop = True
else:
was_just_raised = trace.tb_next is None
if exception is SystemExit and main_debugger.ignore_system_exit_code(value):
should_stop = False
# It was not handled by any plugin, lets check exception breakpoints.
check_excs = []
elif exception in (GeneratorExit, StopIteration):
# These exceptions are control-flow related (they work as a generator
# pause), so, we shouldn't stop on them.
should_stop = False
# Note: check user unhandled before regular exceptions.
exc_break_user = main_debugger.get_exception_breakpoint(
exception, main_debugger.break_on_user_uncaught_exceptions)
if exc_break_user is not None:
check_excs.append((exc_break_user, True))
elif main_debugger.exclude_exception_by_filter(exc_break, trace):
pydev_log.debug("Ignore exception %s in library %s -- (%s)" % (exception, frame.f_code.co_filename, frame.f_code.co_name))
should_stop = False
exc_break_caught = main_debugger.get_exception_breakpoint(
exception, main_debugger.break_on_caught_exceptions)
if exc_break_caught is not None:
check_excs.append((exc_break_caught, False))
elif ignore_exception_trace(trace):
should_stop = False
for exc_break, is_user_uncaught in check_excs:
# Initially mark that it should stop and then go into exclusions.
should_stop = True
elif exc_break.condition is not None and \
not main_debugger.handle_breakpoint_condition(info, exc_break, frame):
should_stop = False
if main_debugger.exclude_exception_by_filter(exc_break, trace):
pydev_log.debug("Ignore exception %s in library %s -- (%s)" % (exception, frame.f_code.co_filename, frame.f_code.co_name))
should_stop = False
elif was_just_raised and main_debugger.skip_on_exceptions_thrown_in_same_context:
# Option: Don't break if an exception is caught in the same function from which it is thrown
should_stop = False
elif exc_break.condition is not None and \
not main_debugger.handle_breakpoint_condition(info, exc_break, frame):
should_stop = False
elif exc_break.notify_on_first_raise_only and main_debugger.skip_on_exceptions_thrown_in_same_context \
and not was_just_raised and not just_raised(trace.tb_next):
# In this case we never stop if it was just raised, so, to know if it was the first we
# need to check if we're in the 2nd method.
should_stop = False # I.e.: we stop only when we're at the caller of a method that throws an exception
elif is_user_uncaught:
# Note: we don't stop here, we just collect the exc_info to use later on...
should_stop = False
if not main_debugger.apply_files_filter(frame, frame.f_code.co_filename, True) \
and (frame.f_back is None or main_debugger.apply_files_filter(frame.f_back, frame.f_back.f_code.co_filename, True)):
# User uncaught means that we're currently in user code but the code
# up the stack is library code.
exc_info = self.exc_info
if not exc_info:
exc_info = (arg, frame.f_lineno, set([frame.f_lineno]))
else:
lines = exc_info[2]
lines.add(frame.f_lineno)
exc_info = (arg, frame.f_lineno, lines)
self.exc_info = exc_info
else:
# I.e.: these are only checked if we're not dealing with user uncaught exceptions.
if exc_break.notify_on_first_raise_only and main_debugger.skip_on_exceptions_thrown_in_same_context \
and not was_just_raised and not just_raised(trace.tb_next):
# In this case we never stop if it was just raised, so, to know if it was the first we
# need to check if we're in the 2nd method.
should_stop = False # I.e.: we stop only when we're at the caller of a method that throws an exception
elif exc_break.notify_on_first_raise_only and not main_debugger.skip_on_exceptions_thrown_in_same_context \
and not was_just_raised:
should_stop = False # I.e.: we stop only when it was just raised
elif exc_break.notify_on_first_raise_only and not main_debugger.skip_on_exceptions_thrown_in_same_context \
and not was_just_raised:
should_stop = False # I.e.: we stop only when it was just raised
elif is_user_uncaught:
should_stop = False
if not main_debugger.apply_files_filter(frame, frame.f_code.co_filename, True) \
and (frame.f_back is None or main_debugger.apply_files_filter(frame.f_back, frame.f_back.f_code.co_filename, True)):
# User uncaught means that we're currently in user code but the code
# up the stack is library code.
exc_info = self.exc_info
if not exc_info:
exc_info = (arg, frame.f_lineno, set([frame.f_lineno]))
else:
lines = exc_info[2]
lines.add(frame.f_lineno)
exc_info = (arg, frame.f_lineno, lines)
self.exc_info = exc_info
elif was_just_raised and main_debugger.skip_on_exceptions_thrown_in_same_context:
# Option: Don't break if an exception is caught in the same function from which it is thrown
should_stop = False
if should_stop:
exception_breakpoint = exc_break
try:
info.pydev_message = exc_break.qname
except:
info.pydev_message = exc_break.qname.encode('utf-8')
break
if should_stop:
exception_breakpoint = exc_break
try:
info.pydev_message = exc_break.qname
except:
info.pydev_message = exc_break.qname.encode('utf-8')
break
if should_stop:
# Always add exception to frame (must remove later after we proceed).
@ -301,11 +308,11 @@ class PyDBFrame:
def handle_user_exception(self, frame):
exc_info = self.exc_info
if exc_info:
return self.handle_exception(frame, 'exception', exc_info[0])
return self.handle_exception(frame, 'exception', exc_info[0], EXCEPTION_TYPE_USER_UNHANDLED)
return False
# IFDEF CYTHON
# cpdef handle_exception(self, frame, str event, arg):
# cpdef handle_exception(self, frame, str event, arg, str exception_type):
# cdef bint stopped;
# cdef tuple abs_real_path_and_base;
# cdef str absolute_filename;
@ -317,7 +324,7 @@ class PyDBFrame:
# cdef object trace_obj;
# cdef object main_debugger;
# ELSE
def handle_exception(self, frame, event, arg):
def handle_exception(self, frame, event, arg, exception_type):
# ENDIF
stopped = False
try:
@ -411,7 +418,7 @@ class PyDBFrame:
stopped = True
main_debugger.send_caught_exception_stack(thread, arg, id(frame))
self.set_suspend(thread, CMD_STEP_CAUGHT_EXCEPTION)
self.do_wait_suspend(thread, frame, event, arg)
self.do_wait_suspend(thread, frame, event, arg, exception_type=exception_type)
main_debugger.send_caught_exception_stack_proceeded(thread)
except:
pydev_log.exception()
@ -625,7 +632,7 @@ class PyDBFrame:
if has_exception_breakpoints:
should_stop, frame = self.should_stop_on_exception(frame, event, arg)
if should_stop:
if self.handle_exception(frame, event, arg):
if self.handle_exception(frame, event, arg, EXCEPTION_TYPE_HANDLED):
return self.trace_dispatch
return self.trace_dispatch
@ -675,7 +682,7 @@ class PyDBFrame:
if has_exception_breakpoints:
should_stop, frame = self.should_stop_on_exception(frame, event, arg)
if should_stop:
if self.handle_exception(frame, event, arg):
if self.handle_exception(frame, event, arg, EXCEPTION_TYPE_HANDLED):
return self.trace_dispatch
is_line = False
is_return = False

View file

@ -1,4 +1,5 @@
from _pydevd_bundle.pydevd_constants import IS_PY3K
from _pydevd_bundle.pydevd_constants import IS_PY3K, EXCEPTION_TYPE_USER_UNHANDLED, \
EXCEPTION_TYPE_UNHANDLED
from _pydev_bundle import pydev_log
import sys
@ -89,9 +90,20 @@ class FramesList(object):
self.exc_desc = None
self.trace_obj = None
# This may be set to set the current frame (for the case where we have
# an unhandled exception where we want to show the root bu we have a different
# executing frame).
self.current_frame = None
def append(self, frame):
self._frames.append(frame)
def last_frame(self):
return self._frames[-1]
def __len__(self):
return len(self._frames)
def __iter__(self):
return iter(self._frames)
@ -107,6 +119,9 @@ class FramesList(object):
lst.append('\n trace_obj: ')
lst.append(str(self.trace_obj))
lst.append('\n current_frame: ')
lst.append(str(self.current_frame))
for frame in self._frames:
lst.append('\n ')
lst.append(repr(frame))
@ -117,13 +132,18 @@ class FramesList(object):
__str__ = __repr__
def create_frames_list_from_traceback(trace_obj, frame, exc_type, exc_desc):
def create_frames_list_from_traceback(trace_obj, frame, exc_type, exc_desc, exception_type=None):
'''
:param trace_obj:
This is the traceback from which the list should be created.
:param frame:
This is the first frame to be considered (i.e.: topmost frame).
This is the first frame to be considered (i.e.: topmost frame). If None is passed, all
the frames from the traceback are shown (so, None should be passed for unhandled exceptions).
:param exception_type:
If this is an unhandled exception or user unhandled exception, we'll not trim the stack to create from the passed
frame, rather, we'll just mark the frame in the frames list.
'''
lst = []
@ -141,7 +161,11 @@ def create_frames_list_from_traceback(trace_obj, frame, exc_type, exc_desc):
frames_list = None
for tb_frame, tb_lineno in reversed(lst):
if frames_list is None and (frame is tb_frame or frame is None):
if frames_list is None and (
(frame is tb_frame) or
(frame is None) or
(exception_type == EXCEPTION_TYPE_USER_UNHANDLED)
):
frames_list = FramesList()
if frames_list is not None:
@ -157,6 +181,12 @@ def create_frames_list_from_traceback(trace_obj, frame, exc_type, exc_desc):
frames_list.exc_desc = exc_desc
frames_list.trace_obj = trace_obj
if exception_type == EXCEPTION_TYPE_USER_UNHANDLED:
frames_list.current_frame = frame
elif exception_type == EXCEPTION_TYPE_UNHANDLED:
if len(frames_list) > 0:
frames_list.current_frame = frames_list.last_frame()
return frames_list

View file

@ -215,7 +215,7 @@ class NetCommandFactoryJson(NetCommandFactory):
else:
frames_list = pydevd_frame_utils.create_frames_list_from_frame(topmost_frame)
for frame_id, frame, method_name, original_filename, filename_in_utf8, lineno, applied_mapping in self._iter_visible_frames_info(
for frame_id, frame, method_name, original_filename, filename_in_utf8, lineno, applied_mapping, show_as_current_frame in self._iter_visible_frames_info(
py_db, frames_list
):
@ -235,6 +235,8 @@ class NetCommandFactoryJson(NetCommandFactory):
presentation_hint = 'subtle'
formatted_name = self._format_frame_name(fmt, method_name, module_name, lineno, filename_in_utf8)
if show_as_current_frame:
formatted_name += ' (Current frame)'
source_reference = pydevd_file_utils.get_client_filename_source_reference(filename_in_utf8)
if not source_reference and not applied_mapping and not os.path.exists(original_filename):

View file

@ -161,6 +161,7 @@ class NetCommandFactory(object):
def _iter_visible_frames_info(self, py_db, frames_list):
assert frames_list.__class__ == FramesList
for frame in frames_list:
show_as_current_frame = frame is frames_list.current_frame
if frame.f_code is None:
pydev_log.info('Frame without f_code: %s', frame)
continue # IronPython sometimes does not have it!
@ -183,7 +184,7 @@ class NetCommandFactory(object):
new_filename_in_utf8, applied_mapping = pydevd_file_utils.map_file_to_client(filename_in_utf8)
applied_mapping = applied_mapping or changed
yield frame_id, frame, method_name, abs_path_real_path_and_base[0], new_filename_in_utf8, lineno, applied_mapping
yield frame_id, frame, method_name, abs_path_real_path_and_base[0], new_filename_in_utf8, lineno, applied_mapping, show_as_current_frame
def make_thread_stack_str(self, py_db, frames_list):
assert frames_list.__class__ == FramesList
@ -192,7 +193,7 @@ class NetCommandFactory(object):
append = cmd_text_list.append
try:
for frame_id, frame, method_name, _original_filename, filename_in_utf8, lineno, _applied_mapping in self._iter_visible_frames_info(
for frame_id, frame, method_name, _original_filename, filename_in_utf8, lineno, _applied_mapping, _show_as_current_frame in self._iter_visible_frames_info(
py_db, frames_list
):

View file

@ -333,72 +333,100 @@ class _PyDevCommandProcessor(object):
thread_id, frame_id, scope, expression = text.split('\t', 3)
self.api.request_console_exec(py_db, seq, thread_id, frame_id, expression)
def cmd_set_py_exception(self, py_db, cmd_id, seq, text):
# Command which receives set of exceptions on which user wants to break the debugger
# text is:
#
# break_on_uncaught;
# break_on_caught;
# skip_on_exceptions_thrown_in_same_context;
# ignore_exceptions_thrown_in_lines_with_ignore_exception;
# ignore_libraries;
# TypeError;ImportError;zipimport.ZipImportError;
#
# i.e.: true;true;true;true;true;TypeError;ImportError;zipimport.ZipImportError;
#
def cmd_set_py_exception_json(self, py_db, cmd_id, seq, text):
# This API is optional and works 'in bulk' -- it's possible
# to get finer-grained control with CMD_ADD_EXCEPTION_BREAK/CMD_REMOVE_EXCEPTION_BREAK
# which allows setting caught/uncaught per exception.
splitted = text.split(';')
py_db.break_on_uncaught_exceptions = {}
py_db.break_on_caught_exceptions = {}
py_db.break_on_user_uncaught_exceptions = {}
if len(splitted) >= 5:
if splitted[0] == 'true':
break_on_uncaught = True
else:
break_on_uncaught = False
# which allows setting caught/uncaught per exception, although global settings such as:
# - skip_on_exceptions_thrown_in_same_context
# - ignore_exceptions_thrown_in_lines_with_ignore_exception
# must still be set through this API (before anything else as this clears all existing
# exception breakpoints).
try:
py_db.break_on_uncaught_exceptions = {}
py_db.break_on_caught_exceptions = {}
py_db.break_on_user_uncaught_exceptions = {}
if splitted[1] == 'true':
break_on_caught = True
else:
break_on_caught = False
as_json = json.loads(text)
break_on_uncaught = as_json.get('break_on_uncaught', False)
break_on_caught = as_json.get('break_on_caught', False)
break_on_user_caught = as_json.get('break_on_user_caught', False)
py_db.skip_on_exceptions_thrown_in_same_context = as_json.get('skip_on_exceptions_thrown_in_same_context', False)
py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception = as_json.get('ignore_exceptions_thrown_in_lines_with_ignore_exception', False)
ignore_libraries = as_json.get('ignore_libraries', False)
exception_types = as_json.get('exception_types', [])
if splitted[2] == 'true':
py_db.skip_on_exceptions_thrown_in_same_context = True
else:
py_db.skip_on_exceptions_thrown_in_same_context = False
if splitted[3] == 'true':
py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception = True
else:
py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception = False
if splitted[4] == 'true':
ignore_libraries = True
else:
ignore_libraries = False
for exception_type in splitted[5:]:
exception_type = exception_type.strip()
for exception_type in exception_types:
if not exception_type:
continue
exception_breakpoint = py_db.add_break_on_exception(
py_db.add_break_on_exception(
exception_type,
condition=None,
expression=None,
notify_on_handled_exceptions=break_on_caught,
notify_on_unhandled_exceptions=break_on_uncaught,
notify_on_user_unhandled_exceptions=False, # TODO (not currently supported in this API).
notify_on_user_unhandled_exceptions=break_on_user_caught,
notify_on_first_raise_only=True,
ignore_libraries=ignore_libraries,
)
py_db.on_breakpoints_changed()
py_db.on_breakpoints_changed()
except:
pydev_log.exception("Error when setting exception list. Received: %s", text)
else:
sys.stderr.write("Error when setting exception list. Received: %s\n" % (text,))
def cmd_set_py_exception(self, py_db, cmd_id, seq, text):
# DEPRECATED. Use cmd_set_py_exception_json instead.
try:
splitted = text.split(';')
py_db.break_on_uncaught_exceptions = {}
py_db.break_on_caught_exceptions = {}
py_db.break_on_user_uncaught_exceptions = {}
if len(splitted) >= 5:
if splitted[0] == 'true':
break_on_uncaught = True
else:
break_on_uncaught = False
if splitted[1] == 'true':
break_on_caught = True
else:
break_on_caught = False
if splitted[2] == 'true':
py_db.skip_on_exceptions_thrown_in_same_context = True
else:
py_db.skip_on_exceptions_thrown_in_same_context = False
if splitted[3] == 'true':
py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception = True
else:
py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception = False
if splitted[4] == 'true':
ignore_libraries = True
else:
ignore_libraries = False
for exception_type in splitted[5:]:
exception_type = exception_type.strip()
if not exception_type:
continue
py_db.add_break_on_exception(
exception_type,
condition=None,
expression=None,
notify_on_handled_exceptions=break_on_caught,
notify_on_unhandled_exceptions=break_on_uncaught,
notify_on_user_unhandled_exceptions=False, # TODO (not currently supported in this API).
notify_on_first_raise_only=True,
ignore_libraries=ignore_libraries,
)
else:
pydev_log.exception("Expected to have at least 5 ';' separated items. Received: %s", text)
except:
pydev_log.exception("Error when setting exception list. Received: %s", text)
def _load_source(self, py_db, cmd_id, seq, text):
filename = text

View file

@ -41,7 +41,7 @@ from _pydevd_bundle.pydevd_constants import (IS_JYTH_LESS25, get_thread_id, get_
dict_keys, dict_iter_items, DebugInfoHolder, PYTHON_SUSPEND, STATE_SUSPEND, STATE_RUN, get_frame,
clear_cached_thread_id, INTERACTIVE_MODE_AVAILABLE, SHOW_DEBUG_INFO_ENV, IS_PY34_OR_GREATER, IS_PY2, NULL,
NO_FTRACE, IS_IRONPYTHON, JSON_PROTOCOL, IS_CPYTHON, HTTP_JSON_PROTOCOL, USE_CUSTOM_SYS_CURRENT_FRAMES_MAP, call_only_once,
ForkSafeLock, IGNORE_BASENAMES_STARTING_WITH)
ForkSafeLock, IGNORE_BASENAMES_STARTING_WITH, EXCEPTION_TYPE_UNHANDLED)
from _pydevd_bundle.pydevd_defaults import PydevdCustomization # Note: import alias used on pydev_monkey.
from _pydevd_bundle.pydevd_custom_frames import CustomFramesContainer, custom_frames_container_init
from _pydevd_bundle.pydevd_dont_trace_files import DONT_TRACE, PYDEV_FILE, LIB_FILE, DONT_TRACE_DIRS
@ -1780,14 +1780,13 @@ class PyDB(object):
""" returns a frame on the thread that has a given frame_id """
return self.suspended_frames_manager.find_frame(thread_id, frame_id)
def do_wait_suspend(self, thread, frame, event, arg, is_unhandled_exception=False): # @UnusedVariable
def do_wait_suspend(self, thread, frame, event, arg, exception_type=None): # @UnusedVariable
""" busy waits until the thread state changes to RUN
it expects thread's state as attributes of the thread.
Upon running, processes any outstanding Stepping commands.
:param is_unhandled_exception:
If True we should use the line of the exception instead of the current line in the frame
as the paused location on the top-level frame (exception info must be passed on 'arg').
:param exception_type:
If pausing due to an exception, its type.
"""
if USE_CUSTOM_SYS_CURRENT_FRAMES_MAP:
constructed_tid_to_last_frame[thread.ident] = sys._getframe()
@ -1809,7 +1808,7 @@ class PyDB(object):
# arg must be the exception info (tuple(exc_type, exc, traceback))
exc_type, exc_desc, trace_obj = arg
if trace_obj is not None:
frames_list = pydevd_frame_utils.create_frames_list_from_traceback(trace_obj, frame, exc_type, exc_desc)
frames_list = pydevd_frame_utils.create_frames_list_from_traceback(trace_obj, frame, exc_type, exc_desc, exception_type=exception_type)
if frames_list is None:
frames_list = pydevd_frame_utils.create_frames_list_from_frame(frame)
@ -1860,7 +1859,7 @@ class PyDB(object):
if keep_suspended:
# This means that we should pause again after a set next statement.
self._threads_suspended_single_notification.increment_suspend_time()
self.do_wait_suspend(thread, frame, event, arg, is_unhandled_exception)
self.do_wait_suspend(thread, frame, event, arg, exception_type)
if DebugInfoHolder.DEBUG_TRACE_LEVEL > 2:
pydev_log.debug('Leaving PyDB.do_wait_suspend: %s (%s) %s', thread, thread_id, id(thread))
@ -1992,7 +1991,7 @@ class PyDB(object):
try:
add_exception_to_frame(frame, arg)
self.set_suspend(thread, CMD_ADD_EXCEPTION_BREAK)
self.do_wait_suspend(thread, frame, 'exception', arg, is_unhandled_exception=True)
self.do_wait_suspend(thread, frame, 'exception', arg, EXCEPTION_TYPE_UNHANDLED)
except:
pydev_log.exception("We've got an error while stopping in unhandled exception: %s.", arg[0])
finally:
@ -2812,7 +2811,7 @@ class Dispatcher(object):
def close(self):
try:
self.reader.do_kill_pydev_thread()
except :
except:
pass

View file

@ -81,6 +81,8 @@ CMD_THREAD_RESUME_SINGLE_NOTIFICATION = 158
CMD_STEP_OVER_MY_CODE = 159
CMD_STEP_RETURN_MY_CODE = 160
CMD_SET_PY_EXCEPTION = 161
CMD_REDIRECT_OUTPUT = 200
CMD_GET_NEXT_STATEMENT_TARGETS = 201
CMD_SET_PROJECT_ROOTS = 202

View file

@ -2,7 +2,7 @@ import pytest
def some_call():
assert 0
assert 0 # raise here
def test_example():

View file

@ -314,7 +314,7 @@ def test_relative_paths(tmpdir):
pydevd_file_utils.NORM_PATHS_CONTAINER.clear()
abs_path = pydevd_file_utils.get_abs_path_real_path_and_base_from_file('my_dir/my_file.pyx')[0]
assert 'site-packages' not in abs_path
assert os.path.normcase(str(tmpdir)) in abs_path
assert str(tmpdir) in abs_path
assert pydevd_file_utils.exists('my_dir/my_file.pyx')
finally:
sys.path.remove(str(tmpdir))
@ -344,7 +344,7 @@ def test_zip_paths(tmpdir):
assert pydevd_file_utils.exists(zipfile_path)
abspath, realpath, basename = pydevd_file_utils.get_abs_path_real_path_and_base_from_file(zipfile_path)
if IS_WINDOWS:
assert abspath == zipfile_path.lower()
assert abspath == zipfile_path
assert basename == zip_basename.lower()
else:
assert abspath == zipfile_path

View file

@ -641,6 +641,26 @@ def test_case_handled_exception_breaks(case_setup):
writer.finished_ok = True
def _check_current_line(json_hit, current_line):
if not isinstance(current_line, (list, tuple)):
current_line = (current_line,)
for frame in json_hit.stack_trace_response.body.stackFrames:
if '(Current frame)' in frame['name']:
if frame['line'] not in current_line:
rep = json.dumps(json_hit.stack_trace_response.body.stackFrames, indent=4)
raise AssertionError('Expected: %s to be one of: %s\nFrames:\n%s.' % (
frame['line'],
current_line,
rep
))
break
else:
rep = json.dumps(json_hit.stack_trace_response.body.stackFrames, indent=4)
raise AssertionError('Could not find (Current frame) in any frame name in: %s.' % (
rep))
@pytest.mark.skipif(IS_PY26, reason='Not ok on Python 2.6')
@pytest.mark.parametrize('stop', [False, True])
def test_case_user_unhandled_exception(case_setup, stop):
@ -665,8 +685,9 @@ def test_case_user_unhandled_exception(case_setup, stop):
json_facade.write_make_initial_run()
if stop:
json_facade.wait_for_thread_stopped(
reason='exception', line=writer.get_line_index_with_content('stop here'), file=target)
json_hit = json_facade.wait_for_thread_stopped(
reason='exception', line=writer.get_line_index_with_content('raise here'), file=target)
_check_current_line(json_hit, writer.get_line_index_with_content('stop here'))
json_facade.write_continue()
@ -705,21 +726,30 @@ def test_case_user_unhandled_exception_coroutine(case_setup, stop):
json_facade.write_make_initial_run()
if stop:
json_facade.wait_for_thread_stopped(
reason='exception', line=writer.get_line_index_with_content('stop here 1'), file=basename)
stop_line = writer.get_line_index_with_content('stop here 1')
current_line = stop_line
json_hit = json_facade.wait_for_thread_stopped(
reason='exception', line=stop_line, file=basename)
_check_current_line(json_hit, current_line)
json_facade.write_continue()
json_facade.wait_for_thread_stopped(
reason='exception', line=writer.get_line_index_with_content('stop here 2'), file=basename)
current_line = writer.get_line_index_with_content('stop here 2')
json_hit = json_facade.wait_for_thread_stopped(
reason='exception', line=stop_line, file=basename)
_check_current_line(json_hit, current_line)
json_facade.write_continue()
json_facade.wait_for_thread_stopped(
reason='exception', line=(
writer.get_line_index_with_content('stop here 3a'),
writer.get_line_index_with_content('stop here 3b')
), file=basename)
current_line = (
writer.get_line_index_with_content('stop here 3a'),
writer.get_line_index_with_content('stop here 3b'),
)
json_hit = json_facade.wait_for_thread_stopped(
reason='exception', line=stop_line, file=basename)
_check_current_line(json_hit, current_line)
json_facade.write_continue()
@ -734,7 +764,7 @@ def test_case_user_unhandled_exception_stop_on_yield(case_setup, pyfile):
def on_yield():
yield
raise AssertionError()
raise AssertionError() # raise here
try:
for _ in on_yield(): # stop here
@ -765,8 +795,9 @@ def test_case_user_unhandled_exception_stop_on_yield(case_setup, pyfile):
json_facade.write_set_exception_breakpoints(['userUnhandled'])
json_facade.write_make_initial_run()
json_facade.wait_for_thread_stopped(
reason='exception', line=writer.get_line_index_with_content('stop here'), file=case_error_on_yield)
json_hit = json_facade.wait_for_thread_stopped(
reason='exception', line=writer.get_line_index_with_content('raise here'), file=case_error_on_yield)
_check_current_line(json_hit, writer.get_line_index_with_content('stop here'))
json_facade.write_continue()
writer.finished_ok = True

View file

@ -162,7 +162,7 @@ class Client(components.Component):
"exceptionBreakpointFilters": [
{"filter": "raised", "label": "Raised Exceptions", "default": False},
{"filter": "uncaught", "label": "Uncaught Exceptions", "default": True},
# {"filter": "userUnhandled", "label": "User Uncaught Exceptions", "default": False},
{"filter": "userUnhandled", "label": "User Uncaught Exceptions", "default": False},
],
}
@ -305,7 +305,7 @@ class Client(components.Component):
python = request(python_key, json.array(unicode, vectorize=True, size=(0,)))
if not len(python):
python = [compat.filename(sys.executable)]
python += request("pythonArgs", json.array(unicode, size=(0,)))
request.arguments["pythonArgs"] = python[1:]