mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
Support step into target. Fixes #288
This commit is contained in:
parent
cdc975c4fb
commit
2ebc4e71b5
26 changed files with 6106 additions and 4553 deletions
|
|
@ -28,7 +28,6 @@ class PyDBAdditionalThreadInfo(object):
|
|||
'pydev_original_step_cmd',
|
||||
'pydev_step_cmd',
|
||||
'pydev_notify_kill',
|
||||
'pydev_smart_step_stop',
|
||||
'pydev_django_resolve_frame',
|
||||
'pydev_call_from_jinja2',
|
||||
'pydev_call_inside_jinja2',
|
||||
|
|
@ -44,6 +43,14 @@ class PyDBAdditionalThreadInfo(object):
|
|||
'top_level_thread_tracer_unhandled',
|
||||
'thread_tracer',
|
||||
'step_in_initial_location',
|
||||
|
||||
# Used for CMD_SMART_STEP_INTO (to know which smart step into variant to use)
|
||||
'pydev_smart_parent_offset',
|
||||
|
||||
# Used for CMD_SMART_STEP_INTO (list[_pydevd_bundle.pydevd_bytecode_utils.Variant])
|
||||
# Filled when the cmd_get_smart_step_into_variants is requested (so, this is a copy
|
||||
# of the last request for a given thread and pydev_smart_parent_offset relies on it).
|
||||
'pydev_smart_step_into_variants',
|
||||
]
|
||||
# ENDIF
|
||||
|
||||
|
|
@ -61,7 +68,6 @@ class PyDBAdditionalThreadInfo(object):
|
|||
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
|
||||
|
|
@ -77,6 +83,8 @@ class PyDBAdditionalThreadInfo(object):
|
|||
self.top_level_thread_tracer_unhandled = None
|
||||
self.thread_tracer = None
|
||||
self.step_in_initial_location = None
|
||||
self.pydev_smart_parent_offset = -1
|
||||
self.pydev_smart_step_into_variants = ()
|
||||
|
||||
def get_topmost_frame(self, thread):
|
||||
'''
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ from _pydevd_bundle.pydevd_comm import (InternalGetThreadStack, internal_get_com
|
|||
internal_get_description, internal_get_frame, internal_evaluate_expression, InternalConsoleExec,
|
||||
internal_get_variable_json, internal_change_variable, internal_change_variable_json,
|
||||
internal_evaluate_expression_json, internal_set_expression_json, internal_get_exception_details_json,
|
||||
internal_step_in_thread)
|
||||
internal_step_in_thread, internal_smart_step_into)
|
||||
from _pydevd_bundle.pydevd_comm_constants import (CMD_THREAD_SUSPEND, file_system_encoding,
|
||||
CMD_STEP_INTO_MY_CODE, CMD_STOP_ON_START)
|
||||
CMD_STEP_INTO_MY_CODE, CMD_STOP_ON_START, CMD_SMART_STEP_INTO)
|
||||
from _pydevd_bundle.pydevd_constants import (get_current_thread_id, set_protocol, get_protocol,
|
||||
HTTP_JSON_PROTOCOL, JSON_PROTOCOL, IS_PY3K, DebugInfoHolder, dict_keys, dict_items, IS_WINDOWS)
|
||||
from _pydevd_bundle.pydevd_net_command_factory_json import NetCommandFactoryJson
|
||||
|
|
@ -228,8 +228,28 @@ class PyDevdAPI(object):
|
|||
elif thread_id.startswith('__frame__:'):
|
||||
sys.stderr.write("Can't make tasklet step command: %s\n" % (thread_id,))
|
||||
|
||||
def request_smart_step_into(self, py_db, seq, thread_id, offset):
|
||||
t = pydevd_find_thread_by_id(thread_id)
|
||||
if t:
|
||||
py_db.post_method_as_internal_command(
|
||||
thread_id, internal_smart_step_into, thread_id, offset, set_additional_thread_info=set_additional_thread_info)
|
||||
elif thread_id.startswith('__frame__:'):
|
||||
sys.stderr.write("Can't set next statement in tasklet: %s\n" % (thread_id,))
|
||||
|
||||
def request_smart_step_into_by_func_name(self, py_db, seq, thread_id, line, func_name):
|
||||
# Same thing as set next, just with a different cmd id.
|
||||
self.request_set_next(py_db, seq, thread_id, CMD_SMART_STEP_INTO, None, line, func_name)
|
||||
|
||||
def request_set_next(self, py_db, seq, thread_id, set_next_cmd_id, original_filename, line, func_name):
|
||||
'''
|
||||
set_next_cmd_id may actually be one of:
|
||||
|
||||
CMD_RUN_TO_LINE
|
||||
CMD_SET_NEXT_STATEMENT
|
||||
|
||||
CMD_SMART_STEP_INTO -- note: request_smart_step_into is preferred if it's possible
|
||||
to work with bytecode offset.
|
||||
|
||||
:param Optional[str] original_filename:
|
||||
If available, the filename may be source translated, otherwise no translation will take
|
||||
place (the set next just needs the line afterwards as it executes locally, but for
|
||||
|
|
|
|||
|
|
@ -0,0 +1,300 @@
|
|||
"""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, KeyifyList
|
||||
from bisect import bisect
|
||||
|
||||
_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:
|
||||
long = int
|
||||
|
||||
try:
|
||||
_unpack_opargs = dis._unpack_opargs
|
||||
except AttributeError:
|
||||
|
||||
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 or if dis cannot be used.
|
||||
"""
|
||||
try:
|
||||
linestarts = dict(dis.findlinestarts(code))
|
||||
except Exception:
|
||||
raise RuntimeError("Unable to get smart step into candidates because dis.findlinestarts is not available.")
|
||||
|
||||
varnames = code.co_varnames
|
||||
names = code.co_names
|
||||
constants = code.co_consts
|
||||
freevars = code.co_freevars
|
||||
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()
|
||||
cmp_op = dis.cmp_op[arg]
|
||||
if cmp_op not in ('exception match', 'BAD'):
|
||||
result.append(Instruction(opname, op, arg, _COMP_OP_MAP.get(cmp_op, cmp_op), 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]
|
||||
elif opname == 'LOAD_DEREF':
|
||||
argval = freevars[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
|
||||
elif opname == 'CALL_FUNCTION_EX':
|
||||
has_keyword_args = arg & 0x01
|
||||
if has_keyword_args:
|
||||
stk.pop()
|
||||
stk.pop() # positional args
|
||||
argc = 0
|
||||
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
|
||||
|
||||
|
||||
# Note that the offset is unique within the frame (so, we can use it as the target id).
|
||||
# Also, as the offset is the instruction offset within the frame, it's possible to
|
||||
# to inspect the parent frame for frame.f_lasti to know where we actually are (as the
|
||||
# caller name may not always match the new frame name).
|
||||
Variant = namedtuple('Variant', ['name', 'is_visited', 'line', 'offset', 'call_order'])
|
||||
|
||||
|
||||
def calculate_smart_step_into_variants(frame, start_line, end_line, base=0):
|
||||
"""
|
||||
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.
|
||||
:note: it's guaranteed that the offsets appear in order.
|
||||
:raise: :py:class:`RuntimeError` if failed to parse the bytecode or if dis cannot be used.
|
||||
"""
|
||||
variants = []
|
||||
is_context_reached = False
|
||||
code = frame.f_code
|
||||
lasti = frame.f_lasti
|
||||
|
||||
call_order_cache = {}
|
||||
|
||||
for inst in _get_smart_step_into_candidates(code):
|
||||
if not isinstance(inst.argval, str):
|
||||
continue
|
||||
|
||||
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
|
||||
|
||||
call_order = call_order_cache.get(inst.argval, 0) + 1
|
||||
call_order_cache[inst.argval] = call_order
|
||||
variants.append(
|
||||
Variant(
|
||||
inst.argval, inst.offset <= lasti, inst.lineno - base, inst.offset, call_order))
|
||||
return variants
|
||||
|
||||
|
||||
def get_smart_step_into_variant_from_frame_offset(frame_f_lasti, variants):
|
||||
"""
|
||||
Given the frame.f_lasti, return the related `Variant`.
|
||||
|
||||
:note: if the offset is found before any variant available or no variants are
|
||||
available, None is returned.
|
||||
|
||||
:rtype: Variant|NoneType
|
||||
"""
|
||||
if not variants:
|
||||
return None
|
||||
|
||||
i = bisect(KeyifyList(variants, lambda entry:entry.offset), frame_f_lasti)
|
||||
|
||||
if i == 0:
|
||||
return None
|
||||
|
||||
else:
|
||||
return variants[i - 1]
|
||||
|
|
@ -63,7 +63,6 @@ each command has a format:
|
|||
* PYDB - pydevd, the python end
|
||||
'''
|
||||
|
||||
import itertools
|
||||
import linecache
|
||||
import os
|
||||
|
||||
|
|
@ -71,14 +70,14 @@ from _pydev_bundle.pydev_imports import _queue
|
|||
from _pydev_imps._pydev_saved_modules import time
|
||||
from _pydev_imps._pydev_saved_modules import threading
|
||||
from _pydev_imps._pydev_saved_modules import socket as socket_module
|
||||
from _pydevd_bundle.pydevd_constants import (DebugInfoHolder, get_thread_id, IS_WINDOWS, IS_JYTHON,
|
||||
from _pydevd_bundle.pydevd_constants import (DebugInfoHolder, IS_WINDOWS, IS_JYTHON,
|
||||
IS_PY2, IS_PY36_OR_GREATER, STATE_RUN, dict_keys, ASYNC_EVAL_TIMEOUT_SEC,
|
||||
get_global_debugger, GetGlobalDebugger, set_global_debugger, silence_warnings_decorator) # Keep for backward compatibility @UnusedImport
|
||||
from _pydev_bundle.pydev_override import overrides
|
||||
import weakref
|
||||
from _pydev_bundle._pydev_completer import extract_token_and_qualifier
|
||||
from _pydevd_bundle._debug_adapter.pydevd_schema import VariablesResponseBody, \
|
||||
SetVariableResponseBody
|
||||
SetVariableResponseBody, StepInTarget, StepInTargetsResponseBody
|
||||
from _pydevd_bundle._debug_adapter import pydevd_base_schema, pydevd_schema
|
||||
from _pydevd_bundle.pydevd_net_command import NetCommand
|
||||
from _pydevd_bundle.pydevd_xml import ExceptionOnEvaluate
|
||||
|
|
@ -95,7 +94,7 @@ except:
|
|||
from urllib.parse import quote_plus, unquote_plus # @Reimport @UnresolvedImport
|
||||
|
||||
import pydevconsole
|
||||
from _pydevd_bundle import pydevd_vars, pydevd_utils, pydevd_io, pydevd_reload
|
||||
from _pydevd_bundle import pydevd_vars, pydevd_io, pydevd_reload, pydevd_bytecode_utils
|
||||
from _pydevd_bundle import pydevd_xml
|
||||
from _pydevd_bundle import pydevd_vm_type
|
||||
import sys
|
||||
|
|
@ -614,7 +613,6 @@ def internal_reload_code(dbg, seq, module_name, filename):
|
|||
else:
|
||||
# Too much info...
|
||||
# _send_io_message(dbg, 'code reload: This usually means you are trying to reload the __main__ module (which cannot be reloaded).\n')
|
||||
from _pydevd_bundle import pydevd_reload
|
||||
for module, module_name in modules_to_reload.values():
|
||||
_send_io_message(dbg, 'code reload: Start reloading module: "' + module_name + '" ... \n')
|
||||
found_module_to_reload = True
|
||||
|
|
@ -691,9 +689,30 @@ def internal_step_in_thread(py_db, thread_id, cmd_id, set_additional_thread_info
|
|||
resume_threads('*', except_thread=thread_to_step)
|
||||
|
||||
|
||||
def internal_smart_step_into(py_db, thread_id, offset, set_additional_thread_info):
|
||||
thread_to_step = pydevd_find_thread_by_id(thread_id)
|
||||
if thread_to_step:
|
||||
info = set_additional_thread_info(thread_to_step)
|
||||
info.pydev_original_step_cmd = CMD_SMART_STEP_INTO
|
||||
info.pydev_step_cmd = CMD_SMART_STEP_INTO
|
||||
info.pydev_step_stop = None
|
||||
info.pydev_smart_parent_offset = int(offset)
|
||||
info.pydev_state = STATE_RUN
|
||||
|
||||
if py_db.stepping_resumes_all_threads:
|
||||
resume_threads('*', except_thread=thread_to_step)
|
||||
|
||||
|
||||
class InternalSetNextStatementThread(InternalThreadCommand):
|
||||
|
||||
def __init__(self, thread_id, cmd_id, line, func_name, seq=0):
|
||||
'''
|
||||
cmd_id may actually be one of:
|
||||
|
||||
CMD_RUN_TO_LINE
|
||||
CMD_SET_NEXT_STATEMENT
|
||||
CMD_SMART_STEP_INTO
|
||||
'''
|
||||
self.thread_id = thread_id
|
||||
self.cmd_id = cmd_id
|
||||
self.line = line
|
||||
|
|
@ -709,13 +728,15 @@ class InternalSetNextStatementThread(InternalThreadCommand):
|
|||
def do_it(self, dbg):
|
||||
t = pydevd_find_thread_by_id(self.thread_id)
|
||||
if t:
|
||||
t.additional_info.pydev_original_step_cmd = self.cmd_id
|
||||
t.additional_info.pydev_step_cmd = self.cmd_id
|
||||
t.additional_info.pydev_step_stop = None
|
||||
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)
|
||||
info = t.additional_info
|
||||
info.pydev_original_step_cmd = self.cmd_id
|
||||
info.pydev_step_cmd = self.cmd_id
|
||||
info.pydev_step_stop = None
|
||||
info.pydev_next_line = int(self.line)
|
||||
info.pydev_func_name = self.func_name
|
||||
info.pydev_message = str(self.seq)
|
||||
info.pydev_smart_parent_offset = -1
|
||||
info.pydev_state = STATE_RUN
|
||||
|
||||
|
||||
@silence_warnings_decorator
|
||||
|
|
@ -934,6 +955,99 @@ def internal_get_frame(dbg, seq, thread_id, frame_id):
|
|||
dbg.writer.add_command(cmd)
|
||||
|
||||
|
||||
def internal_get_smart_step_into_variants(dbg, seq, thread_id, frame_id, start_line, end_line, set_additional_thread_info):
|
||||
try:
|
||||
thread = pydevd_find_thread_by_id(thread_id)
|
||||
frame = dbg.find_frame(thread_id, frame_id)
|
||||
|
||||
if thread is None or frame is None:
|
||||
cmd = dbg.cmd_factory.make_error_message(seq, "Frame not found: %s from thread: %s" % (frame_id, thread_id))
|
||||
dbg.writer.add_command(cmd)
|
||||
return
|
||||
|
||||
variants = pydevd_bytecode_utils.calculate_smart_step_into_variants(frame, int(start_line), int(end_line))
|
||||
info = set_additional_thread_info(thread)
|
||||
|
||||
# Store the last request (may be used afterwards when stepping).
|
||||
info.pydev_smart_step_into_variants = tuple(variants)
|
||||
xml = "<xml>"
|
||||
|
||||
for variant in variants:
|
||||
xml += '<variant name="%s" isVisited="%s" line="%s" offset="%s" callOrder="%s"/>' % (
|
||||
quote(variant.name),
|
||||
str(variant.is_visited).lower(),
|
||||
variant.line,
|
||||
variant.offset,
|
||||
variant.call_order,
|
||||
)
|
||||
|
||||
xml += "</xml>"
|
||||
cmd = NetCommand(CMD_GET_SMART_STEP_INTO_VARIANTS, seq, xml)
|
||||
dbg.writer.add_command(cmd)
|
||||
except:
|
||||
# Error is expected (if `dis` module cannot be used -- i.e.: Jython).
|
||||
pydev_log.exception('Error calculating Smart Step Into Variants.')
|
||||
cmd = dbg.cmd_factory.make_error_message(
|
||||
seq, "Error getting smart step into variants for frame: %s from thread: %s"
|
||||
% (frame_id, thread_id))
|
||||
dbg.writer.add_command(cmd)
|
||||
|
||||
|
||||
def internal_get_step_in_targets_json(dbg, seq, thread_id, frame_id, request, set_additional_thread_info):
|
||||
try:
|
||||
thread = pydevd_find_thread_by_id(thread_id)
|
||||
frame = dbg.find_frame(thread_id, frame_id)
|
||||
|
||||
if thread is None or frame is None:
|
||||
body = StepInTargetsResponseBody([])
|
||||
variables_response = pydevd_base_schema.build_response(
|
||||
request,
|
||||
kwargs={
|
||||
'body': body,
|
||||
'success': False,
|
||||
'message': 'Thread to get step in targets seems to have resumed already.'
|
||||
})
|
||||
cmd = NetCommand(CMD_RETURN, 0, variables_response, is_json=True)
|
||||
dbg.writer.add_command(cmd)
|
||||
return
|
||||
|
||||
start_line = 0
|
||||
end_line = 99999999
|
||||
variants = pydevd_bytecode_utils.calculate_smart_step_into_variants(frame, start_line, end_line)
|
||||
info = set_additional_thread_info(thread)
|
||||
targets = []
|
||||
for variant in variants:
|
||||
if not variant.is_visited:
|
||||
if variant.call_order > 1:
|
||||
targets.append(StepInTarget(id=variant.offset, label='%s (call %s)' % (variant.name, variant.call_order),))
|
||||
else:
|
||||
targets.append(StepInTarget(id=variant.offset, label=variant.name))
|
||||
|
||||
if len(targets) >= 15: # Show at most 15 targets.
|
||||
break
|
||||
|
||||
# Store the last request (may be used afterwards when stepping).
|
||||
info.pydev_smart_step_into_variants = tuple(variants)
|
||||
|
||||
body = StepInTargetsResponseBody(targets=targets)
|
||||
response = pydevd_base_schema.build_response(request, kwargs={'body': body})
|
||||
cmd = NetCommand(CMD_RETURN, 0, response, is_json=True)
|
||||
dbg.writer.add_command(cmd)
|
||||
except Exception as e:
|
||||
# Error is expected (if `dis` module cannot be used -- i.e.: Jython).
|
||||
pydev_log.exception('Error calculating Smart Step Into Variants.')
|
||||
body = StepInTargetsResponseBody([])
|
||||
variables_response = pydevd_base_schema.build_response(
|
||||
request,
|
||||
kwargs={
|
||||
'body': body,
|
||||
'success': False,
|
||||
'message': str(e)
|
||||
})
|
||||
cmd = NetCommand(CMD_RETURN, 0, variables_response, is_json=True)
|
||||
dbg.writer.add_command(cmd)
|
||||
|
||||
|
||||
def internal_get_next_statement_targets(dbg, seq, thread_id, frame_id):
|
||||
''' gets the valid line numbers for use with set next statement '''
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -81,6 +81,8 @@ CMD_STEP_RETURN_MY_CODE = 160
|
|||
CMD_SET_PY_EXCEPTION_JSON = 161
|
||||
CMD_SET_PATH_MAPPING_JSON = 162
|
||||
|
||||
CMD_GET_SMART_STEP_INTO_VARIANTS = 163 # XXX: PyCharm has 160 for this (we're currently incompatible anyways).
|
||||
|
||||
CMD_REDIRECT_OUTPUT = 200
|
||||
CMD_GET_NEXT_STATEMENT_TARGETS = 201
|
||||
CMD_SET_PROJECT_ROOTS = 202
|
||||
|
|
@ -176,6 +178,7 @@ ID_TO_MEANING = {
|
|||
|
||||
'161': 'CMD_SET_PY_EXCEPTION_JSON',
|
||||
'162': 'CMD_SET_PATH_MAPPING_JSON',
|
||||
'163': 'CMD_GET_SMART_STEP_INTO_VARIANTS',
|
||||
|
||||
'200': 'CMD_REDIRECT_OUTPUT',
|
||||
'201': 'CMD_GET_NEXT_STATEMENT_TARGETS',
|
||||
|
|
|
|||
|
|
@ -743,6 +743,19 @@ class Null:
|
|||
NULL = Null()
|
||||
|
||||
|
||||
class KeyifyList(object):
|
||||
|
||||
def __init__(self, inner, key):
|
||||
self.inner = inner
|
||||
self.key = key
|
||||
|
||||
def __len__(self):
|
||||
return len(self.inner)
|
||||
|
||||
def __getitem__(self, k):
|
||||
return self.key(self.inner[k])
|
||||
|
||||
|
||||
def call_only_once(func):
|
||||
'''
|
||||
To be used as a decorator
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -20,3 +20,5 @@ cdef class PyDBAdditionalThreadInfo:
|
|||
cdef public object top_level_thread_tracer_unhandled;
|
||||
cdef public object thread_tracer;
|
||||
cdef public object step_in_initial_location;
|
||||
cdef public int pydev_smart_parent_offset;
|
||||
cdef public tuple pydev_smart_step_into_variants;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ cdef class PyDBAdditionalThreadInfo:
|
|||
# 'pydev_original_step_cmd',
|
||||
# 'pydev_step_cmd',
|
||||
# 'pydev_notify_kill',
|
||||
# 'pydev_smart_step_stop',
|
||||
# 'pydev_django_resolve_frame',
|
||||
# 'pydev_call_from_jinja2',
|
||||
# 'pydev_call_inside_jinja2',
|
||||
|
|
@ -50,6 +49,14 @@ cdef class PyDBAdditionalThreadInfo:
|
|||
# 'top_level_thread_tracer_unhandled',
|
||||
# 'thread_tracer',
|
||||
# 'step_in_initial_location',
|
||||
#
|
||||
# # Used for CMD_SMART_STEP_INTO (to know which smart step into variant to use)
|
||||
# 'pydev_smart_parent_offset',
|
||||
#
|
||||
# # Used for CMD_SMART_STEP_INTO (list[_pydevd_bundle.pydevd_bytecode_utils.Variant])
|
||||
# # Filled when the cmd_get_smart_step_into_variants is requested (so, this is a copy
|
||||
# # of the last request for a given thread and pydev_smart_parent_offset relies on it).
|
||||
# 'pydev_smart_step_into_variants',
|
||||
# ]
|
||||
# ENDIF
|
||||
|
||||
|
|
@ -67,7 +74,6 @@ cdef class PyDBAdditionalThreadInfo:
|
|||
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
|
||||
|
|
@ -83,6 +89,8 @@ cdef class PyDBAdditionalThreadInfo:
|
|||
self.top_level_thread_tracer_unhandled = None
|
||||
self.thread_tracer = None
|
||||
self.step_in_initial_location = None
|
||||
self.pydev_smart_parent_offset = -1
|
||||
self.pydev_smart_step_into_variants = ()
|
||||
|
||||
def get_topmost_frame(self, thread):
|
||||
'''
|
||||
|
|
@ -143,6 +151,7 @@ from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, just_raise
|
|||
from _pydevd_bundle.pydevd_utils import get_clsname_for_code
|
||||
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame
|
||||
from _pydevd_bundle.pydevd_comm_constants import constant_to_str
|
||||
from _pydevd_bundle.pydevd_bytecode_utils import get_smart_step_into_variant_from_frame_offset
|
||||
|
||||
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
||||
# ELSE
|
||||
|
|
@ -641,6 +650,7 @@ cdef class PyDBFrame:
|
|||
cdef bint is_exception_event;
|
||||
cdef bint has_exception_breakpoints;
|
||||
cdef bint can_skip;
|
||||
cdef bint stop;
|
||||
cdef PyDBAdditionalThreadInfo info;
|
||||
cdef int step_cmd;
|
||||
cdef int line;
|
||||
|
|
@ -649,6 +659,7 @@ cdef class PyDBFrame:
|
|||
cdef bint is_return;
|
||||
cdef bint should_stop;
|
||||
cdef dict breakpoints_for_file;
|
||||
cdef dict stop_info;
|
||||
cdef str curr_func_name;
|
||||
cdef bint exist_result;
|
||||
cdef dict frame_skips_cache;
|
||||
|
|
@ -660,6 +671,8 @@ cdef class PyDBFrame:
|
|||
cdef bint is_coroutine_or_generator;
|
||||
cdef int bp_line;
|
||||
cdef object bp;
|
||||
cdef int pydev_smart_parent_offset
|
||||
cdef tuple pydev_smart_step_into_variants
|
||||
# ELSE
|
||||
# def trace_dispatch(self, frame, event, arg):
|
||||
# ENDIF
|
||||
|
|
@ -796,8 +809,8 @@ cdef class PyDBFrame:
|
|||
# to make a step in or step over at that location).
|
||||
# Note: this is especially troublesome when we're skipping code with the
|
||||
# @DontTrace comment.
|
||||
if stop_frame is frame and is_return and step_cmd in (108, 109, 159, 160):
|
||||
if step_cmd in (108, 109):
|
||||
if stop_frame is frame and is_return and step_cmd in (108, 109, 159, 160, 128):
|
||||
if step_cmd in (108, 109, 128):
|
||||
info.pydev_step_cmd = 107
|
||||
else:
|
||||
info.pydev_step_cmd = 144
|
||||
|
|
@ -845,6 +858,9 @@ cdef class PyDBFrame:
|
|||
elif step_cmd in (108, 109, 159, 160) and stop_frame is not frame:
|
||||
can_skip = True
|
||||
|
||||
elif step_cmd == 128 and stop_frame is not frame and stop_frame is not frame.f_back:
|
||||
can_skip = True
|
||||
|
||||
elif step_cmd == 144:
|
||||
if (
|
||||
main_debugger.apply_files_filter(frame, frame.f_code.co_filename, True)
|
||||
|
|
@ -994,7 +1010,7 @@ cdef class PyDBFrame:
|
|||
|
||||
if main_debugger.show_return_values:
|
||||
if is_return and (
|
||||
(info.pydev_step_cmd in (108, 159) and (frame.f_back is stop_frame)) or
|
||||
(info.pydev_step_cmd in (108, 159, 128) and (frame.f_back is stop_frame)) or
|
||||
(info.pydev_step_cmd in (109, 160) and (frame is stop_frame)) or
|
||||
(info.pydev_step_cmd in (107, 206)) or
|
||||
(
|
||||
|
|
@ -1111,19 +1127,32 @@ cdef class PyDBFrame:
|
|||
|
||||
elif step_cmd == 128:
|
||||
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 stop_frame is frame and is_return:
|
||||
# We're exiting the smart step into initial frame (so, we probably didn't find our target).
|
||||
stop = True
|
||||
elif stop_frame is frame.f_back and is_line:
|
||||
pydev_smart_parent_offset = info.pydev_smart_parent_offset
|
||||
pydev_smart_step_into_variants = info.pydev_smart_step_into_variants
|
||||
if pydev_smart_parent_offset >= 0 and pydev_smart_step_into_variants:
|
||||
# Preferred mode (when the smart step into variants are available
|
||||
# and the offset is set).
|
||||
stop = get_smart_step_into_variant_from_frame_offset(frame.f_back.f_lasti, pydev_smart_step_into_variants) is \
|
||||
get_smart_step_into_variant_from_frame_offset(pydev_smart_parent_offset, pydev_smart_step_into_variants)
|
||||
|
||||
if is_line or is_exception_event:
|
||||
curr_func_name = frame.f_code.co_name
|
||||
else:
|
||||
# Only the name/line is available, so, check that.
|
||||
curr_func_name = frame.f_code.co_name
|
||||
|
||||
# global context is set with an empty name
|
||||
if curr_func_name in ('?', '<module>') or curr_func_name is None:
|
||||
curr_func_name = ''
|
||||
# global context is set with an empty name
|
||||
if curr_func_name in ('?', '<module>') or curr_func_name is None:
|
||||
curr_func_name = ''
|
||||
if curr_func_name == info.pydev_func_name and stop_frame.f_lineno == info.pydev_next_line:
|
||||
stop = True
|
||||
|
||||
if curr_func_name == info.pydev_func_name:
|
||||
stop = True
|
||||
if not stop:
|
||||
# In smart step into, if we didn't hit it in this frame once, that'll
|
||||
# not be the case next time either, so, disable tracing for this frame.
|
||||
return None if is_call else NO_FTRACE
|
||||
|
||||
elif step_cmd in (109, 160):
|
||||
stop = is_return and stop_frame is frame
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ DONT_TRACE = {
|
|||
'pydevd_api.py': PYDEV_FILE,
|
||||
'pydevd_base_schema.py': PYDEV_FILE,
|
||||
'pydevd_breakpoints.py': PYDEV_FILE,
|
||||
'pydevd_bytecode_utils.py': PYDEV_FILE,
|
||||
'pydevd_code_to_source.py': PYDEV_FILE,
|
||||
'pydevd_collect_bytecode_info.py': PYDEV_FILE,
|
||||
'pydevd_comm.py': PYDEV_FILE,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, just_raise
|
|||
from _pydevd_bundle.pydevd_utils import get_clsname_for_code
|
||||
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame
|
||||
from _pydevd_bundle.pydevd_comm_constants import constant_to_str
|
||||
from _pydevd_bundle.pydevd_bytecode_utils import get_smart_step_into_variant_from_frame_offset
|
||||
|
||||
# IFDEF CYTHON
|
||||
# cython_inline_constant: CMD_STEP_INTO = 107
|
||||
|
|
@ -520,6 +521,7 @@ class PyDBFrame:
|
|||
# cdef bint is_exception_event;
|
||||
# cdef bint has_exception_breakpoints;
|
||||
# cdef bint can_skip;
|
||||
# cdef bint stop;
|
||||
# cdef PyDBAdditionalThreadInfo info;
|
||||
# cdef int step_cmd;
|
||||
# cdef int line;
|
||||
|
|
@ -528,6 +530,7 @@ class PyDBFrame:
|
|||
# cdef bint is_return;
|
||||
# cdef bint should_stop;
|
||||
# cdef dict breakpoints_for_file;
|
||||
# cdef dict stop_info;
|
||||
# cdef str curr_func_name;
|
||||
# cdef bint exist_result;
|
||||
# cdef dict frame_skips_cache;
|
||||
|
|
@ -539,6 +542,8 @@ class PyDBFrame:
|
|||
# cdef bint is_coroutine_or_generator;
|
||||
# cdef int bp_line;
|
||||
# cdef object bp;
|
||||
# cdef int pydev_smart_parent_offset
|
||||
# cdef tuple pydev_smart_step_into_variants
|
||||
# ELSE
|
||||
def trace_dispatch(self, frame, event, arg):
|
||||
# ENDIF
|
||||
|
|
@ -675,8 +680,8 @@ class PyDBFrame:
|
|||
# to make a step in or step over at that location).
|
||||
# Note: this is especially troublesome when we're skipping code with the
|
||||
# @DontTrace comment.
|
||||
if stop_frame is frame and is_return and step_cmd in (CMD_STEP_OVER, CMD_STEP_RETURN, CMD_STEP_OVER_MY_CODE, CMD_STEP_RETURN_MY_CODE):
|
||||
if step_cmd in (CMD_STEP_OVER, CMD_STEP_RETURN):
|
||||
if stop_frame is frame and is_return and step_cmd in (CMD_STEP_OVER, CMD_STEP_RETURN, CMD_STEP_OVER_MY_CODE, CMD_STEP_RETURN_MY_CODE, CMD_SMART_STEP_INTO):
|
||||
if step_cmd in (CMD_STEP_OVER, CMD_STEP_RETURN, CMD_SMART_STEP_INTO):
|
||||
info.pydev_step_cmd = CMD_STEP_INTO
|
||||
else:
|
||||
info.pydev_step_cmd = CMD_STEP_INTO_MY_CODE
|
||||
|
|
@ -724,6 +729,9 @@ class PyDBFrame:
|
|||
elif step_cmd in (CMD_STEP_OVER, CMD_STEP_RETURN, CMD_STEP_OVER_MY_CODE, CMD_STEP_RETURN_MY_CODE) and stop_frame is not frame:
|
||||
can_skip = True
|
||||
|
||||
elif step_cmd == CMD_SMART_STEP_INTO and stop_frame is not frame and stop_frame is not frame.f_back:
|
||||
can_skip = True
|
||||
|
||||
elif step_cmd == CMD_STEP_INTO_MY_CODE:
|
||||
if (
|
||||
main_debugger.apply_files_filter(frame, frame.f_code.co_filename, True)
|
||||
|
|
@ -873,7 +881,7 @@ class PyDBFrame:
|
|||
|
||||
if main_debugger.show_return_values:
|
||||
if is_return and (
|
||||
(info.pydev_step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE) and (frame.f_back is stop_frame)) or
|
||||
(info.pydev_step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE, CMD_SMART_STEP_INTO) and (frame.f_back is stop_frame)) or
|
||||
(info.pydev_step_cmd in (CMD_STEP_RETURN, CMD_STEP_RETURN_MY_CODE) and (frame is stop_frame)) or
|
||||
(info.pydev_step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_COROUTINE)) or
|
||||
(
|
||||
|
|
@ -990,19 +998,32 @@ class PyDBFrame:
|
|||
|
||||
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 stop_frame is frame and is_return:
|
||||
# We're exiting the smart step into initial frame (so, we probably didn't find our target).
|
||||
stop = True
|
||||
elif stop_frame is frame.f_back and is_line:
|
||||
pydev_smart_parent_offset = info.pydev_smart_parent_offset
|
||||
pydev_smart_step_into_variants = info.pydev_smart_step_into_variants
|
||||
if pydev_smart_parent_offset >= 0 and pydev_smart_step_into_variants:
|
||||
# Preferred mode (when the smart step into variants are available
|
||||
# and the offset is set).
|
||||
stop = get_smart_step_into_variant_from_frame_offset(frame.f_back.f_lasti, pydev_smart_step_into_variants) is \
|
||||
get_smart_step_into_variant_from_frame_offset(pydev_smart_parent_offset, pydev_smart_step_into_variants)
|
||||
|
||||
if is_line or is_exception_event:
|
||||
curr_func_name = frame.f_code.co_name
|
||||
else:
|
||||
# Only the name/line is available, so, check that.
|
||||
curr_func_name = frame.f_code.co_name
|
||||
|
||||
# global context is set with an empty name
|
||||
if curr_func_name in ('?', '<module>') or curr_func_name is None:
|
||||
curr_func_name = ''
|
||||
# global context is set with an empty name
|
||||
if curr_func_name in ('?', '<module>') or curr_func_name is None:
|
||||
curr_func_name = ''
|
||||
if curr_func_name == info.pydev_func_name and stop_frame.f_lineno == info.pydev_next_line:
|
||||
stop = True
|
||||
|
||||
if curr_func_name == info.pydev_func_name:
|
||||
stop = True
|
||||
if not stop:
|
||||
# In smart step into, if we didn't hit it in this frame once, that'll
|
||||
# not be the case next time either, so, disable tracing for this frame.
|
||||
return None if is_call else NO_FTRACE
|
||||
|
||||
elif step_cmd in (CMD_STEP_RETURN, CMD_STEP_RETURN_MY_CODE):
|
||||
stop = is_return and stop_frame is frame
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from _pydevd_bundle.pydevd_comm_constants import CMD_THREAD_CREATE, CMD_RETURN,
|
|||
CMD_STEP_RETURN, CMD_STEP_CAUGHT_EXCEPTION, CMD_ADD_EXCEPTION_BREAK, CMD_SET_BREAK, \
|
||||
CMD_SET_NEXT_STATEMENT, CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION, \
|
||||
CMD_THREAD_RESUME_SINGLE_NOTIFICATION, CMD_THREAD_KILL, CMD_STOP_ON_START, CMD_INPUT_REQUESTED, \
|
||||
CMD_EXIT, CMD_STEP_INTO_COROUTINE, CMD_STEP_RETURN_MY_CODE
|
||||
CMD_EXIT, CMD_STEP_INTO_COROUTINE, CMD_STEP_RETURN_MY_CODE, CMD_SMART_STEP_INTO
|
||||
from _pydevd_bundle.pydevd_constants import get_thread_id, dict_values, ForkSafeLock
|
||||
from _pydevd_bundle.pydevd_net_command import NetCommand, NULL_NET_COMMAND
|
||||
from _pydevd_bundle.pydevd_net_command_factory_xml import NetCommandFactory
|
||||
|
|
@ -301,6 +301,7 @@ class NetCommandFactoryJson(NetCommandFactory):
|
|||
CMD_STEP_INTO_MY_CODE,
|
||||
CMD_STOP_ON_START,
|
||||
CMD_STEP_INTO_COROUTINE,
|
||||
CMD_SMART_STEP_INTO,
|
||||
])
|
||||
_EXCEPTION_REASONS = set([
|
||||
CMD_STEP_CAUGHT_EXCEPTION,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_i
|
|||
from _pydevd_bundle.pydevd_breakpoints import get_exception_class
|
||||
from _pydevd_bundle.pydevd_comm import (
|
||||
InternalEvaluateConsoleExpression, InternalConsoleGetCompletions, InternalRunCustomOperation,
|
||||
internal_get_next_statement_targets)
|
||||
internal_get_next_statement_targets, internal_get_smart_step_into_variants)
|
||||
from _pydevd_bundle.pydevd_constants import IS_PY3K, NEXT_VALUE_SEPARATOR, IS_WINDOWS, IS_PY2, NULL
|
||||
from _pydevd_bundle.pydevd_comm_constants import ID_TO_MEANING, CMD_EXEC_EXPRESSION, CMD_AUTHENTICATE
|
||||
from _pydevd_bundle.pydevd_api import PyDevdAPI
|
||||
|
|
@ -167,7 +167,19 @@ class _PyDevCommandProcessor(object):
|
|||
|
||||
cmd_run_to_line = _cmd_set_next
|
||||
cmd_set_next_statement = _cmd_set_next
|
||||
cmd_smart_step_into = _cmd_set_next
|
||||
|
||||
def cmd_smart_step_into(self, py_db, cmd_id, seq, text):
|
||||
thread_id, line_or_bytecode_offset, func_name = text.split('\t', 2)
|
||||
if line_or_bytecode_offset.startswith('offset='):
|
||||
# In this case we request the smart step into to stop given the parent frame
|
||||
# and the location of the parent frame bytecode offset and not just the func_name
|
||||
# (this implies that `CMD_GET_SMART_STEP_INTO_VARIANTS` was previously used
|
||||
# to know what are the valid stop points).
|
||||
offset = int(line_or_bytecode_offset[len('offset='):])
|
||||
return self.api.request_smart_step_into(py_db, seq, thread_id, offset)
|
||||
else:
|
||||
# If the offset wasn't passed, just use the line/func_name to do the stop.
|
||||
return self.api.request_smart_step_into_by_func_name(py_db, seq, thread_id, line_or_bytecode_offset, func_name)
|
||||
|
||||
def cmd_reload_code(self, py_db, cmd_id, seq, text):
|
||||
text = text.strip()
|
||||
|
|
@ -672,6 +684,12 @@ class _PyDevCommandProcessor(object):
|
|||
py_db.post_method_as_internal_command(
|
||||
thread_id, internal_get_next_statement_targets, seq, thread_id, frame_id)
|
||||
|
||||
def cmd_get_smart_step_into_variants(self, py_db, cmd_id, seq, text):
|
||||
thread_id, frame_id, start_line, end_line = text.split('\t', 3)
|
||||
|
||||
py_db.post_method_as_internal_command(
|
||||
thread_id, internal_get_smart_step_into_variants, seq, thread_id, frame_id, start_line, end_line, set_additional_thread_info=set_additional_thread_info)
|
||||
|
||||
def cmd_set_project_roots(self, py_db, cmd_id, seq, text):
|
||||
self.api.set_project_roots(py_db, text.split(u'\t'))
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ from _pydevd_bundle._debug_adapter.pydevd_schema import (
|
|||
ProcessEvent, Scope, ScopesResponseBody, SetExpressionResponseBody,
|
||||
SetVariableResponseBody, SourceBreakpoint, SourceResponseBody,
|
||||
VariablesResponseBody, SetBreakpointsResponseBody, Response,
|
||||
Capabilities, PydevdAuthorizeRequest, Request)
|
||||
Capabilities, PydevdAuthorizeRequest, Request, StepInTargetsResponse, StepInTarget,
|
||||
StepInTargetsResponseBody)
|
||||
from _pydevd_bundle.pydevd_api import PyDevdAPI
|
||||
from _pydevd_bundle.pydevd_breakpoints import get_exception_class
|
||||
from _pydevd_bundle.pydevd_comm_constants import (
|
||||
|
|
@ -30,6 +31,9 @@ from _pydevd_bundle.pydevd_constants import (PY_IMPL_NAME, DebugInfoHolder, PY_V
|
|||
PY_IMPL_VERSION_STR, IS_64BIT_PROCESS)
|
||||
from _pydevd_bundle.pydevd_trace_dispatch import USING_CYTHON
|
||||
from _pydevd_frame_eval.pydevd_frame_eval_main import USING_FRAME_EVAL
|
||||
from _pydevd_bundle.pydevd_comm import internal_get_step_in_targets_json
|
||||
from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info
|
||||
from _pydevd_bundle.pydevd_thread_lifecycle import pydevd_find_thread_by_id
|
||||
|
||||
|
||||
def _convert_rules_to_exclude_filters(rules, on_error):
|
||||
|
|
@ -229,7 +233,7 @@ class PyDevJsonCommandProcessor(object):
|
|||
supportsFunctionBreakpoints=False,
|
||||
supportsStepBack=False,
|
||||
supportsRestartFrame=False,
|
||||
supportsStepInTargetsRequest=False,
|
||||
supportsStepInTargetsRequest=True,
|
||||
supportsRestartRequest=False,
|
||||
supportsLoadedSourcesRequest=False,
|
||||
supportsTerminateThreadsRequest=False,
|
||||
|
|
@ -541,16 +545,67 @@ class PyDevJsonCommandProcessor(object):
|
|||
arguments = request.arguments # : :type arguments: StepInArguments
|
||||
thread_id = arguments.threadId
|
||||
|
||||
if py_db.get_use_libraries_filter():
|
||||
step_cmd_id = CMD_STEP_INTO_MY_CODE
|
||||
else:
|
||||
step_cmd_id = CMD_STEP_INTO
|
||||
target_id = arguments.targetId
|
||||
if target_id is not None:
|
||||
thread = pydevd_find_thread_by_id(thread_id)
|
||||
info = set_additional_thread_info(thread)
|
||||
pydev_smart_step_into_variants = info.pydev_smart_step_into_variants
|
||||
if not pydev_smart_step_into_variants:
|
||||
variables_response = pydevd_base_schema.build_response(
|
||||
request,
|
||||
kwargs={
|
||||
'success': False,
|
||||
'message': 'Unable to step into target (no targets are saved in the thread info).'
|
||||
})
|
||||
return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)
|
||||
|
||||
self.api.request_step(py_db, thread_id, step_cmd_id)
|
||||
for variant in pydev_smart_step_into_variants:
|
||||
if variant.offset == target_id:
|
||||
self.api.request_smart_step_into(py_db, request.seq, thread_id, variant.offset)
|
||||
break
|
||||
else:
|
||||
variables_response = pydevd_base_schema.build_response(
|
||||
request,
|
||||
kwargs={
|
||||
'success': False,
|
||||
'message': 'Unable to find step into target %s. Available targets: %s' % (
|
||||
target_id, [variant.offset for variant in pydev_smart_step_into_variants])
|
||||
})
|
||||
return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)
|
||||
|
||||
else:
|
||||
if py_db.get_use_libraries_filter():
|
||||
step_cmd_id = CMD_STEP_INTO_MY_CODE
|
||||
else:
|
||||
step_cmd_id = CMD_STEP_INTO
|
||||
|
||||
self.api.request_step(py_db, thread_id, step_cmd_id)
|
||||
|
||||
response = pydevd_base_schema.build_response(request)
|
||||
return NetCommand(CMD_RETURN, 0, response, is_json=True)
|
||||
|
||||
def on_stepintargets_request(self, py_db, request):
|
||||
'''
|
||||
:param StepInTargetsRequest request:
|
||||
'''
|
||||
frame_id = request.arguments.frameId
|
||||
thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
|
||||
frame_id)
|
||||
|
||||
if thread_id is None:
|
||||
body = StepInTargetsResponseBody([])
|
||||
variables_response = pydevd_base_schema.build_response(
|
||||
request,
|
||||
kwargs={
|
||||
'body': body,
|
||||
'success': False,
|
||||
'message': 'Unable to get thread_id from frame_id (thread to get step in targets seems to have resumed already).'
|
||||
})
|
||||
return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)
|
||||
|
||||
py_db.post_method_as_internal_command(
|
||||
thread_id, internal_get_step_in_targets_json, request.seq, thread_id, frame_id, request, set_additional_thread_info)
|
||||
|
||||
def on_stepout_request(self, py_db, request):
|
||||
'''
|
||||
:param StepOutRequest request:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import bisect
|
||||
from _pydevd_bundle.pydevd_constants import dict_items, NULL
|
||||
from _pydevd_bundle.pydevd_constants import dict_items, NULL, KeyifyList
|
||||
import pydevd_file_utils
|
||||
|
||||
|
||||
|
|
@ -34,19 +34,6 @@ class SourceMappingEntry(object):
|
|||
__repr__ = __str__
|
||||
|
||||
|
||||
class _KeyifyList(object):
|
||||
|
||||
def __init__(self, inner, key):
|
||||
self.inner = inner
|
||||
self.key = key
|
||||
|
||||
def __len__(self):
|
||||
return len(self.inner)
|
||||
|
||||
def __getitem__(self, k):
|
||||
return self.key(self.inner[k])
|
||||
|
||||
|
||||
class SourceMapping(object):
|
||||
|
||||
def __init__(self, on_source_mapping_changed=NULL):
|
||||
|
|
@ -141,14 +128,14 @@ class SourceMapping(object):
|
|||
mappings = self._mappings_to_server.get(absolute_normalized_filename)
|
||||
if mappings:
|
||||
|
||||
i = bisect.bisect(_KeyifyList(mappings, lambda entry:entry.line), lineno)
|
||||
i = bisect.bisect(KeyifyList(mappings, lambda entry:entry.line), lineno)
|
||||
if i >= len(mappings):
|
||||
i -= 1
|
||||
|
||||
if i == 0:
|
||||
entry = mappings[i]
|
||||
|
||||
elif i > 0:
|
||||
else:
|
||||
entry = mappings[i - 1]
|
||||
|
||||
if not entry.contains_line(lineno):
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -500,6 +500,7 @@ cdef PyObject * get_bytecode_while_frame_eval(PyFrameObject * frame_obj, int exc
|
|||
cdef int CMD_STEP_OVER_MY_CODE = 159
|
||||
cdef int CMD_STEP_INTO_MY_CODE = 144
|
||||
cdef int CMD_STEP_INTO_COROUTINE = 206
|
||||
cdef int CMD_SMART_STEP_INTO = 128
|
||||
cdef bint can_skip = True
|
||||
try:
|
||||
thread_info = _thread_local_info.thread_info
|
||||
|
|
@ -540,7 +541,7 @@ cdef PyObject * get_bytecode_while_frame_eval(PyFrameObject * frame_obj, int exc
|
|||
if apply_to_global:
|
||||
thread_info.thread_trace_func = trace_func
|
||||
|
||||
if additional_info.pydev_step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_INTO_COROUTINE) or \
|
||||
if additional_info.pydev_step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_INTO_COROUTINE, CMD_SMART_STEP_INTO) or \
|
||||
main_debugger.break_on_caught_exceptions or \
|
||||
main_debugger.break_on_user_uncaught_exceptions or \
|
||||
main_debugger.has_plugin_exception_breaks or \
|
||||
|
|
|
|||
|
|
@ -1974,21 +1974,13 @@ class PyDB(object):
|
|||
# When in a coroutine we switch to CMD_STEP_INTO_COROUTINE.
|
||||
info.pydev_step_cmd = CMD_STEP_INTO_COROUTINE
|
||||
info.pydev_step_stop = frame
|
||||
info.pydev_smart_step_stop = None
|
||||
self.set_trace_for_frame_and_parents(frame)
|
||||
else:
|
||||
info.pydev_step_stop = None
|
||||
info.pydev_smart_step_stop = None
|
||||
self.set_trace_for_frame_and_parents(frame)
|
||||
|
||||
elif info.pydev_step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE):
|
||||
elif info.pydev_step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE, CMD_SMART_STEP_INTO):
|
||||
info.pydev_step_stop = frame
|
||||
info.pydev_smart_step_stop = None
|
||||
self.set_trace_for_frame_and_parents(frame)
|
||||
|
||||
elif info.pydev_step_cmd == CMD_SMART_STEP_INTO:
|
||||
info.pydev_step_stop = None
|
||||
info.pydev_smart_step_stop = frame
|
||||
self.set_trace_for_frame_and_parents(frame)
|
||||
|
||||
elif info.pydev_step_cmd == CMD_RUN_TO_LINE or info.pydev_step_cmd == CMD_SET_NEXT_STATEMENT:
|
||||
|
|
|
|||
|
|
@ -82,6 +82,9 @@ CMD_STEP_OVER_MY_CODE = 159
|
|||
CMD_STEP_RETURN_MY_CODE = 160
|
||||
|
||||
CMD_SET_PY_EXCEPTION = 161
|
||||
CMD_SET_PATH_MAPPING_JSON = 162
|
||||
|
||||
CMD_GET_SMART_STEP_INTO_VARIANTS = 163 # XXX: PyCharm has 160 for this (we're currently incompatible anyways).
|
||||
|
||||
CMD_REDIRECT_OUTPUT = 200
|
||||
CMD_GET_NEXT_STATEMENT_TARGETS = 201
|
||||
|
|
@ -1291,6 +1294,9 @@ class AbstractWriterThread(threading.Thread):
|
|||
def write_set_next_statement(self, thread_id, line, func_name):
|
||||
self.write("%s\t%s\t%s\t%s\t%s" % (CMD_SET_NEXT_STATEMENT, self.next_seq(), thread_id, line, func_name,))
|
||||
|
||||
def write_smart_step_into(self, thread_id, line, func_name):
|
||||
self.write("%s\t%s\t%s\t%s\t%s" % (CMD_SMART_STEP_INTO, self.next_seq(), thread_id, line, func_name,))
|
||||
|
||||
def write_debug_console_expression(self, locator):
|
||||
self.write("%s\t%s\t%s" % (CMD_EVALUATE_CONSOLE_EXPRESSION, self.next_seq(), locator))
|
||||
|
||||
|
|
@ -1381,6 +1387,14 @@ class AbstractWriterThread(threading.Thread):
|
|||
return frame_names
|
||||
return [msg.thread.frame['name']]
|
||||
|
||||
def get_step_into_variants(self, thread_id, frame_id, start_line, end_line):
|
||||
self.write("%s\t%s\t%s\t%s\t%s\t%s" % (CMD_GET_SMART_STEP_INTO_VARIANTS, self.next_seq(), thread_id, frame_id, start_line, end_line))
|
||||
msg = self.wait_for_message(CMD_GET_SMART_STEP_INTO_VARIANTS)
|
||||
if msg.variant:
|
||||
variant_info = [(variant['name'], variant['isVisited'], variant['line'], variant['callOrder'], variant['offset']) for variant in msg.variant]
|
||||
return variant_info
|
||||
return []
|
||||
|
||||
def wait_for_thread_join(self, main_thread_id):
|
||||
|
||||
def condition():
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
def bar():
|
||||
print('on bar mark')
|
||||
|
||||
|
||||
def call_outer(*args):
|
||||
print('on outer mark')
|
||||
|
||||
|
||||
def foo(*args):
|
||||
print('on foo mark')
|
||||
|
||||
|
||||
def main():
|
||||
call_outer(foo(bar())) # break here
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
print('TEST SUCEEDED')
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
|
||||
def foo(arg):
|
||||
print('on foo mark', arg)
|
||||
return arg + 1
|
||||
|
||||
|
||||
def main():
|
||||
# Note that we have multiple foo calls and we have to differentiate and stop at the
|
||||
# proper one.
|
||||
foo(foo(foo(foo(1)))) # break here
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
print('TEST SUCEEDED')
|
||||
|
|
@ -17,7 +17,8 @@ from tests_python.debugger_unittest import (CMD_SET_PROPERTY_TRACE, REASON_CAUGH
|
|||
IS_APPVEYOR, wait_for_condition, CMD_GET_FRAME, CMD_GET_BREAKPOINT_EXCEPTION,
|
||||
CMD_THREAD_SUSPEND, CMD_STEP_OVER, REASON_STEP_OVER, CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION,
|
||||
CMD_THREAD_RESUME_SINGLE_NOTIFICATION, REASON_STEP_RETURN, REASON_STEP_RETURN_MY_CODE,
|
||||
REASON_STEP_OVER_MY_CODE, REASON_STEP_INTO, CMD_THREAD_KILL, IS_PYPY, REASON_STOP_ON_START)
|
||||
REASON_STEP_OVER_MY_CODE, REASON_STEP_INTO, CMD_THREAD_KILL, IS_PYPY, REASON_STOP_ON_START,
|
||||
CMD_SMART_STEP_INTO)
|
||||
from _pydevd_bundle.pydevd_constants import IS_WINDOWS, IS_PY38_OR_GREATER, \
|
||||
IS_MAC
|
||||
from _pydevd_bundle.pydevd_comm_constants import CMD_RELOAD_CODE, CMD_INPUT_REQUESTED
|
||||
|
|
@ -3478,6 +3479,55 @@ def test_step_return_my_code(case_setup):
|
|||
writer.finished_ok = True
|
||||
|
||||
|
||||
def test_smart_step_into_case1(case_setup):
|
||||
with case_setup.test_file('_debugger_case_smart_step_into.py') as writer:
|
||||
line = writer.get_line_index_with_content('break here')
|
||||
writer.write_add_breakpoint(line)
|
||||
writer.write_make_initial_run()
|
||||
hit = writer.wait_for_breakpoint_hit(line=line)
|
||||
|
||||
found = writer.get_step_into_variants(hit.thread_id, hit.frame_id, line, line)
|
||||
|
||||
# Remove the offset to compare (as it changes for each python version)
|
||||
assert [x[:-1] for x in found] == [
|
||||
('bar', 'false', '14', '1'), ('foo', 'false', '14', '1'), ('call_outer', 'false', '14', '1')]
|
||||
|
||||
# Note: this is just using the name, not really taking using the context.
|
||||
writer.write_smart_step_into(hit.thread_id, line, 'foo')
|
||||
hit = writer.wait_for_breakpoint_hit(reason=CMD_SMART_STEP_INTO)
|
||||
assert hit.line == writer.get_line_index_with_content('on foo mark')
|
||||
|
||||
writer.write_run_thread(hit.thread_id)
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
def test_smart_step_into_case2(case_setup):
|
||||
with case_setup.test_file('_debugger_case_smart_step_into2.py') as writer:
|
||||
line = writer.get_line_index_with_content('break here')
|
||||
writer.write_add_breakpoint(line)
|
||||
writer.write_make_initial_run()
|
||||
hit = writer.wait_for_breakpoint_hit(line=line)
|
||||
|
||||
found = writer.get_step_into_variants(hit.thread_id, hit.frame_id, line, line)
|
||||
|
||||
# Note: we have multiple 'foo' calls, so, we have to differentiate to
|
||||
# know in which one we want to stop.
|
||||
writer.write_smart_step_into(hit.thread_id, 'offset=' + found[2][-1], 'foo')
|
||||
hit = writer.wait_for_breakpoint_hit(reason=CMD_SMART_STEP_INTO)
|
||||
assert hit.line == writer.get_line_index_with_content('on foo mark')
|
||||
|
||||
writer.write_get_frame(hit.thread_id, hit.frame_id)
|
||||
writer.wait_for_var([
|
||||
(
|
||||
'<var name="arg" type="int" qualifier="__builtin__" value="int: 3"',
|
||||
'<var name="arg" type="int" qualifier="builtins" value="int: 3"',
|
||||
)
|
||||
])
|
||||
|
||||
writer.write_run_thread(hit.thread_id)
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
def test_step_over_my_code(case_setup):
|
||||
with case_setup.test_file('my_code/my_code.py') as writer:
|
||||
writer.write_set_project_roots([debugger_unittest._get_debugger_test_file('my_code')])
|
||||
|
|
|
|||
|
|
@ -342,8 +342,8 @@ class JsonFacade(object):
|
|||
pause_response = self.wait_for_response(pause_request)
|
||||
assert pause_response.success
|
||||
|
||||
def write_step_in(self, thread_id):
|
||||
arguments = pydevd_schema.StepInArguments(threadId=thread_id)
|
||||
def write_step_in(self, thread_id, target_id=None):
|
||||
arguments = pydevd_schema.StepInArguments(threadId=thread_id, targetId=target_id)
|
||||
self.wait_for_response(self.write_request(pydevd_schema.StepInRequest(arguments)))
|
||||
|
||||
def write_step_next(self, thread_id, wait_for_response=True):
|
||||
|
|
@ -386,6 +386,19 @@ class JsonFacade(object):
|
|||
|
||||
return name_to_scopes
|
||||
|
||||
def get_step_in_targets(self, frame_id):
|
||||
request = self.write_request(pydevd_schema.StepInTargetsRequest(
|
||||
pydevd_schema.StepInTargetsArguments(frame_id)))
|
||||
|
||||
# : :type response: StepInTargetsResponse
|
||||
response = self.wait_for_response(request)
|
||||
|
||||
# : :type body: StepInTargetsResponseBody
|
||||
body = response.body
|
||||
targets = body.targets
|
||||
# : :type targets: List[StepInTarget]
|
||||
return targets
|
||||
|
||||
def get_name_to_var(self, variables_reference):
|
||||
variables_response = self.get_variables_response(variables_reference)
|
||||
return dict((variable['name'], pydevd_schema.Variable(**variable)) for variable in variables_response.body.variables)
|
||||
|
|
@ -5433,6 +5446,54 @@ def do_something():
|
|||
writer.finished_ok = True
|
||||
|
||||
|
||||
def test_step_into_target_basic(case_setup):
|
||||
with case_setup.test_file('_debugger_case_smart_step_into.py') as writer:
|
||||
json_facade = JsonFacade(writer)
|
||||
|
||||
bp = writer.get_line_index_with_content('break here')
|
||||
json_facade.write_set_breakpoints([bp])
|
||||
json_facade.write_make_initial_run()
|
||||
|
||||
# At this point we know that 'do_something' was called at least once.
|
||||
hit = json_facade.wait_for_thread_stopped(line=bp)
|
||||
|
||||
# : :type step_in_targets: List[StepInTarget]
|
||||
step_in_targets = json_facade.get_step_in_targets(hit.frame_id)
|
||||
label_to_id = dict((target['label'], target['id']) for target in step_in_targets)
|
||||
assert set(label_to_id.keys()) == {'bar', 'foo', 'call_outer'}
|
||||
json_facade.write_step_in(hit.thread_id, target_id=label_to_id['foo'])
|
||||
|
||||
on_foo_mark_line = writer.get_line_index_with_content('on foo mark')
|
||||
hit = json_facade.wait_for_thread_stopped(reason='step', line=on_foo_mark_line)
|
||||
json_facade.write_continue()
|
||||
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
def test_step_into_target_multiple(case_setup):
|
||||
with case_setup.test_file('_debugger_case_smart_step_into2.py') as writer:
|
||||
json_facade = JsonFacade(writer)
|
||||
|
||||
bp = writer.get_line_index_with_content('break here')
|
||||
json_facade.write_set_breakpoints([bp])
|
||||
json_facade.write_make_initial_run()
|
||||
|
||||
# At this point we know that 'do_something' was called at least once.
|
||||
hit = json_facade.wait_for_thread_stopped(line=bp)
|
||||
|
||||
# : :type step_in_targets: List[StepInTarget]
|
||||
step_in_targets = json_facade.get_step_in_targets(hit.frame_id)
|
||||
label_to_id = dict((target['label'], target['id']) for target in step_in_targets)
|
||||
assert set(label_to_id.keys()) == {'foo', 'foo (call 2)', 'foo (call 3)', 'foo (call 4)'}
|
||||
json_facade.write_step_in(hit.thread_id, target_id=label_to_id['foo (call 2)'])
|
||||
|
||||
on_foo_mark_line = writer.get_line_index_with_content('on foo mark')
|
||||
hit = json_facade.wait_for_thread_stopped(reason='step', line=on_foo_mark_line)
|
||||
json_facade.write_continue()
|
||||
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main(['-k', 'test_case_skipping_filters', '-s'])
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
import sys
|
||||
|
||||
|
||||
def check(found, expected):
|
||||
assert len(found) == len(expected), '%s != %s' % (found, expected)
|
||||
|
||||
last_offset = -1
|
||||
for f, e in zip(found, expected):
|
||||
if isinstance(e.name, (list, tuple, set)):
|
||||
assert f.name in e.name
|
||||
else:
|
||||
assert f.name == e.name
|
||||
assert f.is_visited == e.is_visited
|
||||
assert f.line == e.line
|
||||
assert f.call_order == e.call_order
|
||||
|
||||
# We can't check the offset because it may be different among different python versions
|
||||
# so, just check that it's always in order.
|
||||
assert f.offset > last_offset
|
||||
last_offset = f.offset
|
||||
|
||||
|
||||
def test_smart_step_into_bytecode_info():
|
||||
|
||||
from _pydevd_bundle import pydevd_bytecode_utils
|
||||
from _pydevd_bundle.pydevd_bytecode_utils import Variant
|
||||
|
||||
def function():
|
||||
|
||||
def some(arg):
|
||||
pass
|
||||
|
||||
def call(arg):
|
||||
pass
|
||||
|
||||
yield sys._getframe()
|
||||
call(some(call(some())))
|
||||
|
||||
generator = iter(function())
|
||||
frame = next(generator)
|
||||
|
||||
found = pydevd_bytecode_utils.calculate_smart_step_into_variants(
|
||||
frame, 0, 99999, base=function.__code__.co_firstlineno)
|
||||
|
||||
check(found, [
|
||||
Variant(name=('_getframe', 'sys'), is_visited=True, line=8, offset=20, call_order=1),
|
||||
Variant(name='some', is_visited=False, line=9, offset=34, call_order=1),
|
||||
Variant(name='call', is_visited=False, line=9, offset=36, call_order=1),
|
||||
Variant(name='some', is_visited=False, line=9, offset=38, call_order=2),
|
||||
Variant(name='call', is_visited=False, line=9, offset=40, call_order=2),
|
||||
])
|
||||
|
||||
|
||||
def test_get_smart_step_into_variant_from_frame_offset():
|
||||
from _pydevd_bundle import pydevd_bytecode_utils
|
||||
from _pydevd_bundle.pydevd_bytecode_utils import Variant
|
||||
|
||||
found = [
|
||||
Variant(name='_getframe', is_visited=True, line=8, offset=20, call_order=1),
|
||||
Variant(name='some', is_visited=False, line=9, offset=34, call_order=1),
|
||||
Variant(name='call', is_visited=False, line=9, offset=36, call_order=1),
|
||||
Variant(name='some', is_visited=False, line=9, offset=38, call_order=2),
|
||||
Variant(name='call', is_visited=False, line=9, offset=40, call_order=2),
|
||||
]
|
||||
assert pydevd_bytecode_utils.get_smart_step_into_variant_from_frame_offset(19, found) is None
|
||||
assert pydevd_bytecode_utils.get_smart_step_into_variant_from_frame_offset(20, found).offset == 20
|
||||
|
||||
assert pydevd_bytecode_utils.get_smart_step_into_variant_from_frame_offset(33, found).offset == 20
|
||||
|
||||
assert pydevd_bytecode_utils.get_smart_step_into_variant_from_frame_offset(34, found).offset == 34
|
||||
assert pydevd_bytecode_utils.get_smart_step_into_variant_from_frame_offset(35, found).offset == 34
|
||||
|
||||
assert pydevd_bytecode_utils.get_smart_step_into_variant_from_frame_offset(36, found).offset == 36
|
||||
|
||||
assert pydevd_bytecode_utils.get_smart_step_into_variant_from_frame_offset(44, found).offset == 40
|
||||
|
||||
|
||||
def test_smart_step_into_bytecode_info_eq():
|
||||
|
||||
from _pydevd_bundle import pydevd_bytecode_utils
|
||||
from _pydevd_bundle.pydevd_bytecode_utils import Variant
|
||||
|
||||
def function():
|
||||
a = 1
|
||||
b = 1
|
||||
if a == b:
|
||||
pass
|
||||
if a != b:
|
||||
pass
|
||||
if a > b:
|
||||
pass
|
||||
if a >= b:
|
||||
pass
|
||||
if a < b:
|
||||
pass
|
||||
if a <= b:
|
||||
pass
|
||||
if a is b:
|
||||
pass
|
||||
|
||||
yield sys._getframe()
|
||||
|
||||
generator = iter(function())
|
||||
frame = next(generator)
|
||||
|
||||
found = pydevd_bytecode_utils.calculate_smart_step_into_variants(
|
||||
frame, 0, 99999, base=function.__code__.co_firstlineno)
|
||||
|
||||
if sys.version_info[:2] < (3, 9):
|
||||
check(found, [
|
||||
Variant(name='__eq__', is_visited=True, line=3, offset=18, call_order=1),
|
||||
Variant(name='__ne__', is_visited=True, line=5, offset=33, call_order=1),
|
||||
Variant(name='__gt__', is_visited=True, line=7, offset=48, call_order=1),
|
||||
Variant(name='__ge__', is_visited=True, line=9, offset=63, call_order=1),
|
||||
Variant(name='__lt__', is_visited=True, line=11, offset=78, call_order=1),
|
||||
Variant(name='__le__', is_visited=True, line=13, offset=93, call_order=1),
|
||||
Variant(name='is', is_visited=True, line=15, offset=108, call_order=1),
|
||||
Variant(name=('_getframe', 'sys'), is_visited=True, line=18, offset=123, call_order=1),
|
||||
])
|
||||
else:
|
||||
check(found, [
|
||||
Variant(name='__eq__', is_visited=True, line=3, offset=18, call_order=1),
|
||||
Variant(name='__ne__', is_visited=True, line=5, offset=33, call_order=1),
|
||||
Variant(name='__gt__', is_visited=True, line=7, offset=48, call_order=1),
|
||||
Variant(name='__ge__', is_visited=True, line=9, offset=63, call_order=1),
|
||||
Variant(name='__lt__', is_visited=True, line=11, offset=78, call_order=1),
|
||||
Variant(name='__le__', is_visited=True, line=13, offset=93, call_order=1),
|
||||
Variant(name=('_getframe', 'sys'), is_visited=True, line=18, offset=123, call_order=1),
|
||||
])
|
||||
|
|
@ -473,3 +473,26 @@ def test_interrupt_main_thread():
|
|||
actual_timeout, timeout)
|
||||
else:
|
||||
raise AssertionError('KeyboardInterrupt not generated in main thread.')
|
||||
|
||||
|
||||
def test_get_smart_step_into_variant_from_frame_offset():
|
||||
from _pydevd_bundle.pydevd_bytecode_utils import get_smart_step_into_variant_from_frame_offset
|
||||
variants = []
|
||||
assert get_smart_step_into_variant_from_frame_offset(0, variants) is None
|
||||
|
||||
class DummyVariant(object):
|
||||
|
||||
def __init__(self, offset):
|
||||
self.offset = offset
|
||||
|
||||
variants.append(DummyVariant(1))
|
||||
assert get_smart_step_into_variant_from_frame_offset(0, variants) is None
|
||||
assert get_smart_step_into_variant_from_frame_offset(1, variants) is variants[0]
|
||||
assert get_smart_step_into_variant_from_frame_offset(2, variants) is variants[0]
|
||||
|
||||
variants.append(DummyVariant(3))
|
||||
assert get_smart_step_into_variant_from_frame_offset(0, variants) is None
|
||||
assert get_smart_step_into_variant_from_frame_offset(1, variants) is variants[0]
|
||||
assert get_smart_step_into_variant_from_frame_offset(2, variants) is variants[0]
|
||||
assert get_smart_step_into_variant_from_frame_offset(3, variants) is variants[1]
|
||||
assert get_smart_step_into_variant_from_frame_offset(4, variants) is variants[1]
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@ class Client(components.Component):
|
|||
"supportsGotoTargetsRequest": True,
|
||||
"supportsClipboardContext": True,
|
||||
"exceptionBreakpointFilters": exception_breakpoint_filters,
|
||||
"supportsStepInTargetsRequest": True,
|
||||
}
|
||||
|
||||
# Common code for "launch" and "attach" request handlers.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue