home *** CD-ROM | disk | FTP | other *** search
/ PC World 2001 April / PCWorld_2001-04_cd.bin / Software / TemaCD / webclean / !!!python!!! / BeOpen-Python-2.0.exe / BDB.PY < prev    next >
Encoding:
Python Source  |  2000-09-28  |  18.3 KB  |  559 lines

  1. """Debugger basics"""
  2.  
  3. import sys
  4. import os
  5. import types
  6.  
  7. BdbQuit = 'bdb.BdbQuit' # Exception to give up completely
  8.  
  9.  
  10. class Bdb:
  11.     
  12.     """Generic Python debugger base class.
  13.  
  14.     This class takes care of details of the trace facility;
  15.     a derived class should implement user interaction.
  16.     The standard debugger class (pdb.Pdb) is an example.
  17.     """
  18.  
  19.     def __init__(self):
  20.         self.breaks = {}
  21.         self.fncache = {}
  22.  
  23.     def canonic(self, filename):
  24.         canonic = self.fncache.get(filename)
  25.         if not canonic:
  26.             canonic = os.path.abspath(filename)
  27.             self.fncache[filename] = canonic
  28.         return canonic
  29.     
  30.     def reset(self):
  31.         import linecache
  32.         linecache.checkcache()
  33.         self.botframe = None
  34.         self.stopframe = None
  35.         self.returnframe = None
  36.         self.quitting = 0
  37.     
  38.     def trace_dispatch(self, frame, event, arg):
  39.         if self.quitting:
  40.             return # None
  41.         if event == 'line':
  42.             return self.dispatch_line(frame)
  43.         if event == 'call':
  44.             return self.dispatch_call(frame, arg)
  45.         if event == 'return':
  46.             return self.dispatch_return(frame, arg)
  47.         if event == 'exception':
  48.             return self.dispatch_exception(frame, arg)
  49.         print 'bdb.Bdb.dispatch: unknown debugging event:', `event`
  50.         return self.trace_dispatch
  51.     
  52.     def dispatch_line(self, frame):
  53.         if self.stop_here(frame) or self.break_here(frame):
  54.             self.user_line(frame)
  55.             if self.quitting: raise BdbQuit
  56.         return self.trace_dispatch
  57.     
  58.     def dispatch_call(self, frame, arg):
  59.         # XXX 'arg' is no longer used
  60.         if self.botframe is None:
  61.             # First call of dispatch since reset()
  62.             self.botframe = frame
  63.             return self.trace_dispatch
  64.         if not (self.stop_here(frame) or self.break_anywhere(frame)):
  65.             # No need to trace this function
  66.             return # None
  67.         self.user_call(frame, arg)
  68.         if self.quitting: raise BdbQuit
  69.         return self.trace_dispatch
  70.     
  71.     def dispatch_return(self, frame, arg):
  72.         if self.stop_here(frame) or frame == self.returnframe:
  73.             self.user_return(frame, arg)
  74.             if self.quitting: raise BdbQuit
  75.     
  76.     def dispatch_exception(self, frame, arg):
  77.         if self.stop_here(frame):
  78.             self.user_exception(frame, arg)
  79.             if self.quitting: raise BdbQuit
  80.         return self.trace_dispatch
  81.     
  82.     # Normally derived classes don't override the following
  83.     # methods, but they may if they want to redefine the
  84.     # definition of stopping and breakpoints.
  85.     
  86.     def stop_here(self, frame):
  87.         if self.stopframe is None:
  88.             return 1
  89.         if frame is self.stopframe:
  90.             return 1
  91.         while frame is not None and frame is not self.stopframe:
  92.             if frame is self.botframe:
  93.                 return 1
  94.             frame = frame.f_back
  95.         return 0
  96.  
  97.     def break_here(self, frame):
  98.         filename = self.canonic(frame.f_code.co_filename)
  99.         if not self.breaks.has_key(filename):
  100.             return 0
  101.         lineno = frame.f_lineno
  102.         if not lineno in self.breaks[filename]:
  103.             return 0
  104.         # flag says ok to delete temp. bp
  105.         (bp, flag) = effective(filename, lineno, frame)
  106.         if bp:
  107.             self.currentbp = bp.number
  108.             if (flag and bp.temporary):
  109.                 self.do_clear(str(bp.number))
  110.             return 1
  111.         else:
  112.             return 0
  113.     
  114.     def break_anywhere(self, frame):
  115.         return self.breaks.has_key(
  116.             self.canonic(frame.f_code.co_filename))
  117.     
  118.     # Derived classes should override the user_* methods
  119.     # to gain control.
  120.     
  121.     def user_call(self, frame, argument_list):
  122.         """This method is called when there is the remote possibility
  123.         that we ever need to stop in this function."""
  124.         pass
  125.     
  126.     def user_line(self, frame):
  127.         """This method is called when we stop or break at this line."""
  128.         pass
  129.     
  130.     def user_return(self, frame, return_value):
  131.         """This method is called when a return trap is set here."""
  132.         pass
  133.     
  134.     def user_exception(self, frame, (exc_type, exc_value, exc_traceback)):
  135.         """This method is called if an exception occurs,
  136.         but only if we are to stop at or just below this level."""
  137.         pass
  138.     
  139.     # Derived classes and clients can call the following methods
  140.     # to affect the stepping state.
  141.     
  142.     def set_step(self):
  143.         """Stop after one line of code."""
  144.         self.stopframe = None
  145.         self.returnframe = None
  146.         self.quitting = 0
  147.     
  148.     def set_next(self, frame):
  149.         """Stop on the next line in or below the given frame."""
  150.         self.stopframe = frame
  151.         self.returnframe = None
  152.         self.quitting = 0
  153.     
  154.     def set_return(self, frame):
  155.         """Stop when returning from the given frame."""
  156.         self.stopframe = frame.f_back
  157.         self.returnframe = frame
  158.         self.quitting = 0
  159.     
  160.     def set_trace(self):
  161.         """Start debugging from here."""
  162.         try:
  163.             1 + ''
  164.         except:
  165.             frame = sys.exc_info()[2].tb_frame.f_back
  166.         self.reset()
  167.         while frame:
  168.             frame.f_trace = self.trace_dispatch
  169.             self.botframe = frame
  170.             frame = frame.f_back
  171.         self.set_step()
  172.         sys.settrace(self.trace_dispatch)
  173.  
  174.     def set_continue(self):
  175.         # Don't stop except at breakpoints or when finished
  176.         self.stopframe = self.botframe
  177.         self.returnframe = None
  178.         self.quitting = 0
  179.         if not self.breaks:
  180.             # no breakpoints; run without debugger overhead
  181.             sys.settrace(None)
  182.             try:
  183.                 1 + ''  # raise an exception
  184.             except:
  185.                 frame = sys.exc_info()[2].tb_frame.f_back
  186.             while frame and frame is not self.botframe:
  187.                 del frame.f_trace
  188.                 frame = frame.f_back
  189.     
  190.     def set_quit(self):
  191.         self.stopframe = self.botframe
  192.         self.returnframe = None
  193.         self.quitting = 1
  194.         sys.settrace(None)
  195.     
  196.     # Derived classes and clients can call the following methods
  197.     # to manipulate breakpoints.  These methods return an
  198.     # error message is something went wrong, None if all is well.
  199.     # Set_break prints out the breakpoint line and file:lineno.
  200.     # Call self.get_*break*() to see the breakpoints or better
  201.     # for bp in Breakpoint.bpbynumber: if bp: bp.bpprint().
  202.     
  203.     def set_break(self, filename, lineno, temporary=0, cond = None):
  204.         filename = self.canonic(filename)
  205.         import linecache # Import as late as possible
  206.         line = linecache.getline(filename, lineno)
  207.         if not line:
  208.             return 'Line %s:%d does not exist' % (filename,
  209.                                    lineno)
  210.         if not self.breaks.has_key(filename):
  211.             self.breaks[filename] = []
  212.         list = self.breaks[filename]
  213.         if not lineno in list:
  214.             list.append(lineno)
  215.         bp = Breakpoint(filename, lineno, temporary, cond)
  216.  
  217.     def clear_break(self, filename, lineno):
  218.         filename = self.canonic(filename)
  219.         if not self.breaks.has_key(filename):
  220.             return 'There are no breakpoints in %s' % filename
  221.         if lineno not in self.breaks[filename]:
  222.             return 'There is no breakpoint at %s:%d' % (filename,
  223.                                     lineno)
  224.         # If there's only one bp in the list for that file,line
  225.         # pair, then remove the breaks entry
  226.         for bp in Breakpoint.bplist[filename, lineno][:]:
  227.             bp.deleteMe()
  228.         if not Breakpoint.bplist.has_key((filename, lineno)):
  229.             self.breaks[filename].remove(lineno)
  230.         if not self.breaks[filename]:
  231.             del self.breaks[filename]
  232.     
  233.     def clear_bpbynumber(self, arg):
  234.         try:
  235.             number = int(arg)
  236.         except:
  237.             return 'Non-numeric breakpoint number (%s)' % arg
  238.         try:
  239.             bp = Breakpoint.bpbynumber[number]
  240.         except IndexError:
  241.             return 'Breakpoint number (%d) out of range' % number
  242.         if not bp:
  243.             return 'Breakpoint (%d) already deleted' % number
  244.         self.clear_break(bp.file, bp.line)
  245.  
  246.     def clear_all_file_breaks(self, filename):
  247.         filename = self.canonic(filename)
  248.         if not self.breaks.has_key(filename):
  249.             return 'There are no breakpoints in %s' % filename
  250.         for line in self.breaks[filename]:
  251.             blist = Breakpoint.bplist[filename, line]
  252.             for bp in blist:
  253.                 bp.deleteMe()
  254.         del self.breaks[filename]
  255.     
  256.     def clear_all_breaks(self):
  257.         if not self.breaks:
  258.             return 'There are no breakpoints'
  259.         for bp in Breakpoint.bpbynumber:
  260.             if bp:
  261.                 bp.deleteMe()
  262.         self.breaks = {}
  263.     
  264.     def get_break(self, filename, lineno):
  265.         filename = self.canonic(filename)
  266.         return self.breaks.has_key(filename) and \
  267.             lineno in self.breaks[filename]
  268.     
  269.     def get_breaks(self, filename, lineno):
  270.         filename = self.canonic(filename)
  271.         return self.breaks.has_key(filename) and \
  272.             lineno in self.breaks[filename] and \
  273.             Breakpoint.bplist[filename, lineno] or []
  274.     
  275.     def get_file_breaks(self, filename):
  276.         filename = self.canonic(filename)
  277.         if self.breaks.has_key(filename):
  278.             return self.breaks[filename]
  279.         else:
  280.             return []
  281.     
  282.     def get_all_breaks(self):
  283.         return self.breaks
  284.     
  285.     # Derived classes and clients can call the following method
  286.     # to get a data structure representing a stack trace.
  287.     
  288.     def get_stack(self, f, t):
  289.         stack = []
  290.         if t and t.tb_frame is f:
  291.             t = t.tb_next
  292.         while f is not None:
  293.             stack.append((f, f.f_lineno))
  294.             if f is self.botframe:
  295.                 break
  296.             f = f.f_back
  297.         stack.reverse()
  298.         i = max(0, len(stack) - 1)
  299.         while t is not None:
  300.             stack.append((t.tb_frame, t.tb_lineno))
  301.             t = t.tb_next
  302.         return stack, i
  303.     
  304.     # 
  305.     
  306.     def format_stack_entry(self, frame_lineno, lprefix=': '):
  307.         import linecache, repr, string
  308.         frame, lineno = frame_lineno
  309.         filename = self.canonic(frame.f_code.co_filename)
  310.         s = filename + '(' + `lineno` + ')'
  311.         if frame.f_code.co_name:
  312.             s = s + frame.f_code.co_name
  313.         else:
  314.             s = s + "<lambda>"
  315.         if frame.f_locals.has_key('__args__'):
  316.             args = frame.f_locals['__args__']
  317.         else:
  318.             args = None
  319.         if args:
  320.             s = s + repr.repr(args)
  321.         else:
  322.             s = s + '()'
  323.         if frame.f_locals.has_key('__return__'):
  324.             rv = frame.f_locals['__return__']
  325.             s = s + '->'
  326.             s = s + repr.repr(rv)
  327.         line = linecache.getline(filename, lineno)
  328.         if line: s = s + lprefix + string.strip(line)
  329.         return s
  330.     
  331.     # The following two methods can be called by clients to use
  332.     # a debugger to debug a statement, given as a string.
  333.     
  334.     def run(self, cmd, globals=None, locals=None):
  335.         if globals is None:
  336.             import __main__
  337.             globals = __main__.__dict__
  338.         if locals is None:
  339.             locals = globals
  340.         self.reset()
  341.         sys.settrace(self.trace_dispatch)
  342.         if not isinstance(cmd, types.CodeType):
  343.             cmd = cmd+'\n'
  344.         try:
  345.             try:
  346.                 exec cmd in globals, locals
  347.             except BdbQuit:
  348.                 pass
  349.         finally:
  350.             self.quitting = 1
  351.             sys.settrace(None)
  352.     
  353.     def runeval(self, expr, globals=None, locals=None):
  354.         if globals is None:
  355.             import __main__
  356.             globals = __main__.__dict__
  357.         if locals is None:
  358.             locals = globals
  359.         self.reset()
  360.         sys.settrace(self.trace_dispatch)
  361.         if not isinstance(expr, types.CodeType):
  362.             expr = expr+'\n'
  363.         try:
  364.             try:
  365.                 return eval(expr, globals, locals)
  366.             except BdbQuit:
  367.                 pass
  368.         finally:
  369.             self.quitting = 1
  370.             sys.settrace(None)
  371.  
  372.     def runctx(self, cmd, globals, locals):
  373.         # B/W compatibility
  374.         self.run(cmd, globals, locals)
  375.  
  376.     # This method is more useful to debug a single function call.
  377.  
  378.     def runcall(self, func, *args):
  379.         self.reset()
  380.         sys.settrace(self.trace_dispatch)
  381.         res = None
  382.         try:
  383.             try:
  384.                 res = apply(func, args)
  385.             except BdbQuit:
  386.                 pass
  387.         finally:
  388.             self.quitting = 1
  389.             sys.settrace(None)
  390.         return res
  391.  
  392.  
  393. def set_trace():
  394.     Bdb().set_trace()
  395.  
  396.  
  397. class Breakpoint:
  398.  
  399.     """Breakpoint class
  400.  
  401.     Implements temporary breakpoints, ignore counts, disabling and
  402.     (re)-enabling, and conditionals.
  403.  
  404.     Breakpoints are indexed by number through bpbynumber and by
  405.     the file,line tuple using bplist.  The former points to a
  406.     single instance of class Breakpoint.  The latter points to a
  407.     list of such instances since there may be more than one
  408.     breakpoint per line.
  409.  
  410.     """
  411.  
  412.     # XXX Keeping state in the class is a mistake -- this means
  413.     # you cannot have more than one active Bdb instance.
  414.  
  415.     next = 1        # Next bp to be assigned
  416.     bplist = {}     # indexed by (file, lineno) tuple
  417.     bpbynumber = [None] # Each entry is None or an instance of Bpt
  418.                 # index 0 is unused, except for marking an
  419.                 # effective break .... see effective()
  420.  
  421.     def __init__(self, file, line, temporary=0, cond = None):
  422.         self.file = file    # This better be in canonical form!
  423.         self.line = line
  424.         self.temporary = temporary
  425.         self.cond = cond
  426.         self.enabled = 1
  427.         self.ignore = 0
  428.         self.hits = 0
  429.         self.number = Breakpoint.next
  430.         Breakpoint.next = Breakpoint.next + 1
  431.         # Build the two lists
  432.         self.bpbynumber.append(self)
  433.         if self.bplist.has_key((file, line)):
  434.             self.bplist[file, line].append(self)
  435.         else:
  436.             self.bplist[file, line] = [self]
  437.  
  438.         
  439.     def deleteMe(self):
  440.         index = (self.file, self.line)
  441.         self.bpbynumber[self.number] = None   # No longer in list
  442.         self.bplist[index].remove(self)
  443.         if not self.bplist[index]:
  444.             # No more bp for this f:l combo
  445.             del self.bplist[index]
  446.  
  447.     def enable(self):
  448.         self.enabled = 1
  449.  
  450.     def disable(self):
  451.         self.enabled = 0
  452.  
  453.     def bpprint(self):
  454.         if self.temporary:
  455.            disp = 'del  '
  456.         else:
  457.            disp = 'keep '
  458.         if self.enabled:
  459.            disp = disp + 'yes'
  460.         else:
  461.            disp = disp + 'no '
  462.         print '%-4dbreakpoint    %s at %s:%d' % (self.number, disp,
  463.                              self.file, self.line)
  464.         if self.cond:
  465.             print '\tstop only if %s' % (self.cond,)
  466.         if self.ignore:
  467.             print '\tignore next %d hits' % (self.ignore)
  468.         if (self.hits):
  469.             if (self.hits > 1): ss = 's'
  470.             else: ss = ''
  471.             print ('\tbreakpoint already hit %d time%s' %
  472.                    (self.hits, ss))
  473.  
  474. # -----------end of Breakpoint class----------
  475.  
  476. # Determines if there is an effective (active) breakpoint at this
  477. # line of code.  Returns breakpoint number or 0 if none
  478. def effective(file, line, frame):
  479.     """Determine which breakpoint for this file:line is to be acted upon.
  480.  
  481.     Called only if we know there is a bpt at this
  482.     location.  Returns breakpoint that was triggered and a flag
  483.     that indicates if it is ok to delete a temporary bp.
  484.  
  485.     """
  486.     possibles = Breakpoint.bplist[file,line]
  487.     for i in range(0, len(possibles)):
  488.         b = possibles[i]
  489.         if b.enabled == 0:
  490.             continue
  491.         # Count every hit when bp is enabled
  492.         b.hits = b.hits + 1
  493.         if not b.cond:
  494.             # If unconditional, and ignoring,
  495.             # go on to next, else break
  496.             if b.ignore > 0:
  497.                 b.ignore = b.ignore -1
  498.                 continue
  499.             else:
  500.                 # breakpoint and marker that's ok
  501.                 # to delete if temporary
  502.                 return (b,1)
  503.         else:
  504.             # Conditional bp.
  505.             # Ignore count applies only to those bpt hits where the
  506.             # condition evaluates to true.
  507.             try:
  508.                 val = eval(b.cond, frame.f_globals,
  509.                        frame.f_locals) 
  510.                 if val:
  511.                     if b.ignore > 0:
  512.                         b.ignore = b.ignore -1
  513.                         # continue
  514.                     else:
  515.                         return (b,1)
  516.                 # else:
  517.                 #   continue
  518.             except:
  519.                 # if eval fails, most conservative
  520.                 # thing is to stop on breakpoint
  521.                 # regardless of ignore count. 
  522.                 # Don't delete temporary,
  523.                 # as another hint to user.
  524.                 return (b,0)
  525.     return (None, None)
  526.  
  527. # -------------------- testing --------------------
  528.  
  529. class Tdb(Bdb):
  530.     def user_call(self, frame, args):
  531.         name = frame.f_code.co_name
  532.         if not name: name = '???'
  533.         print '+++ call', name, args
  534.     def user_line(self, frame):
  535.         import linecache, string
  536.         name = frame.f_code.co_name
  537.         if not name: name = '???'
  538.         fn = self.canonic(frame.f_code.co_filename)
  539.         line = linecache.getline(fn, frame.f_lineno)
  540.         print '+++', fn, frame.f_lineno, name, ':', string.strip(line)
  541.     def user_return(self, frame, retval):
  542.         print '+++ return', retval
  543.     def user_exception(self, frame, exc_stuff):
  544.         print '+++ exception', exc_stuff
  545.         self.set_continue()
  546.  
  547. def foo(n):
  548.     print 'foo(', n, ')'
  549.     x = bar(n*10)
  550.     print 'bar returned', x
  551.  
  552. def bar(a):
  553.     print 'bar(', a, ')'
  554.     return a/2
  555.  
  556. def test():
  557.     t = Tdb()
  558.     t.run('import bdb; bdb.foo(10)')
  559.