ion/
error.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::borrow::Cow;
8use std::fmt::{Display, Formatter};
9use std::{error, fmt, ptr};
10
11use mozjs::error::{throw_internal_error, throw_range_error, throw_type_error};
12use mozjs::jsapi::{CreateError, JS_ReportErrorUTF8, JSExnType, JSObject, JSProtoKey, UndefinedHandleValue};
13
14use crate::conversions::ToValue;
15use crate::exception::ThrowException;
16use crate::stack::Location;
17use crate::{Context, ErrorReport, Exception, Object, Stack, Value};
18
19/// Represents the types of errors that can be thrown and are recognised in the JS Runtime.
20#[derive(Clone, Debug, Eq, PartialEq)]
21pub enum ErrorKind {
22	Normal,
23	Internal,
24	Aggregate,
25	Eval,
26	Range,
27	Reference,
28	Syntax,
29	Type,
30	WasmCompile,
31	WasmLink,
32	WasmRuntime,
33	None,
34}
35
36impl ErrorKind {
37	/// Converts a prototype key into an [ErrorKind].
38	/// Returns [ErrorKind::None] for unrecognised prototype keys.
39	pub fn from_proto_key(key: JSProtoKey) -> ErrorKind {
40		use ErrorKind as EK;
41		use JSProtoKey::{
42			JSProto_AggregateError, JSProto_CompileError, JSProto_Error, JSProto_EvalError, JSProto_InternalError,
43			JSProto_LinkError, JSProto_RangeError, JSProto_ReferenceError, JSProto_RuntimeError, JSProto_SyntaxError,
44			JSProto_TypeError,
45		};
46		match key {
47			JSProto_Error => EK::Normal,
48			JSProto_InternalError => EK::Internal,
49			JSProto_AggregateError => EK::Aggregate,
50			JSProto_EvalError => EK::Eval,
51			JSProto_RangeError => EK::Range,
52			JSProto_ReferenceError => EK::Reference,
53			JSProto_SyntaxError => EK::Syntax,
54			JSProto_TypeError => EK::Type,
55			JSProto_CompileError => EK::WasmCompile,
56			JSProto_LinkError => EK::WasmLink,
57			JSProto_RuntimeError => EK::WasmRuntime,
58			_ => EK::None,
59		}
60	}
61
62	/// Converts an [ErrorKind] into a an exception type.
63	///
64	/// Note that [`ErrorKind::None`] is converted to [`JSEXN_ERR`](JSExnType::JSEXN_ERR).
65	pub fn to_exception_type(&self) -> JSExnType {
66		use {ErrorKind as EK, JSExnType as JSET};
67		match self {
68			EK::Normal | EK::None => JSET::JSEXN_ERR,
69			EK::Internal => JSET::JSEXN_INTERNALERR,
70			EK::Aggregate => JSET::JSEXN_AGGREGATEERR,
71			EK::Eval => JSET::JSEXN_EVALERR,
72			EK::Range => JSET::JSEXN_RANGEERR,
73			EK::Reference => JSET::JSEXN_REFERENCEERR,
74			EK::Syntax => JSET::JSEXN_SYNTAXERR,
75			EK::Type => JSET::JSEXN_TYPEERR,
76			EK::WasmCompile => JSET::JSEXN_WASMCOMPILEERROR,
77			EK::WasmLink => JSET::JSEXN_WASMLINKERROR,
78			EK::WasmRuntime => JSET::JSEXN_WASMRUNTIMEERROR,
79		}
80	}
81}
82
83impl Display for ErrorKind {
84	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
85		use ErrorKind as EK;
86		let str = match self {
87			EK::Normal => "Error",
88			EK::Internal => "InternalError",
89			EK::Aggregate => "AggregateError",
90			EK::Eval => "EvalError",
91			EK::Range => "RangeError",
92			EK::Reference => "ReferenceError",
93			EK::Syntax => "SyntaxError",
94			EK::Type => "TypeError",
95			EK::WasmCompile => "CompileError",
96			EK::WasmLink => "LinkError",
97			EK::WasmRuntime => "RuntimeError",
98			EK::None => "Not an Error",
99		};
100		f.write_str(str)
101	}
102}
103
104/// Represents errors in the JS Runtime
105/// Contains information about the type of error, the error message and the error location.
106///
107/// If created from an error object, it also contains the error object.
108#[derive(Clone, Debug)]
109pub struct Error {
110	pub kind: ErrorKind,
111	pub message: Cow<'static, str>,
112	pub location: Option<Location>,
113	pub object: Option<*mut JSObject>,
114}
115
116impl Error {
117	pub fn new<M: Into<Cow<'static, str>>, K: Into<Option<ErrorKind>>>(message: M, kind: K) -> Error {
118		Error {
119			kind: kind.into().unwrap_or(ErrorKind::Normal),
120			message: message.into(),
121			location: None,
122			object: None,
123		}
124	}
125
126	pub fn none() -> Error {
127		Error {
128			kind: ErrorKind::None,
129			message: Cow::Borrowed(""),
130			location: None,
131			object: None,
132		}
133	}
134
135	pub fn to_object<'cx>(&self, cx: &'cx Context) -> Option<Object<'cx>> {
136		if let Some(object) = self.object {
137			return Some(cx.root(object).into());
138		}
139		if self.kind != ErrorKind::None {
140			unsafe {
141				let exception_type = self.kind.to_exception_type();
142
143				let stack = Stack::from_capture(cx).unwrap();
144				let (file, lineno, column) = stack
145					.records
146					.first()
147					.map(|record| &record.location)
148					.map(|location| (&*location.file, location.lineno, location.column))
149					.unwrap_or_default();
150
151				let stack = Object::from(cx.root(stack.object.unwrap()));
152
153				let file = file.as_value(cx);
154
155				let file_name = cx.root(file.handle().to_string());
156
157				let message = (!self.message.is_empty()).then(|| {
158					let value = self.message.as_value(cx);
159					crate::String::from(cx.root(value.handle().to_string()))
160				});
161				let message = message.unwrap_or_else(|| crate::String::from(cx.root(ptr::null_mut())));
162
163				let mut error = Value::undefined(cx);
164
165				if CreateError(
166					cx.as_ptr(),
167					exception_type,
168					stack.handle().into(),
169					file_name.handle().into(),
170					lineno,
171					column,
172					ptr::null_mut(),
173					message.handle().into(),
174					UndefinedHandleValue,
175					error.handle_mut().into(),
176				) {
177					return Some(error.to_object(cx));
178				}
179			}
180		}
181		None
182	}
183
184	pub fn format(&self) -> String {
185		let Error { kind, message, location, .. } = self;
186		let message = if message.is_empty() {
187			String::new()
188		} else {
189			format!(" - {message}")
190		};
191
192		if let Some(location) = location {
193			let Location { file, lineno, column } = location;
194			if !file.is_empty() {
195				return if *lineno == 0 {
196					format!("{kind} at {file}{message}")
197				} else if *column == 0 {
198					format!("{kind} at {file}:{lineno}{message}")
199				} else {
200					format!("{kind} at {file}:{lineno}:{column}{message}")
201				};
202			}
203		}
204		format!("{kind}{message}")
205	}
206}
207
208impl Display for Error {
209	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
210		f.write_str(&self.message)
211	}
212}
213
214impl From<Error> for fmt::Error {
215	fn from(_: Error) -> fmt::Error {
216		fmt::Error
217	}
218}
219
220impl From<Error> for ErrorReport {
221	fn from(error: Error) -> ErrorReport {
222		ErrorReport::from_exception(Exception::Error(error), None)
223	}
224}
225
226impl<E: error::Error> From<E> for Error {
227	fn from(error: E) -> Error {
228		Error::new(error.to_string(), None)
229	}
230}
231
232impl ThrowException for Error {
233	fn throw(&self, cx: &Context) {
234		unsafe {
235			use ErrorKind as EK;
236			match self.kind {
237				EK::Normal => JS_ReportErrorUTF8(cx.as_ptr(), format!("{}\0", self.message).as_ptr().cast()),
238				EK::Internal => throw_internal_error(cx.as_ptr(), &self.message),
239				EK::Range => throw_range_error(cx.as_ptr(), &self.message),
240				EK::Type => throw_type_error(cx.as_ptr(), &self.message),
241				EK::None => (),
242				_ => unimplemented!("Throwing Exception for this is not implemented"),
243			}
244		}
245	}
246}
247
248impl<'cx> ToValue<'cx> for Error {
249	fn to_value(&self, cx: &'cx Context, value: &mut Value) {
250		self.to_object(cx).to_value(cx, value);
251	}
252}