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