diff options
Diffstat (limited to 'python/gevent/_tblib.py')
-rw-r--r-- | python/gevent/_tblib.py | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/python/gevent/_tblib.py b/python/gevent/_tblib.py new file mode 100644 index 0000000..91edb01 --- /dev/null +++ b/python/gevent/_tblib.py @@ -0,0 +1,431 @@ +# -*- coding: utf-8 -*- +# A vendored version of part of https://github.com/ionelmc/python-tblib +# pylint:disable=redefined-outer-name,reimported,function-redefined,bare-except,no-else-return,broad-except +#### +# Copyright (c) 2013-2016, Ionel Cristian Mărieș +# All rights reserved. + +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: + +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. + +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#### + +# cpython.py + +""" +Taken verbatim from Jinja2. + +https://github.com/mitsuhiko/jinja2/blob/master/jinja2/debug.py#L267 +""" +#import platform # XXX: gevent cannot import platform at the top level; interferes with monkey patching +import sys + + +def _init_ugly_crap(): + """This function implements a few ugly things so that we can patch the + traceback objects. The function returned allows resetting `tb_next` on + any python traceback object. Do not attempt to use this on non cpython + interpreters + """ + import ctypes + from types import TracebackType + + # figure out side of _Py_ssize_t + if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'): + _Py_ssize_t = ctypes.c_int64 + else: + _Py_ssize_t = ctypes.c_int + + # regular python + class _PyObject(ctypes.Structure): + pass + + _PyObject._fields_ = [ + ('ob_refcnt', _Py_ssize_t), + ('ob_type', ctypes.POINTER(_PyObject)) + ] + + # python with trace + if hasattr(sys, 'getobjects'): + class _PyObject(ctypes.Structure): + pass + + _PyObject._fields_ = [ + ('_ob_next', ctypes.POINTER(_PyObject)), + ('_ob_prev', ctypes.POINTER(_PyObject)), + ('ob_refcnt', _Py_ssize_t), + ('ob_type', ctypes.POINTER(_PyObject)) + ] + + class _Traceback(_PyObject): + pass + + _Traceback._fields_ = [ + ('tb_next', ctypes.POINTER(_Traceback)), + ('tb_frame', ctypes.POINTER(_PyObject)), + ('tb_lasti', ctypes.c_int), + ('tb_lineno', ctypes.c_int) + ] + + def tb_set_next(tb, next): + """Set the tb_next attribute of a traceback object.""" + if not (isinstance(tb, TracebackType) and (next is None or isinstance(next, TracebackType))): + raise TypeError('tb_set_next arguments must be traceback objects') + obj = _Traceback.from_address(id(tb)) + if tb.tb_next is not None: + old = _Traceback.from_address(id(tb.tb_next)) + old.ob_refcnt -= 1 + if next is None: + obj.tb_next = ctypes.POINTER(_Traceback)() + else: + next = _Traceback.from_address(id(next)) + next.ob_refcnt += 1 + obj.tb_next = ctypes.pointer(next) + + return tb_set_next + + +tb_set_next = None +#try: +# if platform.python_implementation() == 'CPython': +# tb_set_next = _init_ugly_crap() +#except Exception as exc: +# sys.stderr.write("Failed to initialize cpython support: {!r}".format(exc)) +#del _init_ugly_crap + +# __init__.py +import re +from types import CodeType +from types import TracebackType + +try: + from __pypy__ import tproxy +except ImportError: + tproxy = None + +__version__ = '1.3.0' +__all__ = ('Traceback',) + +PY3 = sys.version_info[0] == 3 +FRAME_RE = re.compile(r'^\s*File "(?P<co_filename>.+)", line (?P<tb_lineno>\d+)(, in (?P<co_name>.+))?$') + + +class _AttrDict(dict): + __slots__ = () + __getattr__ = dict.__getitem__ + + +# noinspection PyPep8Naming +class __traceback_maker(Exception): + pass + + +class TracebackParseError(Exception): + pass + + +class Code(object): + def __init__(self, code): + self.co_filename = code.co_filename + self.co_name = code.co_name + # gevent: copy more attributes + self.co_nlocals = code.co_nlocals + self.co_stacksize = code.co_stacksize + self.co_flags = code.co_flags + self.co_firstlineno = code.co_firstlineno + + +class Frame(object): + def __init__(self, frame): + self.f_globals = dict([ + (k, v) + for k, v in frame.f_globals.items() + if k in ("__file__", "__name__") + ]) + self.f_code = Code(frame.f_code) + + def clear(self): + # For compatibility with PyPy 3.5; + # clear was added to frame in Python 3.4 + # and is called by traceback.clear_frames(), which + # in turn is called by unittest.TestCase.assertRaises + pass + +class Traceback(object): + + tb_next = None + + def __init__(self, tb): + self.tb_frame = Frame(tb.tb_frame) + # noinspection SpellCheckingInspection + self.tb_lineno = int(tb.tb_lineno) + + # Build in place to avoid exceeding the recursion limit + tb = tb.tb_next + prev_traceback = self + cls = type(self) + while tb is not None: + traceback = object.__new__(cls) + traceback.tb_frame = Frame(tb.tb_frame) + traceback.tb_lineno = int(tb.tb_lineno) + prev_traceback.tb_next = traceback + prev_traceback = traceback + tb = tb.tb_next + + def as_traceback(self): + if tproxy: + return tproxy(TracebackType, self.__tproxy_handler) + if not tb_set_next: + raise RuntimeError("Cannot re-create traceback !") + + current = self + top_tb = None + tb = None + while current: + f_code = current.tb_frame.f_code + code = compile('\n' * (current.tb_lineno - 1) + 'raise __traceback_maker', current.tb_frame.f_code.co_filename, 'exec') + if PY3: + code = CodeType( + 0, code.co_kwonlyargcount, + code.co_nlocals, code.co_stacksize, code.co_flags, + code.co_code, code.co_consts, code.co_names, code.co_varnames, + f_code.co_filename, f_code.co_name, + code.co_firstlineno, code.co_lnotab, (), () + ) + else: + code = CodeType( + 0, + code.co_nlocals, code.co_stacksize, code.co_flags, + code.co_code, code.co_consts, code.co_names, code.co_varnames, + f_code.co_filename.encode(), f_code.co_name.encode(), + code.co_firstlineno, code.co_lnotab, (), () + ) + + # noinspection PyBroadException + try: + exec(code, current.tb_frame.f_globals, {}) + except: + next_tb = sys.exc_info()[2].tb_next + if top_tb is None: + top_tb = next_tb + if tb is not None: + tb_set_next(tb, next_tb) + tb = next_tb + del next_tb + + current = current.tb_next + try: + return top_tb + finally: + del top_tb + del tb + + + # noinspection SpellCheckingInspection + def __tproxy_handler(self, operation, *args, **kwargs): + if operation in ('__getattribute__', '__getattr__'): + if args[0] == 'tb_next': + return self.tb_next and self.tb_next.as_traceback() + else: + return getattr(self, args[0]) + else: + return getattr(self, operation)(*args, **kwargs) + + def to_dict(self): + """Convert a Traceback into a dictionary representation""" + if self.tb_next is None: + tb_next = None + else: + tb_next = self.tb_next.to_dict() + + code = { + 'co_filename': self.tb_frame.f_code.co_filename, + 'co_name': self.tb_frame.f_code.co_name, + } + frame = { + 'f_globals': self.tb_frame.f_globals, + 'f_code': code, + } + return { + 'tb_frame': frame, + 'tb_lineno': self.tb_lineno, + 'tb_next': tb_next, + } + + @classmethod + def from_dict(cls, dct): + if dct['tb_next']: + tb_next = cls.from_dict(dct['tb_next']) + else: + tb_next = None + + code = _AttrDict( + co_filename=dct['tb_frame']['f_code']['co_filename'], + co_name=dct['tb_frame']['f_code']['co_name'], + ) + frame = _AttrDict( + f_globals=dct['tb_frame']['f_globals'], + f_code=code, + ) + tb = _AttrDict( + tb_frame=frame, + tb_lineno=dct['tb_lineno'], + tb_next=tb_next, + ) + return cls(tb) + + @classmethod + def from_string(cls, string, strict=True): + frames = [] + header = strict + + for line in string.splitlines(): + line = line.rstrip() + if header: + if line == 'Traceback (most recent call last):': + header = False + continue + frame_match = FRAME_RE.match(line) + if frame_match: + frames.append(frame_match.groupdict()) + elif line.startswith(' '): + pass + elif strict: + break # traceback ended + + if frames: + previous = None + for frame in reversed(frames): + previous = _AttrDict( + frame, + tb_frame=_AttrDict( + frame, + f_globals=_AttrDict( + __file__=frame['co_filename'], + __name__='?', + ), + f_code=_AttrDict(frame), + ), + tb_next=previous, + ) + return cls(previous) + else: + raise TracebackParseError("Could not find any frames in %r." % string) + +# pickling_support.py + + +def unpickle_traceback(tb_frame, tb_lineno, tb_next): + ret = object.__new__(Traceback) + ret.tb_frame = tb_frame + ret.tb_lineno = tb_lineno + ret.tb_next = tb_next + return ret.as_traceback() + + +def pickle_traceback(tb): + return unpickle_traceback, (Frame(tb.tb_frame), tb.tb_lineno, tb.tb_next and Traceback(tb.tb_next)) + + +def install(): + try: + import copy_reg + except ImportError: + import copyreg as copy_reg + + copy_reg.pickle(TracebackType, pickle_traceback) + +# Added by gevent + +# We have to defer the initialization, and especially the import of platform, +# until runtime. If we're monkey patched, we need to be sure to use +# the original __import__ to avoid switching through the hub due to +# import locks on Python 2. See also builtins.py for details. + + +def _unlocked_imports(f): + def g(a): + if sys is None: # pragma: no cover + # interpreter shutdown on Py2 + return + + gb = None + if 'gevent.builtins' in sys.modules: + gb = sys.modules['gevent.builtins'] + gb._unlock_imports() + try: + return f(a) + finally: + if gb is not None: + gb._lock_imports() + g.__name__ = f.__name__ + g.__module__ = f.__module__ + return g + + +def _import_dump_load(): + global dumps + global loads + try: + import cPickle as pickle + except ImportError: + import pickle + dumps = pickle.dumps + loads = pickle.loads + +dumps = loads = None + +_installed = False + + +def _init(): + global _installed + global tb_set_next + if _installed: + return + + _installed = True + import platform + try: + if platform.python_implementation() == 'CPython': + tb_set_next = _init_ugly_crap() + except Exception as exc: + sys.stderr.write("Failed to initialize cpython support: {!r}".format(exc)) + + try: + from __pypy__ import tproxy + except ImportError: + tproxy = None + + if not tb_set_next and not tproxy: + raise ImportError("Cannot use tblib. Runtime not supported.") + _import_dump_load() + install() + + +@_unlocked_imports +def dump_traceback(tb): + # Both _init and dump/load have to be unlocked, because + # copy_reg and pickle can do imports to resolve class names; those + # class names are in this module and greenlet safe though + _init() + return dumps(tb) + + +@_unlocked_imports +def load_traceback(s): + _init() + return loads(s) |