diff --git a/crashpad.doxy b/crashpad.doxy index ea085cfd..45f714ca 100644 --- a/crashpad.doxy +++ b/crashpad.doxy @@ -1944,6 +1944,7 @@ INCLUDE_FILE_PATTERNS = # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. PREDEFINED = COMPILE_ASSERT(a,b)= \ + DOXYGEN \ __attribute__(x)= # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this diff --git a/util/misc/initialization_state.h b/util/misc/initialization_state.h new file mode 100644 index 00000000..b5a72031 --- /dev/null +++ b/util/misc/initialization_state.h @@ -0,0 +1,96 @@ +// 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. + +#ifndef CRASHPAD_UTIL_MISC_INITIALIZATION_INITIALIZATION_STATE_H_ +#define CRASHPAD_UTIL_MISC_INITIALIZATION_INITIALIZATION_STATE_H_ + +#include + +#include "base/basictypes.h" + +namespace crashpad { + +//! \brief Tracks whether data are initialized. +//! +//! Objects of this type track whether the data they’re guarding are +//! initialized. The three possible states are uninitialized (the initial +//! state), initializing, and valid. As the guarded data are initialized, an +//! InitializationState object will normally transition through these three +//! states. A fourth state corresponds to the destruction of objects of this +//! type, making it less likely that a use-after-free of an InitializationState +//! object will appear in the valid state. +//! +//! If the only purpose for tracking the initialization state of guarded data is +//! to DCHECK when the object is in an unexpected state, use +//! InitializationStateDcheck instead. +class InitializationState { + public: + //! \brief The object’s state. + enum State : uint8_t { + //! \brief The object has not yet been initialized. + kStateUninitialized = 0, + + //! \brief The object is being initialized. + //! + //! This state protects against attempted reinitializaton of + //! partially-initialized objects whose initial initialization attempt + //! failed. This state is to be used while objects are initializing, but are + //! not yet fully initialized. + kStateInvalid, + + //! \brief The object has been initialized. + kStateValid, + + //! \brief The object has been destroyed. + kStateDestroyed, + }; + + InitializationState() : state_(kStateUninitialized) {} + ~InitializationState() { state_ = kStateDestroyed; } + + //! \brief Returns `true` if the object’s state is #kStateUninitialized and it + //! is safe to begin initializing it. + bool is_uninitialized() const { return state_ == kStateUninitialized; } + + //! \brief Sets the object’s state to #kStateInvalid, marking initialization + //! as being in process. + void set_invalid() { state_ = kStateInvalid; } + + //! \brief Sets the object’s state to #kStateValid, marking it initialized. + void set_valid() { state_ = kStateValid; } + + //! \brief Returns `true` if the the object’s state is #kStateValid and it has + //! been fully initialized and may be used. + bool is_valid() const { return state_ == kStateValid; } + + protected: + //! \brief Returns the object’s state. + //! + //! Consumers of this class should use an is_state_*() method instead. + State state() const { return state_; } + + //! \brief Sets the object’s state. + //! + //! Consumers of this class should use a set_state_*() method instead. + void set_state(State state) { state_ = state; } + + private: + State state_; + + DISALLOW_COPY_AND_ASSIGN(InitializationState); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_MISC_INITIALIZATION_INITIALIZATION_STATE_H_ diff --git a/util/misc/initialization_state_dcheck.cc b/util/misc/initialization_state_dcheck.cc new file mode 100644 index 00000000..ac584dc6 --- /dev/null +++ b/util/misc/initialization_state_dcheck.cc @@ -0,0 +1,41 @@ +// 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/misc/initialization_state_dcheck.h" + +namespace crashpad { + +#if DCHECK_IS_ON + +InitializationStateDcheck::State InitializationStateDcheck::SetInitializing() { + State old_state = state(); + if (old_state == kStateUninitialized) { + set_invalid(); + } + + return old_state; +} + +InitializationStateDcheck::State InitializationStateDcheck::SetValid() { + State old_state = state(); + if (old_state == kStateInvalid) { + set_valid(); + } + + return old_state; +} + +#endif + +} // namespace crashpad diff --git a/util/misc/initialization_state_dcheck.h b/util/misc/initialization_state_dcheck.h new file mode 100644 index 00000000..11501bc7 --- /dev/null +++ b/util/misc/initialization_state_dcheck.h @@ -0,0 +1,181 @@ +// 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. + +#ifndef CRASHPAD_UTIL_MISC_INITIALIZATION_INITIALIZATION_STATE_DCHECK_H_ +#define CRASHPAD_UTIL_MISC_INITIALIZATION_INITIALIZATION_STATE_DCHECK_H_ + +//! \file + +#include "base/basictypes.h" +#include "base/logging.h" +#include "util/misc/initialization_state.h" + +namespace crashpad { + +#if DCHECK_IS_ON || DOXYGEN + +//! \brief Tracks whether data are initialized, triggering a DCHECK assertion +//! on an invalid data access. +//! +//! Put an InitializationStateDcheck member into a class to help DCHECK that +//! it’s in the right states at the right times. This is useful for classes with +//! Initialize() methods. The chief advantage of InitializationStateDcheck over +//! having a member variable to track state is that when the only use of the +//! variable is to DCHECK, it wastes space (in memory and executable code) in +//! non-DCHECK builds unless the code is also peppered with ugly #ifdefs. +//! +//! This implementation concentrates the ugly #ifdefs in one location. +//! +//! Usage: +//! +//! \code +//! class Class { +//! public: +//! Class() : initialized_() {} +//! +//! void Initialize() { +//! INITIALIZATION_STATE_SET_INITIALIZING(initialized_); +//! // Perform initialization. +//! INITIALIZATION_STATE_SET_VALID(initialized_); +//! } +//! +//! void DoSomething() { +//! INITIALIZATION_STATE_DCHECK_VALID(initialized_); +//! // Do something. +//! } +//! +//! private: +//! InitializationStateDcheck initialized_; +//! }; +//! \endcode +class InitializationStateDcheck : public InitializationState { + public: + InitializationStateDcheck() : InitializationState() {} + + //! \brief Returns the object’s state. + //! + //! Consumers of this class should not call this method. Use the + //! INITIALIZATION_STATE_SET_INITIALIZING(), INITIALIZATION_STATE_SET_VALID(), + //! and INITIALIZATION_STATE_DCHECK_VALID() macros instead. + // + // The superclass’ state() accessor is protected, but it needs to be exposed + // to consumers of this class for the macros below to work properly. The + // macros prefer access to the unerlying state value over a simple boolean + // because with access to the state value, DCHECK_EQ can be used, which, when + // tripped, prints both the expected and observed values. This can aid + // troubleshooting. + State state() const { return InitializationState::state(); } + + //! \brief Marks an uninitialized object as initializing. + //! + //! If the object is in the #kStateUninitialized state, changes its state to + //! #kStateInvalid (initializing) and returns the previous + //! (#kStateUninitialized) state. Otherwise, returns the object’s current + //! state. + //! + //! Consumers of this class should not call this method. Use the + //! INITIALIZATION_STATE_SET_INITIALIZING() macro instead. + State SetInitializing(); + + //! \brief Marks an initializing object as valid. + //! + //! If the object is in the #kStateInvalid (initializing) state, changes its + //! state to #kStateValid and returns the previous (#kStateInvalid) state. + //! Otherwise, returns the object’s current state. + //! + //! Consumers of this class should not call this method. Use the + //! INITIALIZATION_STATE_SET_VALID() macro instead. + State SetValid(); + + private: + DISALLOW_COPY_AND_ASSIGN(InitializationStateDcheck); +}; + +// Using macros enables the non-DCHECK no-op implementation below to be more +// compact and less intrusive. These are macros instead of methods that call +// DCHECK to enable the DCHECK failure message to point to the correct file and +// line number, and to allow additional messages to be streamed on failure with +// the << operator. + +//! \brief Checks that a crashpad::InitializationStateDcheck object is in the +//! crashpad::InitializationState::kStateUninitialized state, and changes +//! its state to initializing +//! (crashpad::InitializationState::kStateInvalid). +//! +//! If the object is not in the correct state, a DCHECK assertion is triggered +//! and the object’s state remains unchanged. +//! +//! \param[in] initialization_state_dcheck A crashpad::InitializationStateDcheck +//! object. +//! +//! \sa crashpad::InitializationStateDcheck +#define INITIALIZATION_STATE_SET_INITIALIZING(initialization_state_dcheck) \ + DCHECK_EQ((initialization_state_dcheck).SetInitializing(), \ + (initialization_state_dcheck).kStateUninitialized) + +//! \brief Checks that a crashpad::InitializationStateDcheck object is in the +//! initializing (crashpad::InitializationState::kStateInvalid) state, and +//! changes its state to crashpad::InitializationState::kStateValid. +//! +//! If the object is not in the correct state, a DCHECK assertion is triggered +//! and the object’s state remains unchanged. +//! +//! \param[in] initialization_state_dcheck A crashpad::InitializationStateDcheck +//! object. +//! +//! \sa crashpad::InitializationStateDcheck +#define INITIALIZATION_STATE_SET_VALID(initialization_state_dcheck) \ + DCHECK_EQ((initialization_state_dcheck).SetValid(), \ + (initialization_state_dcheck).kStateInvalid) + +//! \brief Checks that a crashpad::InitializationStateDcheck object is in the +//! crashpad::InitializationState::kStateValid state. +//! +//! If the object is not in the correct state, a DCHECK assertion is triggered. +//! +//! \param[in] initialization_state_dcheck A crashpad::InitializationStateDcheck +//! object. +//! +//! \sa crashpad::InitializationStateDcheck +#define INITIALIZATION_STATE_DCHECK_VALID(initialization_state_dcheck) \ + DCHECK_EQ((initialization_state_dcheck).state(), \ + (initialization_state_dcheck).kStateValid) + +#else + +// Since this is to be used as a DCHECK (for debugging), it should be +// non-intrusive in non-DCHECK (non-debug, release) builds. An empty struct +// would still have a nonzero size (rationale: +// http://www.stroustrup.com/bs_faq2.html#sizeof-empty). Zero-length arrays are +// technically invalid according to the standard, but clang and g++ accept them +// without complaint even with warnings turned up. They take up no space at all, +// and they can be “initialized” with the same () syntax used to initialize +// objects of the DCHECK_IS_ON InitializationStateDcheck class above. +typedef bool InitializationStateDcheck[0]; + +// The contents of these DCHECKs will never be evaluated, but they make use of +// initialization_state_dcheck to avoid triggering -Wunused-private-field +// warnings. +#define INITIALIZATION_STATE_SET_INITIALIZING(initialization_state_dcheck) \ + DCHECK(&(initialization_state_dcheck)) +#define INITIALIZATION_STATE_SET_VALID(initialization_state_dcheck) \ + DCHECK(&(initialization_state_dcheck)) +#define INITIALIZATION_STATE_DCHECK_VALID(initialization_state_dcheck) \ + DCHECK(&(initialization_state_dcheck)) + +#endif + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_MISC_INITIALIZATION_INITIALIZATION_STATE_DCHECK_H_ diff --git a/util/misc/initialization_state_dcheck_test.cc b/util/misc/initialization_state_dcheck_test.cc new file mode 100644 index 00000000..57a4e54a --- /dev/null +++ b/util/misc/initialization_state_dcheck_test.cc @@ -0,0 +1,128 @@ +// 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/misc/initialization_state_dcheck.h" + +#include "base/logging.h" +#include "gtest/gtest.h" + +namespace { + +using namespace crashpad; + +TEST(InitializationStateDcheck, InitializationStateDcheck) { + InitializationStateDcheck initialization_state_dcheck; + INITIALIZATION_STATE_SET_INITIALIZING(initialization_state_dcheck); + INITIALIZATION_STATE_SET_VALID(initialization_state_dcheck); + INITIALIZATION_STATE_DCHECK_VALID(initialization_state_dcheck); +} + +#if DCHECK_IS_ON + +// InitializationStateDcheck only DCHECKs, so the death tests can only run +// when DCHECKs are enabled. + +TEST(InitializationStateDcheckDeathTest, Uninitialized_NotInvalid) { + // This tests that an attempt to set an uninitialized object as valid without + // transitioning through the initializing (invalid) state fails. + InitializationStateDcheck initialization_state_dcheck; + ASSERT_DEATH(INITIALIZATION_STATE_SET_VALID(initialization_state_dcheck), + "kStateInvalid"); +} + +TEST(InitializationStateDcheckDeathTest, Uninitialized_NotValid) { + // This tests that an attempt to use an uninitialized object as though it + // were valid fails. + InitializationStateDcheck initialization_state_dcheck; + ASSERT_DEATH(INITIALIZATION_STATE_DCHECK_VALID(initialization_state_dcheck), + "kStateValid"); +} + +TEST(InitializationStateDcheckDeathTest, Invalid_NotUninitialized) { + // This tests that an attempt to begin initializing an object on which + // initialization was already attempted fails. + InitializationStateDcheck initialization_state_dcheck; + INITIALIZATION_STATE_SET_INITIALIZING(initialization_state_dcheck); + ASSERT_DEATH( + INITIALIZATION_STATE_SET_INITIALIZING(initialization_state_dcheck), + "kStateUninitialized"); +} + +TEST(InitializationStateDcheckDeathTest, Invalid_NotValid) { + // This tests that an attempt to use an initializing object as though it + // were valid fails. + InitializationStateDcheck initialization_state_dcheck; + INITIALIZATION_STATE_SET_INITIALIZING(initialization_state_dcheck); + ASSERT_DEATH(INITIALIZATION_STATE_DCHECK_VALID(initialization_state_dcheck), + "kStateValid"); +} + +TEST(InitializationStateDcheckDeathTest, Valid_NotUninitialized) { + // This tests that an attempt to begin initializing an object that has already + // been initialized fails. + InitializationStateDcheck initialization_state_dcheck; + INITIALIZATION_STATE_SET_INITIALIZING(initialization_state_dcheck); + INITIALIZATION_STATE_SET_VALID(initialization_state_dcheck); + ASSERT_DEATH( + INITIALIZATION_STATE_SET_INITIALIZING(initialization_state_dcheck), + "kStateUninitialized"); +} + +TEST(InitializationStateDcheckDeathTest, Valid_NotInvalid) { + // This tests that an attempt to set a valid object as valid a second time + // fails. + InitializationStateDcheck initialization_state_dcheck; + INITIALIZATION_STATE_SET_INITIALIZING(initialization_state_dcheck); + INITIALIZATION_STATE_SET_VALID(initialization_state_dcheck); + ASSERT_DEATH(INITIALIZATION_STATE_SET_VALID(initialization_state_dcheck), + "kStateInvalid"); +} + +TEST(InitializationStateDcheckDeathTest, Destroyed_NotUninitialized) { + // This tests that an attempt to reinitialize a destroyed object fails. See + // the InitializationState.InitializationState test for an explanation of this + // use-after-free test. + InitializationStateDcheck* initialization_state_dcheck_pointer; + { + InitializationStateDcheck initialization_state_dcheck; + initialization_state_dcheck_pointer = &initialization_state_dcheck; + INITIALIZATION_STATE_SET_INITIALIZING(initialization_state_dcheck); + INITIALIZATION_STATE_SET_VALID(initialization_state_dcheck); + INITIALIZATION_STATE_DCHECK_VALID(initialization_state_dcheck); + } + ASSERT_DEATH(INITIALIZATION_STATE_SET_INITIALIZING( + *initialization_state_dcheck_pointer), + "kStateUninitialized"); +} + +TEST(InitializationStateDcheckDeathTest, Destroyed_NotValid) { + // This tests that an attempt to use a destroyed object fails. See the + // InitializationState.InitializationState test for an explanation of this + // use-after-free test. + InitializationStateDcheck* initialization_state_dcheck_pointer; + { + InitializationStateDcheck initialization_state_dcheck; + initialization_state_dcheck_pointer = &initialization_state_dcheck; + INITIALIZATION_STATE_SET_INITIALIZING(initialization_state_dcheck); + INITIALIZATION_STATE_SET_VALID(initialization_state_dcheck); + INITIALIZATION_STATE_DCHECK_VALID(initialization_state_dcheck); + } + ASSERT_DEATH( + INITIALIZATION_STATE_DCHECK_VALID(*initialization_state_dcheck_pointer), + "kStateValid"); +} + +#endif + +} // namespace diff --git a/util/misc/initialization_state_test.cc b/util/misc/initialization_state_test.cc new file mode 100644 index 00000000..8330f301 --- /dev/null +++ b/util/misc/initialization_state_test.cc @@ -0,0 +1,56 @@ +// 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/misc/initialization_state.h" + +#include "gtest/gtest.h" + +namespace { + +using namespace crashpad; + +TEST(InitializationState, InitializationState) { + InitializationState* initialization_state_pointer; + { + InitializationState initialization_state; + initialization_state_pointer = &initialization_state; + + EXPECT_TRUE(initialization_state.is_uninitialized()); + EXPECT_FALSE(initialization_state.is_valid()); + + initialization_state.set_invalid(); + + EXPECT_FALSE(initialization_state.is_uninitialized()); + EXPECT_FALSE(initialization_state.is_valid()); + + initialization_state.set_valid(); + + EXPECT_FALSE(initialization_state.is_uninitialized()); + EXPECT_TRUE(initialization_state.is_valid()); + } + + // initialization_state_pointer points to something that no longer exists. + // This portion of the test is intended to check that after an + // InitializationState object goes out of scope, it will not be considered + // valid on a use-after-free, assuming that nothing else was written to its + // former home in memory. + // + // This portion of the test is technically not valid C++, but it exists to + // test that the behavior is as desired when other code uses the language + // improperly. + EXPECT_FALSE(initialization_state_pointer->is_uninitialized()); + EXPECT_FALSE(initialization_state_pointer->is_valid()); +} + +} // namespace diff --git a/util/util.gyp b/util/util.gyp index f476dc68..eafb4922 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -36,6 +36,9 @@ 'mac/launchd.mm', 'mach/task_memory.cc', 'mach/task_memory.h', + 'misc/initialization_state.h', + 'misc/initialization_state_dcheck.cc', + 'misc/initialization_state_dcheck.h', 'misc/uuid.cc', 'misc/uuid.h', 'stdlib/cxx.h', @@ -80,6 +83,8 @@ 'file/string_file_writer_test.cc', 'mac/launchd_test.mm', 'mach/task_memory_test.cc', + 'misc/initialization_state_dcheck_test.cc', + 'misc/initialization_state_test.cc', 'misc/uuid_test.cc', 'stdlib/strlcpy_test.cc', ],