1use std::ptr;
8
9use mozjs::conversions::ConversionBehavior;
10use mozjs::jsapi::{
11 ESClass, ExceptionStack, ExceptionStackBehavior, ExceptionStackOrNull, GetPendingExceptionStack,
12 IdentifyStandardInstance, JS_ClearPendingException, JS_GetPendingException, JS_IsExceptionPending,
13 JS_SetPendingException, Rooted,
14};
15use mozjs::jsval::{JSVal, ObjectValue, UndefinedValue};
16use mozjs::rooted;
17#[cfg(feature = "sourcemap")]
18use swc_sourcemap::SourceMap;
19
20use crate::conversions::{FromValue as _, ToValue};
21use crate::format::{Config, NEWLINE, format_value};
22use crate::stack::Location;
23use crate::{Context, Error, ErrorKind, Object, Result, Stack, Value};
24
25pub trait ThrowException {
26 fn throw(&self, cx: &Context);
27}
28
29#[derive(Clone, Debug)]
32pub enum Exception {
33 Error(Error),
34 Other(JSVal),
35}
36
37impl Exception {
38 pub fn new(cx: &Context) -> Result<Option<Exception>> {
41 unsafe {
42 if JS_IsExceptionPending(cx.as_ptr()) {
43 let mut exception = Value::undefined(cx);
44 if JS_GetPendingException(cx.as_ptr(), exception.handle_mut().into()) {
45 let exception = Exception::from_value(cx, &exception)?;
46 Exception::clear(cx);
47 Ok(Some(exception))
48 } else {
49 Ok(None)
50 }
51 } else {
52 Ok(None)
53 }
54 }
55 }
56
57 pub fn from_value<'cx>(cx: &'cx Context, value: &Value<'cx>) -> Result<Exception> {
59 if value.handle().is_object() {
60 let object = value.to_object(cx);
61 Exception::from_object(cx, &object)
62 } else {
63 Ok(Exception::Other(value.get()))
64 }
65 }
66
67 pub fn from_object<'cx>(cx: &'cx Context, exception: &Object<'cx>) -> Result<Exception> {
70 unsafe {
71 let handle = exception.handle();
72 if exception.get_builtin_class(cx) == ESClass::Error {
73 let message = String::from_value(cx, &exception.get(cx, "message")?.unwrap(), true, ())?;
74 let file: String = exception.get_as(cx, "fileName", true, ())?.unwrap();
75 let lineno: u32 = exception.get_as(cx, "lineNumber", true, ConversionBehavior::Clamp)?.unwrap();
76 let column: u32 = exception.get_as(cx, "columnNumber", true, ConversionBehavior::Clamp)?.unwrap();
77
78 let location = Location { file, lineno, column };
79 let kind = ErrorKind::from_proto_key(IdentifyStandardInstance(handle.get()));
80 let error = Error {
81 kind,
82 message: message.into(),
83 location: Some(location),
84 object: Some(handle.get()),
85 };
86 Ok(Exception::Error(error))
87 } else {
88 Ok(Exception::Other(ObjectValue(handle.get())))
89 }
90 }
91 }
92
93 pub fn clear(cx: &Context) {
95 unsafe { JS_ClearPendingException(cx.as_ptr()) };
96 }
97
98 pub fn is_pending(cx: &Context) -> bool {
100 unsafe { JS_IsExceptionPending(cx.as_ptr()) }
101 }
102
103 pub fn to_error(&self) -> Error {
106 match self {
107 Exception::Error(error) => error.clone(),
108 Exception::Other(_) => Error::none(),
109 }
110 }
111
112 #[cfg(feature = "sourcemap")]
114 pub fn transform_with_sourcemap(&mut self, sourcemap: &SourceMap) {
115 if let Exception::Error(Error { location: Some(location), .. }) = self
116 && let Some(token) = sourcemap.lookup_token(location.lineno - 1, location.column - 1)
117 {
118 location.lineno = token.get_src_line() + 1;
119 location.column = token.get_src_col() + 1;
120 }
121 }
122
123 pub fn format(&self, cx: &Context) -> String {
125 match self {
126 Exception::Error(error) => format!("Uncaught {}", error.format()),
127 Exception::Other(value) => {
128 format!(
129 "Uncaught Exception - {}",
130 format_value(cx, Config::default(), &cx.root(*value).into())
131 )
132 }
133 }
134 }
135}
136
137impl ThrowException for Exception {
138 fn throw(&self, cx: &Context) {
139 match self {
140 Exception::Error(error) => {
141 if let Error { object: Some(object), .. } = error {
142 let exception = Value::from(cx.root(ObjectValue(*object)));
143 unsafe {
144 JS_SetPendingException(
145 cx.as_ptr(),
146 exception.handle().into(),
147 ExceptionStackBehavior::DoNotCapture,
148 );
149 }
150 } else {
151 error.throw(cx);
152 }
153 }
154 Exception::Other(value) => {
155 let value = Value::from(cx.root(*value));
156 unsafe { JS_SetPendingException(cx.as_ptr(), value.handle().into(), ExceptionStackBehavior::Capture) }
157 }
158 }
159 }
160}
161
162impl<'cx> ToValue<'cx> for Exception {
163 fn to_value(&self, cx: &'cx Context, value: &mut Value) {
164 match self {
165 Exception::Error(error) => error.to_value(cx, value),
166 Exception::Other(other) => value.handle_mut().set(*other),
167 }
168 }
169}
170
171impl<E: Into<Error>> From<E> for Exception {
172 fn from(error: E) -> Exception {
173 Exception::Error(error.into())
174 }
175}
176
177#[derive(Clone, Debug)]
179pub struct ErrorReport {
180 pub exception: Exception,
181 pub stack: Option<Stack>,
182}
183
184impl ErrorReport {
185 pub fn new(cx: &Context) -> Result<Option<ErrorReport>> {
188 Ok(Exception::new(cx)?.map(|exception| ErrorReport { exception, stack: None }))
189 }
190
191 pub fn new_with_error_stack(cx: &Context) -> Result<Option<ErrorReport>> {
194 Ok(ErrorReport::new(cx)?.map(|report| ErrorReport::from_exception_with_error_stack(cx, report.exception)))
195 }
196
197 pub fn new_with_exception_stack(cx: &Context) -> Result<Option<ErrorReport>> {
200 unsafe {
201 if JS_IsExceptionPending(cx.as_ptr()) {
202 let mut exception_stack = ExceptionStack {
203 exception_: Rooted::new_unrooted(UndefinedValue()),
204 stack_: Rooted::new_unrooted(ptr::null_mut()),
205 };
206
207 if GetPendingExceptionStack(cx.as_ptr(), &raw mut exception_stack) {
208 let exception = Value::from(cx.root(exception_stack.exception_.data));
209 let exception = Exception::from_value(cx, &exception)?;
210 let stack = Stack::from_object(cx, exception_stack.stack_.data);
211 Exception::clear(cx);
212 Ok(Some(ErrorReport { exception, stack }))
213 } else {
214 Ok(None)
215 }
216 } else {
217 Ok(None)
218 }
219 }
220 }
221
222 pub fn from_exception<S: Into<Option<Stack>>>(exception: Exception, stack: S) -> ErrorReport {
224 ErrorReport { exception, stack: stack.into() }
225 }
226
227 pub fn from_exception_with_error_stack(cx: &Context, exception: Exception) -> ErrorReport {
229 let stack = if let Exception::Error(Error { object: Some(object), .. }) = exception {
230 unsafe {
231 rooted!(in(cx.as_ptr()) let exc = object);
232 Stack::from_object(cx, ExceptionStackOrNull(exc.handle().into()))
233 }
234 } else {
235 None
236 };
237 ErrorReport { exception, stack }
238 }
239
240 #[cfg(feature = "sourcemap")]
242 pub fn transform_with_sourcemap(&mut self, sourcemap: &SourceMap) {
243 self.exception.transform_with_sourcemap(sourcemap);
244 if let Some(stack) = &mut self.stack {
245 stack.transform_with_sourcemap(sourcemap);
246 }
247 }
248
249 pub fn format(&self, cx: &Context) -> String {
251 let mut string = self.exception.format(cx);
252 if let Some(stack) = &self.stack
253 && !stack.is_empty()
254 {
255 string.push_str(NEWLINE);
256 string.push_str(&stack.format());
257 }
258 string
259 }
260}