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.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

+ 33 - 3
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 <class %s>>' % (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):

+ 3 - 0
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])

+ 10 - 0
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()