// Copyright 2009, 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 "client/windows/unittests/exception_handler_test.h"

#include <windows.h>
#include <dbghelp.h>
#include <strsafe.h>
#include <objbase.h>
#include <shellapi.h>

#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/dump_analysis.h"  // NOLINT
#include "common/windows/string_utils-inl.h"
#include "google_breakpad/processor/minidump.h"

namespace testing {

DisableExceptionHandlerInScope::DisableExceptionHandlerInScope() {
  catch_exceptions_ = GTEST_FLAG(catch_exceptions);
  GTEST_FLAG(catch_exceptions) = false;
}

DisableExceptionHandlerInScope::~DisableExceptionHandlerInScope() {
  GTEST_FLAG(catch_exceptions) = catch_exceptions_;
}

}  // namespace testing

namespace {

using std::wstring;
using namespace google_breakpad;

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

const MINIDUMP_TYPE kFullDumpType = static_cast<MINIDUMP_TYPE>(
    MiniDumpWithFullMemory |  // Full memory from process.
    MiniDumpWithProcessThreadData |  // Get PEB and TEB.
    MiniDumpWithHandleData);  // Get all handle information.

class ExceptionHandlerTest : 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();

  // Deletes temporary files.
  virtual void TearDown();

  void DoCrashInvalidParameter();
  void DoCrashPureVirtualCall();

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

  // Client callback.
  static void ClientDumpCallback(
      void *dump_context,
      const google_breakpad::ClientInfo *client_info,
      const std::wstring *dump_path);

  static bool DumpCallback(const wchar_t* dump_path,
                           const wchar_t* minidump_id,
                           void* context,
                           EXCEPTION_POINTERS* exinfo,
                           MDRawAssertionInfo* assertion,
                           bool succeeded);

  static std::wstring dump_file;
  static std::wstring full_dump_file;
};

std::wstring ExceptionHandlerTest::dump_file;
std::wstring ExceptionHandlerTest::full_dump_file;

void ExceptionHandlerTest::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 to use as a c-style string,
  // But we might be working in UNICODE here on Windows.
  int dwRet = MultiByteToWideChar(CP_ACP, 0, test_info->name(),
                                  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_, NULL);
}

void ExceptionHandlerTest::TearDown() {
  if (!dump_file.empty()) {
    ::DeleteFile(dump_file.c_str());
    dump_file = L"";
  }
  if (!full_dump_file.empty()) {
    ::DeleteFile(full_dump_file.c_str());
    full_dump_file = L"";
  }
}

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

// static
void ExceptionHandlerTest::ClientDumpCallback(
    void *dump_context,
    const google_breakpad::ClientInfo *client_info,
    const wstring *dump_path) {
  dump_file = *dump_path;
  // Create the full dump file name from the dump path.
  full_dump_file = dump_file.substr(0, dump_file.length() - 4) + L"-full.dmp";
}

// static
bool ExceptionHandlerTest::DumpCallback(const wchar_t* dump_path,
                    const wchar_t* minidump_id,
                    void* context,
                    EXCEPTION_POINTERS* exinfo,
                    MDRawAssertionInfo* assertion,
                    bool succeeded) {
  dump_file = dump_path;
  dump_file += L"\\";
  dump_file += minidump_id;
  dump_file += L".dmp";
    return succeeded;
}

void ExceptionHandlerTest::DoCrashInvalidParameter() {
  google_breakpad::ExceptionHandler *exc =
      new google_breakpad::ExceptionHandler(
          temp_path_, NULL, NULL, NULL,
          google_breakpad::ExceptionHandler::HANDLER_INVALID_PARAMETER,
          kFullDumpType, kPipeName, NULL);

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

  // 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());
  printf(NULL);
}


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 ExceptionHandlerTest::DoCrashPureVirtualCall() {
  google_breakpad::ExceptionHandler *exc =
      new google_breakpad::ExceptionHandler(
          temp_path_, NULL, NULL, NULL,
          google_breakpad::ExceptionHandler::HANDLER_PURECALL,
          kFullDumpType, kPipeName, NULL);

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

  // 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());

  // Create a new frame to ensure PureVirtualCall is not optimized to some
  // other line in this function.
  {
    PureVirtualCall instance;
  }
}

// This test validates that the minidump is written correctly.
TEST_F(ExceptionHandlerTest, InvalidParameterMiniDumpTest) {
  ASSERT_TRUE(DoesPathExist(temp_path_));

  // Call with a bad argument
  ASSERT_TRUE(DoesPathExist(temp_path_));
  wstring dump_path(temp_path_);
  google_breakpad::CrashGenerationServer server(
      kPipeName, NULL, NULL, NULL, ClientDumpCallback, NULL, NULL, NULL, NULL,
      NULL, true, &dump_path);

  ASSERT_TRUE(dump_file.empty() && full_dump_file.empty());

  // 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());
  EXPECT_EXIT(DoCrashInvalidParameter(), ::testing::ExitedWithCode(0), "");
  ASSERT_TRUE(!dump_file.empty() && !full_dump_file.empty());
  ASSERT_TRUE(DoesPathExist(dump_file.c_str()));
  ASSERT_TRUE(DoesPathExist(full_dump_file.c_str()));

  // Verify the dump for infos.
  DumpAnalysis mini(dump_file);
  DumpAnalysis full(full_dump_file);

  // The dump should have all of these streams.
  EXPECT_TRUE(mini.HasStream(ThreadListStream));
  EXPECT_TRUE(full.HasStream(ThreadListStream));
  EXPECT_TRUE(mini.HasStream(ModuleListStream));
  EXPECT_TRUE(full.HasStream(ModuleListStream));
  EXPECT_TRUE(mini.HasStream(ExceptionStream));
  EXPECT_TRUE(full.HasStream(ExceptionStream));
  EXPECT_TRUE(mini.HasStream(SystemInfoStream));
  EXPECT_TRUE(full.HasStream(SystemInfoStream));
  EXPECT_TRUE(mini.HasStream(MiscInfoStream));
  EXPECT_TRUE(full.HasStream(MiscInfoStream));
  EXPECT_TRUE(mini.HasStream(HandleDataStream));
  EXPECT_TRUE(full.HasStream(HandleDataStream));

  // We expect PEB and TEBs in this dump.
  EXPECT_TRUE(mini.HasTebs() || full.HasTebs());
  EXPECT_TRUE(mini.HasPeb() || full.HasPeb());

  // Minidump should have a memory listing, but no 64-bit memory.
  EXPECT_TRUE(mini.HasStream(MemoryListStream));
  EXPECT_FALSE(mini.HasStream(Memory64ListStream));

  EXPECT_FALSE(full.HasStream(MemoryListStream));
  EXPECT_TRUE(full.HasStream(Memory64ListStream));

  // This is the only place we don't use OR because we want both not
  // to have the streams.
  EXPECT_FALSE(mini.HasStream(ThreadExListStream));
  EXPECT_FALSE(full.HasStream(ThreadExListStream));
  EXPECT_FALSE(mini.HasStream(CommentStreamA));
  EXPECT_FALSE(full.HasStream(CommentStreamA));
  EXPECT_FALSE(mini.HasStream(CommentStreamW));
  EXPECT_FALSE(full.HasStream(CommentStreamW));
  EXPECT_FALSE(mini.HasStream(FunctionTableStream));
  EXPECT_FALSE(full.HasStream(FunctionTableStream));
  EXPECT_FALSE(mini.HasStream(MemoryInfoListStream));
  EXPECT_FALSE(full.HasStream(MemoryInfoListStream));
  EXPECT_FALSE(mini.HasStream(ThreadInfoListStream));
  EXPECT_FALSE(full.HasStream(ThreadInfoListStream));
  EXPECT_FALSE(mini.HasStream(HandleOperationListStream));
  EXPECT_FALSE(full.HasStream(HandleOperationListStream));
  EXPECT_FALSE(mini.HasStream(TokenStream));
  EXPECT_FALSE(full.HasStream(TokenStream));
}


// This test validates that the minidump is written correctly.
TEST_F(ExceptionHandlerTest, PureVirtualCallMiniDumpTest) {
  ASSERT_TRUE(DoesPathExist(temp_path_));

  // Call with a bad argument
  ASSERT_TRUE(DoesPathExist(temp_path_));
  wstring dump_path(temp_path_);
  google_breakpad::CrashGenerationServer server(
      kPipeName, NULL, NULL, NULL, ClientDumpCallback, NULL, NULL, NULL, NULL,
      NULL, true, &dump_path);

  ASSERT_TRUE(dump_file.empty() && full_dump_file.empty());

  // 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());
  EXPECT_EXIT(DoCrashPureVirtualCall(), ::testing::ExitedWithCode(0), "");
  ASSERT_TRUE(!dump_file.empty() && !full_dump_file.empty());
  ASSERT_TRUE(DoesPathExist(dump_file.c_str()));
  ASSERT_TRUE(DoesPathExist(full_dump_file.c_str()));

  // Verify the dump for infos.
  DumpAnalysis mini(dump_file);
  DumpAnalysis full(full_dump_file);

  // The dump should have all of these streams.
  EXPECT_TRUE(mini.HasStream(ThreadListStream));
  EXPECT_TRUE(full.HasStream(ThreadListStream));
  EXPECT_TRUE(mini.HasStream(ModuleListStream));
  EXPECT_TRUE(full.HasStream(ModuleListStream));
  EXPECT_TRUE(mini.HasStream(ExceptionStream));
  EXPECT_TRUE(full.HasStream(ExceptionStream));
  EXPECT_TRUE(mini.HasStream(SystemInfoStream));
  EXPECT_TRUE(full.HasStream(SystemInfoStream));
  EXPECT_TRUE(mini.HasStream(MiscInfoStream));
  EXPECT_TRUE(full.HasStream(MiscInfoStream));
  EXPECT_TRUE(mini.HasStream(HandleDataStream));
  EXPECT_TRUE(full.HasStream(HandleDataStream));

  // We expect PEB and TEBs in this dump.
  EXPECT_TRUE(mini.HasTebs() || full.HasTebs());
  EXPECT_TRUE(mini.HasPeb() || full.HasPeb());

  // Minidump should have a memory listing, but no 64-bit memory.
  EXPECT_TRUE(mini.HasStream(MemoryListStream));
  EXPECT_FALSE(mini.HasStream(Memory64ListStream));

  EXPECT_FALSE(full.HasStream(MemoryListStream));
  EXPECT_TRUE(full.HasStream(Memory64ListStream));

  // This is the only place we don't use OR because we want both not
  // to have the streams.
  EXPECT_FALSE(mini.HasStream(ThreadExListStream));
  EXPECT_FALSE(full.HasStream(ThreadExListStream));
  EXPECT_FALSE(mini.HasStream(CommentStreamA));
  EXPECT_FALSE(full.HasStream(CommentStreamA));
  EXPECT_FALSE(mini.HasStream(CommentStreamW));
  EXPECT_FALSE(full.HasStream(CommentStreamW));
  EXPECT_FALSE(mini.HasStream(FunctionTableStream));
  EXPECT_FALSE(full.HasStream(FunctionTableStream));
  EXPECT_FALSE(mini.HasStream(MemoryInfoListStream));
  EXPECT_FALSE(full.HasStream(MemoryInfoListStream));
  EXPECT_FALSE(mini.HasStream(ThreadInfoListStream));
  EXPECT_FALSE(full.HasStream(ThreadInfoListStream));
  EXPECT_FALSE(mini.HasStream(HandleOperationListStream));
  EXPECT_FALSE(full.HasStream(HandleOperationListStream));
  EXPECT_FALSE(mini.HasStream(TokenStream));
  EXPECT_FALSE(full.HasStream(TokenStream));
}

// Test that writing a minidump produces a valid minidump containing
// some expected structures.
TEST_F(ExceptionHandlerTest, WriteMinidumpTest) {
  ExceptionHandler handler(temp_path_,
                           NULL,
                           DumpCallback,
                           NULL,
                           ExceptionHandler::HANDLER_ALL);

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

  ASSERT_TRUE(handler.WriteMinidump());
  ASSERT_FALSE(dump_file.empty());

  string minidump_filename;
  ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(dump_file,
                                                &minidump_filename));

  // Read the minidump and verify some info.
  Minidump minidump(minidump_filename);
  ASSERT_TRUE(minidump.Read());
  // TODO(ted): more comprehensive tests...
}

// Test that an additional memory region can be included in the minidump.
TEST_F(ExceptionHandlerTest, AdditionalMemory) {
  SYSTEM_INFO si;
  GetSystemInfo(&si);
  const uint32_t kMemorySize = si.dwPageSize;

  // Get some heap memory.
  uint8_t* memory = new uint8_t[kMemorySize];
  const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
  ASSERT_TRUE(memory);

  // Stick some data into the memory so the contents can be verified.
  for (uint32_t i = 0; i < kMemorySize; ++i) {
    memory[i] = i % 255;
  }

  ExceptionHandler handler(temp_path_,
                           NULL,
                           DumpCallback,
                           NULL,
                           ExceptionHandler::HANDLER_ALL);

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

  // Add the memory region to the list of memory to be included.
  handler.RegisterAppMemory(memory, kMemorySize);
  ASSERT_TRUE(handler.WriteMinidump());
  ASSERT_FALSE(dump_file.empty());

  string minidump_filename;
  ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(dump_file,
                                                &minidump_filename));

  // Read the minidump. Ensure that the memory region is present
  Minidump minidump(minidump_filename);
  ASSERT_TRUE(minidump.Read());

  MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList();
  ASSERT_TRUE(dump_memory_list);
  const MinidumpMemoryRegion* region =
    dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress);
  ASSERT_TRUE(region);

  EXPECT_EQ(kMemoryAddress, region->GetBase());
  EXPECT_EQ(kMemorySize, region->GetSize());

  // Verify memory contents.
  EXPECT_EQ(0, memcmp(region->GetMemory(), memory, kMemorySize));

  delete[] memory;
}

// Test that a memory region that was previously registered
// can be unregistered.
TEST_F(ExceptionHandlerTest, AdditionalMemoryRemove) {
  SYSTEM_INFO si;
  GetSystemInfo(&si);
  const uint32_t kMemorySize = si.dwPageSize;

  // Get some heap memory.
  uint8_t* memory = new uint8_t[kMemorySize];
  const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
  ASSERT_TRUE(memory);

  // Stick some data into the memory so the contents can be verified.
  for (uint32_t i = 0; i < kMemorySize; ++i) {
    memory[i] = i % 255;
  }

  ExceptionHandler handler(temp_path_,
                           NULL,
                           DumpCallback,
                           NULL,
                           ExceptionHandler::HANDLER_ALL);

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

  // Add the memory region to the list of memory to be included.
  handler.RegisterAppMemory(memory, kMemorySize);

  // ...and then remove it
  handler.UnregisterAppMemory(memory);

  ASSERT_TRUE(handler.WriteMinidump());
  ASSERT_FALSE(dump_file.empty());

  string minidump_filename;
  ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(dump_file,
                                                &minidump_filename));

  // Read the minidump. Ensure that the memory region is not present.
  Minidump minidump(minidump_filename);
  ASSERT_TRUE(minidump.Read());

  MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList();
  ASSERT_TRUE(dump_memory_list);
  const MinidumpMemoryRegion* region =
    dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress);
  EXPECT_FALSE(region);

  delete[] memory;
}

}  // namespace
