rustpython_vm/builtins/
traceback.rs1use 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}