_tblib.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. # -*- coding: utf-8 -*-
  2. # A vendored version of part of https://github.com/ionelmc/python-tblib
  3. # pylint:disable=redefined-outer-name,reimported,function-redefined,bare-except,no-else-return,broad-except
  4. ####
  5. # Copyright (c) 2013-2016, Ionel Cristian Mărieș
  6. # All rights reserved.
  7. # Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
  8. # following conditions are met:
  9. # 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
  10. # disclaimer.
  11. # 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
  12. # disclaimer in the documentation and/or other materials provided with the distribution.
  13. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  14. # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  15. # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  16. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  17. # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  18. # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  19. # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  20. ####
  21. # cpython.py
  22. """
  23. Taken verbatim from Jinja2.
  24. https://github.com/mitsuhiko/jinja2/blob/master/jinja2/debug.py#L267
  25. """
  26. #import platform # XXX: gevent cannot import platform at the top level; interferes with monkey patching
  27. import sys
  28. def _init_ugly_crap():
  29. """This function implements a few ugly things so that we can patch the
  30. traceback objects. The function returned allows resetting `tb_next` on
  31. any python traceback object. Do not attempt to use this on non cpython
  32. interpreters
  33. """
  34. import ctypes
  35. from types import TracebackType
  36. # figure out side of _Py_ssize_t
  37. if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
  38. _Py_ssize_t = ctypes.c_int64
  39. else:
  40. _Py_ssize_t = ctypes.c_int
  41. # regular python
  42. class _PyObject(ctypes.Structure):
  43. pass
  44. _PyObject._fields_ = [
  45. ('ob_refcnt', _Py_ssize_t),
  46. ('ob_type', ctypes.POINTER(_PyObject))
  47. ]
  48. # python with trace
  49. if hasattr(sys, 'getobjects'):
  50. class _PyObject(ctypes.Structure):
  51. pass
  52. _PyObject._fields_ = [
  53. ('_ob_next', ctypes.POINTER(_PyObject)),
  54. ('_ob_prev', ctypes.POINTER(_PyObject)),
  55. ('ob_refcnt', _Py_ssize_t),
  56. ('ob_type', ctypes.POINTER(_PyObject))
  57. ]
  58. class _Traceback(_PyObject):
  59. pass
  60. _Traceback._fields_ = [
  61. ('tb_next', ctypes.POINTER(_Traceback)),
  62. ('tb_frame', ctypes.POINTER(_PyObject)),
  63. ('tb_lasti', ctypes.c_int),
  64. ('tb_lineno', ctypes.c_int)
  65. ]
  66. def tb_set_next(tb, next):
  67. """Set the tb_next attribute of a traceback object."""
  68. if not (isinstance(tb, TracebackType) and (next is None or isinstance(next, TracebackType))):
  69. raise TypeError('tb_set_next arguments must be traceback objects')
  70. obj = _Traceback.from_address(id(tb))
  71. if tb.tb_next is not None:
  72. old = _Traceback.from_address(id(tb.tb_next))
  73. old.ob_refcnt -= 1
  74. if next is None:
  75. obj.tb_next = ctypes.POINTER(_Traceback)()
  76. else:
  77. next = _Traceback.from_address(id(next))
  78. next.ob_refcnt += 1
  79. obj.tb_next = ctypes.pointer(next)
  80. return tb_set_next
  81. tb_set_next = None
  82. #try:
  83. # if platform.python_implementation() == 'CPython':
  84. # tb_set_next = _init_ugly_crap()
  85. #except Exception as exc:
  86. # sys.stderr.write("Failed to initialize cpython support: {!r}".format(exc))
  87. #del _init_ugly_crap
  88. # __init__.py
  89. import re
  90. from types import CodeType
  91. from types import TracebackType
  92. try:
  93. from __pypy__ import tproxy
  94. except ImportError:
  95. tproxy = None
  96. __version__ = '1.3.0'
  97. __all__ = ('Traceback',)
  98. PY3 = sys.version_info[0] == 3
  99. FRAME_RE = re.compile(r'^\s*File "(?P<co_filename>.+)", line (?P<tb_lineno>\d+)(, in (?P<co_name>.+))?$')
  100. class _AttrDict(dict):
  101. __slots__ = ()
  102. __getattr__ = dict.__getitem__
  103. # noinspection PyPep8Naming
  104. class __traceback_maker(Exception):
  105. pass
  106. class TracebackParseError(Exception):
  107. pass
  108. class Code(object):
  109. def __init__(self, code):
  110. self.co_filename = code.co_filename
  111. self.co_name = code.co_name
  112. # gevent: copy more attributes
  113. self.co_nlocals = code.co_nlocals
  114. self.co_stacksize = code.co_stacksize
  115. self.co_flags = code.co_flags
  116. self.co_firstlineno = code.co_firstlineno
  117. class Frame(object):
  118. def __init__(self, frame):
  119. self.f_globals = dict([
  120. (k, v)
  121. for k, v in frame.f_globals.items()
  122. if k in ("__file__", "__name__")
  123. ])
  124. self.f_code = Code(frame.f_code)
  125. def clear(self):
  126. # For compatibility with PyPy 3.5;
  127. # clear was added to frame in Python 3.4
  128. # and is called by traceback.clear_frames(), which
  129. # in turn is called by unittest.TestCase.assertRaises
  130. pass
  131. class Traceback(object):
  132. tb_next = None
  133. def __init__(self, tb):
  134. self.tb_frame = Frame(tb.tb_frame)
  135. # noinspection SpellCheckingInspection
  136. self.tb_lineno = int(tb.tb_lineno)
  137. # Build in place to avoid exceeding the recursion limit
  138. tb = tb.tb_next
  139. prev_traceback = self
  140. cls = type(self)
  141. while tb is not None:
  142. traceback = object.__new__(cls)
  143. traceback.tb_frame = Frame(tb.tb_frame)
  144. traceback.tb_lineno = int(tb.tb_lineno)
  145. prev_traceback.tb_next = traceback
  146. prev_traceback = traceback
  147. tb = tb.tb_next
  148. def as_traceback(self):
  149. if tproxy:
  150. return tproxy(TracebackType, self.__tproxy_handler)
  151. if not tb_set_next:
  152. raise RuntimeError("Cannot re-create traceback !")
  153. current = self
  154. top_tb = None
  155. tb = None
  156. while current:
  157. f_code = current.tb_frame.f_code
  158. code = compile('\n' * (current.tb_lineno - 1) + 'raise __traceback_maker', current.tb_frame.f_code.co_filename, 'exec')
  159. if PY3:
  160. code = CodeType(
  161. 0, code.co_kwonlyargcount,
  162. code.co_nlocals, code.co_stacksize, code.co_flags,
  163. code.co_code, code.co_consts, code.co_names, code.co_varnames,
  164. f_code.co_filename, f_code.co_name,
  165. code.co_firstlineno, code.co_lnotab, (), ()
  166. )
  167. else:
  168. code = CodeType(
  169. 0,
  170. code.co_nlocals, code.co_stacksize, code.co_flags,
  171. code.co_code, code.co_consts, code.co_names, code.co_varnames,
  172. f_code.co_filename.encode(), f_code.co_name.encode(),
  173. code.co_firstlineno, code.co_lnotab, (), ()
  174. )
  175. # noinspection PyBroadException
  176. try:
  177. exec(code, current.tb_frame.f_globals, {})
  178. except:
  179. next_tb = sys.exc_info()[2].tb_next
  180. if top_tb is None:
  181. top_tb = next_tb
  182. if tb is not None:
  183. tb_set_next(tb, next_tb)
  184. tb = next_tb
  185. del next_tb
  186. current = current.tb_next
  187. try:
  188. return top_tb
  189. finally:
  190. del top_tb
  191. del tb
  192. # noinspection SpellCheckingInspection
  193. def __tproxy_handler(self, operation, *args, **kwargs):
  194. if operation in ('__getattribute__', '__getattr__'):
  195. if args[0] == 'tb_next':
  196. return self.tb_next and self.tb_next.as_traceback()
  197. else:
  198. return getattr(self, args[0])
  199. else:
  200. return getattr(self, operation)(*args, **kwargs)
  201. def to_dict(self):
  202. """Convert a Traceback into a dictionary representation"""
  203. if self.tb_next is None:
  204. tb_next = None
  205. else:
  206. tb_next = self.tb_next.to_dict()
  207. code = {
  208. 'co_filename': self.tb_frame.f_code.co_filename,
  209. 'co_name': self.tb_frame.f_code.co_name,
  210. }
  211. frame = {
  212. 'f_globals': self.tb_frame.f_globals,
  213. 'f_code': code,
  214. }
  215. return {
  216. 'tb_frame': frame,
  217. 'tb_lineno': self.tb_lineno,
  218. 'tb_next': tb_next,
  219. }
  220. @classmethod
  221. def from_dict(cls, dct):
  222. if dct['tb_next']:
  223. tb_next = cls.from_dict(dct['tb_next'])
  224. else:
  225. tb_next = None
  226. code = _AttrDict(
  227. co_filename=dct['tb_frame']['f_code']['co_filename'],
  228. co_name=dct['tb_frame']['f_code']['co_name'],
  229. )
  230. frame = _AttrDict(
  231. f_globals=dct['tb_frame']['f_globals'],
  232. f_code=code,
  233. )
  234. tb = _AttrDict(
  235. tb_frame=frame,
  236. tb_lineno=dct['tb_lineno'],
  237. tb_next=tb_next,
  238. )
  239. return cls(tb)
  240. @classmethod
  241. def from_string(cls, string, strict=True):
  242. frames = []
  243. header = strict
  244. for line in string.splitlines():
  245. line = line.rstrip()
  246. if header:
  247. if line == 'Traceback (most recent call last):':
  248. header = False
  249. continue
  250. frame_match = FRAME_RE.match(line)
  251. if frame_match:
  252. frames.append(frame_match.groupdict())
  253. elif line.startswith(' '):
  254. pass
  255. elif strict:
  256. break # traceback ended
  257. if frames:
  258. previous = None
  259. for frame in reversed(frames):
  260. previous = _AttrDict(
  261. frame,
  262. tb_frame=_AttrDict(
  263. frame,
  264. f_globals=_AttrDict(
  265. __file__=frame['co_filename'],
  266. __name__='?',
  267. ),
  268. f_code=_AttrDict(frame),
  269. ),
  270. tb_next=previous,
  271. )
  272. return cls(previous)
  273. else:
  274. raise TracebackParseError("Could not find any frames in %r." % string)
  275. # pickling_support.py
  276. def unpickle_traceback(tb_frame, tb_lineno, tb_next):
  277. ret = object.__new__(Traceback)
  278. ret.tb_frame = tb_frame
  279. ret.tb_lineno = tb_lineno
  280. ret.tb_next = tb_next
  281. return ret.as_traceback()
  282. def pickle_traceback(tb):
  283. return unpickle_traceback, (Frame(tb.tb_frame), tb.tb_lineno, tb.tb_next and Traceback(tb.tb_next))
  284. def install():
  285. try:
  286. import copy_reg
  287. except ImportError:
  288. import copyreg as copy_reg
  289. copy_reg.pickle(TracebackType, pickle_traceback)
  290. # Added by gevent
  291. # We have to defer the initialization, and especially the import of platform,
  292. # until runtime. If we're monkey patched, we need to be sure to use
  293. # the original __import__ to avoid switching through the hub due to
  294. # import locks on Python 2. See also builtins.py for details.
  295. def _unlocked_imports(f):
  296. def g(a):
  297. if sys is None: # pragma: no cover
  298. # interpreter shutdown on Py2
  299. return
  300. gb = None
  301. if 'gevent.builtins' in sys.modules:
  302. gb = sys.modules['gevent.builtins']
  303. gb._unlock_imports()
  304. try:
  305. return f(a)
  306. finally:
  307. if gb is not None:
  308. gb._lock_imports()
  309. g.__name__ = f.__name__
  310. g.__module__ = f.__module__
  311. return g
  312. def _import_dump_load():
  313. global dumps
  314. global loads
  315. try:
  316. import cPickle as pickle
  317. except ImportError:
  318. import pickle
  319. dumps = pickle.dumps
  320. loads = pickle.loads
  321. dumps = loads = None
  322. _installed = False
  323. def _init():
  324. global _installed
  325. global tb_set_next
  326. if _installed:
  327. return
  328. _installed = True
  329. import platform
  330. try:
  331. if platform.python_implementation() == 'CPython':
  332. tb_set_next = _init_ugly_crap()
  333. except Exception as exc:
  334. sys.stderr.write("Failed to initialize cpython support: {!r}".format(exc))
  335. try:
  336. from __pypy__ import tproxy
  337. except ImportError:
  338. tproxy = None
  339. if not tb_set_next and not tproxy:
  340. raise ImportError("Cannot use tblib. Runtime not supported.")
  341. _import_dump_load()
  342. install()
  343. @_unlocked_imports
  344. def dump_traceback(tb):
  345. # Both _init and dump/load have to be unlocked, because
  346. # copy_reg and pickle can do imports to resolve class names; those
  347. # class names are in this module and greenlet safe though
  348. _init()
  349. return dumps(tb)
  350. @_unlocked_imports
  351. def load_traceback(s):
  352. _init()
  353. return loads(s)