Stringify unknown JavaScript objects in global exception handlers

When turning DOM exceptions into `ErrorInfo` always try to stringify
the JavaScript value, even if it's an object that isn't a `DOMException`
or native exception.  This means that exceptions that extend the `Error`
prototype are now stringified. The result is that test output for WPT
global assertion failures is more useful. For instance for the test
include-frames-from-child-same-origin-grandchild.sub.html:

Before:
```
uncaught exception: unknown (can't convert to string)
```

After:
```
uncaught exception: Error: assert_equals: expected 4 but got 3
```
This commit is contained in:
Martin Robinson 2023-01-12 17:10:34 +01:00
parent 633f14df11
commit e68ebd2617
3 changed files with 81 additions and 30 deletions

View file

@ -29,8 +29,7 @@ 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 js::rust::{HandleObject, HandleValue, MutableHandleValue};
use libc::c_uint;
use std::slice::from_raw_parts;
@ -181,7 +180,7 @@ pub struct ErrorInfo {
}
impl ErrorInfo {
unsafe fn from_native_error(cx: *mut JSContext, object: HandleObject) -> Option<ErrorInfo> {
unsafe fn from_native_error(object: HandleObject, cx: *mut JSContext) -> Option<ErrorInfo> {
let report = JS_ErrorFromException(cx, object);
if report.is_null() {
return None;
@ -209,10 +208,10 @@ impl ErrorInfo {
};
Some(ErrorInfo {
filename: filename,
message: message,
lineno: lineno,
column: column,
filename,
message,
lineno,
column,
})
}
@ -229,6 +228,37 @@ impl ErrorInfo {
column: 0,
})
}
unsafe fn from_object(object: HandleObject, cx: *mut JSContext) -> Option<ErrorInfo> {
if let Some(info) = ErrorInfo::from_native_error(object, cx) {
return Some(info);
}
if let Some(info) = ErrorInfo::from_dom_exception(object, cx) {
return Some(info);
}
return None;
}
unsafe fn from_value(value: HandleValue, cx: *mut JSContext) -> ErrorInfo {
if value.is_object() {
rooted!(in(cx) let object = value.to_object());
if let Some(info) = ErrorInfo::from_object(object.handle(), cx) {
return info;
}
}
match USVString::from_jsval(cx, value, ()) {
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");
},
}
}
}
/// Report a pending exception, thereby clearing it.
@ -248,29 +278,7 @@ pub unsafe fn report_pending_exception(cx: *mut JSContext, dispatch_event: bool,
}
JS_ClearPendingException(cx);
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(), 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,
},
_ => {
panic!("Uncaught exception: failed to stringify primitive");
},
}
};
let error_info = ErrorInfo::from_value(value.handle(), cx);
error!(
"Error at {}:{}:{} {}",

View file

@ -13746,6 +13746,13 @@
{}
]
],
"global_exception_stringification.html": [
"b1c6cb6c390cca290085eea61ba4bf2f34aa4475",
[
null,
{}
]
],
"globals": {
"entry.html": [
"f963385342adbd92e4858a507c88155b4ed4371f",

View file

@ -0,0 +1,36 @@
<!-- doctype html -->
<!DOCTYPE html>
<meta charset=utf-8>
<meta name="viewport" content="width=device-width,initial-scale=1">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<div id="testOutput"></div>
</body>
<script>
setup({ 'allow_uncaught_exception': true });
promise_test(async t => {
function CustomError(message) {
this.message = message;
}
CustomError.prototype = Object.create(Error.prototype);
let message = null;
let waitForError = new Promise(resolve => {
window.onerror = (errorMessage) => {
message = errorMessage;
resolve();
};
});
setTimeout(() => {
throw new CustomError("An exceptional exception.");
}, 0);
await waitForError;
assert_equals(message, "uncaught exception: Error: An exceptional exception.");
}, "Exception is stringified properly.");
</script>