failure.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. # -*- test-case-name: twisted.test.test_failure -*-
  2. # See also test suite twisted.test.test_pbfailure
  3. # Copyright (c) Twisted Matrix Laboratories.
  4. # See LICENSE for details.
  5. """
  6. Asynchronous-friendly error mechanism.
  7. See L{Failure}.
  8. """
  9. from __future__ import division, absolute_import, print_function
  10. # System Imports
  11. import sys
  12. import linecache
  13. import inspect
  14. import opcode
  15. from inspect import getmro
  16. from twisted.python.compat import NativeStringIO as StringIO
  17. from twisted.python import reflect
  18. from twisted.python._oldstyle import _oldStyle, _shouldEnableNewStyle
  19. count = 0
  20. traceupLength = 4
  21. class DefaultException(Exception):
  22. pass
  23. def format_frames(frames, write, detail="default"):
  24. """Format and write frames.
  25. @param frames: is a list of frames as used by Failure.frames, with
  26. each frame being a list of
  27. (funcName, fileName, lineNumber, locals.items(), globals.items())
  28. @type frames: list
  29. @param write: this will be called with formatted strings.
  30. @type write: callable
  31. @param detail: Four detail levels are available:
  32. default, brief, verbose, and verbose-vars-not-captured.
  33. C{Failure.printDetailedTraceback} uses the latter when the caller asks
  34. for verbose, but no vars were captured, so that an explicit warning
  35. about the missing data is shown.
  36. @type detail: string
  37. """
  38. if detail not in ('default', 'brief', 'verbose',
  39. 'verbose-vars-not-captured'):
  40. raise ValueError(
  41. "Detail must be default, brief, verbose, or "
  42. "verbose-vars-not-captured. (not %r)" % (detail,))
  43. w = write
  44. if detail == "brief":
  45. for method, filename, lineno, localVars, globalVars in frames:
  46. w('%s:%s:%s\n' % (filename, lineno, method))
  47. elif detail == "default":
  48. for method, filename, lineno, localVars, globalVars in frames:
  49. w( ' File "%s", line %s, in %s\n' % (filename, lineno, method))
  50. w( ' %s\n' % linecache.getline(filename, lineno).strip())
  51. elif detail == "verbose-vars-not-captured":
  52. for method, filename, lineno, localVars, globalVars in frames:
  53. w("%s:%d: %s(...)\n" % (filename, lineno, method))
  54. w(' [Capture of Locals and Globals disabled (use captureVars=True)]\n')
  55. elif detail == "verbose":
  56. for method, filename, lineno, localVars, globalVars in frames:
  57. w("%s:%d: %s(...)\n" % (filename, lineno, method))
  58. w(' [ Locals ]\n')
  59. # Note: the repr(val) was (self.pickled and val) or repr(val)))
  60. for name, val in localVars:
  61. w(" %s : %s\n" % (name, repr(val)))
  62. w(' ( Globals )\n')
  63. for name, val in globalVars:
  64. w(" %s : %s\n" % (name, repr(val)))
  65. # slyphon: i have a need to check for this value in trial
  66. # so I made it a module-level constant
  67. EXCEPTION_CAUGHT_HERE = "--- <exception caught here> ---"
  68. class NoCurrentExceptionError(Exception):
  69. """
  70. Raised when trying to create a Failure from the current interpreter
  71. exception state and there is no current exception state.
  72. """
  73. class _Traceback(object):
  74. """
  75. Fake traceback object which can be passed to functions in the standard
  76. library L{traceback} module.
  77. """
  78. def __init__(self, frames):
  79. """
  80. Construct a fake traceback object using a list of frames. Note that
  81. although frames generally include locals and globals, this information
  82. is not kept by this object, since locals and globals are not used in
  83. standard tracebacks.
  84. @param frames: [(methodname, filename, lineno, locals, globals), ...]
  85. """
  86. assert len(frames) > 0, "Must pass some frames"
  87. head, frames = frames[0], frames[1:]
  88. name, filename, lineno, localz, globalz = head
  89. self.tb_frame = _Frame(name, filename)
  90. self.tb_lineno = lineno
  91. if len(frames) == 0:
  92. self.tb_next = None
  93. else:
  94. self.tb_next = _Traceback(frames)
  95. class _Frame(object):
  96. """
  97. A fake frame object, used by L{_Traceback}.
  98. @ivar f_code: fake L{code<types.CodeType>} object
  99. @ivar f_globals: fake f_globals dictionary (usually empty)
  100. @ivar f_locals: fake f_locals dictionary (usually empty)
  101. """
  102. def __init__(self, name, filename):
  103. """
  104. @param name: method/function name for this frame.
  105. @type name: C{str}
  106. @param filename: filename for this frame.
  107. @type name: C{str}
  108. """
  109. self.f_code = _Code(name, filename)
  110. self.f_globals = {}
  111. self.f_locals = {}
  112. class _Code(object):
  113. """
  114. A fake code object, used by L{_Traceback} via L{_Frame}.
  115. """
  116. def __init__(self, name, filename):
  117. self.co_name = name
  118. self.co_filename = filename
  119. @_oldStyle
  120. class Failure:
  121. """
  122. A basic abstraction for an error that has occurred.
  123. This is necessary because Python's built-in error mechanisms are
  124. inconvenient for asynchronous communication.
  125. The C{stack} and C{frame} attributes contain frames. Each frame is a tuple
  126. of (funcName, fileName, lineNumber, localsItems, globalsItems), where
  127. localsItems and globalsItems are the contents of
  128. C{locals().items()}/C{globals().items()} for that frame, or an empty tuple
  129. if those details were not captured.
  130. @ivar value: The exception instance responsible for this failure.
  131. @ivar type: The exception's class.
  132. @ivar stack: list of frames, innermost last, excluding C{Failure.__init__}.
  133. @ivar frames: list of frames, innermost first.
  134. """
  135. pickled = 0
  136. stack = None
  137. # The opcode of "yield" in Python bytecode. We need this in _findFailure in
  138. # order to identify whether an exception was thrown by a
  139. # throwExceptionIntoGenerator.
  140. _yieldOpcode = chr(opcode.opmap["YIELD_VALUE"])
  141. def __init__(self, exc_value=None, exc_type=None, exc_tb=None,
  142. captureVars=False):
  143. """
  144. Initialize me with an explanation of the error.
  145. By default, this will use the current C{exception}
  146. (L{sys.exc_info}()). However, if you want to specify a
  147. particular kind of failure, you can pass an exception as an
  148. argument.
  149. If no C{exc_value} is passed, then an "original" C{Failure} will
  150. be searched for. If the current exception handler that this
  151. C{Failure} is being constructed in is handling an exception
  152. raised by L{raiseException}, then this C{Failure} will act like
  153. the original C{Failure}.
  154. For C{exc_tb} only L{traceback} instances or L{None} are allowed.
  155. If L{None} is supplied for C{exc_value}, the value of C{exc_tb} is
  156. ignored, otherwise if C{exc_tb} is L{None}, it will be found from
  157. execution context (ie, L{sys.exc_info}).
  158. @param captureVars: if set, capture locals and globals of stack
  159. frames. This is pretty slow, and makes no difference unless you
  160. are going to use L{printDetailedTraceback}.
  161. """
  162. global count
  163. count = count + 1
  164. self.count = count
  165. self.type = self.value = tb = None
  166. self.captureVars = captureVars
  167. if isinstance(exc_value, str) and exc_type is None:
  168. raise TypeError("Strings are not supported by Failure")
  169. stackOffset = 0
  170. if exc_value is None:
  171. exc_value = self._findFailure()
  172. if exc_value is None:
  173. self.type, self.value, tb = sys.exc_info()
  174. if self.type is None:
  175. raise NoCurrentExceptionError()
  176. stackOffset = 1
  177. elif exc_type is None:
  178. if isinstance(exc_value, Exception):
  179. self.type = exc_value.__class__
  180. else: #allow arbitrary objects.
  181. self.type = type(exc_value)
  182. self.value = exc_value
  183. else:
  184. self.type = exc_type
  185. self.value = exc_value
  186. if isinstance(self.value, Failure):
  187. self.__dict__ = self.value.__dict__
  188. return
  189. if tb is None:
  190. if exc_tb:
  191. tb = exc_tb
  192. elif getattr(self.value, "__traceback__", None):
  193. # Python 3
  194. tb = self.value.__traceback__
  195. frames = self.frames = []
  196. stack = self.stack = []
  197. # added 2003-06-23 by Chris Armstrong. Yes, I actually have a
  198. # use case where I need this traceback object, and I've made
  199. # sure that it'll be cleaned up.
  200. self.tb = tb
  201. if tb:
  202. f = tb.tb_frame
  203. elif not isinstance(self.value, Failure):
  204. # we don't do frame introspection since it's expensive,
  205. # and if we were passed a plain exception with no
  206. # traceback, it's not useful anyway
  207. f = stackOffset = None
  208. while stackOffset and f:
  209. # This excludes this Failure.__init__ frame from the
  210. # stack, leaving it to start with our caller instead.
  211. f = f.f_back
  212. stackOffset -= 1
  213. # Keeps the *full* stack. Formerly in spread.pb.print_excFullStack:
  214. #
  215. # The need for this function arises from the fact that several
  216. # PB classes have the peculiar habit of discarding exceptions
  217. # with bareword "except:"s. This premature exception
  218. # catching means tracebacks generated here don't tend to show
  219. # what called upon the PB object.
  220. while f:
  221. if captureVars:
  222. localz = f.f_locals.copy()
  223. if f.f_locals is f.f_globals:
  224. globalz = {}
  225. else:
  226. globalz = f.f_globals.copy()
  227. for d in globalz, localz:
  228. if "__builtins__" in d:
  229. del d["__builtins__"]
  230. localz = localz.items()
  231. globalz = globalz.items()
  232. else:
  233. localz = globalz = ()
  234. stack.insert(0, (
  235. f.f_code.co_name,
  236. f.f_code.co_filename,
  237. f.f_lineno,
  238. localz,
  239. globalz,
  240. ))
  241. f = f.f_back
  242. while tb is not None:
  243. f = tb.tb_frame
  244. if captureVars:
  245. localz = f.f_locals.copy()
  246. if f.f_locals is f.f_globals:
  247. globalz = {}
  248. else:
  249. globalz = f.f_globals.copy()
  250. for d in globalz, localz:
  251. if "__builtins__" in d:
  252. del d["__builtins__"]
  253. localz = list(localz.items())
  254. globalz = list(globalz.items())
  255. else:
  256. localz = globalz = ()
  257. frames.append((
  258. f.f_code.co_name,
  259. f.f_code.co_filename,
  260. tb.tb_lineno,
  261. localz,
  262. globalz,
  263. ))
  264. tb = tb.tb_next
  265. if inspect.isclass(self.type) and issubclass(self.type, Exception):
  266. parentCs = getmro(self.type)
  267. self.parents = list(map(reflect.qual, parentCs))
  268. else:
  269. self.parents = [self.type]
  270. def trap(self, *errorTypes):
  271. """Trap this failure if its type is in a predetermined list.
  272. This allows you to trap a Failure in an error callback. It will be
  273. automatically re-raised if it is not a type that you expect.
  274. The reason for having this particular API is because it's very useful
  275. in Deferred errback chains::
  276. def _ebFoo(self, failure):
  277. r = failure.trap(Spam, Eggs)
  278. print('The Failure is due to either Spam or Eggs!')
  279. if r == Spam:
  280. print('Spam did it!')
  281. elif r == Eggs:
  282. print('Eggs did it!')
  283. If the failure is not a Spam or an Eggs, then the Failure will be
  284. 'passed on' to the next errback. In Python 2 the Failure will be
  285. raised; in Python 3 the underlying exception will be re-raised.
  286. @type errorTypes: L{Exception}
  287. """
  288. error = self.check(*errorTypes)
  289. if not error:
  290. if _shouldEnableNewStyle:
  291. self.raiseException()
  292. else:
  293. raise self
  294. return error
  295. def check(self, *errorTypes):
  296. """Check if this failure's type is in a predetermined list.
  297. @type errorTypes: list of L{Exception} classes or
  298. fully-qualified class names.
  299. @returns: the matching L{Exception} type, or None if no match.
  300. """
  301. for error in errorTypes:
  302. err = error
  303. if inspect.isclass(error) and issubclass(error, Exception):
  304. err = reflect.qual(error)
  305. if err in self.parents:
  306. return error
  307. return None
  308. # It would be nice to use twisted.python.compat.reraise, but that breaks
  309. # the stack exploration in _findFailure; possibly this can be fixed in
  310. # #5931.
  311. if getattr(BaseException, "with_traceback", None):
  312. # Python 3
  313. def raiseException(self):
  314. raise self.value.with_traceback(self.tb)
  315. else:
  316. exec("""def raiseException(self):
  317. raise self.type, self.value, self.tb""")
  318. raiseException.__doc__ = (
  319. """
  320. raise the original exception, preserving traceback
  321. information if available.
  322. """)
  323. def throwExceptionIntoGenerator(self, g):
  324. """
  325. Throw the original exception into the given generator,
  326. preserving traceback information if available.
  327. @return: The next value yielded from the generator.
  328. @raise StopIteration: If there are no more values in the generator.
  329. @raise anything else: Anything that the generator raises.
  330. """
  331. return g.throw(self.type, self.value, self.tb)
  332. def _findFailure(cls):
  333. """
  334. Find the failure that represents the exception currently in context.
  335. """
  336. tb = sys.exc_info()[-1]
  337. if not tb:
  338. return
  339. secondLastTb = None
  340. lastTb = tb
  341. while lastTb.tb_next:
  342. secondLastTb = lastTb
  343. lastTb = lastTb.tb_next
  344. lastFrame = lastTb.tb_frame
  345. # NOTE: f_locals.get('self') is used rather than
  346. # f_locals['self'] because psyco frames do not contain
  347. # anything in their locals() dicts. psyco makes debugging
  348. # difficult anyhow, so losing the Failure objects (and thus
  349. # the tracebacks) here when it is used is not that big a deal.
  350. # handle raiseException-originated exceptions
  351. if lastFrame.f_code is cls.raiseException.__code__:
  352. return lastFrame.f_locals.get('self')
  353. # handle throwExceptionIntoGenerator-originated exceptions
  354. # this is tricky, and differs if the exception was caught
  355. # inside the generator, or above it:
  356. # it is only really originating from
  357. # throwExceptionIntoGenerator if the bottom of the traceback
  358. # is a yield.
  359. # Pyrex and Cython extensions create traceback frames
  360. # with no co_code, but they can't yield so we know it's okay to just return here.
  361. if ((not lastFrame.f_code.co_code) or
  362. lastFrame.f_code.co_code[lastTb.tb_lasti] != cls._yieldOpcode):
  363. return
  364. # if the exception was caught above the generator.throw
  365. # (outside the generator), it will appear in the tb (as the
  366. # second last item):
  367. if secondLastTb:
  368. frame = secondLastTb.tb_frame
  369. if frame.f_code is cls.throwExceptionIntoGenerator.__code__:
  370. return frame.f_locals.get('self')
  371. # if the exception was caught below the generator.throw
  372. # (inside the generator), it will appear in the frames' linked
  373. # list, above the top-level traceback item (which must be the
  374. # generator frame itself, thus its caller is
  375. # throwExceptionIntoGenerator).
  376. frame = tb.tb_frame.f_back
  377. if frame and frame.f_code is cls.throwExceptionIntoGenerator.__code__:
  378. return frame.f_locals.get('self')
  379. _findFailure = classmethod(_findFailure)
  380. def __repr__(self):
  381. return "<%s %s: %s>" % (reflect.qual(self.__class__),
  382. reflect.qual(self.type),
  383. self.getErrorMessage())
  384. def __str__(self):
  385. return "[Failure instance: %s]" % self.getBriefTraceback()
  386. def __getstate__(self):
  387. """Avoid pickling objects in the traceback.
  388. """
  389. if self.pickled:
  390. return self.__dict__
  391. c = self.__dict__.copy()
  392. c['frames'] = [
  393. [
  394. v[0], v[1], v[2],
  395. _safeReprVars(v[3]),
  396. _safeReprVars(v[4]),
  397. ] for v in self.frames
  398. ]
  399. # added 2003-06-23. See comment above in __init__
  400. c['tb'] = None
  401. if self.stack is not None:
  402. # XXX: This is a band-aid. I can't figure out where these
  403. # (failure.stack is None) instances are coming from.
  404. c['stack'] = [
  405. [
  406. v[0], v[1], v[2],
  407. _safeReprVars(v[3]),
  408. _safeReprVars(v[4]),
  409. ] for v in self.stack
  410. ]
  411. c['pickled'] = 1
  412. return c
  413. def cleanFailure(self):
  414. """
  415. Remove references to other objects, replacing them with strings.
  416. On Python 3, this will also set the C{__traceback__} attribute of the
  417. exception instance to L{None}.
  418. """
  419. self.__dict__ = self.__getstate__()
  420. if getattr(self.value, "__traceback__", None):
  421. # Python 3
  422. self.value.__traceback__ = None
  423. def getTracebackObject(self):
  424. """
  425. Get an object that represents this Failure's stack that can be passed
  426. to traceback.extract_tb.
  427. If the original traceback object is still present, return that. If this
  428. traceback object has been lost but we still have the information,
  429. return a fake traceback object (see L{_Traceback}). If there is no
  430. traceback information at all, return None.
  431. """
  432. if self.tb is not None:
  433. return self.tb
  434. elif len(self.frames) > 0:
  435. return _Traceback(self.frames)
  436. else:
  437. return None
  438. def getErrorMessage(self):
  439. """Get a string of the exception which caused this Failure."""
  440. if isinstance(self.value, Failure):
  441. return self.value.getErrorMessage()
  442. return reflect.safe_str(self.value)
  443. def getBriefTraceback(self):
  444. io = StringIO()
  445. self.printBriefTraceback(file=io)
  446. return io.getvalue()
  447. def getTraceback(self, elideFrameworkCode=0, detail='default'):
  448. io = StringIO()
  449. self.printTraceback(file=io, elideFrameworkCode=elideFrameworkCode, detail=detail)
  450. return io.getvalue()
  451. def printTraceback(self, file=None, elideFrameworkCode=False, detail='default'):
  452. """
  453. Emulate Python's standard error reporting mechanism.
  454. @param file: If specified, a file-like object to which to write the
  455. traceback.
  456. @param elideFrameworkCode: A flag indicating whether to attempt to
  457. remove uninteresting frames from within Twisted itself from the
  458. output.
  459. @param detail: A string indicating how much information to include
  460. in the traceback. Must be one of C{'brief'}, C{'default'}, or
  461. C{'verbose'}.
  462. """
  463. if file is None:
  464. from twisted.python import log
  465. file = log.logerr
  466. w = file.write
  467. if detail == 'verbose' and not self.captureVars:
  468. # We don't have any locals or globals, so rather than show them as
  469. # empty make the output explicitly say that we don't have them at
  470. # all.
  471. formatDetail = 'verbose-vars-not-captured'
  472. else:
  473. formatDetail = detail
  474. # Preamble
  475. if detail == 'verbose':
  476. w( '*--- Failure #%d%s---\n' %
  477. (self.count,
  478. (self.pickled and ' (pickled) ') or ' '))
  479. elif detail == 'brief':
  480. if self.frames:
  481. hasFrames = 'Traceback'
  482. else:
  483. hasFrames = 'Traceback (failure with no frames)'
  484. w("%s: %s: %s\n" % (
  485. hasFrames,
  486. reflect.safe_str(self.type),
  487. reflect.safe_str(self.value)))
  488. else:
  489. w( 'Traceback (most recent call last):\n')
  490. # Frames, formatted in appropriate style
  491. if self.frames:
  492. if not elideFrameworkCode:
  493. format_frames(self.stack[-traceupLength:], w, formatDetail)
  494. w("%s\n" % (EXCEPTION_CAUGHT_HERE,))
  495. format_frames(self.frames, w, formatDetail)
  496. elif not detail == 'brief':
  497. # Yeah, it's not really a traceback, despite looking like one...
  498. w("Failure: ")
  499. # postamble, if any
  500. if not detail == 'brief':
  501. w("%s: %s\n" % (reflect.qual(self.type),
  502. reflect.safe_str(self.value)))
  503. # chaining
  504. if isinstance(self.value, Failure):
  505. # TODO: indentation for chained failures?
  506. file.write(" (chained Failure)\n")
  507. self.value.printTraceback(file, elideFrameworkCode, detail)
  508. if detail == 'verbose':
  509. w('*--- End of Failure #%d ---\n' % self.count)
  510. def printBriefTraceback(self, file=None, elideFrameworkCode=0):
  511. """Print a traceback as densely as possible.
  512. """
  513. self.printTraceback(file, elideFrameworkCode, detail='brief')
  514. def printDetailedTraceback(self, file=None, elideFrameworkCode=0):
  515. """Print a traceback with detailed locals and globals information.
  516. """
  517. self.printTraceback(file, elideFrameworkCode, detail='verbose')
  518. def _safeReprVars(varsDictItems):
  519. """
  520. Convert a list of (name, object) pairs into (name, repr) pairs.
  521. L{twisted.python.reflect.safe_repr} is used to generate the repr, so no
  522. exceptions will be raised by faulty C{__repr__} methods.
  523. @param varsDictItems: a sequence of (name, value) pairs as returned by e.g.
  524. C{locals().items()}.
  525. @returns: a sequence of (name, repr) pairs.
  526. """
  527. return [(name, reflect.safe_repr(obj)) for (name, obj) in varsDictItems]
  528. # slyphon: make post-morteming exceptions tweakable
  529. DO_POST_MORTEM = True
  530. def _debuginit(self, exc_value=None, exc_type=None, exc_tb=None,
  531. captureVars=False,
  532. Failure__init__=Failure.__init__):
  533. """
  534. Initialize failure object, possibly spawning pdb.
  535. """
  536. if (exc_value, exc_type, exc_tb) == (None, None, None):
  537. exc = sys.exc_info()
  538. if not exc[0] == self.__class__ and DO_POST_MORTEM:
  539. try:
  540. strrepr = str(exc[1])
  541. except:
  542. strrepr = "broken str"
  543. print("Jumping into debugger for post-mortem of exception '%s':" % (strrepr,))
  544. import pdb
  545. pdb.post_mortem(exc[2])
  546. Failure__init__(self, exc_value, exc_type, exc_tb, captureVars)
  547. def startDebugMode():
  548. """Enable debug hooks for Failures."""
  549. Failure.__init__ = _debuginit