diff options
Diffstat (limited to 'python/helpers/pydev/pydevd_frame.py')
-rw-r--r-- | python/helpers/pydev/pydevd_frame.py | 451 |
1 files changed, 342 insertions, 109 deletions
diff --git a/python/helpers/pydev/pydevd_frame.py b/python/helpers/pydev/pydevd_frame.py index 05faaeb77ff2..374d2818bf87 100644 --- a/python/helpers/pydev/pydevd_frame.py +++ b/python/helpers/pydev/pydevd_frame.py @@ -1,18 +1,29 @@ -from django_debug import is_django_render_call, get_template_file_name, get_template_line, is_django_suspended, suspend_django, is_django_resolve_call, is_django_context_get_call +import linecache +import os.path +import re +import traceback # @Reimport + from django_debug import find_django_render_frame -from django_frame import just_raised -from django_frame import is_django_exception_break_context +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 pydevd_comm import * #@UnusedWildImport -from pydevd_breakpoints import * #@UnusedWildImport -import traceback #@Reimport -import os.path -import sys +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 from pydevd_signature import sendSignatureCallTrace +import pydevd_vars +import pydevd_dont_trace basename = os.path.basename +IGNORE_EXCEPTION_TAG = re.compile('[^#]*#.*@IgnoreException') + + #======================================================================================================================= # PyDBFrame #======================================================================================================================= @@ -22,6 +33,13 @@ class PyDBFrame: 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 @@ -33,84 +51,234 @@ class PyDBFrame: 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.shouldStopOnException(frame, event, arg) + flag, frame = self.should_stop_on_exception(frame, event, arg) if flag: - self.handle_exception(frame, event, arg) - return self.trace_dispatch + self.handle_exception(frame, event, arg) + return self.trace_dispatch return self.trace_exception - def shouldStopOnException(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, dict(mainDebugger.exception_set), NOTIFY_ALWAYS) - if exception_breakpoint is not None: - if not exception_breakpoint.notify_on_first_raise_only or just_raised(trace): - curr_func_name = frame.f_code.co_name - add_exception_to_frame(frame, (exception, value, trace)) - self.setSuspend(thread, CMD_ADD_EXCEPTION_BREAK) - 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 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): - mainDebugger = self._args[0] - thread = self._args[3] - self.doWaitSuspend(thread, frame, event, arg) - mainDebugger.SetTraceForFrameAndParents(frame) + 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): - mainDebugger, filename, info, thread = self._args + main_debugger, filename, info, thread = self._args try: info.is_tracing = True - if mainDebugger._finishDebuggingSession: + if main_debugger._finishDebuggingSession: return None if getattr(thread, 'pydev_do_not_trace', None): return None - if event == 'call': - sendSignatureCallTrace(mainDebugger, frame, filename) + 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 event not in ('line', 'call', 'return'): - if event == 'exception': - (flag, frame) = self.shouldStopOnException(frame, event, arg) + 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 - else: + + 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 + return None - if event is not 'exception': - breakpoints_for_file = mainDebugger.breakpoints.get(filename) + 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 @@ -118,10 +286,11 @@ class PyDBFrame: #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 = (info.pydev_step_cmd is None and info.pydev_step_stop is None)\ - or (info.pydev_step_cmd in (CMD_STEP_RETURN, CMD_STEP_OVER) and info.pydev_step_stop is not 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) - if mainDebugger.django_breakpoints: + 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, @@ -130,7 +299,7 @@ class PyDBFrame: #so, that's why the additional checks are there. if not breakpoints_for_file: if can_skip: - if mainDebugger.always_exception_set or mainDebugger.django_exception_break: + if has_exception_breakpoints: return self.trace_exception else: return None @@ -143,17 +312,18 @@ class PyDBFrame: if curr_func_name in ('?', '<module>'): curr_func_name = '' - for breakpoint in breakpoints_for_file.values(): #jython does not support itervalues() + 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: - #print 'skipping', frame.f_lineno, info.pydev_state, info.pydev_step_stop, info.pydev_step_cmd - return None - else: - breakpoints_for_file = None + 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 @@ -163,33 +333,63 @@ class PyDBFrame: flag = False - if event == 'call' and info.pydev_state != STATE_SUSPEND and mainDebugger.django_breakpoints \ - and is_django_render_call(frame): - (flag, frame) = self.shouldStopOnDjangoBreak(frame, event, arg) + 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): + 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 info.pydev_step_cmd == CMD_STEP_OVER and info.pydev_step_stop is frame and event in ('line', 'return'): + 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: - if breakpoint.condition is not None: + condition = breakpoint.condition + if condition is not None: try: - val = eval(breakpoint.condition, frame.f_globals, frame.f_locals) + val = eval(condition, frame.f_globals, frame.f_locals) if not val: return self.trace_dispatch except: - pydev_log.info('Error while evaluating condition \'%s\': %s\n' % (breakpoint.condition, sys.exc_info()[1])) - - return self.trace_dispatch + 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: @@ -216,30 +416,45 @@ class PyDBFrame: #step handling. We stop when we hit the right frame try: django_stop = False - if info.pydev_step_cmd == CMD_STEP_INTO: + + 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 info.pydev_step_cmd == CMD_STEP_OVER: + elif step_cmd == CMD_STEP_OVER: if is_django_suspended(thread): - django_stop = event == 'call' and is_django_render_call(frame) + 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 - info.pydev_step_stop = info.pydev_django_resolve_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 = info.pydev_step_stop is frame and event in ('line', 'return') + stop = stop_frame is frame and event in ('line', 'return') - elif info.pydev_step_cmd == CMD_SMART_STEP_INTO: + elif step_cmd == CMD_SMART_STEP_INTO: stop = False if info.pydev_smart_step_stop is frame: info.pydev_func_name = None @@ -253,12 +468,12 @@ class PyDBFrame: curr_func_name = '' if curr_func_name == info.pydev_func_name: - stop = True + stop = True - elif info.pydev_step_cmd == CMD_STEP_RETURN: - stop = event == 'return' and info.pydev_step_stop is frame + elif step_cmd == CMD_STEP_RETURN: + stop = event == 'return' and stop_frame is frame - elif info.pydev_step_cmd == CMD_RUN_TO_LINE or info.pydev_step_cmd == CMD_SET_NEXT_STATEMENT: + elif step_cmd == CMD_RUN_TO_LINE or step_cmd == CMD_SET_NEXT_STATEMENT: stop = False if event == 'line' or event == 'exception': @@ -286,13 +501,13 @@ class PyDBFrame: stop = False if django_stop: - frame = suspend_django(self, mainDebugger, thread, frame) + 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, info.pydev_step_cmd) + self.setSuspend(thread, step_cmd) self.doWaitSuspend(thread, frame, event, arg) else: #return event back = frame.f_back @@ -300,12 +515,18 @@ class PyDBFrame: #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 - if basename(back.f_code.co_filename) == 'pydevd.py' and back.f_code.co_name == 'run': + 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, info.pydev_step_cmd) + self.setSuspend(thread, step_cmd) self.doWaitSuspend(thread, back, event, arg) else: #in jython we may not have a back frame @@ -320,7 +541,7 @@ class PyDBFrame: #if we are quitting, let's stop the tracing retVal = None - if not mainDebugger.quitting: + if not main_debugger.quitting: retVal = self.trace_dispatch return retVal @@ -339,24 +560,36 @@ class PyDBFrame: sys.exc_clear() #don't keep the traceback pass #ok, psyco not available - def shouldStopOnDjangoBreak(self, frame, event, arg): - mainDebugger, filename, info, thread = self._args + def should_stop_on_django_breakpoint(self, frame, event, arg): + mainDebugger = self._args[0] + thread = self._args[3] flag = False - filename = get_template_file_name(frame) - pydev_log.debug("Django is rendering a template: %s\n" % filename) - django_breakpoints_for_file = mainDebugger.django_breakpoints.get(filename) + 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_line = get_template_line(frame) - pydev_log.debug("Tracing template line: %d\n" % template_line) - if DictContains(django_breakpoints_for_file, template_line): - django_breakpoint = django_breakpoints_for_file[template_line] + #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") - if django_breakpoint.is_triggered(frame): - pydev_log.debug("Breakpoint is triggered.\n") flag = True - new_frame = DjangoTemplateFrame(frame) + new_frame = DjangoTemplateFrame( + frame, + template_frame_file=template_frame_file, + template_frame_line=template_frame_line, + ) if django_breakpoint.condition is not None: try: @@ -379,7 +612,7 @@ class PyDBFrame: thread.additionalInfo.message = val if flag: frame = suspend_django(self, mainDebugger, thread, frame) - return (flag, frame) + return flag, frame def add_exception_to_frame(frame, exception_info): frame.f_locals['__exception__'] = exception_info
\ No newline at end of file |