Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
fabioz committed Nov 15, 2023
1 parent 063bd81 commit 6e7da6b
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 16 deletions.
6 changes: 6 additions & 0 deletions _pydevd_bundle/pydevd_cython_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,10 @@

fix_top_level_trace_and_get_trace_func = mod.fix_top_level_trace_and_get_trace_func

handle_exception = mod.handle_exception

should_stop_on_exception = mod.should_stop_on_exception

is_unhandled_exception = mod.is_unhandled_exception

version = getattr(mod, 'version', 0)
16 changes: 8 additions & 8 deletions _pydevd_bundle/pydevd_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def get_smart_step_into_variant_from_frame_offset(*args, **kwargs):


# IFDEF CYTHON
# cdef is_unhandled_exception(container_obj, py_db, frame, int last_raise_line, set raise_lines):
# def is_unhandled_exception(container_obj, py_db, frame, int last_raise_line, set raise_lines):
# ELSE
def is_unhandled_exception(container_obj, py_db, frame, last_raise_line, raise_lines):
# ENDIF
Expand Down Expand Up @@ -114,8 +114,8 @@ def is_unhandled_exception(container_obj, py_db, frame, last_raise_line, raise_l
# ELSE
class _TryExceptContainerObj(object):
'''
A dumb container object just to containe the try..except info when needed. Meant to be
persisent among multiple PyDBFrames to the same code object.
A dumb container object just to contain the try..except info when needed. Meant to be
persistent among multiple PyDBFrames to the same code object.
'''
try_except_infos = None
# ENDIF
Expand Down Expand Up @@ -1012,11 +1012,11 @@ def trace_dispatch(self, frame, event, arg):
# cdef bint was_just_raised;
# cdef list check_excs;
# ELSE
def should_stop_on_exception(py_db, info, frame, thread, arg, prev_exc_info):
def should_stop_on_exception(py_db, info, frame, thread, arg, prev_user_uncaught_exc_info):
# ENDIF

should_stop = False
return_exc_info = prev_exc_info
maybe_user_uncaught_exc_info = prev_user_uncaught_exc_info

# STATE_SUSPEND = 2
if info.pydev_state != 2: # and breakpoint is not None:
Expand Down Expand Up @@ -1084,14 +1084,14 @@ def should_stop_on_exception(py_db, info, frame, thread, arg, prev_exc_info):
and (frame.f_back is None or py_db.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 = prev_exc_info
exc_info = prev_user_uncaught_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)
return_exc_info = exc_info
maybe_user_uncaught_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 py_db.skip_on_exceptions_thrown_in_same_context \
Expand Down Expand Up @@ -1123,7 +1123,7 @@ def should_stop_on_exception(py_db, info, frame, thread, arg, prev_exc_info):
if exception_breakpoint is not None and exception_breakpoint.expression is not None:
py_db.handle_breakpoint_expression(exception_breakpoint, info, frame)

return should_stop, frame, return_exc_info
return should_stop, frame, maybe_user_uncaught_exc_info


# Same thing in the main debugger but only considering the file contents, while the one in the main debugger
Expand Down
4 changes: 4 additions & 0 deletions _pydevd_bundle/pydevd_trace_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,19 @@ def delete_old_compiled_extensions():
if USE_CYTHON_FLAG in ENV_TRUE_LOWER_VALUES:
# We must import the cython version if forcing cython
from _pydevd_bundle.pydevd_cython_wrapper import trace_dispatch, global_cache_skips, global_cache_frame_skips, fix_top_level_trace_and_get_trace_func
from _pydevd_bundle.pydevd_cython_wrapper import should_stop_on_exception, handle_exception, is_unhandled_exception
USING_CYTHON = True

elif USE_CYTHON_FLAG in ENV_FALSE_LOWER_VALUES:
# Use the regular version if not forcing cython
from _pydevd_bundle.pydevd_trace_dispatch_regular import trace_dispatch, global_cache_skips, global_cache_frame_skips, fix_top_level_trace_and_get_trace_func # @UnusedImport
from .pydevd_frame import should_stop_on_exception, handle_exception, is_unhandled_exception

else:
# Regular: use fallback if not found and give message to user
try:
from _pydevd_bundle.pydevd_cython_wrapper import trace_dispatch, global_cache_skips, global_cache_frame_skips, fix_top_level_trace_and_get_trace_func
from _pydevd_bundle.pydevd_cython_wrapper import should_stop_on_exception, handle_exception, is_unhandled_exception

# This version number is always available
from _pydevd_bundle.pydevd_additional_thread_info_regular import version as regular_version
Expand All @@ -58,5 +61,6 @@ def delete_old_compiled_extensions():

except ImportError:
from _pydevd_bundle.pydevd_trace_dispatch_regular import trace_dispatch, global_cache_skips, global_cache_frame_skips, fix_top_level_trace_and_get_trace_func # @UnusedImport
from .pydevd_frame import should_stop_on_exception, handle_exception, is_unhandled_exception
pydev_log.show_compile_cython_command_line()

86 changes: 82 additions & 4 deletions _pydevd_sys_monitoring/pydevd_sys_monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
from _pydevd_bundle import pydevd_dont_trace
from _pydevd_bundle.pydevd_additional_thread_info import _set_additional_thread_info_lock, PyDBAdditionalThreadInfo
from _pydevd_bundle.pydevd_constants import GlobalDebuggerHolder, ForkSafeLock, \
PYDEVD_IPYTHON_CONTEXT
PYDEVD_IPYTHON_CONTEXT, EXCEPTION_TYPE_USER_UNHANDLED
from pydevd_file_utils import NORM_PATHS_AND_BASE_CONTAINER, \
get_abs_path_real_path_and_base_from_file, \
get_abs_path_real_path_and_base_from_frame
from _pydevd_bundle.pydevd_trace_dispatch import should_stop_on_exception, handle_exception
from _pydevd_bundle.pydevd_constants import EXCEPTION_TYPE_HANDLED
from _pydevd_bundle.pydevd_trace_dispatch import is_unhandled_exception

if hasattr(sys, 'monitoring'):
DEBUGGER_ID = sys.monitoring.DEBUGGER_ID
Expand Down Expand Up @@ -194,6 +197,7 @@ def __init__(self):
self.bp_line_to_breakpoint: Set[int] = set()
self.function_breakpoint = None
self.filtered_out: Optional[bool] = None
self.try_except_container_obj: Optional[_TryExceptContainerObj] = None


def _get_thread_info(create: bool, depth:int) -> Optional[ThreadInfo]:
Expand Down Expand Up @@ -443,6 +447,52 @@ def _enable_code_tracing(thread, code, frame, warn_on_filtered_out):
# pass


class _TryExceptContainerObj(object):
'''
A dumb container object just to contain the try..except info when needed. Meant to be
persistent among multiple PyDBFrames to the same code object.
'''
try_except_infos = None


def _unwind_event(code, instruction, exc):
try:
thread_info = _thread_local_info.thread_info
except:
thread_info = _get_thread_info(True, 1)
if thread_info is None:
return

py_db: object = GlobalDebuggerHolder.global_dbg
if py_db is None or py_db.pydb_disposed:
return

if not thread_info.trace or thread_info.thread._is_stopped:
# For thread-related stuff we can't disable the code tracing because other
# threads may still want it...
return

func_code_info: FuncCodeInfo = get_func_code_info(code)
if func_code_info.always_skip_code:
return

print('_unwind_event', code, exc)
frame = sys._getframe(1)
arg = (type(exc), exc, exc.__traceback__)

_should_stop, frame, user_uncaught_exc_info = should_stop_on_exception(py_db, thread_info.additional_info, frame, thread_info.thread, arg, None)
if user_uncaught_exc_info:
if func_code_info.try_except_container_obj is None:
container_obj = _TryExceptContainerObj()
container_obj.try_except_infos = py_db.collect_try_except_info(frame.f_code)
func_code_info.try_except_container_obj = container_obj

if is_unhandled_exception(func_code_info.try_except_container_obj, py_db, frame, user_uncaught_exc_info[1], user_uncaught_exc_info[2]):
print('stop in user uncaught')
handle_exception(py_db, thread_info.thread, frame, user_uncaught_exc_info[0], EXCEPTION_TYPE_USER_UNHANDLED)
return


def _raise_event(code, instruction, exc):
'''
The way this should work is the following: when the user is using
Expand All @@ -454,8 +504,34 @@ def _raise_event(code, instruction, exc):
Note: unlike other events, this one is global and not per-code (so,
it cannot be individually enabled/disabled for a given code object).
'''
thread_info = _get_thread_info(True, 1)
if thread_info is None:
try:
thread_info = _thread_local_info.thread_info
except:
thread_info = _get_thread_info(True, 1)
if thread_info is None:
return

py_db: object = GlobalDebuggerHolder.global_dbg
if py_db is None or py_db.pydb_disposed:
return

if not thread_info.trace or thread_info.thread._is_stopped:
# For thread-related stuff we can't disable the code tracing because other
# threads may still want it...
return

func_code_info: FuncCodeInfo = get_func_code_info(code)
if func_code_info.always_skip_code:
return

print('_raise_event --- ', code, exc)

frame = sys._getframe(1)
arg = (type(exc), exc, exc.__traceback__)
should_stop, frame, _user_uncaught_exc_info = should_stop_on_exception(py_db, thread_info.additional_info, frame, thread_info.thread, arg, None)
print('!!!! should_stop (in raise)', should_stop)
if should_stop:
handle_exception(py_db, thread_info.thread, frame, arg, EXCEPTION_TYPE_HANDLED)
return

thread_info.get_frame_to_consider_unhandled_exception(depth=1)
Expand Down Expand Up @@ -841,11 +917,13 @@ def restart_events(breakpoints_changed: bool=False) -> None:
or py_db.has_plugin_exception_breaks)

