Bug 987995, part 4 - Add new crash reporter annotations for JS out-of-memory conditions. r=mccr8,r=bsmedberg.

This commit is contained in:
Jason Orendorff 2014-05-22 08:18:02 -05:00
parent 162eb32023
commit 0629a4a901
10 changed files with 208 additions and 1 deletions

View File

@ -0,0 +1,21 @@
function run_test()
{
if (!("@mozilla.org/toolkit/crash-reporter;1" in Components.classes)) {
dump("INFO | test_crash_after_js_large_allocation_failure.js | Can't test crashreporter in a non-libxul build.\n");
return;
}
do_crash(
function() {
crashType = CrashTestUtils.CRASH_MOZ_CRASH;
crashReporter.annotateCrashReport("TestingOOMCrash", "Yes");
Components.utils.getJSTestingFunctions().reportLargeAllocationFailure();
Components.utils.forceGC();
},
function(mdump, extra) {
do_check_eq(extra.TestingOOMCrash, "Yes");
do_check_false("JSOutOfMemory" in extra);
do_check_eq(extra.JSLargeAllocationFailure, "Recovered");
},
true);
}

View File

@ -0,0 +1,27 @@
function run_test()
{
if (!("@mozilla.org/toolkit/crash-reporter;1" in Components.classes)) {
dump("INFO | test_crash_after_js_oom_reporting.js | Can't test crashreporter in a non-libxul build.\n");
return;
}
do_crash(
function() {
crashType = CrashTestUtils.CRASH_MOZ_CRASH;
crashReporter.annotateCrashReport("TestingOOMCrash", "Yes");
function crashWhileReporting() {
CrashTestUtils.crash(crashType);
}
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.addObserver(crashWhileReporting, "memory-pressure", false);
Components.utils.getJSTestingFunctions().reportLargeAllocationFailure();
},
function(mdump, extra) {
do_check_eq(extra.TestingOOMCrash, "Yes");
do_check_eq(extra.JSLargeAllocationFailure, "Reporting");
},
true);
}

View File

@ -0,0 +1,20 @@
function run_test()
{
if (!("@mozilla.org/toolkit/crash-reporter;1" in Components.classes)) {
dump("INFO | test_crash_after_js_oom_recovered.js | Can't test crashreporter in a non-libxul build.\n");
return;
}
do_crash(
function() {
crashType = CrashTestUtils.CRASH_MOZ_CRASH;
crashReporter.annotateCrashReport("TestingOOMCrash", "Yes");
Components.utils.getJSTestingFunctions().reportOutOfMemory();
Components.utils.forceGC();
},
function(mdump, extra) {
do_check_eq(extra.TestingOOMCrash, "Yes");
do_check_eq(extra.JSOutOfMemory, "Recovered");
},
true);
}

View File

@ -0,0 +1,34 @@
function run_test()
{
if (!("@mozilla.org/toolkit/crash-reporter;1" in Components.classes)) {
dump("INFO | test_crash_after_js_oom_reported.js | Can't test crashreporter in a non-libxul build.\n");
return;
}
do_crash(
function() {
crashType = CrashTestUtils.CRASH_MOZ_CRASH;
crashReporter.annotateCrashReport("TestingOOMCrash", "Yes");
// GC now to avoid having it happen randomly later, which would make the
// test bogusly fail. See comment below.
Components.utils.forceGC();
Components.utils.getJSTestingFunctions().reportOutOfMemory();
},
function(mdump, extra) {
do_check_eq(extra.TestingOOMCrash, "Yes");
// The JSOutOfMemory field is absent if the JS engine never reported OOM,
// "Reported" if it did, and "Recovered" if it reported OOM but
// subsequently completed a full GC cycle. Since this test calls
// reportOutOfMemory() and then crashes, we expect "Reported".
//
// Theoretically, GC can happen any time, so it is just possible that
// this property could be "Recovered" even if the implementation is
// correct. More likely, though, that indicates a bug, so only accept
// "Reported".
do_check_eq(extra.JSOutOfMemory, "Reported");
},
true);
}

View File

@ -0,0 +1,26 @@
function run_test()
{
if (!("@mozilla.org/toolkit/crash-reporter;1" in Components.classes)) {
dump("INFO | test_crash_after_js_oom_reported_2.js | Can't test crashreporter in a non-libxul build.\n");
return;
}
do_crash(
function() {
crashType = CrashTestUtils.CRASH_MOZ_CRASH;
crashReporter.annotateCrashReport("TestingOOMCrash", "Yes");
Components.utils.getJSTestingFunctions().reportOutOfMemory();
Components.utils.forceGC(); // recover from first OOM
Components.utils.getJSTestingFunctions().reportOutOfMemory();
},
function(mdump, extra) {
do_check_eq(extra.TestingOOMCrash, "Yes");
// Technically, GC can happen at any time, but it would be really
// peculiar for it to happen again heuristically right after a GC was
// forced. If extra.JSOutOfMemory is "Recovered" here, that's most
// likely a bug in the error reporting machinery.
do_check_eq(extra.JSOutOfMemory, "Reported");
},
true);
}

View File

@ -8,6 +8,8 @@ function run_test()
function(mdump, extra) {
do_check_eq(extra.TestKey, "TestValue");
do_check_false("OOMAllocationSize" in extra);
do_check_false("JSOutOfMemory" in extra);
do_check_false("JSLargeAllocationFailure" in extra);
},
// process will exit with a zero exit status
true);

View File

