ion/
exception.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use 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/// Represents an exception in the JS Runtime.
30/// The exception can be an [Error], or any [Value].
31#[derive(Clone, Debug)]
32pub enum Exception {
33	Error(Error),
34	Other(JSVal),
35}
36
37impl Exception {
38	/// Gets an [Exception] from the runtime and clears the pending exception.
39	/// Returns [None] if there is no pending exception.
40	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	/// Converts a [Value] into an [Exception].
58	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	/// Converts an [Object] into an [Exception].
68	/// If the object is an error object, it is parsed as an [Error].
69	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	/// Clears the pending exception within the runtime.
94	pub fn clear(cx: &Context) {
95		unsafe { JS_ClearPendingException(cx.as_ptr()) };
96	}
97
98	/// Checks if an exception is pending in the runtime.
99	pub fn is_pending(cx: &Context) -> bool {
100		unsafe { JS_IsExceptionPending(cx.as_ptr()) }
101	}
102
103	/// Converts [Exception] to an [Error]
104	/// Returns [Error::none()](Error::none) if the exception is not an [Error].
105	pub fn to_error(&self) -> Error {
106		match self {
107			Exception::Error(error) => error.clone(),
108			Exception::Other(_) => Error::none(),
109		}
110	}
111
112	/// If the [Exception] is an [Error], the error location is mapped according to the given [SourceMap].
113	#[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	/// Formats the [Exception] as an error message.
124	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/// Represents an error report, containing an exception and optionally its [stacktrace](Stack).
178#[derive(Clone, Debug)]
179pub struct ErrorReport {
180	pub exception: Exception,
181	pub stack: Option<Stack>,
182}
183
184impl ErrorReport {
185	/// Creates a new [ErrorReport] with an [Exception] from the runtime and clears the pending exception.
186	/// Returns [None] if there is no pending exception.
187	pub fn new(cx: &Context) -> Result<Option<ErrorReport>> {
188		Ok(Exception::new(cx)?.map(|exception| ErrorReport { exception, stack: None }))
189	}
190
191	/// Creates a new [ErrorReport] with an [Exception] and [Error]'s exception stack.
192	/// Returns [None] if there is no pending exception.
193	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	/// Creates a new [ErrorReport] with an [Exception] and exception stack from the runtime.
198	/// Returns [None] if there is no pending exception.
199	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	/// Creates an [ErrorReport] from an existing [Exception] and optionally a [Stack].
223	pub fn from_exception<S: Into<Option<Stack>>>(exception: Exception, stack: S) -> ErrorReport {
224		ErrorReport { exception, stack: stack.into() }
225	}
226
227	/// Creates an [ErrorReport] from an existing [Exception], with the [Error]'s exception stack.
228	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	/// Transforms the location of the [Exception] and the [Stack] if it exists, according to the given [SourceMap].
241	#[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	/// Formats the [ErrorReport] as a string for printing.
250	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}