Skip to content

Commit d8c58b2

Browse files
[VirtualOutputBackend] Add HashingOutputBackend
Add a hashing outputbackend that can hash the outputs for sanity check.
1 parent 090ef9a commit d8c58b2

File tree

2 files changed

+148
-0
lines changed

2 files changed

+148
-0
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
//===- HashingOutputBackends.h - Hashing output backends --------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_SUPPORT_HASHINGOUTPUTBACKEND_H
10+
#define LLVM_SUPPORT_HASHINGOUTPUTBACKEND_H
11+
12+
#include "llvm/ADT/StringExtras.h"
13+
#include "llvm/ADT/StringMap.h"
14+
#include "llvm/Support/Endian.h"
15+
#include "llvm/Support/HashBuilder.h"
16+
#include "llvm/Support/VirtualOutputBackend.h"
17+
#include "llvm/Support/VirtualOutputConfig.h"
18+
#include "llvm/Support/raw_ostream.h"
19+
20+
namespace llvm {
21+
namespace vfs {
22+
23+
/// raw_pwrite_stream that writes to a hasher.
24+
template <typename HasherT>
25+
class HashingStream : public llvm::raw_pwrite_stream {
26+
private:
27+
SmallVector<char> Buffer;
28+
raw_svector_ostream OS;
29+
30+
using HashBuilderT = HashBuilder<HasherT, support::endianness::native>;
31+
HashBuilderT Builder;
32+
33+
void write_impl(const char *Ptr, size_t Size) override {
34+
OS.write(Ptr, Size);
35+
}
36+
37+
void pwrite_impl(const char *Ptr, size_t Size, uint64_t Offset) override {
38+
OS.pwrite(Ptr, Size, Offset);
39+
}
40+
41+
uint64_t current_pos() const override { return OS.str().size(); }
42+
43+
public:
44+
HashingStream() : OS(Buffer) { SetUnbuffered(); }
45+
46+
auto final() {
47+
Builder.update(OS.str());
48+
return Builder.final();
49+
}
50+
};
51+
52+
template <typename HasherT> class HashingOutputFile;
53+
54+
/// An output backend that only generates the hash for outputs.
55+
template <typename HasherT> class HashingOutputBackend : public OutputBackend {
56+
private:
57+
friend class HashingOutputFile<HasherT>;
58+
void addOutputFile(StringRef Path, StringRef Hash) {
59+
OutputHashes[Path] = std::string(Hash);
60+
}
61+
62+
protected:
63+
IntrusiveRefCntPtr<OutputBackend> cloneImpl() const override {
64+
return const_cast<HashingOutputBackend<HasherT> *>(this);
65+
}
66+
67+
Expected<std::unique_ptr<OutputFileImpl>>
68+
createFileImpl(StringRef Path, Optional<OutputConfig> Config) override {
69+
return std::make_unique<HashingOutputFile<HasherT>>(Path, *this);
70+
}
71+
72+
public:
73+
/// Iterator for all the output file names.
74+
auto outputFiles() const { return OutputHashes.keys(); }
75+
76+
/// Get hash value for the output files in hex representation.
77+
/// Return None if the requested path is not generated.
78+
Optional<std::string> getHashValueForFile(StringRef Path) {
79+
auto F = OutputHashes.find(Path);
80+
if (F == OutputHashes.end())
81+
return None;
82+
return toHex(F->second);
83+
}
84+
85+
private:
86+
StringMap<std::string> OutputHashes;
87+
};
88+
89+
/// HashingOutputFile.
90+
template <typename HasherT>
91+
class HashingOutputFile final : public OutputFileImpl {
92+
public:
93+
Error keep() override {
94+
auto Result = OS.final();
95+
Backend.addOutputFile(OutputPath, toStringRef(Result));
96+
return Error::success();
97+
}
98+
Error discard() override { return Error::success(); }
99+
raw_pwrite_stream &getOS() override { return OS; }
100+
101+
HashingOutputFile(StringRef OutputPath,
102+
HashingOutputBackend<HasherT> &Backend)
103+
: OutputPath(OutputPath.str()), Backend(Backend) {}
104+
105+
private:
106+
const std::string OutputPath;
107+
HashingStream<HasherT> OS;
108+
HashingOutputBackend<HasherT> &Backend;
109+
};
110+
111+
} // namespace vfs
112+
} // namespace llvm
113+
114+
#endif // LLVM_SUPPORT_HASHINGOUTPUTBACKEND_H

llvm/unittests/Support/VirtualOutputBackendsTest.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
#include "llvm/Support/VirtualOutputBackends.h"
1010
#include "llvm/ADT/StringMap.h"
11+
#include "llvm/Support/BLAKE3.h"
12+
#include "llvm/Support/HashingOutputBackend.h"
1113
#include "llvm/Support/MemoryBuffer.h"
1214
#include "llvm/Testing/Support/Error.h"
1315
#include "gtest/gtest.h"
@@ -805,5 +807,37 @@ TEST(OnDiskBackendTest, OnlyIfDifferent) {
805807
EXPECT_NE(Status1.getUniqueID(), Status3.getUniqueID());
806808
}
807809

810+
TEST(HashingBackendTest, HashOutput) {
811+
HashingOutputBackend<BLAKE3> Backend;
812+
OutputFile O1, O2, O3, O4, O5;
813+
EXPECT_THAT_ERROR(Backend.createFile("file1").moveInto(O1), Succeeded());
814+
O1 << "some data";
815+
EXPECT_THAT_ERROR(O1.keep(), Succeeded());
816+
EXPECT_THAT_ERROR(Backend.createFile("file2").moveInto(O2), Succeeded());
817+
O2 << "some data";
818+
EXPECT_THAT_ERROR(O2.keep(), Succeeded());
819+
EXPECT_EQ(Backend.getHashValueForFile("file1"),
820+
Backend.getHashValueForFile("file2"));
821+
822+
EXPECT_THAT_ERROR(Backend.createFile("file3").moveInto(O3), Succeeded());
823+
O3 << "some ";
824+
O3 << "data";
825+
EXPECT_THAT_ERROR(O3.keep(), Succeeded());
826+
EXPECT_EQ(Backend.getHashValueForFile("file1"),
827+
Backend.getHashValueForFile("file3"));
828+
829+
EXPECT_THAT_ERROR(Backend.createFile("file4").moveInto(O4), Succeeded());
830+
O4 << "same data";
831+
O4.getOS().pwrite("o", 1, 1);
832+
EXPECT_THAT_ERROR(O4.keep(), Succeeded());
833+
EXPECT_EQ(Backend.getHashValueForFile("file1"),
834+
Backend.getHashValueForFile("file4"));
835+
836+
EXPECT_THAT_ERROR(Backend.createFile("file5").moveInto(O5), Succeeded());
837+
O5 << "different data";
838+
EXPECT_THAT_ERROR(O5.keep(), Succeeded());
839+
EXPECT_NE(Backend.getHashValueForFile("file1"),
840+
Backend.getHashValueForFile("file5"));
841+
}
808842

809843
} // end namespace

0 commit comments

Comments
 (0)