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