Bug 1100498 - Report function names for addon exceptions. r=billm
authorGabor Krizsanits <gkrizsanits@mozilla.com>
Tue, 16 Jun 2015 16:46:17 +0200
changeset 279902 e46d0a7d8794c8f93f477889125600011f16dd46
parent 279901 c921bf16ff1016dfb20046a13c78f40e9340e309
child 279903 7299b8b5a8a165731ce3dd7dfc7a372c5bb9f68f
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm
bugs1100498
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1100498 - Report function names for addon exceptions. r=billm
js/src/jsexn.cpp
js/src/jsfriendapi.h
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -676,52 +676,95 @@ ErrorReport::~ErrorReport()
         size_t i = 0;
         while (ownedReport.messageArgs[i])
             js_free(const_cast<char16_t*>(ownedReport.messageArgs[i++]));
         js_free(ownedReport.messageArgs);
     }
     js_free(const_cast<char16_t*>(ownedReport.ucmessage));
 }
 
+void
+ErrorReport::ReportAddonExceptionToTelementry(JSContext* cx)
+{
+    MOZ_ASSERT(exnObject);
+    RootedObject unwrapped(cx, UncheckedUnwrap(exnObject));
+    MOZ_ASSERT(unwrapped, "UncheckedUnwrap failed?");
+
+    // There is not much we can report if the exception is not an ErrorObject, let's ignore those.
+    if (!unwrapped->is<ErrorObject>())
+        return;
+
+    Rooted<ErrorObject*> errObj(cx, &unwrapped->as<ErrorObject>());
+    RootedObject stack(cx, errObj->stack());
+
+    // Let's ignore TOP level exceptions. For regular add-ons those will not be reported anyway,
+    // for SDK based once it should not be a valid case either.
+    // At this point the frame stack is unwound but the exception object stored the stack so let's
+    // use that for getting the function name.
+    if (!stack)
+        return;
+
+    JSCompartment* comp = stack->compartment();
+    JSAddonId* addonId = comp->addonId;
+
+    // We only want to send the report if the scope that just have thrown belongs to an add-on.
+    // Let's check the compartment of the youngest function on the stack, to determine that.
+    if (!addonId)
+        return;
+
+    RootedString funnameString(cx);
+    JS::SavedFrameResult result = GetSavedFrameFunctionDisplayName(cx, stack, &funnameString);
+    // AccessDenied should never be the case here for add-ons but let's not risk it.
+    JSAutoByteString bytes;
+    const char* funname = nullptr;
+    bool denied = result == JS::SavedFrameResult::AccessDenied;
+    funname = denied ? "unknown"
+                     : funnameString ? AtomToPrintableString(cx,
+                                                             &funnameString->asAtom(),
+                                                             &bytes)
+                                     : "anonymous";
+
+    UniqueChars addonIdChars(JS_EncodeString(cx, addonId));
+
+    const char* filename = nullptr;
+    if (reportp && reportp->filename) {
+        filename = strrchr(reportp->filename, '/');
+        if (filename)
+            filename++;
+    }
+    if (!filename) {
+        filename = "FILE_NOT_FOUND";
+    }
+    char histogramKey[64];
+    JS_snprintf(histogramKey, sizeof(histogramKey),
+                "%s %s %s %u",
+                addonIdChars.get(),
+                funname,
+                filename,
+                (reportp ? reportp->lineno : 0) );
+    cx->runtime()->addTelemetry(JS_TELEMETRY_ADDON_EXCEPTIONS, 1, histogramKey);
+}
+
 bool
 ErrorReport::init(JSContext* cx, HandleValue exn)
 {
     MOZ_ASSERT(!cx->isExceptionPending());
 
-    /*
-     * Because ToString below could error and an exception object could become
-     * unrooted, we must root our exception object, if any.
-     */
     if (exn.isObject()) {
+        // Because ToString below could error and an exception object could become
+        // unrooted, we must root our exception object, if any.
         exnObject = &exn.toObject();
         reportp = ErrorFromException(cx, exnObject);
 
-        JSCompartment* comp = exnObject->compartment();
-        JSAddonId* addonId = comp->addonId;
-        if (addonId) {
-            UniqueChars addonIdChars(JS_EncodeString(cx, addonId));
+        // Let's see if the exception is from add-on code, if so, it should be reported
+        // to telementry.
+        ReportAddonExceptionToTelementry(cx);
+    }
 
-            const char* filename = nullptr;
-            if (reportp && reportp->filename) {
-                filename = strrchr(reportp->filename, '/');
-                if (filename)
-                    filename++;
-            }
-            if (!filename) {
-                filename = "FILE_NOT_FOUND";
-            }
-            char histogramKey[64];
-            JS_snprintf(histogramKey, sizeof(histogramKey),
-                        "%s %s %u",
-                        addonIdChars.get(),
-                        filename,
-                        (reportp ? reportp->lineno : 0) );
-            cx->runtime()->addTelemetry(JS_TELEMETRY_ADDON_EXCEPTIONS, 1, histogramKey);
-        }
-    }
+
     // Be careful not to invoke ToString if we've already successfully extracted
     // an error report, since the exception might be wrapped in a security
     // wrapper, and ToString-ing it might throw.
     if (reportp)
         str = ErrorReportToString(cx, reportp);
     else
         str = ToString<CanGC>(cx, exn);
 
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -1391,16 +1391,19 @@ struct MOZ_STACK_CLASS JS_FRIEND_API(Err
     // but fills in an ErrorReport instead of reporting it.  Uses varargs to
     // make it simpler to call js::ExpandErrorArgumentsVA.
     //
     // Returns false if we fail to actually populate the ErrorReport
     // for some reason (probably out of memory).
     bool populateUncaughtExceptionReport(JSContext* cx, ...);
     bool populateUncaughtExceptionReportVA(JSContext* cx, va_list ap);
 
+    // Reports exceptions from add-on scopes to telementry.
+    void ReportAddonExceptionToTelementry(JSContext* cx);
+
     // We may have a provided JSErrorReport, so need a way to represent that.
     JSErrorReport* reportp;
 
     // And we may have a message.
     const char* message_;
 
     // Or we may need to synthesize a JSErrorReport one of our own.
     JSErrorReport ownedReport;