if has_exception_breakpoint_in_pydb:
required_events |= monitor.events.RAISE
required_events |= monitor.events.RAISE | monitor.events.PY_UNWIND
print('track RAISE')
monitor.register_callback(DEBUGGER_ID, monitor.events.RAISE, _raise_event)
monitor.register_callback(DEBUGGER_ID, monitor.events.PY_UNWIND, _unwind_event)
else:
monitor.register_callback(DEBUGGER_ID, monitor.events.RAISE, None)
monitor.register_callback(DEBUGGER_ID, monitor.events.PY_UNWIND, None)

has_line_breaks = py_db.has_plugin_line_breaks

Expand Down
13 changes: 9 additions & 4 deletions tests_python/test_debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -1402,10 +1402,15 @@ def additional_output_checks(writer, stdout, stderr):
writer.write_run_thread(hit.thread_id)

if not unhandled:
expected_lines = [
writer.get_line_index_with_content('# exc line'),
writer.get_line_index_with_content('# call exc'),
]
if sys.version_info[:2] >= (3, 12):
expected_lines = [
writer.get_line_index_with_content('# call exc'),
]
else:
expected_lines = [
writer.get_line_index_with_content('# exc line'),
writer.get_line_index_with_content('# call exc'),
]

for expected_line in expected_lines:
hit = writer.wait_for_breakpoint_hit(REASON_CAUGHT_EXCEPTION)
Expand Down

0 comments on commit 6e7da6b

Please sign in to comment.