summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/test/unit
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /toolkit/crashreporter/test/unit
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/crashreporter/test/unit')
-rw-r--r--toolkit/crashreporter/test/unit/crasher_subprocess_head.js29
-rw-r--r--toolkit/crashreporter/test/unit/crasher_subprocess_tail.js20
-rw-r--r--toolkit/crashreporter/test/unit/head_crashreporter.js347
-rw-r--r--toolkit/crashreporter/test/unit/head_win64cfi.js215
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_AsyncShutdown.js75
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_abort.js17
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_after_js_large_allocation_failure.js23
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_after_js_large_allocation_failure_reporting.js27
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_after_js_oom_recovered.js22
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_after_js_oom_reported.js36
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_after_js_oom_reported_2.js28
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_backgroundtask_moz_crash.js25
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_exc_guard.js30
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_heap_corruption.js27
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_modules.js44
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_moz_crash.js17
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_oom.js21
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_phc.js59
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_purevirtual.js29
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_rust_panic.js15
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_rust_panic_multiline.js15
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_stack_overflow.js21
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_terminator.js39
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_uncaught_exception.js17
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_large.js6
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_small.js6
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_win64cfi_epilog.js6
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.js22
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.js22
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.js21
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.js20
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_win64cfi_push_nonvol.js6
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol.js6
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol_far.js6
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128.js6
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128_far.js6
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_win64cfi_unknown_op.js12
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_with_memory_report.js49
-rw-r--r--toolkit/crashreporter/test/unit/test_crashreporter.js90
-rw-r--r--toolkit/crashreporter/test/unit/test_crashreporter_appmem.js13
-rw-r--r--toolkit/crashreporter/test/unit/test_crashreporter_crash.js223
-rw-r--r--toolkit/crashreporter/test/unit/test_event_files.js56
-rw-r--r--toolkit/crashreporter/test/unit/test_kill.js43
-rw-r--r--toolkit/crashreporter/test/unit/test_oom_annotation.js71
-rw-r--r--toolkit/crashreporter/test/unit/test_override_exception_handler.js11
-rw-r--r--toolkit/crashreporter/test/unit/xpcshell-phc.ini11
-rw-r--r--toolkit/crashreporter/test/unit/xpcshell.ini131
47 files changed, 2041 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..19525bb605
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/crasher_subprocess_head.js
@@ -0,0 +1,29 @@
+// enable crash reporting first
+var cwd = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
+
+// get the temp dir
+var _tmpd = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+_tmpd.initWithPath(Services.env.get("XPCSHELL_TEST_TEMP_DIR"));
+
+// Allow `crashReporter` to be used as an alias in the tests.
+var crashReporter = Services.appinfo;
+
+// We need to call this or crash events go in an undefined location.
+Services.appinfo.UpdateCrashEventsDir();
+
+// Setting the minidump path is not allowed in content processes
+var processType = Services.appinfo.processType;
+if (processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
+ Services.appinfo.minidumpPath = _tmpd;
+}
+
+var protocolHandler = Services.io
+ .getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler);
+var curDirURI = Services.io.newFileURI(cwd);
+protocolHandler.setSubstitution("test", curDirURI);
+const { CrashTestUtils } = ChromeUtils.importESModule(
+ "resource://test/CrashTestUtils.sys.mjs"
+);
+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..c9e415b567
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/crasher_subprocess_tail.js
@@ -0,0 +1,20 @@
+/* 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(
+ "Test(crasher_subprocess_tail.js:shouldDelay)",
+ () => 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..c37c8acf8c
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/head_crashreporter.js
@@ -0,0 +1,347 @@
+const { makeFakeAppDir } = ChromeUtils.importESModule(
+ "resource://testing-common/AppData.sys.mjs"
+);
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+function getEventDir() {
+ return PathUtils.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 crashD = do_get_tempdir();
+ crashD.append("crash-events");
+ if (!crashD.exists()) {
+ crashD.create(crashD.DIRECTORY_TYPE, 0o700);
+ }
+
+ Services.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 {
+ Services.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 getMinidumpAnalyzerPath() {
+ const binSuffix = AppConstants.platform === "win" ? ".exe" : "";
+ const exeName = "minidump-analyzer" + binSuffix;
+
+ let exe = Services.dirsvc.get("GreBinD", Ci.nsIFile);
+ exe.append(exeName);
+
+ return exe;
+}
+
+function runMinidumpAnalyzer(dumpFile, additionalArgs) {
+ let bin = getMinidumpAnalyzerPath();
+ 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 = async function () {
+ for (let file of [minidump, extrafile, memoryfile]) {
+ while (file.exists()) {
+ try {
+ file.remove(false);
+ } catch (e) {
+ // On Windows the file may be locked, wait briefly and try again
+ await new Promise(resolve => do_timeout(50, resolve));
+ }
+ }
+ }
+ };
+
+ // Just in case, don't let these files linger.
+ registerCleanupFunction(cleanup);
+
+ Assert.ok(extrafile.exists());
+ let extra = await IOUtils.readJSON(extrafile.path);
+
+ if (callback) {
+ await callback(minidump, extra, extrafile, memoryfile);
+ }
+
+ await 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.
+ Services.appinfo.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.
+ Services.appinfo.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);
+ }
+}
+
+/*
+ * Run the `crash` backgroundtask subprocess, crashing it in the
+ * specified manner.
+ *
+ * @param crashType Integer `CrashTestUtils.CRASH_...` code.
+ * @param crashExtras Dictionary of key-value pairs to include in
+ * minidump extras.
+ *
+ * @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_backgroundtask_crash(
+ crashType,
+ crashExtras,
+ callback,
+ canReturnZero
+) {
+ Assert.ok(AppConstants.MOZ_BACKGROUNDTASKS);
+
+ // Get full path to application (not xpcshell)
+ let bin = Services.dirsvc.get("GreBinD", Ci.nsIFile);
+ if (AppConstants.platform === "win") {
+ bin.append(AppConstants.MOZ_APP_NAME + ".exe");
+ } else {
+ bin.append(AppConstants.MOZ_APP_NAME);
+ }
+
+ // run `application --backgroundtask crash ...`.
+ let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ process.init(bin);
+
+ let args = ["--backgroundtask", "crash"];
+ args.push(crashType.toString());
+
+ // Sorted to be deterministic.
+ let sorted = Object.entries(crashExtras).sort((a, b) => a[0] < b[0]);
+ for (let [key, value] of sorted) {
+ args.push(key);
+ args.push(value);
+ }
+
+ let crashD = do_get_tempdir();
+ crashD.append("crash-events");
+ if (!crashD.exists()) {
+ crashD.create(crashD.DIRECTORY_TYPE, 0o700);
+ }
+
+ Services.env.set("CRASHES_EVENTS_DIR", crashD.path);
+
+ // Ensure `resource://testing-common` gets mapped.
+ let protocolHandler = Services.io
+ .getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler);
+
+ let uri = protocolHandler.getSubstitution("testing-common");
+ Assert.ok(uri, "resource://testing-common is not substituted");
+
+ // The equivalent of _TESTING_MODULES_DIR in xpcshell.
+ Services.env.set("XPCSHELL_TESTING_MODULES_URI", uri.spec);
+
+ try {
+ process.run(true, args, args.length);
+ } catch (ex) {
+ // on Windows we exit with a -1 status when crashing.
+ } finally {
+ Services.env.set("CRASHES_EVENTS_DIR", "");
+ Services.env.set("XPCSHELL_TESTING_MODULES_URI", "");
+ }
+
+ if (!canReturnZero) {
+ // should exit with an error (should have crashed)
+ Assert.notEqual(process.exitValue, 0);
+ }
+
+ await handleMinidump(callback);
+}
+
+// Import binary APIs via js-ctypes.
+var { CrashTestUtils } = ChromeUtils.importESModule(
+ "resource://test/CrashTestUtils.sys.mjs"
+);
diff --git a/toolkit/crashreporter/test/unit/head_win64cfi.js b/toolkit/crashreporter/test/unit/head_win64cfi.js
new file mode 100644
index 0000000000..4df99213db
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/head_win64cfi.js
@@ -0,0 +1,215 @@
+/* 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
+ extra = await IOUtils.readJSON(extraFile.path);
+
+ 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..66cc937f28
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_AsyncShutdown.js
@@ -0,0 +1,75 @@
+/* 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() {
+ const { AsyncShutdown } = ChromeUtils.importESModule(
+ "resource://gre/modules/AsyncShutdown.sys.mjs"
+ );
+ const { PromiseUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/PromiseUtils.sys.mjs"
+ );
+
+ 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 = PromiseUtils.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 + IOUtils reports errors correctly.,
+
+function setup_ioutils_crash() {
+ const { PromiseUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/PromiseUtils.sys.mjs"
+ );
+
+ Services.prefs.setIntPref("toolkit.asyncshutdown.crash_timeout", 1);
+
+ IOUtils.profileBeforeChange.addBlocker(
+ "Adding a blocker that will never be resolved",
+ () => PromiseUtils.defer().promise
+ );
+
+ Services.startup.advanceShutdownPhase(
+ Services.startup.SHUTDOWN_PHASE_APPSHUTDOWN
+ );
+ dump("Waiting for crash\n");
+}
+
+function after_ioutils_crash(mdump, extra) {
+ info("after IOUtils crash: " + extra.AsyncShutdownTimeout);
+ let data = JSON.parse(extra.AsyncShutdownTimeout);
+ Assert.equal(
+ data.phase,
+ "IOUtils: waiting for profileBeforeChange IO to complete"
+ );
+}
+
+add_task(async function run_test() {
+ await do_crash(setup_crash, after_crash);
+ await do_crash(setup_ioutils_crash, after_ioutils_crash);
+});
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..aa602dacb4
--- /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..611ba9b6ac
--- /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..cdae0f2d8e
--- /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..7b2491bc0b
--- /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..8796aee3c0
--- /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..ba67981444
--- /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_backgroundtask_moz_crash.js b/toolkit/crashreporter/test/unit/test_crash_backgroundtask_moz_crash.js
new file mode 100644
index 0000000000..847e260925
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_backgroundtask_moz_crash.js
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(async function run_test() {
+ if (!AppConstants.MOZ_BACKGROUNDTASKS) {
+ return;
+ }
+
+ // Try crashing background task with a runtime abort
+ await do_backgroundtask_crash(
+ CrashTestUtils.CRASH_MOZ_CRASH,
+ { TestKey: "TestValue" },
+ function (mdump, extra) {
+ Assert.equal(extra.TestKey, "TestValue");
+ Assert.equal(extra.BackgroundTaskMode, "1");
+ Assert.equal(extra.BackgroundTaskName, "crash");
+ Assert.equal(extra.HeadlessMode, "1");
+ 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_exc_guard.js b/toolkit/crashreporter/test/unit/test_crash_exc_guard.js
new file mode 100644
index 0000000000..f7daffe68c
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_exc_guard.js
@@ -0,0 +1,30 @@
+add_task(async function run_test() {
+ if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
+ dump(
+ "INFO | test_crash_exc_guard.js | Can't test crashreporter in a non-libxul build.\n"
+ );
+ return;
+ }
+
+ // Try crashing by closing a guarded file descriptor
+ await do_crash(
+ function () {
+ crashType = CrashTestUtils.CRASH_EXC_GUARD;
+ crashReporter.annotateCrashReport("TestKey", "TestValue");
+ },
+ async function (mdump, extra, extraFile) {
+ runMinidumpAnalyzer(mdump);
+
+ // Refresh updated extra data
+ extra = await IOUtils.readJSON(extraFile.path);
+
+ Assert.equal(
+ extra.StackTraces.crash_info.type,
+ "EXC_GUARD / GUARD_TYPE_FD / GUARD_EXC_CLOSE"
+ );
+ Assert.equal(extra.TestKey, "TestValue");
+ },
+ // process will exit with a zero exit status
+ 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..0530b8b074
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_heap_corruption.js
@@ -0,0 +1,27 @@
+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
+ extra = await IOUtils.readJSON(extraFile.path);
+
+ Assert.equal(extra.StackTraces.crash_info.type, "STATUS_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_modules.js b/toolkit/crashreporter/test/unit/test_crash_modules.js
new file mode 100644
index 0000000000..dd14e77dab
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_modules.js
@@ -0,0 +1,44 @@
+add_task(async function run_test() {
+ if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
+ dump(
+ "INFO | test_crash_modules.js | Can't test crashreporter in a non-libxul build.\n"
+ );
+ return;
+ }
+
+ await do_crash(
+ function () {
+ const { ctypes } = ChromeUtils.importESModule(
+ "resource://gre/modules/ctypes.sys.mjs"
+ );
+ // Load and unload a DLL so that it will show up as unloaded in the minidump
+ let lib = ctypes.open("wininet");
+ lib.close();
+ },
+ async function (mdump, extra, extraFile) {
+ runMinidumpAnalyzer(mdump);
+
+ // Refresh updated extra data
+ extra = await IOUtils.readJSON(extraFile.path);
+
+ // Check unloaded modules
+ const unloadedModules = extra.StackTraces.unloaded_modules;
+ Assert.ok(!!unloadedModules, "The unloaded_modules field exists");
+ Assert.notEqual(unloadedModules.find(e => e.filename == "wininet.DLL"));
+
+ // Check the module signature information
+ const sigInfo = JSON.parse(extra.ModuleSignatureInfo);
+ Assert.ok(
+ !!sigInfo["Microsoft Windows"],
+ "The module signature info contains valid data"
+ );
+ Assert.greater(
+ sigInfo["Microsoft Windows"].length,
+ 0,
+ "Multiple signed binaries were found"
+ );
+ },
+ // 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..6016957d44
--- /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..3b32da4ab8
--- /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..54bc8ec34e
--- /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..e482eaa415
--- /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..dff1d6281a
--- /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..3908c76a2e
--- /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_stack_overflow.js b/toolkit/crashreporter/test/unit/test_crash_stack_overflow.js
new file mode 100644
index 0000000000..a47c217238
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_stack_overflow.js
@@ -0,0 +1,21 @@
+add_task(async function run_test() {
+ if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
+ dump(
+ "INFO | test_crash_stack_overflow.js | Can't test crashreporter in a non-libxul build.\n"
+ );
+ return;
+ }
+
+ // Try crashing by overflowing a thread's stack
+ await do_crash(
+ function () {
+ crashType = CrashTestUtils.CRASH_STACK_OVERFLOW;
+ crashReporter.annotateCrashReport("TestKey", "TestValue");
+ },
+ async function (mdump, extra, extraFile) {
+ Assert.equal(extra.TestKey, "TestValue");
+ },
+ // 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..c051a7e277
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_terminator.js
@@ -0,0 +1,39 @@
+/* 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() {
+ Services.prefs.setBoolPref("toolkit.terminator.testing", true);
+ Services.prefs.setIntPref("toolkit.asyncshutdown.crash_timeout", 150);
+
+ // 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, "terminator-test-profile-after-change", null);
+
+ // Inform the terminator that shutdown has started
+ // Pick an arbitrary notification
+ terminator.observe(null, "terminator-test-profile-before-change", null);
+ terminator.observe(null, "terminator-test-xpcom-will-shutdown", null);
+
+ dump("Waiting (actively) for the crash\n");
+ Services.tm.spinEventLoopUntil(
+ "Test(test_crash_terminator.js:setup_crash())",
+ () => false
+ );
+}
+
+function after_crash(mdump, extra) {
+ info("Crash signature: " + JSON.stringify(extra, null, "\t"));
+ Assert.equal(extra.ShutdownProgress, "xpcom-will-shutdown");
+}
+
+add_task(async function run_test() {
+ await do_crash(setup_crash, after_crash);
+});
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..bfcd2b0ef2
--- /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.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.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.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.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..e10e6697df
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_with_memory_report.js
@@ -0,0 +1,49 @@
+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 profd = Services.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, extrafile, memoryfile) {
+ Assert.ok(memoryfile.exists());
+ },
+ 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..d1a8b10316
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crashreporter.js
@@ -0,0 +1,90 @@
+function run_test() {
+ dump("INFO | test_crashreporter.js | Get crashreporter service.\n");
+ var cr = Services.appinfo;
+ Assert.ok(Services.appinfo.crashReporterEnabled);
+
+ 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..f42fe10f0c
--- /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..4d0abbc2ef
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crashreporter_crash.js
@@ -0,0 +1,223 @@
+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 { TelemetryController } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetryController.sys.mjs"
+ );
+ TelemetryController.testSetup();
+ },
+ function (mdump, extra) {
+ Assert.equal(extra.TestKey, "TestValue");
+ Assert.equal(extra.TestUnicode, "\u{1F4A9}\n\u{0000}Escape");
+ Assert.ok(
+ extra.Notes.endsWith("JunkMoreJunk"),
+ "Should include our notes"
+ );
+ 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 { TelemetryController } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetryController.sys.mjs"
+ );
+ let { TelemetrySend } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetrySend.sys.mjs"
+ );
+ TelemetrySend.setTestModeEnabled(true);
+ 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 { TelemetryController } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetryController.sys.mjs"
+ );
+ let { TelemetrySend } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetrySend.sys.mjs"
+ );
+ TelemetrySend.setTestModeEnabled(true);
+ 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 { TelemetryController } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetryController.sys.mjs"
+ );
+ let { TelemetrySend } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetrySend.sys.mjs"
+ );
+ TelemetrySend.setTestModeEnabled(true);
+ 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..844d7700ff
--- /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";
+
+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 { TelemetryController } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetryController.sys.mjs"
+ );
+ 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.processTypes[Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT],
+ 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..3d8830bd22
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_kill.js
@@ -0,0 +1,43 @@
+// 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.
+ Services.appinfo.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.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+ );
+ // 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..029497429a
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_oom_annotation.js
@@ -0,0 +1,71 @@
+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) {
+ const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+ );
+ 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 = [
+ ["OOMAllocationSize", true],
+ ["AvailablePageFile", false],
+ ["AvailablePhysicalMemory", false],
+ ["AvailableSwapMemory", false],
+ ["AvailableVirtualMemory", false],
+ ["TotalPageFile", false],
+ ["TotalPhysicalMemory", true],
+ ];
+ break;
+ case "macosx":
+ annotations = [
+ ["OOMAllocationSize", true],
+ ["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..1b4dec8a61
--- /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..d1d0ada544
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/xpcshell-phc.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+head = head_crashreporter.js
+skip-if =
+ toolkit == "android" # 1536217
+ os == "win" && msix # https://bugzilla.mozilla.org/show_bug.cgi?id=1807922
+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..8ba9e894ba
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/xpcshell.ini
@@ -0,0 +1,131 @@
+[DEFAULT]
+head = head_crashreporter.js
+skip-if =
+ toolkit == 'android'
+ os == 'win' && msix # https://bugzilla.mozilla.org/show_bug.cgi?id=1807922
+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]
+run-sequentially = very high failure rate in parallel
+[test_kill.js]
+
+[test_crash_abort.js]
+skip-if = os == 'win'
+
+[test_crash_uncaught_exception.js]
+
+[test_crash_with_memory_report.js]
+[test_crashreporter.js]
+[test_crashreporter_crash.js]
+run-sequentially = very high failure rate in parallel
+[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_backgroundtask_moz_crash.js]
+
+[test_crash_heap_corruption.js]
+skip-if = os != 'win'
+reason = Test covering Windows-specific crash type
+run-sequentially = very high failure rate in parallel
+
+[test_crash_exc_guard.js]
+skip-if = os != 'mac'
+reason = Test covering macOS-specific crash type
+
+[test_crash_modules.js]
+skip-if = os != 'win'
+reason = Test covering Windows-specific module handling
+run-sequentially = very high failure rate in parallel
+
+[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
+
+[test_crash_stack_overflow.js]
+skip-if = os != 'linux'
+reason = Still broken on macOS and not yet supported on Windows