// Copyright 2009 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 <assert.h>
#include <windows.h>
#include <dbghelp.h>
#include <strsafe.h>
#include <objbase.h>
#include <shellapi.h>

#include <memory>
#include <string>

#include "breakpad_googletest_includes.h"
#include "client/windows/crash_generation/crash_generation_server.h"
#include "client/windows/handler/exception_handler.h"
#include "client/windows/unittests/exception_handler_test.h"
#include "common/windows/string_utils-inl.h"
#include "google_breakpad/processor/minidump.h"

namespace {

using std::wstring;
using namespace google_breakpad;

const wchar_t kPipeName[] = L"\\\\.\\pipe\\BreakpadCrashTest\\TestCaseServer";
const char kSuccessIndicator[] = "success";
const char kFailureIndicator[] = "failure";

// Utility function to test for a path's existence.
BOOL DoesPathExist(const TCHAR* path_name);

enum OutOfProcGuarantee {
  OUT_OF_PROC_GUARANTEED,
  OUT_OF_PROC_BEST_EFFORT,
};

class ExceptionHandlerDeathTest : public ::testing::Test {
 protected:
  // Member variable for each test that they can use
  // for temporary storage.
  TCHAR temp_path_[MAX_PATH];
  // Actually constructs a temp path name.
  virtual void SetUp();
  // A helper method that tests can use to crash.
  void DoCrashAccessViolation(const OutOfProcGuarantee out_of_proc_guarantee);
  void DoCrashPureVirtualCall();
};

void ExceptionHandlerDeathTest::SetUp() {
  const ::testing::TestInfo* const test_info =
    ::testing::UnitTest::GetInstance()->current_test_info();
  TCHAR temp_path[MAX_PATH] = { '\0' };
  TCHAR test_name_wide[MAX_PATH] = { '\0' };
  // We want the temporary directory to be what the OS returns
  // to us, + the test case name.
  GetTempPath(MAX_PATH, temp_path);
  // The test case name is exposed as a c-style string,
  // convert it to a wchar_t string.
  int dwRet = MultiByteToWideChar(CP_ACP, 0, test_info->name(),
                                  static_cast<int>(strlen(test_info->name())),
                                  test_name_wide,
                                  MAX_PATH);
  if (!dwRet) {
    assert(false);
  }
  StringCchPrintfW(temp_path_, MAX_PATH, L"%s%s", temp_path, test_name_wide);
  CreateDirectory(temp_path_, nullptr);
}

BOOL DoesPathExist(const TCHAR* path_name) {
  DWORD flags = GetFileAttributes(path_name);
  if (flags == INVALID_FILE_ATTRIBUTES) {
    return FALSE;
  }
  return TRUE;
}

bool MinidumpWrittenCallback(const wchar_t* dump_path,
                             const wchar_t* minidump_id,
                             void* context,
                             EXCEPTION_POINTERS* exinfo,
                             MDRawAssertionInfo* assertion,
                             bool succeeded) {
  if (succeeded && DoesPathExist(dump_path)) {
    fprintf(stderr, kSuccessIndicator);
  } else {
    fprintf(stderr, kFailureIndicator);
  }
  // If we don't flush, the output doesn't get sent before
  // this process dies.
  fflush(stderr);
  return succeeded;
}

TEST_F(ExceptionHandlerDeathTest, InProcTest) {
  // For the in-proc test, we just need to instantiate an exception
  // handler in in-proc mode, and crash.   Since the entire test is
  // reexecuted in the child process, we don't have to worry about
  // the semantics of the exception handler being inherited/not
  // inherited across CreateProcess().
  ASSERT_TRUE(DoesPathExist(temp_path_));
  std::unique_ptr<google_breakpad::ExceptionHandler> exc(
      new google_breakpad::ExceptionHandler(
          temp_path_,
          nullptr,
          &MinidumpWrittenCallback,
          nullptr,
          google_breakpad::ExceptionHandler::HANDLER_ALL));

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

  int* i = nullptr;
  ASSERT_DEATH((*i)++, kSuccessIndicator);
}

static bool gDumpCallbackCalled = false;

void clientDumpCallback(void* dump_context,
                        const google_breakpad::ClientInfo* client_info,
                        const std::wstring* dump_path) {
  gDumpCallbackCalled = true;
}

void ExceptionHandlerDeathTest::DoCrashAccessViolation(
    const OutOfProcGuarantee out_of_proc_guarantee) {
  std::unique_ptr<google_breakpad::ExceptionHandler> exc;

  if (out_of_proc_guarantee == OUT_OF_PROC_GUARANTEED) {
    google_breakpad::CrashGenerationClient* client =
        new google_breakpad::CrashGenerationClient(kPipeName,
                                                   MiniDumpNormal,
                                                   nullptr);  // custom_info
    ASSERT_TRUE(client->Register());
    exc.reset(new google_breakpad::ExceptionHandler(
        temp_path_,
        nullptr,   // filter
        nullptr,   // callback
        nullptr,   // callback_context
        google_breakpad::ExceptionHandler::HANDLER_ALL,
        client));
  } else {
    ASSERT_TRUE(out_of_proc_guarantee == OUT_OF_PROC_BEST_EFFORT);
    exc.reset(new google_breakpad::ExceptionHandler(
        temp_path_,
        nullptr,   // filter
        nullptr,   // callback
        nullptr,   // callback_context
        google_breakpad::ExceptionHandler::HANDLER_ALL,
        MiniDumpNormal,
        kPipeName,
        nullptr));  // custom_info
  }

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

  // Although this is executing in the child process of the death test,
  // if it's not true we'll still get an error rather than the crash
  // being expected.
  ASSERT_TRUE(exc->IsOutOfProcess());
  int* i = nullptr;
  printf("%d\n", (*i)++);
}

TEST_F(ExceptionHandlerDeathTest, OutOfProcTest) {
  // We can take advantage of a detail of google test here to save some
  // complexity in testing: when you do a death test, it actually forks.
  // So we can make the main test harness the crash generation server,
  // and call ASSERT_DEATH on a NULL dereference, it to expecting test
  // the out of process scenario, since it's happening in a different
  // process!  This is different from the above because, above, we pass
  // a NULL pipe name, and we also don't start a crash generation server.

  ASSERT_TRUE(DoesPathExist(temp_path_));
  std::wstring dump_path(temp_path_);
  google_breakpad::CrashGenerationServer server(
      kPipeName, nullptr, nullptr, nullptr, &clientDumpCallback, nullptr,
      nullptr, nullptr, nullptr, nullptr, true, &dump_path);

  // This HAS to be EXPECT_, because when this test case is executed in the
  // child process, the server registration will fail due to the named pipe
  // being the same.
  EXPECT_TRUE(server.Start());
  gDumpCallbackCalled = false;
  ASSERT_DEATH(this->DoCrashAccessViolation(OUT_OF_PROC_BEST_EFFORT), "");
  EXPECT_TRUE(gDumpCallbackCalled);
}

TEST_F(ExceptionHandlerDeathTest, OutOfProcGuaranteedTest) {
  // This is similar to the previous test (OutOfProcTest).  The only difference
  // is that in this test, the crash generation client is created and registered
  // with the crash generation server outside of the ExceptionHandler
  // constructor which allows breakpad users to opt out of the default
  // in-process dump generation when the registration with the crash generation
  // server fails.

  ASSERT_TRUE(DoesPathExist(temp_path_));
  std::wstring dump_path(temp_path_);
  google_breakpad::CrashGenerationServer server(
      kPipeName, nullptr, nullptr, nullptr, &clientDumpCallback, nullptr,
      nullptr, nullptr, nullptr, nullptr, true, &dump_path);

  // This HAS to be EXPECT_, because when this test case is executed in the
  // child process, the server registration will fail due to the named pipe
  // being the same.
  EXPECT_TRUE(server.Start());
  gDumpCallbackCalled = false;
  ASSERT_DEATH(this->DoCrashAccessViolation(OUT_OF_PROC_GUARANTEED), "");
  EXPECT_TRUE(gDumpCallbackCalled);
}

TEST_F(ExceptionHandlerDeathTest, InvalidParameterTest) {
  using google_breakpad::ExceptionHandler;

  ASSERT_TRUE(DoesPathExist(temp_path_));
  ExceptionHandler handler(temp_path_, nullptr, nullptr, nullptr,
                           ExceptionHandler::HANDLER_INVALID_PARAMETER);

  // Disable the message box for assertions
  _CrtSetReportMode(_CRT_ASSERT, 0);

  // Call with a bad argument. The invalid parameter will be swallowed
  // and a dump will be generated, the process will exit(0).
  ASSERT_EXIT(printf(nullptr), ::testing::ExitedWithCode(0), "");
}


struct PureVirtualCallBase {
  PureVirtualCallBase() {
    // We have to reinterpret so the linker doesn't get confused because the
    // method isn't defined.
    reinterpret_cast<PureVirtualCallBase*>(this)->PureFunction();
  }
  virtual ~PureVirtualCallBase() {}
  virtual void PureFunction() const = 0;
};
struct PureVirtualCall : public PureVirtualCallBase {
  PureVirtualCall() { PureFunction(); }
  virtual void PureFunction() const {}
};

void ExceptionHandlerDeathTest::DoCrashPureVirtualCall() {
  PureVirtualCall instance;
}

TEST_F(ExceptionHandlerDeathTest, PureVirtualCallTest) {
  using google_breakpad::ExceptionHandler;

  ASSERT_TRUE(DoesPathExist(temp_path_));
  ExceptionHandler handler(temp_path_, nullptr, nullptr, nullptr,
                           ExceptionHandler::HANDLER_PURECALL);

  // Disable the message box for assertions
  _CrtSetReportMode(_CRT_ASSERT, 0);

  // Calls a pure virtual function.
  EXPECT_EXIT(DoCrashPureVirtualCall(), ::testing::ExitedWithCode(0), "");
}

wstring find_minidump_in_directory(const wstring& directory) {
  wstring search_path = directory + L"\\*";
  WIN32_FIND_DATA find_data;
  HANDLE find_handle = FindFirstFileW(search_path.c_str(), &find_data);
  if (find_handle == INVALID_HANDLE_VALUE)
    return wstring();

  wstring filename;
  do {
    const wchar_t extension[] = L".dmp";
    const size_t extension_length = sizeof(extension) / sizeof(extension[0]) - 1;
    const size_t filename_length = wcslen(find_data.cFileName);
    if (filename_length > extension_length &&
    wcsncmp(extension,
            find_data.cFileName + filename_length - extension_length,
            extension_length) == 0) {
      filename = directory + L"\\" + find_data.cFileName;
      break;
    }
  } while (FindNextFile(find_handle, &find_data));
  FindClose(find_handle);
  return filename;
}

#ifndef ADDRESS_SANITIZER

TEST_F(ExceptionHandlerDeathTest, InstructionPointerMemory) {
  ASSERT_TRUE(DoesPathExist(temp_path_));
  std::unique_ptr<google_breakpad::ExceptionHandler> exc(
      new google_breakpad::ExceptionHandler(
          temp_path_,
          nullptr,
          nullptr,
          nullptr,
          google_breakpad::ExceptionHandler::HANDLER_ALL));

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

  // Get some executable memory.
  const uint32_t kMemorySize = 256;  // bytes
  const int kOffset = kMemorySize / 2;
  // This crashes with SIGILL on x86/x86-64/arm.
  const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
  char* memory = reinterpret_cast<char*>(VirtualAlloc(nullptr,
                                                      kMemorySize,
                                                      MEM_COMMIT | MEM_RESERVE,
                                                      PAGE_EXECUTE_READWRITE));
  ASSERT_TRUE(memory);

  // Write some instructions that will crash. Put them
  // in the middle of the block of memory, because the
  // minidump should contain 128 bytes on either side of the
  // instruction pointer.
  memcpy(memory + kOffset, instructions, sizeof(instructions));

  // Now execute the instructions, which should crash.
  typedef void (*void_function)(void);
  void_function memory_function =
      reinterpret_cast<void_function>(memory + kOffset);
  ASSERT_DEATH(memory_function(), "");

  // free the memory.
  VirtualFree(memory, 0, MEM_RELEASE);

  // Verify that the resulting minidump contains the memory around the IP
  wstring minidump_filename_wide = find_minidump_in_directory(temp_path_);
  ASSERT_FALSE(minidump_filename_wide.empty());
  string minidump_filename;
  ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(minidump_filename_wide,
                                                &minidump_filename));

