Bug 968936 - Emit a warning message with stack trace when the "operation callback" (slow script dialog) stops script execution. r=luke.
authorJason Orendorff <jorendorff@mozilla.com>
Wed, 26 Feb 2014 08:55:35 -0600
changeset 171035 c90e26bbcdf58173fbef9118a9a7092c4a813d62
parent 171034 7a2bb8e2f3cb90771f190016e5f8cd1ca6988266
child 171036 c3fc351a1c5512a93560f321572aaf66936a4a18
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersluke
bugs968936
milestone30.0a1
Bug 968936 - Emit a warning message with stack trace when the "operation callback" (slow script dialog) stops script execution. r=luke.
js/src/js.msg
js/src/jscntxt.cpp
js/src/jsexn.cpp
js/src/jsexn.h
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -430,8 +430,9 @@ MSG_DEF(JSMSG_MODULES_NOT_IMPLEMENTED,  
 MSG_DEF(JSMSG_EXPORT_DECL_AT_TOP_LEVEL, 376, 0, JSEXN_SYNTAXERR, "export declarations may only appear at top level")
 MSG_DEF(JSMSG_RC_AFTER_EXPORT_SPEC_LIST, 377, 0, JSEXN_SYNTAXERR, "missing '}' after export specifier list")
 MSG_DEF(JSMSG_NO_EXPORT_NAME,           378, 0, JSEXN_SYNTAXERR, "missing export name")
 MSG_DEF(JSMSG_DECLARATION_AFTER_EXPORT, 379, 0, JSEXN_SYNTAXERR, "missing declaration after 'export' keyword")
 MSG_DEF(JSMSG_INVALID_PROTOTYPE,        380, 0, JSEXN_TYPEERR, "prototype field is not an object")
 MSG_DEF(JSMSG_TYPEDOBJECT_HANDLE_TO_UNSIZED, 381, 0, JSEXN_TYPEERR, "cannot create a handle to an unsized type")
 MSG_DEF(JSMSG_SETPROTOTYPEOF_FAIL,      382, 1, JSEXN_TYPEERR, "[[SetPrototypeOf]] failed on {0}")
 MSG_DEF(JSMSG_INVALID_ARG_TYPE,         383, 3, JSEXN_TYPEERR, "Invalid type: {0} can't be a{1} {2}")
+MSG_DEF(JSMSG_TERMINATED,               384, 1, JSEXN_ERR, "Script terminated by timeout at:\n{0}")
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -1003,50 +1003,54 @@ js_GetErrorMessage(void *userRef, const 
 bool
 js_InvokeOperationCallback(JSContext *cx)
 {
     JS_ASSERT_REQUEST_DEPTH(cx);
 
     JSRuntime *rt = cx->runtime();
     JS_ASSERT(rt->interrupt);
 
-    /*
-     * Reset the callback counter first, then run GC and yield. If another
-     * thread is racing us here we will accumulate another callback request
-     * which will be serviced at the next opportunity.
-     */
+    // Reset the callback counter first, then run GC and yield. If another
+    // thread is racing us here we will accumulate another callback request
+    // which will be serviced at the next opportunity.
     rt->interrupt = false;
 
-    /*
-     * IonMonkey sets its stack limit to UINTPTR_MAX to trigger operation
-     * callbacks.
-     */
+    // IonMonkey sets its stack limit to UINTPTR_MAX to trigger operation
+    // callbacks.
     rt->resetIonStackLimit();
 
     js::gc::GCIfNeeded(cx);
 
 #ifdef JS_ION
 #ifdef JS_THREADSAFE
     rt->interruptPar = false;
 #endif
 
-    /*
-     * A worker thread may have set the callback after finishing an Ion
-     * compilation.
-     */
+    // A worker thread may have set the callback after finishing an Ion
+    // compilation.
     jit::AttachFinishedCompilations(cx);
 #endif
 
-    /*
-     * Important: Additional callbacks can occur inside the callback handler
-     * if it re-enters the JS engine. The embedding must ensure that the
-     * callback is disconnected before attempting such re-entry.
-     */
+    // Important: Additional callbacks can occur inside the callback handler
+    // if it re-enters the JS engine. The embedding must ensure that the
+    // callback is disconnected before attempting such re-entry.
     JSOperationCallback cb = cx->runtime()->operationCallback;
-    return !cb || cb(cx);
+    if (!cb || cb(cx))
+        return true;
+
+    // No need to set aside any pending exception here: ComputeStackString
+    // already does that.
+    Rooted<JSString*> stack(cx, ComputeStackString(cx));
+    const jschar *chars = stack ? stack->getCharsZ(cx) : nullptr;
+    if (!chars)
+        chars = MOZ_UTF16("(stack not available)");
+    JS_ReportErrorFlagsAndNumberUC(cx, JSREPORT_WARNING, js_GetErrorMessage, nullptr,
+                                   JSMSG_TERMINATED, chars);
+
+    return false;
 }
 
 bool
 js_HandleExecutionInterrupt(JSContext *cx)
 {
     if (cx->runtime()->interrupt)
         return js_InvokeOperationCallback(cx);
     return true;
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -197,18 +197,18 @@ struct SuppressErrorsGuard
     {}
 
     ~SuppressErrorsGuard()
     {
         JS_SetErrorReporter(cx, prevReporter);
     }
 };
 
-static JSString *
-ComputeStackString(JSContext *cx)
+JSString *
+js::ComputeStackString(JSContext *cx)
 {
     StringBuffer sb(cx);
 
     {
         RootedAtom atom(cx);
         SuppressErrorsGuard seg(cx);
         // We should get rid of the CURRENT_CONTEXT and STOP_AT_SAVED here.
         // See bug 960820.
--- a/js/src/jsexn.h
+++ b/js/src/jsexn.h
@@ -12,18 +12,21 @@
 #define jsexn_h
 
 #include "jsapi.h"
 #include "NamespaceImports.h"
 
 namespace js {
 class ErrorObject;
 
-extern JSErrorReport *
+JSErrorReport *
 CopyErrorReport(JSContext *cx, JSErrorReport *report);
+
+JSString *
+ComputeStackString(JSContext *cx);
 }
 
 /*
  * Given a JSErrorReport, check to see if there is an exception associated with
  * the error number.  If there is, then create an appropriate exception object,
  * set it as the pending exception, and set the JSREPORT_EXCEPTION flag on the
  * error report.  Exception-aware host error reporters should probably ignore
  * error reports so flagged.