Skip to content

Commit

Permalink
Merge hit condition, logpoints, redirect command, next statement targ…
Browse files Browse the repository at this point in the history
…ets.
  • Loading branch information
fabioz committed Jun 11, 2018
1 parent e7fdc36 commit 6a4a435
Show file tree
Hide file tree
Showing 14 changed files with 244 additions and 44 deletions.
31 changes: 30 additions & 1 deletion _pydevd_bundle/pydevd_breakpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from _pydev_bundle import pydev_log
from _pydevd_bundle import pydevd_import_class
from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame
from _pydev_imps._pydev_saved_modules import threading


class ExceptionBreakpoint:
Expand Down Expand Up @@ -35,17 +36,45 @@ def __init__(
def __str__(self):
return self.qname

@property
def has_condition(self):
return self.condition is not None

def handle_hit_condition(self, frame):
return False


class LineBreakpoint(object):

def __init__(self, line, condition, func_name, expression, suspend_policy="NONE"):
def __init__(self, line, condition, func_name, expression, suspend_policy="NONE", hit_condition=None, is_logpoint=False):
self.line = line
self.condition = condition
self.func_name = func_name
self.expression = expression
self.suspend_policy = suspend_policy
self.hit_condition = hit_condition
self._hit_count = 0
self._hit_condition_lock = threading.Lock()
# need for frame evaluation: list of code objects, which bytecode was modified by this breakpoint
self.code_objects = set()
self.is_logpoint = is_logpoint

@property
def has_condition(self):
return self.condition is not None or self.hit_condition is not None

def handle_hit_condition(self, frame):
if self.hit_condition is None:
return False
ret = False
with self._hit_condition_lock:
self._hit_count += 1
expr = self.hit_condition.replace('@HIT@', str(self._hit_count))
try:
ret = bool(eval(expr, frame.f_globals, frame.f_locals))
except Exception:
ret = False
return ret


def get_exception_full_qname(exctype):
Expand Down
71 changes: 62 additions & 9 deletions _pydevd_bundle/pydevd_comm.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@
119 CMD_RELOAD_CODE
120 CMD_GET_COMPLETIONS JAVA
200 CMD_REDIRECT_OUTPUT JAVA streams to redirect as string -
'STDOUT' (redirect only STDOUT)
'STDERR' (redirect only STDERR)
'STDOUT STDERR' (redirect both streams)
500 series diagnostics/ok
501 VERSION either Version string (1.0) Currently just used at startup
502 RETURN either Depends on caller -
Expand All @@ -58,6 +63,7 @@
* PYDB - pydevd, the python end
'''

import itertools
import os

from _pydev_bundle.pydev_imports import _queue
Expand Down Expand Up @@ -85,6 +91,7 @@ def unquote(s):
from _pydevd_bundle import pydevd_xml
from _pydevd_bundle import pydevd_vm_type
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame, NORM_PATHS_AND_BASE_CONTAINER, norm_file_to_client
import pydevd_file_utils
import sys
import traceback
from _pydevd_bundle.pydevd_utils import quote_smart as quote, compare_object_attrs_key, to_string
Expand Down Expand Up @@ -163,6 +170,9 @@ def unquote(s):
CMD_SHOW_CYTHON_WARNING = 150
CMD_LOAD_FULL_VALUE = 151

CMD_REDIRECT_OUTPUT = 200
CMD_GET_NEXT_STATEMENT_TARGETS = 201

CMD_VERSION = 501
CMD_RETURN = 502
CMD_ERROR = 901
Expand Down Expand Up @@ -222,6 +232,9 @@ def unquote(s):
'150': 'CMD_SHOW_CYTHON_WARNING',
'151': 'CMD_LOAD_FULL_VALUE',

'200': 'CMD_REDIRECT_OUTPUT',
'201': 'CMD_GET_NEXT_STATEMENT_TARGETS',

'501': 'CMD_VERSION',
'502': 'CMD_RETURN',
'901': 'CMD_ERROR',
Expand Down Expand Up @@ -649,11 +662,10 @@ def make_variable_changed_message(self, seq, payload):
# notify debugger that value was changed successfully
return NetCommand(CMD_RETURN, seq, payload)

def make_io_message(self, v, ctx, dbg=None):
def make_io_message(self, v, ctx):
'''
@param v: the message to pass to the debug server
@param ctx: 1 for stdio 2 for stderr
@param dbg: If not none, add to the writer
'''

try:
Expand All @@ -662,14 +674,9 @@ def make_io_message(self, v, ctx, dbg=None):
v += '...'

v = pydevd_xml.make_valid_xml_value(quote(v, '/>_= \t'))
net = NetCommand(str(CMD_WRITE_TO_CONSOLE), 0, '<xml><io s="%s" ctx="%s"/></xml>' % (v, ctx))
return NetCommand(str(CMD_WRITE_TO_CONSOLE), 0, '<xml><io s="%s" ctx="%s"/></xml>' % (v, ctx))
except:
net = self.make_error_message(0, get_exception_traceback_str())

if dbg:
dbg.writer.add_command(net)

return net
return self.make_error_message(0, get_exception_traceback_str())

def make_version_message(self, seq):
try:
Expand Down Expand Up @@ -885,6 +892,13 @@ def make_exit_message(self):

return net

def make_get_next_statement_targets_message(self, seq, payload):
try:
return NetCommand(CMD_GET_NEXT_STATEMENT_TARGETS, seq, payload)
except Exception:
return self.make_error_message(seq, get_exception_traceback_str())


INTERNAL_TERMINATE_THREAD = 1
INTERNAL_SUSPEND_THREAD = 2

Expand Down Expand Up @@ -1154,6 +1168,45 @@ def do_it(self, dbg):
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)

#=======================================================================================================================
# InternalGetNextStatementTargets
#=======================================================================================================================
class InternalGetNextStatementTargets(InternalThreadCommand):
""" gets the valid line numbers for use with set next statement """
def __init__(self, seq, thread_id, frame_id):
self.sequence = seq
self.thread_id = thread_id
self.frame_id = frame_id

def do_it(self, dbg):
""" Converts request into set of line numbers """
try:
frame = pydevd_vars.find_frame(self.thread_id, self.frame_id)
if frame is not None:
code = frame.f_code
xml = "<xml>"
if hasattr(code, 'co_lnotab'):
lineno = code.co_firstlineno
lnotab = code.co_lnotab
for i in itertools.islice(lnotab, 1, len(lnotab), 2):
if isinstance(i, int):
lineno = lineno + i
else:
# in python 2 elements in co_lnotab are of type str
lineno = lineno + ord(i)
xml += "<line>%d</line>" % (lineno,)
else:
xml += "<line>%d</line>" % (frame.f_lineno,)
del frame
xml += "</xml>"
cmd = dbg.cmd_factory.make_get_next_statement_targets_message(self.sequence, xml)
dbg.writer.add_command(cmd)
else:
cmd = dbg.cmd_factory.make_error_message(self.sequence, "Frame not found: %s from thread: %s" % (self.frame_id, self.thread_id))
dbg.writer.add_command(cmd)
except:
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)

#=======================================================================================================================
# InternalEvaluateExpression
Expand Down
13 changes: 10 additions & 3 deletions _pydevd_bundle/pydevd_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,14 @@ def send_signature_call_trace(*args, **kwargs):
def handle_breakpoint_condition(py_db, info, breakpoint, new_frame):
condition = breakpoint.condition
try:
if breakpoint.handle_hit_condition(new_frame):
return True

if condition is None:
return False

return eval(condition, new_frame.f_globals, new_frame.f_locals)

except:
if type(condition) != type(''):
if hasattr(condition, 'encode'):
Expand Down Expand Up @@ -576,14 +583,15 @@ def trace_dispatch(self, frame, event, arg):
#ok, hit breakpoint, now, we have to discover if it is a conditional breakpoint
# lets do the conditional stuff here
if stop or exist_result:
condition = breakpoint.condition
if condition is not None:
if breakpoint.has_condition:
eval_result = handle_breakpoint_condition(main_debugger, info, breakpoint, new_frame)
if not eval_result:
return self.trace_dispatch

if breakpoint.expression is not None:
handle_breakpoint_expression(breakpoint, info, new_frame)
if breakpoint.is_logpoint:
return self.trace_dispatch

if not main_debugger.first_breakpoint_reached:
if is_call:
Expand Down Expand Up @@ -778,4 +786,3 @@ def trace_dispatch(self, frame, event, arg):
info.is_tracing = False

#end trace_dispatch

2 changes: 2 additions & 0 deletions _pydevd_bundle/pydevd_frame_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ def ignore_exception_trace(trace):

trace = trace.tb_next

return False

def cached_call(obj, func, *args):
cached_name = '_cached_' + func.__name__
if not hasattr(obj, cached_name):
Expand Down
1 change: 0 additions & 1 deletion _pydevd_bundle/pydevd_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ def __getattr__(self, name):
class IOBuf:
'''This class works as a replacement for stdio and stderr.
It is a buffer and when its contents are requested, it will erase what
it has so far so that the next return will not return the same contents again.
'''
def __init__(self):
Expand Down
37 changes: 27 additions & 10 deletions _pydevd_bundle/pydevd_process_net_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
CMD_EVALUATE_CONSOLE_EXPRESSION, InternalEvaluateConsoleExpression, InternalConsoleGetCompletions, \
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_LOAD_FULL_VALUE, CMD_REDIRECT_OUTPUT, CMD_GET_NEXT_STATEMENT_TARGETS, InternalGetNextStatementTargets
from _pydevd_bundle.pydevd_constants import get_thread_id, IS_PY3K, DebugInfoHolder, dict_keys, STATE_RUN, \
NEXT_VALUE_SEPARATOR

Expand Down Expand Up @@ -260,8 +260,14 @@ def process_net_command(py_db, cmd_id, seq, text):
# command to add some breakpoint.
# text is file\tline. Add to breakpoints dictionary
suspend_policy = "NONE"
is_logpoint = False
hit_condition = None
if py_db._set_breakpoints_with_id:
breakpoint_id, type, file, line, func_name, condition, expression = text.split('\t', 6)
try:
breakpoint_id, type, file, line, func_name, condition, expression, hit_condition, is_logpoint = text.split('\t', 8)
is_logpoint = is_logpoint == 'True'
except Exception:
breakpoint_id, type, file, line, func_name, condition, expression = text.split('\t', 6)

breakpoint_id = int(breakpoint_id)
line = int(line)
Expand Down Expand Up @@ -298,22 +304,25 @@ def process_net_command(py_db, cmd_id, seq, text):
sys.stderr.flush()


if len(condition) <= 0 or condition is None or condition == "None":
if condition is not None and (len(condition) <= 0 or condition == "None"):
condition = None

if len(expression) <= 0 or expression is None or expression == "None":
if expression is None and (len(expression) <= 0 or expression == "None"):
expression = None

if hit_condition is not None and (len(hit_condition) <= 0 or hit_condition == "None"):
hit_condition = None

if type == 'python-line':
breakpoint = LineBreakpoint(line, condition, func_name, expression, suspend_policy)
breakpoint = LineBreakpoint(line, condition, func_name, expression, suspend_policy, hit_condition=hit_condition, is_logpoint=is_logpoint)
breakpoints = py_db.breakpoints
file_to_id_to_breakpoint = py_db.file_to_id_to_line_breakpoint
supported_type = True
else:
result = None
plugin = py_db.get_plugin_lazy_init()
if plugin is not None:
result = plugin.add_breakpoint('add_line_breakpoint', py_db, type, file, line, condition, expression, func_name)
result = plugin.add_breakpoint('add_line_breakpoint', py_db, type, file, line, condition, expression, func_name, hit_condition=hit_condition, is_logpoint=is_logpoint)
if result is not None:
supported_type = True
breakpoint, breakpoints = result
Expand Down Expand Up @@ -553,12 +562,12 @@ def process_net_command(py_db, cmd_id, seq, text):

condition = condition.replace("@_@NEW_LINE_CHAR@_@", '\n').replace("@_@TAB_CHAR@_@", '\t').strip()

if len(condition) == 0 or condition == "None":
if condition is None and (len(condition) == 0 or condition == "None"):
condition = None

expression = expression.replace("@_@NEW_LINE_CHAR@_@", '\n').replace("@_@TAB_CHAR@_@", '\t').strip()

if len(expression) == 0 or expression == "None":
if expression is None and (len(expression) == 0 or expression == "None"):
expression = None

if exception.find('-') != -1:
Expand Down Expand Up @@ -740,6 +749,16 @@ def process_net_command(py_db, cmd_id, seq, text):
mode = text.strip() == true_str
pydevd_dont_trace.trace_filter(mode)

elif cmd_id == CMD_REDIRECT_OUTPUT:
if text:
py_db.enable_output_redirection('STDOUT' in text, 'STDERR' in text)

elif cmd_id == CMD_GET_NEXT_STATEMENT_TARGETS:
thread_id, frame_id = text.split('\t', 1)

int_cmd = InternalGetNextStatementTargets(seq, thread_id, frame_id)
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))
Expand All @@ -766,5 +785,3 @@ def process_net_command(py_db, cmd_id, seq, text):
py_db.writer.add_command(cmd)
finally:
py_db._main_lock.release()


3 changes: 2 additions & 1 deletion _pydevd_frame_eval/pydevd_frame_eval_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
dummy_trace_dispatch = None
show_frame_eval_warning = False

USE_FRAME_EVAL = os.environ.get('PYDEVD_USE_FRAME_EVAL', 'NO') # NO means don't use, anything else, use.
# "NO" means we should not use frame evaluation, anythinge else means we should use it.
USE_FRAME_EVAL = os.environ.get('PYDEVD_USE_FRAME_EVAL', 'NO')

if USE_FRAME_EVAL == 'NO':
frame_eval_func, stop_frame_eval = None, None
Expand Down
5 changes: 3 additions & 2 deletions _pydevd_frame_eval/pydevd_frame_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@ def update_globals_dict(globals_dict):
def handle_breakpoint(frame, thread, global_debugger, breakpoint):
# ok, hit breakpoint, now, we have to discover if it is a conditional breakpoint
new_frame = frame
condition = breakpoint.condition
info = thread.additional_info
if condition is not None:
if breakpoint.has_condition:
eval_result = handle_breakpoint_condition(global_debugger, info, breakpoint, new_frame)
if not eval_result:
return False

if breakpoint.expression is not None:
handle_breakpoint_expression(breakpoint, info, new_frame)
if breakpoint.is_logpoint:
return False

if breakpoint.suspend_policy == "ALL":
global_debugger.suspend_all_other_threads(thread)
Expand Down
Loading

0 comments on commit 6a4a435

Please sign in to comment.