1 //===- llvm/unittest/Support/CrashRecoveryTest.cpp ------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "llvm/ADT/Triple.h" 10 #include "llvm/Config/config.h" 11 #include "llvm/Support/CommandLine.h" 12 #include "llvm/Support/Compiler.h" 13 #include "llvm/Support/CrashRecoveryContext.h" 14 #include "llvm/Support/FileSystem.h" 15 #include "llvm/Support/Host.h" 16 #include "llvm/Support/Program.h" 17 #include "llvm/Support/Signals.h" 18 #include "llvm/Support/raw_ostream.h" 19 #include "gtest/gtest.h" 20 21 #ifdef _WIN32 22 #define WIN32_LEAN_AND_MEAN 23 #define NOGDI 24 #include <windows.h> 25 #endif 26 27 #ifdef LLVM_ON_UNIX 28 #ifdef HAVE_SIGNAL_H 29 #include <signal.h> 30 #endif 31 #endif 32 33 using namespace llvm; 34 using namespace llvm::sys; 35 36 static int GlobalInt = 0; 37 static void nullDeref() { *(volatile int *)0x10 = 0; } 38 static void incrementGlobal() { ++GlobalInt; } 39 static void llvmTrap() { LLVM_BUILTIN_TRAP; } 40 static void incrementGlobalWithParam(void *) { ++GlobalInt; } 41 42 TEST(CrashRecoveryTest, Basic) { 43 llvm::CrashRecoveryContext::Enable(); 44 GlobalInt = 0; 45 EXPECT_TRUE(CrashRecoveryContext().RunSafely(incrementGlobal)); 46 EXPECT_EQ(1, GlobalInt); 47 EXPECT_FALSE(CrashRecoveryContext().RunSafely(nullDeref)); 48 EXPECT_FALSE(CrashRecoveryContext().RunSafely(llvmTrap)); 49 } 50 51 struct IncrementGlobalCleanup : CrashRecoveryContextCleanup { 52 IncrementGlobalCleanup(CrashRecoveryContext *CRC) 53 : CrashRecoveryContextCleanup(CRC) {} 54 void recoverResources() override { ++GlobalInt; } 55 }; 56 57 static void noop() {} 58 59 TEST(CrashRecoveryTest, Cleanup) { 60 llvm::CrashRecoveryContext::Enable(); 61 GlobalInt = 0; 62 { 63 CrashRecoveryContext CRC; 64 CRC.registerCleanup(new IncrementGlobalCleanup(&CRC)); 65 EXPECT_TRUE(CRC.RunSafely(noop)); 66 } // run cleanups 67 EXPECT_EQ(1, GlobalInt); 68 69 GlobalInt = 0; 70 { 71 CrashRecoveryContext CRC; 72 CRC.registerCleanup(new IncrementGlobalCleanup(&CRC)); 73 EXPECT_FALSE(CRC.RunSafely(nullDeref)); 74 } // run cleanups 75 EXPECT_EQ(1, GlobalInt); 76 llvm::CrashRecoveryContext::Disable(); 77 } 78 79 TEST(CrashRecoveryTest, DumpStackCleanup) { 80 SmallString<128> Filename; 81 std::error_code EC = sys::fs::createTemporaryFile("crash", "test", Filename); 82 EXPECT_FALSE(EC); 83 sys::RemoveFileOnSignal(Filename); 84 llvm::sys::AddSignalHandler(incrementGlobalWithParam, nullptr); 85 GlobalInt = 0; 86 llvm::CrashRecoveryContext::Enable(); 87 { 88 CrashRecoveryContext CRC; 89 CRC.DumpStackAndCleanupOnFailure = true; 90 EXPECT_TRUE(CRC.RunSafely(noop)); 91 } 92 EXPECT_TRUE(sys::fs::exists(Filename)); 93 EXPECT_EQ(GlobalInt, 0); 94 { 95 CrashRecoveryContext CRC; 96 CRC.DumpStackAndCleanupOnFailure = true; 97 EXPECT_FALSE(CRC.RunSafely(nullDeref)); 98 EXPECT_NE(CRC.RetCode, 0); 99 } 100 EXPECT_FALSE(sys::fs::exists(Filename)); 101 EXPECT_EQ(GlobalInt, 1); 102 llvm::CrashRecoveryContext::Disable(); 103 } 104 105 TEST(CrashRecoveryTest, LimitedStackTrace) { 106 std::string Res; 107 llvm::raw_string_ostream RawStream(Res); 108 PrintStackTrace(RawStream, 1); 109 std::string Str = RawStream.str(); 110 // FIXME: Handle "Depth" parameter in PrintStackTrace() function 111 // to print stack trace upto a specified Depth. 112 if (!Triple(sys::getProcessTriple()).isOSWindows()) { 113 EXPECT_EQ(std::string::npos, Str.find("#1")); 114 } 115 } 116 117 #ifdef _WIN32 118 static void raiseIt() { 119 RaiseException(123, EXCEPTION_NONCONTINUABLE, 0, NULL); 120 } 121 122 TEST(CrashRecoveryTest, RaiseException) { 123 llvm::CrashRecoveryContext::Enable(); 124 EXPECT_FALSE(CrashRecoveryContext().RunSafely(raiseIt)); 125 } 126 127 static void outputString() { 128 OutputDebugStringA("output for debugger\n"); 129 } 130 131 TEST(CrashRecoveryTest, CallOutputDebugString) { 132 llvm::CrashRecoveryContext::Enable(); 133 EXPECT_TRUE(CrashRecoveryContext().RunSafely(outputString)); 134 } 135 136 TEST(CrashRecoveryTest, Abort) { 137 llvm::CrashRecoveryContext::Enable(); 138 auto A = []() { abort(); }; 139 EXPECT_FALSE(CrashRecoveryContext().RunSafely(A)); 140 // Test a second time to ensure we reinstall the abort signal handler. 141 EXPECT_FALSE(CrashRecoveryContext().RunSafely(A)); 142 } 143 #endif 144 145 // Specifically ensure that programs that signal() or abort() through the 146 // CrashRecoveryContext can re-throw again their signal, so that `not --crash` 147 // succeeds. 148 #ifdef LLVM_ON_UNIX 149 // See llvm/utils/unittest/UnitTestMain/TestMain.cpp 150 extern const char *TestMainArgv0; 151 152 // Just a reachable symbol to ease resolving of the executable's path. 153 static cl::opt<std::string> CrashTestStringArg1("crash-test-string-arg1"); 154 155 TEST(CrashRecoveryTest, UnixCRCReturnCode) { 156 using namespace llvm::sys; 157 if (getenv("LLVM_CRC_UNIXCRCRETURNCODE")) { 158 llvm::CrashRecoveryContext::Enable(); 159 CrashRecoveryContext CRC; 160 EXPECT_FALSE(CRC.RunSafely(abort)); 161 EXPECT_EQ(CRC.RetCode, 128 + SIGABRT); 162 // re-throw signal 163 llvm::sys::unregisterHandlers(); 164 raise(CRC.RetCode - 128); 165 llvm_unreachable("Should have exited already!"); 166 } 167 168 std::string Executable = 169 sys::fs::getMainExecutable(TestMainArgv0, &CrashTestStringArg1); 170 StringRef argv[] = { 171 Executable, "--gtest_filter=CrashRecoveryTest.UnixCRCReturnCode"}; 172 173 // Add LLVM_CRC_UNIXCRCRETURNCODE to the environment of the child process. 174 int Res = setenv("LLVM_CRC_UNIXCRCRETURNCODE", "1", 0); 175 ASSERT_EQ(Res, 0); 176 177 std::string Error; 178 bool ExecutionFailed; 179 int RetCode = ExecuteAndWait(Executable, argv, {}, {}, 0, 0, &Error, 180 &ExecutionFailed); 181 ASSERT_EQ(-2, RetCode); 182 } 183 #endif 184