1 // Copyright 2020 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef BASE_STATE_TRANSITIONS_H_ 6 #define BASE_STATE_TRANSITIONS_H_ 7 8 #include <vector> 9 10 #include "base/check_op.h" 11 #include "base/containers/contains.h" 12 #include "base/no_destructor.h" 13 14 namespace base { 15 16 // This class represents a set of state transitions where each state is a value 17 // that supports copy, << and == (e.g. an enum element). It's intended to be 18 // used in DCHECK-enabled builds to check that only valid transitions occur. Its 19 // implementation favours convenience and simplicity over performance. To use it 20 // follow this example: 21 22 // In foo.h 23 // --------- 24 // enum class State { 25 // kState1, 26 // kState2, 27 // kState3, 28 // }; 29 // 30 // // This may require exporting the symbol (e.g. CONTENT_EXPORT) if it will be 31 // // used by any other components: one common way this can happen is if the 32 // // enum is logged in tests (e.g. via gtest's EXPECT_* macros). 33 // std::ostream& operator<<(std::ostream& o, const State& s); 34 // --------- 35 // 36 // In foo.cc 37 // --------- 38 // #include "base/no_destructor.h" 39 // #include "base/state_transitions.h" 40 // 41 // std::ostream& operator<<(std::ostream& o, const State& s) { 42 // return o << static_cast<int>(s); 43 // } 44 // 45 // void DCheckStateTransition(State old_state, State new_state) { 46 // #if DCHECK_IS_ON() 47 // static const base::NoDestructor<StateTransitions<State>> transitions( 48 // StateTransitions<State>({ 49 // {kState1, {kState2, kState3}}, 50 // {kState2, {kState3}}, 51 // {kState3, {}}, 52 // })); 53 // DCHECK_STATE_TRANSITION(transitions, old_state, new_state); 54 // #endif // DCHECK_IS_ON() 55 // } 56 // --------- 57 58 template <typename State> 59 struct StateTransitions { 60 public: 61 // Represents a state and all of the states that are valid transitions from 62 // it. 63 struct StateTransition { StateTransitionStateTransitions::StateTransition64 StateTransition(State source, std::vector<State> destinations) 65 : source(std::move(source)), destinations(std::move(destinations)) {} 66 67 const State source; 68 const std::vector<State> destinations; 69 }; 70 StateTransitionsStateTransitions71 explicit StateTransitions(std::vector<StateTransition> state_transitions) 72 : state_transitions(std::move(state_transitions)) {} 73 74 // Returns a list of states that are valid to transition to from |source|. GetValidTransitionsStateTransitions75 const std::vector<State>& GetValidTransitions(const State& source) const { 76 for (const StateTransition& state_transition : state_transitions) { 77 if (state_transition.source == source) 78 return state_transition.destinations; 79 } 80 static const base::NoDestructor<std::vector<State>> no_transitions; 81 return *no_transitions; 82 } 83 84 // Tests whether transitioning from |source| to |destination| is valid. IsTransitionValidStateTransitions85 bool IsTransitionValid(const State& source, const State& destination) const { 86 return base::Contains(GetValidTransitions(source), destination); 87 } 88 89 const std::vector<StateTransition> state_transitions; 90 }; 91 92 // DCHECK if transitioning from |old_state| to |new_state| is not valid 93 // according to |transitions|. 94 #define DCHECK_STATE_TRANSITION(transitions, old_state, new_state) \ 95 DCHECK((transitions)->IsTransitionValid((old_state), (new_state))) \ 96 << "Invalid transition: " << old_state << " -> " << new_state 97 98 } // namespace base 99 100 #endif // BASE_STATE_TRANSITIONS_H_ 101