aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbors-servo <lbergstrom+bors@mozilla.com>2018-10-02 06:57:38 -0400
committerGitHub <noreply@github.com>2018-10-02 06:57:38 -0400
commit57053e03bbfc65e1d2f283cfa8646038be9f9b06 (patch)
treec15d90f86e590b0a45a4364ed044d2deb34722ff
parent37b978af686975eaf13689580868d9ea201ef04c (diff)
parent06bca43aeef990da8f02cd8b814329f7607365dd (diff)
downloadservo-57053e03bbfc65e1d2f283cfa8646038be9f9b06.tar.gz
servo-57053e03bbfc65e1d2f283cfa8646038be9f9b06.zip
Auto merge of #21825 - jdm:backtrace-helpers, r=Manishearth
Optional backtraces for JS errors and WebGL errors This adds two new build-time features to enable useful debugging tools when investigating why JS and WebGL content isn't working. They're optional because they're quite heavyweight. --- - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes do not require tests because they're optional developer features. <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/21825) <!-- Reviewable:end -->
-rw-r--r--Cargo.lock1
-rw-r--r--components/canvas/Cargo.toml3
-rw-r--r--components/canvas/gl_context.rs13
-rw-r--r--components/canvas/webgl_thread.rs25
-rw-r--r--components/canvas_traits/Cargo.toml3
-rw-r--r--components/canvas_traits/webgl.rs14
-rw-r--r--components/script/Cargo.toml3
-rw-r--r--components/script/dom/bindings/error.rs30
-rw-r--r--components/script/dom/webglrenderingcontext.rs28
-rw-r--r--components/script/lib.rs2
-rw-r--r--components/servo/Cargo.toml6
-rw-r--r--ports/libsimpleservo/Cargo.toml2
-rw-r--r--ports/servo/Cargo.toml2
-rw-r--r--python/servo/build_commands.py5
-rw-r--r--python/servo/command_base.py2
-rw-r--r--servobuild.example8
16 files changed, 131 insertions, 16 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 85859bf7c72..6d839d69c0e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2981,6 +2981,7 @@ version = "0.0.1"
dependencies = [
"app_units 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"audio-video-metadata 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"bluetooth_traits 0.0.1",
diff --git a/components/canvas/Cargo.toml b/components/canvas/Cargo.toml
index d65c1b4b110..4006947acf6 100644
--- a/components/canvas/Cargo.toml
+++ b/components/canvas/Cargo.toml
@@ -9,6 +9,9 @@ publish = false
name = "canvas"
path = "lib.rs"
+[features]
+webgl_backtrace = ["canvas_traits/webgl_backtrace"]
+
[dependencies]
azure = {git = "https://github.com/servo/rust-azure"}
canvas_traits = {path = "../canvas_traits"}
diff --git a/components/canvas/gl_context.rs b/components/canvas/gl_context.rs
index 00e3f3ba4a9..c90ecec9cca 100644
--- a/components/canvas/gl_context.rs
+++ b/components/canvas/gl_context.rs
@@ -2,7 +2,7 @@
* 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/. */
-use canvas_traits::webgl::{WebGLCommand, WebGLVersion};
+use canvas_traits::webgl::{WebGLCommand, WebGLVersion, WebGLCommandBacktrace};
use compositing::compositor_thread::{CompositorProxy, self};
use euclid::Size2D;
use gleam::gl;
@@ -144,13 +144,18 @@ impl GLContextWrapper {
}
}
- pub fn apply_command(&self, cmd: WebGLCommand, state: &mut GLState) {
+ pub fn apply_command(
+ &self,
+ cmd: WebGLCommand,
+ backtrace: WebGLCommandBacktrace,
+ state: &mut GLState
+ ) {
match *self {
GLContextWrapper::Native(ref ctx) => {
- WebGLImpl::apply(ctx, state, cmd);
+ WebGLImpl::apply(ctx, state, cmd, backtrace);
}
GLContextWrapper::OSMesa(ref ctx) => {
- WebGLImpl::apply(ctx, state, cmd);
+ WebGLImpl::apply(ctx, state, cmd, backtrace);
}
}
}
diff --git a/components/canvas/webgl_thread.rs b/components/canvas/webgl_thread.rs
index c6d9082b7e0..4d8a4c044a2 100644
--- a/components/canvas/webgl_thread.rs
+++ b/components/canvas/webgl_thread.rs
@@ -137,8 +137,8 @@ impl<VR: WebVRRenderHandler + 'static> WebGLThread<VR> {
WebGLMsg::RemoveContext(ctx_id) => {
self.remove_webgl_context(ctx_id);
},
- WebGLMsg::WebGLCommand(ctx_id, command) => {
- self.handle_webgl_command(ctx_id, command);
+ WebGLMsg::WebGLCommand(ctx_id, command, backtrace) => {
+ self.handle_webgl_command(ctx_id, command, backtrace);
},
WebGLMsg::WebVRCommand(ctx_id, command) => {
self.handle_webvr_command(ctx_id, command);
@@ -164,10 +164,15 @@ impl<VR: WebVRRenderHandler + 'static> WebGLThread<VR> {
}
/// Handles a WebGLCommand for a specific WebGLContext
- fn handle_webgl_command(&mut self, context_id: WebGLContextId, command: WebGLCommand) {
+ fn handle_webgl_command(
+ &mut self,
+ context_id: WebGLContextId,
+ command: WebGLCommand,
+ backtrace: WebGLCommandBacktrace,
+ ) {
let data = Self::make_current_if_needed_mut(context_id, &mut self.contexts, &mut self.bound_context_id);
if let Some(data) = data {
- data.ctx.apply_command(command, &mut data.state);
+ data.ctx.apply_command(command, backtrace, &mut data.state);
}
}
@@ -672,7 +677,8 @@ impl WebGLImpl {
pub fn apply<Native: NativeGLContextMethods>(
ctx: &GLContext<Native>,
state: &mut GLState,
- command: WebGLCommand
+ command: WebGLCommand,
+ _backtrace: WebGLCommandBacktrace,
) {
match command {
WebGLCommand::GetContextAttributes(ref sender) =>
@@ -1193,7 +1199,14 @@ impl WebGLImpl {
// TODO: update test expectations in order to enable debug assertions
let error = ctx.gl().get_error();
if error != gl::NO_ERROR {
- error!("Last GL operation failed: {:?}", command)
+ error!("Last GL operation failed: {:?}", command);
+ #[cfg(feature = "webgl_backtrace")]
+ {
+ error!("Backtrace from failed WebGL API:\n{}", _backtrace.backtrace);
+ if let Some(backtrace) = _backtrace.js_backtrace {
+ error!("JS backtrace from failed WebGL API:\n{}", backtrace);
+ }
+ }
}
assert_eq!(error, gl::NO_ERROR, "Unexpected WebGL error: 0x{:x} ({})", error, error);
}
diff --git a/components/canvas_traits/Cargo.toml b/components/canvas_traits/Cargo.toml
index b5599232b5c..187b46d617e 100644
--- a/components/canvas_traits/Cargo.toml
+++ b/components/canvas_traits/Cargo.toml
@@ -9,6 +9,9 @@ publish = false
name = "canvas_traits"
path = "lib.rs"
+[features]
+webgl_backtrace = []
+
[dependencies]
cssparser = "0.24.0"
euclid = "0.19"
diff --git a/components/canvas_traits/webgl.rs b/components/canvas_traits/webgl.rs
index 8f9f4e70a31..ad10ed7edd0 100644
--- a/components/canvas_traits/webgl.rs
+++ b/components/canvas_traits/webgl.rs
@@ -24,6 +24,14 @@ pub use ::webgl_channel::WebGLPipeline;
/// Entry point channel type used for sending WebGLMsg messages to the WebGL renderer.
pub use ::webgl_channel::WebGLChan;
+#[derive(Clone, Deserialize, Serialize)]
+pub struct WebGLCommandBacktrace {
+ #[cfg(feature = "webgl_backtrace")]
+ pub backtrace: String,
+ #[cfg(feature = "webgl_backtrace")]
+ pub js_backtrace: Option<String>,
+}
+
/// WebGL Message API
#[derive(Deserialize, Serialize)]
pub enum WebGLMsg {
@@ -35,7 +43,7 @@ pub enum WebGLMsg {
/// Drops a WebGLContext.
RemoveContext(WebGLContextId),
/// Runs a WebGLCommand in a specific WebGLContext.
- WebGLCommand(WebGLContextId, WebGLCommand),
+ WebGLCommand(WebGLContextId, WebGLCommand, WebGLCommandBacktrace),
/// Runs a WebVRCommand in a specific WebGLContext.
WebVRCommand(WebGLContextId, WebVRCommand),
/// Locks a specific WebGLContext. Lock messages are used for a correct synchronization
@@ -121,8 +129,8 @@ impl WebGLMsgSender {
/// Send a WebGLCommand message
#[inline]
- pub fn send(&self, command: WebGLCommand) -> WebGLSendResult {
- self.sender.send(WebGLMsg::WebGLCommand(self.ctx_id, command))
+ pub fn send(&self, command: WebGLCommand, backtrace: WebGLCommandBacktrace) -> WebGLSendResult {
+ self.sender.send(WebGLMsg::WebGLCommand(self.ctx_id, command, backtrace))
}
/// Send a WebVRCommand message
diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml
index 1c8feb0f17e..ca859bb472a 100644
--- a/components/script/Cargo.toml
+++ b/components/script/Cargo.toml
@@ -16,6 +16,8 @@ debugmozjs = ['mozjs/debugmozjs']
unstable = []
unrooted_must_root_lint = ["script_plugins/unrooted_must_root_lint"]
default = ["unrooted_must_root_lint"]
+webgl_backtrace = ["backtrace", "canvas_traits/webgl_backtrace"]
+js_backtrace = ["backtrace"]
[build-dependencies]
cmake = "0.1"
@@ -29,6 +31,7 @@ tinyfiledialogs = "3.0"
[dependencies]
app_units = "0.7"
audio-video-metadata = "0.1.4"
+backtrace = {version = "0.3", optional = true}
base64 = "0.6"
bitflags = "1.0"
bluetooth_traits = {path = "../bluetooth_traits"}
diff --git a/components/script/dom/bindings/error.rs b/components/script/dom/bindings/error.rs
index ce045b2c9d3..64b581f316d 100644
--- a/components/script/dom/bindings/error.rs
+++ b/components/script/dom/bindings/error.rs
@@ -4,6 +4,10 @@
//! Utilities to throw exceptions from Rust bindings.
+#[cfg(feature = "js_backtrace")]
+use backtrace::Backtrace;
+#[cfg(feature = "js_backtrace")]
+use dom::bindings::cell::DomRefCell;
use dom::bindings::codegen::Bindings::DOMExceptionBinding::DOMExceptionMethods;
use dom::bindings::codegen::PrototypeList::proto_id_to_name;
use dom::bindings::conversions::{ConversionResult, FromJSValConvertible, ToJSValConvertible};
@@ -24,6 +28,11 @@ use js::rust::wrappers::JS_SetPendingException;
use libc::c_uint;
use std::slice::from_raw_parts;
+/// An optional stringified JS backtrace and stringified native backtrace from the
+/// the last DOM exception that was reported.
+#[cfg(feature = "js_backtrace")]
+thread_local!(static LAST_EXCEPTION_BACKTRACE: DomRefCell<Option<(Option<String>, String)>> = DomRefCell::new(None));
+
/// DOM exceptions that can be thrown by a native DOM method.
#[derive(Clone, Debug, MallocSizeOf)]
pub enum Error {
@@ -90,6 +99,16 @@ 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) {
+ #[cfg(feature = "js_backtrace")]
+ {
+ capture_stack!(in(cx) let stack);
+ let js_stack = stack.and_then(|s| s.as_string(None));
+ 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,
@@ -244,6 +263,17 @@ pub unsafe fn report_pending_exception(cx: *mut JSContext, dispatch_event: bool)
"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());
diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs
index 7367a38fdcf..754637ad52b 100644
--- a/components/script/dom/webglrenderingcontext.rs
+++ b/components/script/dom/webglrenderingcontext.rs
@@ -2,9 +2,11 @@
* 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/. */
+#[cfg(feature = "webgl_backtrace")]
+use backtrace::Backtrace;
use byteorder::{ByteOrder, NativeEndian, WriteBytesExt};
use canvas_traits::canvas::{byte_swap, multiply_u8_pixel};
-use canvas_traits::webgl::{DOMToTextureCommand, Parameter};
+use canvas_traits::webgl::{DOMToTextureCommand, Parameter, WebGLCommandBacktrace};
use canvas_traits::webgl::{TexParameter, WebGLCommand, WebGLContextShareMode, WebGLError};
use canvas_traits::webgl::{WebGLFramebufferBindingRequest, WebGLMsg, WebGLMsgSender};
use canvas_traits::webgl::{WebGLProgramId, WebGLResult, WebGLSLVersion, WebGLSender};
@@ -316,7 +318,7 @@ impl WebGLRenderingContext {
#[inline]
pub fn send_command(&self, command: WebGLCommand) {
- self.webgl_sender.send(command).unwrap();
+ self.webgl_sender.send(command, capture_webgl_backtrace(self)).unwrap();
}
#[inline]
@@ -1189,6 +1191,25 @@ impl WebGLRenderingContext {
}
}
+#[cfg(not(feature = "webgl_backtrace"))]
+#[inline]
+pub fn capture_webgl_backtrace<T: DomObject>(_: &T) -> WebGLCommandBacktrace {
+ WebGLCommandBacktrace {}
+}
+
+#[cfg(feature = "webgl_backtrace")]
+#[cfg_attr(feature = "webgl_backtrace", allow(unsafe_code))]
+pub fn capture_webgl_backtrace<T: DomObject>(obj: &T) -> WebGLCommandBacktrace {
+ let bt = Backtrace::new();
+ unsafe {
+ capture_stack!(in(obj.global().get_cx()) let stack);
+ WebGLCommandBacktrace {
+ backtrace: format!("{:?}", bt),
+ js_backtrace: stack.and_then(|s| s.as_string(None)),
+ }
+ }
+}
+
impl Drop for WebGLRenderingContext {
fn drop(&mut self) {
let _ = self.webgl_sender.send_remove();
@@ -1521,9 +1542,10 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
let (sender, receiver) = webgl_channel().unwrap();
// If the send does not succeed, assume context lost
+ let backtrace = capture_webgl_backtrace(self);
if self
.webgl_sender
- .send(WebGLCommand::GetContextAttributes(sender))
+ .send(WebGLCommand::GetContextAttributes(sender), backtrace)
.is_err()
{
return None;
diff --git a/components/script/lib.rs b/components/script/lib.rs
index f8341323466..097f69d7808 100644
--- a/components/script/lib.rs
+++ b/components/script/lib.rs
@@ -19,6 +19,8 @@
extern crate app_units;
extern crate audio_video_metadata;
+#[cfg(any(feature = "webgl_backtrace", feature = "js_backtrace"))]
+extern crate backtrace;
extern crate base64;
#[macro_use]
extern crate bitflags;
diff --git a/components/servo/Cargo.toml b/components/servo/Cargo.toml
index 423d6814810..7d2a7895215 100644
--- a/components/servo/Cargo.toml
+++ b/components/servo/Cargo.toml
@@ -17,6 +17,7 @@ webdriver = ["webdriver_server"]
energy-profiling = ["profile_traits/energy-profiling"]
debugmozjs = ["script/debugmozjs"]
googlevr = ["webvr/googlevr"]
+js_backtrace = ["script/js_backtrace"]
webrender_debugger = ["webrender/debugger"]
oculusvr = ["webvr/oculusvr"]
unstable = [
@@ -24,6 +25,11 @@ unstable = [
"profile/unstable",
"script/unstable",
]
+webgl_backtrace = [
+ "script/webgl_backtrace",
+ "canvas/webgl_backtrace",
+ "canvas_traits/webgl_backtrace",
+]
[dependencies]
bluetooth_traits = {path = "../bluetooth_traits"}
diff --git a/ports/libsimpleservo/Cargo.toml b/ports/libsimpleservo/Cargo.toml
index 6cb4d4f6c3c..5af57fe8e69 100644
--- a/ports/libsimpleservo/Cargo.toml
+++ b/ports/libsimpleservo/Cargo.toml
@@ -42,3 +42,5 @@ debugmozjs = ["libservo/debugmozjs"]
unstable = ["libservo/unstable"]
googlevr = ["libservo/googlevr"]
oculusvr = ["libservo/oculusvr"]
+webgl_backtrace = ["libservo/webgl_backtrace"]
+js_backtrace = ["libservo/js_backtrace"]
diff --git a/ports/servo/Cargo.toml b/ports/servo/Cargo.toml
index a31f6306514..298dae298f9 100644
--- a/ports/servo/Cargo.toml
+++ b/ports/servo/Cargo.toml
@@ -30,6 +30,8 @@ webdriver = ["libservo/webdriver"]
energy-profiling = ["libservo/energy-profiling"]
debugmozjs = ["libservo/debugmozjs"]
unstable = ["libservo/unstable"]
+webgl_backtrace = ["libservo/webgl_backtrace"]
+js_backtrace = ["libservo/js_backtrace"]
[target.'cfg(not(target_os = "android"))'.dependencies]
backtrace = "0.3"
diff --git a/python/servo/build_commands.py b/python/servo/build_commands.py
index da74a201aa4..5dc21d53e7d 100644
--- a/python/servo/build_commands.py
+++ b/python/servo/build_commands.py
@@ -264,6 +264,11 @@ class MachCommands(CommandBase):
if debug_mozjs:
features += ["debugmozjs"]
+ if self.config["build"]["webgl-backtrace"]:
+ features += ["webgl-backtrace"]
+ if self.config["build"]["dom-backtrace"]:
+ features += ["dom-backtrace"]
+
if features:
opts += ["--features", "%s" % ' '.join(features)]
diff --git a/python/servo/command_base.py b/python/servo/command_base.py
index b6faa53a5e1..3770de6c9cd 100644
--- a/python/servo/command_base.py
+++ b/python/servo/command_base.py
@@ -288,6 +288,8 @@ class CommandBase(object):
self.config["build"].setdefault("rustflags", "")
self.config["build"].setdefault("incremental", None)
self.config["build"].setdefault("thinlto", False)
+ self.config["build"].setdefault("webgl-backtrace", False)
+ self.config["build"].setdefault("dom-backtrace", False)
self.config.setdefault("android", {})
self.config["android"].setdefault("sdk", "")
diff --git a/servobuild.example b/servobuild.example
index 2df2dc256e1..2070da1a59c 100644
--- a/servobuild.example
+++ b/servobuild.example
@@ -32,6 +32,14 @@ android = false
# Set "debug-mozjs" or use `mach build --debug-mozjs` to build a debug spidermonkey.
debug-mozjs = false
+# When a GL error occurs as a result of a WebGL operation, print the stack trace for the content
+# JS and native Rust code that triggered the failed operation. Warning: very slow.
+webgl-backtrace = false
+
+# When a DOM exception is reported, print the stack trace for the content JS and native Rust code
+# that triggered it.
+dom-backtrace = false
+
# Set to the path to your ccache binary to enable caching of compiler outputs
#ccache = "/usr/local/bin/ccache"