xref: /aosp_15_r20/external/abseil-cpp/absl/hash/internal/spy_hash_state.h (revision 9356374a3709195abf420251b3e825997ff56c0f)
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