Skip to content

Commit 63c0fae

Browse files
fabiozint19h
authored andcommitted
Handle userUnhandled exception breakpoints. Fixes #111
1 parent 1fb9706 commit 63c0fae

15 files changed

+3961
-3395
lines changed

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,7 @@ def add_python_exception_breakpoint(
686686
expression,
687687
notify_on_handled_exceptions,
688688
notify_on_unhandled_exceptions,
689+
notify_on_user_unhandled_exceptions,
689690
notify_on_first_raise_only,
690691
ignore_libraries,
691692
):
@@ -695,6 +696,7 @@ def add_python_exception_breakpoint(
695696
expression=expression,
696697
notify_on_handled_exceptions=notify_on_handled_exceptions,
697698
notify_on_unhandled_exceptions=notify_on_unhandled_exceptions,
699+
notify_on_user_unhandled_exceptions=notify_on_user_unhandled_exceptions,
698700
notify_on_first_raise_only=notify_on_first_raise_only,
699701
ignore_libraries=ignore_libraries,
700702
)
@@ -723,6 +725,10 @@ def remove_python_exception_breakpoint(self, py_db, exception):
723725
cp = py_db.break_on_caught_exceptions.copy()
724726
cp.pop(exception, None)
725727
py_db.break_on_caught_exceptions = cp
728+
729+
cp = py_db.break_on_user_uncaught_exceptions.copy()
730+
cp.pop(exception, None)
731+
py_db.break_on_user_uncaught_exceptions = cp
726732
except:
727733
pydev_log.exception("Error while removing exception %s", sys.exc_info()[0])
728734

@@ -747,6 +753,7 @@ def remove_plugins_exception_breakpoint(self, py_db, exception_type, exception):
747753
def remove_all_exception_breakpoints(self, py_db):
748754
py_db.break_on_uncaught_exceptions = {}
749755
py_db.break_on_caught_exceptions = {}
756+
py_db.break_on_user_uncaught_exceptions = {}
750757

