Skip to content

Commit 2966de4

Browse files
committed
[llvm][cas] Extend on-disk CAS validation to ActionCache
Validate the ActionCache hash-mapped trie structure and sanity check the resulting values. Unlike the CAS itself there is no direct way to check the values are "correct", but at least we can check for invalid zero offsets, which is what we would get if we dropped page writes or truncated the file.
1 parent 4317817 commit 2966de4

File tree

11 files changed

+106
-3
lines changed

11 files changed

+106
-3
lines changed

llvm/include/llvm/CAS/ActionCache.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ class ActionCache {
114114
Globally, std::move(Callback), CancelObj);
115115
}
116116

117+
/// Validate the ActionCache contents.
118+
virtual Error validate() const = 0;
119+
117120
virtual ~ActionCache() = default;
118121

119122
protected:

llvm/include/llvm/CAS/OnDiskKeyValueDB.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ class OnDiskKeyValueDB {
6060
StringRef ValueName, size_t ValueSize,
6161
std::shared_ptr<OnDiskCASLogger> Logger = nullptr);
6262

63+
using CheckValueT = function_ref<Error(FileOffset Offset, ArrayRef<char>)>;
64+
Error validate(CheckValueT CheckValue) const;
65+
6366
private:
6467
OnDiskKeyValueDB(size_t ValueSize, OnDiskHashMappedTrie Cache)
6568
: ValueSize(ValueSize), Cache(std::move(Cache)) {}

