Files
openide/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py
Egor Eliseev dc428e32f4 PY-82241 Fix PythonDebuggerAggregatorEnvTest
Disable sys.monitoring from PyDB.stoptrace.
Disconnect DebuggerProcess if ServerSocket is closed.
Kill DebugProcessHandler if it is KillableProcessHandler.

Merge-request: IJ-MR-167442
Merged-by: Egor Eliseev <Egor.Eliseev@jetbrains.com>
(cherry picked from commit f1a42ad9014d9e172bc994085f53e2123f9870c2)

GitOrigin-RevId: 7a7335ed4f83764e18390490999c06db9a35feb3
2025-07-02 07:22:22 +00:00

957 lines
45 KiB
Python

import linecache
import os.path
import re
import sys
import traceback # @Reimport
# IFDEF CYTHON
# import dis
# ENDIF
from _pydev_bundle import pydev_log
from _pydevd_bundle import pydevd_dont_trace
from _pydevd_bundle import pydevd_vars
from _pydevd_bundle.pydevd_breakpoints import get_exception_breakpoint
from _pydevd_bundle.pydevd_comm_constants import (CMD_STEP_CAUGHT_EXCEPTION, CMD_STEP_RETURN, CMD_STEP_OVER, CMD_SET_BREAK,
CMD_STEP_INTO, CMD_SMART_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_INTO_COROUTINE)
from _pydevd_bundle.pydevd_constants import STATE_SUSPEND, get_current_thread_id, STATE_RUN, dict_iter_values, IS_PY3K, \
dict_keys, RETURN_VALUES_DICT, NO_FTRACE, IS_CPYTHON
from _pydevd_bundle.pydevd_dont_trace_files import DONT_TRACE, PYDEV_FILE
from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, just_raised, remove_exception_from_frame, ignore_exception_trace
from _pydevd_bundle.pydevd_bytecode_utils import find_last_call_name, find_last_func_call_order
from _pydevd_bundle.pydevd_utils import get_clsname_for_code, should_stop_on_failed_test, is_exception_in_test_unit_can_be_ignored, eval_expression
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame
try:
from inspect import CO_GENERATOR
except:
CO_GENERATOR = 0
from _pydevd_bundle.pydevd_constants import IS_PY2
try:
from _pydevd_bundle.pydevd_signature import send_signature_call_trace, send_signature_return_trace
except ImportError:
def send_signature_call_trace(*args, **kwargs):
pass
basename = os.path.basename
IGNORE_EXCEPTION_TAG = re.compile('[^#]*#.*@IgnoreException')
DEBUG_START = ('pydevd.py', '_exec')
DEBUG_START_PY3K = ('_pydev_execfile.py', 'execfile')
TRACE_PROPERTY = 'pydevd_traceproperty.py'
get_file_type = DONT_TRACE.get
def handle_breakpoint_condition(py_db, info, breakpoint, new_frame):
condition = breakpoint.condition
try:
if breakpoint.handle_hit_condition(new_frame):
return True
if condition is None:
return False
return eval_expression(condition, new_frame.f_globals, new_frame.f_locals)
except Exception as e:
if IS_PY2:
# Must be bytes on py2.
if isinstance(condition, unicode):
condition = condition.encode('utf-8')
if not isinstance(e, py_db.skip_print_breakpoint_exception):
sys.stderr.write('Error while evaluating expression: %s\n' % (condition,))
etype, value, tb = sys.exc_info()
traceback.print_exception(etype, value, tb.tb_next)
if not isinstance(e, py_db.skip_suspend_on_breakpoint_exception):
try:
# add exception_type and stacktrace into thread additional info
etype, value, tb = sys.exc_info()
error = ''.join(traceback.format_exception_only(etype, value))
stack = traceback.extract_stack(f=tb.tb_frame.f_back)
# On self.set_suspend(thread, CMD_SET_BREAK) this info will be
# sent to the client.
info.conditional_breakpoint_exception = \
('Condition:\n' + condition + '\n\nError:\n' + error, stack)
except:
traceback.print_exc()
return True
return False
finally:
etype, value, tb = None, None, None
def handle_breakpoint_expression(breakpoint, info, new_frame):
try:
try:
val = eval_expression(breakpoint.expression, new_frame.f_globals, new_frame.f_locals)
except:
val = sys.exc_info()[1]
finally:
if val is not None:
info.pydev_message = str(val)
# IFDEF CYTHON
# def should_stop_on_exception(tuple args, frame, str event, tuple arg):
# cdef PyDBAdditionalThreadInfo info;
# cdef bint flag;
# ELSE
def should_stop_on_exception(args, frame, event, arg):
# ENDIF
# main_debugger, _filename, info, _thread = args
main_debugger = args[0]
info = args[2]
should_stop = False
# STATE_SUSPEND = 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.
exception_breakpoint = get_exception_breakpoint(
exception, main_debugger.break_on_caught_exceptions)
if exception_breakpoint is not None:
if exception_breakpoint.condition is not None:
# Always add exception to frame (must remove later after we proceed).
add_exception_to_frame(frame, (exception, value, trace))
eval_result = handle_breakpoint_condition(main_debugger, info, exception_breakpoint, frame)
remove_exception_from_frame(frame)
if not eval_result:
return False, frame
if exception_breakpoint.ignore_libraries:
if not main_debugger.is_exception_trace_in_project_scope(trace):
return False, frame
if ignore_exception_trace(trace):
return False, frame
was_just_raised = just_raised(trace)
if was_just_raised:
if main_debugger.skip_on_exceptions_thrown_in_same_context:
# Option: Don't break if an exception is caught in the same function from which it is thrown
return False, frame
if exception_breakpoint.notify_on_first_raise_only:
if main_debugger.skip_on_exceptions_thrown_in_same_context:
# 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.
if not was_just_raised and not just_raised(trace.tb_next):
return False, frame # I.e.: we stop only when we're at the caller of a method that throws an exception
else:
if not was_just_raised and not main_debugger.is_top_level_trace_in_project_scope(trace):
return False, frame # I.e.: we stop only when it was just raised
# If it got here we should stop.
should_stop = True
try:
info.pydev_message = exception_breakpoint.qname
except:
info.pydev_message = exception_breakpoint.qname.encode('utf-8')
# Always add exception to frame (must remove later after we proceed).
add_exception_to_frame(frame, (exception, value, trace))
info.pydev_message = "python-%s" % info.pydev_message
else:
# No regular exception breakpoint, let's see if some plugin handles it or if it is a test assertion error.
try:
if main_debugger.plugin is not None:
result = main_debugger.plugin.exception_break(main_debugger, frame, args, arg)
if result:
should_stop, frame = result
if main_debugger.stop_on_failed_tests and main_debugger.is_test_item_or_set_up_caller(trace) \
and not is_exception_in_test_unit_can_be_ignored(exception):
should_stop, frame = should_stop_on_failed_test(arg), frame
info.pydev_message = "python-AssertionError"
except:
should_stop = False
if should_stop:
if exception_breakpoint is not None and exception_breakpoint.expression is not None:
handle_breakpoint_expression(exception_breakpoint, info, frame)
return should_stop, frame
# 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 = {}
filename_to_stat_info = {}
# IFDEF CYTHON
# def handle_exception(tuple args, frame, str event, tuple arg):
# ELSE
def handle_exception(args, frame, event, arg):
# ENDIF
try:
# We have 3 things in arg: exception type, description, traceback object
trace_obj = arg[2]
main_debugger = args[0]
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 main_debugger.ignore_exceptions_thrown_in_lines_with_ignore_exception \
and not main_debugger.stop_on_failed_tests:
for check_trace_obj in (initial_trace_obj, trace_obj):
filename = get_abs_path_real_path_and_base_from_frame(check_trace_obj.tb_frame)[1]
lines_ignored = filename_to_lines_where_exceptions_are_ignored.get(filename)
if lines_ignored is None:
lines_ignored = filename_to_lines_where_exceptions_are_ignored[filename] = {}
try:
curr_stat = os.stat(filename)
curr_stat = (curr_stat.st_size, curr_stat.st_mtime)
except:
curr_stat = None
last_stat = filename_to_stat_info.get(filename)
if last_stat != curr_stat:
filename_to_stat_info[filename] = curr_stat
lines_ignored.clear()
try:
linecache.checkcache(filename)
except:
# Jython 2.1
linecache.checkcache()
from_user_input = main_debugger.filename_to_lines_where_exceptions_are_ignored.get(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(filename, exc_lineno, check_trace_obj.tb_frame.f_globals)
except:
# Jython 2.1
line = linecache.getline(filename, exc_lineno)
if IGNORE_EXCEPTION_TAG.match(line) is not None:
lines_ignored[exc_lineno] = 1
return
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
thread = args[3]
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
thread_id = get_current_thread_id(thread)
if main_debugger.stop_on_failed_tests:
# Our goal is to find the deepest frame in stack that still belongs to the project and stop there.
f = trace_obj.tb_frame
while f:
abs_path, _, _ = get_abs_path_real_path_and_base_from_frame(f)
if main_debugger.in_project_scope(abs_path):
frame = f
break
f = f.f_back
f = None
trace_obj = initial_trace_obj
while trace_obj:
if trace_obj.tb_frame is frame:
break
trace_obj = trace_obj.tb_next
add_exception_to_frame(frame, (arg[0], arg[1], trace_obj))
pydevd_vars.add_additional_frame_by_id(thread_id, frame_id_to_frame)
try:
main_debugger.send_caught_exception_stack(thread, arg, id(frame))
main_debugger.set_suspend(thread, CMD_STEP_CAUGHT_EXCEPTION)
main_debugger.do_wait_suspend(thread, frame, event, arg)
main_debugger.send_caught_exception_stack_proceeded(thread)
finally:
pydevd_vars.remove_additional_frame_by_id(thread_id)
except KeyboardInterrupt as e:
raise e
except:
traceback.print_exc()
main_debugger.set_trace_for_frame_and_parents(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
main_debugger = None
thread = None
def manage_return_values(main_debugger, frame, event, arg):
def get_func_name(frame):
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:
traceback.print_exc()
return func_name
try:
if main_debugger.show_return_values:
if event == "return" and hasattr(frame, "f_code") and hasattr(frame.f_code, "co_name"):
if hasattr(frame, "f_back") and hasattr(frame.f_back, "f_locals"):
if RETURN_VALUES_DICT not in dict_keys(frame.f_back.f_locals):
frame.f_back.f_locals[RETURN_VALUES_DICT] = {}
name = get_func_name(frame)
frame.f_back.f_locals[RETURN_VALUES_DICT][name] = arg
if main_debugger.remove_return_values_flag:
# 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
if RETURN_VALUES_DICT in dict_keys(frame.f_locals):
frame.f_locals.pop(RETURN_VALUES_DICT)
if hasattr(frame, "f_back") and hasattr(frame.f_back, "f_locals"):
if RETURN_VALUES_DICT in dict_keys(frame.f_back.f_locals):
frame.f_back.f_locals.pop(RETURN_VALUES_DICT)
main_debugger.remove_return_values_flag = False
except:
main_debugger.remove_return_values_flag = False
traceback.print_exc()
#=======================================================================================================================
# PyDBFrame
#=======================================================================================================================
# IFDEF CYTHON
# 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
# Note: class (and not instance) attributes.
# IFDEF CYTHON
# cdef tuple _args
# cdef int should_skip
# cdef int _bytecode_offset
# cdef list _instructions
# 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._bytecode_offset = 0
# self._instructions = None
# ELSE
should_skip = -1 # Default value in class (put in instance on set).
def __init__(self, args):
# args = main_debugger, filename, base, info, t, frame
# yeap, much faster than putting in self and then getting it from self later on
self._args = args
self._bytecode_offset = 0
# 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
# def trace_exception(self, frame, str event, arg):
# cdef bint should_stop;
# ELSE
def trace_exception(self, frame, event, arg):
# ENDIF
if event == 'exception':
should_stop, frame = should_stop_on_exception(self._args, frame, event, arg)
if should_stop:
handle_exception(self._args, frame, event, arg)
return self.trace_dispatch
return self.trace_exception
def trace_return(self, frame, event, arg):
if event == 'return':
main_debugger, filename = self._args[0], self._args[1]
send_signature_return_trace(main_debugger, frame, filename, arg)
return self.trace_return
def get_func_name(self, frame):
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:
traceback.print_exc()
return func_name
def clear_run_state(self, info):
info.pydev_step_stop = None
info.pydev_step_cmd = -1
info.pydev_state = STATE_RUN
# IFDEF CYTHON
# cpdef trace_dispatch(self, frame, str event, arg):
# cdef str filename;
# cdef bint is_exception_event;
# cdef bint has_exception_breakpoints;
# cdef bint can_skip;
# 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 str curr_func_name;
# cdef bint exist_result;
# cdef dict frame_skips_cache;
# cdef tuple 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 need_trace_return;
# ELSE
def trace_dispatch(self, frame, event, arg):
# ENDIF
main_debugger, filename, info, thread, frame_skips_cache, frame_cache_key = self._args
# print('frame trace_dispatch %s %s %s %s %s' % (frame.f_lineno, frame.f_code.co_name, frame.f_code.co_filename, event, info.pydev_step_cmd))
# The thread can be already suspended by another function, e.g. built-in breakpoint hook.
if info.is_tracing:
return None
try:
info.is_tracing = True
line = frame.f_lineno
line_cache_key = (frame_cache_key, line)
if main_debugger.pydb_disposed:
if event != 'call': frame.f_trace = NO_FTRACE
return None
# IFDEF CYTHON
# if event == 'opcode':
# instructions = self._get_instructions(frame)
# for i, inst in enumerate(instructions):
# if inst.offset == frame.f_lasti:
# opname, arg, argval = inst.opname, inst.arg, str(inst.argval)
# print('frame trace_dispatch %s %s %s %s %s %s %s %s' % (frame.f_lineno, frame.f_lasti, frame.f_code.co_name,
# frame.f_code.co_filename, event, opname, arg, argval))
# try:
# self._bytecode_offset = instructions[i + 1].offset
# except IndexError:
# break
# return self.trace_dispatch
# ENDIF
plugin_manager = main_debugger.plugin
is_exception_event = event == 'exception'
has_exception_breakpoints = main_debugger.break_on_caught_exceptions or main_debugger.has_plugin_exception_breaks \
or main_debugger.stop_on_failed_tests
if is_exception_event:
if has_exception_breakpoints:
should_stop, frame = should_stop_on_exception(
self._args, frame, event, arg)
if should_stop:
handle_exception(self._args, frame, event, arg)
# No need to reset frame.f_trace to keep the same trace function.
return self.trace_dispatch
is_line = False
is_return = False
is_call = False
else:
is_line = event == 'line'
is_return = event == 'return'
is_call = event == 'call'
if not is_line and not is_return and not is_call:
# Unexpected: just keep the same trace func.
# No need to reset frame.f_trace to keep the same trace function.
return self.trace_dispatch
need_signature_trace_return = False
if main_debugger.signature_factory is not None:
if is_call:
need_signature_trace_return = send_signature_call_trace(main_debugger, frame, filename)
elif is_return:
send_signature_return_trace(main_debugger, frame, filename, arg)
stop_frame = info.pydev_step_stop
step_cmd = info.pydev_step_cmd
is_generator_or_coroutime = frame.f_code.co_flags & 0xa0 # 0xa0 == CO_GENERATOR = 0x20 | CO_COROUTINE = 0x80
breakpoints_for_file = main_debugger.breakpoints.get(filename)
if not is_exception_event:
if is_generator_or_coroutime:
if is_return:
# 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.
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 main_debugger.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
elif is_call:
# Don't stop when calling coroutines, we will on other event anyway if necessary.
return self.trace_dispatch
can_skip = False
if info.pydev_state == 1: # STATE_RUN = 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
# CMD_STEP_RETURN = 109, CMD_STEP_OVER = 108
can_skip = (step_cmd == -1 and stop_frame is None) \
or (step_cmd in (109, 108) and stop_frame is not frame)
if can_skip:
if plugin_manager is not None and main_debugger.has_plugin_line_breaks:
can_skip = not plugin_manager.can_not_skip(main_debugger, frame, info)
# CMD_STEP_OVER = 108
if can_skip and main_debugger.show_return_values and info.pydev_step_cmd == 108 and frame.f_back is info.pydev_step_stop:
# 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 not breakpoints_for_file:
if can_skip:
if has_exception_breakpoints:
frame.f_trace = self.trace_exception
return self.trace_exception
else:
if need_signature_trace_return:
frame.f_trace = self.trace_return
return self.trace_return
else:
if not is_call: frame.f_trace = NO_FTRACE
return None
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:
# No need to reset frame.f_trace to keep the same trace function.
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
# 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 breakpoint in dict_iter_values(breakpoints_for_file): # jython does not support itervalues()
# will match either global or some function
if breakpoint.func_name in ('None', curr_func_name):
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:
frame.f_trace = self.trace_exception
return self.trace_exception
else:
if need_signature_trace_return:
frame.f_trace = self.trace_return
return self.trace_return
else:
if not is_call: frame.f_trace = NO_FTRACE
return None
# 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
# print('NOT skipped: %s %s %s %s' % (frame.f_lineno, frame.f_code.co_name, event, frame.__class__.__name__))
try:
flag = 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
exist_result = False
stop = False
bp_type = None
smart_stop_frame = info.pydev_smart_step_context.smart_step_stop
context_start_line = info.pydev_smart_step_context.start_line
context_end_line = info.pydev_smart_step_context.end_line
is_within_context = context_start_line <= line <= context_end_line
if not is_return and info.pydev_state != STATE_SUSPEND and breakpoints_for_file is not None and line in breakpoints_for_file and not is_exception_event:
breakpoint = breakpoints_for_file[line]
new_frame = frame
stop = True
if step_cmd == CMD_STEP_OVER:
if stop_frame is frame and (is_line or is_return):
stop = False # we don't stop on breakpoint if we have to stop by step-over (it will be processed later)
elif is_generator_or_coroutime and frame.f_back and frame.f_back is stop_frame:
stop = False # we don't stop on breakpoint if stepping is active and we enter a `genexpr` or coroutine context
elif step_cmd == CMD_SMART_STEP_INTO and (frame.f_back is smart_stop_frame and is_within_context):
stop = False
elif plugin_manager is not None and main_debugger.has_plugin_line_breaks:
result = plugin_manager.get_breakpoint(main_debugger, frame, event, self._args)
if result:
exist_result = True
flag, 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 stop or exist_result:
eval_result = False
if breakpoint.has_condition:
eval_result = handle_breakpoint_condition(main_debugger, info, breakpoint, new_frame)
if breakpoint.expression is not None:
handle_breakpoint_expression(breakpoint, info, new_frame)
if breakpoint.is_logpoint and info.pydev_message is not None and len(info.pydev_message) > 0:
cmd = main_debugger.cmd_factory.make_io_message(info.pydev_message + os.linesep, '1')
main_debugger.writer.add_command(cmd)
if breakpoint.has_condition and not eval_result:
# No need to reset frame.f_trace to keep the same trace function.
return self.trace_dispatch
if is_call and frame.f_code.co_name in ('<module>', '<lambda>'):
# 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).
#
# As 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.
# No need to reset frame.f_trace to keep the same trace function.
return self.trace_dispatch
else:
# if the frame is traced after breakpoint stop,
# but the file should be ignored while stepping because of filters
if step_cmd != -1:
if main_debugger.is_filter_enabled and main_debugger.is_ignored_by_filters(filename):
# ignore files matching stepping filters
# No need to reset frame.f_trace to keep the same trace function.
return self.trace_dispatch
if main_debugger.is_filter_libraries and not main_debugger.in_project_scope(filename):
# ignore library files while stepping
# No need to reset frame.f_trace to keep the same trace function.
return self.trace_dispatch
if main_debugger.show_return_values or main_debugger.remove_return_values_flag:
manage_return_values(main_debugger, frame, event, arg)
if stop:
self.set_suspend(
thread,
CMD_SET_BREAK,
suspend_other_threads=breakpoint and breakpoint.suspend_policy == "ALL",
)
elif flag and plugin_manager is not None:
result = plugin_manager.suspend(main_debugger, thread, frame, bp_type)
if result:
frame = result
# if thread has a suspend flag, we suspend with a busy wait
if info.pydev_state == STATE_SUSPEND:
self.do_wait_suspend(thread, frame, event, arg)
# No need to reset frame.f_trace to keep the same trace function.
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 KeyboardInterrupt:
self.clear_run_state(info)
raise
except:
traceback.print_exc()
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, filename):
# -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 == CMD_SMART_STEP_INTO:
stop = False
if smart_stop_frame is frame:
if not is_within_context or not IS_CPYTHON:
# We don't stop on jumps in multiline statements, which the Python interpreter does in some cases,
# if we they happen in smart step into context.
info.pydev_func_name = '.invalid.' # Must match the type in cython
stop = True # act as if we did a step into
if is_line or is_exception_event:
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 smart_stop_frame and smart_stop_frame is frame.f_back:
if curr_func_name == info.pydev_func_name and not IS_CPYTHON:
# for implementations other than CPython we don't perform any additional checks
stop = True
else:
try:
if curr_func_name != info.pydev_func_name and frame.f_back:
# try to find function call name using bytecode analysis
curr_func_name = find_last_call_name(frame.f_back)
if curr_func_name == info.pydev_func_name:
stop = find_last_func_call_order(frame.f_back, context_start_line) \
== info.pydev_smart_step_context.call_order
except:
pydev_log.debug("Exception while handling smart step into in frame tracer, step into will be performed instead.")
info.pydev_smart_step_context.reset()
stop = True # act as if we did a step into
# we have to check this case for situations when a user has tried to step into a native function or method,
# e.g. `len()`, `list.append()`, etc and this was the only call in a return statement
if smart_stop_frame is frame and is_return:
stop = True
elif step_cmd == CMD_STEP_INTO:
stop = is_line or is_return
if plugin_manager is not None:
result = plugin_manager.cmd_step_into(main_debugger, frame, event, self._args, stop_info, stop)
if result:
stop, plugin_stop = result
elif step_cmd == CMD_STEP_INTO_MY_CODE:
if main_debugger.in_project_scope(frame.f_code.co_filename):
stop = is_line
elif step_cmd in (CMD_STEP_OVER, CMD_STEP_INTO_COROUTINE):
stop = stop_frame is frame
if stop:
if is_line:
# the only case we shouldn't stop on a line, is when we traversing though asynchronous framework machinery
if step_cmd == CMD_STEP_INTO_COROUTINE:
stop = main_debugger.in_project_scope(frame.f_code.co_filename)
elif is_return:
stop = frame.f_back and main_debugger.in_project_scope(frame.f_back.f_code.co_filename)
if not stop:
back = frame.f_back
if back:
info.pydev_step_stop = back
if main_debugger.in_project_scope(frame.f_code.co_filename):
# we are returning from the project scope, step over should always lead to the project scope
if is_generator_or_coroutime and step_cmd == CMD_STEP_OVER:
# setting ad hoc command to ensure we will skip line stops in an asynchronous framework
info.pydev_step_cmd = CMD_STEP_INTO_COROUTINE
else:
# we were already outside the project scope because of step into or breakpoint, it's ok to stop
# if we are not chopping a way through an asynchronous framework
stop = not step_cmd == CMD_STEP_INTO_COROUTINE
else:
# if there's no back frame, we just stop as soon as possible
info.pydev_step_cmd = CMD_STEP_INTO
info.pydev_step_stop = None
else:
stop = False
if CMD_STEP_OVER and plugin_manager is not None:
result = plugin_manager.cmd_step_over(main_debugger, frame, event, self._args, stop_info, stop)
if result:
stop, plugin_stop = result
elif step_cmd == CMD_STEP_RETURN:
stop = is_return and stop_frame is frame
else:
stop = False
if stop and step_cmd != -1 and is_return and IS_PY3K and hasattr(frame, "f_back"):
f_code = getattr(frame.f_back, 'f_code', None)
if f_code is not None:
back_filename = os.path.basename(f_code.co_filename)
file_type = get_file_type(back_filename)
if file_type == PYDEV_FILE:
stop = False
if plugin_stop:
stopped_on_plugin = plugin_manager.stop(main_debugger, frame, event, self._args, stop_info, arg, step_cmd)
elif stop:
if is_line:
self.set_suspend(thread, step_cmd)
self.do_wait_suspend(thread, frame, event, arg)
else: # 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_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!
if not is_call: frame.f_trace = NO_FTRACE
return None
elif pydevd_dont_trace.should_trace_hook is not None:
if not pydevd_dont_trace.should_trace_hook(back, back_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
main_debugger.set_trace_for_frame_and_parents(back)
if not is_call: frame.f_trace = NO_FTRACE
return None
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)
self.do_wait_suspend(thread, back, event, arg)
else:
# in jython we may not have a back frame
self.clear_run_state(info)
except KeyboardInterrupt:
self.clear_run_state(info)
raise
except:
try:
traceback.print_exc()
info.pydev_step_cmd = -1
except:
if not is_call: frame.f_trace = NO_FTRACE
return None
# if we are quitting, let's stop the tracing
if not main_debugger.quitting:
# No need to reset frame.f_trace to keep the same trace function.
return self.trace_dispatch
else:
if not is_call: frame.f_trace = NO_FTRACE
return None
finally:
info.is_tracing = False
# end trace_dispatch
# IFDEF CYTHON
# cdef _get_instructions(self, frame):
# if self._instructions is None:
# self._instructions = list(dis.get_instructions(frame.f_code))
# return self._instructions
# ENDIF