751758
plugin = py_db.plugin
752759
if plugin is not None:

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_breakpoints.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ def __init__(
1414
expression,
1515
notify_on_handled_exceptions,
1616
notify_on_unhandled_exceptions,
17+
notify_on_user_unhandled_exceptions,
1718
notify_on_first_raise_only,
1819
ignore_libraries
1920
):
@@ -29,6 +30,7 @@ def __init__(
2930
self.notify_on_unhandled_exceptions = notify_on_unhandled_exceptions
3031
self.notify_on_handled_exceptions = notify_on_handled_exceptions
3132
self.notify_on_first_raise_only = notify_on_first_raise_only
33+
self.notify_on_user_unhandled_exceptions = notify_on_user_unhandled_exceptions
3234
self.ignore_libraries = ignore_libraries
3335

3436
self.type = exctype

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_cython.c

Lines changed: 3629 additions & 3190 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_cython.pyx

Lines changed: 57 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,9 @@ cdef class PyDBFrame:
280280
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
281281
def should_stop_on_exception(self, frame, str event, arg):
282282
cdef PyDBAdditionalThreadInfo info;
283-
cdef bint flag;
283+
cdef bint should_stop;
284+
cdef bint was_just_raised;
285+
cdef list check_excs;
284286
# ELSE
285287
# def should_stop_on_exception(self, frame, event, arg):
286288
# ENDIF
@@ -308,55 +310,71 @@ cdef class PyDBFrame:
308310
pydev_log.exception()
309311

310312
if not should_stop:
313+
was_just_raised = trace.tb_next is None
314+
311315
# It was not handled by any plugin, lets check exception breakpoints.
312-
exception_breakpoint = main_debugger.get_exception_breakpoint(
316+
check_excs = []
317+
exc_break_caught = main_debugger.get_exception_breakpoint(
313318
exception, main_debugger.break_on_caught_exceptions)
319+
if exc_break_caught is not None:
320+
check_excs.append((exc_break_caught, False))
321+
322+
exc_break_user = main_debugger.get_exception_breakpoint(
323+
exception, main_debugger.break_on_user_uncaught_exceptions)
324+
if exc_break_user is not None:
325+
check_excs.append((exc_break_user, True))
326+
327+
for exc_break, is_user_uncaught in check_excs:
328+
# Initially mark that it should stop and then go into exclusions.
329+
should_stop = True
314330

315-
if exception_breakpoint is not None:
316331
if exception is SystemExit and main_debugger.ignore_system_exit_code(value):
317-
return False, frame
332+
should_stop = False
318333

319-
if exception in (GeneratorExit, StopIteration):
334+
elif exception in (GeneratorExit, StopIteration):
320335
# These exceptions are control-flow related (they work as a generator
321336
# pause), so, we shouldn't stop on them.
322-
return False, frame
323-
324-
if exception_breakpoint.condition is not None:
325-
eval_result = main_debugger.handle_breakpoint_condition(info, exception_breakpoint, frame)
326-
if not eval_result:
327-
return False, frame
337+
should_stop = False
328338

329-
if main_debugger.exclude_exception_by_filter(exception_breakpoint, trace):
339+
elif main_debugger.exclude_exception_by_filter(exc_break, trace):
330340
pydev_log.debug("Ignore exception %s in library %s -- (%s)" % (exception, frame.f_code.co_filename, frame.f_code.co_name))
331-
return False, frame
341+
should_stop = False
332342

333-
if ignore_exception_trace(trace):
334-
return False, frame
343+
elif ignore_exception_trace(trace):
344+
should_stop = False
335345

336-
was_just_raised = just_raised(trace)
337-
if was_just_raised:
346+
elif exc_break.condition is not None and \
347+
not main_debugger.handle_breakpoint_condition(info, exc_break, frame):
348+
should_stop = False
338349

339-
if main_debugger.skip_on_exceptions_thrown_in_same_context:
340-
# Option: Don't break if an exception is caught in the same function from which it is thrown
341-
return False, frame
350+
elif was_just_raised and main_debugger.skip_on_exceptions_thrown_in_same_context:
351+
# Option: Don't break if an exception is caught in the same function from which it is thrown
352+
should_stop = False
342353

343-
if exception_breakpoint.notify_on_first_raise_only:
344-
if main_debugger.skip_on_exceptions_thrown_in_same_context:
345-
# In this case we never stop if it was just raised, so, to know if it was the first we
346-
# need to check if we're in the 2nd method.
347-
if not was_just_raised and not just_raised(trace.tb_next):
348-
return False, frame # I.e.: we stop only when we're at the caller of a method that throws an exception
354+
elif exc_break.notify_on_first_raise_only and main_debugger.skip_on_exceptions_thrown_in_same_context \
355+
and not was_just_raised and not just_raised(trace.tb_next):
356+
# In this case we never stop if it was just raised, so, to know if it was the first we
357+
# need to check if we're in the 2nd method.
358+
should_stop = False # I.e.: we stop only when we're at the caller of a method that throws an exception
349359

350-
else:
351-
if not was_just_raised:
352-
return False, frame # I.e.: we stop only when it was just raised
360+
elif exc_break.notify_on_first_raise_only and not main_debugger.skip_on_exceptions_thrown_in_same_context \
361+
and not was_just_raised:
362+
should_stop = False # I.e.: we stop only when it was just raised
353363

354-
# If it got here we should stop.
355-
should_stop = True
356-
try:
357-
info.pydev_message = exception_breakpoint.qname
358-
except:
359-
info.pydev_message = exception_breakpoint.qname.encode('utf-8')
364+
elif is_user_uncaught and not (
365+
not main_debugger.apply_files_filter(frame, frame.f_code.co_filename, True)
366+
and (frame.f_back is None or main_debugger.apply_files_filter(frame.f_back, frame.f_back.f_code.co_filename, True))):
367+
# User uncaught means that we're currently in user code but the code
368+
# up the stack is library code.
369+
should_stop = False
370+
371+
if should_stop:
372+
exception_breakpoint = exc_break
373+
try:
374+
info.pydev_message = exc_break.qname
375+
except:
376+
info.pydev_message = exc_break.qname.encode('utf-8')
377+
break
360378

361379
if should_stop:
362380
# Always add exception to frame (must remove later after we proceed).
@@ -582,7 +600,10 @@ cdef class PyDBFrame:
582600
return None if event == 'call' else NO_FTRACE
583601

584602
plugin_manager = main_debugger.plugin
585-
has_exception_breakpoints = main_debugger.break_on_caught_exceptions or main_debugger.has_plugin_exception_breaks
603+
has_exception_breakpoints = (
604+
main_debugger.break_on_caught_exceptions
605+
or main_debugger.break_on_user_uncaught_exceptions
606+
or main_debugger.has_plugin_exception_breaks)
586607

587608
stop_frame = info.pydev_step_stop
588609
step_cmd = info.pydev_step_cmd

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_frame.py

Lines changed: 57 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@ def trace_exception(self, frame, event, arg):
115115
# IFDEF CYTHON
116116
# def should_stop_on_exception(self, frame, str event, arg):
117117
# cdef PyDBAdditionalThreadInfo info;
118-
# cdef bint flag;
118+
# cdef bint should_stop;
119+
# cdef bint was_just_raised;
120+
# cdef list check_excs;
119121
# ELSE
120122
def should_stop_on_exception(self, frame, event, arg):
121123
# ENDIF
@@ -143,55 +145,71 @@ def should_stop_on_exception(self, frame, event, arg):
143145
pydev_log.exception()
144146

145147
if not should_stop:
148+
was_just_raised = trace.tb_next is None
149+
146150
# It was not handled by any plugin, lets check exception breakpoints.
147-
exception_breakpoint = main_debugger.get_exception_breakpoint(
151+
check_excs = []
152+
exc_break_caught = main_debugger.get_exception_breakpoint(
148153
exception, main_debugger.break_on_caught_exceptions)
154+
if exc_break_caught is not None:
155+
check_excs.append((exc_break_caught, False))
156+
157+
exc_break_user = main_debugger.get_exception_breakpoint(
158+
exception, main_debugger.break_on_user_uncaught_exceptions)
159+
if exc_break_user is not None:
160+
check_excs.append((exc_break_user, True))
161+
162+
for exc_break, is_user_uncaught in check_excs:
163+
# Initially mark that it should stop and then go into exclusions.
164+
should_stop = True
149165

150-
if exception_breakpoint is not None:
151166
if exception is SystemExit and main_debugger.ignore_system_exit_code(value):
152-
return False, frame
167+
should_stop = False
153168

154-
if exception in (GeneratorExit, StopIteration):
169+
elif exception in (GeneratorExit, StopIteration):
155170
# These exceptions are control-flow related (they work as a generator
156171
# pause), so, we shouldn't stop on them.
157-
return False, frame
158-
159-
if exception_breakpoint.condition is not None:
160-
eval_result = main_debugger.handle_breakpoint_condition(info, exception_breakpoint, frame)
161-
if not eval_result:
162-
return False, frame
172+
should_stop = False
163173

164-
if main_debugger.exclude_exception_by_filter(exception_breakpoint, trace):
174+
elif main_debugger.exclude_exception_by_filter(exc_break, trace):
165175
pydev_log.debug("Ignore exception %s in library %s -- (%s)" % (exception, frame.f_code.co_filename, frame.f_code.co_name))
166-
return False, frame
176+
should_stop = False
167177

168-
if ignore_exception_trace(trace):
169-
return False, frame
178+
elif ignore_exception_trace(trace):
179+
should_stop = False
170180

171-
was_just_raised = just_raised(trace)
172-
if was_just_raised:
181+
elif exc_break.condition is not None and \
182+
not main_debugger.handle_breakpoint_condition(info, exc_break, frame):
183+
should_stop = False
173184

174-
if main_debugger.skip_on_exceptions_thrown_in_same_context:
175-
# Option: Don't break if an exception is caught in the same function from which it is thrown
176-
return False, frame
185+
elif was_just_raised and main_debugger.skip_on_exceptions_thrown_in_same_context:
186+
# Option: Don't break if an exception is caught in the same function from which it is thrown
187+
should_stop = False
177188

178-
if exception_breakpoint.notify_on_first_raise_only:
179-
if main_debugger.skip_on_exceptions_thrown_in_same_context:
180-
# In this case we never stop if it was just raised, so, to know if it was the first we
181-
# need to check if we're in the 2nd method.
182-
if not was_just_raised and not just_raised(trace.tb_next):
183-
return False, frame # I.e.: we stop only when we're at the caller of a method that throws an exception
189+
elif exc_break.notify_on_first_raise_only and main_debugger.skip_on_exceptions_thrown_in_same_context \
190+
and not was_just_raised and not just_raised(trace.tb_next):
191+
# In this case we never stop if it was just raised, so, to know if it was the first we
192+
# need to check if we're in the 2nd method.
193+
should_stop = False # I.e.: we stop only when we're at the caller of a method that throws an exception
184194

185-
else:
186-
if not was_just_raised:
187-
return False, frame # I.e.: we stop only when it was just raised
195+
elif exc_break.notify_on_first_raise_only and not main_debugger.skip_on_exceptions_thrown_in_same_context \
196+
and not was_just_raised:
197+
should_stop = False # I.e.: we stop only when it was just raised
188198

189-
# If it got here we should stop.
190-
should_stop = True
191-
try:
192-
info.pydev_message = exception_breakpoint.qname
193-
except:
194-
info.pydev_message = exception_breakpoint.qname.encode('utf-8')
199+
elif is_user_uncaught and not (
200+
not main_debugger.apply_files_filter(frame, frame.f_code.co_filename, True)
201+
and (frame.f_back is None or main_debugger.apply_files_filter(frame.f_back, frame.f_back.f_code.co_filename, True))):
202+
# User uncaught means that we're currently in user code but the code
203+
# up the stack is library code.
204+
should_stop = False
205+
206+
if should_stop:
207+
exception_breakpoint = exc_break
208+
try:
209+
info.pydev_message = exc_break.qname
210+
except:
211+
info.pydev_message = exc_break.qname.encode('utf-8')
212+
break
195213

196214
if should_stop:
197215
# Always add exception to frame (must remove later after we proceed).
@@ -417,7 +435,10 @@ def trace_dispatch(self, frame, event, arg):
417435
return None if event == 'call' else NO_FTRACE
418436

419437
plugin_manager = main_debugger.plugin
420-
has_exception_breakpoints = main_debugger.break_on_caught_exceptions or main_debugger.has_plugin_exception_breaks
438+
has_exception_breakpoints = (
439+
main_debugger.break_on_caught_exceptions
440+
or main_debugger.break_on_user_uncaught_exceptions
441+
or main_debugger.has_plugin_exception_breaks)
421442

422443
stop_frame = info.pydev_step_stop
423444
step_cmd = info.pydev_step_cmd

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ def make_get_thread_stack_message(self, py_db, seq, thread_id, topmost_frame, fm
236236
# Create a source-reference to be used where we provide the source by decompiling the code.
237237
# Note: When the time comes to retrieve the source reference in this case, we'll
238238
# check the linecache first (see: get_decompiled_source_from_frame_id).
239-
source_reference = pydevd_file_utils.create_source_reference_for_frame_id(frame_id)
239+
source_reference = pydevd_file_utils.create_source_reference_for_frame_id(frame_id, original_filename)
240240
else:
241241
# Check if someone added a source reference to the linecache (Python attrs does this).
242242
if linecache.getline(original_filename, 1):

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ def cmd_set_py_exception(self, py_db, cmd_id, seq, text):
345345
splitted = text.split(';')
346346
py_db.break_on_uncaught_exceptions = {}
347347
py_db.break_on_caught_exceptions = {}
348+
py_db.break_on_user_uncaught_exceptions = {}
348349
if len(splitted) >= 5:
349350
if splitted[0] == 'true':
350351
break_on_uncaught = True
@@ -382,6 +383,7 @@ def cmd_set_py_exception(self, py_db, cmd_id, seq, text):
382383
expression=None,
383384
notify_on_handled_exceptions=break_on_caught,
384385
notify_on_unhandled_exceptions=break_on_uncaught,
386+
notify_on_user_unhandled_exceptions=False, # TODO (not currently supported in this API).
385387
notify_on_first_raise_only=True,
386388
ignore_libraries=ignore_libraries,
387389
)
@@ -480,6 +482,7 @@ def cmd_add_exception_break(self, py_db, cmd_id, seq, text):
480482
py_db, exception, condition, expression,
481483
notify_on_handled_exceptions=int(notify_on_handled_exceptions) > 0,
482484
notify_on_unhandled_exceptions=int(notify_on_unhandled_exceptions) == 1,
485+
notify_on_user_unhandled_exceptions=0, # TODO (not currently supported in this API).
483486
notify_on_first_raise_only=int(notify_on_handled_exceptions) == 2,
484487
ignore_libraries=int(ignore_libraries) > 0,
485488
)

0 commit comments

Comments
 (0)