// Copyright 2012 Google LLC
//
// 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 LLC 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.

#ifdef HAVE_CONFIG_H
#include <config.h>  // Must come first
#endif

#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 = nullptr;
  (*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>) :
        nullptr,
      install_callback ?
        (callback_return_value ?
          &MinidumpWrittenCallback<true> :
          &MinidumpWrittenCallback<false>) :
        nullptr,
      nullptr,  // callback_context
      google_breakpad::ExceptionHandler::HANDLER_EXCEPTION);

  // Disable GTest SEH handler
  testing::DisableExceptionHandlerInScope disable_exception_handler;

  DoCrash(nullptr);
}

TEST(AssertDeathSanity, Simple) {
  ASSERT_DEATH(DoCrash(nullptr), "");
}

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>,
      nullptr,  // 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>,
      nullptr,  // 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>,
      nullptr,  // 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
