• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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