diff options
Diffstat (limited to 'python/helpers/pydev/pydevd.py')
-rw-r--r-- | python/helpers/pydev/pydevd.py | 834 |
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) |