import linecache import os.path import re import traceback # @Reimport from django_debug import find_django_render_frame from django_debug import is_django_render_call, is_django_suspended, suspend_django, is_django_resolve_call, is_django_context_get_call from django_frame import DjangoTemplateFrame from django_frame import is_django_exception_break_context from django_frame import just_raised, get_template_file_name, get_template_line import pydev_log from pydevd_breakpoints import get_exception_breakpoint, get_exception_name from pydevd_comm import CMD_ADD_DJANGO_EXCEPTION_BREAK, \ CMD_STEP_CAUGHT_EXCEPTION, CMD_STEP_RETURN, CMD_STEP_OVER, CMD_SET_BREAK, \ CMD_STEP_INTO, CMD_SMART_STEP_INTO, CMD_RUN_TO_LINE, CMD_SET_NEXT_STATEMENT from pydevd_constants import * # @UnusedWildImport from pydevd_file_utils import GetFilenameAndBase try: from pydevd_signature import sendSignatureCallTrace except ImportError: def sendSignatureCallTrace(*args, **kwargs): pass import pydevd_vars import pydevd_dont_trace basename = os.path.basename IGNORE_EXCEPTION_TAG = re.compile('[^#]*#.*@IgnoreException') #======================================================================================================================= # PyDBFrame #======================================================================================================================= class PyDBFrame: '''This makes the tracing for a given frame, so, the trace_dispatch is used initially when we enter into a new context ('call') and then is reused for the entire context. ''' #Note: class (and not instance) attributes. #Same thing in the main debugger but only considering the file contents, while the one in the main debugger #considers the user input (so, the actual result must be a join of both). filename_to_lines_where_exceptions_are_ignored = {} filename_to_stat_info = {} def __init__(self, args): #args = mainDebugger, filename, base, info, t, frame #yeap, much faster than putting in self and then getting it from self later on self._args = args[:-1] def setSuspend(self, *args, **kwargs): self._args[0].setSuspend(*args, **kwargs) def doWaitSuspend(self, *args, **kwargs): self._args[0].doWaitSuspend(*args, **kwargs) def _is_django_render_call(self, frame): try: return self._cached_is_django_render_call except: # Calculate lazily: note that a PyDBFrame always deals with the same # frame over and over, so, we can cache this. # -- although we can't cache things which change over time (such as # the breakpoints for the file). ret = self._cached_is_django_render_call = is_django_render_call(frame) return ret def trace_exception(self, frame, event, arg): if event == 'exception': flag, frame = self.should_stop_on_exception(frame, event, arg) if flag: self.handle_exception(frame, event, arg) return self.trace_dispatch return self.trace_exception def should_stop_on_exception(self, frame, event, arg): mainDebugger, _filename, info, thread = self._args flag = False if info.pydev_state != STATE_SUSPEND: #and breakpoint is not None: exception, value, trace = arg if trace is not None: #on jython trace is None on the first event exception_breakpoint = get_exception_breakpoint( exception, mainDebugger.break_on_caught_exceptions) if exception_breakpoint is not None: if not exception_breakpoint.notify_on_first_raise_only or just_raised(trace): # print frame.f_code.co_name add_exception_to_frame(frame, (exception, value, trace)) thread.additionalInfo.message = exception_breakpoint.qname flag = True else: flag = False else: try: if mainDebugger.django_exception_break and get_exception_name(exception) in [ 'VariableDoesNotExist', 'TemplateDoesNotExist', 'TemplateSyntaxError'] \ and just_raised(trace) and is_django_exception_break_context(frame): render_frame = find_django_render_frame(frame) if render_frame: suspend_frame = suspend_django( self, mainDebugger, thread, render_frame, CMD_ADD_DJANGO_EXCEPTION_BREAK) if suspend_frame: add_exception_to_frame(suspend_frame, (exception, value, trace)) flag = True thread.additionalInfo.message = 'VariableDoesNotExist' suspend_frame.f_back = frame frame = suspend_frame except : flag = False return flag, frame def handle_exception(self, frame, event, arg): try: # print 'handle_exception', frame.f_lineno, frame.f_code.co_name # We have 3 things in arg: exception type, description, traceback object trace_obj = arg[2] mainDebugger = self._args[0] if not hasattr(trace_obj, 'tb_next'): return #Not always there on Jython... initial_trace_obj = trace_obj if trace_obj.tb_next is None and trace_obj.tb_frame is frame: #I.e.: tb_next should be only None in the context it was thrown (trace_obj.tb_frame is frame is just a double check). if mainDebugger.break_on_exceptions_thrown_in_same_context: #Option: Don't break if an exception is caught in the same function from which it is thrown return else: #Get the trace_obj from where the exception was raised... while trace_obj.tb_next is not None: trace_obj = trace_obj.tb_next if mainDebugger.ignore_exceptions_thrown_in_lines_with_ignore_exception: for check_trace_obj in (initial_trace_obj, trace_obj): filename = GetFilenameAndBase(check_trace_obj.tb_frame)[0] filename_to_lines_where_exceptions_are_ignored = self.filename_to_lines_where_exceptions_are_ignored lines_ignored = filename_to_lines_where_exceptions_are_ignored.get(filename) if lines_ignored is None: lines_ignored = filename_to_lines_where_exceptions_are_ignored[filename] = {} try: curr_stat = os.stat(filename) curr_stat = (curr_stat.st_size, curr_stat.st_mtime) except: curr_stat = None last_stat = self.filename_to_stat_info.get(filename) if last_stat != curr_stat: self.filename_to_stat_info[filename] = curr_stat lines_ignored.clear() try: linecache.checkcache(filename) except: #Jython 2.1 linecache.checkcache() from_user_input = mainDebugger.filename_to_lines_where_exceptions_are_ignored.get(filename) if from_user_input: merged = {} merged.update(lines_ignored) #Override what we have with the related entries that the user entered merged.update(from_user_input) else: merged = lines_ignored exc_lineno = check_trace_obj.tb_lineno # print ('lines ignored', lines_ignored) # print ('user input', from_user_input) # print ('merged', merged, 'curr', exc_lineno) if not DictContains(merged, exc_lineno): #Note: check on merged but update lines_ignored. try: line = linecache.getline(filename, exc_lineno, check_trace_obj.tb_frame.f_globals) except: #Jython 2.1 line = linecache.getline(filename, exc_lineno) if IGNORE_EXCEPTION_TAG.match(line) is not None: lines_ignored[exc_lineno] = 1 return else: #Put in the cache saying not to ignore lines_ignored[exc_lineno] = 0 else: #Ok, dict has it already cached, so, let's check it... if merged.get(exc_lineno, 0): return thread = self._args[3] try: frame_id_to_frame = {} frame_id_to_frame[id(frame)] = frame f = trace_obj.tb_frame while f is not None: frame_id_to_frame[id(f)] = f f = f.f_back f = None thread_id = GetThreadId(thread) pydevd_vars.addAdditionalFrameById(thread_id, frame_id_to_frame) try: mainDebugger.sendCaughtExceptionStack(thread, arg, id(frame)) self.setSuspend(thread, CMD_STEP_CAUGHT_EXCEPTION) self.doWaitSuspend(thread, frame, event, arg) mainDebugger.sendCaughtExceptionStackProceeded(thread) finally: pydevd_vars.removeAdditionalFrameById(thread_id) except: traceback.print_exc() mainDebugger.SetTraceForFrameAndParents(frame) finally: #Clear some local variables... trace_obj = None initial_trace_obj = None check_trace_obj = None f = None frame_id_to_frame = None mainDebugger = None thread = None def trace_dispatch(self, frame, event, arg): main_debugger, filename, info, thread = self._args try: info.is_tracing = True if main_debugger._finishDebuggingSession: return None if getattr(thread, 'pydev_do_not_trace', None): return None if event == 'call' and main_debugger.signature_factory: sendSignatureCallTrace(main_debugger, frame, filename) is_exception_event = event == 'exception' has_exception_breakpoints = main_debugger.break_on_caught_exceptions or main_debugger.django_exception_break if is_exception_event: if has_exception_breakpoints: flag, frame = self.should_stop_on_exception(frame, event, arg) if flag: self.handle_exception(frame, event, arg) return self.trace_dispatch elif event not in ('line', 'call', 'return'): #I believe this can only happen in jython on some frontiers on jython and java code, which we don't want to trace. return None stop_frame = info.pydev_step_stop step_cmd = info.pydev_step_cmd if is_exception_event: breakpoints_for_file = None else: # If we are in single step mode and something causes us to exit the current frame, we need to make sure we break # eventually. Force the step mode to step into and the step stop frame to None. # I.e.: F6 in the end of a function should stop in the next possible position (instead of forcing the user # to make a step in or step over at that location). # Note: this is especially troublesome when we're skipping code with the # @DontTrace comment. if stop_frame is frame and event in ('return', 'exception') and step_cmd in (CMD_STEP_RETURN, CMD_STEP_OVER): info.pydev_step_cmd = CMD_STEP_INTO info.pydev_step_stop = None breakpoints_for_file = main_debugger.breakpoints.get(filename) can_skip = False if info.pydev_state == STATE_RUN: #we can skip if: #- we have no stop marked #- we should make a step return/step over and we're not in the current frame can_skip = (step_cmd is None and stop_frame is None)\ or (step_cmd in (CMD_STEP_RETURN, CMD_STEP_OVER) and stop_frame is not frame) check_stop_on_django_render_call = main_debugger.django_breakpoints and self._is_django_render_call(frame) if check_stop_on_django_render_call: can_skip = False # Let's check to see if we are in a function that has a breakpoint. If we don't have a breakpoint, # we will return nothing for the next trace #also, after we hit a breakpoint and go to some other debugging state, we have to force the set trace anyway, #so, that's why the additional checks are there. if not breakpoints_for_file: if can_skip: if has_exception_breakpoints: return self.trace_exception else: return None else: #checks the breakpoint to see if there is a context match in some function curr_func_name = frame.f_code.co_name #global context is set with an empty name if curr_func_name in ('?', ''): curr_func_name = '' for breakpoint in DictIterValues(breakpoints_for_file): #jython does not support itervalues() #will match either global or some function if breakpoint.func_name in ('None', curr_func_name): break else: # if we had some break, it won't get here (so, that's a context that we want to skip) if can_skip: if has_exception_breakpoints: return self.trace_exception else: return None #We may have hit a breakpoint or we are already in step mode. Either way, let's check what we should do in this frame #print 'NOT skipped', frame.f_lineno, frame.f_code.co_name, event try: line = frame.f_lineno flag = False if event == 'call' and info.pydev_state != STATE_SUSPEND and check_stop_on_django_render_call: flag, frame = self.should_stop_on_django_breakpoint(frame, event, arg) #return is not taken into account for breakpoint hit because we'd have a double-hit in this case #(one for the line and the other for the return). if not flag and event != 'return' and info.pydev_state != STATE_SUSPEND and breakpoints_for_file is not None\ and DictContains(breakpoints_for_file, line): #ok, hit breakpoint, now, we have to discover if it is a conditional breakpoint # lets do the conditional stuff here breakpoint = breakpoints_for_file[line] stop = True if step_cmd == CMD_STEP_OVER and stop_frame is frame and event in ('line', 'return'): stop = False #we don't stop on breakpoint if we have to stop by step-over (it will be processed later) else: condition = breakpoint.condition if condition is not None: try: val = eval(condition, frame.f_globals, frame.f_locals) if not val: return self.trace_dispatch except: if type(condition) != type(''): if hasattr(condition, 'encode'): condition = condition.encode('utf-8') msg = 'Error while evaluating expression: %s\n' % (condition,) sys.stderr.write(msg) traceback.print_exc() if not main_debugger.suspend_on_breakpoint_exception: return self.trace_dispatch else: stop = True try: additional_info = None try: additional_info = thread.additionalInfo except AttributeError: pass #that's ok, no info currently set if additional_info is not None: # add exception_type and stacktrace into thread additional info etype, value, tb = sys.exc_info() try: error = ''.join(traceback.format_exception_only(etype, value)) stack = traceback.extract_stack(f=tb.tb_frame.f_back) # On self.setSuspend(thread, CMD_SET_BREAK) this info will be # sent to the client. additional_info.conditional_breakpoint_exception = \ ('Condition:\n' + condition + '\n\nError:\n' + error, stack) finally: etype, value, tb = None, None, None except: traceback.print_exc() if breakpoint.expression is not None: try: try: val = eval(breakpoint.expression, frame.f_globals, frame.f_locals) except: val = sys.exc_info()[1] finally: if val is not None: thread.additionalInfo.message = val if stop: self.setSuspend(thread, CMD_SET_BREAK) # if thread has a suspend flag, we suspend with a busy wait if info.pydev_state == STATE_SUSPEND: self.doWaitSuspend(thread, frame, event, arg) return self.trace_dispatch except: traceback.print_exc() raise #step handling. We stop when we hit the right frame try: django_stop = False should_skip = False if pydevd_dont_trace.should_trace_hook is not None: if not hasattr(self, 'should_skip'): # I.e.: cache the result on self.should_skip (no need to evaluate the same frame multiple times). # Note that on a code reload, we won't re-evaluate this because in practice, the frame.f_code # Which will be handled by this frame is read-only, so, we can cache it safely. should_skip = self.should_skip = not pydevd_dont_trace.should_trace_hook(frame, filename) else: should_skip = self.should_skip if should_skip: stop = False elif step_cmd == CMD_STEP_INTO: stop = event in ('line', 'return') if is_django_suspended(thread): #django_stop = event == 'call' and is_django_render_call(frame) stop = stop and is_django_resolve_call(frame.f_back) and not is_django_context_get_call(frame) if stop: info.pydev_django_resolve_frame = 1 #we remember that we've go into python code from django rendering frame elif step_cmd == CMD_STEP_OVER: if is_django_suspended(thread): django_stop = event == 'call' and self._is_django_render_call(frame) stop = False else: if event == 'return' and info.pydev_django_resolve_frame is not None and is_django_resolve_call(frame.f_back): #we return to Django suspend mode and should not stop before django rendering frame stop_frame = info.pydev_step_stop = info.pydev_django_resolve_frame info.pydev_django_resolve_frame = None thread.additionalInfo.suspend_type = DJANGO_SUSPEND stop = stop_frame is frame and event in ('line', 'return') elif step_cmd == CMD_SMART_STEP_INTO: stop = False if info.pydev_smart_step_stop is frame: info.pydev_func_name = None info.pydev_smart_step_stop = None if event == 'line' or event == 'exception': curr_func_name = frame.f_code.co_name #global context is set with an empty name if curr_func_name in ('?', '') or curr_func_name is None: curr_func_name = '' if curr_func_name == info.pydev_func_name: stop = True elif step_cmd == CMD_STEP_RETURN: stop = event == 'return' and stop_frame is frame elif step_cmd == CMD_RUN_TO_LINE or step_cmd == CMD_SET_NEXT_STATEMENT: stop = False if event == 'line' or event == 'exception': #Yes, we can only act on line events (weird hum?) #Note: This code is duplicated at pydevd.py #Acting on exception events after debugger breaks with exception curr_func_name = frame.f_code.co_name #global context is set with an empty name if curr_func_name in ('?', ''): curr_func_name = '' if curr_func_name == info.pydev_func_name: line = info.pydev_next_line if frame.f_lineno == line: stop = True else: if frame.f_trace is None: frame.f_trace = self.trace_dispatch frame.f_lineno = line frame.f_trace = None stop = True else: stop = False if django_stop: frame = suspend_django(self, main_debugger, thread, frame) if frame: self.doWaitSuspend(thread, frame, event, arg) elif stop: #event is always == line or return at this point if event == 'line': self.setSuspend(thread, step_cmd) self.doWaitSuspend(thread, frame, event, arg) else: #return event back = frame.f_back if back is not None: #When we get to the pydevd run function, the debugging has actually finished for the main thread #(note that it can still go on for other threads, but for this one, we just make it finish) #So, just setting it to None should be OK base = basename(back.f_code.co_filename) if base == 'pydevd.py' and back.f_code.co_name == 'run': back = None elif base == 'pydevd_traceproperty.py': # We dont want to trace the return event of pydevd_traceproperty (custom property for debugging) #if we're in a return, we want it to appear to the user in the previous frame! return None if back is not None: #if we're in a return, we want it to appear to the user in the previous frame! self.setSuspend(thread, step_cmd) self.doWaitSuspend(thread, back, event, arg) else: #in jython we may not have a back frame info.pydev_step_stop = None info.pydev_step_cmd = None info.pydev_state = STATE_RUN except: traceback.print_exc() info.pydev_step_cmd = None #if we are quitting, let's stop the tracing retVal = None if not main_debugger.quitting: retVal = self.trace_dispatch return retVal finally: info.is_tracing = False #end trace_dispatch if USE_PSYCO_OPTIMIZATION: try: import psyco trace_dispatch = psyco.proxy(trace_dispatch) except ImportError: if hasattr(sys, 'exc_clear'): #jython does not have it sys.exc_clear() #don't keep the traceback pass #ok, psyco not available def should_stop_on_django_breakpoint(self, frame, event, arg): mainDebugger = self._args[0] thread = self._args[3] flag = False template_frame_file = get_template_file_name(frame) #pydev_log.debug("Django is rendering a template: %s\n" % template_frame_file) django_breakpoints_for_file = mainDebugger.django_breakpoints.get(template_frame_file) if django_breakpoints_for_file: #pydev_log.debug("Breakpoints for that file: %s\n" % django_breakpoints_for_file) template_frame_line = get_template_line(frame, template_frame_file) #pydev_log.debug("Tracing template line: %d\n" % template_frame_line) if DictContains(django_breakpoints_for_file, template_frame_line): django_breakpoint = django_breakpoints_for_file[template_frame_line] if django_breakpoint.is_triggered(template_frame_file, template_frame_line): #pydev_log.debug("Breakpoint is triggered.\n") flag = True new_frame = DjangoTemplateFrame( frame, template_frame_file=template_frame_file, template_frame_line=template_frame_line, ) if django_breakpoint.condition is not None: try: val = eval(django_breakpoint.condition, new_frame.f_globals, new_frame.f_locals) if not val: flag = False pydev_log.debug("Condition '%s' is evaluated to %s. Not suspending.\n" % (django_breakpoint.condition, val)) except: pydev_log.info( 'Error while evaluating condition \'%s\': %s\n' % (django_breakpoint.condition, sys.exc_info()[1])) if django_breakpoint.expression is not None: try: try: val = eval(django_breakpoint.expression, new_frame.f_globals, new_frame.f_locals) except: val = sys.exc_info()[1] finally: if val is not None: thread.additionalInfo.message = val if flag: frame = suspend_django(self, mainDebugger, thread, frame) return flag, frame def add_exception_to_frame(frame, exception_info): frame.f_locals['__exception__'] = exception_info