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