You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2174 lines
97 KiB
Cython
2174 lines
97 KiB
Cython
from __future__ import print_function
|
|
|
|
# Important: Autogenerated file.
|
|
|
|
# DO NOT edit manually!
|
|
# DO NOT edit manually!
|
|
from _pydevd_bundle.pydevd_constants import (
|
|
STATE_RUN,
|
|
PYTHON_SUSPEND,
|
|
SUPPORT_GEVENT,
|
|
ForkSafeLock,
|
|
_current_frames,
|
|
STATE_SUSPEND,
|
|
get_global_debugger,
|
|
get_thread_id,
|
|
)
|
|
from _pydev_bundle import pydev_log
|
|
from _pydev_bundle._pydev_saved_modules import threading
|
|
from _pydev_bundle.pydev_is_thread_alive import is_thread_alive
|
|
import weakref
|
|
|
|
version = 11
|
|
|
|
|
|
# =======================================================================================================================
|
|
# PyDBAdditionalThreadInfo
|
|
# =======================================================================================================================
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef class PyDBAdditionalThreadInfo:
|
|
# ELSE
|
|
# class PyDBAdditionalThreadInfo(object):
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
# Note: the params in cython are declared in pydevd_cython.pxd.
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
# ELSE
|
|
# __slots__ = [
|
|
# "pydev_state",
|
|
# "pydev_step_stop",
|
|
# "pydev_original_step_cmd",
|
|
# "pydev_step_cmd",
|
|
# "pydev_notify_kill",
|
|
# "pydev_django_resolve_frame",
|
|
# "pydev_call_from_jinja2",
|
|
# "pydev_call_inside_jinja2",
|
|
# "is_tracing",
|
|
# "conditional_breakpoint_exception",
|
|
# "pydev_message",
|
|
# "suspend_type",
|
|
# "pydev_next_line",
|
|
# "pydev_func_name",
|
|
# "suspended_at_unhandled",
|
|
# "trace_suspend_type",
|
|
# "top_level_thread_tracer_no_back_frames",
|
|
# "top_level_thread_tracer_unhandled",
|
|
# "thread_tracer",
|
|
# "step_in_initial_location",
|
|
# # Used for CMD_SMART_STEP_INTO (to know which smart step into variant to use)
|
|
# "pydev_smart_parent_offset",
|
|
# "pydev_smart_child_offset",
|
|
# # Used for CMD_SMART_STEP_INTO (list[_pydevd_bundle.pydevd_bytecode_utils.Variant])
|
|
# # Filled when the cmd_get_smart_step_into_variants is requested (so, this is a copy
|
|
# # of the last request for a given thread and pydev_smart_parent_offset/pydev_smart_child_offset relies on it).
|
|
# "pydev_smart_step_into_variants",
|
|
# "target_id_to_smart_step_into_variant",
|
|
# "pydev_use_scoped_step_frame",
|
|
# "weak_thread",
|
|
# "is_in_wait_loop",
|
|
# ]
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
def __init__(self):
|
|
self.pydev_state = STATE_RUN # STATE_RUN or STATE_SUSPEND
|
|
self.pydev_step_stop = None
|
|
|
|
# Note: we have `pydev_original_step_cmd` and `pydev_step_cmd` because the original is to
|
|
# say the action that started it and the other is to say what's the current tracing behavior
|
|
# (because it's possible that we start with a step over but may have to switch to a
|
|
# different step strategy -- for instance, if a step over is done and we return the current
|
|
# method the strategy is changed to a step in).
|
|
|
|
self.pydev_original_step_cmd = -1 # Something as CMD_STEP_INTO, CMD_STEP_OVER, etc.
|
|
self.pydev_step_cmd = -1 # Something as CMD_STEP_INTO, CMD_STEP_OVER, etc.
|
|
|
|
self.pydev_notify_kill = False
|
|
self.pydev_django_resolve_frame = False
|
|
self.pydev_call_from_jinja2 = None
|
|
self.pydev_call_inside_jinja2 = None
|
|
self.is_tracing = 0
|
|
self.conditional_breakpoint_exception = None
|
|
self.pydev_message = ""
|
|
self.suspend_type = PYTHON_SUSPEND
|
|
self.pydev_next_line = -1
|
|
self.pydev_func_name = ".invalid." # Must match the type in cython
|
|
self.suspended_at_unhandled = False
|
|
self.trace_suspend_type = "trace" # 'trace' or 'frame_eval'
|
|
self.top_level_thread_tracer_no_back_frames = []
|
|
self.top_level_thread_tracer_unhandled = None
|
|
self.thread_tracer = None
|
|
self.step_in_initial_location = None
|
|
self.pydev_smart_parent_offset = -1
|
|
self.pydev_smart_child_offset = -1
|
|
self.pydev_smart_step_into_variants = ()
|
|
self.target_id_to_smart_step_into_variant = {}
|
|
|
|
# Flag to indicate ipython use-case where each line will be executed as a call/line/return
|
|
# in a new new frame but in practice we want to consider each new frame as if it was all
|
|
# part of the same frame.
|
|
#
|
|
# In practice this means that a step over shouldn't revert to a step in and we need some
|
|
# special logic to know when we should stop in a step over as we need to consider 2
|
|
# different frames as being equal if they're logically the continuation of a frame
|
|
# being executed by ipython line by line.
|
|
#
|
|
# See: https://github.com/microsoft/debugpy/issues/869#issuecomment-1132141003
|
|
self.pydev_use_scoped_step_frame = False
|
|
self.weak_thread = None
|
|
|
|
# Purpose: detect if this thread is suspended and actually in the wait loop
|
|
# at this time (otherwise it may be suspended but still didn't reach a point.
|
|
# to pause).
|
|
self.is_in_wait_loop = False
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cpdef object _get_related_thread(self):
|
|
# ELSE
|
|
# def _get_related_thread(self):
|
|
# ENDIF
|
|
# fmt: on
|
|
if self.pydev_notify_kill: # Already killed
|
|
return None
|
|
|
|
if self.weak_thread is None:
|
|
return None
|
|
|
|
thread = self.weak_thread()
|
|
if thread is None:
|
|
return False
|
|
|
|
if not is_thread_alive(thread):
|
|
return None
|
|
|
|
if thread._ident is None: # Can this happen?
|
|
pydev_log.critical("thread._ident is None in _get_related_thread!")
|
|
return None
|
|
|
|
if threading._active.get(thread._ident) is not thread:
|
|
return None
|
|
|
|
return thread
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cpdef bint _is_stepping(self):
|
|
# ELSE
|
|
# def _is_stepping(self):
|
|
# ENDIF
|
|
# fmt: on
|
|
if self.pydev_state == STATE_RUN and self.pydev_step_cmd != -1:
|
|
# This means actually stepping in a step operation.
|
|
return True
|
|
|
|
if self.pydev_state == STATE_SUSPEND and self.is_in_wait_loop:
|
|
# This means stepping because it was suspended but still didn't
|
|
# reach a suspension point.
|
|
return True
|
|
|
|
return False
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cpdef get_topmost_frame(self, thread):
|
|
# ELSE
|
|
# def get_topmost_frame(self, thread):
|
|
# ENDIF
|
|
# fmt: on
|
|
"""
|
|
Gets the topmost frame for the given thread. Note that it may be None
|
|
and callers should remove the reference to the frame as soon as possible
|
|
to avoid disturbing user code.
|
|
"""
|
|
# sys._current_frames(): dictionary with thread id -> topmost frame
|
|
current_frames = _current_frames()
|
|
topmost_frame = current_frames.get(thread._ident)
|
|
if topmost_frame is None:
|
|
# Note: this is expected for dummy threads (so, getting the topmost frame should be
|
|
# treated as optional).
|
|
pydev_log.info(
|
|
"Unable to get topmost frame for thread: %s, thread.ident: %s, id(thread): %s\nCurrent frames: %s.\n" "GEVENT_SUPPORT: %s",
|
|
thread,
|
|
thread.ident,
|
|
id(thread),
|
|
current_frames,
|
|
SUPPORT_GEVENT,
|
|
)
|
|
|
|
return topmost_frame
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cpdef update_stepping_info(self):
|
|
# ELSE
|
|
# def update_stepping_info(self):
|
|
# ENDIF
|
|
# fmt: on
|
|
_update_stepping_info(self)
|
|
|
|
def __str__(self):
|
|
return "State:%s Stop:%s Cmd: %s Kill:%s" % (self.pydev_state, self.pydev_step_stop, self.pydev_step_cmd, self.pydev_notify_kill)
|
|
|
|
|
|
_set_additional_thread_info_lock = ForkSafeLock()
|
|
_next_additional_info = [PyDBAdditionalThreadInfo()]
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cpdef set_additional_thread_info(thread):
|
|
# ELSE
|
|
# def set_additional_thread_info(thread):
|
|
# ENDIF
|
|
# fmt: on
|
|
try:
|
|
additional_info = thread.additional_info
|
|
if additional_info is None:
|
|
raise AttributeError()
|
|
except:
|
|
with _set_additional_thread_info_lock:
|
|
# If it's not there, set it within a lock to avoid any racing
|
|
# conditions.
|
|
try:
|
|
additional_info = thread.additional_info
|
|
except:
|
|
additional_info = None
|
|
|
|
if additional_info is None:
|
|
# Note: don't call PyDBAdditionalThreadInfo constructor at this
|
|
# point as it can piggy-back into the debugger which could
|
|
# get here again, rather get the global ref which was pre-created
|
|
# and add a new entry only after we set thread.additional_info.
|
|
additional_info = _next_additional_info[0]
|
|
thread.additional_info = additional_info
|
|
additional_info.weak_thread = weakref.ref(thread)
|
|
add_additional_info(additional_info)
|
|
del _next_additional_info[:]
|
|
_next_additional_info.append(PyDBAdditionalThreadInfo())
|
|
|
|
return additional_info
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef set _all_infos
|
|
cdef set _infos_stepping
|
|
cdef object _update_infos_lock
|
|
# ELSE
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
_all_infos = set()
|
|
_infos_stepping = set()
|
|
_update_infos_lock = ForkSafeLock()
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _update_stepping_info(PyDBAdditionalThreadInfo info):
|
|
# ELSE
|
|
# def _update_stepping_info(info):
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
global _infos_stepping
|
|
global _all_infos
|
|
|
|
with _update_infos_lock:
|
|
# Removes entries that are no longer valid.
|
|
new_all_infos = set()
|
|
for info in _all_infos:
|
|
if info._get_related_thread() is not None:
|
|
new_all_infos.add(info)
|
|
_all_infos = new_all_infos
|
|
|
|
new_stepping = set()
|
|
for info in _all_infos:
|
|
if info._is_stepping():
|
|
new_stepping.add(info)
|
|
_infos_stepping = new_stepping
|
|
|
|
py_db = get_global_debugger()
|
|
if py_db is not None and not py_db.pydb_disposed:
|
|
thread = info.weak_thread()
|
|
if thread is not None:
|
|
thread_id = get_thread_id(thread)
|
|
_queue, event = py_db.get_internal_queue_and_event(thread_id)
|
|
event.set()
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cpdef add_additional_info(PyDBAdditionalThreadInfo info):
|
|
# ELSE
|
|
# def add_additional_info(info):
|
|
# ENDIF
|
|
# fmt: on
|
|
with _update_infos_lock:
|
|
_all_infos.add(info)
|
|
if info._is_stepping():
|
|
_infos_stepping.add(info)
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cpdef remove_additional_info(PyDBAdditionalThreadInfo info):
|
|
# ELSE
|
|
# def remove_additional_info(info):
|
|
# ENDIF
|
|
# fmt: on
|
|
with _update_infos_lock:
|
|
_all_infos.discard(info)
|
|
_infos_stepping.discard(info)
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cpdef bint any_thread_stepping():
|
|
# ELSE
|
|
# def any_thread_stepping():
|
|
# ENDIF
|
|
# fmt: on
|
|
return bool(_infos_stepping)
|
|
import linecache
|
|
import os.path
|
|
import re
|
|
|
|
from _pydev_bundle import pydev_log
|
|
from _pydevd_bundle import pydevd_dont_trace
|
|
from _pydevd_bundle.pydevd_constants import (
|
|
RETURN_VALUES_DICT,
|
|
NO_FTRACE,
|
|
EXCEPTION_TYPE_HANDLED,
|
|
EXCEPTION_TYPE_USER_UNHANDLED,
|
|
PYDEVD_IPYTHON_CONTEXT,
|
|
PYDEVD_USE_SYS_MONITORING,
|
|
)
|
|
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
|
|
from _pydevd_bundle.pydevd_comm_constants import constant_to_str, CMD_SET_FUNCTION_BREAK
|
|
import sys
|
|
|
|
try:
|
|
from _pydevd_bundle.pydevd_bytecode_utils import get_smart_step_into_variant_from_frame_offset
|
|
except ImportError:
|
|
|
|
def get_smart_step_into_variant_from_frame_offset(*args, **kwargs):
|
|
return None
|
|
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
# ELSE
|
|
# # Note: those are now inlined on cython.
|
|
# 107 = 107
|
|
# 144 = 144
|
|
# 109 = 109
|
|
# 160 = 160
|
|
# 108 = 108
|
|
# 159 = 159
|
|
# 137 = 137
|
|
# 111 = 111
|
|
# 128 = 128
|
|
# 206 = 206
|
|
# 1 = 1
|
|
# 2 = 2
|
|
# ENDIF
|
|
|
|
basename = os.path.basename
|
|
|
|
IGNORE_EXCEPTION_TAG = re.compile("[^#]*#.*@IgnoreException")
|
|
DEBUG_START = ("pydevd.py", "run")
|
|
DEBUG_START_PY3K = ("_pydev_execfile.py", "execfile")
|
|
TRACE_PROPERTY = "pydevd_traceproperty.py"
|
|
|
|
import dis
|
|
|
|
try:
|
|
StopAsyncIteration
|
|
except NameError:
|
|
StopAsyncIteration = StopIteration
|
|
|
|
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
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
|
|
if frame.f_lineno in raise_lines:
|
|
return True
|
|
|
|
else:
|
|
try_except_infos = container_obj.try_except_infos
|
|
if try_except_infos is None:
|
|
container_obj.try_except_infos = try_except_infos = py_db.collect_try_except_info(frame.f_code)
|
|
|
|
if not try_except_infos:
|
|
# Consider the last exception as unhandled because there's no try..except in it.
|
|
return True
|
|
else:
|
|
# Now, consider only the try..except for the raise
|
|
valid_try_except_infos = []
|
|
for try_except_info in try_except_infos:
|
|
if try_except_info.is_line_in_try_block(last_raise_line):
|
|
valid_try_except_infos.append(try_except_info)
|
|
|
|
if not valid_try_except_infos:
|
|
return True
|
|
|
|
else:
|
|
# Note: check all, not only the "valid" ones to cover the case
|
|
# in "tests_python.test_tracing_on_top_level.raise_unhandled10"
|
|
# where one try..except is inside the other with only a raise
|
|
# and it's gotten in the except line.
|
|
for try_except_info in try_except_infos:
|
|
if try_except_info.is_line_in_except_block(frame.f_lineno):
|
|
if frame.f_lineno == try_except_info.except_line or frame.f_lineno in try_except_info.raise_lines_in_except:
|
|
# In a raise inside a try..except block or some except which doesn't
|
|
# match the raised exception.
|
|
return True
|
|
return False
|
|
|
|
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef class _TryExceptContainerObj:
|
|
cdef public list try_except_infos;
|
|
def __init__(self):
|
|
self.try_except_infos = None
|
|
# ELSE
|
|
# 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
|
|
#
|
|
# ENDIF
|
|
|
|
|
|
# =======================================================================================================================
|
|
# PyDBFrame
|
|
# =======================================================================================================================
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef class PyDBFrame:
|
|
# ELSE
|
|
# class PyDBFrame:
|
|
# """This makes the tracing for a given frame, so, the trace_dispatch
|
|
# is used initially when we enter into a new context ('call') and then
|
|
# is reused for the entire context.
|
|
# """
|
|
#
|
|
# ENDIF
|
|
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef tuple _args
|
|
cdef int should_skip
|
|
cdef object exc_info
|
|
def __init__(self, tuple args):
|
|
self._args = args # In the cython version we don't need to pass the frame
|
|
self.should_skip = -1 # On cythonized version, put in instance.
|
|
self.exc_info = ()
|
|
# ELSE
|
|
# should_skip = -1 # Default value in class (put in instance on set).
|
|
# exc_info = () # Default value in class (put in instance on set).
|
|
#
|
|
# if PYDEVD_USE_SYS_MONITORING:
|
|
#
|
|
# def __init__(self, *args, **kwargs):
|
|
# raise RuntimeError("Not expected to be used in sys.monitoring.")
|
|
#
|
|
# else:
|
|
#
|
|
# def __init__(self, args):
|
|
# # args = py_db, abs_path_canonical_path_and_base, base, info, t, frame
|
|
# # yeap, much faster than putting in self and then getting it from self later on
|
|
# self._args = args
|
|
# ENDIF
|
|
|
|
def set_suspend(self, *args, **kwargs):
|
|
self._args[0].set_suspend(*args, **kwargs)
|
|
|
|
def do_wait_suspend(self, *args, **kwargs):
|
|
self._args[0].do_wait_suspend(*args, **kwargs)
|
|
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
def trace_exception(self, frame, str event, arg):
|
|
cdef bint should_stop;
|
|
cdef tuple exc_info;
|
|
# ELSE
|
|
# def trace_exception(self, frame, event, arg):
|
|
# ENDIF
|
|
if event == "exception":
|
|
should_stop, frame, exc_info = should_stop_on_exception(self._args[0], self._args[2], frame, self._args[3], arg, self.exc_info)
|
|
self.exc_info = exc_info
|
|
|
|
if should_stop:
|
|
if handle_exception(self._args[0], self._args[3], frame, arg, EXCEPTION_TYPE_HANDLED):
|
|
return self.trace_dispatch
|
|
|
|
elif event == "return":
|
|
exc_info = self.exc_info
|
|
if exc_info and arg is None:
|
|
frame_skips_cache, frame_cache_key = self._args[4], self._args[5]
|
|
custom_key = (frame_cache_key, "try_exc_info")
|
|
container_obj = frame_skips_cache.get(custom_key)
|
|
if container_obj is None:
|
|
container_obj = frame_skips_cache[custom_key] = _TryExceptContainerObj()
|
|
if is_unhandled_exception(container_obj, self._args[0], frame, exc_info[1], exc_info[2]) and self.handle_user_exception(
|
|
frame
|
|
):
|
|
return self.trace_dispatch
|
|
|
|
return self.trace_exception
|
|
|
|
def handle_user_exception(self, frame):
|
|
exc_info = self.exc_info
|
|
if exc_info:
|
|
return handle_exception(self._args[0], self._args[3], frame, exc_info[0], EXCEPTION_TYPE_USER_UNHANDLED)
|
|
return False
|
|
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef get_func_name(self, frame):
|
|
cdef str func_name
|
|
# ELSE
|
|
# def get_func_name(self, frame):
|
|
# ENDIF
|
|
code_obj = frame.f_code
|
|
func_name = code_obj.co_name
|
|
try:
|
|
cls_name = get_clsname_for_code(code_obj, frame)
|
|
if cls_name is not None:
|
|
return "%s.%s" % (cls_name, func_name)
|
|
else:
|
|
return func_name
|
|
except:
|
|
pydev_log.exception()
|
|
return func_name
|
|
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _show_return_values(self, frame, arg):
|
|
# ELSE
|
|
# def _show_return_values(self, frame, arg):
|
|
# ENDIF
|
|
try:
|
|
try:
|
|
f_locals_back = getattr(frame.f_back, "f_locals", None)
|
|
if f_locals_back is not None:
|
|
return_values_dict = f_locals_back.get(RETURN_VALUES_DICT, None)
|
|
if return_values_dict is None:
|
|
return_values_dict = {}
|
|
f_locals_back[RETURN_VALUES_DICT] = return_values_dict
|
|
name = self.get_func_name(frame)
|
|
return_values_dict[name] = arg
|
|
except:
|
|
pydev_log.exception()
|
|
finally:
|
|
f_locals_back = None
|
|
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _remove_return_values(self, py_db, frame):
|
|
# ELSE
|
|
# def _remove_return_values(self, py_db, frame):
|
|
# ENDIF
|
|
try:
|
|
try:
|
|
# Showing return values was turned off, we should remove them from locals dict.
|
|
# The values can be in the current frame or in the back one
|
|
frame.f_locals.pop(RETURN_VALUES_DICT, None)
|
|
|
|
f_locals_back = getattr(frame.f_back, "f_locals", None)
|
|
if f_locals_back is not None:
|
|
f_locals_back.pop(RETURN_VALUES_DICT, None)
|
|
except:
|
|
pydev_log.exception()
|
|
finally:
|
|
f_locals_back = None
|
|
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _get_unfiltered_back_frame(self, py_db, frame):
|
|
# ELSE
|
|
# def _get_unfiltered_back_frame(self, py_db, frame):
|
|
# ENDIF
|
|
f = frame.f_back
|
|
while f is not None:
|
|
if not py_db.is_files_filter_enabled:
|
|
return f
|
|
|
|
else:
|
|
if py_db.apply_files_filter(f, f.f_code.co_filename, False):
|
|
f = f.f_back
|
|
|
|
else:
|
|
return f
|
|
|
|
return f
|
|
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _is_same_frame(self, target_frame, current_frame):
|
|
cdef PyDBAdditionalThreadInfo info;
|
|
# ELSE
|
|
# def _is_same_frame(self, target_frame, current_frame):
|
|
# ENDIF
|
|
if target_frame is current_frame:
|
|
return True
|
|
|
|
info = self._args[2]
|
|
if info.pydev_use_scoped_step_frame:
|
|
# If using scoped step we don't check the target, we just need to check
|
|
# if the current matches the same heuristic where the target was defined.
|
|
if target_frame is not None and current_frame is not None:
|
|
if target_frame.f_code.co_filename == current_frame.f_code.co_filename:
|
|
# The co_name may be different (it may include the line number), but
|
|
# the filename must still be the same.
|
|
f = current_frame.f_back
|
|
if f is not None and f.f_code.co_name == PYDEVD_IPYTHON_CONTEXT[1]:
|
|
f = f.f_back
|
|
if f is not None and f.f_code.co_name == PYDEVD_IPYTHON_CONTEXT[2]:
|
|
return True
|
|
|
|
return False
|
|
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cpdef trace_dispatch(self, frame, str event, arg):
|
|
cdef tuple abs_path_canonical_path_and_base;
|
|
cdef bint is_exception_event;
|
|
cdef bint has_exception_breakpoints;
|
|
cdef bint can_skip;
|
|
cdef bint stop;
|
|
cdef bint stop_on_plugin_breakpoint;
|
|
cdef PyDBAdditionalThreadInfo info;
|
|
cdef int step_cmd;
|
|
cdef int line;
|
|
cdef bint is_line;
|
|
cdef bint is_call;
|
|
cdef bint is_return;
|
|
cdef bint should_stop;
|
|
cdef dict breakpoints_for_file;
|
|
cdef dict stop_info;
|
|
cdef str curr_func_name;
|
|
cdef dict frame_skips_cache;
|
|
cdef object frame_cache_key;
|
|
cdef tuple line_cache_key;
|
|
cdef int breakpoints_in_line_cache;
|
|
cdef int breakpoints_in_frame_cache;
|
|
cdef bint has_breakpoint_in_frame;
|
|
cdef bint is_coroutine_or_generator;
|
|
cdef int bp_line;
|
|
cdef object bp;
|
|
cdef int pydev_smart_parent_offset
|
|
cdef int pydev_smart_child_offset
|
|
cdef tuple pydev_smart_step_into_variants
|
|
# ELSE
|
|
# def trace_dispatch(self, frame, event, arg):
|
|
# ENDIF
|
|
# Note: this is a big function because most of the logic related to hitting a breakpoint and
|
|
# stepping is contained in it. Ideally this could be split among multiple functions, but the
|
|
# problem in this case is that in pure-python function calls are expensive and even more so
|
|
# when tracing is on (because each function call will get an additional tracing call). We
|
|
# try to address this by using the info.is_tracing for the fastest possible return, but the
|
|
# cost is still high (maybe we could use code-generation in the future and make the code
|
|
# generation be better split among what each part does).
|
|
|
|
try:
|
|
# DEBUG = '_debugger_case_yield_from.py' in frame.f_code.co_filename
|
|
py_db, abs_path_canonical_path_and_base, info, thread, frame_skips_cache, frame_cache_key = self._args
|
|
# if DEBUG: print('frame trace_dispatch %s %s %s %s %s %s, stop: %s' % (frame.f_lineno, frame.f_code.co_name, frame.f_code.co_filename, event, constant_to_str(info.pydev_step_cmd), arg, info.pydev_step_stop))
|
|
info.is_tracing += 1
|
|
|
|
# TODO: This shouldn't be needed. The fact that frame.f_lineno
|
|
# is None seems like a bug in Python 3.11.
|
|
# Reported in: https://github.com/python/cpython/issues/94485
|
|
line = frame.f_lineno or 0 # Workaround or case where frame.f_lineno is None
|
|
line_cache_key = (frame_cache_key, line)
|
|
|
|
if py_db.pydb_disposed:
|
|
return None if event == "call" else NO_FTRACE
|
|
|
|
plugin_manager = py_db.plugin
|
|
has_exception_breakpoints = (
|
|
py_db.break_on_caught_exceptions or py_db.break_on_user_uncaught_exceptions or py_db.has_plugin_exception_breaks
|
|
)
|
|
|
|
stop_frame = info.pydev_step_stop
|
|
step_cmd = info.pydev_step_cmd
|
|
function_breakpoint_on_call_event = None
|
|
|
|
if frame.f_code.co_flags & 0xA0: # 0xa0 == CO_GENERATOR = 0x20 | CO_COROUTINE = 0x80
|
|
# Dealing with coroutines and generators:
|
|
# When in a coroutine we change the perceived event to the debugger because
|
|
# a call, StopIteration exception and return are usually just pausing/unpausing it.
|
|
if event == "line":
|
|
is_line = True
|
|
is_call = False
|
|
is_return = False
|
|
is_exception_event = False
|
|
|
|
elif event == "return":
|
|
is_line = False
|
|
is_call = False
|
|
is_return = True
|
|
is_exception_event = False
|
|
|
|
returns_cache_key = (frame_cache_key, "returns")
|
|
return_lines = frame_skips_cache.get(returns_cache_key)
|
|
if return_lines is None:
|
|
# Note: we're collecting the return lines by inspecting the bytecode as
|
|
# there are multiple returns and multiple stop iterations when awaiting and
|
|
# it doesn't give any clear indication when a coroutine or generator is
|
|
# finishing or just pausing.
|
|
return_lines = set()
|
|
for x in py_db.collect_return_info(frame.f_code):
|
|
# Note: cython does not support closures in cpdefs (so we can't use
|
|
# a list comprehension).
|
|
return_lines.add(x.return_line)
|
|
|
|
frame_skips_cache[returns_cache_key] = return_lines
|
|
|
|
if line not in return_lines:
|
|
# Not really a return (coroutine/generator paused).
|
|
return self.trace_dispatch
|
|
else:
|
|
if self.exc_info:
|
|
self.handle_user_exception(frame)
|
|
return self.trace_dispatch
|
|
|
|
# Tricky handling: usually when we're on a frame which is about to exit
|
|
# we set the step mode to step into, but in this case we'd end up in the
|
|
# asyncio internal machinery, which is not what we want, so, we just
|
|
# ask the stop frame to be a level up.
|
|
#
|
|
# Note that there's an issue here which we may want to fix in the future: if
|
|
# the back frame is a frame which is filtered, we won't stop properly.
|
|
# Solving this may not be trivial as we'd need to put a scope in the step
|
|
# in, but we may have to do it anyways to have a step in which doesn't end
|
|
# up in asyncio).
|
|
#
|
|
# Note2: we don't revert to a step in if we're doing scoped stepping
|
|
# (because on scoped stepping we're always receiving a call/line/return
|
|
# event for each line in ipython, so, we can't revert to step in on return
|
|
# as the return shouldn't mean that we've actually completed executing a
|
|
# frame in this case).
|
|
if stop_frame is frame and not info.pydev_use_scoped_step_frame:
|
|
if step_cmd in (108, 159, 107, 144):
|
|
f = self._get_unfiltered_back_frame(py_db, frame)
|
|
if f is not None:
|
|
info.pydev_step_cmd = 206
|
|
info.pydev_step_stop = f
|
|
else:
|
|
if step_cmd == 108:
|
|
info.pydev_step_cmd = 107
|
|
info.pydev_step_stop = None
|
|
|
|
elif step_cmd == 159:
|
|
info.pydev_step_cmd = 144
|
|
info.pydev_step_stop = None
|
|
|
|
elif step_cmd == 206:
|
|
# We're exiting this one, so, mark the new coroutine context.
|
|
f = self._get_unfiltered_back_frame(py_db, frame)
|
|
if f is not None:
|
|
info.pydev_step_stop = f
|
|
else:
|
|
info.pydev_step_cmd = 107
|
|
info.pydev_step_stop = None
|
|
|
|
elif event == "exception":
|
|
breakpoints_for_file = None
|
|
if has_exception_breakpoints:
|
|
should_stop, frame, exc_info = should_stop_on_exception(
|
|
self._args[0], self._args[2], frame, self._args[3], arg, self.exc_info
|
|
)
|
|
self.exc_info = exc_info
|
|
if should_stop:
|
|
if handle_exception(self._args[0], self._args[3], frame, arg, EXCEPTION_TYPE_HANDLED):
|
|
return self.trace_dispatch
|
|
|
|
return self.trace_dispatch
|
|
else:
|
|
# event == 'call' or event == 'c_XXX'
|
|
return self.trace_dispatch
|
|
|
|
else: # Not coroutine nor generator
|
|
if event == "line":
|
|
is_line = True
|
|
is_call = False
|
|
is_return = False
|
|
is_exception_event = False
|
|
|
|
elif event == "return":
|
|
is_line = False
|
|
is_return = True
|
|
is_call = False
|
|
is_exception_event = False
|
|
|
|
# If we are in single step mode and something causes us to exit the current frame, we need to make sure we break
|
|
# eventually. Force the step mode to step into and the step stop frame to None.
|
|
# I.e.: F6 in the end of a function should stop in the next possible position (instead of forcing the user
|
|
# to make a step in or step over at that location).
|
|
# Note: this is especially troublesome when we're skipping code with the
|
|
# @DontTrace comment.
|
|
if (
|
|
stop_frame is frame
|
|
and not info.pydev_use_scoped_step_frame
|
|
and is_return
|
|
and step_cmd
|
|
in (108, 109, 159, 160, 128)
|
|
):
|
|
if step_cmd in (108, 109, 128):
|
|
info.pydev_step_cmd = 107
|
|
else:
|
|
info.pydev_step_cmd = 144
|
|
info.pydev_step_stop = None
|
|
|
|
if self.exc_info:
|
|
if self.handle_user_exception(frame):
|
|
return self.trace_dispatch
|
|
|
|
elif event == "call":
|
|
is_line = False
|
|
is_call = True
|
|
is_return = False
|
|
is_exception_event = False
|
|
if frame.f_code.co_firstlineno == frame.f_lineno: # Check line to deal with async/await.
|
|
function_breakpoint_on_call_event = py_db.function_breakpoint_name_to_breakpoint.get(frame.f_code.co_name)
|
|
|
|
elif event == "exception":
|
|
is_exception_event = True
|
|
breakpoints_for_file = None
|
|
if has_exception_breakpoints:
|
|
should_stop, frame, exc_info = should_stop_on_exception(
|
|
self._args[0], self._args[2], frame, self._args[3], arg, self.exc_info
|
|
)
|
|
self.exc_info = exc_info
|
|
if should_stop:
|
|
if handle_exception(self._args[0], self._args[3], frame, arg, EXCEPTION_TYPE_HANDLED):
|
|
return self.trace_dispatch
|
|
is_line = False
|
|
is_return = False
|
|
is_call = False
|
|
|
|
else:
|
|
# Unexpected: just keep the same trace func (i.e.: event == 'c_XXX').
|
|
return self.trace_dispatch
|
|
|
|
if not is_exception_event:
|
|
breakpoints_for_file = py_db.breakpoints.get(abs_path_canonical_path_and_base[1])
|
|
|
|
can_skip = False
|
|
|
|
if info.pydev_state == 1: # 1 = 1
|
|
# we can skip if:
|
|
# - we have no stop marked
|
|
# - we should make a step return/step over and we're not in the current frame
|
|
# - we're stepping into a coroutine context and we're not in that context
|
|
if step_cmd == -1:
|
|
can_skip = True
|
|
|
|
elif step_cmd in (
|
|
108,
|
|
109,
|
|
159,
|
|
160,
|
|
) and not self._is_same_frame(stop_frame, frame):
|
|
can_skip = True
|
|
|
|
elif step_cmd == 128 and (
|
|
stop_frame is not None
|
|
and stop_frame is not frame
|
|
and stop_frame is not frame.f_back
|
|
and (frame.f_back is None or stop_frame is not frame.f_back.f_back)
|
|
):
|
|
can_skip = True
|
|
|
|
elif step_cmd == 144:
|
|
if py_db.apply_files_filter(frame, frame.f_code.co_filename, True) and (
|
|
frame.f_back is None or py_db.apply_files_filter(frame.f_back, frame.f_back.f_code.co_filename, True)
|
|
):
|
|
can_skip = True
|
|
|
|
elif step_cmd == 206:
|
|
f = frame
|
|
while f is not None:
|
|
if self._is_same_frame(stop_frame, f):
|
|
break
|
|
f = f.f_back
|
|
else:
|
|
can_skip = True
|
|
|
|
if can_skip:
|
|
if plugin_manager is not None and (py_db.has_plugin_line_breaks or py_db.has_plugin_exception_breaks):
|
|
can_skip = plugin_manager.can_skip(py_db, frame)
|
|
|
|
if (
|
|
can_skip
|
|
and py_db.show_return_values
|
|
and info.pydev_step_cmd in (108, 159)
|
|
and self._is_same_frame(stop_frame, frame.f_back)
|
|
):
|
|
# trace function for showing return values after step over
|
|
can_skip = False
|
|
|
|
# Let's check to see if we are in a function that has a breakpoint. If we don't have a breakpoint,
|
|
# we will return nothing for the next trace
|
|
# also, after we hit a breakpoint and go to some other debugging state, we have to force the set trace anyway,
|
|
# so, that's why the additional checks are there.
|
|
|
|
if function_breakpoint_on_call_event:
|
|
pass # Do nothing here (just keep on going as we can't skip it).
|
|
|
|
elif not breakpoints_for_file:
|
|
if can_skip:
|
|
if has_exception_breakpoints:
|
|
return self.trace_exception
|
|
else:
|
|
return None if is_call else NO_FTRACE
|
|
|
|
else:
|
|
# When cached, 0 means we don't have a breakpoint and 1 means we have.
|
|
if can_skip:
|
|
breakpoints_in_line_cache = frame_skips_cache.get(line_cache_key, -1)
|
|
if breakpoints_in_line_cache == 0:
|
|
return self.trace_dispatch
|
|
|
|
breakpoints_in_frame_cache = frame_skips_cache.get(frame_cache_key, -1)
|
|
if breakpoints_in_frame_cache != -1:
|
|
# Gotten from cache.
|
|
has_breakpoint_in_frame = breakpoints_in_frame_cache == 1
|
|
|
|
else:
|
|
has_breakpoint_in_frame = False
|
|
|
|
try:
|
|
func_lines = set()
|
|
for offset_and_lineno in dis.findlinestarts(frame.f_code):
|
|
if offset_and_lineno[1] is not None:
|
|
func_lines.add(offset_and_lineno[1])
|
|
except:
|
|
# This is a fallback for implementations where we can't get the function
|
|
# lines -- i.e.: jython (in this case clients need to provide the function
|
|
# name to decide on the skip or we won't be able to skip the function
|
|
# completely).
|
|
|
|
# Checks the breakpoint to see if there is a context match in some function.
|
|
curr_func_name = frame.f_code.co_name
|
|
|
|
# global context is set with an empty name
|
|
if curr_func_name in ("?", "<module>", "<lambda>"):
|
|
curr_func_name = ""
|
|
|
|
for bp in breakpoints_for_file.values():
|
|
# will match either global or some function
|
|
if bp.func_name in ("None", curr_func_name):
|
|
has_breakpoint_in_frame = True
|
|
break
|
|
else:
|
|
for bp_line in breakpoints_for_file: # iterate on keys
|
|
if bp_line in func_lines:
|
|
has_breakpoint_in_frame = True
|
|
break
|
|
|
|
# Cache the value (1 or 0 or -1 for default because of cython).
|
|
if has_breakpoint_in_frame:
|
|
frame_skips_cache[frame_cache_key] = 1
|
|
else:
|
|
frame_skips_cache[frame_cache_key] = 0
|
|
|
|
if can_skip and not has_breakpoint_in_frame:
|
|
if has_exception_breakpoints:
|
|
return self.trace_exception
|
|
else:
|
|
return None if is_call else NO_FTRACE
|
|
|
|
# We may have hit a breakpoint or we are already in step mode. Either way, let's check what we should do in this frame
|
|
# if DEBUG: print('NOT skipped: %s %s %s %s' % (frame.f_lineno, frame.f_code.co_name, event, frame.__class__.__name__))
|
|
|
|
try:
|
|
stop_on_plugin_breakpoint = False
|
|
# return is not taken into account for breakpoint hit because we'd have a double-hit in this case
|
|
# (one for the line and the other for the return).
|
|
|
|
stop_info = {}
|
|
breakpoint = None
|
|
stop = False
|
|
stop_reason = 111
|
|
bp_type = None
|
|
|
|
if function_breakpoint_on_call_event:
|
|
breakpoint = function_breakpoint_on_call_event
|
|
stop = True
|
|
new_frame = frame
|
|
stop_reason = CMD_SET_FUNCTION_BREAK
|
|
|
|
elif is_line and info.pydev_state != 2 and breakpoints_for_file is not None and line in breakpoints_for_file:
|
|
breakpoint = breakpoints_for_file[line]
|
|
new_frame = frame
|
|
stop = True
|
|
|
|
elif plugin_manager is not None and py_db.has_plugin_line_breaks:
|
|
result = plugin_manager.get_breakpoint(py_db, frame, event, self._args[2])
|
|
if result:
|
|
stop_on_plugin_breakpoint = True
|
|
breakpoint, new_frame, bp_type = result
|
|
|
|
if breakpoint:
|
|
# ok, hit breakpoint, now, we have to discover if it is a conditional breakpoint
|
|
# lets do the conditional stuff here
|
|
if breakpoint.expression is not None:
|
|
py_db.handle_breakpoint_expression(breakpoint, info, new_frame)
|
|
|
|
if stop or stop_on_plugin_breakpoint:
|
|
eval_result = False
|
|
if breakpoint.has_condition:
|
|
eval_result = py_db.handle_breakpoint_condition(info, breakpoint, new_frame)
|
|
if not eval_result:
|
|
stop = False
|
|
stop_on_plugin_breakpoint = False
|
|
|
|
if is_call and (
|
|
frame.f_code.co_name in ("<lambda>", "<module>") or (line == 1 and frame.f_code.co_name.startswith("<cell"))
|
|
):
|
|
# If we find a call for a module, it means that the module is being imported/executed for the
|
|
# first time. In this case we have to ignore this hit as it may later duplicated by a
|
|
# line event at the same place (so, if there's a module with a print() in the first line
|
|
# the user will hit that line twice, which is not what we want).
|
|
#
|
|
# For lambda, as it only has a single statement, it's not interesting to trace
|
|
# its call and later its line event as they're usually in the same line.
|
|
#
|
|
# For ipython, <cell xxx> may be executed having each line compiled as a new
|
|
# module, so it's the same case as <module>.
|
|
|
|
return self.trace_dispatch
|
|
|
|
# Handle logpoint (on a logpoint we should never stop).
|
|
if (stop or stop_on_plugin_breakpoint) and breakpoint.is_logpoint:
|
|
stop = False
|
|
stop_on_plugin_breakpoint = False
|
|
|
|
if info.pydev_message is not None and len(info.pydev_message) > 0:
|
|
cmd = py_db.cmd_factory.make_io_message(info.pydev_message + os.linesep, "1")
|
|
py_db.writer.add_command(cmd)
|
|
|
|
if py_db.show_return_values:
|
|
if is_return and (
|
|
(
|
|
info.pydev_step_cmd in (108, 159, 128)
|
|
and (self._is_same_frame(stop_frame, frame.f_back))
|
|
)
|
|
or (info.pydev_step_cmd in (109, 160) and (self._is_same_frame(stop_frame, frame)))
|
|
or (info.pydev_step_cmd in (107, 206))
|
|
or (
|
|
info.pydev_step_cmd == 144
|
|
and frame.f_back is not None
|
|
and not py_db.apply_files_filter(frame.f_back, frame.f_back.f_code.co_filename, True)
|
|
)
|
|
):
|
|
self._show_return_values(frame, arg)
|
|
|
|
elif py_db.remove_return_values_flag:
|
|
try:
|
|
self._remove_return_values(py_db, frame)
|
|
finally:
|
|
py_db.remove_return_values_flag = False
|
|
|
|
if stop:
|
|
self.set_suspend(
|
|
thread,
|
|
stop_reason,
|
|
suspend_other_threads=breakpoint and breakpoint.suspend_policy == "ALL",
|
|
)
|
|
|
|
elif stop_on_plugin_breakpoint and plugin_manager is not None:
|
|
result = plugin_manager.suspend(py_db, thread, frame, bp_type)
|
|
if result:
|
|
frame = result
|
|
|
|
# if thread has a suspend flag, we suspend with a busy wait
|
|
if info.pydev_state == 2:
|
|
self.do_wait_suspend(thread, frame, event, arg)
|
|
return self.trace_dispatch
|
|
else:
|
|
if not breakpoint and is_line:
|
|
# No stop from anyone and no breakpoint found in line (cache that).
|
|
frame_skips_cache[line_cache_key] = 0
|
|
|
|
except:
|
|
# Unfortunately Python itself stops the tracing when it originates from
|
|
# the tracing function, so, we can't do much about it (just let the user know).
|
|
exc = sys.exc_info()[0]
|
|
cmd = py_db.cmd_factory.make_console_message(
|
|
"%s raised from within the callback set in sys.settrace.\nDebugging will be disabled for this thread (%s).\n"
|
|
% (
|
|
exc,
|
|
thread,
|
|
)
|
|
)
|
|
py_db.writer.add_command(cmd)
|
|
if not issubclass(exc, (KeyboardInterrupt, SystemExit)):
|
|
pydev_log.exception()
|
|
|
|
raise
|
|
|
|
# step handling. We stop when we hit the right frame
|
|
try:
|
|
should_skip = 0
|
|
if pydevd_dont_trace.should_trace_hook is not None:
|
|
if self.should_skip == -1:
|
|
# I.e.: cache the result on self.should_skip (no need to evaluate the same frame multiple times).
|
|
# Note that on a code reload, we won't re-evaluate this because in practice, the frame.f_code
|
|
# Which will be handled by this frame is read-only, so, we can cache it safely.
|
|
if not pydevd_dont_trace.should_trace_hook(frame.f_code, abs_path_canonical_path_and_base[0]):
|
|
# -1, 0, 1 to be Cython-friendly
|
|
should_skip = self.should_skip = 1
|
|
else:
|
|
should_skip = self.should_skip = 0
|
|
else:
|
|
should_skip = self.should_skip
|
|
|
|
plugin_stop = False
|
|
if should_skip:
|
|
stop = False
|
|
|
|
elif step_cmd in (107, 144, 206):
|
|
force_check_project_scope = step_cmd == 144
|
|
if is_line:
|
|
if not info.pydev_use_scoped_step_frame:
|
|
if force_check_project_scope or py_db.is_files_filter_enabled:
|
|
stop = not py_db.apply_files_filter(frame, frame.f_code.co_filename, force_check_project_scope)
|
|
else:
|
|
stop = True
|
|
else:
|
|
if force_check_project_scope or py_db.is_files_filter_enabled:
|
|
# Make sure we check the filtering inside ipython calls too...
|
|
if not not py_db.apply_files_filter(frame, frame.f_code.co_filename, force_check_project_scope):
|
|
return None if is_call else NO_FTRACE
|
|
|
|
# We can only stop inside the ipython call.
|
|
filename = frame.f_code.co_filename
|
|
if filename.endswith(".pyc"):
|
|
filename = filename[:-1]
|
|
|
|
if not filename.endswith(PYDEVD_IPYTHON_CONTEXT[0]):
|
|
f = frame.f_back
|
|
while f is not None:
|
|
if f.f_code.co_name == PYDEVD_IPYTHON_CONTEXT[1]:
|
|
f2 = f.f_back
|
|
if f2 is not None and f2.f_code.co_name == PYDEVD_IPYTHON_CONTEXT[2]:
|
|
pydev_log.debug("Stop inside ipython call")
|
|
stop = True
|
|
break
|
|
f = f.f_back
|
|
|
|
del f
|
|
|
|
if not stop:
|
|
# In scoped mode if step in didn't work in this context it won't work
|
|
# afterwards anyways.
|
|
return None if is_call else NO_FTRACE
|
|
|
|
elif is_return and frame.f_back is not None and not info.pydev_use_scoped_step_frame:
|
|
if py_db.get_file_type(frame.f_back) == py_db.PYDEV_FILE:
|
|
stop = False
|
|
else:
|
|
if force_check_project_scope or py_db.is_files_filter_enabled:
|
|
stop = not py_db.apply_files_filter(
|
|
frame.f_back, frame.f_back.f_code.co_filename, force_check_project_scope
|
|
)
|
|
if stop:
|
|
# Prevent stopping in a return to the same location we were initially
|
|
# (i.e.: double-stop at the same place due to some filtering).
|
|
if info.step_in_initial_location == (frame.f_back, frame.f_back.f_lineno):
|
|
stop = False
|
|
else:
|
|
stop = True
|
|
else:
|
|
stop = False
|
|
|
|
if stop:
|
|
if step_cmd == 206:
|
|
# i.e.: Check if we're stepping into the proper context.
|
|
f = frame
|
|
while f is not None:
|
|
if self._is_same_frame(stop_frame, f):
|
|
break
|
|
f = f.f_back
|
|
else:
|
|
stop = False
|
|
|
|
if plugin_manager is not None:
|
|
result = plugin_manager.cmd_step_into(py_db, frame, event, self._args[2], self._args[3], stop_info, stop)
|
|
if result:
|
|
stop, plugin_stop = result
|
|
|
|
elif step_cmd in (108, 159):
|
|
# Note: when dealing with a step over my code it's the same as a step over (the
|
|
# difference is that when we return from a frame in one we go to regular step
|
|
# into and in the other we go to a step into my code).
|
|
stop = self._is_same_frame(stop_frame, frame) and is_line
|
|
# Note: don't stop on a return for step over, only for line events
|
|
# i.e.: don't stop in: (stop_frame is frame.f_back and is_return) as we'd stop twice in that line.
|
|
|
|
if plugin_manager is not None:
|
|
result = plugin_manager.cmd_step_over(py_db, frame, event, self._args[2], self._args[3], stop_info, stop)
|
|
if result:
|
|
stop, plugin_stop = result
|
|
|
|
elif step_cmd == 128:
|
|
stop = False
|
|
back = frame.f_back
|
|
if self._is_same_frame(stop_frame, frame) and is_return:
|
|
# We're exiting the smart step into initial frame (so, we probably didn't find our target).
|
|
stop = True
|
|
|
|
elif self._is_same_frame(stop_frame, back) and is_line:
|
|
if info.pydev_smart_child_offset != -1:
|
|
# i.e.: in this case, we're not interested in the pause in the parent, rather
|
|
# we're interested in the pause in the child (when the parent is at the proper place).
|
|
stop = False
|
|
|
|
else:
|
|
pydev_smart_parent_offset = info.pydev_smart_parent_offset
|
|
|
|
pydev_smart_step_into_variants = info.pydev_smart_step_into_variants
|
|
if pydev_smart_parent_offset >= 0 and pydev_smart_step_into_variants:
|
|
# Preferred mode (when the smart step into variants are available
|
|
# and the offset is set).
|
|
stop = get_smart_step_into_variant_from_frame_offset(
|
|
back.f_lasti, pydev_smart_step_into_variants
|
|
) is get_smart_step_into_variant_from_frame_offset(
|
|
pydev_smart_parent_offset, pydev_smart_step_into_variants
|
|
)
|
|
|
|
else:
|
|
# Only the name/line is available, so, check that.
|
|
curr_func_name = frame.f_code.co_name
|
|
|
|
# global context is set with an empty name
|
|
if curr_func_name in ("?", "<module>") or curr_func_name is None:
|
|
curr_func_name = ""
|
|
if curr_func_name == info.pydev_func_name and stop_frame.f_lineno == info.pydev_next_line:
|
|
stop = True
|
|
|
|
if not stop:
|
|
# In smart step into, if we didn't hit it in this frame once, that'll
|
|
# not be the case next time either, so, disable tracing for this frame.
|
|
return None if is_call else NO_FTRACE
|
|
|
|
elif back is not None and self._is_same_frame(stop_frame, back.f_back) and is_line:
|
|
# Ok, we have to track 2 stops at this point, the parent and the child offset.
|
|
# This happens when handling a step into which targets a function inside a list comprehension
|
|
# or generator (in which case an intermediary frame is created due to an internal function call).
|
|
pydev_smart_parent_offset = info.pydev_smart_parent_offset
|
|
pydev_smart_child_offset = info.pydev_smart_child_offset
|
|
# print('matched back frame', pydev_smart_parent_offset, pydev_smart_child_offset)
|
|
# print('parent f_lasti', back.f_back.f_lasti)
|
|
# print('child f_lasti', back.f_lasti)
|
|
stop = False
|
|
if pydev_smart_child_offset >= 0 and pydev_smart_child_offset >= 0:
|
|
pydev_smart_step_into_variants = info.pydev_smart_step_into_variants
|
|
|
|
if pydev_smart_parent_offset >= 0 and pydev_smart_step_into_variants:
|
|
# Note that we don't really check the parent offset, only the offset of
|
|
# the child (because this is a generator, the parent may have moved forward
|
|
# already -- and that's ok, so, we just check that the parent frame
|
|
# matches in this case).
|
|
smart_step_into_variant = get_smart_step_into_variant_from_frame_offset(
|
|
pydev_smart_parent_offset, pydev_smart_step_into_variants
|
|
)
|
|
# print('matched parent offset', pydev_smart_parent_offset)
|
|
# Ok, now, check the child variant
|
|
children_variants = smart_step_into_variant.children_variants
|
|
stop = children_variants and (
|
|
get_smart_step_into_variant_from_frame_offset(back.f_lasti, children_variants)
|
|
is get_smart_step_into_variant_from_frame_offset(pydev_smart_child_offset, children_variants)
|
|
)
|
|
# print('stop at child', stop)
|
|
|
|
if not stop:
|
|
# In smart step into, if we didn't hit it in this frame once, that'll
|
|
# not be the case next time either, so, disable tracing for this frame.
|
|
return None if is_call else NO_FTRACE
|
|
|
|
elif step_cmd in (109, 160):
|
|
stop = is_return and self._is_same_frame(stop_frame, frame)
|
|
|
|
else:
|
|
stop = False
|
|
|
|
if stop and step_cmd != -1 and is_return and hasattr(frame, "f_back"):
|
|
f_code = getattr(frame.f_back, "f_code", None)
|
|
if f_code is not None:
|
|
if py_db.get_file_type(frame.f_back) == py_db.PYDEV_FILE:
|
|
stop = False
|
|
|
|
if plugin_stop:
|
|
plugin_manager.stop(py_db, frame, event, self._args[3], stop_info, arg, step_cmd)
|
|
elif stop:
|
|
if is_line:
|
|
self.set_suspend(thread, step_cmd, original_step_cmd=info.pydev_original_step_cmd)
|
|
self.do_wait_suspend(thread, frame, event, arg)
|
|
elif is_return: # return event
|
|
back = frame.f_back
|
|
if back is not None:
|
|
# When we get to the pydevd run function, the debugging has actually finished for the main thread
|
|
# (note that it can still go on for other threads, but for this one, we just make it finish)
|
|
# So, just setting it to None should be OK
|
|
back_absolute_filename, _, base = get_abs_path_real_path_and_base_from_frame(back)
|
|
if (base, back.f_code.co_name) in (DEBUG_START, DEBUG_START_PY3K):
|
|
back = None
|
|
|
|
elif base == TRACE_PROPERTY:
|
|
# We dont want to trace the return event of pydevd_traceproperty (custom property for debugging)
|
|
# if we're in a return, we want it to appear to the user in the previous frame!
|
|
return None if is_call else NO_FTRACE
|
|
|
|
elif pydevd_dont_trace.should_trace_hook is not None:
|
|
if not pydevd_dont_trace.should_trace_hook(back.f_code, back_absolute_filename):
|
|
# In this case, we'll have to skip the previous one because it shouldn't be traced.
|
|
# Also, we have to reset the tracing, because if the parent's parent (or some
|
|
# other parent) has to be traced and it's not currently, we wouldn't stop where
|
|
# we should anymore (so, a step in/over/return may not stop anywhere if no parent is traced).
|
|
# Related test: _debugger_case17a.py
|
|
py_db.set_trace_for_frame_and_parents(thread.ident, back)
|
|
return None if is_call else NO_FTRACE
|
|
|
|
if back is not None:
|
|
# if we're in a return, we want it to appear to the user in the previous frame!
|
|
self.set_suspend(thread, step_cmd, original_step_cmd=info.pydev_original_step_cmd)
|
|
self.do_wait_suspend(thread, back, event, arg)
|
|
else:
|
|
# in jython we may not have a back frame
|
|
info.pydev_step_stop = None
|
|
info.pydev_original_step_cmd = -1
|
|
info.pydev_step_cmd = -1
|
|
info.pydev_state = 1
|
|
info.update_stepping_info()
|
|
|
|
# if we are quitting, let's stop the tracing
|
|
if py_db.quitting:
|
|
return None if is_call else NO_FTRACE
|
|
|
|
return self.trace_dispatch
|
|
except:
|
|
# Unfortunately Python itself stops the tracing when it originates from
|
|
# the tracing function, so, we can't do much about it (just let the user know).
|
|
exc = sys.exc_info()[0]
|
|
cmd = py_db.cmd_factory.make_console_message(
|
|
"%s raised from within the callback set in sys.settrace.\nDebugging will be disabled for this thread (%s).\n"
|
|
% (
|
|
exc,
|
|
thread,
|
|
)
|
|
)
|
|
py_db.writer.add_command(cmd)
|
|
if not issubclass(exc, (KeyboardInterrupt, SystemExit)):
|
|
pydev_log.exception()
|
|
raise
|
|
|
|
finally:
|
|
info.is_tracing -= 1
|
|
|
|
# end trace_dispatch
|
|
|
|
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
def should_stop_on_exception(py_db, PyDBAdditionalThreadInfo info, frame, thread, arg, prev_user_uncaught_exc_info, is_unwind=False):
|
|
cdef bint should_stop;
|
|
cdef bint was_just_raised;
|
|
cdef list check_excs;
|
|
# ELSE
|
|
# def should_stop_on_exception(py_db, info, frame, thread, arg, prev_user_uncaught_exc_info, is_unwind=False):
|
|
# ENDIF
|
|
|
|
should_stop = False
|
|
maybe_user_uncaught_exc_info = prev_user_uncaught_exc_info
|
|
|
|
# 2 = 2
|
|
if info.pydev_state != 2: # and breakpoint is not None:
|
|
exception, value, trace = arg
|
|
|
|
if trace is not None and hasattr(trace, "tb_next"):
|
|
# on jython trace is None on the first event and it may not have a tb_next.
|
|
|
|
should_stop = False
|
|
exception_breakpoint = None
|
|
try:
|
|
if py_db.plugin is not None:
|
|
result = py_db.plugin.exception_break(py_db, frame, thread, arg, is_unwind)
|
|
if result:
|
|
should_stop, frame = result
|
|
except:
|
|
pydev_log.exception()
|
|
|
|
if not should_stop:
|
|
# Apply checks that don't need the exception breakpoint (where we shouldn't ever stop).
|
|
if exception == SystemExit and py_db.ignore_system_exit_code(value):
|
|
pass
|
|
|
|
elif exception in (GeneratorExit, StopIteration, StopAsyncIteration):
|
|
# These exceptions are control-flow related (they work as a generator
|
|
# pause), so, we shouldn't stop on them.
|
|
pass
|
|
|
|
elif ignore_exception_trace(trace):
|
|
pass
|
|
|
|
else:
|
|
was_just_raised = trace.tb_next is None
|
|
|
|
# It was not handled by any plugin, lets check exception breakpoints.
|
|
check_excs = []
|
|
|
|
# Note: check user unhandled before regular exceptions.
|
|
exc_break_user = py_db.get_exception_breakpoint(exception, py_db.break_on_user_uncaught_exceptions)
|
|
if exc_break_user is not None:
|
|
check_excs.append((exc_break_user, True))
|
|
|
|
exc_break_caught = py_db.get_exception_breakpoint(exception, py_db.break_on_caught_exceptions)
|
|
if exc_break_caught is not None:
|
|
check_excs.append((exc_break_caught, False))
|
|
|
|
for exc_break, is_user_uncaught in check_excs:
|
|
# Initially mark that it should stop and then go into exclusions.
|
|
should_stop = True
|
|
|
|
if py_db.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 exc_break.condition is not None and not py_db.handle_breakpoint_condition(info, exc_break, frame):
|
|
should_stop = False
|
|
|
|
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 py_db.apply_files_filter(frame, frame.f_code.co_filename, True) 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_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)
|
|
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
|
|
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 py_db.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 was_just_raised and py_db.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:
|
|
# Always add exception to frame (must remove later after we proceed).
|
|
add_exception_to_frame(frame, (exception, value, trace))
|
|
|
|
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, maybe_user_uncaught_exc_info
|
|
|
|
|
|
# Same thing in the main debugger but only considering the file contents, while the one in the main debugger
|
|
# considers the user input (so, the actual result must be a join of both).
|
|
filename_to_lines_where_exceptions_are_ignored: dict = {}
|
|
filename_to_stat_info: dict = {}
|
|
|
|
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
def handle_exception(py_db, thread, frame, arg, str exception_type):
|
|
cdef bint stopped;
|
|
cdef tuple abs_real_path_and_base;
|
|
cdef str absolute_filename;
|
|
cdef str canonical_normalized_filename;
|
|
cdef dict lines_ignored;
|
|
cdef dict frame_id_to_frame;
|
|
cdef dict merged;
|
|
cdef object trace_obj;
|
|
# ELSE
|
|
# def handle_exception(py_db, thread, frame, arg, exception_type):
|
|
# ENDIF
|
|
stopped = False
|
|
try:
|
|
# print('handle_exception', frame.f_lineno, frame.f_code.co_name)
|
|
|
|
# We have 3 things in arg: exception type, description, traceback object
|
|
trace_obj = arg[2]
|
|
|
|
initial_trace_obj = trace_obj
|
|
if trace_obj.tb_next is None and trace_obj.tb_frame is frame:
|
|
# I.e.: tb_next should be only None in the context it was thrown (trace_obj.tb_frame is frame is just a double check).
|
|
pass
|
|
else:
|
|
# Get the trace_obj from where the exception was raised...
|
|
while trace_obj.tb_next is not None:
|
|
trace_obj = trace_obj.tb_next
|
|
|
|
if py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception:
|
|
for check_trace_obj in (initial_trace_obj, trace_obj):
|
|
abs_real_path_and_base = get_abs_path_real_path_and_base_from_frame(check_trace_obj.tb_frame)
|
|
absolute_filename = abs_real_path_and_base[0]
|
|
canonical_normalized_filename = abs_real_path_and_base[1]
|
|
|
|
lines_ignored = filename_to_lines_where_exceptions_are_ignored.get(canonical_normalized_filename)
|
|
if lines_ignored is None:
|
|
lines_ignored = filename_to_lines_where_exceptions_are_ignored[canonical_normalized_filename] = {}
|
|
|
|
try:
|
|
curr_stat = os.stat(absolute_filename)
|
|
curr_stat = (curr_stat.st_size, curr_stat.st_mtime)
|
|
except:
|
|
curr_stat = None
|
|
|
|
last_stat = filename_to_stat_info.get(absolute_filename)
|
|
if last_stat != curr_stat:
|
|
filename_to_stat_info[absolute_filename] = curr_stat
|
|
lines_ignored.clear()
|
|
try:
|
|
linecache.checkcache(absolute_filename)
|
|
except:
|
|
pydev_log.exception("Error in linecache.checkcache(%r)", absolute_filename)
|
|
|
|
from_user_input = py_db.filename_to_lines_where_exceptions_are_ignored.get(canonical_normalized_filename)
|
|
if from_user_input:
|
|
merged = {}
|
|
merged.update(lines_ignored)
|
|
# Override what we have with the related entries that the user entered
|
|
merged.update(from_user_input)
|
|
else:
|
|
merged = lines_ignored
|
|
|
|
exc_lineno = check_trace_obj.tb_lineno
|
|
|
|
# print ('lines ignored', lines_ignored)
|
|
# print ('user input', from_user_input)
|
|
# print ('merged', merged, 'curr', exc_lineno)
|
|
|
|
if exc_lineno not in merged: # Note: check on merged but update lines_ignored.
|
|
try:
|
|
line = linecache.getline(absolute_filename, exc_lineno, check_trace_obj.tb_frame.f_globals)
|
|
except:
|
|
pydev_log.exception("Error in linecache.getline(%r, %s, f_globals)", absolute_filename, exc_lineno)
|
|
line = ""
|
|
|
|
if IGNORE_EXCEPTION_TAG.match(line) is not None:
|
|
lines_ignored[exc_lineno] = 1
|
|
return False
|
|
else:
|
|
# Put in the cache saying not to ignore
|
|
lines_ignored[exc_lineno] = 0
|
|
else:
|
|
# Ok, dict has it already cached, so, let's check it...
|
|
if merged.get(exc_lineno, 0):
|
|
return False
|
|
|
|
try:
|
|
frame_id_to_frame = {}
|
|
frame_id_to_frame[id(frame)] = frame
|
|
f = trace_obj.tb_frame
|
|
while f is not None:
|
|
frame_id_to_frame[id(f)] = f
|
|
f = f.f_back
|
|
f = None
|
|
|
|
stopped = True
|
|
py_db.send_caught_exception_stack(thread, arg, id(frame))
|
|
try:
|
|
py_db.set_suspend(thread, 137)
|
|
py_db.do_wait_suspend(thread, frame, "exception", arg, exception_type=exception_type)
|
|
finally:
|
|
py_db.send_caught_exception_stack_proceeded(thread)
|
|
except:
|
|
pydev_log.exception()
|
|
|
|
py_db.set_trace_for_frame_and_parents(thread.ident, frame)
|
|
finally:
|
|
# Make sure the user cannot see the '__exception__' we added after we leave the suspend state.
|
|
remove_exception_from_frame(frame)
|
|
# Clear some local variables...
|
|
frame = None
|
|
trace_obj = None
|
|
initial_trace_obj = None
|
|
check_trace_obj = None
|
|
f = None
|
|
frame_id_to_frame = None
|
|
py_db = None
|
|
thread = None
|
|
|
|
return stopped
|
|
from _pydev_bundle.pydev_is_thread_alive import is_thread_alive
|
|
from _pydev_bundle.pydev_log import exception as pydev_log_exception
|
|
from _pydev_bundle._pydev_saved_modules import threading
|
|
from _pydevd_bundle.pydevd_constants import (
|
|
get_current_thread_id,
|
|
NO_FTRACE,
|
|
USE_CUSTOM_SYS_CURRENT_FRAMES_MAP,
|
|
ForkSafeLock,
|
|
PYDEVD_USE_SYS_MONITORING,
|
|
)
|
|
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame, NORM_PATHS_AND_BASE_CONTAINER
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
from cpython.object cimport PyObject
|
|
from cpython.ref cimport Py_INCREF, Py_XDECREF
|
|
# ELSE
|
|
# from _pydevd_bundle.pydevd_frame import PyDBFrame, is_unhandled_exception
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef dict _global_notify_skipped_step_in
|
|
# ELSE
|
|
# # Note: those are now inlined on cython.
|
|
# 107 = 107
|
|
# 144 = 144
|
|
# 109 = 109
|
|
# 160 = 160
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
# Cache where we should keep that we completely skipped entering some context.
|
|
# It needs to be invalidated when:
|
|
# - Breakpoints are changed
|
|
# It can be used when running regularly (without step over/step in/step return)
|
|
global_cache_skips = {}
|
|
global_cache_frame_skips = {}
|
|
|
|
_global_notify_skipped_step_in = False
|
|
_global_notify_skipped_step_in_lock = ForkSafeLock()
|
|
|
|
|
|
def notify_skipped_step_in_because_of_filters(py_db, frame):
|
|
global _global_notify_skipped_step_in
|
|
|
|
with _global_notify_skipped_step_in_lock:
|
|
if _global_notify_skipped_step_in:
|
|
# Check with lock in place (callers should actually have checked
|
|
# before without the lock in place due to performance).
|
|
return
|
|
_global_notify_skipped_step_in = True
|
|
py_db.notify_skipped_step_in_because_of_filters(frame)
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef class SafeCallWrapper:
|
|
cdef method_object
|
|
def __init__(self, method_object):
|
|
self.method_object = method_object
|
|
def __call__(self, *args):
|
|
#Cannot use 'self' once inside the delegate call since we are borrowing the self reference f_trace field
|
|
#in the frame, and that reference might get destroyed by set trace on frame and parents
|
|
cdef PyObject* method_obj = <PyObject*> self.method_object
|
|
Py_INCREF(<object>method_obj)
|
|
ret = (<object>method_obj)(*args)
|
|
Py_XDECREF (method_obj)
|
|
return SafeCallWrapper(ret) if ret is not None else None
|
|
def get_method_object(self):
|
|
return self.method_object
|
|
# ELSE
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
|
|
def fix_top_level_trace_and_get_trace_func(py_db, frame):
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef str filename;
|
|
cdef str name;
|
|
cdef tuple args;
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
# Note: this is always the first entry-point in the tracing for any thread.
|
|
# After entering here we'll set a new tracing function for this thread
|
|
# where more information is cached (and will also setup the tracing for
|
|
# frames where we should deal with unhandled exceptions).
|
|
thread = None
|
|
# Cache the frame which should be traced to deal with unhandled exceptions.
|
|
# (i.e.: thread entry-points).
|
|
|
|
f_unhandled = frame
|
|
# print('called at', f_unhandled.f_code.co_name, f_unhandled.f_code.co_filename, f_unhandled.f_code.co_firstlineno)
|
|
force_only_unhandled_tracer = False
|
|
while f_unhandled is not None:
|
|
# name = splitext(basename(f_unhandled.f_code.co_filename))[0]
|
|
|
|
name = f_unhandled.f_code.co_filename
|
|
# basename
|
|
i = name.rfind("/")
|
|
j = name.rfind("\\")
|
|
if j > i:
|
|
i = j
|
|
if i >= 0:
|
|
name = name[i + 1 :]
|
|
# remove ext
|
|
i = name.rfind(".")
|
|
if i >= 0:
|
|
name = name[:i]
|
|
|
|
if name == "threading":
|
|
if f_unhandled.f_code.co_name in ("__bootstrap", "_bootstrap"):
|
|
# We need __bootstrap_inner, not __bootstrap.
|
|
return None, False
|
|
|
|
elif f_unhandled.f_code.co_name in ("__bootstrap_inner", "_bootstrap_inner"):
|
|
# Note: be careful not to use threading.currentThread to avoid creating a dummy thread.
|
|
t = f_unhandled.f_locals.get("self")
|
|
force_only_unhandled_tracer = True
|
|
if t is not None and isinstance(t, threading.Thread):
|
|
thread = t
|
|
break
|
|
|
|
elif name == "pydev_monkey":
|
|
if f_unhandled.f_code.co_name == "__call__":
|
|
force_only_unhandled_tracer = True
|
|
break
|
|
|
|
elif name == "pydevd":
|
|
if f_unhandled.f_code.co_name in ("run", "main"):
|
|
# We need to get to _exec
|
|
return None, False
|
|
|
|
if f_unhandled.f_code.co_name == "_exec":
|
|
force_only_unhandled_tracer = True
|
|
break
|
|
|
|
elif name == "pydevd_tracing":
|
|
return None, False
|
|
|
|
elif f_unhandled.f_back is None:
|
|
break
|
|
|
|
f_unhandled = f_unhandled.f_back
|
|
|
|
if thread is None:
|
|
# Important: don't call threadingCurrentThread if we're in the threading module
|
|
# to avoid creating dummy threads.
|
|
if py_db.threading_get_ident is not None:
|
|
thread = py_db.threading_active.get(py_db.threading_get_ident())
|
|
if thread is None:
|
|
return None, False
|
|
else:
|
|
# Jython does not have threading.get_ident().
|
|
thread = py_db.threading_current_thread()
|
|
|
|
if getattr(thread, "pydev_do_not_trace", None):
|
|
py_db.disable_tracing()
|
|
return None, False
|
|
|
|
try:
|
|
additional_info = thread.additional_info
|
|
if additional_info is None:
|
|
raise AttributeError()
|
|
except:
|
|
additional_info = py_db.set_additional_thread_info(thread)
|
|
|
|
# print('enter thread tracer', thread, get_current_thread_id(thread))
|
|
args = (py_db, thread, additional_info, global_cache_skips, global_cache_frame_skips)
|
|
|
|
if f_unhandled is not None:
|
|
if f_unhandled.f_back is None and not force_only_unhandled_tracer:
|
|
# Happens when we attach to a running program (cannot reuse instance because it's mutable).
|
|
top_level_thread_tracer = TopLevelThreadTracerNoBackFrame(ThreadTracer(args), args)
|
|
additional_info.top_level_thread_tracer_no_back_frames.append(
|
|
top_level_thread_tracer
|
|
) # Hack for cython to keep it alive while the thread is alive (just the method in the SetTrace is not enough).
|
|
else:
|
|
top_level_thread_tracer = additional_info.top_level_thread_tracer_unhandled
|
|
if top_level_thread_tracer is None:
|
|
# Stop in some internal place to report about unhandled exceptions
|
|
top_level_thread_tracer = TopLevelThreadTracerOnlyUnhandledExceptions(args)
|
|
additional_info.top_level_thread_tracer_unhandled = top_level_thread_tracer # Hack for cython to keep it alive while the thread is alive (just the method in the SetTrace is not enough).
|
|
|
|
# print(' --> found to trace unhandled', f_unhandled.f_code.co_name, f_unhandled.f_code.co_filename, f_unhandled.f_code.co_firstlineno)
|
|
f_trace = top_level_thread_tracer.get_trace_dispatch_func()
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
f_trace = SafeCallWrapper(f_trace)
|
|
# ENDIF
|
|
# fmt: on
|
|
f_unhandled.f_trace = f_trace
|
|
|
|
if frame is f_unhandled:
|
|
return f_trace, False
|
|
|
|
thread_tracer = additional_info.thread_tracer
|
|
if thread_tracer is None or thread_tracer._args[0] is not py_db:
|
|
thread_tracer = ThreadTracer(args)
|
|
additional_info.thread_tracer = thread_tracer
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
return SafeCallWrapper(thread_tracer), True
|
|
# ELSE
|
|
# return thread_tracer, True
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
|
|
def trace_dispatch(py_db, frame, event, arg):
|
|
thread_trace_func, apply_to_settrace = py_db.fix_top_level_trace_and_get_trace_func(py_db, frame)
|
|
if thread_trace_func is None:
|
|
return None if event == "call" else NO_FTRACE
|
|
if apply_to_settrace:
|
|
py_db.enable_tracing(thread_trace_func)
|
|
return thread_trace_func(frame, event, arg)
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef class TopLevelThreadTracerOnlyUnhandledExceptions:
|
|
cdef public tuple _args;
|
|
def __init__(self, tuple args):
|
|
self._args = args
|
|
# ELSE
|
|
# class TopLevelThreadTracerOnlyUnhandledExceptions(object):
|
|
# def __init__(self, args):
|
|
# self._args = args
|
|
#
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
def trace_unhandled_exceptions(self, frame, event, arg):
|
|
# Note that we ignore the frame as this tracing method should only be put in topmost frames already.
|
|
# print('trace_unhandled_exceptions', event, frame.f_code.co_name, frame.f_code.co_filename, frame.f_code.co_firstlineno)
|
|
if event == "exception" and arg is not None:
|
|
py_db, t, additional_info = self._args[0:3]
|
|
if arg is not None:
|
|
if not additional_info.suspended_at_unhandled:
|
|
additional_info.suspended_at_unhandled = True
|
|
|
|
py_db.stop_on_unhandled_exception(py_db, t, additional_info, arg)
|
|
|
|
# No need to reset frame.f_trace to keep the same trace function.
|
|
return self.trace_unhandled_exceptions
|
|
|
|
def get_trace_dispatch_func(self):
|
|
return self.trace_unhandled_exceptions
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef class TopLevelThreadTracerNoBackFrame:
|
|
cdef public object _frame_trace_dispatch;
|
|
cdef public tuple _args;
|
|
cdef public object try_except_infos;
|
|
cdef public object _last_exc_arg;
|
|
cdef public set _raise_lines;
|
|
cdef public int _last_raise_line;
|
|
def __init__(self, frame_trace_dispatch, tuple args):
|
|
self._frame_trace_dispatch = frame_trace_dispatch
|
|
self._args = args
|
|
self.try_except_infos = None
|
|
self._last_exc_arg = None
|
|
self._raise_lines = set()
|
|
self._last_raise_line = -1
|
|
# ELSE
|
|
# class TopLevelThreadTracerNoBackFrame(object):
|
|
# """
|
|
# This tracer is pretty special in that it's dealing with a frame without f_back (i.e.: top frame
|
|
# on remote attach or QThread).
|
|
#
|
|
# This means that we have to carefully inspect exceptions to discover whether the exception will
|
|
# be unhandled or not (if we're dealing with an unhandled exception we need to stop as unhandled,
|
|
# otherwise we need to use the regular tracer -- unfortunately the debugger has little info to
|
|
# work with in the tracing -- see: https://bugs.python.org/issue34099, so, we inspect bytecode to
|
|
# determine if some exception will be traced or not... note that if this is not available -- such
|
|
# as on Jython -- we consider any top-level exception to be unnhandled).
|
|
# """
|
|
#
|
|
# def __init__(self, frame_trace_dispatch, args):
|
|
# self._frame_trace_dispatch = frame_trace_dispatch
|
|
# self._args = args
|
|
# self.try_except_infos = None
|
|
# self._last_exc_arg = None
|
|
# self._raise_lines = set()
|
|
# self._last_raise_line = -1
|
|
#
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
def trace_dispatch_and_unhandled_exceptions(self, frame, event, arg):
|
|
# DEBUG = 'code_to_debug' in frame.f_code.co_filename
|
|
# if DEBUG: print('trace_dispatch_and_unhandled_exceptions: %s %s %s %s %s %s' % (event, frame.f_code.co_name, frame.f_code.co_filename, frame.f_code.co_firstlineno, self._frame_trace_dispatch, frame.f_lineno))
|
|
frame_trace_dispatch = self._frame_trace_dispatch
|
|
if frame_trace_dispatch is not None:
|
|
self._frame_trace_dispatch = frame_trace_dispatch(frame, event, arg)
|
|
|
|
if event == "exception":
|
|
self._last_exc_arg = arg
|
|
self._raise_lines.add(frame.f_lineno)
|
|
self._last_raise_line = frame.f_lineno
|
|
|
|
elif event == "return" and self._last_exc_arg is not None:
|
|
# For unhandled exceptions we actually track the return when at the topmost level.
|
|
try:
|
|
py_db, t, additional_info = self._args[0:3]
|
|
if not additional_info.suspended_at_unhandled: # Note: only check it here, don't set.
|
|
if is_unhandled_exception(self, py_db, frame, self._last_raise_line, self._raise_lines):
|
|
py_db.stop_on_unhandled_exception(py_db, t, additional_info, self._last_exc_arg)
|
|
finally:
|
|
# Remove reference to exception after handling it.
|
|
self._last_exc_arg = None
|
|
|
|
ret = self.trace_dispatch_and_unhandled_exceptions
|
|
|
|
# Need to reset (the call to _frame_trace_dispatch may have changed it).
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
frame.f_trace = SafeCallWrapper(ret)
|
|
# ELSE
|
|
# frame.f_trace = ret
|
|
# ENDIF
|
|
# fmt: on
|
|
return ret
|
|
|
|
def get_trace_dispatch_func(self):
|
|
return self.trace_dispatch_and_unhandled_exceptions
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef class ThreadTracer:
|
|
cdef public tuple _args;
|
|
def __init__(self, tuple args):
|
|
self._args = args
|
|
# ELSE
|
|
# class ThreadTracer(object):
|
|
# def __init__(self, args):
|
|
# self._args = args
|
|
#
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
def __call__(self, frame, event, arg):
|
|
"""This is the callback used when we enter some context in the debugger.
|
|
|
|
We also decorate the thread we are in with info about the debugging.
|
|
The attributes added are:
|
|
pydev_state
|
|
pydev_step_stop
|
|
pydev_step_cmd
|
|
pydev_notify_kill
|
|
|
|
:param PyDB py_db:
|
|
This is the global debugger (this method should actually be added as a method to it).
|
|
"""
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef str filename;
|
|
cdef str base;
|
|
cdef int pydev_step_cmd;
|
|
cdef object frame_cache_key;
|
|
cdef dict cache_skips;
|
|
cdef bint is_stepping;
|
|
cdef tuple abs_path_canonical_path_and_base;
|
|
cdef PyDBAdditionalThreadInfo additional_info;
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
# DEBUG = 'code_to_debug' in frame.f_code.co_filename
|
|
# if DEBUG: print('ENTER: trace_dispatch: %s %s %s %s' % (frame.f_code.co_filename, frame.f_lineno, event, frame.f_code.co_name))
|
|
py_db, t, additional_info, cache_skips, frame_skips_cache = self._args
|
|
if additional_info.is_tracing:
|
|
return None if event == "call" else NO_FTRACE # we don't wan't to trace code invoked from pydevd_frame.trace_dispatch
|
|
|
|
additional_info.is_tracing += 1
|
|
try:
|
|
pydev_step_cmd = additional_info.pydev_step_cmd
|
|
is_stepping = pydev_step_cmd != -1
|
|
if py_db.pydb_disposed:
|
|
return None if event == "call" else NO_FTRACE
|
|
|
|
# if thread is not alive, cancel trace_dispatch processing
|
|
if not is_thread_alive(t):
|
|
py_db.notify_thread_not_alive(get_current_thread_id(t))
|
|
return None if event == "call" else NO_FTRACE
|
|
|
|
# Note: it's important that the context name is also given because we may hit something once
|
|
# in the global context and another in the local context.
|
|
frame_cache_key = frame.f_code
|
|
if frame_cache_key in cache_skips:
|
|
if not is_stepping:
|
|
# if DEBUG: print('skipped: trace_dispatch (cache hit)', frame_cache_key, frame.f_lineno, event, frame.f_code.co_name)
|
|
return None if event == "call" else NO_FTRACE
|
|
else:
|
|
# When stepping we can't take into account caching based on the breakpoints (only global filtering).
|
|
if cache_skips.get(frame_cache_key) == 1:
|
|
if (
|
|
additional_info.pydev_original_step_cmd in (107, 144)
|
|
and not _global_notify_skipped_step_in
|
|
):
|
|
notify_skipped_step_in_because_of_filters(py_db, frame)
|
|
|
|
back_frame = frame.f_back
|
|
if back_frame is not None and pydev_step_cmd in (
|
|
107,
|
|
144,
|
|
109,
|
|
160,
|
|
):
|
|
back_frame_cache_key = back_frame.f_code
|
|
if cache_skips.get(back_frame_cache_key) == 1:
|
|
# if DEBUG: print('skipped: trace_dispatch (cache hit: 1)', frame_cache_key, frame.f_lineno, event, frame.f_code.co_name)
|
|
return None if event == "call" else NO_FTRACE
|
|
else:
|
|
# if DEBUG: print('skipped: trace_dispatch (cache hit: 2)', frame_cache_key, frame.f_lineno, event, frame.f_code.co_name)
|
|
return None if event == "call" else NO_FTRACE
|
|
|
|
try:
|
|
# Make fast path faster!
|
|
abs_path_canonical_path_and_base = NORM_PATHS_AND_BASE_CONTAINER[frame.f_code.co_filename]
|
|
except:
|
|
abs_path_canonical_path_and_base = get_abs_path_real_path_and_base_from_frame(frame)
|
|
|
|
file_type = py_db.get_file_type(
|
|
frame, abs_path_canonical_path_and_base
|
|
) # we don't want to debug threading or anything related to pydevd
|
|
|
|
if file_type is not None:
|
|
if file_type == 1: # inlining LIB_FILE = 1
|
|
if not py_db.in_project_scope(frame, abs_path_canonical_path_and_base[0]):
|
|
# if DEBUG: print('skipped: trace_dispatch (not in scope)', abs_path_canonical_path_and_base[2], frame.f_lineno, event, frame.f_code.co_name, file_type)
|
|
cache_skips[frame_cache_key] = 1
|
|
return None if event == "call" else NO_FTRACE
|
|
else:
|
|
# if DEBUG: print('skipped: trace_dispatch', abs_path_canonical_path_and_base[2], frame.f_lineno, event, frame.f_code.co_name, file_type)
|
|
cache_skips[frame_cache_key] = 1
|
|
return None if event == "call" else NO_FTRACE
|
|
|
|
if py_db.is_files_filter_enabled:
|
|
if py_db.apply_files_filter(frame, abs_path_canonical_path_and_base[0], False):
|
|
cache_skips[frame_cache_key] = 1
|
|
|
|
if (
|
|
is_stepping
|
|
and additional_info.pydev_original_step_cmd in (107, 144)
|
|
and not _global_notify_skipped_step_in
|
|
):
|
|
notify_skipped_step_in_because_of_filters(py_db, frame)
|
|
|
|
# A little gotcha, sometimes when we're stepping in we have to stop in a
|
|
# return event showing the back frame as the current frame, so, we need
|
|
# to check not only the current frame but the back frame too.
|
|
back_frame = frame.f_back
|
|
if back_frame is not None and pydev_step_cmd in (
|
|
107,
|
|
144,
|
|
109,
|
|
160,
|
|
):
|
|
if py_db.apply_files_filter(back_frame, back_frame.f_code.co_filename, False):
|
|
back_frame_cache_key = back_frame.f_code
|
|
cache_skips[back_frame_cache_key] = 1
|
|
# if DEBUG: print('skipped: trace_dispatch (filtered out: 1)', frame_cache_key, frame.f_lineno, event, frame.f_code.co_name)
|
|
return None if event == "call" else NO_FTRACE
|
|
else:
|
|
# if DEBUG: print('skipped: trace_dispatch (filtered out: 2)', frame_cache_key, frame.f_lineno, event, frame.f_code.co_name)
|
|
return None if event == "call" else NO_FTRACE
|
|
|
|
# if DEBUG: print('trace_dispatch', filename, frame.f_lineno, event, frame.f_code.co_name, file_type)
|
|
|
|
# Just create PyDBFrame directly (removed support for Python versions < 2.5, which required keeping a weak
|
|
# reference to the frame).
|
|
ret = PyDBFrame(
|
|
(
|
|
py_db,
|
|
abs_path_canonical_path_and_base,
|
|
additional_info,
|
|
t,
|
|
frame_skips_cache,
|
|
frame_cache_key,
|
|
)
|
|
).trace_dispatch(frame, event, arg)
|
|
if ret is None:
|
|
# 1 means skipped because of filters.
|
|
# 2 means skipped because no breakpoints were hit.
|
|
cache_skips[frame_cache_key] = 2
|
|
return None if event == "call" else NO_FTRACE
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
frame.f_trace = SafeCallWrapper(ret) # Make sure we keep the returned tracer.
|
|
# ELSE
|
|
# frame.f_trace = ret # Make sure we keep the returned tracer.
|
|
# ENDIF
|
|
# fmt: on
|
|
return ret
|
|
|
|
except SystemExit:
|
|
return None if event == "call" else NO_FTRACE
|
|
|
|
except Exception:
|
|
if py_db.pydb_disposed:
|
|
return None if event == "call" else NO_FTRACE # Don't log errors when we're shutting down.
|
|
# Log it
|
|
try:
|
|
if pydev_log_exception is not None:
|
|
# This can actually happen during the interpreter shutdown in Python 2.7
|
|
pydev_log_exception()
|
|
except:
|
|
# Error logging? We're really in the interpreter shutdown...
|
|
# (https://github.com/fabioz/PyDev.Debugger/issues/8)
|
|
pass
|
|
return None if event == "call" else NO_FTRACE
|
|
finally:
|
|
additional_info.is_tracing -= 1
|
|
|
|
|
|
if USE_CUSTOM_SYS_CURRENT_FRAMES_MAP:
|
|
# This is far from ideal, as we'll leak frames (we'll always have the last created frame, not really
|
|
# the last topmost frame saved -- this should be Ok for our usage, but it may leak frames and things
|
|
# may live longer... as IronPython is garbage-collected, things should live longer anyways, so, it
|
|
# shouldn't be an issue as big as it's in CPython -- it may still be annoying, but this should
|
|
# be a reasonable workaround until IronPython itself is able to provide that functionality).
|
|
#
|
|
# See: https://github.com/IronLanguages/main/issues/1630
|
|
from _pydevd_bundle.pydevd_constants import constructed_tid_to_last_frame
|
|
|
|
_original_call = ThreadTracer.__call__
|
|
|
|
def __call__(self, frame, event, arg):
|
|
constructed_tid_to_last_frame[self._args[1].ident] = frame
|
|
return _original_call(self, frame, event, arg)
|
|
|
|
ThreadTracer.__call__ = __call__
|
|
|
|
if PYDEVD_USE_SYS_MONITORING:
|
|
|
|
def fix_top_level_trace_and_get_trace_func(*args, **kwargs):
|
|
raise RuntimeError("Not used in sys.monitoring mode.")
|