// Copyright 2012, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <windows.h> #include <string> #include "breakpad_googletest_includes.h" #include "client/windows/handler/exception_handler.h" #include "client/windows/unittests/exception_handler_test.h" namespace { const char kFoo[] = "foo"; const char kBar[] = "bar"; const char kStartOfLine[] = "^"; const char kEndOfLine[] = "$"; const char kFilterReturnsTrue[] = "filter_returns_true"; const char kFilterReturnsFalse[] = "filter_returns_false"; const char kCallbackReturnsTrue[] = "callback_returns_true"; const char kCallbackReturnsFalse[] = "callback_returns_false"; bool DoesPathExist(const wchar_t *path_name) { DWORD flags = GetFileAttributes(path_name); if (flags == INVALID_FILE_ATTRIBUTES) { return false; } return true; } // A callback function to run before Breakpad performs any substantial // processing of an exception. A FilterCallback is called before writing // a minidump. context is the parameter supplied by the user as // callback_context when the handler was created. exinfo points to the // exception record, if any; assertion points to assertion information, // if any. // // If a FilterCallback returns true, Breakpad will continue processing, // attempting to write a minidump. If a FilterCallback returns false, // Breakpad will immediately report the exception as unhandled without // writing a minidump, allowing another handler the opportunity to handle it. template <bool filter_return_value> bool CrashHandlerFilter(void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion) { if (filter_return_value) { fprintf(stderr, kFilterReturnsTrue); } else { fprintf(stderr, kFilterReturnsFalse); } fflush(stderr); return filter_return_value; } // A callback function to run after the minidump has been written. // minidump_id is a unique id for the dump, so the minidump // file is <dump_path>\<minidump_id>.dmp. context is the parameter supplied // by the user as callback_context when the handler was created. exinfo // points to the exception record, or NULL if no exception occurred. // succeeded indicates whether a minidump file was successfully written. // assertion points to information about an assertion if the handler was // invoked by an assertion. // // If an exception occurred and the callback returns true, Breakpad will treat // the exception as fully-handled, suppressing any other handlers from being // notified of the exception. If the callback returns false, Breakpad will // treat the exception as unhandled, and allow another handler to handle it. // If there are no other handlers, Breakpad will report the exception to the // system as unhandled, allowing a debugger or native crash dialog the // opportunity to handle the exception. Most callback implementations // should normally return the value of |succeeded|, or when they wish to // not report an exception of handled, false. Callbacks will rarely want to // return true directly (unless |succeeded| is true). // // For out-of-process dump generation, dump path and minidump ID will always // be NULL. In case of out-of-process dump generation, the dump path and // minidump id are controlled by the server process and are not communicated // back to the crashing process. template <bool callback_return_value> bool MinidumpWrittenCallback(const wchar_t* dump_path, const wchar_t* minidump_id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool succeeded) { bool rv = false; if (callback_return_value && succeeded && DoesPathExist(dump_path)) { rv = true; fprintf(stderr, kCallbackReturnsTrue); } else { fprintf(stderr, kCallbackReturnsFalse); } fflush(stderr); return rv; } void DoCrash(const char *message) { if (message) { fprintf(stderr, "%s", message); fflush(stderr); } int *i = NULL; (*i)++; ASSERT_TRUE(false); } void InstallExceptionHandlerAndCrash(bool install_filter, bool filter_return_value, bool install_callback, bool callback_return_value) { wchar_t temp_path[MAX_PATH] = { '\0' }; GetTempPath(MAX_PATH, temp_path); ASSERT_TRUE(DoesPathExist(temp_path)); google_breakpad::ExceptionHandler exc( temp_path, install_filter ? (filter_return_value ? &CrashHandlerFilter<true> : &CrashHandlerFilter<false>) : NULL, install_callback ? (callback_return_value ? &MinidumpWrittenCallback<true> : &MinidumpWrittenCallback<false>) : NULL, NULL, // callback_context google_breakpad::ExceptionHandler::HANDLER_EXCEPTION); // Disable GTest SEH handler testing::DisableExceptionHandlerInScope disable_exception_handler; DoCrash(NULL); } TEST(AssertDeathSanity, Simple) { ASSERT_DEATH(DoCrash(NULL), ""); } TEST(AssertDeathSanity, Regex) { ASSERT_DEATH(DoCrash(kFoo), std::string(kStartOfLine) + std::string(kFoo) + std::string(kEndOfLine)); ASSERT_DEATH(DoCrash(kBar), std::string(kStartOfLine) + std::string(kBar) + std::string(kEndOfLine)); } TEST(ExceptionHandlerCallbacks, FilterTrue_No_Callback) { ASSERT_DEATH( InstallExceptionHandlerAndCrash(true, // install_filter true, // filter_return_value false, // install_callback false), // callback_return_value std::string(kStartOfLine) + std::string(kFilterReturnsTrue) + std::string(kEndOfLine)); } TEST(ExceptionHandlerCallbacks, FilterTrue_Callback) { ASSERT_DEATH( InstallExceptionHandlerAndCrash(true, // install_filter true, // filter_return_value true, // install_callback false), // callback_return_value std::string(kStartOfLine) + std::string(kFilterReturnsTrue) + std::string(kCallbackReturnsFalse) + std::string(kEndOfLine)); } TEST(ExceptionHandlerCallbacks, FilterFalse_No_Callback) { ASSERT_DEATH( InstallExceptionHandlerAndCrash(true, // install_filter false, // filter_return_value false, // install_callback false), // callback_return_value std::string(kStartOfLine) + std::string(kFilterReturnsFalse) + std::string(kEndOfLine)); } // Callback shouldn't be executed when filter returns false TEST(ExceptionHandlerCallbacks, FilterFalse_Callback) { ASSERT_DEATH( InstallExceptionHandlerAndCrash(true, // install_filter false, // filter_return_value true, // install_callback false), // callback_return_value std::string(kStartOfLine) + std::string(kFilterReturnsFalse) + std::string(kEndOfLine)); } TEST(ExceptionHandlerCallbacks, No_Filter_No_Callback) { ASSERT_DEATH( InstallExceptionHandlerAndCrash(false, // install_filter true, // filter_return_value false, // install_callback false), // callback_return_value std::string(kStartOfLine) + std::string(kEndOfLine)); } TEST(ExceptionHandlerCallbacks, No_Filter_Callback) { ASSERT_DEATH( InstallExceptionHandlerAndCrash(false, // install_filter true, // filter_return_value true, // install_callback false), // callback_return_value std::string(kStartOfLine) + std::string(kCallbackReturnsFalse) + std::string(kEndOfLine)); } TEST(ExceptionHandlerNesting, Skip_From_Inner_Filter) { wchar_t temp_path[MAX_PATH] = { '\0' }; GetTempPath(MAX_PATH, temp_path); ASSERT_TRUE(DoesPathExist(temp_path)); google_breakpad::ExceptionHandler exc( temp_path, &CrashHandlerFilter<true>, &MinidumpWrittenCallback<false>, NULL, // callback_context google_breakpad::ExceptionHandler::HANDLER_EXCEPTION); ASSERT_DEATH( InstallExceptionHandlerAndCrash(true, // install_filter false, // filter_return_value true, // install_callback true), // callback_return_value std::string(kStartOfLine) + std::string(kFilterReturnsFalse) + // inner filter std::string(kFilterReturnsTrue) + // outer filter std::string(kCallbackReturnsFalse) + // outer callback std::string(kEndOfLine)); } TEST(ExceptionHandlerNesting, Skip_From_Inner_Callback) { wchar_t temp_path[MAX_PATH] = { '\0' }; GetTempPath(MAX_PATH, temp_path); ASSERT_TRUE(DoesPathExist(temp_path)); google_breakpad::ExceptionHandler exc( temp_path, &CrashHandlerFilter<true>, &MinidumpWrittenCallback<false>, NULL, // callback_context google_breakpad::ExceptionHandler::HANDLER_EXCEPTION); ASSERT_DEATH( InstallExceptionHandlerAndCrash(true, // install_filter true, // filter_return_value true, // install_callback false), // callback_return_value std::string(kStartOfLine) + std::string(kFilterReturnsTrue) + // inner filter std::string(kCallbackReturnsFalse) + // inner callback std::string(kFilterReturnsTrue) + // outer filter std::string(kCallbackReturnsFalse) + // outer callback std::string(kEndOfLine)); } TEST(ExceptionHandlerNesting, Handled_By_Inner_Handler) { wchar_t temp_path[MAX_PATH] = { '\0' }; GetTempPath(MAX_PATH, temp_path); ASSERT_TRUE(DoesPathExist(temp_path)); google_breakpad::ExceptionHandler exc( temp_path, &CrashHandlerFilter<true>, &MinidumpWrittenCallback<true>, NULL, // callback_context google_breakpad::ExceptionHandler::HANDLER_EXCEPTION); ASSERT_DEATH( InstallExceptionHandlerAndCrash(true, // install_filter true, // filter_return_value true, // install_callback true), // callback_return_value std::string(kStartOfLine) + std::string(kFilterReturnsTrue) + // inner filter std::string(kCallbackReturnsTrue) + // inner callback std::string(kEndOfLine)); } } // namespace