ExceptionVarDump.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. __all__ = ["install"]
  2. from panda3d.core import ConfigVariableBool
  3. from direct.directnotify.DirectNotifyGlobal import directNotify
  4. from direct.showbase.PythonUtil import fastRepr, Stack
  5. import sys
  6. import traceback
  7. notify = directNotify.newCategory("ExceptionVarDump")
  8. reentry = 0
  9. def _varDump__init__(self, *args, **kArgs):
  10. global reentry
  11. if reentry > 0:
  12. return
  13. reentry += 1
  14. # frame zero is this frame
  15. f = 1
  16. self._savedExcString = None
  17. self._savedStackFrames = []
  18. while True:
  19. try:
  20. frame = sys._getframe(f)
  21. except ValueError as e:
  22. break
  23. else:
  24. f += 1
  25. self._savedStackFrames.append(frame)
  26. self._moved__init__(*args, **kArgs)
  27. reentry -= 1
  28. sReentry = 0
  29. def _varDump__print(exc) -> None:
  30. global sReentry
  31. global notify
  32. if sReentry > 0:
  33. return
  34. sReentry += 1
  35. if not exc._savedExcString:
  36. s = ''
  37. foundRun = False
  38. for frame in reversed(exc._savedStackFrames):
  39. filename = frame.f_code.co_filename
  40. codename = frame.f_code.co_name
  41. if not foundRun and codename != 'run':
  42. # don't print stack frames before run(),
  43. # they contain builtins and are huge
  44. continue
  45. foundRun = True
  46. s += '\nlocals for %s:%s\n' % (filename, codename)
  47. locals = frame.f_locals
  48. for var in locals:
  49. obj = locals[var]
  50. rep = fastRepr(obj)
  51. s += '::%s = %s\n' % (var, rep)
  52. exc._savedExcString = s
  53. exc._savedStackFrames = None
  54. notify.info(exc._savedExcString)
  55. sReentry -= 1
  56. oldExcepthook = None
  57. # store these values here so that Task.py can always reliably access them
  58. # from its main exception handler
  59. wantStackDumpLog = False
  60. wantStackDumpUpload = False
  61. variableDumpReasons: list = []
  62. dumpOnExceptionInit = False
  63. class _AttrNotFound:
  64. pass
  65. def _excepthookDumpVars(eType, eValue, tb):
  66. origTb = tb
  67. excStrs = traceback.format_exception(eType, eValue, origTb)
  68. s = 'printing traceback in case variable repr crashes the process...\n'
  69. for excStr in excStrs:
  70. s += excStr
  71. notify.info(s)
  72. s = 'DUMPING STACK FRAME VARIABLES'
  73. #import pdb;pdb.set_trace()
  74. #foundRun = False
  75. foundRun = True
  76. while tb is not None:
  77. frame = tb.tb_frame
  78. code = frame.f_code
  79. # this is a list of every string identifier used in this stack frame's code
  80. codeNames = set(code.co_names)
  81. # skip everything before the 'run' method, those frames have lots of
  82. # not-useful information
  83. if not foundRun:
  84. if code.co_name == 'run':
  85. foundRun = True
  86. else:
  87. tb = tb.tb_next
  88. continue
  89. s += '\n File "%s", line %s, in %s' % (
  90. code.co_filename, frame.f_lineno, code.co_name)
  91. stateStack = Stack()
  92. # prime the stack with the variables we should visit from the frame's data structures
  93. # grab all of the local, builtin and global variables that appear in the code's name list
  94. name2obj = {}
  95. for name, obj in frame.f_builtins.items():
  96. if name in codeNames:
  97. name2obj[name] = obj
  98. for name, obj in frame.f_globals.items():
  99. if name in codeNames:
  100. name2obj[name] = obj
  101. for name, obj in frame.f_locals.items():
  102. if name in codeNames:
  103. name2obj[name] = obj
  104. traversedIds = set()
  105. # push them in reverse alphabetical order so they'll be popped in the correct order
  106. for name in sorted(name2obj, reverse=True):
  107. stateStack.push([name, name2obj[name], traversedIds])
  108. while len(stateStack) > 0:
  109. name, obj, traversedIds = stateStack.pop()
  110. #notify.info('%s, %s, %s' % (name, fastRepr(obj), traversedIds))
  111. r = fastRepr(obj, maxLen=10)
  112. if isinstance(r, str):
  113. r = r.replace('\n', '\\n')
  114. s += '\n %s = %s' % (name, r)
  115. # if we've already traversed through this object, don't traverse through it again
  116. if id(obj) not in traversedIds:
  117. attrName2obj = {}
  118. for attrName in codeNames:
  119. attr = getattr(obj, attrName, _AttrNotFound)
  120. if attr is not _AttrNotFound:
  121. # prevent infinite recursion on method wrappers (__init__.__init__.__init__...)
  122. try:
  123. className = attr.__class__.__name__
  124. except Exception:
  125. pass
  126. else:
  127. if className == 'method-wrapper':
  128. continue
  129. attrName2obj[attrName] = attr
  130. if len(attrName2obj) > 0:
  131. ids = set(traversedIds)
  132. ids.add(id(obj))
  133. # push them in reverse alphabetical order so they'll be popped in the correct order
  134. for attrName in sorted(attrName2obj, reverse=True):
  135. obj = attrName2obj[attrName]
  136. stateStack.push(['%s.%s' % (name, attrName), obj, ids])
  137. tb = tb.tb_next
  138. if foundRun:
  139. s += '\n'
  140. if wantStackDumpLog:
  141. notify.info(s)
  142. if wantStackDumpUpload:
  143. excStrs = traceback.format_exception(eType, eValue, origTb)
  144. for excStr in excStrs:
  145. s += excStr
  146. timeMgr = None
  147. try:
  148. timeMgr = base.cr.timeManager
  149. except Exception:
  150. try:
  151. timeMgr = simbase.air.timeManager
  152. except Exception:
  153. pass
  154. if timeMgr:
  155. timeMgr.setStackDump(s)
  156. oldExcepthook(eType, eValue, origTb)
  157. def install(log: bool, upload: bool) -> None:
  158. """Installs the exception hook."""
  159. global oldExcepthook
  160. global wantStackDumpLog
  161. global wantStackDumpUpload
  162. global dumpOnExceptionInit
  163. wantStackDumpLog = log
  164. wantStackDumpUpload = upload
  165. dumpOnExceptionInit = ConfigVariableBool('variable-dump-on-exception-init', False)
  166. if dumpOnExceptionInit:
  167. # this mode doesn't completely work because exception objects
  168. # thrown by the interpreter don't get created until the
  169. # stack has been unwound and an except block has been reached
  170. if not hasattr(Exception, '_moved__init__'):
  171. Exception._moved__init__ = Exception.__init__ # type: ignore[attr-defined]
  172. Exception.__init__ = _varDump__init__ # type: ignore[method-assign]
  173. else:
  174. if sys.excepthook is not _excepthookDumpVars:
  175. oldExcepthook = sys.excepthook
  176. sys.excepthook = _excepthookDumpVars