Skip to main content

rustpython_vm/builtins/
traceback.rs

1use super::{PyList, PyType};
2use crate::{
3    AsObject, Context, Py, PyPayload, PyRef, PyResult, VirtualMachine, class::PyClassImpl,
4    frame::FrameObjectRef, function::PySetterValue, types::Constructor,
5};
6use rustpython_common::lock::PyMutex;
7use rustpython_compiler_core::OneIndexed;
8
9#[pyclass(module = false, name = "traceback", traverse)]
10#[derive(Debug)]
11pub struct PyTraceback {
12    pub next: PyMutex<Option<PyTracebackRef>>,
13    #[pymember(name = "tb_frame", audit_read)]
14    pub frame: FrameObjectRef,
15    #[pymember(name = "tb_lasti")]
16    #[pytraverse(skip)]
17    pub lasti: i32,
18    #[pytraverse(skip)]
19    pub lineno: OneIndexed,
20}
21
22pub(crate) type PyTracebackRef = PyRef<PyTraceback>;
23
24impl PyPayload for PyTraceback {
25    #[inline]
26    fn class(ctx: &Context) -> &'static Py<PyType> {
27        ctx.types.traceback_type
28    }
29}
30
31impl PyTraceback {
32    #[must_use]
33    pub const fn new(
34        next: Option<PyRef<Self>>,
35        frame: FrameObjectRef,
36        lasti: i32,
37        lineno: OneIndexed,
38    ) -> Self {
39        Self {
40            next: PyMutex::new(next),
41            frame,
42            lasti,
43            lineno,
44        }
45    }
46}
47
48#[pyclass(with(Constructor))]
49impl Py<PyTraceback> {
50    #[pygetset]
51    fn tb_lineno(&self) -> usize {
52        self.lineno.get()
53    }
54
55    #[pygetset]
56    fn tb_next(&self) -> Option<PyRef<PyTraceback>> {
57        self.next.lock().as_ref().cloned()
58    }
59
60    #[pymethod]
61    fn __dir__(&self, vm: &VirtualMachine) -> PyList {
62        PyList::from(
63            ["tb_frame", "tb_next", "tb_lasti", "tb_lineno"]
64                .iter()
65                .map(|&s| vm.ctx.new_str(s).into())
66                .collect::<Vec<_>>(),
67        )
68    }
69
70    #[pygetset(setter)]
71    fn set_tb_next(
72        zelf: &Self,
73        value: PySetterValue<Option<PyRef<PyTraceback>>>,
74        vm: &VirtualMachine,
75    ) -> PyResult<()> {
76        let value = match value {
77            PySetterValue::Assign(v) => v,
78            PySetterValue::Delete => {
79                return Err(vm.new_type_error("can't delete tb_next attribute"));
80            }
81        };
82        if let Some(ref new_next) = value {
83            let mut cursor = new_next.clone();
84            loop {
85                if cursor.is(zelf) {
86                    return Err(vm.new_value_error("traceback loop detected"));
87                }
88                let next = cursor.next.lock().clone();
89                match next {
90                    Some(n) => cursor = n,
91                    None => break,
92                }
93            }
94        }
95        *zelf.next.lock() = value;
96        Ok(())
97    }
98}
99
100impl Constructor for PyTraceback {
101    type Args = (Option<PyRef<Self>>, FrameObjectRef, i32, usize);
102
103    fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
104        let (next, frame, lasti, lineno) = args;
105        let lineno =
106            OneIndexed::new(lineno).ok_or_else(|| vm.new_value_error("lineno must be positive"))?;
107        Ok(Self::new(next, frame, lasti, lineno))
108    }
109}
110
111impl PyTracebackRef {
112    pub fn iter(&self) -> impl Iterator<Item = Self> {
113        core::iter::successors(Some(self.clone()), |tb| tb.next.lock().clone())
114    }
115}
116
117pub(crate) fn init(context: &'static Context) {
118    PyTraceback::extend_class(context, context.types.traceback_type);
119}
120
121#[cfg(feature = "serde")]
122impl serde::Serialize for PyTraceback {
123    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
124        use serde::ser::SerializeStruct;
125
126        let mut struc = s.serialize_struct("PyTraceback", 3)?;
127        struc.serialize_field("name", self.frame.iframe().code().obj_name.as_str())?;
128        struc.serialize_field("lineno", &self.lineno.get())?;
129        struc.serialize_field(
130            "filename",
131            self.frame.iframe().code().source_path().as_str(),
132        )?;
133        struc.end()
134    }
135}