diff --git a/llvm/include/llvm/ADT/ArrayRef.h b/llvm/include/llvm/ADT/ArrayRef.h --- a/llvm/include/llvm/ADT/ArrayRef.h +++ b/llvm/include/llvm/ADT/ArrayRef.h @@ -11,9 +11,10 @@ #include "llvm/ADT/Hashing.h" #include "llvm/ADT/None.h" -#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/Support/Compiler.h" +#include "llvm/Support/HashBuilder.h" #include #include #include @@ -571,6 +572,35 @@ return hash_combine_range(S.begin(), S.end()); } + /// Support hashing via `HashBuilder` for hashable data types, e.g. integral, + /// enum, or floating-point values. + /// + /// `Value.size()` is taken into account to ensure cases like + /// ``` + /// builder.update({1}); + /// builder.update({2, 3}); + /// ``` + /// and + /// ``` + /// builder.update({1, 2}); + /// builder.update({3}); + /// ``` + /// do not collide. + template + std::enable_if_t::value> + updateHash(HashBuilder &HBuilder, ArrayRef Value) { + HBuilder.update(Value.size()); + HBuilder.updateBytes(reinterpret_cast(Value.data()), + Value.size() * sizeof(T)); + } + + /// Support hashing via `HashBuilder` for non-hashable data types. + template + std::enable_if_t::value> + updateHash(HashBuilder &HBuilder, ArrayRef Value) { + HBuilder.updateRange(Value.begin(), Value.end()); + } + // Provide DenseMapInfo for ArrayRefs. template struct DenseMapInfo> { static inline ArrayRef getEmptyKey() { diff --git a/llvm/include/llvm/ADT/StringRef.h b/llvm/include/llvm/ADT/StringRef.h --- a/llvm/include/llvm/ADT/StringRef.h +++ b/llvm/include/llvm/ADT/StringRef.h @@ -12,6 +12,7 @@ #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/iterator_range.h" #include "llvm/Support/Compiler.h" +#include "llvm/Support/HashBuilder.h" #include #include #include @@ -948,6 +949,26 @@ LLVM_NODISCARD hash_code hash_value(StringRef S); + /// Support hashing via `HashBuilder`. + /// + /// `Value.size()` is taken into account to ensure cases like + /// ``` + /// builder.update("a"); + /// builder.update("bc"); + /// ``` + /// and + /// ``` + /// builder.update("ab"); + /// builder.update("c"); + /// ``` + /// do not collide. + template + void updateHash(HashBuilder &HBuilder, StringRef Value) { + HBuilder.update(Value.size()); + HBuilder.updateBytes(reinterpret_cast(Value.data()), + Value.size()); + } + // Provide DenseMapInfo for StringRefs. template <> struct DenseMapInfo { static inline StringRef getEmptyKey() { diff --git a/llvm/include/llvm/Support/HashBuilder.h b/llvm/include/llvm/Support/HashBuilder.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/Support/HashBuilder.h @@ -0,0 +1,206 @@ +//===- llvm/Support/HashBuilder.h - Convenient hashing interface-*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements an interface allowing to conveniently build hashes of +// various data types, without relying on the underlying hasher type to know +// about hashed data types. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_HASHBUILDER_H +#define LLVM_SUPPORT_HASHBUILDER_H + +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/type_traits.h" + +#include +#include + +namespace llvm { + +namespace hash_builder { +namespace detail { +/// Trait to indicate whether a type's bits can be hashed directly. +/// A type for which this trait is true will be treated like a bag of bytes by +/// `HashBuilder`. +template +struct is_hashable_data + : std::integral_constant::value || + std::is_floating_point::value> {}; +} // namespace detail +} // namespace hash_builder + +/// Interface to help hash various types through a hasher type. +/// +/// Via provided specializations of `update` and `updateRange` functions, +/// various types (e.g. `ArrayRef`, `StringRef`, etc.) can be hashed without +/// requiring any knowledge of hashed types from the hasher type. +/// +/// The only methods expected from the templated hasher type `HasherT` are: +/// * a default constructor +/// * void update(const uint8_t *Ptr, size_t Size) +/// +/// From a user point of view, the interface provides the following: +/// * `template update(const T &Value)` +/// The `update` function implements hashing of various types. +/// * `template void updateRange(ItT First, ItT Last)` +/// The `updateRange` function is designed to aid hashing a range of values. +/// +/// User-defined `struct` types can participate in this interface by providing +/// an `updateHash` templated function. See the associated template +/// specialization for details. +/// +/// This interface does not impose requirements on the hasher +/// `update(const uint8_t *Ptr, size_t Size)` method. +/// We want to avoid collisions for variable-size types; for example for +/// ``` +/// builder.update({1}); +/// builder.update({2, 3}); +/// ``` +/// and +/// ``` +/// builder.update({1, 2}); +/// builder.update({3}); +/// ``` +/// . Thus, specializations of `update` and `updateHash` for variable-size types +/// should not assume that the hasher type considers the size as part of the +/// hash; they should explicitly update the hash with the size. See for example +/// specializations for `ArrayRef` and `StringRef`. +template class HashBuilder { +public: + HasherT &Hasher; + + explicit HashBuilder(HasherT &Hasher) : Hasher(Hasher) {} + + template + using has_update_hash_t = + decltype(updateHash(std::declval(), std::declval())); + + /// Implement hashing for hashable data types, e.g. integral, enum, or + /// floating-point values. + /// + /// The value is simply treated like a bag of bytes. + template + std::enable_if_t::value> + update(T Value) { + updateBytes(reinterpret_cast(&Value), sizeof(Value)); + } + + /// Implement hashing for user-defined `struct`s. + /// + /// Any user-define `struct` can participate in hashing via `HashBuilder` by + /// providing a `updateHash` templated function: + // + /// ``` + /// template + /// void updateHash(HashBuilder &HBuilder, + /// const UserDefinedStruct &Value); + /// ``` + /// + /// For example: + /// + /// ``` + /// struct SimpleStruct { char c; int i; }; + // + /// template + /// void updateHash(HashBuilder &HBuilder, + /// const SimpleStruct &Value) { + /// HBuilder.update(Value.c); + /// HBuilder.update(Value.i); + /// } + /// + /// struct StructWithPrivateMember { + /// public: + /// explicit StructWithPrivateMember(int i, float f) : i(i), f(f) {} + /// + /// int i; + /// private: + /// float f; + /// + /// template + /// friend void updateHash(HashBuilder &HBuilder, + /// const StructWithPrivateMember& Value) { + /// HBuilder.update(Value.i); + /// HBuilder.update(Value.f); + /// } + /// }; + /// ``` + template + std::enable_if_t::value> + update(const T &Value) { + updateHash(*this, Value); + } + + template void update(const std::basic_string &Value) { + return updateRange(Value.begin(), Value.end()); + } + + template void update(std::pair Value) { + update(Value.first); + update(Value.second); + } + + template + typename std::enable_if<(sizeof...(Ts) > 1)>::type + update(std::tuple Arg) { + updateTupleHelper(Arg, typename std::index_sequence_for()); + } + + /// A convenenience variadic helper. + /// It simply iterates over its arguments, in order. + /// ``` + /// update(Arg1, Arg2); + /// ``` + /// is equivalent to + /// ``` + /// update(Arg1) + /// update(Arg2) + /// ``` + template + typename std::enable_if<(sizeof...(Ts) > 1)>::type update(const Ts &...Args) { + std::tuple Unused{(update(Args), Args)...}; + } + + template + void updateRange(InputIteratorT First, InputIteratorT Last) { + updateRangeImpl(First, Last); + } + + /// Update the hash with a "bag of bytes". This does not rely on `HasherT` to + /// take `Size` into account. + void updateBytes(const uint8_t *Ptr, size_t Size) { + Hasher.update(Ptr, Size); + } + +private: + template + void updateTupleHelper(const std::tuple &Arg, + std::index_sequence) { + std::tuple Unused{ + (update(std::get(Arg)), std::get(Arg))...}; + } + + template + std::enable_if_t::value> + updateRangeImpl(T *First, T *Last) { + update(Last - First); + updateBytes(reinterpret_cast(First), + (Last - First) * sizeof(T)); + } + + template + void updateRangeImpl(InputIteratorT First, InputIteratorT Last) { + update(Last - First); + for (auto It = First; It != Last; ++It) + update(*It); + } +}; + +} // end namespace llvm + +#endif // LLVM_SUPPORT_HASHBUILDER_H diff --git a/llvm/include/llvm/Support/MD5.h b/llvm/include/llvm/Support/MD5.h --- a/llvm/include/llvm/Support/MD5.h +++ b/llvm/include/llvm/Support/MD5.h @@ -84,6 +84,9 @@ /// Updates the hash for the byte stream provided. void update(ArrayRef Data); + /// Updates the hash for the byte stream provided. + void update(const uint8_t *Ptr, size_t Size); + /// Updates the hash for the StringRef provided. void update(StringRef Str); diff --git a/llvm/include/llvm/Support/SHA1.h b/llvm/include/llvm/Support/SHA1.h --- a/llvm/include/llvm/Support/SHA1.h +++ b/llvm/include/llvm/Support/SHA1.h @@ -33,6 +33,9 @@ /// Digest more data. void update(ArrayRef Data); + /// Digest more data. + void update(const uint8_t *Ptr, size_t Size); + /// Digest more data. void update(StringRef Str); diff --git a/llvm/include/llvm/Support/SHA256.h b/llvm/include/llvm/Support/SHA256.h --- a/llvm/include/llvm/Support/SHA256.h +++ b/llvm/include/llvm/Support/SHA256.h @@ -40,6 +40,9 @@ /// Digest more data. void update(ArrayRef Data); + /// Digest more data. + void update(const uint8_t *Ptr, size_t Size); + /// Digest more data. void update(StringRef Str); diff --git a/llvm/lib/Support/MD5.cpp b/llvm/lib/Support/MD5.cpp --- a/llvm/lib/Support/MD5.cpp +++ b/llvm/lib/Support/MD5.cpp @@ -187,11 +187,12 @@ MD5::MD5() = default; /// Incrementally add the bytes in \p Data to the hash. -void MD5::update(ArrayRef Data) { +void MD5::update(ArrayRef Data) { update(Data.data(), Data.size()); } + +/// Incrementally add bytes in to the hash. +void MD5::update(const uint8_t *Ptr, size_t Size) { MD5_u32plus saved_lo; unsigned long used, free; - const uint8_t *Ptr = Data.data(); - unsigned long Size = Data.size(); saved_lo = lo; if ((lo = (saved_lo + Size) & 0x1fffffff) < saved_lo) @@ -226,8 +227,7 @@ // Note that this isn't a string and so this won't include any trailing NULL // bytes. void MD5::update(StringRef Str) { - ArrayRef SVal((const uint8_t *)Str.data(), Str.size()); - update(SVal); + update(reinterpret_cast(Str.data()), Str.size()); } /// Finish the hash and place the resulting hash into \p result. diff --git a/llvm/lib/Support/SHA1.cpp b/llvm/lib/Support/SHA1.cpp --- a/llvm/lib/Support/SHA1.cpp +++ b/llvm/lib/Support/SHA1.cpp @@ -238,9 +238,12 @@ addUncounted(C); } +void SHA1::update(const uint8_t *Ptr, size_t Size) { + update(ArrayRef(Ptr, Size)); +} + void SHA1::update(StringRef Str) { - update( - ArrayRef((uint8_t *)const_cast(Str.data()), Str.size())); + update(reinterpret_cast(Str.data()), Str.size()); } void SHA1::pad() { diff --git a/llvm/lib/Support/SHA256.cpp b/llvm/lib/Support/SHA256.cpp --- a/llvm/lib/Support/SHA256.cpp +++ b/llvm/lib/Support/SHA256.cpp @@ -217,6 +217,10 @@ addUncounted(C); } +void SHA256::update(const uint8_t *Ptr, size_t Size) { + update(ArrayRef(Ptr, Size)); +} + void SHA256::update(StringRef Str) { update( ArrayRef((uint8_t *)const_cast(Str.data()), Str.size())); diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt --- a/llvm/unittests/Support/CMakeLists.txt +++ b/llvm/unittests/Support/CMakeLists.txt @@ -39,6 +39,7 @@ FormatVariadicTest.cpp FSUniqueIDTest.cpp GlobPatternTest.cpp + HashBuilderTest.cpp Host.cpp IndexedAccessorTest.cpp InstructionCostTest.cpp diff --git a/llvm/unittests/Support/HashBuilderTest.cpp b/llvm/unittests/Support/HashBuilderTest.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/Support/HashBuilderTest.cpp @@ -0,0 +1,259 @@ +//===- llvm/unittest/Support/HashBuilderTest.cpp - HashBuilder unit tests -===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/HashBuilder.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/MD5.h" +#include "llvm/Support/SHA1.h" +#include "llvm/Support/SHA256.h" +#include "gtest/gtest.h" + +#include + +using namespace llvm; + +using MD5Words = std::pair; + +// A helper to concisely compute a hash for variadic arguments. +template static MD5Words computeMD5Words(const Ts &...Args) { + MD5 MD5Hash; + HashBuilder HBuilder(MD5Hash); + + HBuilder.update(Args...); + + MD5::MD5Result MD5Res; + HBuilder.Hasher.final(MD5Res); + return MD5Res.words(); +} + +// A helper to concisely compute a hash for a range. +template +static MD5Words computeMD5WordsForRange(InputIteratorT First, + InputIteratorT Last) { + MD5 MD5Hash; + HashBuilder HBuilder(MD5Hash); + + HBuilder.updateRange(First, Last); + + MD5::MD5Result MD5Res; + HBuilder.Hasher.final(MD5Res); + return MD5Res.words(); +} + +enum TestEnumeration { TE_One = 1, TE_Two = 2 }; + +TEST(HashBuilderTest, BasicTestMD5) { + char c = 'c'; + int i = 1; + uint64_t ui64 = static_cast(1) << 50; + volatile int vi = 71; + const volatile int cvi = 72; + double d = 123.0; + + MD5 MD5Hash; + HashBuilder HBuilder(MD5Hash); + + HBuilder.update(c); + HBuilder.update(i); + HBuilder.update(ui64); + HBuilder.update(TE_One); + HBuilder.update(vi); + HBuilder.update(cvi); + HBuilder.update(d); + + MD5::MD5Result MD5Res; + HBuilder.Hasher.final(MD5Res); + EXPECT_EQ(MD5Res.words(), + MD5Words(0x7FE040B0B5311CE2ULL, 0xBE6766F347F32726ULL)); +} + +TEST(HashBuilderTest, BasicTestSHA1) { + char c = 'c'; + int i = 1; + uint64_t ui64 = static_cast(1) << 50; + volatile int vi = 71; + const volatile int cvi = 72; + double d = 123.0; + + SHA1 SHA1Hash; + HashBuilder HBuilder(SHA1Hash); + + HBuilder.update(c); + HBuilder.update(i); + HBuilder.update(ui64); + HBuilder.update(TE_One); + HBuilder.update(vi); + HBuilder.update(cvi); + HBuilder.update(d); + + EXPECT_EQ(SHA1Hash.final(), "\xEC\x87\x19" + "a\xC8\xCA.o%S\xA3\xEE\xC9\x9B" + "a\xB1" + "EX<\xCB"); +} + +TEST(HashBuilderTest, BasicTestSHA256) { + char c = 'c'; + int i = 1; + uint64_t ui64 = static_cast(1) << 50; + volatile int vi = 71; + const volatile int cvi = 72; + double d = 123.0; + + SHA256 SHA256Hash; + HashBuilder HBuilder(SHA256Hash); + + HBuilder.update(c); + HBuilder.update(i); + HBuilder.update(ui64); + HBuilder.update(TE_One); + HBuilder.update(vi); + HBuilder.update(cvi); + HBuilder.update(d); + + EXPECT_EQ(SHA256Hash.final(), + "urD\x16\xC4\x90\x90\xE7k\x8CH|J\x98\x1C\xAE\xEC~" + "\x1A\x88\xFF\xF8q\xD3\xCE-\x8A\x8D\xCF" + "F\xD6\x11"); +} + +struct SimpleStruct { + char c; + int i; +}; + +template +void updateHash(HashBuilder &HBuilder, const SimpleStruct &Value) { + HBuilder.update(Value.c); + HBuilder.update(Value.i); +} + +struct StructWithPrivateMember { +public: + explicit StructWithPrivateMember(std::string s, float f) : s(s), f(f) {} + + std::string s; + +private: + float f; + + template + friend void updateHash(HashBuilder &HBuilder, + const StructWithPrivateMember &Value) { + HBuilder.update(Value.s); + HBuilder.update(Value.f); + } +}; + +TEST(HashBuilderTest, HashUserDefinedStruct) { + EXPECT_EQ(computeMD5Words(SimpleStruct{'c', 123}), computeMD5Words('c', 123)); + EXPECT_EQ(computeMD5Words(StructWithPrivateMember{"s", 2.0f}), + computeMD5Words("s", 2.0f)); +} + +TEST(HashBuilderTest, HashArrayRefHashableDataTypes) { + ArrayRef array{1, 20, 0x12345678}; + EXPECT_NE(computeMD5Words(array), computeMD5Words(1, 20, 0x12345678)); + EXPECT_EQ(computeMD5Words(array), + computeMD5WordsForRange(array.begin(), array.end())); + EXPECT_EQ(computeMD5Words(array), + computeMD5WordsForRange(array.data(), array.data() + array.size())); +} + +TEST(HashBuilderTest, HashArrayRef) { + ArrayRef array_123{1, 2, 3}; + ArrayRef array_empty{}; + + ArrayRef array_12{1, 2}; + ArrayRef array_3{3}; + + ArrayRef array_1{1}; + ArrayRef array_23{2, 3}; + + MD5Words H_123_E = computeMD5Words(array_123, array_empty); + MD5Words H_12_3 = computeMD5Words(array_12, array_3); + MD5Words H_1_23 = computeMD5Words(array_1, array_23); + MD5Words H_E_123 = computeMD5Words(array_empty, array_123); + + EXPECT_NE(H_123_E, H_12_3); + EXPECT_NE(H_123_E, H_1_23); + EXPECT_NE(H_123_E, H_E_123); + EXPECT_NE(H_12_3, H_1_23); + EXPECT_NE(H_12_3, H_E_123); + EXPECT_NE(H_1_23, H_E_123); +} + +TEST(HashBuilderTest, HashArrayRefNonHashableDataTypes) { + ArrayRef array{{'a', 100}, {'b', 200}}; + EXPECT_NE(computeMD5Words(array), + computeMD5Words(SimpleStruct{'a', 100}, SimpleStruct{'b', 200})); +} + +TEST(HashBuilderTest, HashStringRef) { + StringRef s123("123"); + StringRef s(""); + + StringRef s12("12"); + StringRef s3("3"); + + StringRef s1("1"); + StringRef s23("23"); + + MD5Words H_123_E = computeMD5Words(s123, s); + MD5Words H_12_3 = computeMD5Words(s12, s3); + MD5Words H_1_23 = computeMD5Words(s1, s23); + MD5Words H_E_123 = computeMD5Words(s, 123); + + EXPECT_NE(H_123_E, H_12_3); + EXPECT_NE(H_123_E, H_1_23); + EXPECT_NE(H_123_E, H_E_123); + EXPECT_NE(H_12_3, H_1_23); + EXPECT_NE(H_12_3, H_E_123); + EXPECT_NE(H_1_23, H_E_123); +} + +TEST(HashBuilderTest, HashStdString) { + EXPECT_EQ(computeMD5Words(std::string("123")), + computeMD5Words(StringRef("123"))); +} + +TEST(HashBuilderTest, HashStdPairTuple) { + EXPECT_EQ(computeMD5Words(std::make_pair(1, "string")), + computeMD5Words(std::make_tuple(1, "string"))); +} + +TEST(HashBuilderTest, HashVariadic) { + MD5Words VariadicHash; + MD5Words SerialHash; + + { + MD5 MD5Hash; + HashBuilder HBuilder(MD5Hash); + + HBuilder.update(100); + HBuilder.update(2.7); + HBuilder.update("string"); + + MD5::MD5Result MD5Res; + HBuilder.Hasher.final(MD5Res); + SerialHash = MD5Res.words(); + } + + { + MD5 MD5Hash; + HashBuilder HBuilder(MD5Hash); + + HBuilder.update(100, 2.7, "string"); + + MD5::MD5Result MD5Res; + HBuilder.Hasher.final(MD5Res); + VariadicHash = MD5Res.words(); + } + + EXPECT_EQ(VariadicHash, SerialHash); +} diff --git a/llvm/unittests/Support/MD5Test.cpp b/llvm/unittests/Support/MD5Test.cpp --- a/llvm/unittests/Support/MD5Test.cpp +++ b/llvm/unittests/Support/MD5Test.cpp @@ -68,4 +68,28 @@ EXPECT_EQ(0x3be167ca6c49fb7dULL, MD5Res.high()); EXPECT_EQ(0x00e49261d7d3fcc3ULL, MD5Res.low()); } + +TEST(MD5Test, RawPointerAndSize) { + ArrayRef Input((const uint8_t *)"abcdefghijklmnopqrstuvwxyz", 26); + + std::pair HashFromArrayRef; + { + MD5 Hash; + Hash.update(Input); + MD5::MD5Result MD5Res; + Hash.final(MD5Res); + HashFromArrayRef = MD5Res.words(); + } + + std::pair HashFromPointerAndSize; + { + MD5 Hash; + Hash.update(Input.data(), Input.size()); + MD5::MD5Result MD5Res; + Hash.final(MD5Res); + HashFromPointerAndSize = MD5Res.words(); + } + + EXPECT_EQ(HashFromArrayRef, HashFromPointerAndSize); } +} // namespace diff --git a/llvm/unittests/Support/SHA256.cpp b/llvm/unittests/Support/SHA256.cpp --- a/llvm/unittests/Support/SHA256.cpp +++ b/llvm/unittests/Support/SHA256.cpp @@ -77,4 +77,24 @@ "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0"); } +TEST(SHA256Test, RawPointerAndSize) { + ArrayRef Input((const uint8_t *)"abcdefghijklmnopqrstuvwxyz", 26); + + std::string HashFromArrayRef; + { + SHA256 Hash; + Hash.update(Input); + HashFromArrayRef = toHex(Hash.final()); + } + + std::string HashFromPointerAndSize; + { + SHA256 Hash; + Hash.update(Input.data(), Input.size()); + HashFromPointerAndSize = toHex(Hash.final()); + } + + EXPECT_EQ(HashFromArrayRef, HashFromPointerAndSize); +} + } // namespace diff --git a/llvm/unittests/Support/raw_sha1_ostream_test.cpp b/llvm/unittests/Support/raw_sha1_ostream_test.cpp --- a/llvm/unittests/Support/raw_sha1_ostream_test.cpp +++ b/llvm/unittests/Support/raw_sha1_ostream_test.cpp @@ -59,6 +59,26 @@ ASSERT_EQ("3E4A614101AD84985AB0FE54DC12A6D71551E5AE", Hash); } +TEST(sha1_hash_test, RawPointerAndSize) { + ArrayRef Input((const uint8_t *)"abcdefghijklmnopqrstuvwxyz", 26); + + std::string HashFromArrayRef; + { + SHA1 Hash; + Hash.update(Input); + HashFromArrayRef = toHex(Hash.final()); + } + + std::string HashFromPointerAndSize; + { + SHA1 Hash; + Hash.update(Input.data(), Input.size()); + HashFromPointerAndSize = toHex(Hash.final()); + } + + EXPECT_EQ(HashFromArrayRef, HashFromPointerAndSize); +} + // Check that getting the intermediate hash in the middle of the stream does // not invalidate the final result. TEST(raw_sha1_ostreamTest, Intermediate) {