// Copyright 2010 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 <objbase.h>
#include <dbghelp.h>

#include "breakpad_googletest_includes.h"
#include "client/windows/crash_generation/minidump_generator.h"
#include "client/windows/unittests/dump_analysis.h"  // NOLINT

namespace {

// Minidump with stacks, PEB, TEB, and unloaded module list.
const MINIDUMP_TYPE kSmallDumpType = static_cast<MINIDUMP_TYPE>(
    MiniDumpWithProcessThreadData |  // Get PEB and TEB.
    MiniDumpWithUnloadedModules);  // Get unloaded modules when available.

// Minidump with all of the above, plus memory referenced from stack.
const MINIDUMP_TYPE kLargerDumpType = static_cast<MINIDUMP_TYPE>(
    MiniDumpWithProcessThreadData |  // Get PEB and TEB.
    MiniDumpWithUnloadedModules |  // Get unloaded modules when available.
    MiniDumpWithIndirectlyReferencedMemory);  // Get memory referenced by stack.

// Large dump with all process memory.
const MINIDUMP_TYPE kFullDumpType = static_cast<MINIDUMP_TYPE>(
    MiniDumpWithFullMemory |  // Full memory from process.
    MiniDumpWithProcessThreadData |  // Get PEB and TEB.
    MiniDumpWithHandleData |  // Get all handle information.
    MiniDumpWithUnloadedModules);  // Get unloaded modules when available.

class MinidumpTest: public testing::Test {
 public:
  MinidumpTest() {
    wchar_t temp_dir_path[ MAX_PATH ] = {0};
    ::GetTempPath(MAX_PATH, temp_dir_path);
    dump_path_ = temp_dir_path;
  }

  virtual void SetUp() {
    // Make sure URLMon isn't loaded into our process.
    ASSERT_EQ(NULL, ::GetModuleHandle(L"urlmon.dll"));

    // Then load and unload it to ensure we have something to
    // stock the unloaded module list with.
    HMODULE urlmon = ::LoadLibrary(L"urlmon.dll");
    ASSERT_TRUE(urlmon != NULL);
    ASSERT_TRUE(::FreeLibrary(urlmon));
  }

  virtual void 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 WriteDump(ULONG flags) {
    using google_breakpad::MinidumpGenerator;

    // Fake exception is access violation on write to this.
    EXCEPTION_RECORD ex_record = {
        STATUS_ACCESS_VIOLATION,  // ExceptionCode
        0,  // ExceptionFlags
        NULL,  // ExceptionRecord;
        reinterpret_cast<void*>(static_cast<uintptr_t>(0xCAFEBABE)),  // ExceptionAddress;
        2,  // NumberParameters;
        { EXCEPTION_WRITE_FAULT, reinterpret_cast<ULONG_PTR>(this) }
    };
    CONTEXT ctx_record = {};
    EXCEPTION_POINTERS ex_ptrs = {
      &ex_record,
      &ctx_record,
    };

    MinidumpGenerator generator(dump_path_,
                                ::GetCurrentProcess(),
                                ::GetCurrentProcessId(),
                                ::GetCurrentThreadId(),
                                ::GetCurrentThreadId(),
                                &ex_ptrs,
                                NULL,
                                static_cast<MINIDUMP_TYPE>(flags),
                                TRUE);
    generator.GenerateDumpFile(&dump_file_);
    generator.GenerateFullDumpFile(&full_dump_file_);
    // And write a dump
    bool result = generator.WriteMinidump();
    return result == TRUE;
  }

 protected:
  std::wstring dump_file_;
  std::wstring full_dump_file_;

  std::wstring dump_path_;
};

// We need to be able to get file information from Windows
bool HasFileInfo(const std::wstring& file_path) {
  DWORD dummy;
  const wchar_t* path = file_path.c_str();
  DWORD length = ::GetFileVersionInfoSize(path, &dummy);
  if (length == 0)
    return NULL;

  void* data = calloc(length, 1);
  if (!data)
    return false;

  if (!::GetFileVersionInfo(path, dummy, length, data)) {
    free(data);
    return false;
  }

  void* translate = NULL;
  UINT page_count;
  BOOL query_result = VerQueryValue(
      data,
      L"\\VarFileInfo\\Translation",
      static_cast<void**>(&translate),
      &page_count);

  free(data);
  if (query_result && translate) {
    return true;
  } else {
    return false;
  }
}

TEST_F(MinidumpTest, Version) {
  // Loads DbgHelp.dll in process
  ImagehlpApiVersion();

  HMODULE dbg_help = ::GetModuleHandle(L"dbghelp.dll");
  ASSERT_TRUE(dbg_help != NULL);

  wchar_t dbg_help_file[1024] = {};
  ASSERT_TRUE(::GetModuleFileName(dbg_help,
                                  dbg_help_file,
                                  sizeof(dbg_help_file) /
                                      sizeof(*dbg_help_file)));
  ASSERT_TRUE(HasFileInfo(std::wstring(dbg_help_file)) != NULL);

//  LOG(INFO) << "DbgHelp.dll version: " << file_info->file_version();
}

TEST_F(MinidumpTest, Normal) {
  EXPECT_TRUE(WriteDump(MiniDumpNormal));
  DumpAnalysis mini(dump_file_);

  // We expect threads, modules and some memory.
  EXPECT_TRUE(mini.HasStream(ThreadListStream));
  EXPECT_TRUE(mini.HasStream(ModuleListStream));
  EXPECT_TRUE(mini.HasStream(MemoryListStream));
  EXPECT_TRUE(mini.HasStream(ExceptionStream));
  EXPECT_TRUE(mini.HasStream(SystemInfoStream));
  EXPECT_TRUE(mini.HasStream(MiscInfoStream));

  EXPECT_FALSE(mini.HasStream(ThreadExListStream));
  EXPECT_FALSE(mini.HasStream(Memory64ListStream));
  EXPECT_FALSE(mini.HasStream(CommentStreamA));
  EXPECT_FALSE(mini.HasStream(CommentStreamW));
  EXPECT_FALSE(mini.HasStream(HandleDataStream));
  EXPECT_FALSE(mini.HasStream(FunctionTableStream));
  EXPECT_FALSE(mini.HasStream(UnloadedModuleListStream));
  EXPECT_FALSE(mini.HasStream(MemoryInfoListStream));
  EXPECT_FALSE(mini.HasStream(ThreadInfoListStream));
  EXPECT_FALSE(mini.HasStream(HandleOperationListStream));
  EXPECT_FALSE(mini.HasStream(TokenStream));

  // We expect no PEB nor TEBs in this dump.
  EXPECT_FALSE(mini.HasTebs());
  EXPECT_FALSE(mini.HasPeb());

  // We expect no off-stack memory in this dump.
  EXPECT_FALSE(mini.HasMemory(this));
}

TEST_F(MinidumpTest, SmallDump) {
  ASSERT_TRUE(WriteDump(kSmallDumpType));
  DumpAnalysis mini(dump_file_);

  EXPECT_TRUE(mini.HasStream(ThreadListStream));
  EXPECT_TRUE(mini.HasStream(ModuleListStream));
  EXPECT_TRUE(mini.HasStream(MemoryListStream));
  EXPECT_TRUE(mini.HasStream(ExceptionStream));
  EXPECT_TRUE(mini.HasStream(SystemInfoStream));
  EXPECT_TRUE(mini.HasStream(UnloadedModuleListStream));
  EXPECT_TRUE(mini.HasStream(MiscInfoStream));

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

  EXPECT_FALSE(mini.HasStream(ThreadExListStream));
  EXPECT_FALSE(mini.HasStream(Memory64ListStream));
  EXPECT_FALSE(mini.HasStream(CommentStreamA));
  EXPECT_FALSE(mini.HasStream(CommentStreamW));
  EXPECT_FALSE(mini.HasStream(HandleDataStream));
  EXPECT_FALSE(mini.HasStream(FunctionTableStream));
  EXPECT_FALSE(mini.HasStream(MemoryInfoListStream));
  EXPECT_FALSE(mini.HasStream(ThreadInfoListStream));
  EXPECT_FALSE(mini.HasStream(HandleOperationListStream));
  EXPECT_FALSE(mini.HasStream(TokenStream));

  // We expect no off-stack memory in this dump.
  EXPECT_FALSE(mini.HasMemory(this));
}

TEST_F(MinidumpTest, LargerDump) {
  ASSERT_TRUE(WriteDump(kLargerDumpType));
  DumpAnalysis mini(dump_file_);

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

  // We expect memory referenced by stack in this dump.
  EXPECT_TRUE(mini.HasMemory(this));

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

  EXPECT_FALSE(mini.HasStream(ThreadExListStream));
  EXPECT_FALSE(mini.HasStream(Memory64ListStream));
  EXPECT_FALSE(mini.HasStream(CommentStreamA));
  EXPECT_FALSE(mini.HasStream(CommentStreamW));
  EXPECT_FALSE(mini.HasStream(HandleDataStream));
  EXPECT_FALSE(mini.HasStream(FunctionTableStream));
  EXPECT_FALSE(mini.HasStream(MemoryInfoListStream));
  EXPECT_FALSE(mini.HasStream(ThreadInfoListStream));
  EXPECT_FALSE(mini.HasStream(HandleOperationListStream));
  EXPECT_FALSE(mini.HasStream(TokenStream));
}

TEST_F(MinidumpTest, FullDump) {
  ASSERT_TRUE(WriteDump(kFullDumpType));
  ASSERT_TRUE(dump_file_ != L"");
  ASSERT_TRUE(full_dump_file_ != L"");
  DumpAnalysis mini(dump_file_);
  DumpAnalysis full(full_dump_file_);

  // Either dumps can contain part of the information.

  // 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(UnloadedModuleListStream));
  EXPECT_TRUE(full.HasStream(UnloadedModuleListStream));
  EXPECT_TRUE(mini.HasStream(MiscInfoStream));
  EXPECT_TRUE(full.HasStream(MiscInfoStream));
  EXPECT_TRUE(mini.HasStream(HandleDataStream));
  EXPECT_TRUE(full.HasStream(HandleDataStream));

  // We expect memory referenced by stack in this dump.
  EXPECT_FALSE(mini.HasMemory(this));
  EXPECT_TRUE(full.HasMemory(this));

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

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

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

}  // namespace
