exception-test.c++ (8682B)
1 // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors 2 // Licensed under the MIT License: 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a copy 5 // of this software and associated documentation files (the "Software"), to deal 6 // in the Software without restriction, including without limitation the rights 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 // copies of the Software, and to permit persons to whom the Software is 9 // furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 // THE SOFTWARE. 21 22 #include "exception.h" 23 #include "debug.h" 24 #include <kj/compat/gtest.h> 25 #include <stdexcept> 26 #include <stdint.h> 27 28 namespace kj { 29 namespace _ { // private 30 namespace { 31 32 TEST(Exception, TrimSourceFilename) { 33 #if _WIN32 34 EXPECT_TRUE(trimSourceFilename(__FILE__) == "kj/exception-test.c++" || 35 trimSourceFilename(__FILE__) == "kj\\exception-test.c++"); 36 #else 37 EXPECT_EQ(trimSourceFilename(__FILE__), "kj/exception-test.c++"); 38 #endif 39 } 40 41 TEST(Exception, RunCatchingExceptions) { 42 bool recovered = false; 43 Maybe<Exception> e = kj::runCatchingExceptions([&]() { 44 KJ_FAIL_ASSERT("foo") { 45 break; 46 } 47 recovered = true; 48 }); 49 50 #if KJ_NO_EXCEPTIONS 51 EXPECT_TRUE(recovered); 52 #else 53 EXPECT_FALSE(recovered); 54 #endif 55 56 KJ_IF_MAYBE(ex, e) { 57 EXPECT_EQ("foo", ex->getDescription()); 58 } else { 59 ADD_FAILURE() << "Expected exception"; 60 } 61 } 62 63 #if !KJ_NO_EXCEPTIONS 64 TEST(Exception, RunCatchingExceptionsStdException) { 65 Maybe<Exception> e = kj::runCatchingExceptions([&]() { 66 throw std::logic_error("foo"); 67 }); 68 69 KJ_IF_MAYBE(ex, e) { 70 EXPECT_EQ("std::exception: foo", ex->getDescription()); 71 } else { 72 ADD_FAILURE() << "Expected exception"; 73 } 74 } 75 76 TEST(Exception, RunCatchingExceptionsOtherException) { 77 Maybe<Exception> e = kj::runCatchingExceptions([&]() { 78 throw 123; 79 }); 80 81 KJ_IF_MAYBE(ex, e) { 82 #if __GNUC__ && !KJ_NO_RTTI 83 EXPECT_EQ("unknown non-KJ exception of type: int", ex->getDescription()); 84 #else 85 EXPECT_EQ("unknown non-KJ exception", ex->getDescription()); 86 #endif 87 } else { 88 ADD_FAILURE() << "Expected exception"; 89 } 90 } 91 #endif 92 93 #if !KJ_NO_EXCEPTIONS 94 // We skip this test when exceptions are disabled because making it no-exceptions-safe defeats 95 // the purpose of the test: recoverable exceptions won't throw inside a destructor in the first 96 // place. 97 98 class ThrowingDestructor: public UnwindDetector { 99 public: 100 ~ThrowingDestructor() noexcept(false) { 101 catchExceptionsIfUnwinding([]() { 102 KJ_FAIL_ASSERT("this is a test, not a real bug"); 103 }); 104 } 105 }; 106 107 TEST(Exception, UnwindDetector) { 108 // If no other exception is happening, ThrowingDestructor's destructor throws one. 109 Maybe<Exception> e = kj::runCatchingExceptions([&]() { 110 ThrowingDestructor t; 111 }); 112 113 KJ_IF_MAYBE(ex, e) { 114 EXPECT_EQ("this is a test, not a real bug", ex->getDescription()); 115 } else { 116 ADD_FAILURE() << "Expected exception"; 117 } 118 119 // If another exception is happening, ThrowingDestructor's destructor's exception is squelched. 120 e = kj::runCatchingExceptions([&]() { 121 ThrowingDestructor t; 122 KJ_FAIL_ASSERT("baz") { 123 break; 124 } 125 }); 126 127 KJ_IF_MAYBE(ex, e) { 128 EXPECT_EQ("baz", ex->getDescription()); 129 } else { 130 ADD_FAILURE() << "Expected exception"; 131 } 132 } 133 #endif 134 135 #if !__MINGW32__ // Inexplicably crashes when exception is thrown from constructor. 136 TEST(Exception, ExceptionCallbackMustBeOnStack) { 137 KJ_EXPECT_THROW_MESSAGE("must be allocated on the stack", new ExceptionCallback); 138 } 139 #endif // !__MINGW32__ 140 141 #if !KJ_NO_EXCEPTIONS 142 TEST(Exception, ScopeSuccessFail) { 143 bool success = false; 144 bool failure = false; 145 146 { 147 KJ_ON_SCOPE_SUCCESS(success = true); 148 KJ_ON_SCOPE_FAILURE(failure = true); 149 150 EXPECT_FALSE(success); 151 EXPECT_FALSE(failure); 152 } 153 154 EXPECT_TRUE(success); 155 EXPECT_FALSE(failure); 156 157 success = false; 158 failure = false; 159 160 try { 161 KJ_ON_SCOPE_SUCCESS(success = true); 162 KJ_ON_SCOPE_FAILURE(failure = true); 163 164 EXPECT_FALSE(success); 165 EXPECT_FALSE(failure); 166 167 throw 1; 168 } catch (int) {} 169 170 EXPECT_FALSE(success); 171 EXPECT_TRUE(failure); 172 } 173 #endif 174 175 #if __GNUG__ || defined(__clang__) 176 kj::String testStackTrace() __attribute__((noinline)); 177 #elif _MSC_VER 178 __declspec(noinline) kj::String testStackTrace(); 179 #endif 180 181 kj::String testStackTrace() { 182 // getStackTrace() normally skips its immediate caller, so we wrap it in another layer. 183 return getStackTrace(); 184 } 185 186 KJ_TEST("getStackTrace() returns correct line number, not line + 1") { 187 // Backtraces normally produce the return address of each stack frame, but that's usually the 188 // address immediately after the one that made the call. As a result, it used to be that stack 189 // traces often pointed to the line after the one that made a call, which was confusing. This 190 // checks that this bug is fixed. 191 // 192 // This is not a very robust test, because: 193 // 1) Since symbolic stack traces are not available in many situations (e.g. release builds 194 // lacking debug symbols, systems where addr2line isn't present, etc.), we only check that 195 // the stack trace does *not* contain the *wrong* value, rather than checking that it does 196 // contain the right one. 197 // 2) This test only detects the problem if the call instruction to testStackTrace() is the 198 // *last* instruction attributed to its line of code. Whether or not this is true seems to be 199 // dependent on obscure complier behavior. For example, below, it could only be the case if 200 // RVO is applied -- but in my testing, RVO does seem to be applied here. I tried several 201 // variations involving passing via an output parameter or a global variable rather than 202 // returning, but found some variations detected the problem and others didn't, essentially 203 // at random. 204 205 auto trace = testStackTrace(); 206 auto wrong = kj::str("exception-test.c++:", __LINE__); 207 208 KJ_ASSERT(strstr(trace.cStr(), wrong.cStr()) == nullptr, trace, wrong); 209 } 210 211 #if !KJ_NO_EXCEPTIONS 212 KJ_TEST("InFlightExceptionIterator works") { 213 bool caught = false; 214 try { 215 KJ_DEFER({ 216 try { 217 KJ_FAIL_ASSERT("bar"); 218 } catch (const kj::Exception& e) { 219 InFlightExceptionIterator iter; 220 KJ_IF_MAYBE(e2, iter.next()) { 221 KJ_EXPECT(e2 == &e, e2->getDescription()); 222 } else { 223 KJ_FAIL_EXPECT("missing first exception"); 224 } 225 226 KJ_IF_MAYBE(e2, iter.next()) { 227 KJ_EXPECT(e2->getDescription() == "foo", e2->getDescription()); 228 } else { 229 KJ_FAIL_EXPECT("missing second exception"); 230 } 231 232 KJ_EXPECT(iter.next() == nullptr, "more than two exceptions"); 233 234 caught = true; 235 } 236 }); 237 KJ_FAIL_ASSERT("foo"); 238 } catch (const kj::Exception& e) { 239 // expected 240 } 241 242 KJ_EXPECT(caught); 243 } 244 #endif 245 246 KJ_TEST("computeRelativeTrace") { 247 auto testCase = [](uint expectedPrefix, 248 ArrayPtr<const uintptr_t> trace, ArrayPtr<const uintptr_t> relativeTo) { 249 auto tracePtr = KJ_MAP(x, trace) { return (void*)x; }; 250 auto relativeToPtr = KJ_MAP(x, relativeTo) { return (void*)x; }; 251 252 auto result = computeRelativeTrace(tracePtr, relativeToPtr); 253 KJ_EXPECT(result.begin() == tracePtr.begin()); 254 255 KJ_EXPECT(result.size() == expectedPrefix, trace, relativeTo, result); 256 }; 257 258 testCase(8, 259 {1, 2, 3, 4, 5, 6, 7, 8}, 260 {8, 7, 6, 5, 4, 3, 2, 1}); 261 262 testCase(5, 263 {1, 2, 3, 4, 5, 6, 7, 8}, 264 {8, 7, 6, 5, 5, 6, 7, 8}); 265 266 testCase(5, 267 {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 268 {8, 7, 6, 5, 5, 6, 7, 8}); 269 270 testCase(5, 271 {1, 2, 3, 4, 5, 6, 7, 8, 6, 7, 8}, 272 {8, 7, 6, 5, 5, 6, 7, 8}); 273 274 testCase(9, 275 {1, 2, 3, 4, 5, 6, 7, 8, 5, 5, 6, 7, 8}, 276 {8, 7, 6, 5, 5, 6, 7, 8}); 277 278 testCase(5, 279 {1, 2, 3, 4, 5, 5, 6, 7, 8, 5, 6, 7, 8}, 280 {8, 7, 6, 5, 5, 6, 7, 8}); 281 282 testCase(5, 283 {1, 2, 3, 4, 5, 6, 7, 8}, 284 {8, 7, 6, 5, 5, 6, 7, 8, 7, 8}); 285 286 testCase(5, 287 {1, 2, 3, 4, 5, 6, 7, 8}, 288 {8, 7, 6, 5, 6, 7, 8, 7, 8}); 289 } 290 291 } // namespace 292 } // namespace _ (private) 293 } // namespace kj