diff options
author | yvt <i@yvt.jp> | 2021-07-10 17:24:27 +0900 |
---|---|---|
committer | yvt <i@yvt.jp> | 2021-07-10 17:55:42 +0900 |
commit | 01a7de50ab1843d85295f9dccad7f4c099e7208c (patch) | |
tree | ee53fb6e8889deb7b880ee969e6c662e6128d210 /components/script/dom/bindings/error.rs | |
parent | ff8d2cdbbfc7a9dc7f38b7dd47cb350fde39388f (diff) | |
parent | 94b613fbdaa2b98f2179fc0bbda13c64e6fa0d38 (diff) | |
download | servo-01a7de50ab1843d85295f9dccad7f4c099e7208c.tar.gz servo-01a7de50ab1843d85295f9dccad7f4c099e7208c.zip |
Merge remote-tracking branch 'upstream/master' into feat-cow-infra
`tests/wpt/web-platform-tests/html/browsers/origin/cross-origin-objects/cross-origin-objects.html`
was reverted to the upstream version.
Diffstat (limited to 'components/script/dom/bindings/error.rs')
-rw-r--r-- | components/script/dom/bindings/error.rs | 195 |
1 files changed, 119 insertions, 76 deletions
diff --git a/components/script/dom/bindings/error.rs b/components/script/dom/bindings/error.rs index d38a55c31be..79cc8fbd869 100644 --- a/components/script/dom/bindings/error.rs +++ b/components/script/dom/bindings/error.rs @@ -1,31 +1,48 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Utilities to throw exceptions from Rust bindings. -use dom::bindings::codegen::Bindings::DOMExceptionBinding::DOMExceptionMethods; -use dom::bindings::codegen::PrototypeList::proto_id_to_name; -use dom::bindings::conversions::{ConversionResult, FromJSValConvertible, ToJSValConvertible}; -use dom::bindings::conversions::root_from_object; -use dom::bindings::str::USVString; -use dom::domexception::{DOMErrorName, DOMException}; -use dom::globalscope::GlobalScope; +#[cfg(feature = "js_backtrace")] +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::PrototypeList::proto_id_to_name; +use crate::dom::bindings::conversions::root_from_object; +use crate::dom::bindings::conversions::{ + ConversionResult, FromJSValConvertible, ToJSValConvertible, +}; +use crate::dom::bindings::str::USVString; +use crate::dom::domexception::{DOMErrorName, DOMException}; +use crate::dom::globalscope::GlobalScope; +use crate::realms::InRealm; +use crate::script_runtime::JSContext as SafeJSContext; +#[cfg(feature = "js_backtrace")] +use backtrace::Backtrace; use js::error::{throw_range_error, throw_type_error}; -use js::jsapi::HandleObject; +use js::jsapi::ExceptionStackBehavior; use js::jsapi::JSContext; use js::jsapi::JS_ClearPendingException; -use js::jsapi::JS_ErrorFromException; -use js::jsapi::JS_GetPendingException; use js::jsapi::JS_IsExceptionPending; -use js::jsapi::JS_SetPendingException; -use js::jsapi::MutableHandleValue; +#[cfg(feature = "js_backtrace")] +use js::jsapi::StackFormat as JSStackFormat; use js::jsval::UndefinedValue; +use js::rust::wrappers::JS_ErrorFromException; +use js::rust::wrappers::JS_GetPendingException; +use js::rust::wrappers::JS_SetPendingException; +use js::rust::HandleObject; +use js::rust::MutableHandleValue; use libc::c_uint; use std::slice::from_raw_parts; +#[cfg(feature = "js_backtrace")] +thread_local! { + /// An optional stringified JS backtrace and stringified native backtrace from the + /// the last DOM exception that was reported. + static LAST_EXCEPTION_BACKTRACE: DomRefCell<Option<(Option<String>, String)>> = DomRefCell::new(None); +} + /// DOM exceptions that can be thrown by a native DOM method. -#[derive(Debug, Clone, HeapSizeOf)] +#[derive(Clone, Debug, MallocSizeOf)] pub enum Error { /// IndexSizeError DOMException IndexSize, @@ -69,6 +86,10 @@ pub enum Error { TypeMismatch, /// InvalidModificationError DOMException InvalidModification, + /// NotReadableError DOMException + NotReadable, + /// OperationError DOMException + Operation, /// TypeError JavaScript Error Type(String), @@ -87,7 +108,17 @@ pub type Fallible<T> = Result<T, Error>; pub type ErrorResult = Fallible<()>; /// Set a pending exception for the given `result` on `cx`. -pub unsafe fn throw_dom_exception(cx: *mut JSContext, global: &GlobalScope, result: Error) { +pub fn throw_dom_exception(cx: SafeJSContext, global: &GlobalScope, result: Error) { + #[cfg(feature = "js_backtrace")] + unsafe { + capture_stack!(in(*cx) let stack); + let js_stack = stack.and_then(|s| s.as_string(None, JSStackFormat::Default)); + let rust_stack = Backtrace::new(); + LAST_EXCEPTION_BACKTRACE.with(|backtrace| { + *backtrace.borrow_mut() = Some((js_stack, format!("{:?}", rust_stack))); + }); + } + let code = match result { Error::IndexSize => DOMErrorName::IndexSizeError, Error::NotFound => DOMErrorName::NotFoundError, @@ -110,27 +141,31 @@ pub unsafe fn throw_dom_exception(cx: *mut JSContext, global: &GlobalScope, resu Error::QuotaExceeded => DOMErrorName::QuotaExceededError, Error::TypeMismatch => DOMErrorName::TypeMismatchError, Error::InvalidModification => DOMErrorName::InvalidModificationError, - Error::Type(message) => { - assert!(!JS_IsExceptionPending(cx)); - throw_type_error(cx, &message); + Error::NotReadable => DOMErrorName::NotReadableError, + Error::Operation => DOMErrorName::OperationError, + Error::Type(message) => unsafe { + assert!(!JS_IsExceptionPending(*cx)); + throw_type_error(*cx, &message); return; }, - Error::Range(message) => { - assert!(!JS_IsExceptionPending(cx)); - throw_range_error(cx, &message); + Error::Range(message) => unsafe { + assert!(!JS_IsExceptionPending(*cx)); + throw_range_error(*cx, &message); return; }, - Error::JSFailed => { - assert!(JS_IsExceptionPending(cx)); + Error::JSFailed => unsafe { + assert!(JS_IsExceptionPending(*cx)); return; - } + }, }; - assert!(!JS_IsExceptionPending(cx)); - let exception = DOMException::new(global, code); - rooted!(in(cx) let mut thrown = UndefinedValue()); - exception.to_jsval(cx, thrown.handle_mut()); - JS_SetPendingException(cx, thrown.handle()); + unsafe { + assert!(!JS_IsExceptionPending(*cx)); + let exception = DOMException::new(global, code); + rooted!(in(*cx) let mut thrown = UndefinedValue()); + exception.to_jsval(*cx, thrown.handle_mut()); + JS_SetPendingException(*cx, thrown.handle(), ExceptionStackBehavior::Capture); + } } /// A struct encapsulating information about a runtime script error. @@ -146,15 +181,14 @@ pub struct ErrorInfo { } impl ErrorInfo { - unsafe fn from_native_error(cx: *mut JSContext, object: HandleObject) - -> Option<ErrorInfo> { + unsafe fn from_native_error(cx: *mut JSContext, object: HandleObject) -> Option<ErrorInfo> { let report = JS_ErrorFromException(cx, object); if report.is_null() { return None; } let filename = { - let filename = (*report).filename as *const u8; + let filename = (*report)._base.filename as *const u8; if !filename.is_null() { let length = (0..).find(|idx| *filename.offset(*idx) == 0).unwrap(); let filename = from_raw_parts(filename, length as usize); @@ -164,14 +198,14 @@ impl ErrorInfo { } }; - let lineno = (*report).lineno; - let column = (*report).column; + let lineno = (*report)._base.lineno; + let column = (*report)._base.column; let message = { - let message = (*report).ucmessage; + let message = (*report)._base.message_.data_ as *const u8; let length = (0..).find(|idx| *message.offset(*idx) == 0).unwrap(); let message = from_raw_parts(message, length as usize); - String::from_utf16_lossy(message) + String::from_utf8_lossy(message).into_owned() }; Some(ErrorInfo { @@ -182,15 +216,15 @@ impl ErrorInfo { }) } - fn from_dom_exception(object: HandleObject) -> Option<ErrorInfo> { - let exception = match root_from_object::<DOMException>(object.get()) { + fn from_dom_exception(object: HandleObject, cx: *mut JSContext) -> Option<ErrorInfo> { + let exception = match root_from_object::<DOMException>(object.get(), cx) { Ok(exception) => exception, Err(_) => return None, }; Some(ErrorInfo { filename: "".to_string(), - message: exception.Stringifier().into(), + message: exception.stringifier().into(), lineno: 0, column: 0, }) @@ -201,8 +235,10 @@ impl ErrorInfo { /// /// The `dispatch_event` argument is temporary and non-standard; passing false /// prevents dispatching the `error` event. -pub unsafe fn report_pending_exception(cx: *mut JSContext, dispatch_event: bool) { - if !JS_IsExceptionPending(cx) { return; } +pub unsafe fn report_pending_exception(cx: *mut JSContext, dispatch_event: bool, realm: InRealm) { + if !JS_IsExceptionPending(cx) { + return; + } rooted!(in(cx) let mut value = UndefinedValue()); if !JS_GetPendingException(cx, value.handle_mut()) { @@ -215,24 +251,20 @@ pub unsafe fn report_pending_exception(cx: *mut JSContext, dispatch_event: bool) let error_info = if value.is_object() { rooted!(in(cx) let object = value.to_object()); ErrorInfo::from_native_error(cx, object.handle()) - .or_else(|| ErrorInfo::from_dom_exception(object.handle())) - .unwrap_or_else(|| { - ErrorInfo { - message: format!("uncaught exception: unknown (can't convert to string)"), - filename: String::new(), - lineno: 0, - column: 0, - } + .or_else(|| ErrorInfo::from_dom_exception(object.handle(), cx)) + .unwrap_or_else(|| ErrorInfo { + message: format!("uncaught exception: unknown (can't convert to string)"), + filename: String::new(), + lineno: 0, + column: 0, }) } else { match USVString::from_jsval(cx, value.handle(), ()) { - Ok(ConversionResult::Success(USVString(string))) => { - ErrorInfo { - message: format!("uncaught exception: {}", string), - filename: String::new(), - lineno: 0, - column: 0, - } + Ok(ConversionResult::Success(USVString(string))) => ErrorInfo { + message: format!("uncaught exception: {}", string), + filename: String::new(), + lineno: 0, + column: 0, }, _ => { panic!("Uncaught exception: failed to stringify primitive"); @@ -240,40 +272,51 @@ pub unsafe fn report_pending_exception(cx: *mut JSContext, dispatch_event: bool) } }; - error!("Error at {}:{}:{} {}", - error_info.filename, - error_info.lineno, - error_info.column, - error_info.message); + error!( + "Error at {}:{}:{} {}", + error_info.filename, error_info.lineno, error_info.column, error_info.message + ); + #[cfg(feature = "js_backtrace")] + { + LAST_EXCEPTION_BACKTRACE.with(|backtrace| { + if let Some((js_backtrace, rust_backtrace)) = backtrace.borrow_mut().take() { + if let Some(stack) = js_backtrace { + eprintln!("JS backtrace:\n{}", stack); + } + eprintln!("Rust backtrace:\n{}", rust_backtrace); + } + }); + } if dispatch_event { - GlobalScope::from_context(cx) - .report_an_error(error_info, value.handle()); + GlobalScope::from_context(cx, realm).report_an_error(error_info, value.handle()); } } -/// Throw an exception to signal that a `JSVal` can not be converted to any of -/// the types in an IDL union type. -pub unsafe fn throw_not_in_union(cx: *mut JSContext, names: &'static str) { - assert!(!JS_IsExceptionPending(cx)); - let error = format!("argument could not be converted to any of: {}", names); - throw_type_error(cx, &error); -} - /// Throw an exception to signal that a `JSObject` can not be converted to a /// given DOM type. pub unsafe fn throw_invalid_this(cx: *mut JSContext, proto_id: u16) { debug_assert!(!JS_IsExceptionPending(cx)); - let error = format!("\"this\" object does not implement interface {}.", - proto_id_to_name(proto_id)); + let error = format!( + "\"this\" object does not implement interface {}.", + proto_id_to_name(proto_id) + ); throw_type_error(cx, &error); } impl Error { /// Convert this error value to a JS value, consuming it in the process. - pub unsafe fn to_jsval(self, cx: *mut JSContext, global: &GlobalScope, rval: MutableHandleValue) { - assert!(!JS_IsExceptionPending(cx)); - throw_dom_exception(cx, global, self); + pub unsafe fn to_jsval( + self, + cx: *mut JSContext, + global: &GlobalScope, + rval: MutableHandleValue, + ) { + match self { + Error::JSFailed => (), + _ => assert!(!JS_IsExceptionPending(cx)), + } + throw_dom_exception(SafeJSContext::from_ptr(cx), global, self); assert!(JS_IsExceptionPending(cx)); assert!(JS_GetPendingException(cx, rval)); JS_ClearPendingException(cx); |