  // Read the minidump. Locate the exception record and the
  // memory list, and then ensure that there is a memory region
  // in the memory list that covers at least 128 bytes on either
  // side of the instruction pointer from the exception record.
  {
    Minidump minidump(minidump_filename);
    ASSERT_TRUE(minidump.Read());

    MinidumpException* exception = minidump.GetException();
    MinidumpMemoryList* memory_list = minidump.GetMemoryList();
    ASSERT_TRUE(exception);
    ASSERT_TRUE(memory_list);
    ASSERT_LT((unsigned)0, memory_list->region_count());

    MinidumpContext* context = exception->GetContext();
    ASSERT_TRUE(context);

    uint64_t instruction_pointer;
    ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer));

    MinidumpMemoryRegion* region =
        memory_list->GetMemoryRegionForAddress(instruction_pointer);
    ASSERT_TRUE(region);

    EXPECT_LE(kMemorySize, region->GetSize());
    const uint8_t* bytes = region->GetMemory();
    ASSERT_TRUE(bytes);

    uint64_t ip_offset = instruction_pointer - region->GetBase();
    EXPECT_GE(region->GetSize() - kOffset, ip_offset);
    EXPECT_LE(kOffset, ip_offset);

    uint8_t prefix_bytes[kOffset];
    uint8_t suffix_bytes[kMemorySize - kOffset - sizeof(instructions)];
    memset(prefix_bytes, 0, sizeof(prefix_bytes));
    memset(suffix_bytes, 0, sizeof(suffix_bytes));
    EXPECT_EQ(0, memcmp(bytes + ip_offset - kOffset, prefix_bytes,
                        sizeof(prefix_bytes)));
    EXPECT_EQ(0, memcmp(bytes + ip_offset, instructions, sizeof(instructions)));
    EXPECT_EQ(0, memcmp(bytes + ip_offset + sizeof(instructions), suffix_bytes,
                        sizeof(suffix_bytes)));
  }

  DeleteFileW(minidump_filename_wide.c_str());
}

TEST_F(ExceptionHandlerDeathTest, InstructionPointerMemoryMinBound) {
  ASSERT_TRUE(DoesPathExist(temp_path_));
  std::unique_ptr<google_breakpad::ExceptionHandler> exc(
      new google_breakpad::ExceptionHandler(
          temp_path_,
          nullptr,
          nullptr,
          nullptr,
          google_breakpad::ExceptionHandler::HANDLER_ALL));

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

  SYSTEM_INFO sSysInfo;         // Useful information about the system
  GetSystemInfo(&sSysInfo);     // Initialize the structure.

  const uint32_t kMemorySize = 256;  // bytes
  const DWORD kPageSize = sSysInfo.dwPageSize;
  const int kOffset = 0;
  // This crashes with SIGILL on x86/x86-64/arm.
  const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
  // Get some executable memory. Specifically, reserve two pages,
  // but only commit the second.
  char* all_memory = reinterpret_cast<char*>(VirtualAlloc(nullptr,
                                                          kPageSize * 2,
                                                          MEM_RESERVE,
                                                          PAGE_NOACCESS));
  ASSERT_TRUE(all_memory);
  char* memory = all_memory + kPageSize;
  ASSERT_TRUE(VirtualAlloc(memory, kPageSize,
                           MEM_COMMIT, PAGE_EXECUTE_READWRITE));

  // Write some instructions that will crash. Put them
  // in the middle of the block of memory, because the
  // minidump should contain 128 bytes on either side of the
  // instruction pointer.
  memcpy(memory + kOffset, instructions, sizeof(instructions));

  // Now execute the instructions, which should crash.
  typedef void (*void_function)(void);
  void_function memory_function =
      reinterpret_cast<void_function>(memory + kOffset);
  ASSERT_DEATH(memory_function(), "");

  // free the memory.
  VirtualFree(memory, 0, MEM_RELEASE);

  // Verify that the resulting minidump contains the memory around the IP
  wstring minidump_filename_wide = find_minidump_in_directory(temp_path_);
  ASSERT_FALSE(minidump_filename_wide.empty());
  string minidump_filename;
  ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(minidump_filename_wide,
                                                &minidump_filename));

  // Read the minidump. Locate the exception record and the
  // memory list, and then ensure that there is a memory region
  // in the memory list that covers the instruction pointer from
  // the exception record.
  {
    Minidump minidump(minidump_filename);
    ASSERT_TRUE(minidump.Read());

    MinidumpException* exception = minidump.GetException();
    MinidumpMemoryList* memory_list = minidump.GetMemoryList();
    ASSERT_TRUE(exception);
    ASSERT_TRUE(memory_list);
    ASSERT_LT((unsigned)0, memory_list->region_count());

    MinidumpContext* context = exception->GetContext();
    ASSERT_TRUE(context);

    uint64_t instruction_pointer;
    ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer));

    MinidumpMemoryRegion* region =
        memory_list->GetMemoryRegionForAddress(instruction_pointer);
    ASSERT_TRUE(region);

    EXPECT_EQ(kMemorySize / 2, region->GetSize());
    const uint8_t* bytes = region->GetMemory();
    ASSERT_TRUE(bytes);

    uint8_t suffix_bytes[kMemorySize / 2 - sizeof(instructions)];
    memset(suffix_bytes, 0, sizeof(suffix_bytes));
    EXPECT_TRUE(memcmp(bytes + kOffset,
                       instructions, sizeof(instructions)) == 0);
    EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions),
                       suffix_bytes, sizeof(suffix_bytes)) == 0);
  }

  DeleteFileW(minidump_filename_wide.c_str());
}

