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.
1990 lines
75 KiB
Cython
1990 lines
75 KiB
Cython
from __future__ import print_function
|
|
|
|
# Important: Autogenerated file.
|
|
|
|
# DO NOT edit manually!
|
|
# DO NOT edit manually!
|
|
# Copyright: Brainwy Software
|
|
#
|
|
# License: EPL
|
|
|
|
from collections import namedtuple
|
|
import dis
|
|
import os
|
|
import re
|
|
import sys
|
|
from _pydev_bundle._pydev_saved_modules import threading
|
|
from types import CodeType, FrameType
|
|
from typing import Dict, Optional, Tuple, Any
|
|
from os.path import basename, splitext
|
|
|
|
from _pydev_bundle import pydev_log
|
|
from _pydev_bundle.pydev_is_thread_alive import is_thread_alive as pydevd_is_thread_alive
|
|
from _pydevd_bundle import pydevd_dont_trace
|
|
from _pydevd_bundle.pydevd_constants import (
|
|
IS_PY313_OR_GREATER,
|
|
GlobalDebuggerHolder,
|
|
ForkSafeLock,
|
|
PYDEVD_IPYTHON_CONTEXT,
|
|
EXCEPTION_TYPE_USER_UNHANDLED,
|
|
RETURN_VALUES_DICT,
|
|
PYTHON_SUSPEND,
|
|
)
|
|
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
|
|
from _pydevd_bundle.pydevd_breakpoints import stop_on_unhandled_exception
|
|
from _pydevd_bundle.pydevd_utils import get_clsname_for_code
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
import cython
|
|
from _pydevd_bundle.pydevd_cython cimport set_additional_thread_info, any_thread_stepping, PyDBAdditionalThreadInfo
|
|
# ELSE
|
|
# from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info, any_thread_stepping, PyDBAdditionalThreadInfo
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
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
|
|
|
|
|
|
if hasattr(sys, "monitoring"):
|
|
DEBUGGER_ID = sys.monitoring.DEBUGGER_ID
|
|
monitor = sys.monitoring
|
|
|
|
_thread_local_info = threading.local()
|
|
_get_ident = threading.get_ident
|
|
_thread_active = threading._active # noqa
|
|
|
|
CMD_STEP_INTO: int = 107
|
|
CMD_STEP_OVER: int = 108
|
|
CMD_STEP_INTO_MY_CODE: int = 144
|
|
CMD_STEP_INTO_COROUTINE: int = 206
|
|
CMD_SMART_STEP_INTO: int = 128
|
|
can_skip: bool = True
|
|
CMD_STEP_RETURN: int = 109
|
|
CMD_STEP_OVER_MY_CODE: int = 159
|
|
CMD_STEP_RETURN_MY_CODE: int = 160
|
|
CMD_SET_BREAK: int = 111
|
|
CMD_SET_FUNCTION_BREAK: int = 208
|
|
STATE_RUN: int = 1
|
|
STATE_SUSPEND: int = 2
|
|
|
|
IGNORE_EXCEPTION_TAG = re.compile("[^#]*#.*@IgnoreException")
|
|
DEBUG_START = ("pydevd.py", "run")
|
|
DEBUG_START_PY3K = ("_pydev_execfile.py", "execfile")
|
|
TRACE_PROPERTY = "pydevd_traceproperty.py"
|
|
|
|
_global_notify_skipped_step_in = False
|
|
_global_notify_skipped_step_in_lock = ForkSafeLock()
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _notify_skipped_step_in_because_of_filters(py_db, frame):
|
|
# ELSE
|
|
# def _notify_skipped_step_in_because_of_filters(py_db, frame):
|
|
# ENDIF
|
|
# fmt: on
|
|
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)
|
|
|
|
|
|
# Easy for cython: always get the one at level 0 as that's the caller frame
|
|
# (on Python we have to control the depth to get the first user frame).
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
@cython.cfunc
|
|
def _getframe(depth=0):
|
|
return sys._getframe()
|
|
# ELSE
|
|
# _getframe = sys._getframe
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _get_bootstrap_frame(depth):
|
|
# ELSE
|
|
# def _get_bootstrap_frame(depth: int) -> Tuple[Optional[FrameType], bool]:
|
|
# ENDIF
|
|
# fmt: on
|
|
try:
|
|
return _thread_local_info.f_bootstrap, _thread_local_info.is_bootstrap_frame_internal
|
|
except:
|
|
frame = _getframe(depth)
|
|
f_bootstrap = frame
|
|
# print('called at', f_bootstrap.f_code.co_name, f_bootstrap.f_code.co_filename, f_bootstrap.f_code.co_firstlineno)
|
|
is_bootstrap_frame_internal = False
|
|
while f_bootstrap is not None:
|
|
filename = f_bootstrap.f_code.co_filename
|
|
name = splitext(basename(filename))[0]
|
|
|
|
if name == "threading":
|
|
if f_bootstrap.f_code.co_name in ("__bootstrap", "_bootstrap"):
|
|
# We need __bootstrap_inner, not __bootstrap.
|
|
return None, False
|
|
|
|
elif f_bootstrap.f_code.co_name in ("__bootstrap_inner", "_bootstrap_inner", "is_alive"):
|
|
# Note: be careful not to use threading.current_thread to avoid creating a dummy thread.
|
|
is_bootstrap_frame_internal = True
|
|
break
|
|
|
|
elif name == "pydev_monkey":
|
|
if f_bootstrap.f_code.co_name == "__call__":
|
|
is_bootstrap_frame_internal = True
|
|
break
|
|
|
|
elif name == "pydevd":
|
|
if f_bootstrap.f_code.co_name in ("run", "main"):
|
|
# We need to get to _exec
|
|
return None, False
|
|
|
|
if f_bootstrap.f_code.co_name == "_exec":
|
|
is_bootstrap_frame_internal = True
|
|
break
|
|
|
|
elif f_bootstrap.f_back is None:
|
|
break
|
|
|
|
f_bootstrap = f_bootstrap.f_back
|
|
|
|
if f_bootstrap is not None:
|
|
_thread_local_info.is_bootstrap_frame_internal = is_bootstrap_frame_internal
|
|
_thread_local_info.f_bootstrap = f_bootstrap
|
|
return _thread_local_info.f_bootstrap, _thread_local_info.is_bootstrap_frame_internal
|
|
|
|
return f_bootstrap, is_bootstrap_frame_internal
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _get_unhandled_exception_frame(exc, int depth):
|
|
# ELSE
|
|
# def _get_unhandled_exception_frame(exc, depth: int) -> Optional[FrameType]:
|
|
# ENDIF
|
|
# fmt: on
|
|
try:
|
|
# Unhandled frame has to be from the same exception.
|
|
if _thread_local_info.f_unhandled_exc is exc:
|
|
return _thread_local_info.f_unhandled_frame
|
|
else:
|
|
del _thread_local_info.f_unhandled_frame
|
|
del _thread_local_info.f_unhandled_exc
|
|
raise AttributeError('Not the same exception')
|
|
except:
|
|
f_unhandled = _getframe(depth)
|
|
|
|
while f_unhandled is not None and f_unhandled.f_back is not None:
|
|
f_back = f_unhandled.f_back
|
|
filename = f_back.f_code.co_filename
|
|
name = splitext(basename(filename))[0]
|
|
|
|
# When the back frame is the bootstrap (or if we have no back
|
|
# frame) then use this frame as the one to track.
|
|
if name == "threading":
|
|
if f_back.f_code.co_name in ("__bootstrap", "_bootstrap", "__bootstrap_inner", "_bootstrap_inner", "run"):
|
|
break
|
|
|
|
elif name == "pydev_monkey":
|
|
if f_back.f_code.co_name == "__call__":
|
|
break
|
|
|
|
elif name == "pydevd":
|
|
if f_back.f_code.co_name in ("_exec", "run", "main"):
|
|
break
|
|
|
|
elif name == "pydevd_runpy":
|
|
if f_back.f_code.co_name.startswith(("run", "_run")):
|
|
break
|
|
|
|
elif name == "<frozen runpy>":
|
|
if f_back.f_code.co_name.startswith(("run", "_run")):
|
|
break
|
|
|
|
elif name == "runpy":
|
|
if f_back.f_code.co_name.startswith(("run", "_run")):
|
|
break
|
|
|
|
f_unhandled = f_back
|
|
|
|
if f_unhandled is not None:
|
|
_thread_local_info.f_unhandled_frame = f_unhandled
|
|
_thread_local_info.f_unhandled_exc = exc
|
|
return _thread_local_info.f_unhandled_frame
|
|
|
|
return f_unhandled
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef class ThreadInfo:
|
|
cdef unsigned long thread_ident
|
|
cdef PyDBAdditionalThreadInfo additional_info
|
|
thread: threading.Thread
|
|
trace: bool
|
|
_use_is_stopped: bool
|
|
# ELSE
|
|
# class ThreadInfo:
|
|
# additional_info: PyDBAdditionalThreadInfo
|
|
# thread_ident: int
|
|
# thread: threading.Thread
|
|
# trace: bool
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
def __init__(self, thread, unsigned long thread_ident, bint trace, PyDBAdditionalThreadInfo additional_info):
|
|
# ELSE
|
|
# def __init__(self, thread: threading.Thread, thread_ident: int, trace: bool, additional_info: PyDBAdditionalThreadInfo):
|
|
# ENDIF
|
|
# fmt: on
|
|
self.thread = thread
|
|
self.thread_ident = thread_ident
|
|
self.additional_info = additional_info
|
|
self.trace = trace
|
|
self._use_is_stopped = hasattr(thread, '_is_stopped')
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef bint is_thread_alive(self):
|
|
# ELSE
|
|
# def is_thread_alive(self):
|
|
# ENDIF
|
|
# fmt: on
|
|
if self._use_is_stopped:
|
|
return not self.thread._is_stopped
|
|
else:
|
|
return pydevd_is_thread_alive(self.thread)
|
|
|
|
|
|
class _DeleteDummyThreadOnDel:
|
|
"""
|
|
Helper class to remove a dummy thread from threading._active on __del__.
|
|
"""
|
|
|
|
def __init__(self, dummy_thread):
|
|
self._dummy_thread = dummy_thread
|
|
self._tident = dummy_thread.ident
|
|
# Put the thread on a thread local variable so that when
|
|
# the related thread finishes this instance is collected.
|
|
#
|
|
# Note: no other references to this instance may be created.
|
|
# If any client code creates a reference to this instance,
|
|
# the related _DummyThread will be kept forever!
|
|
_thread_local_info._track_dummy_thread_ref = self
|
|
|
|
def __del__(self):
|
|
with threading._active_limbo_lock:
|
|
if _thread_active.get(self._tident) is self._dummy_thread:
|
|
_thread_active.pop(self._tident, None)
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _create_thread_info(depth):
|
|
cdef unsigned long thread_ident
|
|
# ELSE
|
|
# def _create_thread_info(depth):
|
|
# ENDIF
|
|
# fmt: on
|
|
# Don't call threading.currentThread because if we're too early in the process
|
|
# we may create a dummy thread.
|
|
thread_ident = _get_ident()
|
|
|
|
f_bootstrap_frame, is_bootstrap_frame_internal = _get_bootstrap_frame(depth + 1)
|
|
if f_bootstrap_frame is None:
|
|
return None # Case for threading when it's still in bootstrap or early in pydevd.
|
|
|
|
if is_bootstrap_frame_internal:
|
|
t = None
|
|
if f_bootstrap_frame.f_code.co_name in ("__bootstrap_inner", "_bootstrap_inner", "is_alive"):
|
|
# Note: be careful not to use threading.current_thread to avoid creating a dummy thread.
|
|
t = f_bootstrap_frame.f_locals.get("self")
|
|
if not isinstance(t, threading.Thread):
|
|
t = None
|
|
|
|
elif f_bootstrap_frame.f_code.co_name in ("_exec", "__call__"):
|
|
# Note: be careful not to use threading.current_thread to avoid creating a dummy thread.
|
|
t = f_bootstrap_frame.f_locals.get("t")
|
|
if not isinstance(t, threading.Thread):
|
|
t = None
|
|
|
|
else:
|
|
# This means that the first frame is not in threading nor in pydevd.
|
|
# In practice this means it's some unmanaged thread, so, creating
|
|
# a dummy thread is ok in this use-case.
|
|
t = threading.current_thread()
|
|
|
|
if t is None:
|
|
t = _thread_active.get(thread_ident)
|
|
|
|
if isinstance(t, threading._DummyThread) and not IS_PY313_OR_GREATER:
|
|
_thread_local_info._ref = _DeleteDummyThreadOnDel(t)
|
|
|
|
if t is None:
|
|
return None
|
|
|
|
if getattr(t, "is_pydev_daemon_thread", False):
|
|
return ThreadInfo(t, thread_ident, False, None)
|
|
else:
|
|
try:
|
|
additional_info = t.additional_info
|
|
if additional_info is None:
|
|
raise AttributeError()
|
|
except:
|
|
additional_info = set_additional_thread_info(t)
|
|
return ThreadInfo(t, thread_ident, True, additional_info)
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef class FuncCodeInfo:
|
|
cdef str co_filename
|
|
cdef str canonical_normalized_filename
|
|
cdef str abs_path_filename
|
|
cdef bint always_skip_code
|
|
cdef bint breakpoint_found
|
|
cdef bint function_breakpoint_found
|
|
cdef bint plugin_line_breakpoint_found
|
|
cdef bint plugin_call_breakpoint_found
|
|
cdef bint plugin_line_stepping
|
|
cdef bint plugin_call_stepping
|
|
cdef bint plugin_return_stepping
|
|
cdef int pydb_mtime
|
|
cdef dict bp_line_to_breakpoint
|
|
cdef object function_breakpoint
|
|
cdef bint always_filtered_out
|
|
cdef bint filtered_out_force_checked
|
|
cdef object try_except_container_obj
|
|
cdef object code_obj
|
|
cdef str co_name
|
|
# ELSE
|
|
# class FuncCodeInfo:
|
|
#
|
|
# ENDIF
|
|
# fmt: on
|
|
def __init__(self):
|
|
self.co_filename: str = ""
|
|
self.canonical_normalized_filename: str = ""
|
|
self.abs_path_filename: str = ""
|
|
|
|
# These is never seen and we never stop, even if it's a callback coming
|
|
# from user code (these are completely invisible to the debugging tracing).
|
|
self.always_skip_code: bool = False
|
|
|
|
self.breakpoint_found: bool = False
|
|
self.function_breakpoint_found: bool = False
|
|
|
|
# A plugin can choose whether to stop on function calls or line events.
|
|
self.plugin_line_breakpoint_found: bool = False
|
|
self.plugin_call_breakpoint_found: bool = False
|
|
|
|
self.plugin_line_stepping: bool = False
|
|
self.plugin_call_stepping: bool = False
|
|
self.plugin_return_stepping: bool = False
|
|
|
|
# When pydb_mtime != PyDb.mtime the validity of breakpoints have
|
|
# to be re-evaluated (if invalid a new FuncCodeInfo must be created and
|
|
# tracing can't be disabled for the related frames).
|
|
self.pydb_mtime: int = -1
|
|
|
|
self.bp_line_to_breakpoint: Dict[int, Any] = {}
|
|
self.function_breakpoint = None
|
|
|
|
# This means some file is globally filtered out during debugging. Note
|
|
# that we may still need to pause in it (in a step return to user code,
|
|
# we may need to track this one).
|
|
self.always_filtered_out: bool = False
|
|
|
|
# This should be used to filter code in a CMD_STEP_INTO_MY_CODE
|
|
# (and other XXX_MY_CODE variants).
|
|
self.filtered_out_force_checked: bool = False
|
|
|
|
self.try_except_container_obj: Optional[_TryExceptContainerObj] = None
|
|
self.code_obj: CodeType = None
|
|
self.co_name: str = ""
|
|
|
|
def get_line_of_offset(self, offset):
|
|
for start, end, line in self.code_obj.co_lines():
|
|
if start is not None and end is not None and line is not None:
|
|
if offset >= start and offset <= end:
|
|
return line
|
|
return -1
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _get_thread_info(bint create, int depth):
|
|
# ELSE
|
|
# def _get_thread_info(create: bool, depth: int) -> Optional[ThreadInfo]:
|
|
# ENDIF
|
|
# fmt: on
|
|
"""
|
|
Provides thread-related info.
|
|
|
|
May return None if the thread is still not active.
|
|
"""
|
|
try:
|
|
# Note: changing to a `dict[thread.ident] = thread_info` had almost no
|
|
# effect in the performance.
|
|
return _thread_local_info.thread_info
|
|
except:
|
|
if not create:
|
|
return None
|
|
thread_info = _create_thread_info(depth + 1)
|
|
if thread_info is None:
|
|
return None
|
|
|
|
_thread_local_info.thread_info = thread_info
|
|
return _thread_local_info.thread_info
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef class _CodeLineInfo:
|
|
cdef dict line_to_offset
|
|
cdef int first_line
|
|
cdef int last_line
|
|
# ELSE
|
|
# class _CodeLineInfo:
|
|
# line_to_offset: Dict[int, Any]
|
|
# first_line: int
|
|
# last_line: int
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
def __init__(self, dict line_to_offset, int first_line, int last_line):
|
|
self.line_to_offset = line_to_offset
|
|
self.first_line = first_line
|
|
self.last_line = last_line
|
|
# ELSE
|
|
# def __init__(self, line_to_offset, first_line, last_line):
|
|
# self.line_to_offset = line_to_offset
|
|
# self.first_line = first_line
|
|
# self.last_line = last_line
|
|
#
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
# Note: this method has a version in cython too
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _CodeLineInfo _get_code_line_info(code_obj, _cache={}):
|
|
# ELSE
|
|
# def _get_code_line_info(code_obj, _cache={}) -> _CodeLineInfo:
|
|
# ENDIF
|
|
# fmt: on
|
|
try:
|
|
return _cache[code_obj]
|
|
except:
|
|
line_to_offset = {}
|
|
first_line = None
|
|
last_line = None
|
|
|
|
for offset, line in dis.findlinestarts(code_obj):
|
|
if line is not None:
|
|
line_to_offset[line] = offset
|
|
|
|
if len(line_to_offset):
|
|
first_line = min(line_to_offset)
|
|
last_line = max(line_to_offset)
|
|
ret = _CodeLineInfo(line_to_offset, first_line, last_line)
|
|
_cache[code_obj] = ret
|
|
return ret
|
|
|
|
|
|
_code_to_func_code_info_cache: Dict[CodeType, "FuncCodeInfo"] = {}
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cpdef FuncCodeInfo _get_func_code_info(code_obj, frame_or_depth):
|
|
cdef FuncCodeInfo func_code_info
|
|
# ELSE
|
|
# def _get_func_code_info(code_obj, frame_or_depth) -> FuncCodeInfo:
|
|
# ENDIF
|
|
# fmt: on
|
|
"""
|
|
Provides code-object related info.
|
|
|
|
Note that it contains informations on the breakpoints for a given function.
|
|
If breakpoints change a new FuncCodeInfo instance will be created.
|
|
|
|
Note that this can be called by any thread.
|
|
"""
|
|
py_db = GlobalDebuggerHolder.global_dbg
|
|
if py_db is None:
|
|
return None
|
|
|
|
func_code_info = _code_to_func_code_info_cache.get(code_obj)
|
|
if func_code_info is not None:
|
|
if func_code_info.pydb_mtime == py_db.mtime:
|
|
# if DEBUG:
|
|
# print('_get_func_code_info: matched mtime', key, code_obj)
|
|
return func_code_info
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef dict cache_file_type
|
|
cdef tuple cache_file_type_key
|
|
cdef PyCodeObject * code
|
|
cdef str co_filename
|
|
cdef str co_name
|
|
code = <PyCodeObject *> code_obj
|
|
co_filename = <str> code.co_filename
|
|
co_name = <str> code.co_name
|
|
# ELSE
|
|
# cache_file_type: dict
|
|
# cache_file_type_key: tuple
|
|
# code = code_obj
|
|
# co_filename: str = code.co_filename
|
|
# co_name: str = code.co_name
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
# print('_get_func_code_info: new (mtime did not match)', key, code_obj)
|
|
|
|
func_code_info = FuncCodeInfo()
|
|
func_code_info.code_obj = code_obj
|
|
code_line_info = _get_code_line_info(code_obj)
|
|
line_to_offset = code_line_info.line_to_offset
|
|
func_code_info.pydb_mtime = py_db.mtime
|
|
|
|
func_code_info.co_filename = co_filename
|
|
func_code_info.co_name = co_name
|
|
|
|
# Compute whether to always skip this.
|
|
try:
|
|
abs_path_real_path_and_base = NORM_PATHS_AND_BASE_CONTAINER[co_filename]
|
|
except:
|
|
abs_path_real_path_and_base = get_abs_path_real_path_and_base_from_file(co_filename)
|
|
|
|
func_code_info.abs_path_filename = abs_path_real_path_and_base[0]
|
|
func_code_info.canonical_normalized_filename = abs_path_real_path_and_base[1]
|
|
|
|
frame = None
|
|
cache_file_type = py_db.get_cache_file_type()
|
|
# Note: this cache key must be the same from PyDB.get_file_type() -- see it for comments
|
|
# on the cache.
|
|
cache_file_type_key = (code.co_firstlineno, abs_path_real_path_and_base[0], code_obj)
|
|
try:
|
|
file_type = cache_file_type[cache_file_type_key] # Make it faster
|
|
except:
|
|
if frame is None:
|
|
if frame_or_depth.__class__ == int:
|
|
frame = _getframe(frame_or_depth + 1)
|
|
else:
|
|
frame = frame_or_depth
|
|
assert frame.f_code is code_obj, "%s != %s" % (frame.f_code, code_obj)
|
|
|
|
file_type = py_db.get_file_type(frame, abs_path_real_path_and_base) # we don't want to debug anything related to pydevd
|
|
|
|
if file_type is not None:
|
|
func_code_info.always_skip_code = True
|
|
func_code_info.always_filtered_out = True
|
|
_code_to_func_code_info_cache[code_obj] = func_code_info
|
|
return func_code_info
|
|
|
|
# still not set, check for dont trace comments.
|
|
if pydevd_dont_trace.should_trace_hook is not None:
|
|
# I.e.: cache the result 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(code_obj, func_code_info.abs_path_filename):
|
|
if frame is None:
|
|
if frame_or_depth.__class__ == int:
|
|
frame = _getframe(frame_or_depth + 1)
|
|
else:
|
|
frame = frame_or_depth
|
|
assert frame.f_code is code_obj
|
|
|
|
func_code_info.always_filtered_out = True
|
|
_code_to_func_code_info_cache[code_obj] = func_code_info
|
|
return func_code_info
|
|
|
|
if frame is None:
|
|
if frame_or_depth.__class__ == int:
|
|
frame = _getframe(frame_or_depth + 1)
|
|
else:
|
|
frame = frame_or_depth
|
|
assert frame.f_code is code_obj
|
|
|
|
func_code_info.filtered_out_force_checked = py_db.apply_files_filter(frame, func_code_info.abs_path_filename, True)
|
|
|
|
if py_db.is_files_filter_enabled:
|
|
func_code_info.always_filtered_out = py_db.apply_files_filter(frame, func_code_info.abs_path_filename, False)
|
|
if func_code_info.always_filtered_out:
|
|
_code_to_func_code_info_cache[code_obj] = func_code_info
|
|
return func_code_info
|
|
|
|
else:
|
|
func_code_info.always_filtered_out = False
|
|
|
|
# Handle regular breakpoints
|
|
breakpoints: dict = py_db.breakpoints.get(func_code_info.canonical_normalized_filename)
|
|
function_breakpoint: object = py_db.function_breakpoint_name_to_breakpoint.get(func_code_info.co_name)
|
|
# print('\n---')
|
|
# print(py_db.breakpoints)
|
|
# print(func_code_info.canonical_normalized_filename)
|
|
# print(py_db.breakpoints.get(func_code_info.canonical_normalized_filename))
|
|
if function_breakpoint:
|
|
# Go directly into tracing mode
|
|
func_code_info.function_breakpoint_found = True
|
|
func_code_info.function_breakpoint = function_breakpoint
|
|
|
|
if breakpoints:
|
|
# if DEBUG:
|
|
# print('found breakpoints', code_obj_py.co_name, breakpoints)
|
|
|
|
bp_line_to_breakpoint = {}
|
|
|
|
for breakpoint_line, bp in breakpoints.items():
|
|
if breakpoint_line in line_to_offset:
|
|
bp_line_to_breakpoint[breakpoint_line] = bp
|
|
|
|
func_code_info.breakpoint_found = bool(bp_line_to_breakpoint)
|
|
func_code_info.bp_line_to_breakpoint = bp_line_to_breakpoint
|
|
|
|
if py_db.plugin:
|
|
plugin_manager = py_db.plugin
|
|
is_tracked_frame = plugin_manager.is_tracked_frame(frame)
|
|
|
|
if is_tracked_frame:
|
|
if py_db.has_plugin_line_breaks:
|
|
required_events_breakpoint = plugin_manager.required_events_breakpoint()
|
|
func_code_info.plugin_line_breakpoint_found = "line" in required_events_breakpoint
|
|
func_code_info.plugin_call_breakpoint_found = "call" in required_events_breakpoint
|
|
|
|
required_events_stepping = plugin_manager.required_events_stepping()
|
|
func_code_info.plugin_line_stepping: bool = "line" in required_events_stepping
|
|
func_code_info.plugin_call_stepping: bool = "call" in required_events_stepping
|
|
func_code_info.plugin_return_stepping: bool = "return" in required_events_stepping
|
|
|
|
_code_to_func_code_info_cache[code_obj] = func_code_info
|
|
return func_code_info
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _enable_line_tracing(code):
|
|
# ELSE
|
|
# def _enable_line_tracing(code):
|
|
# ENDIF
|
|
# fmt: on
|
|
# print('enable line tracing', code)
|
|
_ensure_monitoring()
|
|
events = monitor.get_local_events(DEBUGGER_ID, code)
|
|
monitor.set_local_events(DEBUGGER_ID, code, events | monitor.events.LINE | monitor.events.JUMP)
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _enable_return_tracing(code):
|
|
# ELSE
|
|
# def _enable_return_tracing(code):
|
|
# ENDIF
|
|
# fmt: on
|
|
# print('enable return tracing', code)
|
|
_ensure_monitoring()
|
|
events = monitor.get_local_events(DEBUGGER_ID, code)
|
|
monitor.set_local_events(DEBUGGER_ID, code, events | monitor.events.PY_RETURN)
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cpdef disable_code_tracing(code):
|
|
# ELSE
|
|
# def disable_code_tracing(code):
|
|
# ENDIF
|
|
# fmt: on
|
|
_ensure_monitoring()
|
|
monitor.set_local_events(DEBUGGER_ID, code, 0)
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cpdef enable_code_tracing(unsigned long thread_ident, code, frame):
|
|
# ELSE
|
|
# def enable_code_tracing(thread_ident: Optional[int], code, frame) -> bool:
|
|
# ENDIF
|
|
# fmt: on
|
|
"""
|
|
Note: this must enable code tracing for the given code/frame.
|
|
|
|
The frame can be from any thread!
|
|
|
|
:return: Whether code tracing was added in this function to the given code.
|
|
"""
|
|
# DEBUG = False # 'my_code.py' in code.co_filename or 'other.py' in code.co_filename
|
|
# if DEBUG:
|
|
# print('==== enable code tracing', code.co_filename[-30:], code.co_name)
|
|
py_db: object = GlobalDebuggerHolder.global_dbg
|
|
if py_db is None or py_db.pydb_disposed:
|
|
return False
|
|
|
|
func_code_info: FuncCodeInfo = _get_func_code_info(code, frame)
|
|
if func_code_info.always_skip_code:
|
|
# if DEBUG:
|
|
# print('disable (always skip)')
|
|
return False
|
|
|
|
try:
|
|
thread = threading._active.get(thread_ident)
|
|
if thread is None:
|
|
return False
|
|
additional_info = set_additional_thread_info(thread)
|
|
except:
|
|
# Cannot set based on stepping
|
|
return False
|
|
|
|
return _enable_code_tracing(py_db, additional_info, func_code_info, code, frame, False)
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cpdef reset_thread_local_info():
|
|
# ELSE
|
|
# def reset_thread_local_info():
|
|
# ENDIF
|
|
# fmt: on
|
|
"""Resets the thread local info TLS store for use after a fork()."""
|
|
global _thread_local_info
|
|
_thread_local_info = threading.local()
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef bint _enable_code_tracing(py_db, PyDBAdditionalThreadInfo additional_info, FuncCodeInfo func_code_info, code, frame, bint warn_on_filtered_out):
|
|
cdef int step_cmd
|
|
cdef bint is_stepping
|
|
cdef bint code_tracing_added
|
|
# ELSE
|
|
# def _enable_code_tracing(py_db, additional_info, func_code_info: FuncCodeInfo, code, frame, warn_on_filtered_out) -> bool:
|
|
# ENDIF
|
|
# fmt: on
|
|
"""
|
|
:return: Whether code tracing was added in this function to the given code.
|
|
"""
|
|
# DEBUG = False # 'my_code.py' in code.co_filename or 'other.py' in code.co_filename
|
|
step_cmd = additional_info.pydev_step_cmd
|
|
is_stepping = step_cmd != -1
|
|
code_tracing_added = False
|
|
|
|
if func_code_info.always_filtered_out:
|
|
# if DEBUG:
|
|
# print('disable (always filtered out)')
|
|
if (
|
|
warn_on_filtered_out
|
|
and is_stepping
|
|
and additional_info.pydev_original_step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE)
|
|
and not _global_notify_skipped_step_in
|
|
):
|
|
_notify_skipped_step_in_because_of_filters(py_db, frame)
|
|
|
|
if is_stepping:
|
|
# Tracing may be needed for return value
|
|
_enable_step_tracing(py_db, code, step_cmd, additional_info, frame)
|
|
code_tracing_added = True
|
|
return code_tracing_added
|
|
|
|
if func_code_info.breakpoint_found or func_code_info.plugin_line_breakpoint_found:
|
|
_enable_line_tracing(code)
|
|
code_tracing_added = True
|
|
|
|
if is_stepping:
|
|
_enable_step_tracing(py_db, code, step_cmd, additional_info, frame)
|
|
code_tracing_added = True
|
|
|
|
return code_tracing_added
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _enable_step_tracing(py_db, code, step_cmd, PyDBAdditionalThreadInfo info, frame):
|
|
# ELSE
|
|
# def _enable_step_tracing(py_db, code, step_cmd, info, frame):
|
|
# ENDIF
|
|
# fmt: on
|
|
if step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_INTO_COROUTINE, CMD_SMART_STEP_INTO):
|
|
# Stepping (must have line/return tracing enabled).
|
|
_enable_line_tracing(code)
|
|
_enable_return_tracing(code)
|
|
|
|
elif step_cmd in (CMD_STEP_RETURN, CMD_STEP_RETURN_MY_CODE) and _is_same_frame(info, info.pydev_step_stop, frame):
|
|
_enable_return_tracing(code)
|
|
|
|
elif step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE):
|
|
if _is_same_frame(info, info.pydev_step_stop, frame):
|
|
_enable_line_tracing(code)
|
|
|
|
# Wee need to enable return tracing because if we have a return during a step over
|
|
# we need to stop too.
|
|
_enable_return_tracing(code)
|
|
elif py_db.show_return_values and _is_same_frame(info, info.pydev_step_stop, frame.f_back):
|
|
# Show return values on step over.
|
|
_enable_return_tracing(code)
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef class _TryExceptContainerObj:
|
|
cdef list try_except_infos
|
|
# ELSE
|
|
# class _TryExceptContainerObj:
|
|
# ENDIF
|
|
# fmt: on
|
|
"""
|
|
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.
|
|
"""
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
def __init__(self, list try_except_infos):
|
|
self.try_except_infos = try_except_infos
|
|
# ELSE
|
|
# def __init__(self, try_except_infos):
|
|
# self.try_except_infos = try_except_infos
|
|
#
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _unwind_event(code, instruction, exc):
|
|
cdef ThreadInfo thread_info
|
|
cdef FuncCodeInfo func_code_info
|
|
# ELSE
|
|
# def _unwind_event(code, instruction, exc):
|
|
# ENDIF
|
|
# fmt: on
|
|
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 not thread_info.is_thread_alive():
|
|
# 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, 1)
|
|
if func_code_info.always_skip_code:
|
|
return
|
|
|
|
# print('_unwind_event', code, exc)
|
|
frame = _getframe(1)
|
|
arg = (type(exc), exc, exc.__traceback__)
|
|
|
|
has_caught_exception_breakpoint_in_pydb = (
|
|
py_db.break_on_caught_exceptions or py_db.break_on_user_uncaught_exceptions or py_db.has_plugin_exception_breaks
|
|
)
|
|
|
|
if has_caught_exception_breakpoint_in_pydb:
|
|
_should_stop, frame, user_uncaught_exc_info = should_stop_on_exception(
|
|
py_db, thread_info.additional_info, frame, thread_info.thread, arg, None, is_unwind=True
|
|
)
|
|
if user_uncaught_exc_info:
|
|
# TODO: Check: this may no longer be needed as in the unwind we know it's
|
|
# an exception bubbling up (wait for all tests to pass to check it).
|
|
if func_code_info.try_except_container_obj is None:
|
|
container_obj = _TryExceptContainerObj(py_db.collect_try_except_info(frame.f_code))
|
|
func_code_info.try_except_container_obj = container_obj
|
|
|
|
is_unhandled = is_unhandled_exception(
|
|
func_code_info.try_except_container_obj, py_db, frame, user_uncaught_exc_info[1], user_uncaught_exc_info[2]
|
|
)
|
|
|
|
if is_unhandled:
|
|
handle_exception(py_db, thread_info.thread, frame, user_uncaught_exc_info[0], EXCEPTION_TYPE_USER_UNHANDLED)
|
|
return
|
|
|
|
break_on_uncaught_exceptions = py_db.break_on_uncaught_exceptions
|
|
if break_on_uncaught_exceptions:
|
|
if frame is _get_unhandled_exception_frame(exc, 1):
|
|
stop_on_unhandled_exception(py_db, thread_info.thread, thread_info.additional_info, arg)
|
|
return
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _raise_event(code, instruction, exc):
|
|
cdef ThreadInfo thread_info
|
|
cdef FuncCodeInfo func_code_info
|
|
# ELSE
|
|
# def _raise_event(code, instruction, exc):
|
|
# ENDIF
|
|
# fmt: on
|
|
"""
|
|
The way this should work is the following: when the user is using
|
|
pydevd to do the launch and we're on a managed stack, we should consider
|
|
unhandled only if it gets into a pydevd. If it's a thread, if it stops
|
|
inside the threading and if it's an unmanaged thread (i.e.: QThread)
|
|
then stop if it doesn't have a back frame.
|
|
|
|
Note: unlike other events, this one is global and not per-code (so,
|
|
it cannot be individually enabled/disabled for a given code object).
|
|
"""
|
|
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 not thread_info.is_thread_alive():
|
|
# 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, 1)
|
|
if func_code_info.always_skip_code:
|
|
return
|
|
|
|
frame = _getframe(1)
|
|
arg = (type(exc), exc, exc.__traceback__)
|
|
|
|
# Compute the previous exception info (if any). We use it to check if the exception
|
|
# should be stopped
|
|
prev_exc_info = _thread_local_info._user_uncaught_exc_info if hasattr(_thread_local_info, "_user_uncaught_exc_info") else None
|
|
should_stop, frame, _user_uncaught_exc_info = should_stop_on_exception(
|
|
py_db, thread_info.additional_info, frame, thread_info.thread, arg, prev_exc_info
|
|
)
|
|
|
|
# Save the current exception info for the next raise event.
|
|
_thread_local_info._user_uncaught_exc_info = _user_uncaught_exc_info
|
|
|
|
# print('!!!! should_stop (in raise)', should_stop)
|
|
if should_stop:
|
|
handle_exception(py_db, thread_info.thread, frame, arg, EXCEPTION_TYPE_HANDLED)
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef str get_func_name(frame):
|
|
cdef str func_name
|
|
# ELSE
|
|
# def get_func_name(frame):
|
|
# ENDIF
|
|
# fmt: on
|
|
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
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _show_return_values(frame, arg):
|
|
# ELSE
|
|
# def _show_return_values(frame, arg):
|
|
# ENDIF
|
|
# fmt: on
|
|
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 = get_func_name(frame)
|
|
return_values_dict[name] = arg
|
|
except:
|
|
pydev_log.exception()
|
|
finally:
|
|
f_locals_back = None
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _remove_return_values(py_db, frame):
|
|
# ELSE
|
|
# def _remove_return_values(py_db, frame):
|
|
# ENDIF
|
|
# fmt: on
|
|
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
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _return_event(code, instruction, retval):
|
|
cdef ThreadInfo thread_info
|
|
cdef FuncCodeInfo func_code_info
|
|
cdef PyDBAdditionalThreadInfo info
|
|
cdef int step_cmd
|
|
# ELSE
|
|
# def _return_event(code, instruction, retval):
|
|
# ENDIF
|
|
# fmt: on
|
|
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 monitor.DISABLE
|
|
|
|
if not thread_info.trace or not thread_info.is_thread_alive():
|
|
# 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, 1)
|
|
if func_code_info.always_skip_code:
|
|
return monitor.DISABLE
|
|
|
|
info = thread_info.additional_info
|
|
|
|
# We know the frame depth.
|
|
frame = _getframe(1)
|
|
|
|
step_cmd = info.pydev_step_cmd
|
|
if step_cmd == -1:
|
|
return
|
|
|
|
if info.suspend_type != PYTHON_SUSPEND:
|
|
# Plugin stepping
|
|
if func_code_info.plugin_return_stepping:
|
|
_plugin_stepping(py_db, step_cmd, "return", frame, thread_info)
|
|
return
|
|
|
|
if info.pydev_state == STATE_SUSPEND:
|
|
# We're already suspended, don't handle any more events on this thread.
|
|
_do_wait_suspend(py_db, thread_info, frame, "return", None)
|
|
return
|
|
|
|
# Python line stepping
|
|
stop_frame = info.pydev_step_stop
|
|
if step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_INTO_COROUTINE):
|
|
force_check_project_scope = step_cmd == CMD_STEP_INTO_MY_CODE
|
|
if frame.f_back is not None and not info.pydev_use_scoped_step_frame:
|
|
back_func_code_info = _get_func_code_info(frame.f_back.f_code, frame.f_back)
|
|
if (
|
|
# Not filtered out.
|
|
not back_func_code_info.always_skip_code
|
|
and not back_func_code_info.always_filtered_out
|
|
and not (force_check_project_scope and back_func_code_info.filtered_out_force_checked)
|
|
# Prevent stopping in a return to the same location we were initially
|
|
# (i.e.: double-stop at the same place due to some filtering).
|
|
and info.step_in_initial_location != (frame.f_back, frame.f_back.f_lineno)
|
|
):
|
|
if py_db.show_return_values:
|
|
_show_return_values(frame, retval)
|
|
|
|
_stop_on_return(py_db, thread_info, info, step_cmd, frame, retval)
|
|
return
|
|
|
|
if step_cmd in (CMD_STEP_RETURN, CMD_STEP_RETURN_MY_CODE) and _is_same_frame(info, stop_frame, frame):
|
|
if py_db.show_return_values:
|
|
_show_return_values(frame, retval)
|
|
|
|
_stop_on_return(py_db, thread_info, info, step_cmd, frame, retval)
|
|
return
|
|
|
|
elif (
|
|
step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE)
|
|
and not info.pydev_use_scoped_step_frame
|
|
and _is_same_frame(info, stop_frame, frame)
|
|
):
|
|
# This isn't in the sys.settrace version: on a step over, if we return and the return is valid, show
|
|
# as a step return instead of going back to step into mode (but if the back frame is not valid, then
|
|
# go to step into mode).
|
|
f_back = frame.f_back
|
|
if f_back is not None:
|
|
back_func_code_info = _get_func_code_info(f_back.f_code, 2)
|
|
force_check_project_scope = step_cmd == CMD_STEP_OVER_MY_CODE
|
|
|
|
if (
|
|
back_func_code_info is not None
|
|
and not back_func_code_info.always_skip_code
|
|
and not back_func_code_info.always_filtered_out
|
|
and not (force_check_project_scope and back_func_code_info.filtered_out_force_checked)
|
|
):
|
|
if py_db.show_return_values:
|
|
_show_return_values(frame, retval)
|
|
|
|
_stop_on_return(py_db, thread_info, info, step_cmd, frame, retval)
|
|
return
|
|
|
|
elif step_cmd == CMD_SMART_STEP_INTO:
|
|
if _is_same_frame(info, stop_frame, frame):
|
|
# We're exiting the smart step into initial frame (so, we probably didn't find our target).
|
|
if py_db.show_return_values:
|
|
_show_return_values(frame, retval)
|
|
|
|
_stop_on_return(py_db, thread_info, info, step_cmd, frame, retval)
|
|
return
|
|
|
|
if py_db.show_return_values:
|
|
if (
|
|
(
|
|
info.pydev_step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE, CMD_SMART_STEP_INTO)
|
|
and (_is_same_frame(info, stop_frame, frame.f_back))
|
|
)
|
|
or (info.pydev_step_cmd in (CMD_STEP_RETURN, CMD_STEP_RETURN_MY_CODE) and (info, _is_same_frame(info, stop_frame, frame)))
|
|
or (info.pydev_step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_COROUTINE))
|
|
or (
|
|
info.pydev_step_cmd == CMD_STEP_INTO_MY_CODE
|
|
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)
|
|
)
|
|
):
|
|
_show_return_values(frame, retval)
|
|
|
|
if step_cmd in (CMD_STEP_OVER, CMD_STEP_RETURN, CMD_STEP_OVER_MY_CODE, CMD_STEP_RETURN_MY_CODE, CMD_SMART_STEP_INTO):
|
|
# 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.
|
|
stop_frame = info.pydev_step_stop
|
|
if stop_frame is frame and not info.pydev_use_scoped_step_frame:
|
|
if step_cmd in (CMD_STEP_OVER, CMD_STEP_RETURN, CMD_SMART_STEP_INTO):
|
|
info.pydev_step_cmd = CMD_STEP_INTO
|
|
else:
|
|
info.pydev_step_cmd = CMD_STEP_INTO_MY_CODE
|
|
info.pydev_step_stop = None
|
|
_enable_code_tracing_for_frame_and_parents(thread_info, stop_frame.f_back)
|
|
if py_db.show_return_values:
|
|
_show_return_values(frame, retval)
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _enable_code_tracing_for_frame_and_parents(ThreadInfo thread_info, frame):
|
|
cdef FuncCodeInfo func_code_info
|
|
# ELSE
|
|
# def _enable_code_tracing_for_frame_and_parents(thread_info, frame):
|
|
# ENDIF
|
|
# fmt: on
|
|
py_db: object = GlobalDebuggerHolder.global_dbg
|
|
if py_db is None or py_db.pydb_disposed:
|
|
return
|
|
|
|
while frame is not None:
|
|
func_code_info: FuncCodeInfo = _get_func_code_info(frame.f_code, frame)
|
|
if func_code_info.always_skip_code:
|
|
frame = frame.f_back
|
|
continue
|
|
|
|
_enable_code_tracing(py_db, thread_info.additional_info, func_code_info, frame.f_code, frame, False)
|
|
frame = frame.f_back
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _stop_on_return(py_db, ThreadInfo thread_info, PyDBAdditionalThreadInfo info, int step_cmd, frame, retval):
|
|
# ELSE
|
|
# def _stop_on_return(py_db, thread_info, info, step_cmd, frame, retval):
|
|
# ENDIF
|
|
# fmt: on
|
|
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
|
|
|
|
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_info.thread_ident, back)
|
|
return
|
|
|
|
if back is not None:
|
|
# if we're in a return, we want it to appear to the user in the previous frame!
|
|
py_db.set_suspend(thread_info.thread, step_cmd, original_step_cmd=info.pydev_original_step_cmd)
|
|
_do_wait_suspend(py_db, thread_info, back, "return", retval)
|
|
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 = STATE_RUN
|
|
info.update_stepping_info()
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _stop_on_breakpoint(py_db, ThreadInfo thread_info, int stop_reason, bp, frame, new_frame, bint stop, bint stop_on_plugin_breakpoint, str bp_type):
|
|
cdef PyDBAdditionalThreadInfo additional_info
|
|
# ELSE
|
|
# def _stop_on_breakpoint(
|
|
# py_db, thread_info: ThreadInfo, stop_reason: int, bp, frame, new_frame, stop: bool, stop_on_plugin_breakpoint: bool, bp_type: str
|
|
# ):
|
|
# ENDIF
|
|
# fmt: on
|
|
"""
|
|
:param bp: the breakpoint hit (additional conditions will be checked now).
|
|
:param frame: the actual frame
|
|
:param new_frame: either the actual frame or the frame provided by the plugins.
|
|
:param stop: whether we should do a regular line breakpoint.
|
|
:param stop_on_plugin_breakpoint: whether we should stop in a plugin breakpoint.
|
|
|
|
:return:
|
|
True if the breakpoint was suspended inside this function and False otherwise.
|
|
Note that even if False is returned, it's still possible
|
|
"""
|
|
additional_info = thread_info.additional_info
|
|
# ok, hit breakpoint, now, we have to discover if it is a conditional breakpoint
|
|
# lets do the conditional stuff here
|
|
if bp.expression is not None:
|
|
# If it has an expression, it's always handled even if we don't stop.
|
|
py_db.handle_breakpoint_expression(bp, additional_info, new_frame)
|
|
|
|
if stop or stop_on_plugin_breakpoint:
|
|
if bp.has_condition:
|
|
eval_result = py_db.handle_breakpoint_condition(additional_info, bp, new_frame)
|
|
if not eval_result:
|
|
stop = False
|
|
stop_on_plugin_breakpoint = False
|
|
|
|
# Handle logpoint (on a logpoint we should never stop).
|
|
if (stop or stop_on_plugin_breakpoint) and bp.is_logpoint:
|
|
stop = False
|
|
stop_on_plugin_breakpoint = False
|
|
|
|
if additional_info.pydev_message is not None and len(additional_info.pydev_message) > 0:
|
|
cmd = py_db.cmd_factory.make_io_message(additional_info.pydev_message + os.linesep, "1")
|
|
py_db.writer.add_command(cmd)
|
|
|
|
if stop:
|
|
py_db.set_suspend(
|
|
thread_info.thread,
|
|
stop_reason,
|
|
suspend_other_threads=bp and bp.suspend_policy == "ALL",
|
|
)
|
|
# print('suspend on breakpoint...')
|
|
_do_wait_suspend(py_db, thread_info, frame, "line", None)
|
|
return True
|
|
|
|
elif stop_on_plugin_breakpoint:
|
|
stop_at_frame = py_db.plugin.suspend(py_db, thread_info.thread, frame, bp_type)
|
|
if stop_at_frame and thread_info.additional_info.pydev_state == STATE_SUSPEND:
|
|
_do_wait_suspend(py_db, thread_info, stop_at_frame, "line", None)
|
|
return
|
|
|
|
return False
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _plugin_stepping(py_db, int step_cmd, event, frame, ThreadInfo thread_info):
|
|
cdef bint stop
|
|
cdef dict stop_info
|
|
# ELSE
|
|
# def _plugin_stepping(py_db, step_cmd, event, frame, thread_info):
|
|
# ENDIF
|
|
# fmt: on
|
|
plugin_manager = py_db.plugin
|
|
# Step return makes no sense for plugins (I guess?!?), so, just handle as step into.
|
|
if step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_INTO_COROUTINE, CMD_SMART_STEP_INTO) or step_cmd in (
|
|
CMD_STEP_RETURN,
|
|
CMD_STEP_RETURN_MY_CODE,
|
|
):
|
|
stop_info = {}
|
|
stop = False
|
|
result = plugin_manager.cmd_step_into(py_db, frame, event, thread_info.additional_info, thread_info.thread, stop_info, stop)
|
|
if result:
|
|
stop, plugin_stop = result
|
|
if plugin_stop:
|
|
plugin_manager.stop(py_db, frame, event, thread_info.thread, stop_info, None, step_cmd)
|
|
return
|
|
|
|
elif step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE):
|
|
if plugin_manager is not None:
|
|
stop_info = {}
|
|
stop = False
|
|
result = plugin_manager.cmd_step_over(py_db, frame, event, thread_info.additional_info, thread_info.thread, stop_info, stop)
|
|
if result:
|
|
stop, plugin_stop = result
|
|
if plugin_stop:
|
|
plugin_manager.stop(py_db, frame, event, thread_info.thread, stop_info, None, step_cmd)
|
|
return
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _jump_event(code, int from_offset, int to_offset):
|
|
cdef ThreadInfo thread_info
|
|
cdef FuncCodeInfo func_code_info
|
|
cdef int from_line
|
|
cdef int to_line
|
|
# ELSE
|
|
# def _jump_event(code, from_offset, to_offset):
|
|
# ENDIF
|
|
# fmt: on
|
|
# A bunch of things have to be repeated especially because in the sys.monitoring
|
|
# everything is global, yet, when we start tracing something for stepping that
|
|
# needs to be per-thread.
|
|
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 monitor.DISABLE
|
|
|
|
# If we get another jump event, remove the extra check for the line event
|
|
if hasattr(_thread_local_info, "f_disable_next_line_if_match"):
|
|
del _thread_local_info.f_disable_next_line_if_match
|
|
|
|
if not thread_info.trace or not thread_info.is_thread_alive():
|
|
# 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, 1)
|
|
if func_code_info.always_skip_code or func_code_info.always_filtered_out:
|
|
return monitor.DISABLE
|
|
|
|
# Same logic as "sys_trace_jump_func" in https://github.com/python/cpython/blob/main/Python/legacy_tracing.c
|
|
|
|
# Ignore forward jump.
|
|
# print('jump event', code.co_name, 'from offset', from_offset, 'to offset', to_offset)
|
|
if to_offset > from_offset:
|
|
return monitor.DISABLE
|
|
|
|
from_line = func_code_info.get_line_of_offset(from_offset or 0)
|
|
to_line = func_code_info.get_line_of_offset(to_offset or 0)
|
|
|
|
if from_line != to_line:
|
|
# I.e.: use case: "yield from [j for j in a if j % 2 == 0]"
|
|
return monitor.DISABLE
|
|
|
|
# We know the frame depth.
|
|
frame = _getframe(1)
|
|
|
|
# Disable the next line event as we're jumping to a line. The line event will be redundant.
|
|
_thread_local_info.f_disable_next_line_if_match = (func_code_info.co_filename, frame.f_lineno)
|
|
# pydev_log.debug('_jump_event', code.co_name, 'from line', from_line, 'to line', frame.f_lineno)
|
|
|
|
return _internal_line_event(func_code_info, frame, frame.f_lineno)
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _line_event(code, int line):
|
|
cdef ThreadInfo thread_info
|
|
cdef FuncCodeInfo func_code_info
|
|
# ELSE
|
|
# def _line_event(code, line):
|
|
# ENDIF
|
|
# fmt: on
|
|
|
|
# A bunch of things have to be repeated especially because in the sys.monitoring
|
|
# everything is global, yet, when we start tracing something for stepping that
|
|
# needs to be per-thread.
|
|
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 monitor.DISABLE
|
|
|
|
# If we get another line event, remove the extra check for the line event
|
|
if hasattr(_thread_local_info, "f_disable_next_line_if_match"):
|
|
(co_filename, line_to_skip) = _thread_local_info.f_disable_next_line_if_match
|
|
del _thread_local_info.f_disable_next_line_if_match
|
|
if line_to_skip is line and co_filename == code.co_filename:
|
|
# The last jump already jumped to this line and we haven't had any
|
|
# line events or jumps since then. We don't want to consider this line twice
|
|
# pydev_log.debug('_line_event skipped', line)
|
|
return
|
|
|
|
if not thread_info.trace or not thread_info.is_thread_alive():
|
|
# 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, 1)
|
|
if func_code_info.always_skip_code or func_code_info.always_filtered_out:
|
|
return monitor.DISABLE
|
|
|
|
# pydev_log.debug('_line_event', code.co_name, line)
|
|
|
|
# We know the frame depth.
|
|
frame = _getframe(1)
|
|
return _internal_line_event(func_code_info, frame, line)
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _internal_line_event(FuncCodeInfo func_code_info, frame, int line):
|
|
cdef ThreadInfo thread_info
|
|
cdef PyDBAdditionalThreadInfo info
|
|
cdef int step_cmd
|
|
cdef bint stop
|
|
cdef bint stop_on_plugin_breakpoint
|
|
cdef int stop_reason
|
|
cdef bint force_check_project_scope
|
|
# ELSE
|
|
# def _internal_line_event(func_code_info, frame, line):
|
|
# ENDIF
|
|
# fmt: on
|
|
py_db: object = GlobalDebuggerHolder.global_dbg
|
|
thread_info = _thread_local_info.thread_info
|
|
info = thread_info.additional_info
|
|
|
|
step_cmd = info.pydev_step_cmd
|
|
|
|
# print('line event', info, id(info), thread_info.thread.name)
|
|
# print('line event', info.pydev_state, line, threading.current_thread(), code)
|
|
# If we reached here, it was not filtered out.
|
|
|
|
if func_code_info.breakpoint_found:
|
|
bp = None
|
|
stop = False
|
|
stop_on_plugin_breakpoint = False
|
|
|
|
stop_info = {}
|
|
stop_reason = CMD_SET_BREAK
|
|
bp_type = None
|
|
|
|
bp = func_code_info.bp_line_to_breakpoint.get(line)
|
|
if bp is not None:
|
|
new_frame = frame
|
|
stop = True
|
|
|
|
if bp:
|
|
if _stop_on_breakpoint(py_db, thread_info, stop_reason, bp, frame, new_frame, stop, stop_on_plugin_breakpoint, "python-line"):
|
|
return
|
|
|
|
if func_code_info.plugin_line_breakpoint_found:
|
|
result = py_db.plugin.get_breakpoint(py_db, frame, "line", info)
|
|
if result:
|
|
stop_reason = CMD_SET_BREAK
|
|
stop = False
|
|
stop_on_plugin_breakpoint = True
|
|
bp, new_frame, bp_type = result
|
|
_stop_on_breakpoint(py_db, thread_info, stop_reason, bp, frame, new_frame, stop, stop_on_plugin_breakpoint, bp_type)
|
|
return
|
|
|
|
if info.pydev_state == STATE_SUSPEND:
|
|
# Note: it's possible that it was suspended with a pause (and we'd stop here too).
|
|
# print('suspend (pause)...')
|
|
_do_wait_suspend(py_db, thread_info, frame, "line", None)
|
|
return
|
|
|
|
# Ok, did not suspend due to a breakpoint, let's see if we're stepping.
|
|
stop_frame = info.pydev_step_stop
|
|
if step_cmd == -1:
|
|
if func_code_info.breakpoint_found or func_code_info.plugin_line_breakpoint_found or any_thread_stepping():
|
|
return None
|
|
|
|
return monitor.DISABLE
|
|
|
|
if info.suspend_type != PYTHON_SUSPEND:
|
|
# Plugin stepping
|
|
if func_code_info.plugin_line_stepping:
|
|
_plugin_stepping(py_db, step_cmd, "line", frame, thread_info)
|
|
return
|
|
|
|
# Python stepping now
|
|
if step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_INTO_COROUTINE):
|
|
force_check_project_scope = step_cmd == CMD_STEP_INTO_MY_CODE
|
|
if not info.pydev_use_scoped_step_frame:
|
|
if func_code_info.always_filtered_out or (force_check_project_scope and func_code_info.filtered_out_force_checked):
|
|
return
|
|
|
|
py_db.set_suspend(thread_info.thread, step_cmd, original_step_cmd=info.pydev_original_step_cmd)
|
|
_do_wait_suspend(py_db, thread_info, frame, "line", None)
|
|
return
|
|
else:
|
|
# Make sure we check the filtering inside ipython calls too...
|
|
if func_code_info.always_filtered_out or (force_check_project_scope and func_code_info.filtered_out_force_checked):
|
|
return
|
|
|
|
stop = False
|
|
# 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")
|
|
py_db.set_suspend(thread_info.thread, step_cmd, original_step_cmd=info.pydev_original_step_cmd)
|
|
thread_info.additional_info.trace_suspend_type = "sys_monitor"
|
|
_do_wait_suspend(py_db, thread_info, frame, "line", None)
|
|
break
|
|
f = f.f_back
|
|
|
|
del f
|
|
|
|
# In scoped mode if step in didn't work in this context it won't work
|
|
# afterwards anyways.
|
|
return
|
|
|
|
elif step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE):
|
|
# 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).
|
|
if _is_same_frame(info, stop_frame, frame):
|
|
py_db.set_suspend(thread_info.thread, step_cmd, original_step_cmd=info.pydev_original_step_cmd)
|
|
_do_wait_suspend(py_db, thread_info, frame, "line", None)
|
|
return
|
|
|
|
elif step_cmd == CMD_SMART_STEP_INTO:
|
|
stop = False
|
|
back = frame.f_back
|
|
if _is_same_frame(info, stop_frame, back):
|
|
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
|
|
|
|
elif back is not None and _is_same_frame(info, stop_frame, back.f_back):
|
|
# 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
|
|
|
|
if stop:
|
|
py_db.set_suspend(thread_info.thread, step_cmd, original_step_cmd=info.pydev_original_step_cmd)
|
|
_do_wait_suspend(py_db, thread_info, frame, "line", None)
|
|
return
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _start_method_event(code, instruction_offset):
|
|
cdef ThreadInfo thread_info
|
|
cdef FuncCodeInfo func_code_info
|
|
cdef bint stop
|
|
cdef int stop_reason
|
|
cdef bint stop_on_plugin_breakpoint
|
|
cdef PyDBAdditionalThreadInfo info
|
|
cdef int step_cmd
|
|
cdef bint code_tracing_added
|
|
# ELSE
|
|
# def _start_method_event(code, instruction_offset):
|
|
# ENDIF
|
|
# fmt: on
|
|
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 monitor.DISABLE
|
|
|
|
if not thread_info.trace or not thread_info.is_thread_alive():
|
|
# For thread-related stuff we can't disable the code tracing because other
|
|
# threads may still want it...
|
|
return
|
|
|
|
frame = _getframe(1)
|
|
func_code_info = _get_func_code_info(code, frame)
|
|
if func_code_info.always_skip_code:
|
|
# if DEBUG:
|
|
# print('disable (always skip)')
|
|
return monitor.DISABLE
|
|
|
|
keep_enabled: bool = _enable_code_tracing(py_db, thread_info.additional_info, func_code_info, code, frame, True)
|
|
|
|
if func_code_info.function_breakpoint_found:
|
|
bp = func_code_info.function_breakpoint
|
|
stop = True
|
|
new_frame = frame
|
|
stop_reason = CMD_SET_FUNCTION_BREAK
|
|
stop_on_plugin_breakpoint = False
|
|
|
|
_stop_on_breakpoint(py_db, thread_info, stop_reason, bp, frame, new_frame, stop, stop_on_plugin_breakpoint, "python-function")
|
|
return
|
|
|
|
if py_db.plugin:
|
|
plugin_manager = py_db.plugin
|
|
|
|
# Check breaking on breakpoints in a 'call'
|
|
info = thread_info.additional_info
|
|
if func_code_info.plugin_call_breakpoint_found:
|
|
result = plugin_manager.get_breakpoint(py_db, frame, "call", info)
|
|
if result:
|
|
stop_reason = CMD_SET_BREAK
|
|
stop = False
|
|
stop_on_plugin_breakpoint = True
|
|
bp, new_frame, bp_type = result
|
|
_stop_on_breakpoint(py_db, thread_info, stop_reason, bp, frame, new_frame, stop, stop_on_plugin_breakpoint, bp_type)
|
|
return
|
|
|
|
keep_enabled = True
|
|
|
|
# Check breaking on line stepping in a 'call'
|
|
step_cmd = info.pydev_step_cmd
|
|
if step_cmd != -1 and func_code_info.plugin_call_stepping and info.suspend_type != PYTHON_SUSPEND:
|
|
_plugin_stepping(py_db, step_cmd, "call", frame, thread_info)
|
|
return
|
|
|
|
if keep_enabled or any_thread_stepping():
|
|
return None
|
|
|
|
return monitor.DISABLE
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cpdef _ensure_monitoring():
|
|
# ELSE
|
|
# def _ensure_monitoring():
|
|
# ENDIF
|
|
# fmt: on
|
|
DEBUGGER_ID = monitor.DEBUGGER_ID
|
|
if not monitor.get_tool(DEBUGGER_ID):
|
|
monitor.use_tool_id(DEBUGGER_ID, "pydevd")
|
|
update_monitor_events()
|
|
restart_events()
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cpdef start_monitoring(bint all_threads=False):
|
|
cdef ThreadInfo thread_info
|
|
# ELSE
|
|
# def start_monitoring(all_threads=False):
|
|
# ENDIF
|
|
# fmt: on
|
|
if all_threads:
|
|
# print('start monitoring, all_threads=', all_threads)
|
|
DEBUGGER_ID = monitor.DEBUGGER_ID
|
|
if not monitor.get_tool(DEBUGGER_ID):
|
|
monitor.use_tool_id(DEBUGGER_ID, "pydevd")
|
|
update_monitor_events()
|
|
restart_events()
|
|
else:
|
|
try:
|
|
thread_info = _thread_local_info.thread_info
|
|
except:
|
|
# code=None means we can already get the threading.current_thread.
|
|
thread_info = _get_thread_info(True, 1)
|
|
if thread_info is None:
|
|
# print('start monitoring, thread=', None)
|
|
return
|
|
# print('start monitoring, thread=', thread_info.thread)
|
|
thread_info.trace = True
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cpdef stop_monitoring(all_threads=False):
|
|
cdef ThreadInfo thread_info
|
|
# ELSE
|
|
# def stop_monitoring(all_threads=False):
|
|
# ENDIF
|
|
# fmt: on
|
|
if all_threads:
|
|
# print('stop monitoring, all_threads=', all_threads)
|
|
if monitor.get_tool(monitor.DEBUGGER_ID) == "pydevd":
|
|
monitor.set_events(monitor.DEBUGGER_ID, 0)
|
|
monitor.register_callback(DEBUGGER_ID, monitor.events.PY_START, None)
|
|
monitor.register_callback(DEBUGGER_ID, monitor.events.PY_RESUME, None)
|
|
monitor.register_callback(DEBUGGER_ID, monitor.events.LINE, None)
|
|
monitor.register_callback(DEBUGGER_ID, monitor.events.JUMP, None)
|
|
monitor.register_callback(DEBUGGER_ID, monitor.events.PY_RETURN, None)
|
|
monitor.register_callback(DEBUGGER_ID, monitor.events.RAISE, None)
|
|
monitor.free_tool_id(monitor.DEBUGGER_ID)
|
|
else:
|
|
try:
|
|
thread_info = _thread_local_info.thread_info
|
|
except:
|
|
thread_info = _get_thread_info(False, 1)
|
|
if thread_info is None:
|
|
return
|
|
# print('stop monitoring, thread=', thread_info.thread)
|
|
thread_info.trace = False
|
|
|
|
|
|
def update_monitor_events(suspend_requested: Optional[bool]=None) -> None:
|
|
"""
|
|
This should be called when breakpoints change.
|
|
|
|
:param suspend: means the user requested threads to be suspended
|
|
"""
|
|
if monitor.get_tool(monitor.DEBUGGER_ID) != "pydevd":
|
|
# It is still not initialized.
|
|
return
|
|
|
|
# When breakpoints change we need to update what we want to track based
|
|
# on the breakpoints.
|
|
py_db = GlobalDebuggerHolder.global_dbg
|
|
if py_db is None:
|
|
return
|
|
|
|
if suspend_requested is None:
|
|
suspend_requested = False
|
|
|
|
for t in threading.enumerate():
|
|
if getattr(t, "pydev_do_not_trace", False):
|
|
continue
|
|
try:
|
|
additional_info = t.additional_info
|
|
if additional_info is None:
|
|
# i.e.: if we don't have it then it makes no sense to check if it was suspended or is stepping
|
|
continue
|
|
except AttributeError:
|
|
continue
|
|
if additional_info.pydev_step_cmd != -1 or additional_info.pydev_state == 2:
|
|
suspend_requested = True
|
|
break
|
|
|
|
required_events = 0
|
|
|
|
has_caught_exception_breakpoint_in_pydb = (
|
|
py_db.break_on_caught_exceptions or py_db.break_on_user_uncaught_exceptions or py_db.has_plugin_exception_breaks
|
|
)
|
|
|
|
break_on_uncaught_exceptions = py_db.break_on_uncaught_exceptions
|
|
|
|
if has_caught_exception_breakpoint_in_pydb:
|
|
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:
|
|
if break_on_uncaught_exceptions:
|
|
required_events |= monitor.events.PY_UNWIND
|
|
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_breaks = py_db.has_plugin_line_breaks
|
|
if not has_breaks:
|
|
if py_db.function_breakpoint_name_to_breakpoint:
|
|
has_breaks = True
|
|
else:
|
|
file_to_line_to_breakpoints = py_db.breakpoints
|
|
for line_to_breakpoints in file_to_line_to_breakpoints.values():
|
|
if line_to_breakpoints:
|
|
has_breaks = True
|
|
break
|
|
|
|
if has_breaks or suspend_requested:
|
|
# print('track PY_START|PY_RESUME, suspend_requested=', suspend_requested)
|
|
required_events |= monitor.events.PY_START | monitor.events.PY_RESUME
|
|
|
|
monitor.register_callback(DEBUGGER_ID, monitor.events.PY_START, _start_method_event)
|
|
# monitor.register_callback(DEBUGGER_ID, monitor.events.PY_RESUME, _resume_method_event)
|
|
monitor.register_callback(DEBUGGER_ID, monitor.events.LINE, _line_event)
|
|
if not IS_PY313_OR_GREATER:
|
|
# In Python 3.13+ jump_events aren't necessary as we have a line_event for every
|
|
# jump location.
|
|
monitor.register_callback(DEBUGGER_ID, monitor.events.JUMP, _jump_event)
|
|
monitor.register_callback(DEBUGGER_ID, monitor.events.PY_RETURN, _return_event)
|
|
|
|
else:
|
|
monitor.register_callback(DEBUGGER_ID, monitor.events.PY_START, None)
|
|
monitor.register_callback(DEBUGGER_ID, monitor.events.PY_RESUME, None)
|
|
monitor.register_callback(DEBUGGER_ID, monitor.events.LINE, None)
|
|
monitor.register_callback(DEBUGGER_ID, monitor.events.JUMP, None)
|
|
monitor.register_callback(DEBUGGER_ID, monitor.events.PY_RETURN, None)
|
|
|
|
monitor.set_events(DEBUGGER_ID, required_events)
|
|
|
|
|
|
def restart_events() -> None:
|
|
# Note: if breakpoints change, update_monitor_events usually needs to be
|
|
# called first, then the line event tracing must be set for existing frames
|
|
# and then this function must be called at the end.
|
|
monitor.restart_events()
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
cdef _is_same_frame(PyDBAdditionalThreadInfo info, target_frame, current_frame):
|
|
# ELSE
|
|
# def _is_same_frame(info, target_frame, current_frame):
|
|
# ENDIF
|
|
# fmt: on
|
|
if target_frame is current_frame:
|
|
return True
|
|
|
|
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
|
|
|
|
|
|
# fmt: off
|
|
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
|
def _do_wait_suspend(py_db, ThreadInfo thread_info, frame, event, arg):
|
|
# ELSE
|
|
# def _do_wait_suspend(py_db, thread_info, frame, event, arg):
|
|
# ENDIF
|
|
# fmt: on
|
|
thread_info.additional_info.trace_suspend_type = "sys_monitor"
|
|
py_db.do_wait_suspend(thread_info.thread, frame, event, arg)
|
|
|
|
# This can be used to diagnose exceptions inside of the debugger itself.
|
|
#
|
|
# import types
|
|
# import functools
|
|
#
|
|
#
|
|
# def safe_func(method):
|
|
#
|
|
# @functools.wraps(method)
|
|
# def new_method(*args, **kwargs):
|
|
# try:
|
|
# return method(*args, **kwargs)
|
|
# except:
|
|
# import traceback;traceback.print_exc()
|
|
# raise
|
|
#
|
|
# return new_method
|
|
#
|
|
#
|
|
# for name, obj in list(globals().items()):
|
|
# if name.endswith('_event'):
|
|
# if isinstance(obj, types.FunctionType):
|
|
# globals()[name] = safe_func(obj)
|
|
#
|
|
#
|
|
# def _getframe(depth):
|
|
# return sys._getframe(depth + 1)
|