Author: Carl Friedrich Bolz-Tereick <cfb...@gmx.de> Branch: py3.7 Changeset: r98568:10615e618c98 Date: 2020-01-22 13:56 +0100 http://bitbucket.org/pypy/pypy/changeset/10615e618c98/
Log: implement https://bugs.python.org/issue30579 Allow the instantiation and modification of tracebacks, which some frameworks (examples in the issue are trio and jinja2) would really like to do diff --git a/pypy/interpreter/pytraceback.py b/pypy/interpreter/pytraceback.py --- a/pypy/interpreter/pytraceback.py +++ b/pypy/interpreter/pytraceback.py @@ -1,5 +1,14 @@ +import sys + +from pypy.interpreter.gateway import unwrap_spec from pypy.interpreter import baseobjspace -from pypy.interpreter.error import OperationError +from pypy.interpreter.error import oefmt, OperationError + +# for some strange reason CPython allows the setting of the lineno to be +# negative. I am not sure why that is useful, but let's use the most negative +# value as a sentinel to denote the default behaviour "please take the lineno +# from the frame and lasti" +LINENO_NOT_COMPUTED = -sys.maxint-1 def offset2lineno(c, stopat): # even position in lnotab denote byte increments, odd line increments. @@ -30,18 +39,53 @@ * 'tb_next' """ - def __init__(self, space, frame, lasti, next): + def __init__(self, space, frame, lasti, next, lineno=LINENO_NOT_COMPUTED): self.space = space self.frame = frame self.lasti = lasti self.next = next + self.lineno = lineno def get_lineno(self): - return offset2lineno(self.frame.pycode, self.lasti) + if self.lineno == LINENO_NOT_COMPUTED: + self.lineno = offset2lineno(self.frame.pycode, self.lasti) + return self.lineno - def descr_tb_lineno(self, space): + def descr_get_tb_lineno(self, space): return space.newint(self.get_lineno()) + def descr_set_tb_lineno(self, space, w_lineno): + self.lineno = space.int_w(w_lineno) + + def descr_get_tb_lasti(self, space): + return space.newint(self.lasti) + + def descr_set_tb_lasti(self, space, w_lasti): + self.lasti = space.int_w(w_lasti) + + def descr_get_next(self, space): + return self.next + + def descr_set_next(self, space, w_next): + newnext = space.interp_w(PyTraceback, w_next, can_be_None=True) + # check for loops + curr = newnext + while curr is not None: + if curr is self: + raise oefmt(space.w_ValueError, 'traceback loop detected') + curr = curr.next + self.next = newnext + + @staticmethod + @unwrap_spec(lasti=int, lineno=int) + def descr_new(space, w_subtype, w_next, w_frame, lasti, lineno): + from pypy.interpreter.pyframe import PyFrame + w_next = space.interp_w(PyTraceback, w_next, can_be_None=True) + w_frame = space.interp_w(PyFrame, w_frame) + traceback = space.allocate_instance(PyTraceback, w_subtype) + PyTraceback.__init__(traceback, space, w_frame, lasti, w_next, lineno) + return traceback + def descr__reduce__(self, space): from pypy.interpreter.mixedmodule import MixedModule w_mod = space.getbuiltinmodule('_pickle_support') @@ -53,17 +97,19 @@ self.frame, space.newint(self.lasti), self.next, + space.newint(self.lineno) ] nt = space.newtuple return nt([new_inst, nt(tup_base), nt(tup_state)]) def descr__setstate__(self, space, w_args): from pypy.interpreter.pyframe import PyFrame - args_w = space.unpackiterable(w_args) - w_frame, w_lasti, w_next = args_w + args_w = space.unpackiterable(w_args, 4) + w_frame, w_lasti, w_next, w_lineno = args_w self.frame = space.interp_w(PyFrame, w_frame) self.lasti = space.int_w(w_lasti) self.next = space.interp_w(PyTraceback, w_next, can_be_None=True) + self.lineno = space.int_w(w_lineno) def descr__dir__(self, space): return space.newlist([space.newtext(n) for n in diff --git a/pypy/interpreter/test/apptest_traceback.py b/pypy/interpreter/test/apptest_traceback.py new file mode 100644 --- /dev/null +++ b/pypy/interpreter/test/apptest_traceback.py @@ -0,0 +1,65 @@ +import sys +from pytest import raises +from types import TracebackType + +def ve(): + raise ValueError + +def get_tb(): + try: + ve() + except ValueError as e: + return e.__traceback__ + +def test_mutation(): + tb = get_tb() + + # allowed + tb.tb_next = None + assert tb.tb_next is None + + tb2 = get_tb() + tb.tb_next = tb2 + assert tb.tb_next is tb2 + + with raises(TypeError): + tb.tb_next = "rabc" + + # loops are forbidden + with raises(ValueError): + tb2.tb_next = tb + + with raises(ValueError): + tb.tb_next = tb + + tb.tb_lasti = 1233 + assert tb.tb_lasti == 1233 + with raises(TypeError): + tb.tb_lasti = "abc" + + tb.tb_lineno = 1233 + assert tb.tb_lineno == 1233 + with raises(TypeError): + tb.tb_lineno = "abc" + + +def test_construct(): + frame = sys._getframe() + tb = get_tb() + tb2 = TracebackType(tb, frame, 1, 2) + assert tb2.tb_next is tb + assert tb2.tb_frame is frame + assert tb2.tb_lasti == 1 + assert tb2.tb_lineno == 2 + + tb2 = TracebackType(tb, frame, 1, -1) + assert tb2.tb_next is tb + assert tb2.tb_frame is frame + assert tb2.tb_lasti == 1 + assert tb2.tb_lineno == -1 + +def test_can_subclass(): + with raises(TypeError): + class TB(TracebackType): + pass + diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py --- a/pypy/interpreter/typedef.py +++ b/pypy/interpreter/typedef.py @@ -813,14 +813,15 @@ PyTraceback.typedef = TypeDef("traceback", __reduce__ = interp2app(PyTraceback.descr__reduce__), + __new__ = interp2app(PyTraceback.descr_new), __setstate__ = interp2app(PyTraceback.descr__setstate__), __dir__ = interp2app(PyTraceback.descr__dir__), tb_frame = interp_attrproperty_w('frame', cls=PyTraceback), - tb_lasti = interp_attrproperty('lasti', cls=PyTraceback, wrapfn="newint"), - tb_lineno = GetSetProperty(PyTraceback.descr_tb_lineno), - tb_next = interp_attrproperty_w('next', cls=PyTraceback), + tb_lasti = GetSetProperty(PyTraceback.descr_get_tb_lasti, PyTraceback.descr_set_tb_lasti), + tb_lineno = GetSetProperty(PyTraceback.descr_get_tb_lineno, PyTraceback.descr_set_tb_lineno), + tb_next = GetSetProperty(PyTraceback.descr_get_next, PyTraceback.descr_set_next), ) -assert not PyTraceback.typedef.acceptable_as_base_class # no __new__ +PyTraceback.typedef.acceptable_as_base_class = False GeneratorIterator.typedef = TypeDef("generator", __repr__ = interp2app(GeneratorIterator.descr__repr__), _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit