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