From e79a8641254a3893040be79b744e48cd1cace27b Mon Sep 17 00:00:00 2001 From: Darren Ranalli Date: Wed, 3 Sep 2008 21:16:09 +0000 Subject: [PATCH] working exception variable dump --- direct/src/showbase/ExceptionVarDump.py | 82 ++++++++++++++++++++----- direct/src/showbase/PythonUtil.py | 36 ++++++++++- direct/src/showbase/ShowBase.py | 3 + direct/src/task/Task.py | 10 +++ 4 files changed, 111 insertions(+), 20 deletions(-) diff --git a/direct/src/showbase/ExceptionVarDump.py b/direct/src/showbase/ExceptionVarDump.py index 0e7575e767..82a49998f6 100755 --- a/direct/src/showbase/ExceptionVarDump.py +++ b/direct/src/showbase/ExceptionVarDump.py @@ -1,8 +1,11 @@ +from pandac.PandaModules import ConfigConfigureGetConfigConfigShowbase as config from direct.directnotify.DirectNotifyGlobal import directNotify from direct.showbase.PythonUtil import fastRepr from exceptions import Exception import sys -import traceback +import types + +notify = directNotify.newCategory("ExceptionVarDump") reentry = 0 @@ -11,30 +14,33 @@ def _varDump__init__(self, *args, **kArgs): if reentry > 0: return reentry += 1 + # frame zero is this frame f = 1 self._savedExcString = None self._savedStackFrames = [] while True: try: frame = sys._getframe(f) + except ValueError, e: + break + else: f += 1 self._savedStackFrames.append(frame) - except: - break self._moved__init__(*args, **kArgs) reentry -= 1 sReentry = 0 -def _varDump__str__(self, *args, **kArgs): +def _varDump__print(exc): global sReentry + global notify if sReentry > 0: return sReentry += 1 - if not self._savedExcString: + if not exc._savedExcString: s = '' foundRun = False - for frame in reversed(self._savedStackFrames): + for frame in reversed(exc._savedStackFrames): filename = frame.f_code.co_filename codename = frame.f_code.co_name if not foundRun and codename != 'run': @@ -48,17 +54,59 @@ def _varDump__str__(self, *args, **kArgs): obj = locals[var] rep = fastRepr(obj) s += '::%s = %s\n' % (var, rep) - self._savedExcString = s - self._savedStackFrames = None - notify = directNotify.newCategory("ExceptionVarDump") - notify.info(self._savedExcString) - str = self._moved__str__(*args, **kArgs) + exc._savedExcString = s + exc._savedStackFrames = None + notify.info(exc._savedExcString) sReentry -= 1 - return str + +oldExcepthook = None +# store these values here so that Task.py can always reliably access these values +# from its main exception handler +wantVariableDump = False +dumpOnExceptionInit = False + +def _excepthookDumpVars(eType, eValue, traceback): + s = 'DUMPING STACK FRAME VARIABLES' + tb = traceback + #import pdb;pdb.set_trace() + foundRun = False + while tb is not None: + frame = tb.tb_frame + code = frame.f_code + tb = tb.tb_next + # skip everything before the 'run' method, those frames have lots of + # not-useful information + if not foundRun: + if code.co_name == 'run': + foundRun = True + else: + continue + s += '\n File "%s", line %s, in %s' % ( + code.co_filename, frame.f_lineno, code.co_name) + for name, val in frame.f_locals.iteritems(): + r = fastRepr(val) + if type(r) is types.StringType: + r = r.replace('\n', '\\n') + s += '\n %s=%s' % (name, r) + s += '\n' + notify.info(s) + oldExcepthook(eType, eValue, traceback) def install(): - if not hasattr(Exception, '_moved__init__'): - Exception._moved__init__ = Exception.__init__ - Exception.__init__ = _varDump__init__ - Exception._moved__str__ = Exception.__str__ - Exception.__str__ = _varDump__str__ + global oldExcepthook + global wantVariableDump + global dumpOnExceptionInit + + wantVariableDump = True + dumpOnExceptionInit = config.GetBool('variable-dump-on-exception-init', 0) + if dumpOnExceptionInit: + # this mode doesn't completely work because exception objects + # thrown by the interpreter don't get created until the + # stack has been unwound and an except block has been reached + if not hasattr(Exception, '_moved__init__'): + Exception._moved__init__ = Exception.__init__ + Exception.__init__ = _varDump__init__ + else: + if sys.excepthook is not _excepthookDumpVars: + oldExcepthook = sys.excepthook + sys.excepthook = _excepthookDumpVars diff --git a/direct/src/showbase/PythonUtil.py b/direct/src/showbase/PythonUtil.py index 01975f4a0a..054eed924c 100644 --- a/direct/src/showbase/PythonUtil.py +++ b/direct/src/showbase/PythonUtil.py @@ -2244,11 +2244,29 @@ def gcDebugOn(): import gc return (gc.get_debug() & gc.DEBUG_SAVEALL) == gc.DEBUG_SAVEALL +# base class for all Panda C++ objects +# libdtoolconfig doesn't seem to have this, grab it off of PandaNode +dtoolSuperBase = None + +def _getDtoolSuperBase(): + global dtoolSuperBase + from pandac.PandaModules import PandaNode + dtoolSuperBase = PandaNode('').__class__.__bases__[0].__bases__[0].__bases__[0] + def safeRepr(obj): + global dtoolSuperBase + if dtoolSuperBase is None: + _getDtoolSuperBase() + + if isinstance(obj, dtoolSuperBase): + # repr of C++ object could crash, particularly if the object has been deleted + return '<%s.%s instance at %s>' % ( + obj.__class__.__module__, obj.__class__.__name__, hex(id(obj))) + try: return repr(obj) except: - return '<** FAILED REPR OF %s **>' % obj.__class__.__name__ + return '<** FAILED REPR OF %s instance at %s **>' % (obj.__class__.__name__, hex(id(obj))) def fastRepr(obj, maxLen=200, strFactor=10, _visitedIds=None): """ caps the length of iterable types, so very large objects will print faster. @@ -2302,7 +2320,10 @@ def fastRepr(obj, maxLen=200, strFactor=10, _visitedIds=None): else: return safeRepr(obj) else: - return safeRepr(obj) + r = safeRepr(obj) + if len(r) > maxLen: + r = r[:maxLen] + return r except: return '<** FAILED REPR OF %s **>' % obj.__class__.__name__ @@ -2445,11 +2466,20 @@ class RefCounter: return result def itype(obj): + # version of type that gives more complete information about instance types + global dtoolSuperBase t = type(obj) if t is types.InstanceType: return '%s of >' % (repr(types.InstanceType)[:-1], - str(obj.__class__)) + str(obj.__class__)) else: + # C++ object instances appear to be types via type() + # check if this is a C++ object + if dtoolSuperBase is None: + _getDtoolSuperBase() + if isinstance(obj, dtoolSuperBase): + return '%s of %s>' % (repr(types.InstanceType)[:-1], + str(obj.__class__)) return t def deeptype(obj, maxLen=100, _visitedIds=None): diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index 28567d8afd..2fd980558d 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -35,6 +35,7 @@ import Loader import time from direct.fsm import ClassicFSM from direct.fsm import State +from direct.showbase import ExceptionVarDump import DirectObject import SfxPlayer if __debug__: @@ -69,6 +70,8 @@ class ShowBase(DirectObject.DirectObject): notify = directNotify.newCategory("ShowBase") def __init__(self): + if config.GetBool('want-variable-dump', 1): + ExceptionVarDump.install() # Locate the directory containing the main program maindir=os.path.abspath(sys.path[0]) diff --git a/direct/src/task/Task.py b/direct/src/task/Task.py index a0a728ecab..cfca519ead 100644 --- a/direct/src/task/Task.py +++ b/direct/src/task/Task.py @@ -14,6 +14,7 @@ from pandac.libpandaexpressModules import * from direct.directnotify.DirectNotifyGlobal import * from direct.showbase.PythonUtil import * from direct.showbase.MessengerGlobal import * +from direct.showbase import ExceptionVarDump import time import fnmatch import string @@ -981,6 +982,15 @@ class TaskManager: self.stop() else: raise + except Exception, e: + if self.extendedExceptions: + self.stop() + print_exc_plus() + else: + if (ExceptionVarDump.wantVariableDump and + ExceptionVarDump.dumpOnExceptionInit): + ExceptionVarDump._varDump__print(e) + raise except: if self.extendedExceptions: self.stop()