diff options
Diffstat (limited to 'toolkit/crashreporter/breakpad-client/windows/unittests')
8 files changed, 2399 insertions, 0 deletions
diff --git a/toolkit/crashreporter/breakpad-client/windows/unittests/crash_generation_server_test.cc b/toolkit/crashreporter/breakpad-client/windows/unittests/crash_generation_server_test.cc new file mode 100644 index 0000000000..09f2dd200d --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/windows/unittests/crash_generation_server_test.cc @@ -0,0 +1,303 @@ +// Copyright 2010, 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 "breakpad_googletest_includes.h" +#include "client/windows/crash_generation/crash_generation_server.h" +#include "client/windows/common/ipc_protocol.h" + +using testing::_; + +namespace { + +const wchar_t kPipeName[] = + L"\\\\.\\pipe\\CrashGenerationServerTest\\TestCaseServer"; + +const DWORD kPipeDesiredAccess = FILE_READ_DATA | + FILE_WRITE_DATA | + FILE_WRITE_ATTRIBUTES; + +const DWORD kPipeFlagsAndAttributes = SECURITY_IDENTIFICATION | + SECURITY_SQOS_PRESENT; + +const DWORD kPipeMode = PIPE_READMODE_MESSAGE; + +#define arraysize(f) (sizeof(f) / sizeof(*f)) +const google_breakpad::CustomInfoEntry kCustomInfoEntries[] = { + google_breakpad::CustomInfoEntry(L"prod", L"CrashGenerationServerTest"), + google_breakpad::CustomInfoEntry(L"ver", L"1.0"), +}; + +class CrashGenerationServerTest : public ::testing::Test { + public: + CrashGenerationServerTest() + : crash_generation_server_(kPipeName, + NULL, + CallOnClientConnected, &mock_callbacks_, + CallOnClientDumpRequested, &mock_callbacks_, + CallOnClientExited, &mock_callbacks_, + CallOnClientUploadRequested, &mock_callbacks_, + false, + NULL), + thread_id_(0), + exception_pointers_(NULL) { + memset(&assert_info_, 0, sizeof(assert_info_)); + } + + protected: + class MockCrashGenerationServerCallbacks { + public: + MOCK_METHOD1(OnClientConnected, + void(const google_breakpad::ClientInfo* client_info)); + MOCK_METHOD2(OnClientDumpRequested, + void(const google_breakpad::ClientInfo* client_info, + const std::wstring* file_path)); + MOCK_METHOD1(OnClientExited, + void(const google_breakpad::ClientInfo* client_info)); + MOCK_METHOD1(OnClientUploadRequested, + void(const DWORD crash_id)); + }; + + enum ClientFault { + NO_FAULT, + CLOSE_AFTER_CONNECT, + SEND_INVALID_REGISTRATION, + TRUNCATE_REGISTRATION, + CLOSE_AFTER_REGISTRATION, + RESPONSE_BUFFER_TOO_SMALL, + CLOSE_AFTER_RESPONSE, + SEND_INVALID_ACK + }; + + void SetUp() { + ASSERT_TRUE(crash_generation_server_.Start()); + } + + void FaultyClient(ClientFault fault_type) { + HANDLE pipe = CreateFile(kPipeName, + kPipeDesiredAccess, + 0, + NULL, + OPEN_EXISTING, + kPipeFlagsAndAttributes, + NULL); + + if (pipe == INVALID_HANDLE_VALUE) { + ASSERT_EQ(ERROR_PIPE_BUSY, GetLastError()); + + // Cannot continue retrying if wait on pipe fails. + ASSERT_TRUE(WaitNamedPipe(kPipeName, 500)); + + pipe = CreateFile(kPipeName, + kPipeDesiredAccess, + 0, + NULL, + OPEN_EXISTING, + kPipeFlagsAndAttributes, + NULL); + } + + ASSERT_NE(pipe, INVALID_HANDLE_VALUE); + + DWORD mode = kPipeMode; + ASSERT_TRUE(SetNamedPipeHandleState(pipe, &mode, NULL, NULL)); + + DoFaultyClient(fault_type, pipe); + + CloseHandle(pipe); + } + + void DoTestFault(ClientFault fault) { + EXPECT_CALL(mock_callbacks_, OnClientConnected(_)).Times(0); + ASSERT_NO_FATAL_FAILURE(FaultyClient(fault)); + ASSERT_NO_FATAL_FAILURE(FaultyClient(fault)); + ASSERT_NO_FATAL_FAILURE(FaultyClient(fault)); + + EXPECT_CALL(mock_callbacks_, OnClientConnected(_)); + + ASSERT_NO_FATAL_FAILURE(FaultyClient(NO_FAULT)); + + // Slight hack. The OnClientConnected is only invoked after the ack is + // received by the server. At that point, the FaultyClient call has already + // returned. The best way to wait until the server is done handling that is + // to send one more ping, whose processing will be blocked by delivery of + // the OnClientConnected message. + ASSERT_NO_FATAL_FAILURE(FaultyClient(CLOSE_AFTER_CONNECT)); + } + + MockCrashGenerationServerCallbacks mock_callbacks_; + + private: + // Depends on the caller to successfully open the pipe before invocation and + // to close it immediately afterwards. + void DoFaultyClient(ClientFault fault_type, HANDLE pipe) { + if (fault_type == CLOSE_AFTER_CONNECT) { + return; + } + + google_breakpad::CustomClientInfo custom_info = {kCustomInfoEntries, + arraysize(kCustomInfoEntries)}; + + google_breakpad::ProtocolMessage msg( + fault_type == SEND_INVALID_REGISTRATION ? + google_breakpad::MESSAGE_TAG_NONE : + google_breakpad::MESSAGE_TAG_REGISTRATION_REQUEST, + GetCurrentProcessId(), + MiniDumpNormal, + &thread_id_, + &exception_pointers_, + &assert_info_, + custom_info, + NULL, + NULL, + NULL); + + DWORD bytes_count = 0; + + ASSERT_TRUE(WriteFile(pipe, + &msg, + fault_type == TRUNCATE_REGISTRATION ? + sizeof(msg) / 2 : sizeof(msg), + &bytes_count, + NULL)); + + if (fault_type == CLOSE_AFTER_REGISTRATION) { + return; + } + + google_breakpad::ProtocolMessage reply; + + if (!ReadFile(pipe, + &reply, + fault_type == RESPONSE_BUFFER_TOO_SMALL ? + sizeof(google_breakpad::ProtocolMessage) / 2 : + sizeof(google_breakpad::ProtocolMessage), + &bytes_count, + NULL)) { + switch (fault_type) { + case TRUNCATE_REGISTRATION: + case RESPONSE_BUFFER_TOO_SMALL: + case SEND_INVALID_REGISTRATION: + return; + + default: + FAIL() << "Unexpectedly failed to register."; + } + } + + if (fault_type == CLOSE_AFTER_RESPONSE) { + return; + } + + google_breakpad::ProtocolMessage ack_msg; + ack_msg.tag = google_breakpad::MESSAGE_TAG_REGISTRATION_ACK; + + ASSERT_TRUE(WriteFile(pipe, + &ack_msg, + SEND_INVALID_ACK ? + sizeof(ack_msg) : sizeof(ack_msg) / 2, + &bytes_count, + NULL)); + + return; + } + + static void CallOnClientConnected( + void* context, const google_breakpad::ClientInfo* client_info) { + static_cast<MockCrashGenerationServerCallbacks*>(context)-> + OnClientConnected(client_info); + } + + static void CallOnClientDumpRequested( + void* context, + const google_breakpad::ClientInfo* client_info, + const std::wstring* file_path) { + static_cast<MockCrashGenerationServerCallbacks*>(context)-> + OnClientDumpRequested(client_info, file_path); + } + + static void CallOnClientExited( + void* context, const google_breakpad::ClientInfo* client_info) { + static_cast<MockCrashGenerationServerCallbacks*>(context)-> + OnClientExited(client_info); + } + + static void CallOnClientUploadRequested(void* context, const DWORD crash_id) { + static_cast<MockCrashGenerationServerCallbacks*>(context)-> + OnClientUploadRequested(crash_id); + } + + DWORD thread_id_; + EXCEPTION_POINTERS* exception_pointers_; + MDRawAssertionInfo assert_info_; + + google_breakpad::CrashGenerationServer crash_generation_server_; +}; + +TEST_F(CrashGenerationServerTest, PingServerTest) { + DoTestFault(CLOSE_AFTER_CONNECT); +} + +TEST_F(CrashGenerationServerTest, InvalidRegistration) { + DoTestFault(SEND_INVALID_REGISTRATION); +} + +TEST_F(CrashGenerationServerTest, TruncateRegistration) { + DoTestFault(TRUNCATE_REGISTRATION); +} + +TEST_F(CrashGenerationServerTest, CloseAfterRegistration) { + DoTestFault(CLOSE_AFTER_REGISTRATION); +} + +TEST_F(CrashGenerationServerTest, ResponseBufferTooSmall) { + DoTestFault(RESPONSE_BUFFER_TOO_SMALL); +} + +TEST_F(CrashGenerationServerTest, CloseAfterResponse) { + DoTestFault(CLOSE_AFTER_RESPONSE); +} + +// It turns out that, as long as you send one byte, the ACK is accepted and +// registration succeeds. +TEST_F(CrashGenerationServerTest, SendInvalidAck) { + EXPECT_CALL(mock_callbacks_, OnClientConnected(_)); + ASSERT_NO_FATAL_FAILURE(FaultyClient(SEND_INVALID_ACK)); + + // See DoTestFault for an explanation of this line + ASSERT_NO_FATAL_FAILURE(FaultyClient(CLOSE_AFTER_CONNECT)); + + EXPECT_CALL(mock_callbacks_, OnClientConnected(_)); + ASSERT_NO_FATAL_FAILURE(FaultyClient(NO_FAULT)); + + // See DoTestFault for an explanation of this line + ASSERT_NO_FATAL_FAILURE(FaultyClient(CLOSE_AFTER_CONNECT)); +} + +} // anonymous namespace diff --git a/toolkit/crashreporter/breakpad-client/windows/unittests/dump_analysis.cc b/toolkit/crashreporter/breakpad-client/windows/unittests/dump_analysis.cc new file mode 100644 index 0000000000..53d4ddbde9 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/windows/unittests/dump_analysis.cc @@ -0,0 +1,184 @@ +// Copyright (c) 2010, 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 <objbase.h> +#include <dbghelp.h> + +#include "breakpad_googletest_includes.h" +#include "client/windows/unittests/dump_analysis.h" // NOLINT + +DumpAnalysis::~DumpAnalysis() { + if (dump_file_view_ != NULL) { + EXPECT_TRUE(::UnmapViewOfFile(dump_file_view_)); + ::CloseHandle(dump_file_mapping_); + dump_file_mapping_ = NULL; + } + + if (dump_file_handle_ != NULL) { + ::CloseHandle(dump_file_handle_); + dump_file_handle_ = NULL; + } +} + +void DumpAnalysis::EnsureDumpMapped() { + if (dump_file_view_ == NULL) { + dump_file_handle_ = ::CreateFile(dump_file_.c_str(), + GENERIC_READ, + 0, + NULL, + OPEN_EXISTING, + 0, + NULL); + ASSERT_TRUE(dump_file_handle_ != NULL); + ASSERT_TRUE(dump_file_mapping_ == NULL); + + dump_file_mapping_ = ::CreateFileMapping(dump_file_handle_, + NULL, + PAGE_READONLY, + 0, + 0, + NULL); + ASSERT_TRUE(dump_file_mapping_ != NULL); + + dump_file_view_ = ::MapViewOfFile(dump_file_mapping_, + FILE_MAP_READ, + 0, + 0, + 0); + ASSERT_TRUE(dump_file_view_ != NULL); + } +} + +bool DumpAnalysis::HasTebs() const { + MINIDUMP_THREAD_LIST* thread_list = NULL; + size_t thread_list_size = GetStream(ThreadListStream, &thread_list); + + if (thread_list_size > 0 && thread_list != NULL) { + for (ULONG i = 0; i < thread_list->NumberOfThreads; ++i) { + if (!HasMemory(thread_list->Threads[i].Teb)) + return false; + } + + return true; + } + + // No thread list, no TEB info. + return false; +} + +bool DumpAnalysis::HasPeb() const { + MINIDUMP_THREAD_LIST* thread_list = NULL; + size_t thread_list_size = GetStream(ThreadListStream, &thread_list); + + if (thread_list_size > 0 && thread_list != NULL && + thread_list->NumberOfThreads > 0) { + FakeTEB* teb = NULL; + if (!HasMemory(thread_list->Threads[0].Teb, &teb)) + return false; + + return HasMemory(teb->peb); + } + + return false; +} + +bool DumpAnalysis::HasStream(ULONG stream_number) const { + void* stream = NULL; + size_t stream_size = GetStreamImpl(stream_number, &stream); + return stream_size > 0 && stream != NULL; +} + +size_t DumpAnalysis::GetStreamImpl(ULONG stream_number, void** stream) const { + MINIDUMP_DIRECTORY* directory = NULL; + ULONG memory_list_size = 0; + BOOL ret = ::MiniDumpReadDumpStream(dump_file_view_, + stream_number, + &directory, + stream, + &memory_list_size); + + return ret ? memory_list_size : 0; +} + +bool DumpAnalysis::HasMemoryImpl(const void *addr_in, size_t structuresize, + void **structure) const { + uintptr_t address = reinterpret_cast<uintptr_t>(addr_in); + MINIDUMP_MEMORY_LIST* memory_list = NULL; + size_t memory_list_size = GetStream(MemoryListStream, &memory_list); + if (memory_list_size > 0 && memory_list != NULL) { + for (ULONG i = 0; i < memory_list->NumberOfMemoryRanges; ++i) { + MINIDUMP_MEMORY_DESCRIPTOR& descr = memory_list->MemoryRanges[i]; + const uintptr_t range_start = + static_cast<uintptr_t>(descr.StartOfMemoryRange); + uintptr_t range_end = range_start + descr.Memory.DataSize; + + if (address >= range_start && + address + structuresize < range_end) { + // The start address falls in the range, and the end address is + // in bounds, return a pointer to the structure if requested. + if (structure != NULL) + *structure = RVA_TO_ADDR(dump_file_view_, descr.Memory.Rva); + + return true; + } + } + } + + // We didn't find the range in a MINIDUMP_MEMORY_LIST, so maybe this + // is a full dump using MINIDUMP_MEMORY64_LIST with all the memory at the + // end of the dump file. + MINIDUMP_MEMORY64_LIST* memory64_list = NULL; + memory_list_size = GetStream(Memory64ListStream, &memory64_list); + if (memory_list_size > 0 && memory64_list != NULL) { + // Keep track of where the current descriptor maps to. + RVA64 curr_rva = memory64_list->BaseRva; + for (ULONG i = 0; i < memory64_list->NumberOfMemoryRanges; ++i) { + MINIDUMP_MEMORY_DESCRIPTOR64& descr = memory64_list->MemoryRanges[i]; + uintptr_t range_start = + static_cast<uintptr_t>(descr.StartOfMemoryRange); + uintptr_t range_end = range_start + static_cast<size_t>(descr.DataSize); + + if (address >= range_start && + address + structuresize < range_end) { + // The start address falls in the range, and the end address is + // in bounds, return a pointer to the structure if requested. + if (structure != NULL) + *structure = RVA_TO_ADDR(dump_file_view_, curr_rva); + + return true; + } + + // Advance the current RVA. + curr_rva += descr.DataSize; + } + } + + return false; +} diff --git a/toolkit/crashreporter/breakpad-client/windows/unittests/dump_analysis.h b/toolkit/crashreporter/breakpad-client/windows/unittests/dump_analysis.h new file mode 100644 index 0000000000..6cef48d814 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/windows/unittests/dump_analysis.h @@ -0,0 +1,102 @@ +// Copyright (c) 2010, 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. + +#ifndef CLIENT_WINDOWS_UNITTESTS_DUMP_ANALYSIS_H_ +#define CLIENT_WINDOWS_UNITTESTS_DUMP_ANALYSIS_H_ + +#include "client/windows/crash_generation/minidump_generator.h" + +// Convenience to get to the PEB pointer in a TEB. +struct FakeTEB { + char dummy[0x30]; + void* peb; +}; + +class DumpAnalysis { + public: + explicit DumpAnalysis(const std::wstring& file_path) + : dump_file_(file_path), dump_file_view_(NULL), dump_file_mapping_(NULL), + dump_file_handle_(NULL) { + EnsureDumpMapped(); + } + ~DumpAnalysis(); + + bool HasStream(ULONG stream_number) const; + + // This is template to keep type safety in the front, but we end up casting + // to void** inside the implementation to pass the pointer to Win32. So + // casting here is considered safe. + template <class StreamType> + size_t GetStream(ULONG stream_number, StreamType** stream) const { + return GetStreamImpl(stream_number, reinterpret_cast<void**>(stream)); + } + + bool HasTebs() const; + bool HasPeb() const; + bool HasMemory(ULONG64 address) const { + return HasMemory<BYTE>(address, NULL); + } + + bool HasMemory(const void* address) const { + return HasMemory<BYTE>(address, NULL); + } + + template <class StructureType> + bool HasMemory(ULONG64 address, StructureType** structure = NULL) const { + // We can't cope with 64 bit addresses for now. + if (address > 0xFFFFFFFFUL) + return false; + + return HasMemory(reinterpret_cast<void*>(address), structure); + } + + template <class StructureType> + bool HasMemory(const void* addr_in, StructureType** structure = NULL) const { + return HasMemoryImpl(addr_in, sizeof(StructureType), + reinterpret_cast<void**>(structure)); + } + + protected: + void EnsureDumpMapped(); + + HANDLE dump_file_mapping_; + HANDLE dump_file_handle_; + void* dump_file_view_; + std::wstring dump_file_; + + private: + // This is the implementation of GetStream<>. + size_t GetStreamImpl(ULONG stream_number, void** stream) const; + + // This is the implementation of HasMemory<>. + bool HasMemoryImpl(const void* addr_in, size_t pointersize, + void** structure) const; +}; + +#endif // CLIENT_WINDOWS_UNITTESTS_DUMP_ANALYSIS_H_ diff --git a/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_death_test.cc b/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_death_test.cc new file mode 100644 index 0000000000..5ef9e64d1d --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_death_test.cc @@ -0,0 +1,587 @@ +// 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 <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/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_, NULL); +} + +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_)); + scoped_ptr<google_breakpad::ExceptionHandler> exc( + new google_breakpad::ExceptionHandler( + temp_path_, + NULL, + &MinidumpWrittenCallback, + NULL, + google_breakpad::ExceptionHandler::HANDLER_ALL)); + + // Disable GTest SEH handler + testing::DisableExceptionHandlerInScope disable_exception_handler; + + int *i = NULL; + 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) { + scoped_ptr<google_breakpad::ExceptionHandler> exc; + + if (out_of_proc_guarantee == OUT_OF_PROC_GUARANTEED) { + google_breakpad::CrashGenerationClient *client = + new google_breakpad::CrashGenerationClient(kPipeName, + MiniDumpNormal, + NULL); // custom_info + ASSERT_TRUE(client->Register()); + exc.reset(new google_breakpad::ExceptionHandler( + temp_path_, + NULL, // filter + NULL, // callback + NULL, // 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_, + NULL, // filter + NULL, // callback + NULL, // callback_context + google_breakpad::ExceptionHandler::HANDLER_ALL, + MiniDumpNormal, + kPipeName, + NULL)); // 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 = NULL; + 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, NULL, NULL, NULL, &clientDumpCallback, NULL, NULL, NULL, NULL, + NULL, 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, NULL, NULL, NULL, &clientDumpCallback, NULL, NULL, NULL, NULL, + NULL, 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_, NULL, NULL, NULL, + 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(NULL), ::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_, NULL, NULL, NULL, + 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_)); + scoped_ptr<google_breakpad::ExceptionHandler> exc( + new google_breakpad::ExceptionHandler( + temp_path_, + NULL, + NULL, + NULL, + 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(NULL, + 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_)); + scoped_ptr<google_breakpad::ExceptionHandler> exc( + new google_breakpad::ExceptionHandler( + temp_path_, + NULL, + NULL, + NULL, + 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(NULL, + 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_)); + scoped_ptr<google_breakpad::ExceptionHandler> exc( + new google_breakpad::ExceptionHandler( + temp_path_, + NULL, + NULL, + NULL, + 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(NULL, + 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 diff --git a/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_nesting_test.cc b/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_nesting_test.cc new file mode 100644 index 0000000000..3ae1d7cd05 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_nesting_test.cc @@ -0,0 +1,327 @@ +// 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 diff --git a/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_test.cc b/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_test.cc new file mode 100644 index 0000000000..a4ce12a8aa --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_test.cc @@ -0,0 +1,503 @@ +// 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(), + 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_, 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 diff --git a/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_test.h b/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_test.h new file mode 100644 index 0000000000..ef973e5392 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_test.h @@ -0,0 +1,61 @@ +// 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. + +#ifndef CLIENT_WINDOWS_UNITTESTS_EXCEPTION_HANDLER_TEST_H_ +#define CLIENT_WINDOWS_UNITTESTS_EXCEPTION_HANDLER_TEST_H_ + +namespace testing { + +// By default, GTest (on Windows) installs a SEH filter (and a handler) before +// starting to run all the tests in order to avoid test interruptions is some +// of the tests are crashing. Unfortunately, this functionality prevents the +// execution to reach the UnhandledExceptionFilter installed by Google-Breakpad +// ExceptionHandler so in order to test the Google-Breakpad exception handling +// code the exception handling done by GTest must be disabled. +// Usage: +// +// google_breakpad::ExceptionHandler exc(...); +// +// // Disable GTest SEH handler +// testing::DisableExceptionHandlerInScope disable_exception_handler; +// ... +// ASSERT_DEATH( ... some crash ...); +// +class DisableExceptionHandlerInScope { + public: + DisableExceptionHandlerInScope(); + ~DisableExceptionHandlerInScope(); + + private: + bool catch_exceptions_; +}; + +} // namespace testing + +#endif // CLIENT_WINDOWS_UNITTESTS_EXCEPTION_HANDLER_TEST_H_ diff --git a/toolkit/crashreporter/breakpad-client/windows/unittests/minidump_test.cc b/toolkit/crashreporter/breakpad-client/windows/unittests/minidump_test.cc new file mode 100644 index 0000000000..82641125c1 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/windows/unittests/minidump_test.cc @@ -0,0 +1,332 @@ +// Copyright (c) 2010, 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 <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 |