diff options
Diffstat (limited to 'toolkit/crashreporter/test/unit')
48 files changed, 1890 insertions, 0 deletions
diff --git a/toolkit/crashreporter/test/unit/crasher_subprocess_head.js b/toolkit/crashreporter/test/unit/crasher_subprocess_head.js new file mode 100644 index 0000000000..fdeefb5c3d --- /dev/null +++ b/toolkit/crashreporter/test/unit/crasher_subprocess_head.js @@ -0,0 +1,35 @@ +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +// enable crash reporting first +var cwd = Services.dirsvc.get("CurWorkD", Ci.nsIFile); + +// get the temp dir +var env = Cc["@mozilla.org/process/environment;1"].getService( + Ci.nsIEnvironment +); +var _tmpd = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); +_tmpd.initWithPath(env.get("XPCSHELL_TEST_TEMP_DIR")); + +var crashReporter = Cc["@mozilla.org/toolkit/crash-reporter;1"].getService( + Ci.nsICrashReporter +); + +// We need to call this or crash events go in an undefined location. +crashReporter.UpdateCrashEventsDir(); + +// Setting the minidump path is not allowed in content processes +var processType = Services.appinfo.processType; +if (processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) { + crashReporter.minidumpPath = _tmpd; +} + +var protocolHandler = Services.io + .getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); +var curDirURI = Services.io.newFileURI(cwd); +protocolHandler.setSubstitution("test", curDirURI); +const { CrashTestUtils } = ChromeUtils.import( + "resource://test/CrashTestUtils.jsm" +); +var crashType = CrashTestUtils.CRASH_INVALID_POINTER_DEREF; +var shouldDelay = false; diff --git a/toolkit/crashreporter/test/unit/crasher_subprocess_tail.js b/toolkit/crashreporter/test/unit/crasher_subprocess_tail.js new file mode 100644 index 0000000000..398e091c98 --- /dev/null +++ b/toolkit/crashreporter/test/unit/crasher_subprocess_tail.js @@ -0,0 +1,17 @@ +/* import-globals-from crasher_subprocess_head.js */ + +// Let the event loop process a bit before crashing. +if (shouldDelay) { + let shouldCrashNow = false; + + Services.tm.dispatchToMainThread({ + run: () => { + shouldCrashNow = true; + }, + }); + + Services.tm.spinEventLoopUntil(() => shouldCrashNow); +} + +// now actually crash +CrashTestUtils.crash(crashType); diff --git a/toolkit/crashreporter/test/unit/head_crashreporter.js b/toolkit/crashreporter/test/unit/head_crashreporter.js new file mode 100644 index 0000000000..a9a99c171a --- /dev/null +++ b/toolkit/crashreporter/test/unit/head_crashreporter.js @@ -0,0 +1,267 @@ +var { OS, require } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); +ChromeUtils.import("resource://gre/modules/Services.jsm", this); +ChromeUtils.import("resource://testing-common/AppData.jsm", this); +var { AppConstants } = ChromeUtils.import( + "resource://gre/modules/AppConstants.jsm" +); + +function getEventDir() { + return OS.Path.join(do_get_tempdir().path, "crash-events"); +} + +function sendCommandAsync(command) { + return new Promise(resolve => { + sendCommand(command, resolve); + }); +} + +/* + * Run an xpcshell subprocess and crash it. + * + * @param setup + * A string of JavaScript code to execute in the subprocess + * before crashing. If this is a function and not a string, + * it will have .toSource() called on it, and turned into + * a call to itself. (for programmer convenience) + * This code will be evaluted between crasher_subprocess_head.js + * and crasher_subprocess_tail.js, so it will have access + * to everything defined in crasher_subprocess_head.js, + * which includes "crashReporter", a variable holding + * the crash reporter service. + * + * @param callback + * A JavaScript function to be called after the subprocess + * crashes. It will be passed (minidump, extra, extrafile), where + * - minidump is an nsIFile of the minidump file produced, + * - extra is an object containing the key,value pairs from + * the .extra file. + * - extrafile is an nsIFile of the extra file + * + * @param canReturnZero + * If true, the subprocess may return with a zero exit code. + * Certain types of crashes may not cause the process to + * exit with an error. + * + */ +async function do_crash(setup, callback, canReturnZero) { + // get current process filename (xpcshell) + let bin = Services.dirsvc.get("XREExeF", Ci.nsIFile); + if (!bin.exists()) { + // weird, can't find xpcshell binary? + do_throw("Can't find xpcshell binary!"); + } + // get Gre dir (GreD) + let greD = Services.dirsvc.get("GreD", Ci.nsIFile); + let headfile = do_get_file("crasher_subprocess_head.js"); + let tailfile = do_get_file("crasher_subprocess_tail.js"); + // run xpcshell -g GreD -f head -e "some setup code" -f tail + let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(bin); + let args = ["-g", greD.path, "-f", headfile.path]; + if (setup) { + if (typeof setup == "function") { + // funky, but convenient + setup = "(" + setup.toSource() + ")();"; + } + args.push("-e", setup); + } + args.push("-f", tailfile.path); + + let env = Cc["@mozilla.org/process/environment;1"].getService( + Ci.nsIEnvironment + ); + + let crashD = do_get_tempdir(); + crashD.append("crash-events"); + if (!crashD.exists()) { + crashD.create(crashD.DIRECTORY_TYPE, 0o700); + } + + env.set("CRASHES_EVENTS_DIR", crashD.path); + + try { + process.run(true, args, args.length); + } catch (ex) { + // on Windows we exit with a -1 status when crashing. + } finally { + env.set("CRASHES_EVENTS_DIR", ""); + } + + if (!canReturnZero) { + // should exit with an error (should have crashed) + Assert.notEqual(process.exitValue, 0); + } + + await handleMinidump(callback); +} + +function getMinidump() { + let en = do_get_tempdir().directoryEntries; + while (en.hasMoreElements()) { + let f = en.nextFile; + if (f.leafName.substr(-4) == ".dmp") { + return f; + } + } + + return null; +} + +function runMinidumpAnalyzer(dumpFile, additionalArgs) { + if (AppConstants.platform !== "win") { + return; + } + + // find minidump-analyzer executable. + let bin = Services.dirsvc.get("XREExeF", Ci.nsIFile); + ok(bin && bin.exists()); + bin = bin.parent; + ok(bin && bin.exists()); + bin.append("minidump-analyzer.exe"); + ok(bin.exists()); + + let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(bin); + let args = []; + if (additionalArgs) { + args = args.concat(additionalArgs); + } + args.push(dumpFile.path); + process.run(true /* blocking */, args, args.length); +} + +async function handleMinidump(callback) { + // find minidump + let minidump = getMinidump(); + + if (minidump == null) { + do_throw("No minidump found!"); + } + + let extrafile = minidump.clone(); + extrafile.leafName = extrafile.leafName.slice(0, -4) + ".extra"; + + let memoryfile = minidump.clone(); + memoryfile.leafName = memoryfile.leafName.slice(0, -4) + ".memory.json.gz"; + + let cleanup = function() { + [minidump, extrafile, memoryfile].forEach(file => { + if (file.exists()) { + file.remove(false); + } + }); + }; + + // Just in case, don't let these files linger. + registerCleanupFunction(cleanup); + + Assert.ok(extrafile.exists()); + let data = await OS.File.read(extrafile.path); + let decoder = new TextDecoder(); + let extra = JSON.parse(decoder.decode(data)); + + if (callback) { + await callback(minidump, extra, extrafile); + } + + cleanup(); +} + +function spinEventLoop() { + return new Promise(resolve => { + executeSoon(resolve); + }); +} + +/** + * Helper for testing a content process crash. + * + * This variant accepts a setup function which runs in the content process + * to set data as needed _before_ the crash. The tail file triggers a generic + * crash after setup. + */ +async function do_content_crash(setup, callback) { + do_load_child_test_harness(); + + // Setting the minidump path won't work in the child, so we need to do + // that here. + let crashReporter = Cc["@mozilla.org/toolkit/crash-reporter;1"].getService( + Ci.nsICrashReporter + ); + crashReporter.minidumpPath = do_get_tempdir(); + + /* import-globals-from ../unit/crasher_subprocess_head.js */ + /* import-globals-from ../unit/crasher_subprocess_tail.js */ + + let headfile = do_get_file("../unit/crasher_subprocess_head.js"); + let tailfile = do_get_file("../unit/crasher_subprocess_tail.js"); + if (setup) { + if (typeof setup == "function") { + // funky, but convenient + setup = "(" + setup.toSource() + ")();"; + } + } + + do_get_profile(); + await makeFakeAppDir(); + await sendCommandAsync('load("' + headfile.path.replace(/\\/g, "/") + '");'); + if (setup) { + await sendCommandAsync(setup); + } + await sendCommandAsync('load("' + tailfile.path.replace(/\\/g, "/") + '");'); + await spinEventLoop(); + + let minidump = getMinidump(); + let id = minidump.leafName.slice(0, -4); + await Services.crashmanager.ensureCrashIsPresent(id); + try { + await handleMinidump(callback); + } catch (x) { + do_report_unexpected_exception(x); + } +} + +/** + * Helper for testing a content process crash. + * + * This variant accepts a trigger function which runs in the content process + * and does something to _trigger_ the crash. + */ +async function do_triggered_content_crash(trigger, callback) { + do_load_child_test_harness(); + + // Setting the minidump path won't work in the child, so we need to do + // that here. + let crashReporter = Cc["@mozilla.org/toolkit/crash-reporter;1"].getService( + Ci.nsICrashReporter + ); + crashReporter.minidumpPath = do_get_tempdir(); + + /* import-globals-from ../unit/crasher_subprocess_head.js */ + + let headfile = do_get_file("../unit/crasher_subprocess_head.js"); + if (trigger) { + if (typeof trigger == "function") { + // funky, but convenient + trigger = "(" + trigger.toSource() + ")();"; + } + } + + do_get_profile(); + await makeFakeAppDir(); + await sendCommandAsync('load("' + headfile.path.replace(/\\/g, "/") + '");'); + await sendCommandAsync(trigger); + await spinEventLoop(); + let id = getMinidump().leafName.slice(0, -4); + await Services.crashmanager.ensureCrashIsPresent(id); + try { + await handleMinidump(callback); + } catch (x) { + do_report_unexpected_exception(x); + } +} + +// Import binary APIs via js-ctypes. +var { CrashTestUtils } = ChromeUtils.import( + "resource://test/CrashTestUtils.jsm" +); diff --git a/toolkit/crashreporter/test/unit/head_win64cfi.js b/toolkit/crashreporter/test/unit/head_win64cfi.js new file mode 100644 index 0000000000..4e794e3b10 --- /dev/null +++ b/toolkit/crashreporter/test/unit/head_win64cfi.js @@ -0,0 +1,217 @@ +/* import-globals-from head_crashreporter.js */ + +let gTestCrasherSyms = null; +let gModules = null; + +// Returns the offset (int) of an IP with a given base address. +// This is effectively (ip - base), except a bit more complication due to +// Javascript's shaky handling of 64-bit integers. +// base & ip are passed as hex strings. +function getModuleOffset(base, ip) { + let i = 0; + // Find where the two addresses diverge, which enables us to perform a 32-bit + // subtraction. + // e.g. "0x1111111111112222" + // - "0x1111111111111111" + // becomes 2222 - 1111 + for (; i < base.length; ++i) { + if (base[i] != ip[i]) { + break; + } + } + if (i == base.length) { + return 0; + } + let lhs2 = "0x" + base.substring(i); + let rhs2 = "0x" + ip.substring(i); + return parseInt(rhs2) - parseInt(lhs2); +} + +// Uses gTestCrasherSyms to convert an address to a symbol. +function findNearestTestCrasherSymbol(addr) { + addr += 1; // Breakpad sometimes offsets addresses; correct for this. + let closestDistance = null; + let closestSym = null; + for (let sym in gTestCrasherSyms) { + if (addr >= gTestCrasherSyms[sym]) { + let thisDistance = addr - gTestCrasherSyms[sym]; + if (closestDistance === null || thisDistance < closestDistance) { + closestDistance = thisDistance; + closestSym = sym; + } + } + } + if (closestSym === null) { + return null; + } + return { symbol: closestSym, offset: closestDistance }; +} + +// Populate known symbols for testcrasher.dll. +// Use the same prop names as from CrashTestUtils to avoid the need for mapping. +function initTestCrasherSymbols() { + gTestCrasherSyms = {}; + for (let k in CrashTestUtils) { + // Not all keys here are valid symbol names. getWin64CFITestFnAddrOffset + // will return 0 in those cases, no need to filter here. + if (Number.isInteger(CrashTestUtils[k])) { + let t = CrashTestUtils.getWin64CFITestFnAddrOffset(CrashTestUtils[k]); + if (t > 0) { + gTestCrasherSyms[k] = t; + } + } + } +} + +function stackFrameToString(frameIndex, frame) { + // Calculate the module offset. + let ip = frame.ip; + let symbol = ""; + let moduleOffset = "unknown_offset"; + let filename = "unknown_module"; + + if ( + typeof frame.module_index !== "undefined" && + frame.module_index >= 0 && + frame.module_index < gModules.length + ) { + let base = gModules[frame.module_index].base_addr; + moduleOffset = getModuleOffset(base, ip); + filename = gModules[frame.module_index].filename; + + if (filename === "testcrasher.dll") { + let nearestSym = findNearestTestCrasherSymbol(moduleOffset); + if (nearestSym !== null) { + symbol = nearestSym.symbol + "+" + nearestSym.offset.toString(16); + } + } + } + + let ret = + "frames[" + + frameIndex + + "] ip=" + + ip + + " " + + symbol + + ", module:" + + filename + + ", trust:" + + frame.trust + + ", moduleOffset:" + + moduleOffset.toString(16); + return ret; +} + +function dumpStackFrames(frames, maxFrames) { + for (let i = 0; i < Math.min(maxFrames, frames.length); ++i) { + info(stackFrameToString(i, frames[i])); + } +} + +// Test that the top of the given stack (from extra data) matches the given +// expected frames. +// +// expected is { symbol: "", trust: "" } +function assertStack(stack, expected) { + for (let i = 0; i < stack.length; ++i) { + if (i >= expected.length) { + ok("Top stack frames were expected"); + return; + } + let frame = stack[i]; + let expectedFrame = expected[i]; + let dumpThisFrame = function() { + info(" Actual frame: " + stackFrameToString(i, frame)); + info( + "Expected { symbol: " + + expectedFrame.symbol + + ", trust: " + + expectedFrame.trust + + "}" + ); + }; + + if (expectedFrame.trust) { + if (expectedFrame.trust.startsWith("!")) { + // A "!" prefix on the frame trust matching is a logical "not". + if (frame.trust === expectedFrame.trust.substring(1)) { + dumpThisFrame(); + info("Expected frame trust matched when it should not have."); + ok(false); + } + } else if (frame.trust !== expectedFrame.trust) { + dumpThisFrame(); + info("Expected frame trust did not match."); + ok(false); + } + } + + if (expectedFrame.symbol) { + if (typeof frame.module_index === "undefined") { + // Without a module_index, it happened in an unknown module. Currently + // you can't specify an expected "unknown" module. + info("Unknown symbol in unknown module."); + ok(false); + } + if (frame.module_index < 0 || frame.module_index >= gModules.length) { + dumpThisFrame(); + info("Unknown module."); + ok(false); + return; + } + let base = gModules[frame.module_index].base_addr; + let moduleOffset = getModuleOffset(base, frame.ip); + let filename = gModules[frame.module_index].filename; + if (filename == "testcrasher.dll") { + let nearestSym = findNearestTestCrasherSymbol(moduleOffset); + if (nearestSym === null) { + dumpThisFrame(); + info("Unknown symbol."); + ok(false); + return; + } + + if (nearestSym.symbol !== expectedFrame.symbol) { + dumpThisFrame(); + info("Mismatching symbol."); + ok(false); + } + } + } + } +} + +// Performs a crash, runs minidump-analyzer, and checks expected stack analysis. +// +// how: The crash to perform. Constants defined in both CrashTestUtils.jsm +// and nsTestCrasher.cpp (i.e. CRASH_X64CFI_PUSH_NONVOL) +// expectedStack: An array of {"symbol", "trust"} where trust is "cfi", +// "context", "scan", et al. May be null if you don't need to check the stack. +// minidumpAnalyzerArgs: An array of additional arguments to pass to +// minidump-analyzer.exe. +async function do_x64CFITest(how, expectedStack, minidumpAnalyzerArgs) { + // Setup is run in the subprocess so we cannot use any closures. + let setupFn = "crashType = CrashTestUtils." + how + ";"; + + let callbackFn = async function(minidumpFile, extra, extraFile) { + runMinidumpAnalyzer(minidumpFile, minidumpAnalyzerArgs); + + // Refresh updated extra data + let data = await OS.File.read(extraFile.path); + let decoder = new TextDecoder(); + extra = JSON.parse(decoder.decode(data)); + + initTestCrasherSymbols(); + let stackTraces = extra.StackTraces; + let crashingThreadIndex = stackTraces.crash_info.crashing_thread; + gModules = stackTraces.modules; + let crashingFrames = stackTraces.threads[crashingThreadIndex].frames; + + dumpStackFrames(crashingFrames, 10); + + assertStack(crashingFrames, expectedStack); + }; + + do_crash(setupFn, callbackFn, true, true); +} diff --git a/toolkit/crashreporter/test/unit/test_crash_AsyncShutdown.js b/toolkit/crashreporter/test/unit/test_crash_AsyncShutdown.js new file mode 100644 index 0000000000..3f1fa8af8b --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_AsyncShutdown.js @@ -0,0 +1,112 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that AsyncShutdown report errors correctly + +// Note: these functions are evaluated in their own process, hence the need +// to import modules into each function. + +function setup_crash() { + /* global AsyncShutdown */ + ChromeUtils.import("resource://gre/modules/AsyncShutdown.jsm", this); + ChromeUtils.import("resource://gre/modules/Services.jsm", this); + ChromeUtils.import("resource://gre/modules/Promise.jsm", this); + + Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true); + Services.prefs.setIntPref("toolkit.asyncshutdown.crash_timeout", 10); + + let TOPIC = "testing-async-shutdown-crash"; + let phase = AsyncShutdown._getPhase(TOPIC); + phase.addBlocker("A blocker that is never satisfied", function() { + dump("Installing blocker\n"); + let deferred = Promise.defer(); + return deferred.promise; + }); + + Services.obs.notifyObservers(null, TOPIC); + dump(new Error().stack + "\n"); + dump("Waiting for crash\n"); +} + +function after_crash(mdump, extra) { + info("after crash: " + extra.AsyncShutdownTimeout); + let data = JSON.parse(extra.AsyncShutdownTimeout); + Assert.equal(data.phase, "testing-async-shutdown-crash"); + Assert.equal(data.conditions[0].name, "A blocker that is never satisfied"); + // This test spawns subprocesses by using argument "-e" of xpcshell, so + // this is the filename known to xpcshell. + Assert.equal(data.conditions[0].filename, "-e"); +} + +// Test that AsyncShutdown + OS.File reports errors correctly, in a case in which +// the latest operation succeeded + +function setup_osfile_crash_noerror() { + ChromeUtils.import("resource://gre/modules/Services.jsm", this); + ChromeUtils.import("resource://gre/modules/osfile.jsm", this); + ChromeUtils.import("resource://gre/modules/Promise.jsm", this); + + Services.prefs.setIntPref("toolkit.asyncshutdown.crash_timeout", 1); + Services.prefs.setBoolPref("toolkit.osfile.native", false); + + OS.File.profileBeforeChange.addBlocker( + "Adding a blocker that will never be resolved", + () => Promise.defer().promise + ); + OS.File.getCurrentDirectory(); + + Services.obs.notifyObservers(null, "profile-before-change"); + dump("Waiting for crash\n"); +} + +function after_osfile_crash_noerror(mdump, extra) { + info("after OS.File crash: " + extra.AsyncShutdownTimeout); + let data = JSON.parse(extra.AsyncShutdownTimeout); + let state = data.conditions[0].state; + info("Keys: " + Object.keys(state).join(", ")); + Assert.equal(data.phase, "profile-before-change"); + Assert.ok(state.launched); + Assert.ok(!state.shutdown); + Assert.ok(state.worker); + Assert.ok(!!state.latestSent); + Assert.equal(state.latestSent[1], "getCurrentDirectory"); +} + +// Test that AsyncShutdown + OS.File reports errors correctly, in a case in which +// the latest operation failed + +function setup_osfile_crash_exn() { + ChromeUtils.import("resource://gre/modules/Services.jsm", this); + ChromeUtils.import("resource://gre/modules/osfile.jsm", this); + ChromeUtils.import("resource://gre/modules/Promise.jsm", this); + + Services.prefs.setIntPref("toolkit.asyncshutdown.crash_timeout", 1); + Services.prefs.setBoolPref("toolkit.osfile.native", false); + + OS.File.profileBeforeChange.addBlocker( + "Adding a blocker that will never be resolved", + () => Promise.defer().promise + ); + OS.File.read("I do not exist"); + + Services.obs.notifyObservers(null, "profile-before-change"); + dump("Waiting for crash\n"); +} + +function after_osfile_crash_exn(mdump, extra) { + info("after OS.File crash: " + extra.AsyncShutdownTimeout); + let data = JSON.parse(extra.AsyncShutdownTimeout); + let state = data.conditions[0].state; + info("Keys: " + Object.keys(state).join(", ")); + Assert.equal(data.phase, "profile-before-change"); + Assert.ok(!state.shutdown); + Assert.ok(state.worker); + Assert.ok(!!state.latestSent); + Assert.equal(state.latestSent[1], "read"); +} + +add_task(async function run_test() { + await do_crash(setup_crash, after_crash); + await do_crash(setup_osfile_crash_noerror, after_osfile_crash_noerror); + await do_crash(setup_osfile_crash_exn, after_osfile_crash_exn); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_abort.js b/toolkit/crashreporter/test/unit/test_crash_abort.js new file mode 100644 index 0000000000..106c1c4535 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_abort.js @@ -0,0 +1,17 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function run_test() { + // Try crashing with an abort(). + await do_crash( + function() { + crashType = CrashTestUtils.CRASH_ABORT; + crashReporter.annotateCrashReport("TestKey", "TestValue"); + }, + function(mdump, extra) { + Assert.equal(extra.TestKey, "TestValue"); + }, + // process will exit with a zero exit status + true + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_after_js_large_allocation_failure.js b/toolkit/crashreporter/test/unit/test_crash_after_js_large_allocation_failure.js new file mode 100644 index 0000000000..7572d1fcf9 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_after_js_large_allocation_failure.js @@ -0,0 +1,23 @@ +add_task(async function run_test() { + if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) { + dump( + "INFO | test_crash_after_js_large_allocation_failure.js | Can't test crashreporter in a non-libxul build.\n" + ); + return; + } + + await do_crash( + function() { + crashType = CrashTestUtils.CRASH_MOZ_CRASH; + crashReporter.annotateCrashReport("TestKey", "Yes"); + Cu.getJSTestingFunctions().reportLargeAllocationFailure(); + Cu.forceGC(); + }, + function(mdump, extra) { + Assert.equal(extra.TestKey, "Yes"); + Assert.equal(false, "JSOutOfMemory" in extra); + Assert.equal(extra.JSLargeAllocationFailure, "Recovered"); + }, + true + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_after_js_large_allocation_failure_reporting.js b/toolkit/crashreporter/test/unit/test_crash_after_js_large_allocation_failure_reporting.js new file mode 100644 index 0000000000..2b519f1a44 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_after_js_large_allocation_failure_reporting.js @@ -0,0 +1,27 @@ +add_task(async function run_test() { + if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) { + dump( + "INFO | test_crash_after_js_oom_reporting.js | Can't test crashreporter in a non-libxul build.\n" + ); + return; + } + + await do_crash( + function() { + crashType = CrashTestUtils.CRASH_MOZ_CRASH; + crashReporter.annotateCrashReport("TestKey", "Yes"); + + function crashWhileReporting() { + CrashTestUtils.crash(crashType); + } + + Services.obs.addObserver(crashWhileReporting, "memory-pressure"); + Cu.getJSTestingFunctions().reportLargeAllocationFailure(); + }, + function(mdump, extra) { + Assert.equal(extra.TestKey, "Yes"); + Assert.equal(extra.JSLargeAllocationFailure, "Reporting"); + }, + true + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_after_js_oom_recovered.js b/toolkit/crashreporter/test/unit/test_crash_after_js_oom_recovered.js new file mode 100644 index 0000000000..e09114abf4 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_after_js_oom_recovered.js @@ -0,0 +1,22 @@ +add_task(async function run_test() { + if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) { + dump( + "INFO | test_crash_after_js_oom_recovered.js | Can't test crashreporter in a non-libxul build.\n" + ); + return; + } + + await do_crash( + function() { + crashType = CrashTestUtils.CRASH_MOZ_CRASH; + crashReporter.annotateCrashReport("TestKey", "Yes"); + Cu.getJSTestingFunctions().reportOutOfMemory(); + Cu.forceGC(); + }, + function(mdump, extra) { + Assert.equal(extra.TestKey, "Yes"); + Assert.equal(extra.JSOutOfMemory, "Recovered"); + }, + true + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_after_js_oom_reported.js b/toolkit/crashreporter/test/unit/test_crash_after_js_oom_reported.js new file mode 100644 index 0000000000..70075bff4a --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_after_js_oom_reported.js @@ -0,0 +1,36 @@ +add_task(async function run_test() { + if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) { + dump( + "INFO | test_crash_after_js_oom_reported.js | Can't test crashreporter in a non-libxul build.\n" + ); + return; + } + + await do_crash( + function() { + crashType = CrashTestUtils.CRASH_MOZ_CRASH; + crashReporter.annotateCrashReport("TestKey", "Yes"); + + // GC now to avoid having it happen randomly later, which would make the + // test bogusly fail. See comment below. + Cu.forceGC(); + + Cu.getJSTestingFunctions().reportOutOfMemory(); + }, + function(mdump, extra) { + Assert.equal(extra.TestKey, "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". + Assert.equal(extra.JSOutOfMemory, "Reported"); + }, + true + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_after_js_oom_reported_2.js b/toolkit/crashreporter/test/unit/test_crash_after_js_oom_reported_2.js new file mode 100644 index 0000000000..ad45217d47 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_after_js_oom_reported_2.js @@ -0,0 +1,28 @@ +add_task(async function run_test() { + if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) { + dump( + "INFO | test_crash_after_js_oom_reported_2.js | Can't test crashreporter in a non-libxul build.\n" + ); + return; + } + + await do_crash( + function() { + crashType = CrashTestUtils.CRASH_MOZ_CRASH; + crashReporter.annotateCrashReport("TestKey", "Yes"); + Cu.getJSTestingFunctions().reportOutOfMemory(); + Cu.forceGC(); // recover from first OOM + Cu.getJSTestingFunctions().reportOutOfMemory(); + }, + function(mdump, extra) { + Assert.equal(extra.TestKey, "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. + Assert.equal(extra.JSOutOfMemory, "Reported"); + }, + true + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_heap_corruption.js b/toolkit/crashreporter/test/unit/test_crash_heap_corruption.js new file mode 100644 index 0000000000..cff294b702 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_heap_corruption.js @@ -0,0 +1,32 @@ +add_task(async function run_test() { + if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) { + dump( + "INFO | test_crash_heap_corruption.js | Can't test crashreporter in a non-libxul build.\n" + ); + return; + } + + // Try crashing with a STATUS_HEAP_CORRUPTION exception + await do_crash( + function() { + crashType = CrashTestUtils.CRASH_HEAP_CORRUPTION; + crashReporter.annotateCrashReport("TestKey", "TestValue"); + }, + async function(mdump, extra, extraFile) { + runMinidumpAnalyzer(mdump); + + // Refresh updated extra data + let data = await OS.File.read(extraFile.path); + let decoder = new TextDecoder(); + extra = JSON.parse(decoder.decode(data)); + + Assert.equal( + extra.StackTraces.crash_info.type, + "EXCEPTION_HEAP_CORRUPTION" + ); + Assert.equal(extra.TestKey, "TestValue"); + }, + // process will exit with a zero exit status + true + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_moz_crash.js b/toolkit/crashreporter/test/unit/test_crash_moz_crash.js new file mode 100644 index 0000000000..22eb36b816 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_moz_crash.js @@ -0,0 +1,17 @@ +add_task(async function run_test() { + // Try crashing with a runtime abort + await do_crash( + function() { + crashType = CrashTestUtils.CRASH_MOZ_CRASH; + crashReporter.annotateCrashReport("TestKey", "TestValue"); + }, + function(mdump, extra) { + Assert.equal(extra.TestKey, "TestValue"); + Assert.equal(false, "OOMAllocationSize" in extra); + Assert.equal(false, "JSOutOfMemory" in extra); + Assert.equal(false, "JSLargeAllocationFailure" in extra); + }, + // process will exit with a zero exit status + true + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_oom.js b/toolkit/crashreporter/test/unit/test_crash_oom.js new file mode 100644 index 0000000000..16cb087d7a --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_oom.js @@ -0,0 +1,21 @@ +add_task(async function run_test() { + if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) { + dump( + "INFO | test_crash_oom.js | Can't test crashreporter in a non-libxul build.\n" + ); + return; + } + + await do_crash( + function() { + crashType = CrashTestUtils.CRASH_OOM; + }, + function(mdump, extra) { + Assert.ok("OOMAllocationSize" in extra); + Assert.ok(Number(extra.OOMAllocationSize) > 0); + Assert.ok("TotalPhysicalMemory" in extra); + Assert.ok(Number(extra.TotalPhysicalMemory) >= 0); + }, + true + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_phc.js b/toolkit/crashreporter/test/unit/test_crash_phc.js new file mode 100644 index 0000000000..415b8e707f --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_phc.js @@ -0,0 +1,59 @@ +function check(extra, kind, size, hasFreeStack) { + Assert.equal(extra.PHCKind, kind); + + // This is a string holding a decimal address. + Assert.ok(/^\d+$/.test(extra.PHCBaseAddress)); + + Assert.equal(extra.PHCUsableSize, size); + + // These are strings holding comma-separated lists of decimal addresses. + // Sometimes on Mac they have a single entry. + Assert.ok(/^(\d+,)*\d+$/.test(extra.PHCAllocStack)); + if (hasFreeStack) { + Assert.ok(/^(\d+,)*\d+$/.test(extra.PHCFreeStack)); + } else { + Assert.ok(!extra.hasOwnProperty("PHCFreeStack")); + } +} + +add_task(async function run_test() { + if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) { + dump( + "INFO | test_crash_phc.js | Can't test crashreporter in a non-libxul build.\n" + ); + return; + } + + await do_crash( + function() { + crashType = CrashTestUtils.CRASH_PHC_USE_AFTER_FREE; + }, + function(mdump, extra) { + // CRASH_PHC_USE_AFTER_FREE uses 32 for the size. + check(extra, "FreedPage", 32, /* hasFreeStack */ true); + }, + true + ); + + await do_crash( + function() { + crashType = CrashTestUtils.CRASH_PHC_DOUBLE_FREE; + }, + function(mdump, extra) { + // CRASH_PHC_DOUBLE_FREE uses 64 for the size. + check(extra, "FreedPage", 64, /* hasFreeStack */ true); + }, + true + ); + + do_crash( + function() { + crashType = CrashTestUtils.CRASH_PHC_BOUNDS_VIOLATION; + }, + function(mdump, extra) { + // CRASH_PHC_BOUNDS_VIOLATION uses 96 for the size. + check(extra, "GuardPage", 96, /* hasFreeStack */ false); + }, + true + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_purevirtual.js b/toolkit/crashreporter/test/unit/test_crash_purevirtual.js new file mode 100644 index 0000000000..b370e20958 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_purevirtual.js @@ -0,0 +1,29 @@ +add_task(async function run_test() { + if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) { + dump( + "INFO | test_crash_purevirtual.js | Can't test crashreporter in a non-libxul build.\n" + ); + return; + } + + var isOSX = "nsILocalFileMac" in Ci; + if (isOSX) { + dump( + "INFO | test_crash_purevirtual.js | TODO: purecalls not caught on OS X\n" + ); + return; + } + + // Try crashing with a pure virtual call + await do_crash( + function() { + crashType = CrashTestUtils.CRASH_PURE_VIRTUAL_CALL; + crashReporter.annotateCrashReport("TestKey", "TestValue"); + }, + function(mdump, extra) { + Assert.equal(extra.TestKey, "TestValue"); + }, + // process will exit with a zero exit status + true + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_rust_panic.js b/toolkit/crashreporter/test/unit/test_crash_rust_panic.js new file mode 100644 index 0000000000..372b3bb2dc --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_rust_panic.js @@ -0,0 +1,15 @@ +add_task(async function run_test() { + // Try crashing with a Rust panic + await do_crash( + function() { + Cc["@mozilla.org/xpcom/debug;1"] + .getService(Ci.nsIDebug2) + .rustPanic("OH NO"); + }, + function(mdump, extra) { + Assert.equal(extra.MozCrashReason, "OH NO"); + }, + // process will exit with a zero exit status + true + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_rust_panic_multiline.js b/toolkit/crashreporter/test/unit/test_crash_rust_panic_multiline.js new file mode 100644 index 0000000000..d77574af40 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_rust_panic_multiline.js @@ -0,0 +1,15 @@ +add_task(async function run_test() { + // Try crashing with a Rust panic + await do_crash( + function() { + Cc["@mozilla.org/xpcom/debug;1"] + .getService(Ci.nsIDebug2) + .rustPanic("OH NO\nOH NOES!"); + }, + function(mdump, extra) { + Assert.equal(extra.MozCrashReason, "OH NO\nOH NOES!"); + }, + // process will exit with a zero exit status + true + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_terminator.js b/toolkit/crashreporter/test/unit/test_crash_terminator.js new file mode 100644 index 0000000000..908df581ef --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_terminator.js @@ -0,0 +1,40 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the Shutdown Terminator report errors correctly + +function setup_crash() { + const { Services } = ChromeUtils.import( + "resource://gre/modules/Services.jsm" + ); + + Services.prefs.setBoolPref("toolkit.terminator.testing", true); + Services.prefs.setIntPref("toolkit.asyncshutdown.crash_timeout", 10); + + // Initialize the terminator + // (normally, this is done through the manifest file, but xpcshell + // doesn't take them into account). + let terminator = Cc[ + "@mozilla.org/toolkit/shutdown-terminator;1" + ].createInstance(Ci.nsIObserver); + terminator.observe(null, "profile-after-change", null); + + // Inform the terminator that shutdown has started + // Pick an arbitrary notification + terminator.observe(null, "xpcom-will-shutdown", null); + terminator.observe(null, "profile-before-change", null); + + dump("Waiting (actively) for the crash\n"); + Services.tm.spinEventLoopUntil(() => false); +} + +function after_crash(mdump, extra) { + info("Crash signature: " + JSON.stringify(extra, null, "\t")); + Assert.equal(extra.ShutdownProgress, "profile-before-change"); +} + +add_task(async function run_test() { + await do_crash(setup_crash, after_crash); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_thread_annotation.js b/toolkit/crashreporter/test/unit/test_crash_thread_annotation.js new file mode 100644 index 0000000000..115ab23aaa --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_thread_annotation.js @@ -0,0 +1,18 @@ +add_task(async function run_test() { + if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) { + dump( + "INFO | test_crash_thread_annotation.js | Can't test crashreporter in a non-libxul build.\n" + ); + return; + } + + await do_crash( + function() { + crashType = CrashTestUtils.CRASH_INVALID_POINTER_DEREF; + }, + function(mdump, extra) { + Assert.ok("ThreadIdNameMapping" in extra); + }, + true + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_uncaught_exception.js b/toolkit/crashreporter/test/unit/test_crash_uncaught_exception.js new file mode 100644 index 0000000000..98158e6010 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_uncaught_exception.js @@ -0,0 +1,17 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function run_test() { + // Try crashing with an uncaught exception. + await do_crash( + function() { + crashType = CrashTestUtils.CRASH_UNCAUGHT_EXCEPTION; + crashReporter.annotateCrashReport("TestKey", "TestValue"); + }, + function(mdump, extra) { + Assert.equal(extra.TestKey, "TestValue"); + }, + // process will exit with a zero exit status + true + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_large.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_large.js new file mode 100644 index 0000000000..a16fe20b4c --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_large.js @@ -0,0 +1,6 @@ +add_task(async function run_test() { + await do_x64CFITest("CRASH_X64CFI_ALLOC_LARGE", [ + { symbol: "CRASH_X64CFI_ALLOC_LARGE", trust: "context" }, + { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }, + ]); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_small.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_small.js new file mode 100644 index 0000000000..0b321bac6f --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_small.js @@ -0,0 +1,6 @@ +add_task(async function run_test() { + await do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL", [ + { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" }, + { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }, + ]); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_epilog.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_epilog.js new file mode 100644 index 0000000000..423b67f6f9 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_epilog.js @@ -0,0 +1,6 @@ +add_task(async function run_test() { + await do_x64CFITest("CRASH_X64CFI_EPILOG", [ + { symbol: "CRASH_X64CFI_EPILOG", trust: "context" }, + { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }, + ]); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.exe b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.exe Binary files differnew file mode 100644 index 0000000000..5283cc5df7 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.exe diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.js new file mode 100644 index 0000000000..a8a900c995 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.js @@ -0,0 +1,22 @@ +add_task(async function run_test() { + // Test that minidump-analyzer gracefully handles chained + // unwind code entries that form a circular reference + // (infinite loop). + let exe = do_get_file("test_crash_win64cfi_infinite_code_chain.exe"); + ok(exe); + + // Perform a crash. The PE used for unwind info should fail, resulting in + // fallback behavior, calculating the first frame from thread context. + // Further frames would be calculated with either frame_pointer or scan trust, + // but should not be calculated via CFI. If we see CFI here that would be an + // indication that either our alternative EXE was not used, or we failed to + // abandon unwind info parsing. + await do_x64CFITest( + "CRASH_X64CFI_ALLOC_SMALL", + [ + { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" }, + { symbol: null, trust: "!cfi" }, + ], + ["--force-use-module", exe.path] + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.exe b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.exe Binary files differnew file mode 100644 index 0000000000..de48576f65 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.exe diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.js new file mode 100644 index 0000000000..de3d31f0ef --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.js @@ -0,0 +1,22 @@ +add_task(async function run_test() { + // Test that minidump-analyzer gracefully handles chained + // IMAGE_RUNTIME_FUNCTION_ENTRY items that form a circular reference + // (infinite loop). + let exe = do_get_file("test_crash_win64cfi_infinite_entry_chain.exe"); + ok(exe); + + // Perform a crash. The PE used for unwind info should fail, resulting in + // fallback behavior, calculating the first frame from thread context. + // Further frames would be calculated with either frame_pointer or scan trust, + // but should not be calculated via CFI. If we see CFI here that would be an + // indication that either our alternative EXE was not used, or we failed to + // abandon unwind info parsing. + await do_x64CFITest( + "CRASH_X64CFI_ALLOC_SMALL", + [ + { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" }, + { symbol: null, trust: "!cfi" }, + ], + ["--force-use-module", exe.path] + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.exe b/toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.exe Binary files differnew file mode 100644 index 0000000000..ab4ce326bd --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.exe diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.js new file mode 100644 index 0000000000..3d1eb02011 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.js @@ -0,0 +1,21 @@ +add_task(async function run_test() { + // Test that minidump-analyzer gracefully handles an invalid pointer to the + // exception unwind information. + let exe = do_get_file("test_crash_win64cfi_invalid_exception_rva.exe"); + ok(exe); + + // Perform a crash. The PE used for unwind info should fail, resulting in + // fallback behavior, calculating the first frame from thread context. + // Further frames would be calculated with either frame_pointer or scan trust, + // but should not be calculated via CFI. If we see CFI here that would be an + // indication that either our alternative EXE was not used, or we failed to + // abandon unwind info parsing. + await do_x64CFITest( + "CRASH_X64CFI_ALLOC_SMALL", + [ + { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" }, + { symbol: null, trust: "!cfi" }, + ], + ["--force-use-module", exe.path] + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.exe b/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.exe new file mode 100644 index 0000000000..5fa1d76ffc --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.exe @@ -0,0 +1 @@ +this is not a valid PE file.
\ No newline at end of file diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.js new file mode 100644 index 0000000000..5fcbee9cbe --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.js @@ -0,0 +1,20 @@ +add_task(async function run_test() { + // Test that minidump-analyzer gracefully handles corrupt PE files. + let exe = do_get_file("test_crash_win64cfi_not_a_pe.exe"); + ok(exe); + + // Perform a crash. The PE used for unwind info should fail, resulting in + // fallback behavior, calculating the first frame from thread context. + // Further frames would be calculated with either frame_pointer or scan trust, + // but should not be calculated via CFI. If we see CFI here that would be an + // indication that either our alternative EXE was not used, or we failed to + // abandon unwind info parsing. + await do_x64CFITest( + "CRASH_X64CFI_ALLOC_SMALL", + [ + { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" }, + { symbol: null, trust: "!cfi" }, + ], + ["--force-use-module", exe.path] + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_push_nonvol.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_push_nonvol.js new file mode 100644 index 0000000000..e875ab3434 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_push_nonvol.js @@ -0,0 +1,6 @@ +add_task(async function run_test() { + await do_x64CFITest("CRASH_X64CFI_PUSH_NONVOL", [ + { symbol: "CRASH_X64CFI_PUSH_NONVOL", trust: "context" }, + { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }, + ]); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol.js new file mode 100644 index 0000000000..9bc309834a --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol.js @@ -0,0 +1,6 @@ +add_task(async function run_test() { + await do_x64CFITest("CRASH_X64CFI_SAVE_NONVOL", [ + { symbol: "CRASH_X64CFI_SAVE_NONVOL", trust: "context" }, + { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }, + ]); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol_far.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol_far.js new file mode 100644 index 0000000000..b87b4b35b4 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol_far.js @@ -0,0 +1,6 @@ +add_task(async function run_test() { + await do_x64CFITest("CRASH_X64CFI_SAVE_NONVOL_FAR", [ + { symbol: "CRASH_X64CFI_SAVE_NONVOL_FAR", trust: "context" }, + { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }, + ]); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128.js new file mode 100644 index 0000000000..22d09f157a --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128.js @@ -0,0 +1,6 @@ +add_task(async function run_test() { + await do_x64CFITest("CRASH_X64CFI_SAVE_XMM128", [ + { symbol: "CRASH_X64CFI_SAVE_XMM128", trust: "context" }, + { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }, + ]); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128_far.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128_far.js new file mode 100644 index 0000000000..bd5f16baa8 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128_far.js @@ -0,0 +1,6 @@ +add_task(async function run_test() { + await do_x64CFITest("CRASH_X64CFI_SAVE_XMM128_FAR", [ + { symbol: "CRASH_X64CFI_SAVE_XMM128_FAR", trust: "context" }, + { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }, + ]); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_unknown_op.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_unknown_op.js new file mode 100644 index 0000000000..cdcd8c2aad --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_unknown_op.js @@ -0,0 +1,12 @@ +add_task(async function run_test() { + // In the case of an unknown unwind code or missing CFI, + // make certain we can still walk the stack via stack scan. The crashing + // function places NO_MANS_LAND on the stack so it will get picked up via + // stack scan. + await do_x64CFITest("CRASH_X64CFI_UNKNOWN_OPCODE", [ + { symbol: "CRASH_X64CFI_UNKNOWN_OPCODE", trust: "context" }, + // Trust may either be scan or frame_pointer; we don't really care as + // long as the address is expected. + { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }, + ]); +}); diff --git a/toolkit/crashreporter/test/unit/test_crash_with_memory_report.js b/toolkit/crashreporter/test/unit/test_crash_with_memory_report.js new file mode 100644 index 0000000000..ffc30f38b5 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_with_memory_report.js @@ -0,0 +1,52 @@ +add_task(async function run_test() { + if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) { + dump( + "INFO | test_crash_oom.js | Can't test crashreporter in a non-libxul build.\n" + ); + return; + } + + // This was shamelessly copied and stripped down from do_get_profile() in + // head.js so that nsICrashReporter::saveMemoryReport can use a profile + // within the crasher subprocess. + + await do_crash( + function() { + // Delay crashing so that the memory report has time to complete. + shouldDelay = true; + + let env = Cc["@mozilla.org/process/environment;1"].getService( + Ci.nsIEnvironment + ); + let profd = env.get("XPCSHELL_TEST_PROFILE_DIR"); + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.initWithPath(profd); + + let provider = { + getFile(prop, persistent) { + persistent.value = true; + if ( + prop == "ProfD" || + prop == "ProfLD" || + prop == "ProfDS" || + prop == "ProfLDS" || + prop == "TmpD" + ) { + return file.clone(); + } + throw Components.Exception("", Cr.NS_ERROR_FAILURE); + }, + QueryInterface: ChromeUtils.generateQI(["nsIDirectoryServiceProvider"]), + }; + Services.dirsvc + .QueryInterface(Ci.nsIDirectoryService) + .registerProvider(provider); + + crashReporter.saveMemoryReport(); + }, + function(mdump, extra) { + Assert.equal(extra.ContainsMemoryReport, "1"); + }, + true + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crashreporter.js b/toolkit/crashreporter/test/unit/test_crashreporter.js new file mode 100644 index 0000000000..ee3653834a --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crashreporter.js @@ -0,0 +1,94 @@ +function run_test() { + dump("INFO | test_crashreporter.js | Get crashreporter service.\n"); + var cr = Cc["@mozilla.org/toolkit/crash-reporter;1"].getService( + Ci.nsICrashReporter + ); + Assert.notEqual(cr, null); + + Assert.ok(cr.enabled); + + try { + cr.serverURL; + do_throw("Getting serverURL when not set should have thrown!"); + } catch (ex) { + Assert.equal(ex.result, Cr.NS_ERROR_FAILURE); + } + + // check setting/getting serverURL + + // try it with two different URLs, just for kicks + var testspecs = [ + "http://example.com/submit", + "https://example.org/anothersubmit", + ]; + for (var i = 0; i < testspecs.length; ++i) { + cr.serverURL = Services.io.newURI(testspecs[i]); + Assert.equal(cr.serverURL.spec, testspecs[i]); + } + + // should not allow setting non-http/https URLs + try { + cr.serverURL = Services.io.newURI("ftp://example.com/submit"); + do_throw("Setting serverURL to a non-http(s) URL should have thrown!"); + } catch (ex) { + Assert.equal(ex.result, Cr.NS_ERROR_INVALID_ARG); + } + + // check getting/setting minidumpPath + // it should be $TEMP by default, but I'm not sure if we can exactly test that + // this will at least test that it doesn't throw + Assert.notEqual(cr.minidumpPath.path, ""); + var cwd = do_get_cwd(); + cr.minidumpPath = cwd; + Assert.equal(cr.minidumpPath.path, cwd.path); + + // Test annotateCrashReport() + try { + cr.annotateCrashReport(undefined, ""); + do_throw( + "Calling annotateCrashReport() with an undefined key should have thrown!" + ); + } catch (ex) { + Assert.equal(ex.result, Cr.NS_ERROR_INVALID_ARG); + } + try { + cr.annotateCrashReport("foobar", ""); + do_throw( + "Calling annotateCrashReport() with a bogus key should have thrown!" + ); + } catch (ex) { + Assert.equal(ex.result, Cr.NS_ERROR_INVALID_ARG); + } + cr.annotateCrashReport("TestKey", "testData1"); + // Replace previous data. + cr.annotateCrashReport("TestKey", "testData2"); + // Allow nul chars in annotations. + cr.annotateCrashReport("TestKey", "da\0ta"); + + cr.appendAppNotesToCrashReport("additional testData3"); + // Add more data. + cr.appendAppNotesToCrashReport("additional testData4"); + + // Test removeCrashReportAnnotation() + try { + cr.removeCrashReportAnnotation(undefined); + do_throw( + "Calling removeCrashReportAnnotation() with an undefined key should have thrown!" + ); + } catch (ex) { + Assert.equal(ex.result, Cr.NS_ERROR_INVALID_ARG); + } + try { + cr.removeCrashReportAnnotation("foobar"); + do_throw( + "Calling removeCrashReportAnnotation() with a bogus key should have thrown!" + ); + } catch (ex) { + Assert.equal(ex.result, Cr.NS_ERROR_INVALID_ARG); + } + cr.removeCrashReportAnnotation("TestKey"); + + // Testing setting the minidumpPath field + cr.minidumpPath = cwd; + Assert.equal(cr.minidumpPath.path, cwd.path); +} diff --git a/toolkit/crashreporter/test/unit/test_crashreporter_appmem.js b/toolkit/crashreporter/test/unit/test_crashreporter_appmem.js new file mode 100644 index 0000000000..c7ca192935 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crashreporter_appmem.js @@ -0,0 +1,13 @@ +add_task(async function run_test() { + await do_crash( + function() { + let appAddr = CrashTestUtils.saveAppMemory(); + crashReporter.registerAppMemory(appAddr, 32); + }, + function(mdump, extra) { + Assert.ok(mdump.exists()); + Assert.ok(mdump.fileSize > 0); + Assert.ok(CrashTestUtils.dumpCheckMemory(mdump.path)); + } + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_crashreporter_crash.js b/toolkit/crashreporter/test/unit/test_crashreporter_crash.js new file mode 100644 index 0000000000..7403c321e1 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crashreporter_crash.js @@ -0,0 +1,221 @@ +add_task(async function run_test() { + var is_win7_or_newer = false; + var is_windows = false; + var ph = Cc["@mozilla.org/network/protocol;1?name=http"].getService( + Ci.nsIHttpProtocolHandler + ); + var match = ph.userAgent.match(/Windows NT (\d+).(\d+)/); + if (match) { + is_windows = true; + } + if ( + match && + (parseInt(match[1]) > 6 || + (parseInt(match[1]) == 6 && parseInt(match[2]) >= 1)) + ) { + is_win7_or_newer = true; + } + + // try a basic crash + await do_content_crash(null, function(mdump, extra) { + Assert.ok(mdump.exists()); + Assert.ok(mdump.fileSize > 0); + Assert.ok("StartupTime" in extra); + Assert.ok("CrashTime" in extra); + Assert.ok( + CrashTestUtils.dumpHasStream( + mdump.path, + CrashTestUtils.MD_THREAD_LIST_STREAM + ) + ); + Assert.ok(CrashTestUtils.dumpHasInstructionPointerMemory(mdump.path)); + if (is_windows) { + [ + "SystemMemoryUsePercentage", + "TotalVirtualMemory", + "AvailableVirtualMemory", + "AvailablePageFile", + "AvailablePhysicalMemory", + ].forEach(function(prop) { + Assert.ok(/^\d+$/.test(extra[prop].toString())); + }); + } + if (is_win7_or_newer) { + Assert.ok( + CrashTestUtils.dumpHasStream( + mdump.path, + CrashTestUtils.MD_MEMORY_INFO_LIST_STREAM + ) + ); + } + }); + + // check setting some basic data + await do_crash( + function() { + // Add various annotations + crashReporter.annotateCrashReport("TestKey", "TestValue"); + crashReporter.annotateCrashReport( + "TestUnicode", + "\u{1F4A9}\n\u{0000}Escape" + ); + crashReporter.annotateCrashReport("Add-ons", "test%40mozilla.org:0.1"); + crashReporter.appendAppNotesToCrashReport("Junk"); + crashReporter.appendAppNotesToCrashReport("MoreJunk"); + + // TelemetrySession setup will trigger the session annotation + let scope = {}; + ChromeUtils.import( + "resource://gre/modules/TelemetryController.jsm", + scope + ); + scope.TelemetryController.testSetup(); + }, + function(mdump, extra) { + Assert.equal(extra.TestKey, "TestValue"); + Assert.equal(extra.TestUnicode, "\u{1F4A9}\n\u{0000}Escape"); + Assert.equal(extra.Notes, "JunkMoreJunk"); + Assert.equal(extra["Add-ons"], "test%40mozilla.org:0.1"); + const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + Assert.ok( + "TelemetrySessionId" in extra, + "The TelemetrySessionId field is present in the extra file" + ); + Assert.ok( + UUID_REGEX.test(extra.TelemetrySessionId), + "The TelemetrySessionId is a UUID" + ); + Assert.ok( + !("TelemetryClientId" in extra), + "The TelemetryClientId field is omitted by default" + ); + Assert.ok( + !("TelemetryServerURL" in extra), + "The TelemetryServerURL field is omitted by default" + ); + } + ); + + await do_crash( + function() { + // Enable the FHR, official policy bypass (since we're in a test) and + // specify a telemetry server & client ID. + Services.prefs.setBoolPref( + "datareporting.policy.dataSubmissionPolicyBypassNotification", + true + ); + Services.prefs.setBoolPref( + "datareporting.healthreport.uploadEnabled", + true + ); + Services.prefs.setCharPref( + "toolkit.telemetry.server", + "http://a.telemetry.server" + ); + Services.prefs.setIntPref("telemetry.fog.test.localhost_port", -1); + Services.prefs.setCharPref( + "toolkit.telemetry.cachedClientID", + "f3582dee-22b9-4d73-96d1-79ef5bf2fc24" + ); + + // TelemetrySession setup will trigger the session annotation + let scope = {}; + ChromeUtils.import( + "resource://gre/modules/TelemetryController.jsm", + scope + ); + ChromeUtils.import("resource://gre/modules/TelemetrySend.jsm", scope); + scope.TelemetrySend.setTestModeEnabled(true); + scope.TelemetryController.testSetup(); + }, + function(mdump, extra) { + Assert.ok( + "TelemetryClientId" in extra, + "The TelemetryClientId field is present when the FHR is on" + ); + Assert.equal( + extra.TelemetryClientId, + "f3582dee-22b9-4d73-96d1-79ef5bf2fc24", + "The TelemetryClientId matches the expected value" + ); + Assert.ok( + "TelemetryServerURL" in extra, + "The TelemetryServerURL field is present when the FHR is on" + ); + Assert.equal( + extra.TelemetryServerURL, + "http://a.telemetry.server", + "The TelemetryServerURL matches the expected value" + ); + } + ); + + await do_crash( + function() { + // Disable the FHR upload, no telemetry annotations should be present. + Services.prefs.setBoolPref( + "datareporting.policy.dataSubmissionPolicyBypassNotification", + true + ); + Services.prefs.setBoolPref( + "datareporting.healthreport.uploadEnabled", + false + ); + + // TelemetrySession setup will trigger the session annotation + let scope = {}; + ChromeUtils.import( + "resource://gre/modules/TelemetryController.jsm", + scope + ); + ChromeUtils.import("resource://gre/modules/TelemetrySend.jsm", scope); + scope.TelemetrySend.setTestModeEnabled(true); + scope.TelemetryController.testSetup(); + }, + function(mdump, extra) { + Assert.ok( + !("TelemetryClientId" in extra), + "The TelemetryClientId field is omitted when FHR upload is disabled" + ); + Assert.ok( + !("TelemetryServerURL" in extra), + "The TelemetryServerURL field is omitted when FHR upload is disabled" + ); + } + ); + + await do_crash( + function() { + // No telemetry annotations should be present if the user has not been + // notified yet + Services.prefs.setBoolPref( + "datareporting.policy.dataSubmissionPolicyBypassNotification", + false + ); + Services.prefs.setBoolPref( + "datareporting.healthreport.uploadEnabled", + true + ); + + // TelemetrySession setup will trigger the session annotation + let scope = {}; + ChromeUtils.import( + "resource://gre/modules/TelemetryController.jsm", + scope + ); + ChromeUtils.import("resource://gre/modules/TelemetrySend.jsm", scope); + scope.TelemetrySend.setTestModeEnabled(true); + scope.TelemetryController.testSetup(); + }, + function(mdump, extra) { + Assert.ok( + !("TelemetryClientId" in extra), + "The TelemetryClientId field is omitted when FHR upload is disabled" + ); + Assert.ok( + !("TelemetryServerURL" in extra), + "The TelemetryServerURL field is omitted when FHR upload is disabled" + ); + } + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_event_files.js b/toolkit/crashreporter/test/unit/test_event_files.js new file mode 100644 index 0000000000..e5c18afe97 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_event_files.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +ChromeUtils.import("resource://gre/modules/Services.jsm", this); +ChromeUtils.import("resource://testing-common/AppData.jsm", this); + +add_task(async function test_setup() { + do_get_profile(); + await makeFakeAppDir(); +}); + +add_task(async function test_main_process_crash() { + let cm = Services.crashmanager; + Assert.ok(cm, "CrashManager available."); + + let basename; + let count = await new Promise((resolve, reject) => { + do_crash( + function() { + // TelemetrySession setup will trigger the session annotation + let scope = {}; + ChromeUtils.import( + "resource://gre/modules/TelemetryController.jsm", + scope + ); + scope.TelemetryController.testSetup(); + crashType = CrashTestUtils.CRASH_MOZ_CRASH; + crashReporter.annotateCrashReport("ShutdownProgress", "event-test"); + }, + (minidump, extra) => { + basename = minidump.leafName; + Object.defineProperty(cm, "_eventsDirs", { value: [getEventDir()] }); + cm.aggregateEventsFiles().then(resolve, reject); + }, + true + ); + }); + Assert.equal(count, 1, "A single crash event file was seen."); + let crashes = await cm.getCrashes(); + Assert.equal(crashes.length, 1); + let crash = crashes[0]; + Assert.ok(crash.isOfType(cm.PROCESS_TYPE_MAIN, cm.CRASH_TYPE_CRASH)); + Assert.equal(crash.id + ".dmp", basename, "ID recorded properly"); + Assert.equal(crash.metadata.ShutdownProgress, "event-test"); + Assert.ok("TelemetrySessionId" in crash.metadata); + Assert.ok("UptimeTS" in crash.metadata); + Assert.ok( + /^[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}$/.test( + crash.metadata.TelemetrySessionId + ) + ); + Assert.ok("CrashTime" in crash.metadata); + Assert.ok(/^\d+$/.test(crash.metadata.CrashTime)); +}); diff --git a/toolkit/crashreporter/test/unit/test_kill.js b/toolkit/crashreporter/test/unit/test_kill.js new file mode 100644 index 0000000000..b7d86eb4e1 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_kill.js @@ -0,0 +1,44 @@ +// Test that calling Services.processtools.kill doesn't create a crash report. +add_task(async function run_test() { + if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) { + dump( + "INFO | test_kill.js | Can't test crashreporter in a non-libxul build.\n" + ); + return; + } + + // Let's launch a child process and kill it (from within, it's simpler). + + do_load_child_test_harness(); + + // Setting the minidump path won't work in the child, so we need to do + // that here. + let crashReporter = Cc["@mozilla.org/toolkit/crash-reporter;1"].getService( + Ci.nsICrashReporter + ); + crashReporter.minidumpPath = do_get_tempdir(); + let headfile = do_get_file("../unit/crasher_subprocess_head.js"); + const CRASH_THEN_WAIT = + "const ProcessTools = Cc['@mozilla.org/processtools-service;1'].getService(Ci.nsIProcessToolsService);\ + console.log('Child process commiting ritual self-sacrifice');\ + ProcessTools.kill(ProcessTools.pid);\ + console.error('Oops, I should be dead');\ + while (true) {} ;"; + do_get_profile(); + await makeFakeAppDir(); + await sendCommandAsync('load("' + headfile.path.replace(/\\/g, "/") + '");'); + await sendCommandAsync(CRASH_THEN_WAIT); + + // Let's wait a little to give the child process a chance to create a minidump. + let { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm"); + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, 100)); + + // Now make sure that we have no minidump. + let minidump = getMinidump(); + Assert.equal( + minidump, + null, + `There should be no minidump ${minidump == null ? "null" : minidump.path}` + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_oom_annotation.js b/toolkit/crashreporter/test/unit/test_oom_annotation.js new file mode 100644 index 0000000000..90a13563ba --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_oom_annotation.js @@ -0,0 +1,67 @@ +add_task(async function run_test() { + if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) { + dump( + "INFO | test_crash_oom.js | Can't test crashreporter in a non-libxul build.\n" + ); + return; + } + + await do_content_crash( + function() { + crashType = CrashTestUtils.CRASH_OOM; + crashReporter.annotateCrashReport("TestKey", "Yes"); + }, + function(mdump, extra) { + ChromeUtils.import("resource://gre/modules/AppConstants.jsm", this); + Assert.equal(extra.TestKey, "Yes"); + + // A list of pairs [annotation name, must be > 0] + let annotations; + switch (AppConstants.platform) { + case "win": + annotations = [ + ["OOMAllocationSize", true], + ["SystemMemoryUsePercentage", false], + ["TotalVirtualMemory", true], + ["AvailableVirtualMemory", false], + ["TotalPageFile", false], + ["AvailablePageFile", false], + ["TotalPhysicalMemory", true], + ["AvailablePhysicalMemory", false], + ]; + break; + case "linux": + annotations = [ + ["AvailablePageFile", false], + ["AvailablePhysicalMemory", false], + ["AvailableSwapMemory", false], + ["AvailableVirtualMemory", false], + ["TotalPageFile", false], + ["TotalPhysicalMemory", true], + ]; + break; + case "macosx": + annotations = [ + ["AvailablePhysicalMemory", false], + ["AvailableSwapMemory", false], + ["PurgeablePhysicalMemory", false], + ["TotalPhysicalMemory", true], + ]; + break; + default: + annotations = []; + } + for (let [label, shouldBeGreaterThanZero] of annotations) { + Assert.ok(label in extra, `Annotation ${label} is present`); + + // All these annotations should represent non-negative numbers. + // A few of them (e.g. physical memory) are guaranteed to be positive. + if (shouldBeGreaterThanZero) { + Assert.ok(Number(extra[label]) > 0); + } else { + Assert.ok(Number(extra[label]) >= 0); + } + } + } + ); +}); diff --git a/toolkit/crashreporter/test/unit/test_override_exception_handler.js b/toolkit/crashreporter/test/unit/test_override_exception_handler.js new file mode 100644 index 0000000000..53be62d665 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_override_exception_handler.js @@ -0,0 +1,11 @@ +add_task(async function run_test() { + // Ensure that attempting to override the exception handler doesn't cause + // us to lose our exception handler. + await do_crash( + function() { + CrashTestUtils.TryOverrideExceptionHandler(); + }, + function(mdump, extra) {}, + true + ); +}); diff --git a/toolkit/crashreporter/test/unit/xpcshell-phc.ini b/toolkit/crashreporter/test/unit/xpcshell-phc.ini new file mode 100644 index 0000000000..056bbd6fbe --- /dev/null +++ b/toolkit/crashreporter/test/unit/xpcshell-phc.ini @@ -0,0 +1,9 @@ +[DEFAULT] +head = head_crashreporter.js +skip-if = toolkit == 'android' || (os == "win" && processor == "aarch64") # 1536217 +support-files = + crasher_subprocess_head.js + crasher_subprocess_tail.js + +[test_crash_phc.js] + diff --git a/toolkit/crashreporter/test/unit/xpcshell.ini b/toolkit/crashreporter/test/unit/xpcshell.ini new file mode 100644 index 0000000000..65882e7447 --- /dev/null +++ b/toolkit/crashreporter/test/unit/xpcshell.ini @@ -0,0 +1,113 @@ +[DEFAULT] +head = head_crashreporter.js +skip-if = toolkit == 'android' +support-files = + crasher_subprocess_head.js + crasher_subprocess_tail.js + +[test_crash_moz_crash.js] +[test_crash_purevirtual.js] +[test_crash_rust_panic.js] +[test_crash_rust_panic_multiline.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] +[test_oom_annotation.js] +[test_kill.js] + +[test_crash_abort.js] +skip-if = os == 'win' + +[test_crash_uncaught_exception.js] + +[test_crash_thread_annotation.js] + +[test_crash_with_memory_report.js] +[test_crashreporter.js] +[test_crashreporter_crash.js] +[test_override_exception_handler.js] +skip-if = os != 'win' + +[test_crashreporter_appmem.js] +# we need to skip this due to bug 838613 +skip-if = (os != 'win' && os != 'linux') || (os=='linux' && bits==32) + +[test_crash_AsyncShutdown.js] +[test_event_files.js] +[test_crash_terminator.js] + +[test_crash_heap_corruption.js] +skip-if = os != 'win' + +[test_crash_win64cfi_unknown_op.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = !(os == 'win' && bits == 64 && processor == 'x86_64') +reason = Windows test specific to the x86-64 architecture + +[test_crash_win64cfi_push_nonvol.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = !(os == 'win' && bits == 64 && processor == 'x86_64') +reason = Windows test specific to the x86-64 architecture + +[test_crash_win64cfi_alloc_small.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = !(os == 'win' && bits == 64 && processor == 'x86_64') +reason = Windows test specific to the x86-64 architecture + +[test_crash_win64cfi_alloc_large.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = !(os == 'win' && bits == 64 && processor == 'x86_64') +reason = Windows test specific to the x86-64 architecture + +[test_crash_win64cfi_save_nonvol.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = !(os == 'win' && bits == 64 && processor == 'x86_64') +reason = Windows test specific to the x86-64 architecture + +[test_crash_win64cfi_save_nonvol_far.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = !(os == 'win' && bits == 64 && processor == 'x86_64') +reason = Windows test specific to the x86-64 architecture + +[test_crash_win64cfi_save_xmm128.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = !(os == 'win' && bits == 64 && processor == 'x86_64') +reason = Windows test specific to the x86-64 architecture + +[test_crash_win64cfi_save_xmm128_far.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = !(os == 'win' && bits == 64 && processor == 'x86_64') +reason = Windows test specific to the x86-64 architecture + +[test_crash_win64cfi_epilog.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = !(os == 'win' && bits == 64 && processor == 'x86_64') +reason = Windows test specific to the x86-64 architecture + +[test_crash_win64cfi_infinite_entry_chain.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = !(os == 'win' && bits == 64 && processor == 'x86_64') +reason = Windows test specific to the x86-64 architecture +support-files = test_crash_win64cfi_infinite_entry_chain.exe + +[test_crash_win64cfi_infinite_code_chain.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = !(os == 'win' && bits == 64 && processor == 'x86_64') +reason = Windows test specific to the x86-64 architecture +support-files = test_crash_win64cfi_infinite_code_chain.exe + +[test_crash_win64cfi_invalid_exception_rva.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = !(os == 'win' && bits == 64 && processor == 'x86_64') +reason = Windows test specific to the x86-64 architecture +support-files = test_crash_win64cfi_invalid_exception_rva.exe + +[test_crash_win64cfi_not_a_pe.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = !(os == 'win' && bits == 64 && processor == 'x86_64') +reason = Windows test specific to the x86-64 architecture +support-files = test_crash_win64cfi_not_a_pe.exe + |