IDEA-CR-57291: PY-37771 Port new IDEA debugger smart step into UI/UX to PyCharm

(cherry picked from commit 4fefae6a1d9fbc6df174d53222ceba9208691b65)

GitOrigin-RevId: d6e65ace1378765be246fe09a86d2bff133855df
This commit is contained in:
Andrey Lisin
2020-02-21 17:31:28 +00:00
committed by intellij-monorepo-bot
parent 1031d64091
commit 5029797a6c
87 changed files with 9324 additions and 4831 deletions
@@ -7,7 +7,7 @@ from _pydev_bundle import pydev_log
from _pydevd_bundle.pydevd_frame import PyDBFrame
# ENDIF
version = 24
version = 25
if not hasattr(sys, '_current_frames'):
@@ -70,7 +70,6 @@ class PyDBAdditionalThreadInfo(object):
'pydev_step_stop',
'pydev_step_cmd',
'pydev_notify_kill',
'pydev_smart_step_stop',
'pydev_django_resolve_frame',
'pydev_call_from_jinja2',
'pydev_call_inside_jinja2',
@@ -82,6 +81,7 @@ class PyDBAdditionalThreadInfo(object):
'pydev_func_name',
'suspended_at_unhandled',
'trace_suspend_type',
'pydev_smart_step_context'
]
# ENDIF
@@ -90,7 +90,6 @@ class PyDBAdditionalThreadInfo(object):
self.pydev_step_stop = None
self.pydev_step_cmd = -1 # Something as CMD_STEP_INTO, CMD_STEP_OVER, etc.
self.pydev_notify_kill = False
self.pydev_smart_step_stop = None
self.pydev_django_resolve_frame = False
self.pydev_call_from_jinja2 = None
self.pydev_call_inside_jinja2 = None
@@ -102,6 +101,7 @@ class PyDBAdditionalThreadInfo(object):
self.pydev_func_name = '.invalid.' # Must match the type in cython
self.suspended_at_unhandled = False
self.trace_suspend_type = 'trace' # 'trace' or 'frame_eval'
self.pydev_smart_step_context = PydevSmartStepContext()
def get_topmost_frame(self, thread):
'''
@@ -118,6 +118,34 @@ class PyDBAdditionalThreadInfo(object):
self.pydev_state, self.pydev_step_stop, self.pydev_step_cmd, self.pydev_notify_kill)
# IFDEF CYTHON
# cdef class PydevSmartStepContext:
# ELSE
class PydevSmartStepContext:
# ENDIF
# Note: the params in cython are declared in pydevd_cython.pxd.
# IFDEF CYTHON
# ELSE
__slots__ = [
'smart_step_stop',
'call_order',
'filename',
'start_line',
'end_line',
]
# ENDIF
def __init__(self):
self.smart_step_stop = None
self.call_order = -1
self.filename = None
self.start_line = -1
self.end_line = -1
reset = __init__
from _pydev_imps._pydev_saved_modules import threading
_set_additional_thread_info_lock = threading.Lock()
@@ -0,0 +1,298 @@
"""Bytecode analysing utils. Originally added for using in smart step into."""
import dis
import inspect
from collections import namedtuple
from _pydevd_bundle.pydevd_constants import IS_PY3K, IS_CPYTHON
__all__ = ["get_smart_step_into_candidates"]
_LOAD_OPNAMES = {
'LOAD_BUILD_CLASS',
'LOAD_CONST',
'LOAD_NAME',
'LOAD_ATTR',
'LOAD_GLOBAL',
'LOAD_FAST',
'LOAD_CLOSURE',
'LOAD_DEREF',
}
_CALL_OPNAMES = {
'CALL_FUNCTION',
'CALL_FUNCTION_KW',
}
if IS_PY3K:
for opname in ('LOAD_CLASSDEREF', 'LOAD_METHOD'):
_LOAD_OPNAMES.add(opname)
for opname in ('CALL_FUNCTION_EX', 'CALL_METHOD'):
_CALL_OPNAMES.add(opname)
else:
_LOAD_OPNAMES.add('LOAD_LOCALS')
for opname in ('CALL_FUNCTION_VAR', 'CALL_FUNCTION_VAR_KW'):
_CALL_OPNAMES.add(opname)
_BINARY_OPS = set([opname for opname in dis.opname if opname.startswith('BINARY_')])
_BINARY_OP_MAP = {
'BINARY_POWER': '__pow__',
'BINARY_MULTIPLY': '__mul__',
'BINARY_MATRIX_MULTIPLY': '__matmul__',
'BINARY_FLOOR_DIVIDE': '__floordiv__',
'BINARY_TRUE_DIVIDE': '__div__',
'BINARY_MODULO': '__mod__',
'BINARY_ADD': '__add__',
'BINARY_SUBTRACT': '__sub__',
'BINARY_LSHIFT': '__lshift__',
'BINARY_RSHIFT': '__rshift__',
'BINARY_AND': '__and__',
'BINARY_OR': '__or__',
'BINARY_XOR': '__xor__',
'BINARY_SUBSCR': '__getitem__',
}
if not IS_PY3K:
_BINARY_OP_MAP['BINARY_DIVIDE'] = '__div__'
_UNARY_OPS = set([opname for opname in dis.opname if opname.startswith('UNARY_') and opname != 'UNARY_NOT'])
_UNARY_OP_MAP = {
'UNARY_POSITIVE': '__pos__',
'UNARY_NEGATIVE': '__neg__',
'UNARY_INVERT': '__invert__',
}
_MAKE_OPS = set([opname for opname in dis.opname if opname.startswith('MAKE_')])
_COMP_OP_MAP = {
'<': '__lt__',
'<=': '__le__',
'==': '__eq__',
'!=': '__ne__',
'>': '__gt__',
'>=': '__ge__',
'in': '__contains__',
'not in': '__contains__',
}
def _is_load_opname(opname):
return opname in _LOAD_OPNAMES
def _is_call_opname(opname):
return opname in _CALL_OPNAMES
def _is_binary_opname(opname):
return opname in _BINARY_OPS
def _is_unary_opname(opname):
return opname in _UNARY_OPS
def _is_make_opname(opname):
return opname in _MAKE_OPS
# Similar to :py:class:`dis._Instruction` but without fields we don't use. Also :py:class:`dis._Instruction`
# is not available in Python 2.
Instruction = namedtuple("Instruction", ["opname", "opcode", "arg", "argval", "lineno", "offset"])
if IS_PY3K:
_unpack_opargs = dis._unpack_opargs
long = int
else:
def _unpack_opargs(code):
n = len(code)
i = 0
extended_arg = 0
while i < n:
c = code[i]
op = ord(c)
offset = i
arg = None
i += 1
if op >= dis.HAVE_ARGUMENT:
arg = ord(code[i]) + ord(code[i + 1]) * 256 + extended_arg
extended_arg = 0
i += 2
if op == dis.EXTENDED_ARG:
extended_arg = arg * long(65536)
yield (offset, op, arg)
def _code_to_name(inst):
"""If thw instruction's ``argval`` is :py:class:`types.CodeType`, replace it with the name and return the updated instruction.
:type inst: :py:class:`Instruction`
:rtype: :py:class:`Instruction`
"""
if inspect.iscode(inst.argval):
return inst._replace(argval=inst.argval.co_name)
return inst
def get_smart_step_into_candidates(code):
"""Iterate through the bytecode and return a list of instructions which can be smart step into candidates.
:param code: A code object where we searching for calls.
:type code: :py:class:`types.CodeType`
:return: list of :py:class:`~Instruction` that represents the objects that were called
by one of the Python call instructions.
:raise: :py:class:`RuntimeError` if failed to parse the bytecode.
"""
if not IS_CPYTHON:
# For implementations other than CPython we fall back to simple step into.
return []
linestarts = dict(dis.findlinestarts(code))
varnames = code.co_varnames
names = code.co_names
constants = code.co_consts
lineno = None
stk = [] # only the instructions related to calls are pushed in the stack
result = []
for offset, op, arg in _unpack_opargs(code.co_code):
try:
if linestarts is not None:
lineno = linestarts.get(offset, None) or lineno
opname = dis.opname[op]
argval = None
if arg is None:
if _is_binary_opname(opname):
stk.pop()
result.append(Instruction(opname, op, arg, _BINARY_OP_MAP[opname], lineno, offset))
elif _is_unary_opname(opname):
result.append(Instruction(opname, op, arg, _UNARY_OP_MAP[opname], lineno, offset))
if opname == 'COMPARE_OP':
stk.pop()
result.append(Instruction(opname, op, arg, _COMP_OP_MAP[dis.cmp_op[arg]], lineno, offset))
if _is_load_opname(opname):
if opname == 'LOAD_CONST':
argval = constants[arg]
elif opname == 'LOAD_NAME' or opname == 'LOAD_GLOBAL':
argval = names[arg]
elif opname == 'LOAD_ATTR':
stk.pop()
argval = names[arg]
elif opname == 'LOAD_FAST':
argval = varnames[arg]
elif IS_PY3K and opname == 'LOAD_METHOD':
stk.pop()
argval = names[arg]
stk.append(Instruction(opname, op, arg, argval, lineno, offset))
elif _is_make_opname(opname):
tos = stk.pop() # qualified name of the function or function code in Python 2
argc = 0
if IS_PY3K:
stk.pop() # function code
for flag in (0x01, 0x02, 0x04, 0x08):
if arg & flag:
argc += 1 # each flag means one extra element to pop
else:
argc = arg
tos = _code_to_name(tos)
while argc > 0:
stk.pop()
argc -= 1
stk.append(tos)
elif _is_call_opname(opname):
argc = arg # the number of the function or method arguments
if opname == 'CALL_FUNCTION_KW' or not IS_PY3K and opname == 'CALL_FUNCTION_VAR':
stk.pop() # pop the mapping or iterable with arguments or parameters
elif not IS_PY3K and opname == 'CALL_FUNCTION_VAR_KW':
stk.pop() # pop the mapping with arguments
stk.pop() # pop the iterable with parameters
elif not IS_PY3K and opname == 'CALL_FUNCTION':
argc = arg & 0xff # positional args
argc += ((arg >> 8) * 2) # keyword args
while argc > 0:
stk.pop() # popping args from the stack
argc -= 1
tos = _code_to_name(stk[-1])
if tos.opname == 'LOAD_BUILD_CLASS':
# an internal `CALL_FUNCTION` for building a class
continue
result.append(tos._replace(offset=offset)) # the actual offset is not when a function was loaded but when it was called
except:
err_msg = "Bytecode parsing error at: offset(%d), opname(%s), arg(%d)" % (offset, dis.opname[op], arg)
raise RuntimeError(err_msg)
return result
Variant = namedtuple('Variant', ['name', 'is_visited'])
def calculate_smart_step_into_variants(frame, start_line, end_line):
"""
Calculate smart step into variants for the given line range.
:param frame:
:type frame: :py:class:`types.FrameType`
:param start_line:
:param end_line:
:return: A list of call names from the first to the last.
:raise: :py:class:`RuntimeError` if failed to parse the bytecode.
"""
variants = []
is_context_reached = False
code = frame.f_code
lasti = frame.f_lasti
for inst in get_smart_step_into_candidates(code):
if inst.lineno and inst.lineno > end_line:
break
if not is_context_reached and inst.lineno is not None and inst.lineno >= start_line:
is_context_reached = True
if not is_context_reached:
continue
variants.append(Variant(inst.argval, inst.offset <= lasti))
return variants
def find_last_func_call_order(frame, start_line):
"""Find the call order of the last function call between ``start_line`` and last executed instruction.
:param frame: A frame inside which we are looking the function call.
:type frame: :py:class:`types.FrameType`
:param start_line:
:return: call order or -1 if we fail to find the call order for some
reason.
:rtype: int
:raise: :py:class:`RuntimeError` if failed to parse the bytecode.
"""
code = frame.f_code
lasti = frame.f_lasti
cache = {}
call_order = -1
for inst in get_smart_step_into_candidates(code):
if inst.offset > lasti:
break
if inst.lineno >= start_line:
name = inst.argval
call_order = cache.setdefault(name, -1)
call_order += 1
cache[name] = call_order
return call_order
def find_last_call_name(frame):
"""Find the name of the last call made in the frame.
:param frame: A frame inside which we are looking the last call.
:type frame: :py:class:`types.FrameType`
:return: The name of a function or method that has been called last.
:rtype: str
:raise: :py:class:`RuntimeError` if failed to parse the bytecode.
"""
last_call_name = None
code = frame.f_code
lasti = frame.f_lasti
for inst in get_smart_step_into_candidates(code):
if inst.offset > lasti:
break
last_call_name = inst.argval
return last_call_name
@@ -93,10 +93,12 @@ from _pydevd_bundle import pydevd_vars
import pydevd_tracing
from _pydevd_bundle import pydevd_xml
from _pydevd_bundle import pydevd_vm_type
from _pydevd_bundle import pydevd_bytecode_utils
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame, norm_file_to_client, is_real_file
import pydevd_file_utils
import os
import sys
import inspect
import traceback
from _pydevd_bundle.pydevd_utils import quote_smart as quote, compare_object_attrs_key, to_string, \
get_non_pydevd_threads
@@ -139,7 +141,7 @@ from _pydevd_bundle.pydevd_comm_constants import (
CMD_STOP_ON_START, CMD_GET_EXCEPTION_DETAILS, CMD_PROCESS_CREATED_MSG_RECEIVED, CMD_PYDEVD_JSON_CONFIG,
CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION, CMD_THREAD_RESUME_SINGLE_NOTIFICATION,
CMD_REDIRECT_OUTPUT, CMD_GET_NEXT_STATEMENT_TARGETS, CMD_SET_PROJECT_ROOTS, CMD_VERSION,
CMD_RETURN, CMD_SET_PROTOCOL, CMD_ERROR,)
CMD_RETURN, CMD_SET_PROTOCOL, CMD_ERROR, CMD_GET_SMART_STEP_INTO_VARIANTS,)
MAX_IO_MSG_SIZE = 1000 #if the io is too big, we'll not send all (could make the debugger too non-responsive)
#this number can be changed if there's need to do so
@@ -1175,6 +1177,36 @@ class InternalSetNextStatementThread(InternalThreadCommand):
t.additional_info.pydev_message = str(self.seq)
class InternalSmartStepInto(InternalThreadCommand):
def __init__(self, thread_id, frame_id, cmd_id, func_name, line, call_order, start_line, end_line, seq=0):
self.thread_id = thread_id
self.cmd_id = cmd_id
self.line = line
self.start_line = start_line
self.end_line = end_line
self.seq = seq
self.call_order = call_order
if IS_PY2:
if isinstance(func_name, unicode):
# On cython with python 2.X it requires an str, not unicode (but on python 3.3 it should be a str, not bytes).
func_name = func_name.encode('utf-8')
self.func_name = func_name
def do_it(self, dbg):
t = pydevd_find_thread_by_id(self.thread_id)
if t:
t.additional_info.pydev_step_cmd = self.cmd_id
t.additional_info.pydev_next_line = int(self.line)
t.additional_info.pydev_func_name = self.func_name
t.additional_info.pydev_state = STATE_RUN
t.additional_info.pydev_message = str(self.seq)
t.additional_info.pydev_smart_step_context.call_order = int(self.call_order)
t.additional_info.pydev_smart_step_context.start_line = int(self.start_line)
t.additional_info.pydev_smart_step_context.end_line = int(self.end_line)
#=======================================================================================================================
# InternalGetVariable
#=======================================================================================================================
@@ -1303,6 +1335,43 @@ class InternalGetFrame(InternalThreadCommand):
cmd = dbg.cmd_factory.make_error_message(self.sequence, "Error resolving frame: %s from thread: %s" % (self.frame_id, self.thread_id))
dbg.writer.add_command(cmd)
class InternalGetSmartStepIntoVariants(InternalThreadCommand):
def __init__(self, seq, thread_id, frame_id, start_line, end_line):
self.sequence = seq
self.thread_id = thread_id
self.frame_id = frame_id
self.start_line = int(start_line)
self.end_line = int(end_line)
def do_it(self, dbg):
try:
frame = pydevd_vars.find_frame(self.thread_id, self.frame_id)
variants = pydevd_bytecode_utils.calculate_smart_step_into_variants(frame, self.start_line, self.end_line)
xml = "<xml>"
for name, is_visited in variants:
xml += '<variant name="%s" isVisited="%s"></variant>' % (quote(name), str(is_visited).lower())
xml += "</xml>"
cmd = NetCommand(CMD_GET_SMART_STEP_INTO_VARIANTS, self.sequence, xml)
dbg.writer.add_command(cmd)
except:
traceback.print_exc()
cmd = dbg.cmd_factory.make_error_message(self.sequence, "Error getting smart step into veriants for frame: %s from thread: %s"
% (self.frame_id, self.thread_id))
self._reset_smart_step_context()
dbg.writer.add_command(cmd)
def _reset_smart_step_context(self):
t = pydevd_find_thread_by_id(self.thread_id)
if t:
try:
t.additional_info.pydev_smart_step_context.reset()
except:
pydevd_log(1, "Error while resetting smart step into context for thread %s" % self.thread_id)
#=======================================================================================================================
# InternalGetNextStatementTargets
#=======================================================================================================================
@@ -77,6 +77,8 @@ CMD_THREAD_RESUME_SINGLE_NOTIFICATION = 158
CMD_PROCESS_CREATED_MSG_RECEIVED = 159
CMD_GET_SMART_STEP_INTO_VARIANTS = 160
CMD_REDIRECT_OUTPUT = 200
CMD_GET_NEXT_STATEMENT_TARGETS = 201
CMD_SET_PROJECT_ROOTS = 202
@@ -154,6 +156,8 @@ ID_TO_MEANING = {
'158': 'CMD_THREAD_RESUME_SINGLE_NOTIFICATION',
'159': 'CMD_PROCESS_CREATED_MSG_RECEIVED',
'160': 'CMD_GET_SMART_STEP_INTO_VARIANTS',
'200': 'CMD_REDIRECT_OUTPUT',
'201': 'CMD_GET_NEXT_STATEMENT_TARGETS',
'202': 'CMD_SET_PROJECT_ROOTS',
File diff suppressed because it is too large Load Diff
@@ -3,7 +3,6 @@ cdef class PyDBAdditionalThreadInfo:
cdef public object pydev_step_stop; # Actually, it's a frame or None
cdef public int pydev_step_cmd;
cdef public bint pydev_notify_kill;
cdef public object pydev_smart_step_stop; # Actually, it's a frame or None
cdef public bint pydev_django_resolve_frame;
cdef public object pydev_call_from_jinja2;
cdef public object pydev_call_inside_jinja2;
@@ -15,3 +14,13 @@ cdef class PyDBAdditionalThreadInfo:
cdef public str pydev_func_name;
cdef public bint suspended_at_unhandled;
cdef public str trace_suspend_type;
cdef public PydevSmartStepContext pydev_smart_step_context;
cdef class PydevSmartStepContext:
cdef public object smart_step_stop; # Actually, it's a frame or None
cdef public int call_order;
cdef public str filename;
cdef public int line;
cdef public int start_line;
cdef public int end_line;
@@ -13,7 +13,7 @@ pydev_log.debug("Using Cython speedups")
# from _pydevd_bundle.pydevd_frame import PyDBFrame
# ENDIF
version = 24
version = 25
if not hasattr(sys, '_current_frames'):
@@ -76,7 +76,6 @@ cdef class PyDBAdditionalThreadInfo:
# 'pydev_step_stop',
# 'pydev_step_cmd',
# 'pydev_notify_kill',
# 'pydev_smart_step_stop',
# 'pydev_django_resolve_frame',
# 'pydev_call_from_jinja2',
# 'pydev_call_inside_jinja2',
@@ -88,6 +87,7 @@ cdef class PyDBAdditionalThreadInfo:
# 'pydev_func_name',
# 'suspended_at_unhandled',
# 'trace_suspend_type',
# 'pydev_smart_step_context'
# ]
# ENDIF
@@ -96,7 +96,6 @@ cdef class PyDBAdditionalThreadInfo:
self.pydev_step_stop = None
self.pydev_step_cmd = -1 # Something as CMD_STEP_INTO, CMD_STEP_OVER, etc.
self.pydev_notify_kill = False
self.pydev_smart_step_stop = None
self.pydev_django_resolve_frame = False
self.pydev_call_from_jinja2 = None
self.pydev_call_inside_jinja2 = None
@@ -108,6 +107,7 @@ cdef class PyDBAdditionalThreadInfo:
self.pydev_func_name = '.invalid.' # Must match the type in cython
self.suspended_at_unhandled = False
self.trace_suspend_type = 'trace' # 'trace' or 'frame_eval'
self.pydev_smart_step_context = PydevSmartStepContext()
def get_topmost_frame(self, thread):
'''
@@ -124,6 +124,34 @@ cdef class PyDBAdditionalThreadInfo:
self.pydev_state, self.pydev_step_stop, self.pydev_step_cmd, self.pydev_notify_kill)
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
cdef class PydevSmartStepContext:
# ELSE
# class PydevSmartStepContext:
# ENDIF
# Note: the params in cython are declared in pydevd_cython.pxd.
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
# ELSE
# __slots__ = [
# 'smart_step_stop',
# 'call_order',
# 'filename',
# 'start_line',
# 'end_line',
# ]
# ENDIF
def __init__(self):
self.smart_step_stop = None
self.call_order = -1
self.filename = None
self.start_line = -1
self.end_line = -1
reset = __init__
from _pydev_imps._pydev_saved_modules import threading
_set_additional_thread_info_lock = threading.Lock()
@@ -160,9 +188,10 @@ 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_RUN_TO_LINE, CMD_SET_NEXT_STATEMENT, CMD_STEP_INTO_MY_CODE)
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
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
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame, is_real_file
@@ -223,7 +252,7 @@ def handle_breakpoint_condition(py_db, info, breakpoint, new_frame):
except:
traceback.print_exc()
return True
return False
finally:
@@ -771,12 +800,19 @@ cdef class PyDBFrame:
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:
breakpoint = breakpoints_for_file[line]
new_frame = frame
stop = True
if step_cmd == CMD_STEP_OVER and 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 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, self, frame, event, self._args)
if result:
@@ -831,11 +867,11 @@ cdef class PyDBFrame:
if stop:
self.set_suspend(
thread,
CMD_SET_BREAK,
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:
@@ -860,6 +896,7 @@ cdef class PyDBFrame:
# 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).
@@ -877,6 +914,44 @@ cdef class PyDBFrame:
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:
@@ -900,25 +975,8 @@ cdef class PyDBFrame:
if result:
stop, plugin_stop = result
elif step_cmd == CMD_SMART_STEP_INTO:
stop = False
if info.pydev_smart_step_stop is frame:
info.pydev_func_name = '.invalid.' # Must match the type in cython
info.pydev_smart_step_stop = None
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 curr_func_name == info.pydev_func_name:
stop = True
elif step_cmd == CMD_STEP_RETURN:
stop = is_return and stop_frame is frame
else:
stop = False
@@ -72,6 +72,7 @@ DONT_TRACE = {
'pydevd_additional_thread_info_regular.py': PYDEV_FILE,
'pydevd_breakpointhook.py': PYDEV_FILE,
'pydevd_breakpoints.py': PYDEV_FILE,
'pydevd_bytecode_utils.py': PYDEV_FILE,
'pydevd_collect_try_except_info.py': PYDEV_FILE,
'pydevd_comm.py': PYDEV_FILE,
'pydevd_comm_constants.py': PYDEV_FILE,
@@ -15,9 +15,10 @@ 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_RUN_TO_LINE, CMD_SET_NEXT_STATEMENT, CMD_STEP_INTO_MY_CODE)
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
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
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame, is_real_file
@@ -78,7 +79,7 @@ def handle_breakpoint_condition(py_db, info, breakpoint, new_frame):
except:
traceback.print_exc()
return True
return False
finally:
@@ -626,12 +627,19 @@ class PyDBFrame:
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:
breakpoint = breakpoints_for_file[line]
new_frame = frame
stop = True
if step_cmd == CMD_STEP_OVER and 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 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, self, frame, event, self._args)
if result:
@@ -686,11 +694,11 @@ class PyDBFrame:
if stop:
self.set_suspend(
thread,
CMD_SET_BREAK,
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:
@@ -715,6 +723,7 @@ class PyDBFrame:
# 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).
@@ -732,6 +741,44 @@ class PyDBFrame:
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:
@@ -755,25 +802,8 @@ class PyDBFrame:
if result:
stop, plugin_stop = result
elif step_cmd == CMD_SMART_STEP_INTO:
stop = False
if info.pydev_smart_step_stop is frame:
info.pydev_func_name = '.invalid.' # Must match the type in cython
info.pydev_smart_step_stop = None
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 curr_func_name == info.pydev_func_name:
stop = True
elif step_cmd == CMD_STEP_RETURN:
stop = is_return and stop_frame is frame
else:
stop = False
@@ -1,5 +1,3 @@
from _pydevd_bundle.pydevd_constants import IS_PY3K
class Frame(object):
def __init__(
self,
@@ -64,5 +62,3 @@ def cached_call(obj, func, *args):
setattr(obj, cached_name, func(*args))
return getattr(obj, cached_name)
@@ -20,9 +20,9 @@ from _pydevd_bundle.pydevd_comm import (CMD_RUN, CMD_VERSION, CMD_LIST_THREADS,
CMD_RUN_CUSTOM_OPERATION, InternalRunCustomOperation, CMD_IGNORE_THROWN_EXCEPTION_AT, CMD_ENABLE_DONT_TRACE, \
CMD_SHOW_RETURN_VALUES, ID_TO_MEANING, CMD_GET_DESCRIPTION, InternalGetDescription, InternalLoadFullValue, \
CMD_LOAD_FULL_VALUE, CMD_PROCESS_CREATED_MSG_RECEIVED, CMD_REDIRECT_OUTPUT, CMD_GET_NEXT_STATEMENT_TARGETS,
InternalGetNextStatementTargets, CMD_SET_PROJECT_ROOTS, \
InternalGetNextStatementTargets, CMD_SET_PROJECT_ROOTS, CMD_GET_SMART_STEP_INTO_VARIANTS, \
CMD_GET_THREAD_STACK, CMD_THREAD_DUMP_TO_STDERR, CMD_STOP_ON_START, CMD_GET_EXCEPTION_DETAILS, NetCommand, \
CMD_SET_PROTOCOL, CMD_PYDEVD_JSON_CONFIG, InternalGetThreadStack)
CMD_SET_PROTOCOL, CMD_PYDEVD_JSON_CONFIG, InternalGetThreadStack, InternalSmartStepInto, InternalGetSmartStepIntoVariants,)
from _pydevd_bundle.pydevd_constants import (get_thread_id, IS_PY3K, DebugInfoHolder, dict_keys, STATE_RUN, \
NEXT_VALUE_SEPARATOR, IS_WINDOWS, get_current_thread_id)
from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info
@@ -173,21 +173,26 @@ def process_net_command(py_db, cmd_id, seq, text):
elif text.startswith('__frame__:'):
sys.stderr.write("Can't make tasklet step command: %s\n" % (text,))
elif cmd_id == CMD_RUN_TO_LINE or cmd_id == CMD_SET_NEXT_STATEMENT or cmd_id == CMD_SMART_STEP_INTO:
# we received some command to make a single step
thread_id, line, func_name = text.split('\t', 2)
elif cmd_id in (CMD_RUN_TO_LINE, CMD_SET_NEXT_STATEMENT, CMD_SMART_STEP_INTO):
if cmd_id == CMD_SMART_STEP_INTO:
# we received a smart step into command
thread_id, frame_id, line, func_name, call_order, start_line, end_line = text.split('\t', 6)
else:
# we received some command to make a single step
thread_id, line, func_name = text.split('\t', 2)
if func_name == "None":
# global context
func_name = ''
t = pydevd_find_thread_by_id(thread_id)
if t:
int_cmd = InternalSetNextStatementThread(thread_id, cmd_id, line, func_name, seq)
if cmd_id == CMD_SMART_STEP_INTO:
int_cmd = InternalSmartStepInto(thread_id, frame_id, cmd_id, func_name, line, call_order, start_line, end_line, seq)
else:
int_cmd = InternalSetNextStatementThread(thread_id, cmd_id, line, func_name, seq)
py_db.post_internal_command(int_cmd, thread_id)
elif thread_id.startswith('__frame__:'):
sys.stderr.write("Can't set next statement in tasklet: %s\n" % (thread_id,))
elif cmd_id == CMD_RELOAD_CODE:
# we received some command to make a reload of a module
module_name = text.strip()
@@ -856,6 +861,11 @@ def process_net_command(py_db, cmd_id, seq, text):
frame = None
t = None
elif cmd_id == CMD_GET_SMART_STEP_INTO_VARIANTS:
thread_id, frame_id, start_line, end_line = text.split('\t', 3)
int_cmd = InternalGetSmartStepIntoVariants(seq, thread_id, frame_id, start_line, end_line)
py_db.post_internal_command(int_cmd, thread_id)
else:
#I have no idea what this is all about
cmd = py_db.cmd_factory.make_error_message(seq, "unexpected command " + str(cmd_id))
@@ -833,6 +833,7 @@ static const char *__pyx_f[] = {
/*--- Type declarations ---*/
struct __pyx_obj_14_pydevd_bundle_13pydevd_cython_PyDBAdditionalThreadInfo;
struct __pyx_obj_14_pydevd_bundle_13pydevd_cython_PydevSmartStepContext;
struct __pyx_obj_18_pydevd_frame_eval_22pydevd_frame_evaluator_ThreadInfo;
struct __pyx_obj_18_pydevd_frame_eval_22pydevd_frame_evaluator_FuncCodeInfo;
@@ -847,7 +848,6 @@ struct __pyx_obj_14_pydevd_bundle_13pydevd_cython_PyDBAdditionalThreadInfo {
PyObject *pydev_step_stop;
int pydev_step_cmd;
int pydev_notify_kill;
PyObject *pydev_smart_step_stop;
int pydev_django_resolve_frame;
PyObject *pydev_call_from_jinja2;
PyObject *pydev_call_inside_jinja2;
@@ -859,6 +859,25 @@ struct __pyx_obj_14_pydevd_bundle_13pydevd_cython_PyDBAdditionalThreadInfo {
PyObject *pydev_func_name;
int suspended_at_unhandled;
PyObject *trace_suspend_type;
struct __pyx_obj_14_pydevd_bundle_13pydevd_cython_PydevSmartStepContext *pydev_smart_step_context;
};
/* "_pydevd_bundle/pydevd_cython.pxd":20
*
*
* cdef class PydevSmartStepContext: # <<<<<<<<<<<<<<
* cdef public object smart_step_stop; # Actually, it's a frame or None
* cdef public int call_order;
*/
struct __pyx_obj_14_pydevd_bundle_13pydevd_cython_PydevSmartStepContext {
PyObject_HEAD
PyObject *smart_step_stop;
int call_order;
PyObject *filename;
int line;
int start_line;
int end_line;
};
@@ -1395,6 +1414,7 @@ static int __Pyx_InitStrings(__Pyx_StringTabEntry *t);
/* Module declarations from '_pydevd_bundle.pydevd_cython' */
static PyTypeObject *__pyx_ptype_14_pydevd_bundle_13pydevd_cython_PyDBAdditionalThreadInfo = 0;
static PyTypeObject *__pyx_ptype_14_pydevd_bundle_13pydevd_cython_PydevSmartStepContext = 0;
/* Module declarations from '_pydevd_frame_eval.pydevd_frame_evaluator' */
static PyTypeObject *__pyx_ptype_18_pydevd_frame_eval_22pydevd_frame_evaluator_ThreadInfo = 0;
@@ -9375,6 +9395,8 @@ static int __Pyx_modinit_type_import_code(void) {
__Pyx_GOTREF(__pyx_t_1);
__pyx_ptype_14_pydevd_bundle_13pydevd_cython_PyDBAdditionalThreadInfo = __Pyx_ImportType(__pyx_t_1, "_pydevd_bundle.pydevd_cython", "PyDBAdditionalThreadInfo", sizeof(struct __pyx_obj_14_pydevd_bundle_13pydevd_cython_PyDBAdditionalThreadInfo), __Pyx_ImportType_CheckSize_Warn);
if (!__pyx_ptype_14_pydevd_bundle_13pydevd_cython_PyDBAdditionalThreadInfo) __PYX_ERR(2, 1, __pyx_L1_error)
__pyx_ptype_14_pydevd_bundle_13pydevd_cython_PydevSmartStepContext = __Pyx_ImportType(__pyx_t_1, "_pydevd_bundle.pydevd_cython", "PydevSmartStepContext", sizeof(struct __pyx_obj_14_pydevd_bundle_13pydevd_cython_PydevSmartStepContext), __Pyx_ImportType_CheckSize_Warn);
if (!__pyx_ptype_14_pydevd_bundle_13pydevd_cython_PydevSmartStepContext) __PYX_ERR(2, 20, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
__Pyx_RefNannyFinishContext();
return 0;
@@ -62,6 +62,8 @@ public abstract class AbstractCommand<T> {
public static final int SHOW_WARNING = 150;
public static final int LOAD_FULL_VALUE = 151;
public static final int CMD_GET_SMART_STEP_INTO_VARIANTS = 160;
/**
* The code of the message that means that IDE received
* {@link #PROCESS_CREATED} message from the Python debugger script.
@@ -198,6 +198,12 @@ public class ClientModeMultiProcessDebugger implements ProcessDebugger {
return debugger(threadId).loadFrame(threadId, frameId);
}
@Override
public List<Pair<String, Boolean>> getSmartStepIntoVariants(String threadId, String frameId, int startContextLine, int endContextLine)
throws PyDebuggerException {
return debugger(threadId).getSmartStepIntoVariants(threadId, frameId, startContextLine, endContextLine);
}
@Override
public XValueChildrenList loadVariable(String threadId, String frameId, PyDebugValue var) throws PyDebuggerException {
return debugger(threadId).loadVariable(threadId, frameId, var);
@@ -398,8 +404,8 @@ public class ClientModeMultiProcessDebugger implements ProcessDebugger {
}
@Override
public void smartStepInto(String threadId, String functionName) {
debugger(threadId).smartStepInto(threadId, functionName);
public void smartStepInto(String threadId, String frameId, String functionName, int callOrder, int contextStartLine, int contextEndLine) {
debugger(threadId).smartStepInto(threadId, frameId, functionName, callOrder, contextStartLine, contextEndLine);
}
@Override
@@ -0,0 +1,45 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.debugger.pydev;
import com.intellij.openapi.util.Pair;
import com.jetbrains.python.debugger.PyDebuggerException;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class GetSmartStepIntoVariantsCommand extends AbstractFrameCommand {
private final int myContextStartLine;
private final int myContextEndLine;
private List<Pair<String, Boolean>> myVariants;
protected GetSmartStepIntoVariantsCommand(RemoteDebugger debugger, String threadId, String frameId,
int contextStartLine, int contextEndLine) {
super(debugger, CMD_GET_SMART_STEP_INTO_VARIANTS, threadId, frameId);
myContextStartLine = contextStartLine;
myContextEndLine = contextEndLine;
}
@Override
protected void buildPayload(Payload payload) {
super.buildPayload(payload);
payload.add(myContextStartLine).add(myContextEndLine);
}
@Override
public boolean isResponseExpected() {
return true;
}
@Override
protected void processResponse(@NotNull ProtocolFrame response) throws PyDebuggerException {
super.processResponse(response);
String payload = response.getPayload();
myVariants = ProtocolParser.parseSmartStepIntoVariants(payload);
}
public List<Pair<String, Boolean>> getVariants() {
return myVariants;
}
}
@@ -176,6 +176,12 @@ public class MultiProcessDebugger implements ProcessDebugger {
return debugger(threadId).loadFrame(threadId, frameId);
}
@Override
public List<Pair<String, Boolean>> getSmartStepIntoVariants(String threadId, String frameId, int startContextLine, int endContextLine)
throws PyDebuggerException {
return debugger(threadId).getSmartStepIntoVariants(threadId, frameId, startContextLine, endContextLine);
}
@Override
public XValueChildrenList loadVariable(String threadId, String frameId, PyDebugValue var) throws PyDebuggerException {
return debugger(threadId).loadVariable(threadId, frameId, var);
@@ -361,8 +367,8 @@ public class MultiProcessDebugger implements ProcessDebugger {
}
@Override
public void smartStepInto(String threadId, String functionName) {
debugger(threadId).smartStepInto(threadId, functionName);
public void smartStepInto(String threadId, String frameId, String functionName, int callOrder, int contextStartLine, int contextEndLine) {
debugger(threadId).smartStepInto(threadId, frameId, functionName, callOrder, contextStartLine, contextEndLine);
}
@Override
@@ -31,6 +31,8 @@ public interface ProcessDebugger {
XValueChildrenList loadFrame(String threadId, String frameId) throws PyDebuggerException;
List<Pair<String, Boolean>> getSmartStepIntoVariants(String threadId, String frameId, int startContextLine, int endContextLine) throws PyDebuggerException;
// todo: don't generate temp variables for qualified expressions - just split 'em
XValueChildrenList loadVariable(String threadId, String frameId, PyDebugValue var) throws PyDebuggerException;
@@ -80,7 +82,7 @@ public interface ProcessDebugger {
void run() throws PyDebuggerException;
void smartStepInto(String threadId, String functionName);
void smartStepInto(String threadId, String frameId, String functionName, int callOrder, int contextStartLine, int contextEndLine);
void resumeOrStep(String threadId, ResumeOrStepCommand.Mode mode);
@@ -315,6 +315,19 @@ public class ProtocolParser {
return result.createArrayChunk();
}
public static @NotNull List<Pair<String, Boolean>> parseSmartStepIntoVariants(String text) throws PyDebuggerException {
XppReader reader = openReader(text, false);
List<Pair<String, Boolean>> variants = Lists.newArrayList();
while (reader.hasMoreChildren()) {
reader.moveDown();
String variantName = read(reader, "name", true);
Boolean isVisited = read(reader, "isVisited", true).equals("true");
variants.add(Pair.create(variantName, isVisited));
reader.moveUp();
}
return variants;
}
private static void parseArrayHeaderData(XppReader reader, ArrayChunkBuilder result) throws PyDebuggerException {
List<String> rowHeaders = Lists.newArrayList();
List<ArrayChunk.ColHeader> colHeaders = Lists.newArrayList();
@@ -169,6 +169,14 @@ public class RemoteDebugger implements ProcessDebugger {
return command.getVariables();
}
@Override
public List<Pair<String, Boolean>> getSmartStepIntoVariants(String threadId, String frameId, int startContextLine, int endContextLine)
throws PyDebuggerException {
GetSmartStepIntoVariantsCommand command = new GetSmartStepIntoVariantsCommand(this, threadId, frameId, startContextLine, endContextLine);
command.execute();
return command.getVariants();
}
// todo: don't generate temp variables for qualified expressions - just split 'em
@Override
public XValueChildrenList loadVariable(final String threadId, final String frameId, final PyDebugValue var) throws PyDebuggerException {
@@ -440,8 +448,9 @@ public class RemoteDebugger implements ProcessDebugger {
}
@Override
public void smartStepInto(String threadId, String functionName) {
final SmartStepIntoCommand command = new SmartStepIntoCommand(this, threadId, functionName);
public void smartStepInto(String threadId, String frameId, String functionName, int callOrder, int contextStartLine, int contextEndLine) {
final SmartStepIntoCommand command = new SmartStepIntoCommand(this, threadId, frameId, functionName, callOrder,
contextStartLine, contextEndLine);
execute(command);
}
@@ -3,19 +3,25 @@ package com.jetbrains.python.debugger.pydev;
import org.jetbrains.annotations.NotNull;
public class SmartStepIntoCommand extends AbstractThreadCommand {
public class SmartStepIntoCommand extends AbstractFrameCommand {
private final String myFuncName;
private final int myCallOrder;
private final int myContextStartLine;
private final int myContextEndLine;
public SmartStepIntoCommand(@NotNull final RemoteDebugger debugger, String threadId,
String funcName) {
super(debugger, SMART_STEP_INTO, threadId);
public SmartStepIntoCommand(@NotNull final RemoteDebugger debugger, String threadId, String frameId,
String funcName, int callOrder, int contextStartLine, int contextEndLine) {
super(debugger, SMART_STEP_INTO, threadId, frameId);
myFuncName = funcName;
myCallOrder = callOrder;
myContextStartLine = contextStartLine;
myContextEndLine = contextEndLine;
}
@Override
protected void buildPayload(Payload payload) {
super.buildPayload(payload);
payload.add("0").add(myFuncName);
payload.add("0").add(myFuncName).add(myCallOrder).add(myContextStartLine).add(myContextEndLine);
}
}
@@ -56,6 +56,9 @@ import com.jetbrains.python.console.pydev.PydevCompletionVariant;
import com.jetbrains.python.debugger.containerview.PyViewNumericContainerAction;
import com.jetbrains.python.debugger.pydev.*;
import com.jetbrains.python.debugger.settings.PyDebuggerSettings;
import com.jetbrains.python.debugger.smartstepinto.PySmartStepIntoContext;
import com.jetbrains.python.debugger.smartstepinto.PySmartStepIntoHandler;
import com.jetbrains.python.debugger.smartstepinto.PySmartStepIntoVariant;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.PyResolveUtil;
@@ -99,7 +102,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
private boolean myDownloadSources = false;
protected PyPositionConverter myPositionConverter;
private final XSmartStepIntoHandler<?> mySmartStepIntoHandler;
@NotNull private final XSmartStepIntoHandler<?> mySmartStepIntoHandler;
private boolean myWaitingForConnection = false;
private PyStackFrame myConsoleContextFrame = null;
private PyReferrersLoader myReferrersProvider;
@@ -109,6 +112,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
private final Map<String, Map<String, PyDebugValueDescriptor>> myDescriptorsCache = Maps.newConcurrentMap();
public PyDebugProcess(@NotNull XDebugSession session,
@NotNull ServerSocket serverSocket,
@NotNull ExecutionConsole executionConsole,
@@ -144,7 +148,6 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
myBreakpointHandlers = breakpointHandlers.toArray(XBreakpointHandler.EMPTY_ARRAY);
myEditorsProvider = new PyDebuggerEditorsProvider();
mySmartStepIntoHandler = new PySmartStepIntoHandler(this);
myProcessHandler = processHandler;
myExecutionConsole = executionConsole;
if (myProcessHandler != null) {
@@ -157,6 +160,8 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
myPositionConverter = new PyLocalPositionConverter();
}
mySmartStepIntoHandler = new PySmartStepIntoHandler(this);
PyDebugValueExecutionService executionService = PyDebugValueExecutionService.getInstance(getProject());
executionService.sessionStarted(this);
session.addSessionListener(new XDebugSessionListener() {
@@ -280,6 +285,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
}
@Override
@NotNull
public XSmartStepIntoHandler<?> getSmartStepIntoHandler() {
return mySmartStepIntoHandler;
}
@@ -501,7 +507,6 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
}
}
public void setShowReturnValues(boolean showReturnValues) {
myDebugger.setShowReturnValues(showReturnValues);
}
@@ -543,15 +548,32 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
passToCurrentThread(context, ResumeOrStepCommand.Mode.STEP_OUT);
}
public void startSmartStepInto(String functionName) {
dropFrameCaches();
public void startSmartStepInto(@NotNull PySmartStepIntoVariant variant) {
String threadId = variant.getContext().getFrame().getThreadId();
String frameId = variant.getContext().getFrame().getFrameId();
if (isConnected()) {
dropFrameCaches();
for (PyThreadInfo suspendedThread : mySuspendedThreads) {
myDebugger.smartStepInto(suspendedThread.getId(), functionName);
if (threadId.equals(suspendedThread.getId())) {
PySmartStepIntoContext context = variant.getContext();
myDebugger.smartStepInto(threadId, frameId, variant.getFunctionName(), variant.getCallOrder(),
context.getStartLine(), context.getEndLine());
break;
}
}
}
}
public List<Pair<String, Boolean>> getSmartStepIntoVariants(int startContextLine, int endContextLine) {
try {
PyStackFrame frame = currentFrame();
return myDebugger.getSmartStepIntoVariants(frame.getThreadId(), frame.getFrameId(), startContextLine, endContextLine);
}
catch (PyDebuggerException e) {
return Collections.emptyList();
}
}
@Override
public void stop() {
myDebugger.close();
@@ -1,95 +0,0 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.debugger;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.xdebugger.XDebugSession;
import com.intellij.xdebugger.XDebuggerUtil;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.stepping.XSmartStepIntoHandler;
import com.intellij.xdebugger.stepping.XSmartStepIntoVariant;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.PyCallExpression;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyExpression;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Set;
public class PySmartStepIntoHandler extends XSmartStepIntoHandler<PySmartStepIntoHandler.PySmartStepIntoVariant> {
private final XDebugSession mySession;
private final PyDebugProcess myProcess;
public PySmartStepIntoHandler(final PyDebugProcess process) {
mySession = process.getSession();
myProcess = process;
}
@Override
@NotNull
public List<PySmartStepIntoVariant> computeSmartStepVariants(@NotNull XSourcePosition position) {
final Document document = FileDocumentManager.getInstance().getDocument(position.getFile());
final List<PySmartStepIntoVariant> variants = Lists.newArrayList();
final Set<PyCallExpression> visitedCalls = Sets.newHashSet();
final int line = position.getLine();
XDebuggerUtil.getInstance().iterateLine(mySession.getProject(), document, line, psiElement -> {
addVariants(document, line, psiElement, variants, visitedCalls);
return true;
});
return variants;
}
@Override
public void startStepInto(@NotNull PySmartStepIntoVariant smartStepIntoVariant) {
myProcess.startSmartStepInto(smartStepIntoVariant.getFunctionName());
}
@Override
public String getPopupTitle(@NotNull XSourcePosition position) {
return PyBundle.message("debug.popup.title.step.into.function");
}
private static void addVariants(Document document, int line, @Nullable PsiElement element,
List<PySmartStepIntoVariant> variants,
Set<PyCallExpression> visited) {
if (element == null) return;
final PyCallExpression expression = PsiTreeUtil.getParentOfType(element, PyCallExpression.class);
if (expression != null &&
expression.getTextRange().getEndOffset() <= document.getLineEndOffset(line) &&
visited.add(expression)) {
addVariants(document, line, expression.getParent(), variants, visited);
PyExpression ref = expression.getCallee();
variants.add(new PySmartStepIntoVariant(ref));
}
}
public static class PySmartStepIntoVariant extends XSmartStepIntoVariant {
//private final String myFunctionName;
private final PyElement myElement;
public PySmartStepIntoVariant(PyElement element) {
myElement = element;
}
@Override
public String getText() {
return myElement.getText() + "()";
}
public String getFunctionName() {
String name = myElement.getName();
return name != null ? name : getText();
}
}
}
@@ -1,6 +1,7 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.debugger;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.application.ApplicationManager;
@@ -22,6 +23,7 @@ import com.intellij.xdebugger.frame.XValue;
import com.intellij.xdebugger.frame.XValueChildrenList;
import com.jetbrains.python.debugger.settings.PyDebuggerSettings;
import icons.PythonIcons;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -37,14 +39,15 @@ public class PyStackFrame extends XStackFrame {
private static final Object STACK_FRAME_EQUALITY_OBJECT = new Object();
public static final String DOUBLE_UNDERSCORE = "__";
public static final String RETURN_VALUES_GROUP_NAME = "Return Values";
public static final String SPECIAL_VARIABLES_GROUP_NAME = "Special Variables";
public static final Set<String> HIDE_TYPES = ContainerUtil.set("function", "type", "classobj", "module");
@NotNull @NonNls public static final String RETURN_VALUES_GROUP_NAME = "Return Values";
@NotNull @NonNls public static final String SPECIAL_VARIABLES_GROUP_NAME = "Special Variables";
@NotNull @NonNls public static final Set<String> HIDE_TYPES = ContainerUtil.set("function", "type", "classobj", "module");
public static final int DUNDER_VALUES_IND = 0;
public static final int SPECIAL_TYPES_IND = DUNDER_VALUES_IND + 1;
public static final int IPYTHON_VALUES_IND = SPECIAL_TYPES_IND + 1;
public static final int NUMBER_OF_GROUPS = IPYTHON_VALUES_IND + 1;
@NotNull @NonNls public static final Set<String> COMPREHENSION_NAMES = ImmutableSet.of("<genexpr>", "<listcomp>", "<dictcomp>",
"<setcomp>");
private final Project myProject;
private final PyFrameAccessor myDebugProcess;
private final PyStackFrameInfo myFrameInfo;
@@ -217,6 +220,15 @@ public class PyStackFrame extends XStackFrame {
return myPosition;
}
@NotNull
public String getName() {
return myFrameInfo.getName();
}
public boolean isComprehension() {
return COMPREHENSION_NAMES.contains(getName());
}
public void setChildrenDescriptors(@Nullable Map<String, PyDebugValueDescriptor> childrenDescriptors) {
myChildrenDescriptors = childrenDescriptors;
}
@@ -28,6 +28,7 @@ public class PyDebuggerSettings extends XDebuggerSettings<PyDebuggerSettings> im
private boolean myWatchReturnValues = false;
private boolean mySimplifiedView = true;
private volatile PyDebugValue.ValuesPolicy myValuesPolicy = PyDebugValue.ValuesPolicy.ASYNC;
private boolean myAlwaysDoSmartStepIntoEnabled = true;
public PyDebuggerSettings() {
super("python");
@@ -78,6 +79,14 @@ public class PyDebuggerSettings extends XDebuggerSettings<PyDebuggerSettings> im
mySteppingFiltersEnabled = steppingFiltersEnabled;
}
public void setAlwaysDoSmartStepIntoEnabled(boolean alwaysDoSmartStepIntoEnabled) {
myAlwaysDoSmartStepIntoEnabled = alwaysDoSmartStepIntoEnabled;
}
public boolean isAlwaysDoSmartStepInto() {
return myAlwaysDoSmartStepIntoEnabled;
}
@NotNull
public List<PySteppingFilter> getSteppingFilters() {
return mySteppingFilters;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.jetbrains.python.debugger.settings.PyDebuggerSteppingConfigurableUi">
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="500" height="400"/>
@@ -17,23 +17,33 @@
<text resource-bundle="messages/PyBundle" key="form.debugger.stepping.do.not.step.into.scripts"/>
</properties>
</component>
<hspacer id="1eb52">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
<grid id="79cc7" binding="mySteppingPanel" custom-create="true" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
<grid row="3" column="0" row-span="1" col-span="2" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children/>
</grid>
<component id="dd487" class="com.intellij.ui.components.JBCheckBox" binding="myAlwaysDoSmartStepIntoCheckBox">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<horizontalAlignment value="2"/>
<selected value="false"/>
<text value="Always do smart step into"/>
</properties>
</component>
<hspacer id="1eb52">
<constraints>
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
<component id="5fb35" class="com.intellij.ui.components.JBCheckBox" binding="myLibrariesFilterCheckBox">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<horizontalAlignment value="2"/>
@@ -40,6 +40,7 @@ public class PyDebuggerSteppingConfigurableUi implements ConfigurableUi<PyDebugg
private JPanel mySteppingPanel;
private JBCheckBox myLibrariesFilterCheckBox;
private JBCheckBox myStepFilterEnabledCheckBox;
private JBCheckBox myAlwaysDoSmartStepIntoCheckBox;
private TableModelEditor<PySteppingFilter> myPySteppingFilterEditor;
public PyDebuggerSteppingConfigurableUi() {
@@ -62,6 +63,7 @@ public class PyDebuggerSteppingConfigurableUi implements ConfigurableUi<PyDebugg
public void reset(@NotNull PyDebuggerSettings settings) {
myLibrariesFilterCheckBox.setSelected(settings.isLibrariesFilterEnabled());
myStepFilterEnabledCheckBox.setSelected(settings.isSteppingFiltersEnabled());
myAlwaysDoSmartStepIntoCheckBox.setSelected(settings.isAlwaysDoSmartStepInto());
myPySteppingFilterEditor.reset(settings.getSteppingFilters());
myPySteppingFilterEditor.enabled(myStepFilterEnabledCheckBox.isSelected());
}
@@ -70,6 +72,7 @@ public class PyDebuggerSteppingConfigurableUi implements ConfigurableUi<PyDebugg
public boolean isModified(@NotNull PyDebuggerSettings settings) {
return myLibrariesFilterCheckBox.isSelected() != settings.isLibrariesFilterEnabled()
|| myStepFilterEnabledCheckBox.isSelected() != settings.isSteppingFiltersEnabled()
|| myAlwaysDoSmartStepIntoCheckBox.isSelected() != settings.isAlwaysDoSmartStepInto()
|| myPySteppingFilterEditor.isModified();
}
@@ -77,6 +80,7 @@ public class PyDebuggerSteppingConfigurableUi implements ConfigurableUi<PyDebugg
public void apply(@NotNull PyDebuggerSettings settings) throws ConfigurationException {
settings.setLibrariesFilterEnabled(myLibrariesFilterCheckBox.isSelected());
settings.setSteppingFiltersEnabled(myStepFilterEnabledCheckBox.isSelected());
settings.setAlwaysDoSmartStepIntoEnabled(myAlwaysDoSmartStepIntoCheckBox.isSelected());
if (myPySteppingFilterEditor.isModified()) {
settings.setSteppingFilters(myPySteppingFilterEditor.apply());
}
@@ -0,0 +1,63 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.debugger.smartstepinto;
import com.jetbrains.python.debugger.PyStackFrame;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public class PySmartStepIntoContext {
private final int myStartLine;
private final int myEndLine;
@NotNull private final PyStackFrame myFrame;
public PySmartStepIntoContext(int startLine, int endLine, @NotNull PyStackFrame frame) {
myStartLine = startLine;
myEndLine = endLine;
myFrame = frame;
}
public int getStartLine() {
return myStartLine;
}
public int getEndLine() {
return myEndLine;
}
@NotNull
public PyStackFrame getFrame() {
return myFrame;
}
/**
* Compares this context to the specified object. The result is {@code
* true} if the argument is instance of {@code PySmartStepIntoContext},
* the line the two contexts were created are the same and either the IDs
* of the frames they were called are the same or this context frame is
* the frame of a generator expression. The latest is important because we
* don't want to make a difference between a generator expression frame and
* the frame it was called from. Otherwise we would create a new context
* on each generator iteration.
*
* @param o
* The object to compare this {@code PySmartStepIntoContext} against
*
* @return {@code true} if the given object represents a {@code PySmartStepIntoContext}
* equivalent to this context, {@code false} otherwise
*
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PySmartStepIntoContext)) return false;
PySmartStepIntoContext context = (PySmartStepIntoContext)o;
return myStartLine == context.myStartLine && myEndLine == context.myEndLine
&& myFrame.getFrameId().equals(context.getFrame().getFrameId());
}
@Override
public int hashCode() {
return Objects.hash(myStartLine, myFrame);
}
}
@@ -0,0 +1,149 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.debugger.smartstepinto;
import com.google.common.collect.Lists;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.DocumentUtil;
import com.intellij.xdebugger.XDebugSession;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.stepping.XSmartStepIntoHandler;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.debugger.PyDebugProcess;
import com.jetbrains.python.debugger.PyStackFrame;
import com.jetbrains.python.debugger.settings.PyDebuggerSettings;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyStatement;
import com.jetbrains.python.psi.PyStatementPart;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.concurrency.Promise;
import org.jetbrains.concurrency.Promises;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class PySmartStepIntoHandler extends XSmartStepIntoHandler<PySmartStepIntoVariant> {
@NotNull private final XDebugSession mySession;
@NotNull private final PyDebugProcess myProcess;
public PySmartStepIntoHandler(@NotNull final PyDebugProcess process) {
mySession = process.getSession();
myProcess = process;
}
@Override
public void startStepInto(@NotNull PySmartStepIntoVariant smartStepIntoVariant) {
myProcess.startSmartStepInto(smartStepIntoVariant);
}
@Override
public String getPopupTitle(@NotNull XSourcePosition position) {
return PyBundle.message("debug.popup.title.step.into.function");
}
@Override
@NotNull
public List<PySmartStepIntoVariant> computeSmartStepVariants(@NotNull XSourcePosition position) {
PyStackFrame currentFrame = (PyStackFrame)mySession.getCurrentStackFrame();
if (currentFrame == null || currentFrame.isComprehension()) return Collections.emptyList();
PySmartStepIntoContext context = createSmartStepIntoContext(currentFrame);
if (context == null) return Collections.emptyList();
final Document document = FileDocumentManager.getInstance().getDocument(position.getFile());
if (document == null) return Collections.emptyList();
Future<List<Pair<String, Boolean>>> future = ApplicationManager.getApplication().executeOnPooledThread(
() -> myProcess.getSmartStepIntoVariants(context.getStartLine(), context.getEndLine()));
List<Pair<String, Boolean>> variantsFromPython;
try {
variantsFromPython = future.get();
}
catch (InterruptedException | ExecutionException e) {
return Collections.emptyList();
}
if (variantsFromPython.size() == 0) return Collections.emptyList();
return removePossiblyUnreachableVariants(document, position.getLine(), variantsFromPython, context);
}
private @NotNull List<PySmartStepIntoVariant> removePossiblyUnreachableVariants(@NotNull Document document, int line,
@NotNull List<Pair<String, Boolean>> variantsFromPython,
@NotNull PySmartStepIntoContext context) {
PsiElement statement = findLineTopStatement(document, line);
if (statement == null) return Collections.emptyList();
List<PySmartStepIntoVariant> result = Lists.newArrayList();
// We are going to filter the variants that PyCharm cannot resolve to be sure we don't suggest stepping into a native function.
statement.acceptChildren(new PySmartStepIntoVariantVisitor(result, variantsFromPython, context));
return result;
}
@Nullable
public PsiElement findLineTopStatement(@NotNull Document document, int line) {
int offset = DocumentUtil.getFirstNonSpaceCharOffset(document, line);
PsiFile file = PsiDocumentManager.getInstance(mySession.getProject()).getPsiFile(document);
if (file == null) return null;
PsiElement element = file.findElementAt(offset);
int lineEndOffset = document.getLineEndOffset(line);
while (element != null) {
if ((element instanceof PyFunction) && (element.getStartOffsetInParent() < lineEndOffset)) {
// A decorated function. We allowing stepping into decorators.
return element;
}
if (element.getTextOffset() < lineEndOffset) {
if (element instanceof PyStatement || element instanceof PyStatementPart)
return element;
}
element = element.getParent();
}
return null;
}
@NotNull
@Override
public Promise<List<PySmartStepIntoVariant>> computeStepIntoVariants(@NotNull XSourcePosition position) {
if (PyDebuggerSettings.getInstance().isAlwaysDoSmartStepInto()) {
return computeSmartStepVariantsAsync(position);
}
return Promises.rejectedPromise();
}
/**
* Creates and stores a smart step into context for the given frame.
*/
@Nullable
public PySmartStepIntoContext createSmartStepIntoContext(@NotNull PyStackFrame frame) {
XSourcePosition position = frame.getSourcePosition();
if (position == null) return null;
Document document = FileDocumentManager.getInstance().getDocument(position.getFile());
if (document == null) return null;
PySmartStepIntoHandler handler = (PySmartStepIntoHandler)myProcess.getSmartStepIntoHandler();
PsiElement statement = handler.findLineTopStatement(document, position.getLine());
if (statement != null) {
TextRange range = statement.getTextRange();
int startLine = document.getLineNumber(range.getStartOffset());
int endLine = document.getLineNumber(range.getEndOffset());
return new PySmartStepIntoContext(startLine + 1, endLine + 1, frame);
}
return null;
}
}
@@ -0,0 +1,57 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.debugger.smartstepinto;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.xdebugger.stepping.XSmartStepIntoVariant;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
/**
* An abstract class for a thing we can smart step into.
*
* Should you need to support another Python expression as a smart step into target,
* subclass this class and add the respective method override to {@code PySmartStepIntoVariantVisitor}.
*
*/
public abstract class PySmartStepIntoVariant extends XSmartStepIntoVariant {
@NotNull protected final PsiElement myElement;
protected final int myCallOrder;
@NotNull protected final PySmartStepIntoContext myContext;
protected PySmartStepIntoVariant(@NotNull PsiElement element, int callOrder, @NotNull PySmartStepIntoContext context) {
myElement = element;
myCallOrder = callOrder;
myContext = context;
}
@Nullable public abstract String getFunctionName();
public int getCallOrder() {
return myCallOrder;
}
@NotNull public PySmartStepIntoContext getContext() {
return myContext;
}
@Override
@Nullable public TextRange getHighlightRange() {
return myElement.getTextRange();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PySmartStepIntoVariant)) return false;
PySmartStepIntoVariant variant = (PySmartStepIntoVariant)o;
return myElement.equals(variant.myElement);
}
@Override
public int hashCode() {
return Objects.hash(myElement);
}
}
@@ -0,0 +1,48 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.debugger.smartstepinto;
import com.intellij.openapi.util.TextRange;
import com.jetbrains.python.psi.PyCallExpression;
import com.jetbrains.python.psi.PyExpression;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PySmartStepIntoVariantCallExpression extends PySmartStepIntoVariant {
@Nullable private final PyExpression myCallee;
public PySmartStepIntoVariantCallExpression(@NotNull PyCallExpression element, int callOrder, @NotNull PySmartStepIntoContext context) {
super(element, callOrder, context);
myCallee = element.getCallee();
}
@Nullable
@Override
public String getFunctionName() {
return myCallee != null ? myCallee.getName() : null;
}
@Nullable
@Override
public TextRange getHighlightRange() {
if (myCallee == null) return null;
String calleeName = myCallee.getName();
if (calleeName == null) return null;
TextRange range = myCallee.getTextRange();
// For example, for the `foo().bar().baz()` call expression the callee will be `foo().bar().baz`.
// The range must be adjusted in such cases to match only the last part which is `baz`.
if (calleeName.length() < range.getLength()) {
int diff = range.getLength() - calleeName.length();
return new TextRange(range.getStartOffset() + diff, range.getEndOffset());
}
return range;
}
@Nullable
@Override
public String getText() {
return myCallee != null ? myCallee.getText() + "()" : null;
}
}
@@ -0,0 +1,42 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.debugger.smartstepinto;
import com.jetbrains.python.debugger.PyStackFrame;
import com.jetbrains.python.psi.PyComprehensionElement;
import com.jetbrains.python.psi.PyDictCompExpression;
import com.jetbrains.python.psi.PyGeneratorExpression;
import com.jetbrains.python.psi.PyListCompExpression;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
public class PySmartStepIntoVariantComprehension extends PySmartStepIntoVariant {
protected PySmartStepIntoVariantComprehension(@NotNull PyComprehensionElement element, int callOrder,
@NotNull PySmartStepIntoContext context) {
super(element, callOrder, context);
}
@Override
@NotNull
public String getFunctionName() {
return getText();
}
@NonNls
@Override
@NotNull
public String getText() {
if (myElement instanceof PyGeneratorExpression)
return "<genexpr>";
else if (myElement instanceof PyListCompExpression)
return "<listcomp>";
else if (myElement instanceof PyDictCompExpression)
return "<dictcomp>";
else
return "<setcomp>";
}
public static boolean isComprehensionName(@NotNull String name) {
return PyStackFrame.COMPREHENSION_NAMES.contains(name);
}
}
@@ -0,0 +1,60 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.debugger.smartstepinto;
import com.google.common.collect.ImmutableMap;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.jetbrains.python.psi.PyElementType;
import com.jetbrains.python.psi.PyPrefixExpression;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
public class PySmartStepIntoVariantOperator extends PySmartStepIntoVariant {
@NonNls private static final Map<String, String> UNARY_OPERATOR_MAPPING = ImmutableMap.of(
"__sub__", "__neg__",
"__add__", "__pos__",
"__invert__", "__invert__"
);
public PySmartStepIntoVariantOperator(@NotNull PsiElement element,
int callOrder,
@NotNull PySmartStepIntoContext context) {
super(element, callOrder, context);
}
@Nullable
@Override
public String getFunctionName() {
if (myElement instanceof LeafPsiElement) {
return ((PyElementType)((LeafPsiElement)myElement).getElementType()).getSpecialMethodName();
}
else if (myElement instanceof PyPrefixExpression) {
return getUnaryOperatorSpecialMethodName(((PyPrefixExpression)myElement).getOperator());
}
return null;
}
@NotNull
@Override
public String getText() {
String text = myElement.getText();
return myElement instanceof PyPrefixExpression ? text.substring(0, 1) : text;
}
@NotNull
@Override
public TextRange getHighlightRange() {
TextRange textRange = myElement.getTextRange();
return myElement instanceof PyPrefixExpression ?
new TextRange(textRange.getStartOffset(), textRange.getStartOffset() + 1) : textRange;
}
@Nullable
public static String getUnaryOperatorSpecialMethodName(@NotNull PyElementType operator) {
return UNARY_OPERATOR_MAPPING.getOrDefault(operator.getSpecialMethodName(), null);
}
}
@@ -0,0 +1,156 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.debugger.smartstepinto;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.types.TypeEvalContext;
import com.jetbrains.python.pyi.PyiFile;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class PySmartStepIntoVariantVisitor extends PyRecursiveElementVisitor {
@NotNull @NonNls private static final ImmutableSet<String> BUILTINS_MODULES = ImmutableSet.of("builtins.py", "__builtin__.py");
int myVariantIndex = -1;
@NotNull private final List<PySmartStepIntoVariant> myCollector;
@NotNull private final List<Pair<String, Boolean>> myVariantsFromPython;
@NotNull private final PySmartStepIntoContext myContext;
@NotNull private final Map<String, Integer> mySeenVariants = Maps.newHashMap();
@NotNull private final Set<PsiElement> alreadyVisited = Sets.newHashSet();
public PySmartStepIntoVariantVisitor(@NotNull List<PySmartStepIntoVariant> collector,
@NotNull List<Pair<String, Boolean>> variantsFromPython,
@NotNull PySmartStepIntoContext context) {
myCollector = collector;
myVariantsFromPython = variantsFromPython;
myContext = context;
}
@Override
public void visitPyCallExpression(@NotNull PyCallExpression node) {
node.acceptChildren(this);
if (alreadyVisited.contains(node)) return;
alreadyVisited.add(node);
if (myVariantIndex == myVariantsFromPython.size() - 1) return;
PyExpression callee = node.getCallee();
if (callee == null || callee.getName() == null) return;
if (!callee.getName().equals(myVariantsFromPython.get(myVariantIndex + 1).first)) return;
myVariantIndex++;
int callOrder = getCallOrder();
mySeenVariants.put(myVariantsFromPython.get(myVariantIndex).first, ++callOrder);
PsiElement ref = callee.getReference() != null ? callee.getReference().resolve() : null;
if (ref != null && isBuiltIn(ref)) return;
if (ref instanceof PyFunction && ((((PyFunction)ref).isAsync()) || ((PyFunction)ref).isGenerator())) return;
if (isAlreadySeen()) return;
myCollector.add(new PySmartStepIntoVariantCallExpression(node, callOrder, myContext));
}
@Override
public void visitPyDecoratorList(@NotNull PyDecoratorList node) {
for (PyDecorator decorator : node.getDecorators())
visitPyCallExpression(decorator);
}
@Override
public void visitPyComprehensionElement(PyComprehensionElement node) {
node.acceptChildren(this);
if (alreadyVisited.contains(node)) return;
alreadyVisited.add(node);
if (myVariantIndex == myVariantsFromPython.size() - 1) return;
if (!PySmartStepIntoVariantComprehension.isComprehensionName(myVariantsFromPython.get(myVariantIndex + 1).first)) return;
myVariantIndex++;
int callOrder = getCallOrder();
mySeenVariants.put(myVariantsFromPython.get(myVariantIndex).first, ++callOrder);
if (isAlreadySeen()) return;
myCollector.add(new PySmartStepIntoVariantComprehension(node, callOrder, myContext));
}
@Override
public void visitPyBinaryExpression(@NotNull PyBinaryExpression node) {
// We have to visit both branches of a binary expression first to preserve the execution order as it is in a CPython interpreter,
// E.g. in the `f() + g()` expression the execution order goes like `f()`, `g()`, `+`, but it's `f()`, `+`, `g()` in the PSI tree.
node.getLeftExpression().accept(this);
if (node.getRightExpression() != null) node.getRightExpression().accept(this);
node.acceptChildren(this);
if (alreadyVisited.contains(node)) return;
alreadyVisited.add(node);
if (myVariantIndex == myVariantsFromPython.size() - 1) return;
PyElementType operator = node.getOperator();
if (PyTokenTypes.OPERATIONS.contains(operator)) processOperator(operator, node);
}
@Override
public void visitPyPrefixExpression(@NotNull PyPrefixExpression node) {
PyExpression operand = node.getOperand();
if (operand != null) operand.accept(this);
PyElementType operator = node.getOperator();
processOperator(operator, node);
}
private void processOperator(PyElementType operator, PyReferenceOwner expression) {
boolean isBinaryOperator = expression instanceof PyBinaryExpression;
String specialMethodName = isBinaryOperator ? operator.getSpecialMethodName() :
PySmartStepIntoVariantOperator.getUnaryOperatorSpecialMethodName(operator);
if (specialMethodName == null || !specialMethodName.equals(myVariantsFromPython.get(myVariantIndex + 1).first)) return;
myVariantIndex++;
int callOrder = getCallOrder();
mySeenVariants.put(myVariantsFromPython.get(myVariantIndex).first, ++callOrder);
PsiElement resolved = expression.getReference(
PyResolveContext.defaultContext().withTypeEvalContext(TypeEvalContext.userInitiated(
expression.getProject(), expression.getContainingFile()))).resolve();
if (resolved == null || isBuiltIn(resolved) || isAlreadySeen()) return;
PsiElement psiOperator = isBinaryOperator ? ((PyBinaryExpression)expression).getPsiOperator() : expression;
if (psiOperator != null) myCollector.add(new PySmartStepIntoVariantOperator(psiOperator, callOrder, myContext));
}
private static boolean isBuiltIn(@NotNull PsiElement ref) {
PsiElement navFile = ref.getNavigationElement().getContainingFile();
return (navFile instanceof PyFile && BUILTINS_MODULES.contains(navFile.getContainingFile().getName())
|| navFile instanceof PyiFile);
}
private boolean isAlreadySeen() {
return myVariantsFromPython.get(myVariantIndex).second;
}
private int getCallOrder() {
return mySeenVariants.getOrDefault(myVariantsFromPython.get(myVariantIndex).first, -1);
}
}
+6
View File
@@ -0,0 +1,6 @@
class A:
def __init__(self, x):
self.x = x
def get_x(self):
return self.x
@@ -0,0 +1,25 @@
def foo():
return bar() + baz() + bar() + barbaz(10) + barbaz(bar()) + add(bar(), baz())
def bar():
x = 42
return x
def baz():
y = bar() + bar()
return y
def barbaz(i):
return i
def add(x, y):
return x + y
if __name__ == '__main__':
foo()
foo()
@@ -0,0 +1,3 @@
import helper
a = helper.A(42).get_x()
@@ -0,0 +1,5 @@
def identity(x):
return x
z = identity(int("1") + identity((identity(identity(identity(42))))))
@@ -0,0 +1,17 @@
class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Point(self.x - other.x, self.y - other.y)
p1 = Point(1, 1)
p2 = Point(2, 2)
p3 = Point(3, 3)
p = p1 + p2 - p3 - Point(4, 4) + Point(5, 5)
@@ -0,0 +1,48 @@
from __future__ import print_function
class A:
def __init__(self, a):
self.a = a
def __add__(self, o):
print("__add__")
return A(self.a + o.a)
def __sub__(self, o):
print("__sub__")
return A(self.a - o.a)
def __mul__(self, o):
print("__mul__")
return A(self.a * o.a)
def __truediv__(self, o):
print("__truediv__")
return A(self.a / o.a)
__div__ = __truediv__ # Python 2 compatibility
def __floordiv__(self, o):
print("__floordiv__")
return A(self.a // o.a)
def __mod__(self, o):
print("__mod__")
return A(self.a)
def __pow__(self, o):
print("__pow__")
return A(self.a ** o.a)
def identity(x):
return x
def foo():
return (((((A(1) + A(2) + A(3) - A(3)) * A(1)) / A(identity(2))) // A(
1)) % 2) ** A(3)
foo()
@@ -0,0 +1,7 @@
class A(object):
def f(self, x):
return self
a = A()
a.f(1).f(2).f(3)
@@ -0,0 +1,18 @@
from __future__ import print_function
class A:
def __init__(self, a):
self.a = a
def __gt__(self, other):
if (self.a > other.a):
return True
else:
return False
if (A(2) > A(3) > A(1)):
print("ob1 is greater than ob2")
else:
print("ob2 is greater than ob1")
@@ -0,0 +1,22 @@
from __future__ import print_function
def cond1():
return True
def cond2():
return False
if __name__ == '__main__':
xs = [1, 2, 3]
if 2 in xs:
print("YES")
else:
print("NO")
if cond1() and cond2():
print("YES")
else:
print("NO")
@@ -0,0 +1,9 @@
class A(object):
def __init__(self, x):
self.x = x
def get_x(self):
return self.x
a = A(42).get_x()
@@ -0,0 +1,13 @@
def deco(func):
def wrapper(x):
x = x + 2
return func(x)
return wrapper
@deco
def f(x):
return x
y = f(1) + f(2) # breakpoint
@@ -0,0 +1,27 @@
def foo(i):
return i
def generate_power(exponent):
def decorator(f):
def inner(*args):
result = f(*args)
return exponent ** result
return inner
return decorator
@generate_power(foo(foo(3))) # breakpoint
@generate_power(foo(foo(5)))
def raise_three(n):
return n
@generate_power(2)
def raise_two(n):
return n
raise_three(raise_two(2)) # breakpoint
@@ -0,0 +1,12 @@
class A(object):
def __init__(self, x):
self.x = x
class B(A):
def __init__(self, x, y):
super(B, self).__init__(x)
self.y = y
b = B(1, 2)
@@ -0,0 +1,13 @@
from __future__ import print_function
def add1(x):
return x + 1
def add10(x):
return x + 10
for i in (add1(x) + add10(x) for x in range(3)):
print(i)
@@ -0,0 +1,20 @@
from __future__ import print_function
def make_class(x):
class C(A):
p = x
return C
def foo():
return 100
class D(make_class(foo())): # breakpoint
def __init__(self):
self.d = 3
def foo(self):
return 1
@@ -0,0 +1,11 @@
def f(x):
return x
L = [1, 2, 3]
z = (
f(0) + f(1) +
L.pop() + f(2) + f(3) +
f(4) + L.pop()
)
@@ -0,0 +1,26 @@
from __future__ import print_function
import json
def return_my_lucking_link():
url = 'https://example.com/'
return url
def return_my_lucking_payload():
payload = {'some': 'data'}
return payload
class A(object):
def do_stuff(self, x, data=None):
return "%s: %s" % (x, data)
r = A().do_stuff( # breakpoint
return_my_lucking_link(),
data=json.dumps(return_my_lucking_payload())
)
print(r) # breakpoint
@@ -0,0 +1,11 @@
def f(lst):
lst.reverse()
lst.append(42)
return lst
L = [1, 2, 3]
counter = 2
while counter > 0:
len(f(f((f(L))))) # breakpoint
counter -= 1
@@ -0,0 +1,6 @@
def f(s):
s = s[::-1]
return s.swapcase()
result = f(f(f(f(f('abcdef'))))) # breakpoint
@@ -0,0 +1,23 @@
class A(object):
def __init__(self, x):
self.x = x
def __neg__(self):
return A(-self.x)
def __pos__(self):
return A(abs(self.x))
def __invert__(self):
return A(~self.x)
def __add__(self, other):
return A(self.x + other.x)
a1 = A(1)
a2 = A(2)
a3 = A(3)
a5 = a1 + a2 + (-a3) + (+A(-4)) + (~a1) # breakpoint
+1 -1
View File
@@ -18,7 +18,7 @@ try:
try:
print(zoo(1).foo(2)) #we got ZeroDivision here
finally:
print(zoo(0).foo(2))
print(zoo(0).foo(4))
except:
pass
@@ -5,6 +5,7 @@ import com.google.common.collect.Sets;
import com.intellij.execution.ExecutionResult;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
@@ -27,6 +28,7 @@ import com.jetbrains.env.PyExecutionFixtureTestTask;
import com.jetbrains.python.console.PythonDebugLanguageConsoleView;
import com.jetbrains.python.debugger.*;
import com.jetbrains.python.debugger.pydev.PyDebugCallback;
import com.jetbrains.python.debugger.smartstepinto.PySmartStepIntoVariant;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -120,13 +122,20 @@ public abstract class PyBaseDebuggerTask extends PyExecutionFixtureTestTask {
debugProcess.startStepIntoMyCode(currentSession.getSuspendContext());
}
protected void smartStepInto(String funcName) {
protected void smartStepInto(String funcName, int callOrder) {
XDebugSession currentSession = XDebuggerManager.getInstance(getProject()).getCurrentSession();
Assert.assertTrue(currentSession.isSuspended());
Assert.assertEquals(0, myPausedSemaphore.availablePermits());
myDebugProcess.startSmartStepInto(funcName);
ReadAction.run(() -> {
List<?> smartStepIntoVariants = getSmartStepIntoVariants();
for (Object o : smartStepIntoVariants) {
PySmartStepIntoVariant variant = (PySmartStepIntoVariant) o;
if (variant.getFunctionName().equals(funcName) && variant.getCallOrder() == callOrder)
myDebugProcess.startSmartStepInto(variant);
}
});
}
protected Pair<Boolean, String> setNextStatement(int line) throws PyDebuggerException {
@@ -528,6 +537,11 @@ public abstract class PyBaseDebuggerTask extends PyExecutionFixtureTestTask {
return hasChildWithValue(children, Integer.toString(value));
}
public List<?> getSmartStepIntoVariants() {
XSourcePosition position = XDebuggerManager.getInstance(getProject()).getCurrentSession().getCurrentPosition();
return myDebugProcess.getSmartStepIntoHandler().computeSmartStepVariants(position);
}
@Override
public void setUp(final String testName) throws Exception {
if (myFixture == null) {
File diff suppressed because it is too large Load Diff
@@ -142,116 +142,6 @@ public class PythonDebuggerTest extends PyEnvTestCase {
});
}
@Test
public void testStepOver() {
runPythonTest(new PyDebuggerTask("/debug", "test2.py") {
@Override
public void before() {
toggleBreakpoint(getFilePath(getScriptName()), 5);
}
@Override
public void testing() throws Exception {
waitForPause();
stepOver();
waitForPause();
stepOver();
waitForPause();
eval("z").hasValue("2");
}
});
}
@Test
public void testStepInto() {
runPythonTest(new PyDebuggerTask("/debug", "test2.py") {
@Override
public void before() {
toggleBreakpoint(getFilePath(getScriptName()), 5);
}
@Override
public void testing() throws Exception {
waitForPause();
stepInto();
waitForPause();
eval("x").hasValue("1");
stepOver();
waitForPause();
eval("y").hasValue("3");
stepOver();
waitForPause();
eval("z").hasValue("1");
}
});
}
@Test
public void testStepIntoMyCode() {
runPythonTest(new PyDebuggerTask("/debug", "test_my_code.py") {
@Override
public void before() {
toggleBreakpoint(getFilePath(getScriptName()), 5);
toggleBreakpoint(getFilePath(getScriptName()), 7);
}
@Override
public void testing() throws Exception {
waitForPause();
stepIntoMyCode();
waitForPause();
eval("x").hasValue("2");
resume();
waitForPause();
eval("x").hasValue("3");
stepIntoMyCode();
waitForPause();
eval("stopped_in_user_file").hasValue("True");
}
});
}
@Test
public void testSmartStepInto() {
runPythonTest(new PyDebuggerTask("/debug", "test3.py") {
@Override
public void before() {
toggleBreakpoint(getFilePath(getScriptName()), 14);
}
@Override
public void testing() throws Exception {
waitForPause();
smartStepInto("foo");
waitForPause();
stepOver();
waitForPause();
eval("y").hasValue("4");
}
});
}
@Test
public void testSmartStepInto2() {
runPythonTest(new PyDebuggerTask("/debug", "test3.py") {
@Override
public void before() {
toggleBreakpoint(getFilePath(getScriptName()), 18);
toggleBreakpoint(getFilePath(getScriptName()), 25);
}
@Override
public void testing() throws Exception {
waitForPause();
removeBreakpoint(getFilePath(getScriptName()), 18);
smartStepInto("foo");
waitForPause();
eval("a.z").hasValue("1");
}
});
}
@Test
public void testInput() {
runPythonTest(new PyDebuggerTask("/debug", "test_input.py") {
@@ -636,109 +526,6 @@ public class PythonDebuggerTest extends PyEnvTestCase {
});
}
@Test
public void testStepOverConditionalBreakpoint() {
runPythonTest(new PyDebuggerTask("/debug", "test_stepOverCondition.py") {
@Override
public void before() {
toggleBreakpoint(getScriptName(), 1);
toggleBreakpoint(getScriptName(), 2);
XDebuggerTestUtil.setBreakpointCondition(getProject(), 2, "y == 3");
}
@Override
public void testing() throws Exception {
waitForPause();
stepOver();
waitForPause();
eval("y").hasValue("2");
}
});
}
@Test
public void testStepOverYieldFrom() {
runPythonTest(new PyDebuggerTask("/debug", "test_step_over_yield.py") {
@Override
protected void init() {
setMultiprocessDebug(true);
}
@Override
public void before() {
toggleBreakpoint(getScriptName(), 6);
}
@Override
public void testing() throws Exception {
waitForPause();
stepOver();
waitForPause();
eval("a").hasValue("42");
stepOver();
waitForPause();
eval("a").hasValue("42");
stepOver();
waitForPause();
eval("sum").hasValue("6");
resume();
}
@NotNull
@Override
public Set<String> getTags() {
return Sets.newHashSet("python34");
}
});
}
@Test
public void testSteppingFilter() {
runPythonTest(new PyDebuggerTask("/debug", "test_stepping_filter.py") {
@Override
public void before() {
toggleBreakpoint(getScriptName(), 4);
List<PySteppingFilter> filters = new ArrayList<>();
filters.add(new PySteppingFilter(true, "*/test_m?_code.py"));
final PyDebuggerSettings debuggerSettings = PyDebuggerSettings.getInstance();
debuggerSettings.setLibrariesFilterEnabled(true);
debuggerSettings.setSteppingFiltersEnabled(true);
debuggerSettings.setSteppingFilters(filters);
}
@Override
public void doFinally() {
final PyDebuggerSettings debuggerSettings = PyDebuggerSettings.getInstance();
debuggerSettings.setLibrariesFilterEnabled(false);
debuggerSettings.setSteppingFiltersEnabled(false);
}
@Override
public void testing() throws Exception {
waitForPause();
stepInto();
waitForPause();
eval("stopped_in_user_file").hasValue("True");
stepInto();
waitForPause();
eval("stopped_in_user_file").hasValue("True");
}
});
}
@Test
public void testReturnValues() {
runPythonTest(new PyDebuggerTask("/debug", "test_return_values.py") {
@@ -904,38 +691,6 @@ public class PythonDebuggerTest extends PyEnvTestCase {
});
}
@Test
public void testResumeAfterStepping() {
// This test case is important for frame evaluation debugging, because we reuse old tracing function for stepping and there were
// some problems with switching between frame evaluation and tracing
runPythonTest(new PyDebuggerTask("/debug", "test_resume_after_step.py") {
@Override
public void before() {
toggleBreakpoint(getScriptName(), 2);
toggleBreakpoint(getScriptName(), 5);
toggleBreakpoint(getScriptName(), 12);
}
@Override
public void testing() throws Exception {
waitForPause();
eval("a").hasValue("1");
stepOver();
waitForPause();
stepOver();
waitForPause();
eval("c").hasValue("3");
resume();
waitForPause();
eval("d").hasValue("4");
resume();
waitForPause();
eval("t").hasValue("1");
resume();
}
});
}
@Test
public void testAddBreakWhileRunning() {
runPythonTest(new PyDebuggerTask("/debug", "test_resume_after_step.py") {
@@ -1778,37 +1533,6 @@ public class PythonDebuggerTest extends PyEnvTestCase {
});
}
@Test
public void testStepIntoWithThreads() {
runPythonTest(new PyDebuggerTask("/debug", "test_step_into_with_threads.py") {
@Override
public void before() {
toggleBreakpoint(getFilePath(getScriptName()), 15);
toggleBreakpoint(getFilePath(getScriptName()), 17);
}
@Override
public void testing() throws Exception {
waitForPause();
stepInto();
waitForPause();
stepOver();
waitForPause();
waitForOutput("foo");
resume();
waitForOutput("bar");
waitForPause();
stepInto();
waitForPause();
stepOver();
waitForPause();
waitForOutput("baz");
resume();
waitForTerminate();
}
});
}
@Test
@StagingOn(os = TestEnv.WINDOWS)
public void testNoDebuggerRelatedStacktraceOnDebuggerStop() {