Bug fix: closing the file
[codimension.git] / src / debugger / client / base_cdm_dbg.py
blob039643553f8084954b498738188a37c568ee3485
2 # -*- coding: utf-8 -*-
4 # codimension - graphics python two-way code editor and analyzer
5 # Copyright (C) 2010 Sergey Satskiy <sergey.satskiy@gmail.com>
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 # $Id$
24 # The file was taken from eric 4 and adopted for codimension.
25 # Original copyright:
26 # Copyright (c) 2002 - 2012 Detlev Offenbach <detlev@die-offenbachs.de>
29 """
30 Module implementing the debug base class.
31 """
33 import sys
34 import bdb
35 import os
36 import types
37 import atexit
38 import inspect
40 from protocol_cdm_dbg import ( ResponseClearWatch, ResponseClearBreak,
41 ResponseLine, ResponseSyntax, ResponseException )
43 RECURSION_LIMIT = 64
46 def setRecursionLimit( limit ):
47 " Sets the recursion limit "
48 global RECURSION_LIMIT
49 RECURSION_LIMIT = limit
50 return
52 class DebugBase( bdb.Bdb ):
53 """
54 Base class of the debugger - the part which is external to IDE
56 Provides simple wrapper methods around bdb for the 'owning' client to
57 call to step etc.
58 """
60 def __init__( self, dbgClient ):
61 """
62 Constructor
64 @param dbgClient the owning client
65 """
66 bdb.Bdb.__init__( self )
68 self._dbgClient = dbgClient
69 self._mainThread = 1
71 self.breaks = self._dbgClient.breakpoints
73 self.__event = ""
74 self.__isBroken = ""
75 self.cFrame = None
77 # current frame we are at
78 self.currentFrame = None
79 self.currentFrameLocals = None
81 # frame that we are stepping in, can be different than currentFrame
82 self.stepFrame = None
84 # provide a hook to perform a hard breakpoint
85 # Use it like this:
86 # if hasattr(sys, 'breakpoint'): sys.breakpoint()
87 sys.breakpoint = self.set_trace
89 # initialize parent
90 bdb.Bdb.reset( self )
92 self.__recursionDepth = -1
93 self.setRecursionDepth( inspect.currentframe() )
94 return
96 def getCurrentFrame( self ):
97 """
98 Provides the current frame.
100 @return the current frame
102 return self.currentFrame
104 def getCurrentFrameLocals( self ):
106 Provides the locals dictionary of the current frame.
108 @return locals dictionary of the current frame
110 return self.currentFrameLocals
112 def step( self, traceMode ):
114 Performs step in this thread.
116 @param traceMode If it is non-zero, then the step is a step into,
117 otherwise it is a step over.
119 self.stepFrame = self.currentFrame
120 if traceMode:
121 self.currentFrame = None
122 self.set_step()
123 else:
124 self.set_next( self.currentFrame )
125 return
127 def stepOut( self ):
129 Performs a step out of the current call
131 self.stepFrame = self.currentFrame
132 self.set_return( self.currentFrame )
133 return
135 def go( self, special ):
137 Resumes the thread.
139 It resumes the thread stopping only at breakpoints or exceptions.
141 @param special flag indicating a special continue operation
143 self.currentFrame = None
144 self.set_continue( special )
145 return
147 def setRecursionDepth( self, frame ):
149 Determines the current recursion depth
151 @param frame The current stack frame.
153 self.__recursionDepth = 0
154 while frame is not None:
155 self.__recursionDepth += 1
156 frame = frame.f_back
157 return
159 def profile( self, frame, event, arg ):
161 Public method used to trace some stuff independant of the debugger
162 trace function.
164 @param frame The current stack frame.
165 @param event The trace event (string)
166 @param arg The arguments
168 if event == 'return':
169 self.cFrame = frame.f_back
170 self.__recursionDepth -= 1
171 elif event == 'call':
172 self.cFrame = frame
173 self.__recursionDepth += 1
174 if self.__recursionDepth > RECURSION_LIMIT:
175 raise RuntimeError(
176 'maximum recursion depth exceeded\n'
177 '(offending frame is too down the stack)' )
178 return
180 def trace_dispatch( self, frame, event, arg ):
182 Reimplemented from bdb.py to do some special things.
184 This specialty is to check the connection to the debug server
185 for new events (i.e. new breakpoints) while we are going through
186 the code.
188 @param frame The current stack frame.
189 @param event The trace event (string)
190 @param arg The arguments
191 @return local trace function """
192 if self.quitting:
193 return # None
195 # give the client a chance to push through new break points.
196 self._dbgClient.eventPoll()
198 self.__event = event
199 self.__isBroken = False
201 if event == 'line':
202 return self.dispatch_line( frame )
203 if event == 'call':
204 return self.dispatch_call( frame, arg )
205 if event == 'return':
206 return self.dispatch_return( frame, arg )
207 if event == 'exception':
208 return self.dispatch_exception( frame, arg )
209 if event == 'c_call':
210 return self.trace_dispatch
211 if event == 'c_exception':
212 return self.trace_dispatch
213 if event == 'c_return':
214 return self.trace_dispatch
216 print 'DebugBase.trace_dispatch: unknown debugging event: ' + \
217 str( event )
218 return self.trace_dispatch
220 def dispatch_line( self, frame ):
222 Reimplemented from bdb.py to do some special things.
224 This speciality is to check the connection to the debug server
225 for new events (i.e. new breakpoints) while we are going through
226 the code.
228 @param frame The current stack frame.
229 @return local trace function
231 if self.stop_here( frame ) or self.break_here( frame ):
232 self.user_line( frame )
233 if self.quitting:
234 raise bdb.BdbQuit
235 return self.trace_dispatch
237 def dispatch_return( self, frame, arg ):
239 Reimplemented from bdb.py to handle passive mode cleanly.
241 @param frame The current stack frame.
242 @param arg The arguments
243 @return local trace function
245 if self.stop_here( frame ) or frame == self.returnframe:
246 self.user_return( frame, arg )
247 if self.quitting and not self._dbgClient.passive:
248 raise bdb.BdbQuit
249 return self.trace_dispatch
251 def dispatch_exception( self, frame, arg ):
253 Reimplemented from bdb.py to always call user_exception.
255 @param frame The current stack frame.
256 @param arg The arguments
257 @return local trace function
259 if not self.__skip_it( frame ):
260 self.user_exception( frame, arg )
261 if self.quitting:
262 raise bdb.BdbQuit
263 return self.trace_dispatch
265 def set_trace( self, frame = None ):
267 Overridden method of bdb.py to do some special setup.
269 @param frame frame to start debugging from
271 bdb.Bdb.set_trace( self, frame )
272 sys.setprofile( self.profile )
273 return
275 def set_continue( self, special ):
277 Reimplemented from bdb.py to always get informed of exceptions.
279 @param special flag indicating a special continue operation
281 # Modified version of the one found in bdb.py
282 # Here we only set a new stop frame if it is a normal continue.
283 if not special:
284 self.stopframe = self.botframe
285 self.returnframe = None
286 self.quitting = 0
287 return
289 def set_quit( self ):
291 Public method to quit.
293 It wraps call to bdb to clear the current frame properly.
295 self.currentFrame = None
296 sys.setprofile( None )
297 bdb.Bdb.set_quit( self )
298 return
300 def fix_frame_filename( self, frame ):
302 Public method used to fixup the filename for a given frame.
304 The logic employed here is that if a module was loaded
305 from a .pyc file, then the correct .py to operate with
306 should be in the same path as the .pyc. The reason this
307 logic is needed is that when a .pyc file is generated, the
308 filename embedded and thus what is readable in the code object
309 of the frame object is the fully qualified filepath when the
310 pyc is generated. If files are moved from machine to machine
311 this can break debugging as the .pyc will refer to the .py
312 on the original machine. Another case might be sharing
313 code over a network... This logic deals with that.
315 @param frame the frame object
317 # get module name from __file__
318 if frame.f_globals.has_key( '__file__' ) and \
319 frame.f_globals[ '__file__' ] and \
320 frame.f_globals[ '__file__' ] == frame.f_code.co_filename:
321 root, ext = os.path.splitext( frame.f_globals[ '__file__' ] )
322 if ext in [ '.pyc', '.py', '.pyo' ]:
323 fixedName = root + '.py'
324 if os.path.exists( fixedName ):
325 return fixedName
327 return frame.f_code.co_filename
329 def set_watch( self, cond, temporary = 0 ):
331 Public method to set a watch expression
333 @param cond expression of the watch expression (string)
334 @param temporary flag indicating a temporary watch expression (boolean)
336 bpoint = bdb.Breakpoint( "Watch", 0, temporary, cond )
337 if cond.endswith( '??created??' ) or cond.endswith( '??changed??' ):
338 bpoint.condition, bpoint.special = cond.split()
339 else:
340 bpoint.condition = cond
341 bpoint.special = ""
342 bpoint.values = {}
343 if not self.breaks.has_key( "Watch" ):
344 self.breaks[ "Watch" ] = 1
345 else:
346 self.breaks[ "Watch" ] += 1
347 return
349 def clear_watch( self, cond ):
351 Public method to clear a watch expression
353 @param cond expression of the watch expression to be cleared (string)
355 try:
356 possibles = bdb.Breakpoint.bplist[ "Watch", 0 ]
357 for i in xrange( 0, len( possibles ) ):
358 b = possibles[ i ]
359 if b.cond == cond:
360 b.deleteMe()
361 self.breaks[ "Watch" ] -= 1
362 if self.breaks[ "Watch" ] == 0:
363 del self.breaks[ "Watch" ]
364 break
365 except KeyError:
366 pass
367 return
369 def get_watch( self, cond ):
371 Public method to get a watch expression
373 @param cond expression of the watch expression to be cleared (string)
375 possibles = bdb.Breakpoint.bplist[ "Watch", 0 ]
376 for i in xrange( 0, len( possibles ) ):
377 b = possibles[ i ]
378 if b.cond == cond:
379 return b
380 return
382 def __do_clearWatch( self, cond ):
384 Private method called to clear a temporary watch expression
386 @param cond expression of the watch expression to be cleared (string)
388 self.clear_watch( cond )
389 self._dbgClient.write( '%s%s\n' % ( ResponseClearWatch, cond ) )
390 return
392 def __effective( self, frame ):
394 Determines if a watch expression is effective
396 @param frame the current execution frame
397 @return tuple of watch expression and a flag to indicate, that a temporary
398 watch expression may be deleted (bdb.Breakpoint, boolean)
400 possibles = bdb.Breakpoint.bplist[ "Watch", 0 ]
401 for i in xrange( 0, len( possibles ) ):
402 b = possibles[ i ]
403 if b.enabled == 0:
404 continue
405 if not b.cond:
406 # watch expression without expression shouldn't occur,
407 # just ignore it
408 continue
409 try:
410 val = eval( b.condition, frame.f_globals, frame.f_locals )
411 if b.special:
412 if b.special == '??created??':
413 if b.values[ frame ][ 0 ] == 0:
414 b.values[ frame ][ 0 ] = 1
415 b.values[ frame ][ 1 ] = val
416 return ( b, 1 )
417 else:
418 continue
419 b.values[ frame ][ 0 ] = 1
420 if b.special == '??changed??':
421 if b.values[ frame ][ 1 ] != val:
422 b.values[ frame ][ 1 ] = val
423 if b.values[ frame ][ 2 ] > 0:
424 b.values[ frame ][ 2 ] -= 1
425 continue
426 else:
427 return ( b, 1 )
428 else:
429 continue
430 continue
431 if val:
432 if b.ignore > 0:
433 b.ignore -= 1
434 continue
435 else:
436 return ( b, 1 )
437 except:
438 if b.special:
439 try:
440 b.values[ frame ][ 0 ] = 0
441 except KeyError:
442 b.values[ frame ] = [ 0, None, b.ignore ]
443 continue
444 return ( None, None )
446 def break_here( self, frame ):
447 """ Reimplemented from bdb.py to fix the filename from the frame
449 See fix_frame_filename for more info.
451 @param frame the frame object
452 @return flag indicating the break status (boolean)
454 filename = self.canonic( self.fix_frame_filename( frame ) )
455 if not self.breaks.has_key( filename ) and \
456 not self.breaks.has_key( "Watch" ):
457 return 0
459 if self.breaks.has_key( filename ):
460 lineno = frame.f_lineno
461 if lineno in self.breaks[ filename ]:
462 # flag says ok to delete temp. bp
463 ( bp, flag ) = bdb.effective( filename, lineno, frame )
464 if bp:
465 self.currentbp = bp.number
466 if ( flag and bp.temporary ):
467 self.__do_clear( filename, lineno )
468 return 1
470 if self.breaks.has_key( "Watch" ):
471 # flag says ok to delete temp. bp
472 ( bp, flag ) = self.__effective( frame )
473 if bp:
474 self.currentbp = bp.number
475 if ( flag and bp.temporary ):
476 self.__do_clearWatch( bp.cond )
477 return 1
479 return 0
481 def break_anywhere( self, frame ):
483 Reimplemented from bdb.py to do some special things.
485 These speciality is to fix the filename from the frame
486 (see fix_frame_filename for more info).
488 @param frame the frame object
489 @return flag indicating the break status (boolean)
491 return self.breaks.has_key(
492 self.canonic( self.fix_frame_filename( frame ) ) ) or \
493 ( self.breaks.has_key( "Watch" ) and self.breaks[ "Watch" ] )
495 def get_break( self, filename, lineno ):
497 Reimplemented from bdb.py to get the first
498 breakpoint of a particular line.
500 Only one breakpoint per line is supported so this overwritten
501 method will return this one and only breakpoint.
503 @param filename the filename of the bp to retrieve (string)
504 @param ineno the linenumber of the bp to retrieve (integer)
505 @return breakpoint or None, if there is no bp
507 filename = self.canonic( filename )
508 return self.breaks.has_key( filename ) and \
509 lineno in self.breaks[ filename ] and \
510 bdb.Breakpoint.bplist[ filename, lineno ][ 0 ] or None
512 def __do_clear( self, filename, lineno ):
514 Clears a temporary breakpoint
516 @param filename name of the file the bp belongs to
517 @param lineno linenumber of the bp
519 self.clear_break( filename, lineno )
520 self._dbgClient.write( '%s%s,%d\n' % ( ResponseClearBreak,
521 filename, lineno ) )
522 return
524 def getStack( self ):
526 Provides the stack
528 @return list of lists with file name (string), line number (integer)
529 and function name (string)
531 frame = self.cFrame
532 stack = []
533 while frame is not None:
534 fname = self._dbgClient.absPath( \
535 self.fix_frame_filename( frame ) )
536 fline = frame.f_lineno
537 ffunc = frame.f_code.co_name
539 if ffunc == '?':
540 ffunc = ''
542 stack.append( [ fname, fline, ffunc ] )
544 if frame == self._dbgClient.mainFrame:
545 frame = None
546 else:
547 frame = frame.f_back
549 return stack
551 def user_line( self, frame ):
553 Reimplemented to handle the program about to execute
554 a particular line.
556 @param frame the frame object
558 line = frame.f_lineno
560 # We never stop on line 0.
561 if line == 0:
562 return
564 fn = self._dbgClient.absPath( self.fix_frame_filename( frame ) )
566 # See if we are skipping at the start of a newly loaded program.
567 if self._dbgClient.mainFrame is None:
568 if fn != self._dbgClient.getRunning():
569 return
570 self._dbgClient.mainFrame = frame
572 self.currentFrame = frame
573 self.currentFrameLocals = frame.f_locals
574 # remember the locals because it is reinitialized when accessed
576 fr = frame
577 stack = []
578 while fr is not None:
579 # Reset the trace function so we can be sure
580 # to trace all functions up the stack... This gets around
581 # problems where an exception/breakpoint has occurred
582 # but we had disabled tracing along the way via a None
583 # return from dispatch_call
584 fr.f_trace = self.trace_dispatch
585 fname = self._dbgClient.absPath( self.fix_frame_filename( fr ) )
586 fline = fr.f_lineno
587 ffunc = fr.f_code.co_name
589 if ffunc == '?':
590 ffunc = ''
592 stack.append( [ fname, fline, ffunc ] )
594 if fr == self._dbgClient.mainFrame:
595 fr = None
596 else:
597 fr = fr.f_back
599 self.__isBroken = True
601 self._dbgClient.write( '%s%s\n' % ( ResponseLine, unicode( stack ) ) )
602 self._dbgClient.eventLoop()
604 def user_exception( self, frame,
605 ( exctype, excval, exctb ),
606 unhandled = 0 ):
608 Reimplemented to report an exception to the debug server
610 @param frame the frame object
611 @param exctype the type of the exception
612 @param excval data about the exception
613 @param exctb traceback for the exception
614 @param unhandled flag indicating an uncaught exception
616 if exctype in [ SystemExit, bdb.BdbQuit ]:
617 atexit._run_exitfuncs()
618 if excval is None:
619 excval = 0
620 elif isinstance( excval, ( unicode, str ) ):
621 self._dbgClient.write( excval )
622 excval = 1
623 if isinstance( excval, int ):
624 self._dbgClient.progTerminated( excval )
625 else:
626 self._dbgClient.progTerminated( excval.code )
627 return
629 elif exctype in [ SyntaxError, IndentationError ]:
630 try:
631 message, ( filename, linenr, charnr, text ) = excval
632 except ValueError:
633 exclist = []
634 else:
635 exclist = [ message, [ filename, linenr, charnr ] ]
637 self._dbgClient.write( "%s%s\n" % ( ResponseSyntax,
638 unicode( exclist ) ) )
640 else:
641 if type( exctype ) in [ types.ClassType, # Python up to 2.4
642 types.TypeType ]: # Python 2.5+
643 exctype = exctype.__name__
645 if excval is None:
646 excval = ''
648 if unhandled:
649 exctypetxt = "unhandled %s" % unicode( exctype )
650 else:
651 exctypetxt = unicode( exctype )
652 try:
653 exclist = [ exctypetxt,
654 unicode( excval ).encode(
655 self._dbgClient.getCoding() ) ]
656 except TypeError:
657 exclist = [ exctypetxt, str( excval ) ]
659 if exctb:
660 frlist = self.__extract_stack( exctb )
661 frlist.reverse()
663 self.currentFrame = frlist[ 0 ]
664 self.currentFrameLocals = frlist[0].f_locals
665 # remember the locals because it is reinitialized when accessed
667 for fr in frlist:
668 filename = self._dbgClient.absPath(
669 self.fix_frame_filename( fr ) )
670 linenr = fr.f_lineno
672 fBasename = os.path.basename( filename )
673 if fBasename.endswith( "_cdm_dbg.py" ) or \
674 fBasename == "bdb.py":
675 break
677 exclist.append( [ filename, linenr ] )
679 self._dbgClient.write( "%s%s\n" % ( ResponseException,
680 unicode( exclist ) ) )
682 if exctb is None:
683 return
685 self._dbgClient.eventLoop()
686 return
688 def __extract_stack( self, exctb ):
690 Provides a list of stack frames
692 @param exctb exception traceback
693 @return list of stack frames
695 tb = exctb
696 stack = []
697 while tb is not None:
698 stack.append( tb.tb_frame )
699 tb = tb.tb_next
700 tb = None
701 return stack
703 def user_return( self, frame, retval ):
705 Reimplemented to report program termination to the debug server.
707 @param frame the frame object
708 @param retval the return value of the program
710 # The program has finished if we have just left the first frame.
711 if frame == self._dbgClient.mainFrame and \
712 self._mainThread:
713 atexit._run_exitfuncs()
714 self._dbgClient.progTerminated( retval )
715 elif frame is not self.stepFrame:
716 self.stepFrame = None
717 self.user_line( frame )
718 return
720 def stop_here( self, frame ):
722 Reimplemented to filter out debugger files.
724 Tracing is turned off for files that are part of the
725 debugger that are called from the application being debugged.
727 @param frame the frame object
728 @return flag indicating whether the debugger should stop here
730 if self.__skip_it( frame ):
731 return 0
732 return bdb.Bdb.stop_here( self, frame )
734 def __skip_it( self, frame ):
736 Private method to filter out debugger files.
738 Tracing is turned off for files that are part of the
739 debugger that are called from the application being debugged.
741 @param frame the frame object
742 @return flag indicating whether the debugger should skip this frame
744 fname = self.fix_frame_filename( frame )
746 # Eliminate things like <string> and <stdin>.
747 if fname[ 0 ] == '<':
748 return True
750 #XXX - think of a better way to do this. It's only a convience for
751 #debugging the debugger - when the debugger code is in the current
752 #directory.
753 if ( 'debugger' + os.path.sep + 'client' + os.path.sep ) in fname:
754 return True
756 if self._dbgClient.shouldSkip( fname ):
757 return True
759 return False
761 def isBroken( self ):
763 Public method to return the broken state of the debugger.
765 @return flag indicating the broken state (boolean)
767 return self.__isBroken
769 def getEvent( self ):
771 Public method to return the last debugger event.
773 @return last debugger event (string)
775 return self.__event