summaryrefslogtreecommitdiff
path: root/python/helpers/pydev/pydevd.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/helpers/pydev/pydevd.py')
-rw-r--r--python/helpers/pydev/pydevd.py834
1 files changed, 581 insertions, 253 deletions
diff --git a/python/helpers/pydev/pydevd.py b/python/helpers/pydev/pydevd.py
index 2077903ea091..1733c26b5e91 100644
--- a/python/helpers/pydev/pydevd.py
+++ b/python/helpers/pydev/pydevd.py
@@ -1,4 +1,6 @@
#IMPORTANT: pydevd_constants must be the 1st thing defined because it'll keep a reference to the original sys._getframe
+from __future__ import nested_scopes # Jython 2.1 support
+
import traceback
from django_debug import DjangoLineBreakpoint
@@ -35,7 +37,7 @@ from pydevd_comm import CMD_CHANGE_VARIABLE, \
CMD_ADD_DJANGO_EXCEPTION_BREAK, \
CMD_REMOVE_DJANGO_EXCEPTION_BREAK, \
CMD_SMART_STEP_INTO,\
- InternalChangeVariable, \
+ InternalChangeVariable, \
InternalGetCompletions, \
InternalEvaluateExpression, \
InternalConsoleExec, \
@@ -55,23 +57,35 @@ from pydevd_comm import CMD_CHANGE_VARIABLE, \
PydevdLog, \
StartClient, \
StartServer, \
- InternalSetNextStatementThread, ReloadCodeCommand
+ InternalSetNextStatementThread, \
+ ReloadCodeCommand, \
+ CMD_SET_PY_EXCEPTION, \
+ CMD_IGNORE_THROWN_EXCEPTION_AT,\
+ InternalGetBreakpointException, \
+ InternalSendCurrExceptionTrace,\
+ InternalSendCurrExceptionTraceProceeded,\
+ CMD_ENABLE_DONT_TRACE, \
+ CMD_GET_FILE_CONTENTS,\
+ CMD_SET_PROPERTY_TRACE, CMD_RUN_CUSTOM_OPERATION,\
+ InternalRunCustomOperation, CMD_EVALUATE_CONSOLE_EXPRESSION, InternalEvaluateConsoleExpression,\
+ InternalConsoleGetCompletions
+
from pydevd_file_utils import NormFileToServer, GetFilenameAndBase
import pydevd_file_utils
import pydevd_vars
import pydevd_vm_type
import pydevd_tracing
import pydevd_io
-import pydev_monkey
from pydevd_additional_thread_info import PyDBAdditionalThreadInfo
from pydevd_custom_frames import CustomFramesContainer, CustomFramesContainerInit
+import pydevd_dont_trace
+import pydevd_traceproperty
+from _pydev_imps import _pydev_time as time
if USE_LIB_COPY:
- import _pydev_time as time
import _pydev_threading as threading
else:
- import time
import threading
import os
@@ -80,10 +94,13 @@ import os
threadingEnumerate = threading.enumerate
threadingCurrentThread = threading.currentThread
+try:
+ 'dummy'.encode('utf-8') # Added because otherwise Jython 2.2.1 wasn't finding the encoding (if it wasn't loaded in the main thread).
+except:
+ pass
DONT_TRACE = {
# commonly used things from the stdlib that we don't want to trace
- 'threading.py':1,
'Queue.py':1,
'queue.py':1,
'socket.py':1,
@@ -92,12 +109,19 @@ DONT_TRACE = {
'threading.py':1,
#things from pydev that we don't want to trace
+ '_pydev_execfile.py':1,
+ '_pydev_jython_execfile.py':1,
+ '_pydev_threading':1,
+ 'django_debug.py':1,
+ 'django_frame.py':1,
+ 'pydev_log.py':1,
'pydevd.py':1 ,
'pydevd_additional_thread_info.py':1,
- 'pydevd_custom_frames.py':1,
'pydevd_comm.py':1,
'pydevd_console.py':1 ,
'pydevd_constants.py':1,
+ 'pydevd_custom_frames.py':1,
+ 'pydevd_dont_trace.py':1,
'pydevd_exec.py':1,
'pydevd_exec2.py':1,
'pydevd_file_utils.py':1,
@@ -105,17 +129,18 @@ DONT_TRACE = {
'pydevd_import_class.py':1 ,
'pydevd_io.py':1 ,
'pydevd_psyco_stub.py':1,
+ 'pydevd_referrers.py':1 ,
'pydevd_reload.py':1 ,
'pydevd_resolver.py':1 ,
+ 'pydevd_save_locals.py':1 ,
+ 'pydevd_signature.py':1,
'pydevd_stackless.py':1 ,
'pydevd_traceproperty.py':1,
'pydevd_tracing.py':1 ,
- 'pydevd_signature.py':1,
'pydevd_utils.py':1,
'pydevd_vars.py':1,
'pydevd_vm_type.py':1,
- '_pydev_execfile.py':1,
- '_pydev_jython_execfile.py':1
+ 'pydevd_xml.py':1,
}
if IS_PY3K:
@@ -135,17 +160,28 @@ remote = False
from _pydev_filesystem_encoding import getfilesystemencoding
file_system_encoding = getfilesystemencoding()
-def isThreadAlive(t):
- try:
- # If thread is not started yet we treat it as alive.
- # It is required to debug threads started by start_new_thread in Python 3.4
- if hasattr(t, '_is_stopped'):
- alive = not t._is_stopped
- else:
- alive = not t.__stopped
- except:
- alive = t.isAlive()
- return alive
+
+# Hack for https://sw-brainwy.rhcloud.com/tracker/PyDev/363 (i.e.: calling isAlive() can throw AssertionError under some circumstances)
+# It is required to debug threads started by start_new_thread in Python 3.4
+_temp = threading.Thread()
+if hasattr(_temp, '_is_stopped'): # Python 3.4 has this
+ def isThreadAlive(t):
+ try:
+ return not t._is_stopped
+ except:
+ return t.isAlive()
+
+elif hasattr(_temp, '_Thread__stopped'): # Python 2.7 has this
+ def isThreadAlive(t):
+ try:
+ return not t._Thread__stopped
+ except:
+ return t.isAlive()
+
+else: # Haven't checked all other versions, so, let's use the regular isAlive call in this case.
+ def isThreadAlive(t):
+ return t.isAlive()
+del _temp
#=======================================================================================================================
# PyDBCommandThread
@@ -159,7 +195,7 @@ class PyDBCommandThread(PyDBDaemonThread):
self.setName('pydevd.CommandThread')
def OnRun(self):
- for i in range(1, 10):
+ for i in xrange(1, 10):
time.sleep(0.5) #this one will only start later on (because otherwise we may not have any non-daemon threads
if self.killReceived:
return
@@ -187,7 +223,7 @@ def killAllPydevThreads():
for t in threads:
if hasattr(t, 'doKillPydevThread'):
t.doKillPydevThread()
-
+
#=======================================================================================================================
# PyDBCheckAliveThread
@@ -220,62 +256,6 @@ class PyDBCheckAliveThread(PyDBDaemonThread):
def doKillPydevThread(self):
pass
-if USE_LIB_COPY:
- import _pydev_thread as thread
-else:
- try:
- import thread
- except ImportError:
- import _thread as thread #Py3K changed it.
-
-_original_start_new_thread = thread.start_new_thread
-
-if getattr(thread, '_original_start_new_thread', None) is None:
- thread._original_start_new_thread = thread.start_new_thread
-
-#=======================================================================================================================
-# NewThreadStartup
-#=======================================================================================================================
-class NewThreadStartup:
-
- def __init__(self, original_func, args, kwargs):
- self.original_func = original_func
- self.args = args
- self.kwargs = kwargs
-
- def __call__(self):
- global_debugger = GetGlobalDebugger()
- global_debugger.SetTrace(global_debugger.trace_dispatch)
- self.original_func(*self.args, **self.kwargs)
-
-thread.NewThreadStartup = NewThreadStartup
-
-#=======================================================================================================================
-# pydev_start_new_thread
-#=======================================================================================================================
-def _pydev_start_new_thread(function, args, kwargs={}):
- '''
- We need to replace the original thread.start_new_thread with this function so that threads started through
- it and not through the threading module are properly traced.
- '''
- if USE_LIB_COPY:
- import _pydev_thread as thread
- else:
- try:
- import thread
- except ImportError:
- import _thread as thread #Py3K changed it.
-
- return thread._original_start_new_thread(thread.NewThreadStartup(function, args, kwargs), ())
-
-class PydevStartNewThread(object):
- def __get__(self, obj, type=None):
- return self
-
- def __call__(self, function, args, kwargs={}):
- return _pydev_start_new_thread(function, args, kwargs)
-
-pydev_start_new_thread = PydevStartNewThread()
#=======================================================================================================================
@@ -304,10 +284,18 @@ class PyDB:
self.quitting = None
self.cmdFactory = NetCommandFactory()
self._cmd_queue = {} # the hash of Queues. Key is thread id, value is thread
+
self.breakpoints = {}
self.django_breakpoints = {}
- self.exception_set = {}
- self.always_exception_set = set()
+
+ self.file_to_id_to_line_breakpoint = {}
+ self.file_to_id_to_django_breakpoint = {}
+
+ # Note: breakpoints dict should not be mutated: a copy should be created
+ # and later it should be assigned back (to prevent concurrency issues).
+ self.break_on_uncaught_exceptions = {}
+ self.break_on_caught_exceptions = {}
+
self.django_exception_break = {}
self.readyToRun = False
self._main_lock = threading.Lock()
@@ -316,15 +304,31 @@ class PyDB:
CustomFramesContainer._py_db_command_thread_event = self._py_db_command_thread_event
self._finishDebuggingSession = False
self._terminationEventSent = False
- self.force_post_mortem_stop = 0
self.signature_factory = None
self.SetTrace = pydevd_tracing.SetTrace
+ self.break_on_exceptions_thrown_in_same_context = False
+ self.ignore_exceptions_thrown_in_lines_with_ignore_exception = True
+
+ # Suspend debugger even if breakpoint condition raises an exception
+ SUSPEND_ON_BREAKPOINT_EXCEPTION = True
+ self.suspend_on_breakpoint_exception = SUSPEND_ON_BREAKPOINT_EXCEPTION
+
+ # By default user can step into properties getter/setter/deleter methods
+ self.disable_property_trace = False
+ self.disable_property_getter_trace = False
+ self.disable_property_setter_trace = False
+ self.disable_property_deleter_trace = False
#this is a dict of thread ids pointing to thread ids. Whenever a command is passed to the java end that
#acknowledges that a thread was created, the thread id should be passed here -- and if at some time we do not
#find that thread alive anymore, we must remove it from this list and make the java side know that the thread
#was killed.
self._running_thread_ids = {}
+ self._set_breakpoints_with_id = False
+
+ # This attribute holds the file-> lines which have an @IgnoreException.
+ self.filename_to_lines_where_exceptions_are_ignored = {}
+
def haveAliveThreads(self):
for t in threadingEnumerate():
@@ -398,12 +402,12 @@ class PyDB:
global bufferStdErrToServer
if bufferStdOutToServer:
- initStdoutRedirect()
- self.checkOutput(sys.stdoutBuf, 1) #@UndefinedVariable
+ initStdoutRedirect()
+ self.checkOutput(sys.stdoutBuf, 1) #@UndefinedVariable
if bufferStdErrToServer:
- initStderrRedirect()
- self.checkOutput(sys.stderrBuf, 2) #@UndefinedVariable
+ initStderrRedirect()
+ self.checkOutput(sys.stderrBuf, 2) #@UndefinedVariable
def checkOutput(self, out, outCtx):
'''Checks the output to see if we have to send some buffered output to the debug server
@@ -521,6 +525,58 @@ class PyDB:
additionalInfo = None
+ def consolidate_breakpoints(self, file, id_to_breakpoint, breakpoints):
+ break_dict = {}
+ for breakpoint_id, pybreakpoint in DictIterItems(id_to_breakpoint):
+ break_dict[pybreakpoint.line] = pybreakpoint
+
+ breakpoints[file] = break_dict
+
+
+ def add_break_on_exception(
+ self,
+ exception,
+ notify_always,
+ notify_on_terminate,
+ notify_on_first_raise_only,
+ ):
+ eb = ExceptionBreakpoint(
+ exception,
+ notify_always,
+ notify_on_terminate,
+ notify_on_first_raise_only,
+ )
+
+ if eb.notify_on_terminate:
+ cp = self.break_on_uncaught_exceptions.copy()
+ cp[exception] = eb
+ if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
+ pydev_log.error("Exceptions to hook on terminate: %s\n" % (cp,))
+ self.break_on_uncaught_exceptions = cp
+
+ if eb.notify_always:
+ cp = self.break_on_caught_exceptions.copy()
+ cp[exception] = eb
+ if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
+ pydev_log.error("Exceptions to hook always: %s\n" % (cp,))
+ self.break_on_caught_exceptions = cp
+
+ return eb
+
+ def update_after_exceptions_added(self, added):
+ updated_on_caught = False
+ updated_on_uncaught = False
+
+ for eb in added:
+ if not updated_on_uncaught and eb.notify_on_terminate:
+ updated_on_uncaught = True
+ update_exception_hook(self)
+
+ if not updated_on_caught and eb.notify_always:
+ updated_on_caught = True
+ self.setTracingForUntracedContexts()
+
+
def processNetCommand(self, cmd_id, seq, text):
'''Processes a command received from the Java side
@@ -536,6 +592,7 @@ class PyDB:
it may be worth refactoring it (actually, reordering the ifs so that the ones used mostly come before
probably will give better performance).
'''
+ #print ID_TO_MEANING[str(cmd_id)], repr(text)
self._main_lock.acquire()
try:
@@ -546,9 +603,28 @@ class PyDB:
elif cmd_id == CMD_VERSION:
# response is version number
- local_version, pycharm_os = text.split('\t', 1)
+ # ide_os should be 'WINDOWS' or 'UNIX'.
+ ide_os = 'WINDOWS'
+
+ # Breakpoints can be grouped by 'LINE' or by 'ID'.
+ breakpoints_by = 'LINE'
- pydevd_file_utils.set_pycharm_os(pycharm_os)
+ splitted = text.split('\t')
+ if len(splitted) == 1:
+ _local_version = splitted
+
+ elif len(splitted) == 2:
+ _local_version, ide_os = splitted
+
+ elif len(splitted) == 3:
+ _local_version, ide_os, breakpoints_by = splitted
+
+ if breakpoints_by == 'ID':
+ self._set_breakpoints_with_id = True
+ else:
+ self._set_breakpoints_with_id = False
+
+ pydevd_file_utils.set_ide_os(ide_os)
cmd = self.cmdFactory.makeVersionMessage(seq)
@@ -684,26 +760,42 @@ class PyDB:
elif cmd_id == CMD_SET_BREAK:
# func name: 'None': match anything. Empty: match global, specified: only method context.
-
# command to add some breakpoint.
# text is file\tline. Add to breakpoints dictionary
- type, file, line, condition, expression = text.split('\t', 4)
+ if self._set_breakpoints_with_id:
+ breakpoint_id, type, file, line, func_name, condition, expression = text.split('\t', 6)
- if not IS_PY3K: # In Python 3, the frame object will have unicode for the file, whereas on python 2 it has a byte-array encoded with the filesystem encoding.
- file = file.encode(file_system_encoding)
-
- if condition.startswith('**FUNC**'):
- func_name, condition = condition.split('\t', 1)
+ breakpoint_id = int(breakpoint_id)
+ line = int(line)
# We must restore new lines and tabs as done in
# AbstractDebugTarget.breakpointAdded
condition = condition.replace("@_@NEW_LINE_CHAR@_@", '\n').\
replace("@_@TAB_CHAR@_@", '\t').strip()
- func_name = func_name[8:]
+ expression = expression.replace("@_@NEW_LINE_CHAR@_@", '\n').\
+ replace("@_@TAB_CHAR@_@", '\t').strip()
else:
- func_name = 'None' # Match anything if not specified.
+ #Note: this else should be removed after PyCharm migrates to setting
+ #breakpoints by id (and ideally also provides func_name).
+ type, file, line, condition, expression = text.split('\t', 4)
+ # If we don't have an id given for each breakpoint, consider
+ # the id to be the line.
+ breakpoint_id = line = int(line)
+ if condition.startswith('**FUNC**'):
+ func_name, condition = condition.split('\t', 1)
+
+ # We must restore new lines and tabs as done in
+ # AbstractDebugTarget.breakpointAdded
+ condition = condition.replace("@_@NEW_LINE_CHAR@_@", '\n').\
+ replace("@_@TAB_CHAR@_@", '\t').strip()
+
+ func_name = func_name[8:]
+ else:
+ func_name = 'None' # Match anything if not specified.
+ if not IS_PY3K: # In Python 3, the frame object will have unicode for the file, whereas on python 2 it has a byte-array encoded with the filesystem encoding.
+ file = file.encode(file_system_encoding)
file = NormFileToServer(file)
@@ -712,7 +804,6 @@ class PyDB:
' to file that does not exist: %s (will have no effect)\n' % (file,))
sys.stderr.flush()
- line = int(line)
if len(condition) <= 0 or condition is None or condition == "None":
condition = None
@@ -721,57 +812,68 @@ class PyDB:
expression = None
if type == 'python-line':
- breakpoint = LineBreakpoint(type, True, condition, func_name, expression)
- breakpoint.add(self.breakpoints, file, line, func_name)
+ breakpoint = LineBreakpoint(line, condition, func_name, expression)
+ breakpoints = self.breakpoints
+ file_to_id_to_breakpoint = self.file_to_id_to_line_breakpoint
elif type == 'django-line':
- breakpoint = DjangoLineBreakpoint(type, file, line, True, condition, func_name, expression)
- breakpoint.add(self.django_breakpoints, file, line, func_name)
+ breakpoint = DjangoLineBreakpoint(file, line, condition, func_name, expression)
+ breakpoints = self.django_breakpoints
+ file_to_id_to_breakpoint = self.file_to_id_to_django_breakpoint
else:
raise NameError(type)
+ if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
+ pydev_log.debug('Added breakpoint:%s - line:%s - func_name:%s\n' % (file, line, func_name.encode('utf-8')))
+ sys.stderr.flush()
+
+ if DictContains(file_to_id_to_breakpoint, file):
+ id_to_pybreakpoint = file_to_id_to_breakpoint[file]
+ else:
+ id_to_pybreakpoint = file_to_id_to_breakpoint[file] = {}
+
+ id_to_pybreakpoint[breakpoint_id] = breakpoint
+ self.consolidate_breakpoints(file, id_to_pybreakpoint, breakpoints)
+
self.setTracingForUntracedContexts()
elif cmd_id == CMD_REMOVE_BREAK:
#command to remove some breakpoint
- #text is file\tline. Remove from breakpoints dictionary
- type, file, line = text.split('\t', 2)
+ #text is type\file\tid. Remove from breakpoints dictionary
+ breakpoint_type, file, breakpoint_id = text.split('\t', 2)
+
+ if not IS_PY3K: # In Python 3, the frame object will have unicode for the file, whereas on python 2 it has a byte-array encoded with the filesystem encoding.
+ file = file.encode(file_system_encoding)
+
file = NormFileToServer(file)
+
try:
- line = int(line)
+ breakpoint_id = int(breakpoint_id)
except ValueError:
- pass
+ pydev_log.error('Error removing breakpoint. Expected breakpoint_id to be an int. Found: %s' % (breakpoint_id,))
else:
- found = False
- try:
- if type == 'django-line':
- del self.django_breakpoints[file][line]
- elif type == 'python-line':
- del self.breakpoints[file][line] #remove the breakpoint in that line
- else:
- try:
- del self.django_breakpoints[file][line]
- found = True
- except:
- pass
- try:
- del self.breakpoints[file][line] #remove the breakpoint in that line
- found = True
- except:
- pass
+ if breakpoint_type == 'python-line':
+ breakpoints = self.breakpoints
+ file_to_id_to_breakpoint = self.file_to_id_to_line_breakpoint
+ elif breakpoint_type == 'django-line':
+ breakpoints = self.django_breakpoints
+ file_to_id_to_breakpoint = self.file_to_id_to_django_breakpoint
+ else:
+ raise NameError(breakpoint_type)
+ try:
+ id_to_pybreakpoint = file_to_id_to_breakpoint[file]
if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
- sys.stderr.write('Removed breakpoint:%s - %s\n' % (file, line))
- sys.stderr.flush()
+ existing = id_to_pybreakpoint[breakpoint_id]
+ sys.stderr.write('Removed breakpoint:%s - line:%s - func_name:%s (id: %s)\n' % (
+ file, existing.line, existing.func_name.encode('utf-8'), breakpoint_id))
+
+ del id_to_pybreakpoint[breakpoint_id]
+ self.consolidate_breakpoints(file, id_to_pybreakpoint, breakpoints)
except KeyError:
- found = False
+ pydev_log.error("Error removing breakpoint: Breakpoint id not found: %s id: %s. Available ids: %s\n" % (
+ file, breakpoint_id, DictKeys(id_to_pybreakpoint)))
- if not found:
- #ok, it's not there...
- if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
- #Sometimes, when adding a breakpoint, it adds a remove command before (don't really know why)
- sys.stderr.write("breakpoint not found: %s - %s\n" % (file, line))
- sys.stderr.flush()
elif cmd_id == CMD_EVALUATE_EXPRESSION or cmd_id == CMD_EXEC_EXPRESSION:
#command to evaluate the given expression
@@ -790,29 +892,118 @@ class PyDB:
int_cmd = InternalConsoleExec(seq, thread_id, frame_id, expression)
self.postInternalCommand(int_cmd, thread_id)
- elif cmd_id == CMD_ADD_EXCEPTION_BREAK:
- exception, notify_always, notify_on_terminate = text.split('\t', 2)
+ elif cmd_id == CMD_SET_PY_EXCEPTION:
+ # Command which receives set of exceptions on which user wants to break the debugger
+ # text is: break_on_uncaught;break_on_caught;TypeError;ImportError;zipimport.ZipImportError;
+ # This API is optional and works 'in bulk' -- it's possible
+ # to get finer-grained control with CMD_ADD_EXCEPTION_BREAK/CMD_REMOVE_EXCEPTION_BREAK
+ # which allows setting caught/uncaught per exception.
+ #
+ splitted = text.split(';')
+ self.break_on_uncaught_exceptions = {}
+ self.break_on_caught_exceptions = {}
+ added = []
+ if len(splitted) >= 4:
+ if splitted[0] == 'true':
+ break_on_uncaught = True
+ else:
+ break_on_uncaught = False
- eb = ExceptionBreakpoint(exception, notify_always, notify_on_terminate)
+ if splitted[1] == 'true':
+ break_on_caught = True
+ else:
+ break_on_caught = False
- self.exception_set[exception] = eb
+ if splitted[2] == 'true':
+ self.break_on_exceptions_thrown_in_same_context = True
+ else:
+ self.break_on_exceptions_thrown_in_same_context = False
- if eb.notify_on_terminate:
- update_exception_hook(self)
- if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
- pydev_log.error("Exceptions to hook on terminate: %s\n" % (self.exception_set,))
+ if splitted[3] == 'true':
+ self.ignore_exceptions_thrown_in_lines_with_ignore_exception = True
+ else:
+ self.ignore_exceptions_thrown_in_lines_with_ignore_exception = False
+
+ for exception_type in splitted[4:]:
+ exception_type = exception_type.strip()
+ if not exception_type:
+ continue
+
+ exception_breakpoint = self.add_break_on_exception(
+ exception_type,
+ notify_always=break_on_caught,
+ notify_on_terminate=break_on_uncaught,
+ notify_on_first_raise_only=False,
+ )
+ added.append(exception_breakpoint)
- if eb.notify_always:
- self.always_exception_set.add(exception)
- if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
- pydev_log.error("Exceptions to hook always: %s\n" % (self.always_exception_set,))
- self.setTracingForUntracedContexts()
+ self.update_after_exceptions_added(added)
+
+ else:
+ sys.stderr.write("Error when setting exception list. Received: %s\n" % (text,))
+
+ elif cmd_id == CMD_GET_FILE_CONTENTS:
+
+ if not IS_PY3K: # In Python 3, the frame object will have unicode for the file, whereas on python 2 it has a byte-array encoded with the filesystem encoding.
+ text = text.encode(file_system_encoding)
+
+ if os.path.exists(text):
+ f = open(text, 'r')
+ try:
+ source = f.read()
+ finally:
+ f.close()
+ cmd = self.cmdFactory.makeGetFileContents(seq, source)
+
+ elif cmd_id == CMD_SET_PROPERTY_TRACE:
+ # Command which receives whether to trace property getter/setter/deleter
+ # text is feature_state(true/false);disable_getter/disable_setter/disable_deleter
+ if text != "":
+ splitted = text.split(';')
+ if len(splitted) >= 3:
+ if self.disable_property_trace is False and splitted[0] == 'true':
+ # Replacing property by custom property only when the debugger starts
+ pydevd_traceproperty.replace_builtin_property()
+ self.disable_property_trace = True
+ # Enable/Disable tracing of the property getter
+ if splitted[1] == 'true':
+ self.disable_property_getter_trace = True
+ else:
+ self.disable_property_getter_trace = False
+ # Enable/Disable tracing of the property setter
+ if splitted[2] == 'true':
+ self.disable_property_setter_trace = True
+ else:
+ self.disable_property_setter_trace = False
+ # Enable/Disable tracing of the property deleter
+ if splitted[3] == 'true':
+ self.disable_property_deleter_trace = True
+ else:
+ self.disable_property_deleter_trace = False
+ else:
+ # User hasn't configured any settings for property tracing
+ pass
+
+ elif cmd_id == CMD_ADD_EXCEPTION_BREAK:
+ exception, notify_always, notify_on_terminate = text.split('\t', 2)
+ exception_breakpoint = self.add_break_on_exception(
+ exception,
+ notify_always=int(notify_always) > 0,
+ notify_on_terminate = int(notify_on_terminate) == 1,
+ notify_on_first_raise_only=int(notify_always) == 2
+ )
+ self.update_after_exceptions_added([exception_breakpoint])
elif cmd_id == CMD_REMOVE_EXCEPTION_BREAK:
exception = text
try:
- del self.exception_set[exception]
- self.always_exception_set.remove(exception)
+ cp = self.break_on_uncaught_exceptions.copy()
+ DictPop(cp, exception, None)
+ self.break_on_uncaught_exceptions = cp
+
+ cp = self.break_on_caught_exceptions.copy()
+ DictPop(cp, exception, None)
+ self.break_on_caught_exceptions = cp
except:
pydev_log.debug("Error while removing exception %s"%sys.exc_info()[0]);
update_exception_hook(self)
@@ -840,6 +1031,77 @@ class PyDB:
except :
pass
+ elif cmd_id == CMD_EVALUATE_CONSOLE_EXPRESSION:
+ # Command which takes care for the debug console communication
+ if text != "":
+ thread_id, frame_id, console_command = text.split('\t', 2)
+ console_command, line = console_command.split('\t')
+ if console_command == 'EVALUATE':
+ int_cmd = InternalEvaluateConsoleExpression(seq, thread_id, frame_id, line)
+ elif console_command == 'GET_COMPLETIONS':
+ int_cmd = InternalConsoleGetCompletions(seq, thread_id, frame_id, line)
+ self.postInternalCommand(int_cmd, thread_id)
+
+ elif cmd_id == CMD_RUN_CUSTOM_OPERATION:
+ # Command which runs a custom operation
+ if text != "":
+ try:
+ location, custom = text.split('||', 1)
+ except:
+ sys.stderr.write('Custom operation now needs a || separator. Found: %s\n' % (text,))
+ raise
+
+ thread_id, frame_id, scopeattrs = location.split('\t', 2)
+
+ if scopeattrs.find('\t') != -1: # there are attributes beyond scope
+ scope, attrs = scopeattrs.split('\t', 1)
+ else:
+ scope, attrs = (scopeattrs, None)
+
+ # : style: EXECFILE or EXEC
+ # : encoded_code_or_file: file to execute or code
+ # : fname: name of function to be executed in the resulting namespace
+ style, encoded_code_or_file, fnname = custom.split('\t', 3)
+ int_cmd = InternalRunCustomOperation(seq, thread_id, frame_id, scope, attrs,
+ style, encoded_code_or_file, fnname)
+ self.postInternalCommand(int_cmd, thread_id)
+
+ elif cmd_id == CMD_IGNORE_THROWN_EXCEPTION_AT:
+ if text:
+ replace = 'REPLACE:' # Not all 3.x versions support u'REPLACE:', so, doing workaround.
+ if not IS_PY3K:
+ replace = unicode(replace)
+
+ if text.startswith(replace):
+ text = text[8:]
+ self.filename_to_lines_where_exceptions_are_ignored.clear()
+
+ if text:
+ for line in text.split('||'): # Can be bulk-created (one in each line)
+ filename, line_number = line.split('|')
+ if not IS_PY3K:
+ filename = filename.encode(file_system_encoding)
+
+ filename = NormFileToServer(filename)
+
+ if os.path.exists(filename):
+ lines_ignored = self.filename_to_lines_where_exceptions_are_ignored.get(filename)
+ if lines_ignored is None:
+ lines_ignored = self.filename_to_lines_where_exceptions_are_ignored[filename] = {}
+ lines_ignored[int(line_number)] = 1
+ else:
+ sys.stderr.write('pydev debugger: warning: trying to ignore exception thrown'\
+ ' on file that does not exist: %s (will have no effect)\n' % (filename,))
+
+ elif cmd_id == CMD_ENABLE_DONT_TRACE:
+ if text:
+ true_str = 'true' # Not all 3.x versions support u'str', so, doing workaround.
+ if not IS_PY3K:
+ true_str = unicode(true_str)
+
+ mode = text.strip() == true_str
+ pydevd_dont_trace.trace_filter(mode)
+
else:
#I have no idea what this is all about
cmd = self.cmdFactory.makeErrorMessage(seq, "unexpected command " + str(cmd_id))
@@ -881,6 +1143,44 @@ class PyDB:
thread.additionalInfo.pydev_state = STATE_SUSPEND
thread.stop_reason = stop_reason
+ # If conditional breakpoint raises any exception during evaluation send details to Java
+ if stop_reason == CMD_SET_BREAK and self.suspend_on_breakpoint_exception:
+ self.sendBreakpointConditionException(thread)
+
+
+ def sendBreakpointConditionException(self, thread):
+ """If conditional breakpoint raises an exception during evaluation
+ send exception details to java
+ """
+ thread_id = GetThreadId(thread)
+ conditional_breakpoint_exception_tuple = thread.additionalInfo.conditional_breakpoint_exception
+ # conditional_breakpoint_exception_tuple - should contain 2 values (exception_type, stacktrace)
+ if conditional_breakpoint_exception_tuple and len(conditional_breakpoint_exception_tuple) == 2:
+ exc_type, stacktrace = conditional_breakpoint_exception_tuple
+ int_cmd = InternalGetBreakpointException(thread_id, exc_type, stacktrace)
+ # Reset the conditional_breakpoint_exception details to None
+ thread.additionalInfo.conditional_breakpoint_exception = None
+ self.postInternalCommand(int_cmd, thread_id)
+
+
+ def sendCaughtExceptionStack(self, thread, arg, curr_frame_id):
+ """Sends details on the exception which was caught (and where we stopped) to the java side.
+
+ arg is: exception type, description, traceback object
+ """
+ thread_id = GetThreadId(thread)
+ int_cmd = InternalSendCurrExceptionTrace(thread_id, arg, curr_frame_id)
+ self.postInternalCommand(int_cmd, thread_id)
+
+
+ def sendCaughtExceptionStackProceeded(self, thread):
+ """Sends that some thread was resumed and is no longer showing an exception trace.
+ """
+ thread_id = GetThreadId(thread)
+ int_cmd = InternalSendCurrExceptionTraceProceeded(thread_id)
+ self.postInternalCommand(int_cmd, thread_id)
+ self.processInternalCommands()
+
def doWaitSuspend(self, thread, frame, event, arg): #@UnusedVariable
""" busy waits until the thread state changes to RUN
@@ -898,7 +1198,7 @@ class PyDB:
try:
from_this_thread = []
- for frame_id, custom_frame in CustomFramesContainer.custom_frames.items():
+ for frame_id, custom_frame in DictIterItems(CustomFramesContainer.custom_frames):
if custom_frame.thread_id == thread.ident:
# print >> sys.stderr, 'Frame created: ', frame_id
self.writer.addCommand(self.cmdFactory.makeCustomFrameCreatedMessage(frame_id, custom_frame.name))
@@ -991,7 +1291,6 @@ class PyDB:
def handle_post_mortem_stop(self, additionalInfo, t):
pydev_log.debug("We are stopping in post-mortem\n")
- self.force_post_mortem_stop -= 1
frame, frames_byid = additionalInfo.pydev_force_stop_at_exception
thread_id = GetThreadId(t)
pydevd_vars.addAdditionalFrameById(thread_id, frames_byid)
@@ -1060,9 +1359,9 @@ class PyDB:
if additionalInfo.is_tracing:
f = frame
while f is not None:
- fname, bs = GetFilenameAndBase(f)
- if bs == 'pydevd_frame.py':
- if 'trace_dispatch' == f.f_code.co_name:
+ if 'trace_dispatch' == f.f_code.co_name:
+ _fname, bs = GetFilenameAndBase(f)
+ if bs == 'pydevd_frame.py':
return None #we don't wan't to trace code invoked from pydevd_frame.trace_dispatch
f = f.f_back
@@ -1071,9 +1370,6 @@ class PyDB:
self.processThreadNotAlive(GetThreadId(t))
return None # suspend tracing
- if is_file_to_ignore:
- return None
-
# each new frame...
return additionalInfo.CreateDbFrame((self, filename, additionalInfo, t, frame)).trace_dispatch(frame, event, arg)
@@ -1120,18 +1416,18 @@ class PyDB:
def update_trace(self, frame, dispatch_func, overwrite_prev):
if frame.f_trace is None:
- frame.f_trace = dispatch_func
+ frame.f_trace = dispatch_func
else:
- if overwrite_prev:
- frame.f_trace = dispatch_func
- else:
- try:
- #If it's the trace_exception, go back to the frame trace dispatch!
- if frame.f_trace.im_func.__name__ == 'trace_exception':
- frame.f_trace = frame.f_trace.im_self.trace_dispatch
- except AttributeError:
- pass
- frame = frame.f_back
+ if overwrite_prev:
+ frame.f_trace = dispatch_func
+ else:
+ try:
+ #If it's the trace_exception, go back to the frame trace dispatch!
+ if frame.f_trace.im_func.__name__ == 'trace_exception':
+ frame.f_trace = frame.f_trace.im_self.trace_dispatch
+ except AttributeError:
+ pass
+ frame = frame.f_back
del frame
def prepareToRun(self):
@@ -1150,6 +1446,7 @@ class PyDB:
PyDBCommandThread(self).start()
PyDBCheckAliveThread(self).start()
+
def patch_threads(self):
try:
# not available in jython!
@@ -1157,11 +1454,8 @@ class PyDB:
except:
pass
- try:
- thread.start_new_thread = pydev_start_new_thread
- thread.start_new = pydev_start_new_thread
- except:
- pass
+ from pydev_monkey import patch_thread_modules
+ patch_thread_modules()
def run(self, file, globals=None, locals=None, set_trace=True):
@@ -1185,7 +1479,7 @@ class PyDB:
sys.modules['__main__'] = m
if hasattr(sys.modules['pydevd'], '__loader__'):
setattr(m, '__loader__', getattr(sys.modules['pydevd'], '__loader__'))
-
+
m.__file__ = file
globals = m.__dict__
try:
@@ -1246,7 +1540,8 @@ def processCommandLine(argv):
setup['server'] = False
setup['port'] = 0
setup['file'] = ''
- setup['multiproc'] = False
+ setup['multiproc'] = False #Used by PyCharm (reuses connection: ssh tunneling)
+ setup['multiprocess'] = False # Used by PyDev (creates new connection to ide)
setup['save-signatures'] = False
i = 0
del argv[0]
@@ -1279,6 +1574,9 @@ def processCommandLine(argv):
elif (argv[i] == '--multiproc'):
del argv[i]
setup['multiproc'] = True
+ elif (argv[i] == '--multiprocess'):
+ del argv[i]
+ setup['multiprocess'] = True
elif (argv[i] == '--save-signatures'):
del argv[i]
setup['save-signatures'] = True
@@ -1423,7 +1721,7 @@ def _locked_settrace(
CustomFramesContainer.custom_frames_lock.acquire()
try:
- for _frameId, custom_frame in CustomFramesContainer.custom_frames.items():
+ for _frameId, custom_frame in DictIterItems(CustomFramesContainer.custom_frames):
debugger.SetTraceForFrameAndParents(custom_frame.frame, False)
finally:
CustomFramesContainer.custom_frames_lock.release()
@@ -1492,24 +1790,21 @@ def stoptrace():
threading.settrace(None) # for all future threads
except:
pass
-
- try:
- thread.start_new_thread = _original_start_new_thread
- thread.start_new = _original_start_new_thread
- except:
- pass
-
+
+ from pydev_monkey import undo_patch_thread_modules
+ undo_patch_thread_modules()
+
debugger = GetGlobalDebugger()
-
+
if debugger:
debugger.trace_dispatch = None
-
+
debugger.SetTraceForFrameAndParents(GetFrame(), False)
-
+
debugger.exiting()
-
- killAllPydevThreads()
-
+
+ killAllPydevThreads()
+
connected = False
class Dispatcher(object):
@@ -1544,21 +1839,28 @@ class DispatchReader(ReaderThread):
self.killReceived = True
+DISPATCH_APPROACH_NEW_CONNECTION = 1 # Used by PyDev
+DISPATCH_APPROACH_EXISTING_CONNECTION = 2 # Used by PyCharm
+DISPATCH_APPROACH = DISPATCH_APPROACH_NEW_CONNECTION
+
def dispatch():
- argv = sys.original_argv[:]
- setup = processCommandLine(argv)
+ setup = SetupHolder.setup
host = setup['client']
port = setup['port']
- dispatcher = Dispatcher()
- try:
- dispatcher.connect(host, port)
- port = dispatcher.port
- finally:
- dispatcher.close()
+ if DISPATCH_APPROACH == DISPATCH_APPROACH_EXISTING_CONNECTION:
+ dispatcher = Dispatcher()
+ try:
+ dispatcher.connect(host, port)
+ port = dispatcher.port
+ finally:
+ dispatcher.close()
return host, port
def settrace_forked():
+ '''
+ When creating a fork from a process in the debugger, we need to reset the whole debugger environment!
+ '''
host, port = dispatch()
import pydevd_tracing
@@ -1578,6 +1880,15 @@ def settrace_forked():
overwrite_prev_trace=True,
patch_multiprocessing=True,
)
+
+#=======================================================================================================================
+# SetupHolder
+#=======================================================================================================================
+class SetupHolder:
+
+ setup = None
+
+
#=======================================================================================================================
# main
#=======================================================================================================================
@@ -1586,6 +1897,7 @@ if __name__ == '__main__':
try:
sys.original_argv = sys.argv[:]
setup = processCommandLine(sys.argv)
+ SetupHolder.setup = setup
except ValueError:
traceback.print_exc()
usage(1)
@@ -1611,62 +1923,73 @@ if __name__ == '__main__':
f = setup['file']
fix_app_engine_debug = False
- if setup['multiproc']:
- pydev_log.debug("Started in multiproc mode\n")
-
- dispatcher = Dispatcher()
- try:
- dispatcher.connect(host, port)
- if dispatcher.port is not None:
- port = dispatcher.port
- pydev_log.debug("Received port %d\n" %port)
- pydev_log.info("pydev debugger: process %d is connecting\n"% os.getpid())
- try:
- pydev_monkey.patch_new_process_functions()
- except:
- pydev_log.error("Error patching process functions\n")
- traceback.print_exc()
- else:
- pydev_log.error("pydev debugger: couldn't get port for new debug process\n")
- finally:
- dispatcher.close()
+ try:
+ import pydev_monkey
+ except:
+ pass #Not usable on jython 2.1
else:
- pydev_log.info("pydev debugger: starting\n")
+ if setup['multiprocess']: # PyDev
+ pydev_monkey.patch_new_process_functions()
- try:
- pydev_monkey.patch_new_process_functions_with_warning()
- except:
- pydev_log.error("Error patching process functions\n")
- traceback.print_exc()
+ elif setup['multiproc']: # PyCharm
+ pydev_log.debug("Started in multiproc mode\n")
+ # Note: we're not inside method, so, no need for 'global'
+ DISPATCH_APPROACH = DISPATCH_APPROACH_EXISTING_CONNECTION
+
+ dispatcher = Dispatcher()
+ try:
+ dispatcher.connect(host, port)
+ if dispatcher.port is not None:
+ port = dispatcher.port
+ pydev_log.debug("Received port %d\n" %port)
+ pydev_log.info("pydev debugger: process %d is connecting\n"% os.getpid())
- # Only do this patching if we're not running with multiprocess turned on.
- if f.find('dev_appserver.py') != -1:
- if os.path.basename(f).startswith('dev_appserver.py'):
- appserver_dir = os.path.dirname(f)
- version_file = os.path.join(appserver_dir, 'VERSION')
- if os.path.exists(version_file):
try:
- stream = open(version_file, 'r')
- try:
- for line in stream.read().splitlines():
- line = line.strip()
- if line.startswith('release:'):
- line = line[8:].strip()
- version = line.replace('"', '')
- version = version.split('.')
- if int(version[0]) > 1:
- fix_app_engine_debug = True
-
- elif int(version[0]) == 1:
- if int(version[1]) >= 7:
- # Only fix from 1.7 onwards
- fix_app_engine_debug = True
- break
- finally:
- stream.close()
+ pydev_monkey.patch_new_process_functions()
except:
+ pydev_log.error("Error patching process functions\n")
traceback.print_exc()
+ else:
+ pydev_log.error("pydev debugger: couldn't get port for new debug process\n")
+ finally:
+ dispatcher.close()
+ else:
+ pydev_log.info("pydev debugger: starting\n")
+
+ try:
+ pydev_monkey.patch_new_process_functions_with_warning()
+ except:
+ pydev_log.error("Error patching process functions\n")
+ traceback.print_exc()
+
+ # Only do this patching if we're not running with multiprocess turned on.
+ if f.find('dev_appserver.py') != -1:
+ if os.path.basename(f).startswith('dev_appserver.py'):
+ appserver_dir = os.path.dirname(f)
+ version_file = os.path.join(appserver_dir, 'VERSION')
+ if os.path.exists(version_file):
+ try:
+ stream = open(version_file, 'r')
+ try:
+ for line in stream.read().splitlines():
+ line = line.strip()
+ if line.startswith('release:'):
+ line = line[8:].strip()
+ version = line.replace('"', '')
+ version = version.split('.')
+ if int(version[0]) > 1:
+ fix_app_engine_debug = True
+
+ elif int(version[0]) == 1:
+ if int(version[1]) >= 7:
+ # Only fix from 1.7 onwards
+ fix_app_engine_debug = True
+ break
+ finally:
+ stream.close()
+ except:
+ traceback.print_exc()
try:
# In the default run (i.e.: run directly on debug mode), we try to patch stackless as soon as possible
@@ -1718,16 +2041,21 @@ if __name__ == '__main__':
import pydevd_psyco_stub
sys.modules['psyco'] = pydevd_psyco_stub
- debugger = PyDB()
+ debugger = PyDB()
- if setup['save-signatures']:
- if pydevd_vm_type.GetVmType() == pydevd_vm_type.PydevdVmType.JYTHON:
- sys.stderr.write("Collecting run-time type information is not supported for Jython\n")
- else:
- debugger.signature_factory = SignatureFactory()
+ if setup['save-signatures']:
+ if pydevd_vm_type.GetVmType() == pydevd_vm_type.PydevdVmType.JYTHON:
+ sys.stderr.write("Collecting run-time type information is not supported for Jython\n")
+ else:
+ debugger.signature_factory = SignatureFactory()
- debugger.connect(host, port)
+ try:
+ debugger.connect(host, port)
+ except:
+ sys.stderr.write("Could not connect to %s: %s\n" % (host, port))
+ traceback.print_exc()
+ sys.exit(1)
- connected = True #Mark that we're connected when started from inside ide.
+ connected = True # Mark that we're connected when started from inside ide.
- debugger.run(setup['file'], None, None)
+ debugger.run(setup['file'], None, None)