Browse Source

working exception variable dump

Darren Ranalli 17 years ago
parent
commit
e79a864125

+ 65 - 17
direct/src/showbase/ExceptionVarDump.py

@@ -1,8 +1,11 @@
+from pandac.PandaModules import ConfigConfigureGetConfigConfigShowbase as config
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.showbase.PythonUtil import fastRepr
 from direct.showbase.PythonUtil import fastRepr
 from exceptions import Exception
 from exceptions import Exception
 import sys
 import sys
-import traceback
+import types
+
+notify = directNotify.newCategory("ExceptionVarDump")
 
 
 reentry = 0
 reentry = 0
 
 
@@ -11,30 +14,33 @@ def _varDump__init__(self, *args, **kArgs):
     if reentry > 0:
     if reentry > 0:
         return
         return
     reentry += 1
     reentry += 1
+    # frame zero is this frame
     f = 1
     f = 1
     self._savedExcString = None
     self._savedExcString = None
     self._savedStackFrames = []
     self._savedStackFrames = []
     while True:
     while True:
         try:
         try:
             frame = sys._getframe(f)
             frame = sys._getframe(f)
+        except ValueError, e:
+            break
+        else:
             f += 1
             f += 1
             self._savedStackFrames.append(frame)
             self._savedStackFrames.append(frame)
-        except:
-            break
     self._moved__init__(*args, **kArgs)
     self._moved__init__(*args, **kArgs)
     reentry -= 1
     reentry -= 1
 
 
 sReentry = 0
 sReentry = 0
 
 
-def _varDump__str__(self, *args, **kArgs):
+def _varDump__print(exc):
     global sReentry
     global sReentry
+    global notify
     if sReentry > 0:
     if sReentry > 0:
         return
         return
     sReentry += 1
     sReentry += 1
-    if not self._savedExcString:
+    if not exc._savedExcString:
         s = ''
         s = ''
         foundRun = False
         foundRun = False
-        for frame in reversed(self._savedStackFrames):
+        for frame in reversed(exc._savedStackFrames):
             filename = frame.f_code.co_filename
             filename = frame.f_code.co_filename
             codename = frame.f_code.co_name
             codename = frame.f_code.co_name
             if not foundRun and codename != 'run':
             if not foundRun and codename != 'run':
@@ -48,17 +54,59 @@ def _varDump__str__(self, *args, **kArgs):
                 obj = locals[var]
                 obj = locals[var]
                 rep = fastRepr(obj)
                 rep = fastRepr(obj)
                 s += '::%s = %s\n' % (var, rep)
                 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
     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():
 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

+ 33 - 3
direct/src/showbase/PythonUtil.py

@@ -2244,11 +2244,29 @@ def gcDebugOn():
     import gc
     import gc
     return (gc.get_debug() & gc.DEBUG_SAVEALL) == gc.DEBUG_SAVEALL
     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):
 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:
     try:
         return repr(obj)
         return repr(obj)
     except:
     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):
 def fastRepr(obj, maxLen=200, strFactor=10, _visitedIds=None):
     """ caps the length of iterable types, so very large objects will print faster.
     """ 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:
             else:
                 return safeRepr(obj)
                 return safeRepr(obj)
         else:
         else:
-            return safeRepr(obj)
+            r = safeRepr(obj)
+            if len(r) > maxLen:
+                r = r[:maxLen]
+            return r
     except:
     except:
         return '<** FAILED REPR OF %s **>' % obj.__class__.__name__
         return '<** FAILED REPR OF %s **>' % obj.__class__.__name__
 
 
@@ -2445,11 +2466,20 @@ class RefCounter:
         return result
         return result
 
 
 def itype(obj):
 def itype(obj):
+    # version of type that gives more complete information about instance types
+    global dtoolSuperBase
     t = type(obj)
     t = type(obj)
     if t is types.InstanceType:
     if t is types.InstanceType:
         return '%s of <class %s>>' % (repr(types.InstanceType)[:-1],
         return '%s of <class %s>>' % (repr(types.InstanceType)[:-1],
-                                     str(obj.__class__))
+                                      str(obj.__class__))
     else:
     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
         return t
 
 
 def deeptype(obj, maxLen=100, _visitedIds=None):
 def deeptype(obj, maxLen=100, _visitedIds=None):

+ 3 - 0
direct/src/showbase/ShowBase.py

@@ -35,6 +35,7 @@ import Loader
 import time
 import time
 from direct.fsm import ClassicFSM
 from direct.fsm import ClassicFSM
 from direct.fsm import State
 from direct.fsm import State
+from direct.showbase import ExceptionVarDump
 import DirectObject
 import DirectObject
 import SfxPlayer
 import SfxPlayer
 if __debug__:
 if __debug__:
@@ -69,6 +70,8 @@ class ShowBase(DirectObject.DirectObject):
     notify = directNotify.newCategory("ShowBase")
     notify = directNotify.newCategory("ShowBase")
 
 
     def __init__(self):
     def __init__(self):
+        if config.GetBool('want-variable-dump', 1):
+            ExceptionVarDump.install()
 
 
         # Locate the directory containing the main program
         # Locate the directory containing the main program
         maindir=os.path.abspath(sys.path[0])
         maindir=os.path.abspath(sys.path[0])

+ 10 - 0
direct/src/task/Task.py

@@ -14,6 +14,7 @@ from pandac.libpandaexpressModules import *
 from direct.directnotify.DirectNotifyGlobal import *
 from direct.directnotify.DirectNotifyGlobal import *
 from direct.showbase.PythonUtil import *
 from direct.showbase.PythonUtil import *
 from direct.showbase.MessengerGlobal import *
 from direct.showbase.MessengerGlobal import *
+from direct.showbase import ExceptionVarDump
 import time
 import time
 import fnmatch
 import fnmatch
 import string
 import string
@@ -981,6 +982,15 @@ class TaskManager:
                         self.stop()
                         self.stop()
                     else:
                     else:
                         raise
                         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:
                 except:
                     if self.extendedExceptions:
                     if self.extendedExceptions:
                         self.stop()
                         self.stop()