llvm/include/llvm/CAS/UnifiedOnDiskCache.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ class UnifiedOnDiskCache {
124124

125125
~UnifiedOnDiskCache();
126126

127+
Error validateActionCache();
128+
127129
private:
128130
UnifiedOnDiskCache();
129131

llvm/lib/CAS/ActionCaches.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ class InMemoryActionCache final : public ActionCache {
5252
Expected<std::optional<CASID>> getImpl(ArrayRef<uint8_t> ActionKey,
5353
bool Globally) const final;
5454

55+
Error validate() const final {
56+
return createStringError("InMemoryActionCache doesn't support validate()");
57+
}
58+
5559
private:
5660
using DataT = CacheEntry<sizeof(HashType)>;
5761
using InMemoryCacheT = ThreadSafeHashMappedTrie<DataT, sizeof(HashType)>;
@@ -68,6 +72,8 @@ class OnDiskActionCache final : public ActionCache {
6872

6973
static Expected<std::unique_ptr<OnDiskActionCache>> create(StringRef Path);
7074

75+
Error validate() const final;
76+
7177
private:
7278
static StringRef getHashName() { return "BLAKE3"; }
7379

@@ -86,6 +92,8 @@ class UnifiedOnDiskActionCache final : public ActionCache {
8692

8793
UnifiedOnDiskActionCache(std::shared_ptr<ondisk::UnifiedOnDiskCache> UniDB);
8894

95+
Error validate() const final;
96+
8997
private:
9098
std::shared_ptr<ondisk::UnifiedOnDiskCache> UniDB;
9199
};
@@ -198,6 +206,12 @@ Error OnDiskActionCache::putImpl(ArrayRef<uint8_t> Key, const CASID &Result,
198206
ArrayRef((const uint8_t *)Observed.data(), Observed.size()));
199207
}
200208

209+
Error OnDiskActionCache::validate() const {
210+
// FIXME: without the matching CAS there is nothing we can check about the
211+
// cached values. The hash size is already validated by the DB validator.
212+
return DB->validate(nullptr);
213+
}
214+
201215
UnifiedOnDiskActionCache::UnifiedOnDiskActionCache(
202216
std::shared_ptr<ondisk::UnifiedOnDiskCache> UniDB)
203217
: ActionCache(builtin::BuiltinCASContext::getDefaultContext()),
@@ -233,6 +247,10 @@ Error UnifiedOnDiskActionCache::putImpl(ArrayRef<uint8_t> Key,
233247
UniDB->getGraphDB().getDigest(*Observed));
234248
}
235249

250+
Error UnifiedOnDiskActionCache::validate() const {
251+
return UniDB->validateActionCache();
252+
}
253+
236254
Expected<std::unique_ptr<ActionCache>>
237255
cas::createOnDiskActionCache(StringRef Path) {
238256
#if LLVM_ENABLE_ONDISK_CAS

llvm/lib/CAS/OnDiskKeyValueDB.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,25 @@ OnDiskKeyValueDB::open(StringRef Path, StringRef HashName, unsigned KeySize,
8181
return std::unique_ptr<OnDiskKeyValueDB>(
8282
new OnDiskKeyValueDB(ValueSize, std::move(*ActionCache)));
8383
}
84+
85+
Error OnDiskKeyValueDB::validate(CheckValueT CheckValue) const {
86+
return Cache.validate(
87+
[&](FileOffset Offset,
88+
OnDiskHashMappedTrie::ConstValueProxy Record) -> Error {
89+
auto formatError = [&](Twine Msg) {
90+
return createStringError(
91+
llvm::errc::illegal_byte_sequence,
92+
"bad cache value at 0x" +
93+
utohexstr((unsigned)Offset.get(), /*LowerCase=*/true) + ": " +
94+
Msg.str());
95+
};
96+
97+
if (Record.Data.size() != ValueSize)
98+
return formatError("wrong cache value size");
99+
if (!isAligned(Align(8), Record.Data.size()))
100+
return formatError("wrong cache value alignment");
101+
if (CheckValue)
102+
return CheckValue(Offset, Record.Data);
103+
return Error::success();
104+
});
105+
}

llvm/lib/CAS/PluginAPI.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ struct llcas_functions_t {
108108
bool globally, void *ctx_cb,
109109
llcas_actioncache_put_cb,
110110
llcas_cancellable_t *);
111+
112+
bool (*actioncache_validate)(llcas_cas_t, char **error);
111113
};
112114

113115
#endif // LLVM_LIB_CAS_PLUGINAPI_H

llvm/lib/CAS/PluginAPI_functions.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CASPLUGINAPI_FUNCTION(actioncache_get_for_digest, true)
77
CASPLUGINAPI_FUNCTION(actioncache_get_for_digest_async, true)
88
CASPLUGINAPI_FUNCTION(actioncache_put_for_digest, true)
99
CASPLUGINAPI_FUNCTION(actioncache_put_for_digest_async, true)
10+
CASPLUGINAPI_FUNCTION(actioncache_validate, false)
1011
CASPLUGINAPI_FUNCTION(cancellable_cancel, false)
1112
CASPLUGINAPI_FUNCTION(cancellable_dispose, false)
1213
CASPLUGINAPI_FUNCTION(cas_contains_object, true)

llvm/lib/CAS/PluginCAS.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,8 @@ class PluginActionCache : public ActionCache {
461461

462462
PluginActionCache(std::shared_ptr<PluginCASContext>);
463463

464+
Error validate() const final;
465+
464466
private:
465467
std::shared_ptr<PluginCASContext> Ctx;
466468
};
@@ -596,6 +598,16 @@ void PluginActionCache::putImplAsync(ArrayRef<uint8_t> ResolvedKey,
596598
PluginActionCache::PluginActionCache(std::shared_ptr<PluginCASContext> CASCtx)
597599
: ActionCache(*CASCtx), Ctx(std::move(CASCtx)) {}
598600

601+
Error PluginActionCache::validate() const {
602+
if (Ctx->Functions.actioncache_validate) {
603+
char *c_err = nullptr;
604+
if (Ctx->Functions.actioncache_validate(Ctx->c_cas, &c_err))
605+
return Ctx->errorAndDispose(c_err);
606+
return Error::success();
607+
}
608+
return createStringError("plugin action cache doesn't support validation");
609+
}
610+
599611
//===----------------------------------------------------------------------===//
600612
// createPluginCASDatabases API
601613
//===----------------------------------------------------------------------===//

llvm/lib/CAS/UnifiedOnDiskCache.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
#include "llvm/CAS/UnifiedOnDiskCache.h"
5353
#include "OnDiskCommon.h"
5454
#include "llvm/ADT/ScopeExit.h"
55+
#include "llvm/ADT/StringExtras.h"
5556
#include "llvm/CAS/OnDiskCASLogger.h"
5657
#include "llvm/CAS/OnDiskKeyValueDB.h"
5758
#include "llvm/Support/Compiler.h"
@@ -123,6 +124,30 @@ UnifiedOnDiskCache::faultInFromUpstreamKV(ArrayRef<uint8_t> Key) {
123124
return KVPut(Key, *PrimaryID);
124125
}
125126

127+
Error UnifiedOnDiskCache::validateActionCache() {
128+
auto ValidateRef = [&](FileOffset Offset, ArrayRef<char> Value) -> Error {
129+
assert(Value.size() == sizeof(uint64_t) && "should be validated already");
130+
auto ID = ObjectID::fromOpaqueData(support::endian::read64le(Value.data()));
131+
auto formatError = [&](Twine Msg) {
132+
return createStringError(
133+
llvm::errc::illegal_byte_sequence,
134+
"bad record at 0x" +
135+
utohexstr((unsigned)Offset.get(), /*LowerCase=*/true) + ": " +
136+
Msg.str());
137+
};
138+
if (ID.getOpaqueData() == 0)
139+
return formatError("zero is not a valid ref");
140+
if (!PrimaryGraphDB->containsObject(ID))
141+
return formatError("cas does not contain ref");
142+
return Error::success();
143+
};
144+
if (Error E = PrimaryKVDB->validate(ValidateRef))
145+
return E;
146+
if (UpstreamKVDB)
147+
return UpstreamKVDB->validate(ValidateRef);
148+
return Error::success();
149+
}
150+
126151
/// \returns all the 'v<version>.<x>' names of sub-directories, sorted with
127152
/// ascending order of the integer after the dot.
128153
static Error getAllDBDirs(StringRef Path,

llvm/test/tools/llvm-cas/validation.test

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,17 @@ RUN: rm %t/cas/v1.1/v8.data
99
RUN: not llvm-cas --cas %t/cas --validate
1010
RUN: not llvm-cas --cas %t/cas --validate --check-hash
1111

12+
RUN: mkdir %t/ac
13+
14+
RUN: llvm-cas --cas %t/ac --make-blob \
15+
RUN: --data /dev/null > %t/empty.casid
16+
RUN: echo "abc" | \
17+
RUN: llvm-cas --cas %t/ac --make-blob \
18+
RUN: --data - >%t/abc.casid
19+
20+
RUN: llvm-cas --cas %t/ac --put-cache-key @%t/abc.casid @%t/empty.casid
21+
RUN: llvm-cas --cas %t/ac --validate
22+
# Note: records are 40 bytes (32 hash bytes + 8 byte value), so trim the last
23+
# allocated record, leaving it invalid.
24+
RUN: truncate -s -40 %t/ac/v1.1/v3.actions
25+
RUN: not llvm-cas --cas %t/ac --validate

llvm/tools/llvm-cas/llvm-cas.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ static int putCacheKey(ObjectStore &CAS, ActionCache &AC,
6464
ArrayRef<std::string> Objects);
6565
static int getCacheResult(ObjectStore &CAS, ActionCache &AC, const CASID &ID);
6666
static int validateObject(ObjectStore &CAS, const CASID &ID);
67-
static int validate(ObjectStore &CAS, bool CheckHash);
67+
static int validate(ObjectStore &CAS, ActionCache &AC, bool CheckHash);
6868
static int ingestCasIDFile(cas::ObjectStore &CAS, ArrayRef<std::string> CASIDs);
6969
static int checkLockFiles(StringRef CASPath);
7070

@@ -184,7 +184,7 @@ int main(int Argc, char **Argv) {
184184
return dump(*CAS);
185185

186186
if (Command == Validate)
187-
return validate(*CAS, CheckHash);
187+
return validate(*CAS, *AC, CheckHash);
188188

189189
if (Command == MakeBlob)
190190
return makeBlob(*CAS, DataPath);
@@ -722,9 +722,10 @@ int validateObject(ObjectStore &CAS, const CASID &ID) {
722722
return 0;
723723
}
724724

725-
int validate(ObjectStore &CAS, bool CheckHash) {
725+
int validate(ObjectStore &CAS, ActionCache &AC, bool CheckHash) {
726726
ExitOnError ExitOnErr("llvm-cas: validate: ");
727727
ExitOnErr(CAS.validate(CheckHash));
728+
ExitOnErr(AC.validate());
728729
outs() << "validated successfully\n";
729730
return 0;
730731
}

0 commit comments

Comments
 (0)