Chromium Code Reviews| Index: util/mach/exc_client_variants_test.cc |
| diff --git a/util/mach/exc_client_variants_test.cc b/util/mach/exc_client_variants_test.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..558ffb663bf06e25f9b96353bd52b706671711f7 |
| --- /dev/null |
| +++ b/util/mach/exc_client_variants_test.cc |
| @@ -0,0 +1,288 @@ |
| +// Copyright 2014 The Crashpad Authors. All rights reserved. |
| +// |
| +// Licensed under the Apache License, Version 2.0 (the "License"); |
| +// you may not use this file except in compliance with the License. |
| +// You may obtain a copy of the License at |
| +// |
| +// http://www.apache.org/licenses/LICENSE-2.0 |
| +// |
| +// Unless required by applicable law or agreed to in writing, software |
| +// distributed under the License is distributed on an "AS IS" BASIS, |
| +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| +// See the License for the specific language governing permissions and |
| +// limitations under the License. |
| + |
| +#include "util/mach/exc_client_variants.h" |
| + |
| +#include <mach/mach.h> |
| +#include <pthread.h> |
| +#include <string.h> |
| + |
| +#include "base/basictypes.h" |
| +#include "base/strings/stringprintf.h" |
| +#include "gtest/gtest.h" |
| +#include "util/mach/exc_server_variants.h" |
| +#include "util/mach/mach_extensions.h" |
| +#include "util/test/mac/mach_errors.h" |
| +#include "util/test/mac/mach_multiprocess.h" |
| + |
| +namespace { |
| + |
| +using namespace crashpad; |
| +using namespace crashpad::test; |
| + |
| +class TestExcClientVariants : public UniversalMachExcServer, |
| + public MachMultiprocess { |
| + public: |
| + TestExcClientVariants(exception_behavior_t behavior, bool all_fields) |
| + : UniversalMachExcServer(), |
| + MachMultiprocess(), |
| + behavior_(behavior), |
| + all_fields_(all_fields), |
| + handled_(false) { |
| + ++exception_; |
| + ++exception_code_; |
| + ++exception_subcode_; |
| + } |
| + |
| + // UniversalMachExcServer: |
| + |
| + virtual kern_return_t CatchMachException( |
| + exception_behavior_t behavior, |
| + exception_handler_t exception_port, |
| + thread_t thread, |
| + task_t task, |
| + exception_type_t exception, |
| + const mach_exception_data_type_t* code, |
| + mach_msg_type_number_t code_count, |
| + thread_state_flavor_t* flavor, |
| + const natural_t* old_state, |
| + mach_msg_type_number_t old_state_count, |
| + thread_state_t new_state, |
| + mach_msg_type_number_t* new_state_count, |
| + bool* destroy_complex_request) override { |
| + *destroy_complex_request = true; |
| + |
| + EXPECT_FALSE(handled_); |
| + handled_ = true; |
| + |
| + EXPECT_EQ(behavior_, behavior); |
| + EXPECT_EQ(LocalPort(), exception_port); |
| + |
| + if (HasIdentity()) { |
| + EXPECT_NE(kMachPortNull, thread); |
| + EXPECT_EQ(ChildTask(), task); |
| + } else { |
| + EXPECT_EQ(kMachPortNull, thread); |
| + EXPECT_EQ(kMachPortNull, task); |
| + } |
| + |
| + mach_exception_code_t expect_code = exception_code_; |
| + mach_exception_subcode_t expect_subcode = exception_subcode_; |
| + if ((behavior & MACH_EXCEPTION_CODES) == 0) { |
| + expect_code = static_cast<exception_data_type_t>(expect_code); |
| + expect_subcode = static_cast<exception_data_type_t>(expect_subcode); |
| + } |
| + |
| + EXPECT_EQ(exception_, exception); |
| + EXPECT_EQ(2u, code_count); |
| + EXPECT_EQ(expect_code, code[0]); |
| + EXPECT_EQ(expect_subcode, code[1]); |
| + |
| + if (HasState()) { |
| + EXPECT_EQ(exception_ + 10, *flavor); |
| + EXPECT_EQ(MACHINE_THREAD_STATE_COUNT, old_state_count); |
| + EXPECT_NE(static_cast<const natural_t*>(NULL), old_state); |
| + EXPECT_EQ(static_cast<mach_msg_type_number_t>(THREAD_STATE_MAX), |
| + *new_state_count); |
| + EXPECT_NE(static_cast<natural_t*>(NULL), new_state); |
| + |
| + for (size_t index = 0; index < old_state_count; ++index) { |
| + EXPECT_EQ(index, old_state[index]); |
| + } |
| + |
| + // Use a flavor known to be different from the incoming flavor, for a test |
| + // of the “out” side of the inout flavor parameter. |
| + *flavor = exception_ + 20; |
| + *new_state_count = MACHINE_THREAD_STATE_COUNT; |
| + |
| + // Send a new state back to the client. |
| + for (size_t index = 0; index < *new_state_count; ++index) { |
| + EXPECT_EQ(0u, new_state[index]); |
| + new_state[index] = MACHINE_THREAD_STATE_COUNT - index; |
| + } |
| + } else { |
| + EXPECT_EQ(THREAD_STATE_NONE, *flavor); |
| + EXPECT_EQ(0u, old_state_count); |
| + EXPECT_EQ(NULL, old_state); |
| + EXPECT_EQ(0u, *new_state_count); |
| + EXPECT_EQ(NULL, new_state); |
| + } |
| + |
| + return KERN_SUCCESS; |
| + } |
| + |
| + private: |
| + // MachMultiprocess: |
| + |
| + virtual void MachMultiprocessParent() override { |
| + kern_return_t kr = MachMessageServer::Run(this, |
| + LocalPort(), |
| + MACH_MSG_OPTION_NONE, |
| + MachMessageServer::kOneShot, |
| + MachMessageServer::kBlocking, |
| + 0); |
| + EXPECT_EQ(KERN_SUCCESS, kr) |
| + << MachErrorMessage(kr, "MachMessageServer::Run"); |
| + |
| + EXPECT_TRUE(handled_); |
| + } |
| + |
| + virtual void MachMultiprocessChild() override { |
| + exception_type_t exception = exception_; |
| + mach_exception_data_type_t code[] = { |
|
Robert Sesek
2014/09/15 20:55:43
const?
|
| + exception_code_, |
| + exception_subcode_ |
| + }; |
| + |
| + thread_t thread = MACH_PORT_NULL; |
| + task_t task = MACH_PORT_NULL; |
| + if (all_fields_ || HasIdentity()) { |
| + thread = pthread_mach_thread_np(pthread_self()); |
|
Robert Sesek
2014/09/15 20:55:43
Why do you do this instead of just mach_thread_sel
|
| + task = mach_task_self(); |
| + } |
| + |
| + thread_state_flavor_t flavor; |
| + thread_state_flavor_t* flavor_p = NULL; |
| + natural_t old_state[MACHINE_THREAD_STATE_COUNT]; |
| + thread_state_t old_state_p = NULL; |
| + mach_msg_type_number_t old_state_count = 0; |
| + natural_t new_state[THREAD_STATE_MAX]; |
| + thread_state_t new_state_p = NULL; |
| + mach_msg_type_number_t new_state_count; |
| + mach_msg_type_number_t* new_state_count_p = NULL; |
| + if (all_fields_ || HasState()) { |
| + // Pick a different flavor each time based on the value of exception_. |
| + // These aren’t real flavors, it’s just for testing. |
| + flavor = exception_ + 10; |
| + flavor_p = &flavor; |
| + for (size_t index = 0; index < arraysize(old_state); ++index) { |
| + old_state[index] = index; |
| + } |
| + old_state_p = reinterpret_cast<thread_state_t>(&old_state); |
| + old_state_count = arraysize(old_state); |
| + |
| + // new_state and new_state_count are out parameters that the server should |
| + // never see or use, so set them to bogus values. The call to the server |
| + // should overwrite these. |
| + memset(new_state, 0xa5, sizeof(new_state)); |
| + new_state_p = reinterpret_cast<thread_state_t>(&new_state); |
| + new_state_count = 0x5a; |
| + new_state_count_p = &new_state_count; |
| + } |
| + |
| + EXPECT_EQ(KERN_SUCCESS, UniversalExceptionRaise(behavior_, |
| + RemotePort(), |
| + thread, |
| + task, |
| + exception, |
| + code, |
| + arraysize(code), |
| + flavor_p, |
| + old_state_p, |
| + old_state_count, |
| + new_state_p, |
| + new_state_count_p)); |
| + |
| + if (HasState()) { |
| + // Verify the out parameters. |
| + |
| + EXPECT_EQ(exception_ + 20, flavor); |
| + EXPECT_EQ(MACHINE_THREAD_STATE_COUNT, new_state_count); |
| + |
| + for (size_t index = 0; index < new_state_count; ++index) { |
| + EXPECT_EQ(MACHINE_THREAD_STATE_COUNT - index, new_state[index]); |
| + } |
| + } |
| + } |
| + |
| + exception_behavior_t BasicBehavior() const { |
| + return behavior_ & ~MACH_EXCEPTION_CODES; |
| + } |
| + |
| + bool HasIdentity() const { |
| + exception_behavior_t basic_behavior = BasicBehavior(); |
| + return basic_behavior == EXCEPTION_DEFAULT || |
| + basic_behavior == EXCEPTION_STATE_IDENTITY; |
| + } |
| + |
| + bool HasState() const { |
| + exception_behavior_t basic_behavior = BasicBehavior(); |
| + return basic_behavior == EXCEPTION_STATE || |
| + basic_behavior == EXCEPTION_STATE_IDENTITY; |
| + } |
| + |
| + // The behavior to test. |
| + exception_behavior_t behavior_; |
| + |
| + // If false, only fields required for the current value of behavior_ are set |
| + // in a call to UniversalExceptionRaise(). The thread and task fields are only |
| + // set for identity-carrying behaviors, and the flavor and state fields are |
| + // only set for state-carrying behaviors. If true, all fields are set |
| + // regardless of the behavior. Testing in both ways verifies that |
| + // UniversalExceptionRaise() can tolerate the null arguments documented as |
| + // usable when the behavior allows it, and that it ignores these arguments |
| + // even when set when the behavior does not make use of them. |
| + bool all_fields_; |
| + |
| + // true if an exception message was handled. |
| + bool handled_; |
| + |
| + // These fields will increment for each instantiation of the test class. |
| + static exception_type_t exception_; |
| + static mach_exception_code_t exception_code_; |
| + static mach_exception_subcode_t exception_subcode_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(TestExcClientVariants); |
| +}; |
| + |
| +exception_type_t TestExcClientVariants::exception_ = 0; |
| + |
| +// exception_code_ and exception_subcode_ are always large enough to require |
| +// 64 bits, so that when the 32-bit-only variants not using MACH_EXCEPITON_CODES |
| +// are tested, the code and subcode fields can be checked for proper truncation. |
| +mach_exception_code_t TestExcClientVariants::exception_code_ = 0x100000000; |
| +mach_exception_subcode_t TestExcClientVariants::exception_subcode_ = |
| + 0xffffffff00000000; |
| + |
| +TEST(ExcClientVariants, UniversalExceptionRaise) { |
| + const exception_behavior_t kBehaviors[] = { |
| + EXCEPTION_DEFAULT, |
| + EXCEPTION_STATE, |
| + EXCEPTION_STATE_IDENTITY, |
| + kMachExceptionCodes | EXCEPTION_DEFAULT, |
| + kMachExceptionCodes | EXCEPTION_STATE, |
| + kMachExceptionCodes | EXCEPTION_STATE_IDENTITY, |
| + }; |
| + |
| + for (size_t index = 0; index < arraysize(kBehaviors); ++index) { |
| + exception_behavior_t behavior = kBehaviors[index]; |
| + SCOPED_TRACE(base::StringPrintf("index %zu, behavior %d", index, behavior)); |
| + |
| + { |
| + SCOPED_TRACE("all_fields = false"); |
| + |
| + TestExcClientVariants test_exc_client_variants(behavior, false); |
| + test_exc_client_variants.Run(); |
| + } |
| + |
| + { |
| + SCOPED_TRACE("all_fields = true"); |
| + |
| + TestExcClientVariants test_exc_client_variants(behavior, true); |
| + test_exc_client_variants.Run(); |
| + } |
| + } |
| +} |
| + |
| +} // namespace |