TEST_F(ExceptionHandlerDeathTest, InstructionPointerMemoryMaxBound) {
  ASSERT_TRUE(DoesPathExist(temp_path_));
  std::unique_ptr<google_breakpad::ExceptionHandler> exc(
      new google_breakpad::ExceptionHandler(
          temp_path_,
          nullptr,
          nullptr,
          nullptr,
          google_breakpad::ExceptionHandler::HANDLER_ALL));

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

  SYSTEM_INFO sSysInfo;         // Useful information about the system
  GetSystemInfo(&sSysInfo);     // Initialize the structure.

  const DWORD kPageSize = sSysInfo.dwPageSize;
  // This crashes with SIGILL on x86/x86-64/arm.
  const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
  const int kOffset = kPageSize - sizeof(instructions);
  // Get some executable memory. Specifically, reserve two pages,
  // but only commit the first.
  char* memory = reinterpret_cast<char*>(VirtualAlloc(nullptr,
                                                      kPageSize * 2,
                                                      MEM_RESERVE,
                                                      PAGE_NOACCESS));
  ASSERT_TRUE(memory);
  ASSERT_TRUE(VirtualAlloc(memory, kPageSize,
                           MEM_COMMIT, PAGE_EXECUTE_READWRITE));

  // Write some instructions that will crash.
  memcpy(memory + kOffset, instructions, sizeof(instructions));

  // Now execute the instructions, which should crash.
  typedef void (*void_function)(void);
  void_function memory_function =
      reinterpret_cast<void_function>(memory + kOffset);
  ASSERT_DEATH(memory_function(), "");

  // free the memory.
  VirtualFree(memory, 0, MEM_RELEASE);

  // Verify that the resulting minidump contains the memory around the IP
  wstring minidump_filename_wide = find_minidump_in_directory(temp_path_);
  ASSERT_FALSE(minidump_filename_wide.empty());
  string minidump_filename;
  ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(minidump_filename_wide,
                                                &minidump_filename));

  // Read the minidump. Locate the exception record and the
  // memory list, and then ensure that there is a memory region
  // in the memory list that covers the instruction pointer from
  // the exception record.
  {
    Minidump minidump(minidump_filename);
    ASSERT_TRUE(minidump.Read());

    MinidumpException* exception = minidump.GetException();
    MinidumpMemoryList* memory_list = minidump.GetMemoryList();
    ASSERT_TRUE(exception);
    ASSERT_TRUE(memory_list);
    ASSERT_LT((unsigned)0, memory_list->region_count());

    MinidumpContext* context = exception->GetContext();
    ASSERT_TRUE(context);

    uint64_t instruction_pointer;
    ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer));

    MinidumpMemoryRegion* region =
        memory_list->GetMemoryRegionForAddress(instruction_pointer);
    ASSERT_TRUE(region);

    const size_t kPrefixSize = 128;  // bytes
    EXPECT_EQ(kPrefixSize + sizeof(instructions), region->GetSize());
    const uint8_t* bytes = region->GetMemory();
    ASSERT_TRUE(bytes);

    uint8_t prefix_bytes[kPrefixSize];
    memset(prefix_bytes, 0, sizeof(prefix_bytes));
    EXPECT_EQ(0, memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)));
    EXPECT_EQ(0, memcmp(bytes + kPrefixSize,
                        instructions, sizeof(instructions)));
  }

  DeleteFileW(minidump_filename_wide.c_str());
}

#endif  // !ADDRESS_SANITIZER

}  // namespace
