Auto merge of #11803 - jdm:catch-unwind, r=nox

Avoid unwinding into C stack frames

Fix the biggest cause of #6462 by wrapping lots of JS->Rust transitions in catch_panic, and calling resume_panic after all Rust->JS transitions return.

Known issue:
* Finalizers can be called in response to any JS engine allocation that triggers a GC, so it's possible for a Rust object's Drop implementation that panics to leave an interrupted panic in TLS. This is why 30d8009 is part of this PR; the underlying problem is that there's no clear place to resume the panic after it is interrupted.

---
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix #6462 (github issue number if applicable).
- [X] There are tests for these changes OR

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/11803)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2016-06-22 08:36:01 -05:00 committed by GitHub
commit 87d991ebd2
8 changed files with 81 additions and 12 deletions

View file

@ -2145,7 +2145,7 @@ class CGAbstractMethod(CGThing):
""" """
def __init__(self, descriptor, name, returnType, args, inline=False, def __init__(self, descriptor, name, returnType, args, inline=False,
alwaysInline=False, extern=False, pub=False, templateArgs=None, alwaysInline=False, extern=False, pub=False, templateArgs=None,
unsafe=False, docs=None): unsafe=False, docs=None, doesNotPanic=False):
CGThing.__init__(self) CGThing.__init__(self)
self.descriptor = descriptor self.descriptor = descriptor
self.name = name self.name = name
@ -2157,6 +2157,7 @@ class CGAbstractMethod(CGThing):
self.pub = pub self.pub = pub
self.unsafe = unsafe self.unsafe = unsafe
self.docs = docs self.docs = docs
self.catchPanic = self.extern and not doesNotPanic
def _argstring(self): def _argstring(self):
return ', '.join([a.declare() for a in self.args]) return ', '.join([a.declare() for a in self.args])
@ -2199,6 +2200,19 @@ class CGAbstractMethod(CGThing):
if self.unsafe and not self.extern: if self.unsafe and not self.extern:
body = CGWrapper(CGIndenter(body), pre="unsafe {\n", post="\n}") body = CGWrapper(CGIndenter(body), pre="unsafe {\n", post="\n}")
if self.catchPanic:
body = CGWrapper(CGIndenter(body),
pre="let result = panic::catch_unwind(AssertUnwindSafe(|| {\n",
post=("""}));
match result {
Ok(result) => result,
Err(error) => {
store_panic_result(error);
return%s;
}
}
""" % ("" if self.returnType == "void" else " false")))
return CGWrapper(CGIndenter(body), return CGWrapper(CGIndenter(body),
pre=self.definition_prologue(), pre=self.definition_prologue(),
post=self.definition_epilogue()).define() post=self.definition_epilogue()).define()
@ -2447,9 +2461,9 @@ class CGAbstractExternMethod(CGAbstractMethod):
Abstract base class for codegen of implementation-only (no Abstract base class for codegen of implementation-only (no
declaration) static methods. declaration) static methods.
""" """
def __init__(self, descriptor, name, returnType, args): def __init__(self, descriptor, name, returnType, args, doesNotPanic=False):
CGAbstractMethod.__init__(self, descriptor, name, returnType, args, CGAbstractMethod.__init__(self, descriptor, name, returnType, args,
inline=False, extern=True) inline=False, extern=True, doesNotPanic=doesNotPanic)
class PropertyArrays(): class PropertyArrays():
@ -4830,7 +4844,7 @@ return true;""" % (getIndexedOrExpando, getNamed)
class CGDOMJSProxyHandler_className(CGAbstractExternMethod): class CGDOMJSProxyHandler_className(CGAbstractExternMethod):
def __init__(self, descriptor): def __init__(self, descriptor):
args = [Argument('*mut JSContext', 'cx'), Argument('HandleObject', '_proxy')] args = [Argument('*mut JSContext', 'cx'), Argument('HandleObject', '_proxy')]
CGAbstractExternMethod.__init__(self, descriptor, "className", "*const i8", args) CGAbstractExternMethod.__init__(self, descriptor, "className", "*const i8", args, doesNotPanic=True)
self.descriptor = descriptor self.descriptor = descriptor
def getBody(self): def getBody(self):
@ -4845,7 +4859,7 @@ class CGAbstractClassHook(CGAbstractExternMethod):
Meant for implementing JSClass hooks, like Finalize or Trace. Does very raw Meant for implementing JSClass hooks, like Finalize or Trace. Does very raw
'this' unwrapping as it assumes that the unwrapped type is always known. 'this' unwrapping as it assumes that the unwrapped type is always known.
""" """
def __init__(self, descriptor, name, returnType, args): def __init__(self, descriptor, name, returnType, args, doesNotPanic=False):
CGAbstractExternMethod.__init__(self, descriptor, name, returnType, CGAbstractExternMethod.__init__(self, descriptor, name, returnType,
args) args)
@ -4905,7 +4919,7 @@ class CGClassTraceHook(CGAbstractClassHook):
def __init__(self, descriptor): def __init__(self, descriptor):
args = [Argument('*mut JSTracer', 'trc'), Argument('*mut JSObject', 'obj')] args = [Argument('*mut JSTracer', 'trc'), Argument('*mut JSObject', 'obj')]
CGAbstractClassHook.__init__(self, descriptor, TRACE_HOOK_NAME, 'void', CGAbstractClassHook.__init__(self, descriptor, TRACE_HOOK_NAME, 'void',
args) args, doesNotPanic=True)
self.traceGlobal = descriptor.isGlobal() self.traceGlobal = descriptor.isGlobal()
def generate_code(self): def generate_code(self):
@ -5597,11 +5611,13 @@ class CGBindingRoot(CGThing):
'mem::heap_size_of_raw_self_and_children', 'mem::heap_size_of_raw_self_and_children',
'libc', 'libc',
'util::prefs', 'util::prefs',
'script_runtime::{store_panic_result, maybe_take_panic_result}',
'std::borrow::ToOwned', 'std::borrow::ToOwned',
'std::cmp', 'std::cmp',
'std::mem', 'std::mem',
'std::num', 'std::num',
'std::os', 'std::os',
'std::panic::{self, AssertUnwindSafe}',
'std::ptr', 'std::ptr',
'std::str', 'std::str',
'std::rc', 'std::rc',
@ -6088,6 +6104,9 @@ class CallbackMethod(CallbackMember):
" length_: ${argc} as ::libc::size_t,\n" " length_: ${argc} as ::libc::size_t,\n"
" elements_: ${argv}\n" " elements_: ${argv}\n"
" }, rval.handle_mut());\n" " }, rval.handle_mut());\n"
"if let Some(error) = maybe_take_panic_result() {\n"
" panic::resume_unwind(error);\n"
"}\n"
"if !ok {\n" "if !ok {\n"
" return Err(JSFailed);\n" " return Err(JSFailed);\n"
"}\n").substitute(replacements) "}\n").substitute(replacements)

View file

@ -192,7 +192,9 @@ impl Node {
debug_assert!(thread_state::get().is_script()); debug_assert!(thread_state::get().is_script());
let win = window_from_node(self); let win = window_from_node(self);
self.style_and_layout_data.set(None); self.style_and_layout_data.set(None);
win.layout_chan().send(Msg::ReapStyleAndLayoutData(data)).unwrap(); if win.layout_chan().send(Msg::ReapStyleAndLayoutData(data)).is_err() {
warn!("layout thread unreachable - leaking layout data");
}
} }
/// Adds a new child to the end of this node's list of children. /// Adds a new child to the end of this node's list of children.

View file

@ -580,6 +580,8 @@ impl TestBindingMethods for TestBinding {
ptr::write_volatile(p, 0xbaadc0de); ptr::write_volatile(p, 0xbaadc0de);
} }
} }
fn Panic(&self) { panic!("explicit panic from script") }
} }
impl TestBinding { impl TestBinding {

View file

@ -455,6 +455,8 @@ interface TestBinding {
static void funcControlledStaticMethodEnabled(); static void funcControlledStaticMethodEnabled();
[Func="TestBinding::condition_satisfied"] [Func="TestBinding::condition_satisfied"]
const unsigned short funcControlledConstEnabled = 0; const unsigned short funcControlledConstEnabled = 0;
void panic();
}; };
partial interface TestBinding { partial interface TestBinding {

View file

@ -61,7 +61,7 @@ use script_layout_interface::message::{Msg, Reflow, ReflowQueryType, ScriptReflo
use script_layout_interface::reporter::CSSErrorReporter; use script_layout_interface::reporter::CSSErrorReporter;
use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC}; use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC};
use script_layout_interface::rpc::{MarginStyleResponse, ResolvedStyleResponse}; use script_layout_interface::rpc::{MarginStyleResponse, ResolvedStyleResponse};
use script_runtime::{ScriptChan, ScriptPort}; use script_runtime::{ScriptChan, ScriptPort, maybe_take_panic_result};
use script_thread::SendableMainThreadScriptChan; use script_thread::SendableMainThreadScriptChan;
use script_thread::{MainThreadScriptChan, MainThreadScriptMsg, RunnableWrapper}; use script_thread::{MainThreadScriptChan, MainThreadScriptMsg, RunnableWrapper};
use script_traits::{ConstellationControlMsg, UntrustedNodeAddress}; use script_traits::{ConstellationControlMsg, UntrustedNodeAddress};
@ -74,6 +74,7 @@ use std::collections::{HashMap, HashSet};
use std::default::Default; use std::default::Default;
use std::ffi::CString; use std::ffi::CString;
use std::io::{Write, stderr, stdout}; use std::io::{Write, stderr, stdout};
use std::panic;
use std::rc::Rc; use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::TryRecvError::{Disconnected, Empty}; use std::sync::mpsc::TryRecvError::{Disconnected, Empty};
@ -914,6 +915,10 @@ impl<'a, T: Reflectable> ScriptHelpers for &'a T {
report_pending_exception(cx, globalhandle.get()); report_pending_exception(cx, globalhandle.get());
} }
} }
if let Some(error) = maybe_take_panic_result() {
panic::resume_unwind(error);
}
} }
) )
} }

View file

@ -28,11 +28,12 @@ use msg::constellation_msg::{PipelineId, ReferrerPolicy, PanicMsg};
use net_traits::{LoadContext, ResourceThreads, load_whole_resource}; use net_traits::{LoadContext, ResourceThreads, load_whole_resource};
use net_traits::{RequestSource, LoadOrigin, CustomResponseSender, IpcSend}; use net_traits::{RequestSource, LoadOrigin, CustomResponseSender, IpcSend};
use profile_traits::{mem, time}; use profile_traits::{mem, time};
use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort}; use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, maybe_take_panic_result};
use script_traits::ScriptMsg as ConstellationMsg; use script_traits::ScriptMsg as ConstellationMsg;
use script_traits::{MsDuration, TimerEvent, TimerEventId, TimerEventRequest, TimerSource}; use script_traits::{MsDuration, TimerEvent, TimerEventId, TimerEventRequest, TimerSource};
use std::cell::Cell; use std::cell::Cell;
use std::default::Default; use std::default::Default;
use std::panic;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
@ -320,8 +321,14 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope {
} }
}; };
match self.runtime.evaluate_script( let result = self.runtime.evaluate_script(
self.reflector().get_jsobject(), &source, url.as_str(), 1, rval.handle_mut()) { self.reflector().get_jsobject(), &source, url.as_str(), 1, rval.handle_mut());
if let Some(error) = maybe_take_panic_result() {
panic::resume_unwind(error);
}
match result {
Ok(_) => (), Ok(_) => (),
Err(_) => { Err(_) => {
println!("evaluate_script failed"); println!("evaluate_script failed");

View file

@ -19,7 +19,8 @@ use js::jsapi::{JSObject, RuntimeOptionsRef, SetPreserveWrapperCallback};
use js::rust::Runtime; use js::rust::Runtime;
use profile_traits::mem::{Report, ReportKind, ReportsChan}; use profile_traits::mem::{Report, ReportKind, ReportsChan};
use script_thread::{Runnable, STACK_ROOTS, trace_thread}; use script_thread::{Runnable, STACK_ROOTS, trace_thread};
use std::cell::Cell; use std::any::Any;
use std::cell::{RefCell, Cell};
use std::io::{Write, stdout}; use std::io::{Write, stdout};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::os; use std::os;
@ -321,6 +322,21 @@ pub fn get_reports(cx: *mut JSContext, path_seg: String) -> Vec<Report> {
reports reports
} }
thread_local!(static PANIC_RESULT: RefCell<Option<Box<Any + Send>>> = RefCell::new(None));
pub fn store_panic_result(error: Box<Any + Send>) {
PANIC_RESULT.with(|result| {
assert!(result.borrow().is_none());
*result.borrow_mut() = Some(error);
});
}
pub fn maybe_take_panic_result() -> Option<Box<Any + Send>> {
PANIC_RESULT.with(|result| {
result.borrow_mut().take()
})
}
thread_local!(static GC_CYCLE_START: Cell<Option<Tm>> = Cell::new(None)); thread_local!(static GC_CYCLE_START: Cell<Option<Tm>> = Cell::new(None));
thread_local!(static GC_SLICE_START: Cell<Option<Tm>> = Cell::new(None)); thread_local!(static GC_SLICE_START: Cell<Option<Tm>> = Cell::new(None));

16
tests/html/panic.html Normal file
View file

@ -0,0 +1,16 @@
<!-- To exercise these tests, set the `dom.testbinding.enabled` to true.
It is expected that the browser will not abort due to failed JS engine assertions. -->
<!-- Straightforward test - invoking a panic from a toplevel script execution -->
<script>
(new TestBinding()).panic();
</script>
<!-- invoking a panic from an event handler which is invoked by native code -->
<!--<iframe src="data:,hi there" onload="(new TestBinding()).panic()"></iframe>-->
<!-- invoking a panic from an event handler which is invoked by script -->
<!--<div onclick="(new TestBinding()).panic()"></div>
<script>
document.querySelector('div').click();
</script>-->