1 // Copyright 2018 The Abseil Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #ifndef ABSL_HASH_INTERNAL_SPY_HASH_STATE_H_ 16 #define ABSL_HASH_INTERNAL_SPY_HASH_STATE_H_ 17 18 #include <algorithm> 19 #include <ostream> 20 #include <string> 21 #include <vector> 22 23 #include "absl/hash/hash.h" 24 #include "absl/strings/match.h" 25 #include "absl/strings/str_format.h" 26 #include "absl/strings/str_join.h" 27 28 namespace absl { 29 ABSL_NAMESPACE_BEGIN 30 namespace hash_internal { 31 32 // SpyHashState is an implementation of the HashState API that simply 33 // accumulates all input bytes in an internal buffer. This makes it useful 34 // for testing AbslHashValue overloads (so long as they are templated on the 35 // HashState parameter), since it can report the exact hash representation 36 // that the AbslHashValue overload produces. 37 // 38 // Sample usage: 39 // EXPECT_EQ(SpyHashState::combine(SpyHashState(), foo), 40 // SpyHashState::combine(SpyHashState(), bar)); 41 template <typename T> 42 class SpyHashStateImpl : public HashStateBase<SpyHashStateImpl<T>> { 43 public: SpyHashStateImpl()44 SpyHashStateImpl() : error_(std::make_shared<absl::optional<std::string>>()) { 45 static_assert(std::is_void<T>::value, ""); 46 } 47 48 // Move-only 49 SpyHashStateImpl(const SpyHashStateImpl&) = delete; 50 SpyHashStateImpl& operator=(const SpyHashStateImpl&) = delete; 51 SpyHashStateImpl(SpyHashStateImpl && other)52 SpyHashStateImpl(SpyHashStateImpl&& other) noexcept { 53 *this = std::move(other); 54 } 55 56 SpyHashStateImpl& operator=(SpyHashStateImpl&& other) noexcept { 57 hash_representation_ = std::move(other.hash_representation_); 58 error_ = other.error_; 59 moved_from_ = other.moved_from_; 60 other.moved_from_ = true; 61 return *this; 62 } 63 64 template <typename U> SpyHashStateImpl(SpyHashStateImpl<U> && other)65 SpyHashStateImpl(SpyHashStateImpl<U>&& other) { // NOLINT 66 hash_representation_ = std::move(other.hash_representation_); 67 error_ = other.error_; 68 moved_from_ = other.moved_from_; 69 other.moved_from_ = true; 70 } 71 72 template <typename A, typename... Args> combine(SpyHashStateImpl s,const A & a,const Args &...args)73 static SpyHashStateImpl combine(SpyHashStateImpl s, const A& a, 74 const Args&... args) { 75 // Pass an instance of SpyHashStateImpl<A> when trying to combine `A`. This 76 // allows us to test that the user only uses this instance for combine calls 77 // and does not call AbslHashValue directly. 78 // See AbslHashValue implementation at the bottom. 79 s = SpyHashStateImpl<A>::HashStateBase::combine(std::move(s), a); 80 return SpyHashStateImpl::combine(std::move(s), args...); 81 } combine(SpyHashStateImpl s)82 static SpyHashStateImpl combine(SpyHashStateImpl s) { 83 if (direct_absl_hash_value_error_) { 84 *s.error_ = "AbslHashValue should not be invoked directly."; 85 } else if (s.moved_from_) { 86 *s.error_ = "Used moved-from instance of the hash state object."; 87 } 88 return s; 89 } 90 SetDirectAbslHashValueError()91 static void SetDirectAbslHashValueError() { 92 direct_absl_hash_value_error_ = true; 93 } 94 95 // Two SpyHashStateImpl objects are equal if they hold equal hash 96 // representations. 97 friend bool operator==(const SpyHashStateImpl& lhs, 98 const SpyHashStateImpl& rhs) { 99 return lhs.hash_representation_ == rhs.hash_representation_; 100 } 101 102 friend bool operator!=(const SpyHashStateImpl& lhs, 103 const SpyHashStateImpl& rhs) { 104 return !(lhs == rhs); 105 } 106 107 enum class CompareResult { 108 kEqual, 109 kASuffixB, 110 kBSuffixA, 111 kUnequal, 112 }; 113 Compare(const SpyHashStateImpl & a,const SpyHashStateImpl & b)114 static CompareResult Compare(const SpyHashStateImpl& a, 115 const SpyHashStateImpl& b) { 116 const std::string a_flat = absl::StrJoin(a.hash_representation_, ""); 117 const std::string b_flat = absl::StrJoin(b.hash_representation_, ""); 118 if (a_flat == b_flat) return CompareResult::kEqual; 119 if (absl::EndsWith(a_flat, b_flat)) return CompareResult::kBSuffixA; 120 if (absl::EndsWith(b_flat, a_flat)) return CompareResult::kASuffixB; 121 return CompareResult::kUnequal; 122 } 123 124 // operator<< prints the hash representation as a hex and ASCII dump, to 125 // facilitate debugging. 126 friend std::ostream& operator<<(std::ostream& out, 127 const SpyHashStateImpl& hash_state) { 128 out << "[\n"; 129 for (auto& s : hash_state.hash_representation_) { 130 size_t offset = 0; 131 for (char c : s) { 132 if (offset % 16 == 0) { 133 out << absl::StreamFormat("\n0x%04x: ", offset); 134 } 135 if (offset % 2 == 0) { 136 out << " "; 137 } 138 out << absl::StreamFormat("%02x", c); 139 ++offset; 140 } 141 out << "\n"; 142 } 143 return out << "]"; 144 } 145 146 // The base case of the combine recursion, which writes raw bytes into the 147 // internal buffer. combine_contiguous(SpyHashStateImpl hash_state,const unsigned char * begin,size_t size)148 static SpyHashStateImpl combine_contiguous(SpyHashStateImpl hash_state, 149 const unsigned char* begin, 150 size_t size) { 151 const size_t large_chunk_stride = PiecewiseChunkSize(); 152 // Combining a large contiguous buffer must have the same effect as 153 // doing it piecewise by the stride length, followed by the (possibly 154 // empty) remainder. 155 while (size > large_chunk_stride) { 156 hash_state = SpyHashStateImpl::combine_contiguous( 157 std::move(hash_state), begin, large_chunk_stride); 158 begin += large_chunk_stride; 159 size -= large_chunk_stride; 160 } 161 162 if (size > 0) { 163 hash_state.hash_representation_.emplace_back( 164 reinterpret_cast<const char*>(begin), size); 165 } 166 return hash_state; 167 } 168 169 using SpyHashStateImpl::HashStateBase::combine_contiguous; 170 171 template <typename CombinerT> RunCombineUnordered(SpyHashStateImpl state,CombinerT combiner)172 static SpyHashStateImpl RunCombineUnordered(SpyHashStateImpl state, 173 CombinerT combiner) { 174 UnorderedCombinerCallback cb; 175 176 combiner(SpyHashStateImpl<void>{}, std::ref(cb)); 177 178 std::sort(cb.element_hash_representations.begin(), 179 cb.element_hash_representations.end()); 180 state.hash_representation_.insert(state.hash_representation_.end(), 181 cb.element_hash_representations.begin(), 182 cb.element_hash_representations.end()); 183 if (cb.error && cb.error->has_value()) { 184 state.error_ = std::move(cb.error); 185 } 186 return state; 187 } 188 error()189 absl::optional<std::string> error() const { 190 if (moved_from_) { 191 return "Returned a moved-from instance of the hash state object."; 192 } 193 return *error_; 194 } 195 196 private: 197 template <typename U> 198 friend class SpyHashStateImpl; 199 200 struct UnorderedCombinerCallback { 201 std::vector<std::string> element_hash_representations; 202 std::shared_ptr<absl::optional<std::string>> error; 203 204 // The inner spy can have a different type. 205 template <typename U> operatorUnorderedCombinerCallback206 void operator()(SpyHashStateImpl<U>& inner) { 207 element_hash_representations.push_back( 208 absl::StrJoin(inner.hash_representation_, "")); 209 if (inner.error_->has_value()) { 210 error = std::move(inner.error_); 211 } 212 inner = SpyHashStateImpl<void>{}; 213 } 214 }; 215 216 // This is true if SpyHashStateImpl<T> has been passed to a call of 217 // AbslHashValue with the wrong type. This detects that the user called 218 // AbslHashValue directly (because the hash state type does not match). 219 static bool direct_absl_hash_value_error_; 220 221 std::vector<std::string> hash_representation_; 222 // This is a shared_ptr because we want all instances of the particular 223 // SpyHashState run to share the field. This way we can set the error for 224 // use-after-move and all the copies will see it. 225 std::shared_ptr<absl::optional<std::string>> error_; 226 bool moved_from_ = false; 227 }; 228 229 template <typename T> 230 bool SpyHashStateImpl<T>::direct_absl_hash_value_error_; 231 232 template <bool& B> 233 struct OdrUse { OdrUseOdrUse234 constexpr OdrUse() {} 235 bool& b = B; 236 }; 237 238 template <void (*)()> 239 struct RunOnStartup { 240 static bool run; 241 static constexpr OdrUse<run> kOdrUse{}; 242 }; 243 244 template <void (*f)()> 245 bool RunOnStartup<f>::run = (f(), true); 246 247 template < 248 typename T, typename U, 249 // Only trigger for when (T != U), 250 typename = absl::enable_if_t<!std::is_same<T, U>::value>, 251 // This statement works in two ways: 252 // - First, it instantiates RunOnStartup and forces the initialization of 253 // `run`, which set the global variable. 254 // - Second, it triggers a SFINAE error disabling the overload to prevent 255 // compile time errors. If we didn't disable the overload we would get 256 // ambiguous overload errors, which we don't want. 257 int = RunOnStartup<SpyHashStateImpl<T>::SetDirectAbslHashValueError>::run> 258 void AbslHashValue(SpyHashStateImpl<T>, const U&); 259 260 using SpyHashState = SpyHashStateImpl<void>; 261 262 } // namespace hash_internal 263 ABSL_NAMESPACE_END 264 } // namespace absl 265 266 #endif // ABSL_HASH_INTERNAL_SPY_HASH_STATE_H_ 267