xref: /llvm-project/llvm/unittests/Support/CrashRecoveryTest.cpp (revision f5314d15af4f4514103ea12c74cb208538b8bef5)
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