1use std::fmt::{Display, Formatter, Write as _};
8use std::mem::MaybeUninit;
9use std::ptr::NonNull;
10use std::{fmt, ptr};
11
12use mozjs::conversions::jsstr_to_string;
13use mozjs::jsapi::{
14 BuildStackString, CaptureCurrentStack, JS_StackCapture_AllFrames, JS_StackCapture_MaxFrames, JSObject, JSString,
15 StackFormat,
16};
17use mozjs::rooted;
18#[cfg(feature = "sourcemap")]
19use swc_sourcemap::SourceMap;
20
21use crate::format::{INDENT, NEWLINE};
22use crate::utils::normalise_path;
23use crate::{Context, Object};
24
25#[derive(Clone, Debug, PartialEq, Eq)]
27pub struct Location {
28 pub file: String,
29 pub lineno: u32,
30 pub column: u32,
31}
32
33#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct StackRecord {
36 pub function: Option<String>,
37 pub location: Location,
38}
39
40#[derive(Clone, Debug)]
44pub struct Stack {
45 pub records: Vec<StackRecord>,
46 pub object: Option<*mut JSObject>,
47}
48
49impl Location {
50 #[cfg(feature = "sourcemap")]
52 pub fn transform_with_sourcemap(&mut self, sourcemap: &SourceMap) {
53 if self.lineno != 0
54 && self.column != 0
55 && let Some(token) = sourcemap.lookup_token(self.lineno - 1, self.column - 1)
56 {
57 self.lineno = token.get_src_line() + 1;
58 self.column = token.get_src_col() + 1;
59 }
60 }
61}
62
63impl StackRecord {
64 #[cfg(feature = "sourcemap")]
66 pub fn transform_with_sourcemap(&mut self, sourcemap: &SourceMap) {
67 self.location.transform_with_sourcemap(sourcemap);
68 }
69}
70
71impl Display for StackRecord {
72 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
73 f.write_str(self.function.as_deref().unwrap_or(""))?;
74 f.write_char('@')?;
75 f.write_str(&self.location.file)?;
76 f.write_char(':')?;
77 f.write_str(&self.location.lineno.to_string())?;
78 f.write_char(':')?;
79 f.write_str(&self.location.column.to_string())?;
80 Ok(())
81 }
82}
83
84impl Stack {
85 pub fn from_string(string: &str) -> Stack {
87 let mut records = Vec::new();
88 for line in string.lines() {
89 let (function, line) = line.split_once('@').unwrap();
90 let (line, column) = line.rsplit_once(':').unwrap();
91 let (file, lineno) = line.rsplit_once(':').unwrap();
92
93 let function = if function.is_empty() {
94 None
95 } else {
96 Some(String::from(function))
97 };
98 let file = String::from(normalise_path(file).to_str().unwrap());
99 let lineno = lineno.parse().unwrap();
100 let column = column.parse().unwrap();
101
102 records.push(StackRecord {
103 function,
104 location: Location { file, lineno, column },
105 });
106 }
107 Stack { records, object: None }
108 }
109
110 pub fn from_object(cx: &Context, stack: *mut JSObject) -> Option<Stack> {
112 stack_to_string(cx, stack).as_deref().map(Stack::from_string).map(|mut s| {
113 s.object = Some(stack);
114 s
115 })
116 }
117
118 pub fn from_capture(cx: &Context) -> Option<Stack> {
120 let stack = capture_stack(cx, None)?;
121 Stack::from_object(cx, stack)
122 }
123
124 pub fn is_empty(&self) -> bool {
126 self.records.is_empty()
127 }
128
129 #[cfg(feature = "sourcemap")]
131 pub fn transform_with_sourcemap(&mut self, sourcemap: &SourceMap) {
132 for record in &mut self.records {
133 record.transform_with_sourcemap(sourcemap);
134 }
135 }
136
137 pub fn format(&self) -> String {
139 let mut string = String::new();
140 for record in &self.records {
141 string.push_str(INDENT);
142 string.push_str(&record.to_string());
143 string.push_str(NEWLINE);
144 }
145 string.pop();
146 string
147 }
148}
149
150impl Display for Stack {
151 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
152 f.write_str(&self.format())
153 }
154}
155
156fn capture_stack(cx: &Context, max_frames: Option<u32>) -> Option<*mut JSObject> {
157 unsafe {
158 let mut capture = MaybeUninit::uninit();
159 match max_frames {
160 None => JS_StackCapture_AllFrames(capture.as_mut_ptr()),
161 Some(count) => JS_StackCapture_MaxFrames(count, capture.as_mut_ptr()),
162 }
163 let mut capture = capture.assume_init();
164
165 let mut stack = Object::null(cx);
166 let start_at = Object::null(cx);
167 CaptureCurrentStack(
168 cx.as_ptr(),
169 stack.handle_mut().into(),
170 &raw mut capture,
171 start_at.handle().into(),
172 )
173 .then(|| stack.handle().get())
174 }
175}
176
177fn stack_to_string(cx: &Context, stack: *mut JSObject) -> Option<String> {
178 unsafe {
179 rooted!(in(cx.as_ptr()) let stack = stack);
180 rooted!(in(cx.as_ptr()) let mut string: *mut JSString);
181
182 BuildStackString(
183 cx.as_ptr(),
184 ptr::null_mut(),
185 stack.handle().into(),
186 string.handle_mut().into(),
187 0,
188 StackFormat::SpiderMonkey,
189 )
190 .then(|| NonNull::new(string.get()).map(|str| jsstr_to_string(cx.as_ptr(), str)))
191 .flatten()
192 }
193}