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/Config/config.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/Program.h" |
15 | #include "llvm/Support/Signals.h" |
16 | #include "llvm/Support/raw_ostream.h" |
17 | #include "llvm/TargetParser/Host.h" |
18 | #include "llvm/TargetParser/Triple.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(cleanup: 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(cleanup: 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(Prefix: "crash" , Suffix: "test" , ResultPath&: Filename); |
82 | EXPECT_FALSE(EC); |
83 | sys::RemoveFileOnSignal(Filename); |
84 | llvm::sys::AddSignalHandler(FnPtr: incrementGlobalWithParam, Cookie: 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 | // FIXME: Handle "Depth" parameter in PrintStackTrace() function |
107 | // to print stack trace upto a specified Depth. |
108 | if (Triple(sys::getProcessTriple()).isOSWindows()) |
109 | GTEST_SKIP(); |
110 | std::string Res; |
111 | llvm::raw_string_ostream RawStream(Res); |
112 | PrintStackTrace(OS&: RawStream, Depth: 1); |
113 | std::string Str = RawStream.str(); |
114 | EXPECT_EQ(std::string::npos, Str.find("#1" )); |
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(name: "LLVM_CRC_UNIXCRCRETURNCODE" )) { |
158 | llvm::CrashRecoveryContext::Enable(); |
159 | CrashRecoveryContext CRC; |
160 | // This path runs in a subprocess that exits by signalling, so don't use |
161 | // the googletest macros to verify things as they won't report properly. |
162 | if (CRC.RunSafely(Fn: abort)) |
163 | llvm_unreachable("RunSafely returned true!" ); |
164 | if (CRC.RetCode != 128 + SIGABRT) |
165 | llvm_unreachable("Unexpected RetCode!" ); |
166 | // re-throw signal |
167 | llvm::sys::unregisterHandlers(); |
168 | raise(sig: CRC.RetCode - 128); |
169 | llvm_unreachable("Should have exited already!" ); |
170 | } |
171 | |
172 | std::string Executable = |
173 | sys::fs::getMainExecutable(argv0: TestMainArgv0, MainExecAddr: &CrashTestStringArg1); |
174 | StringRef argv[] = { |
175 | Executable, "--gtest_filter=CrashRecoveryTest.UnixCRCReturnCode" }; |
176 | |
177 | // Add LLVM_CRC_UNIXCRCRETURNCODE to the environment of the child process. |
178 | int Res = setenv(name: "LLVM_CRC_UNIXCRCRETURNCODE" , value: "1" , replace: 0); |
179 | ASSERT_EQ(Res, 0); |
180 | |
181 | Res = unsetenv(name: "GTEST_SHARD_INDEX" ); |
182 | ASSERT_EQ(Res, 0); |
183 | Res = unsetenv(name: "GTEST_TOTAL_SHARDS" ); |
184 | ASSERT_EQ(Res, 0); |
185 | |
186 | std::string Error; |
187 | bool ExecutionFailed; |
188 | int RetCode = ExecuteAndWait(Program: Executable, Args: argv, Env: {}, Redirects: {}, SecondsToWait: 0, MemoryLimit: 0, ErrMsg: &Error, |
189 | ExecutionFailed: &ExecutionFailed); |
190 | ASSERT_EQ(-2, RetCode); |
191 | } |
192 | #endif |
193 | |