@ -1,7 +1,7 @@
function run_test()
{
if (!("@mozilla.org/toolkit/crash-reporter;1" in Components.classes)) {
dump("INFO | test_crash_purevirtual.js | Can't test crashreporter in a non-libxul build.\n");
dump("INFO | test_crash_oom.js | Can't test crashreporter in a non-libxul build.\n");
return;
}

View File

@ -8,6 +8,11 @@ support-files =
[test_crash_moz_crash.js]
[test_crash_purevirtual.js]
[test_crash_runtimeabort.js]
[test_crash_after_js_oom_reported.js]
[test_crash_after_js_oom_recovered.js]
[test_crash_after_js_oom_reported_2.js]
[test_crash_after_js_large_allocation_failure.js]
[test_crash_after_js_large_allocation_failure_reporting.js]
[test_crash_oom.js]
skip-if = os == 'win' && debug

View File

@ -66,6 +66,7 @@
#include "nsCycleCollectionParticipant.h"
#include "nsCycleCollector.h"
#include "nsDOMJSUtils.h"
#include "nsExceptionHandler.h"
#include "nsIException.h"
#include "nsThreadUtils.h"
#include "xpcpublic.h"
@ -467,6 +468,8 @@ CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSRuntime* aParentRuntime,
, mJSZoneCycleCollectorGlobal(sJSZoneCycleCollectorGlobal)
, mJSRuntime(nullptr)
, mJSHolders(512)
, mOutOfMemoryState(OOMState::OK)
, mLargeAllocationFailureState(OOMState::OK)
{
mozilla::dom::InitScriptSettings();
@ -1176,6 +1179,22 @@ CycleCollectedJSRuntime::FinalizeDeferredThings(DeferredFinalizeType aType)
}
}
void
CycleCollectedJSRuntime::AnnotateAndSetOutOfMemory(OOMState *aStatePtr, OOMState aNewState)
{
*aStatePtr = aNewState;
#ifdef MOZ_CRASHREPORTER
CrashReporter::AnnotateCrashReport(aStatePtr == &mOutOfMemoryState
? NS_LITERAL_CSTRING("JSOutOfMemory")
: NS_LITERAL_CSTRING("JSLargeAllocationFailure"),
aNewState == OOMState::Reporting
? NS_LITERAL_CSTRING("Reporting")
: aNewState == OOMState::Reported
? NS_LITERAL_CSTRING("Reported")
: NS_LITERAL_CSTRING("Recovered"));
#endif
}
void
CycleCollectedJSRuntime::OnGC(JSGCStatus aStatus)
{
@ -1184,6 +1203,15 @@ CycleCollectedJSRuntime::OnGC(JSGCStatus aStatus)
nsCycleCollector_prepareForGarbageCollection();
break;
case JSGC_END: {
#ifdef MOZ_CRASHREPORTER
if (mOutOfMemoryState == OOMState::Reported) {
AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Recovered);
}
if (mLargeAllocationFailureState == OOMState::Reported) {
AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, OOMState::Recovered);
}
#endif
/*
* If the previous GC created a runnable to finalize objects
* incrementally, and if it hasn't finished yet, finish it now. We
@ -1210,11 +1238,15 @@ CycleCollectedJSRuntime::OnGC(JSGCStatus aStatus)
void
CycleCollectedJSRuntime::OnOutOfMemory()
{
AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reporting);
CustomOutOfMemoryCallback();
AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reported);
}
void
CycleCollectedJSRuntime::OnLargeAllocationFailure()
{
AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, OOMState::Reporting);
CustomLargeAllocationFailureCallback();
AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, OOMState::Reported);
}

View File

@ -206,6 +206,41 @@ private:
void FinalizeDeferredThings(DeferredFinalizeType aType);
public:
// Two conditions, JSOutOfMemory and JSLargeAllocationFailure, are noted in
// crash reports. Here are the values that can appear in the reports:
MOZ_BEGIN_NESTED_ENUM_CLASS(OOMState, uint32_t)
// The condition has never happened. No entry appears in the crash report.
OK,
// We are currently reporting the given condition.
//
// Suppose a crash report contains "JSLargeAllocationFailure:
// Reporting". This means we crashed while executing memory-pressure
// observers, trying to shake loose some memory. The large allocation in
// question did not return null: it is still on the stack. Had we not
// crashed, it would have been retried.
Reporting,
// The condition has been reported since the last GC.
//
// If a crash report contains "JSOutOfMemory: Reported", that means a small
// allocation failed, and then we crashed, probably due to buggy
// error-handling code that ran after allocation returned null.
//
// This contrasts with "Reporting" which means that no error-handling code
// had executed yet.
Reported,
// The condition has happened, but a GC cycle ended since then.
//
// GC is taken as a proxy for "we've been banging on the heap a good bit
// now and haven't crashed; the OOM was probably handled correctly".
Recovered
MOZ_END_NESTED_ENUM_CLASS(OOMState)
private:
void AnnotateAndSetOutOfMemory(OOMState *aStatePtr, OOMState aNewState);
void OnGC(JSGCStatus aStatus);
void OnOutOfMemory();
void OnLargeAllocationFailure();
@ -269,8 +304,13 @@ private:
nsRefPtr<IncrementalFinalizeRunnable> mFinalizeRunnable;
nsCOMPtr<nsIException> mPendingException;
OOMState mOutOfMemoryState;
OOMState mLargeAllocationFailureState;
};
MOZ_FINISH_NESTED_ENUM_CLASS(CycleCollectedJSRuntime::OOMState)
} // namespace mozilla
#endif // mozilla_CycleCollectedJSRuntime_h__