[pypy-commit] pypy py3.7: implement https://bugs.python.org/issue30579

cfbolz pypy.commits at gmail.com
Wed Jan 22 08:15:02 EST 2020


Author: Carl Friedrich Bolz-Tereick <cfbolz at 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__),


More information about the pypy-commit mailing list