Add cache validation to ensure the validity

Create cache identity from GL_VERSION and store in ShaderCache.
In the next time ShaderCache is restored from disk, compare the
cache identity to ensure its validity. If GL_VERSION changes in
between, flush out entire FileBlobCache and start from an empty
one.

Bug: b/71800782
Test: Wrote a new unit test to save and restore ShaderCache
Test: hwui_unit_tests
Change-Id: Ie573dc4f18733eee090725be30445d879765231b
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index d582983..edce305 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -72,6 +72,7 @@
         "libft2",
         "libminikin",
         "libandroidfw",
+        "libcrypto",
     ],
     static_libs: [
         "libEGL_blobCache",
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index 6700748..073b481 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -18,6 +18,8 @@
 #include <algorithm>
 #include <log/log.h>
 #include <thread>
+#include <array>
+#include <openssl/sha.h>
 #include "FileBlobCache.h"
 #include "Properties.h"
 #include "utils/TraceUtils.h"
@@ -41,7 +43,40 @@
     return sCache;
 }
 
-void ShaderCache::initShaderDiskCache() {
+bool ShaderCache::validateCache(const void* identity, ssize_t size) {
+    if (nullptr == identity && size == 0)
+        return true;
+
+    if (nullptr == identity || size < 0) {
+        if (CC_UNLIKELY(Properties::debugLevel & kDebugCaches)) {
+            ALOGW("ShaderCache::validateCache invalid cache identity");
+        }
+        mBlobCache->clear();
+        return false;
+    }
+
+    SHA256_CTX ctx;
+    SHA256_Init(&ctx);
+
+    SHA256_Update(&ctx, identity, size);
+    mIDHash.resize(SHA256_DIGEST_LENGTH);
+    SHA256_Final(mIDHash.data(), &ctx);
+
+    std::array<uint8_t, SHA256_DIGEST_LENGTH> hash;
+    auto key = sIDKey;
+    auto loaded = mBlobCache->get(&key, sizeof(key), hash.data(), hash.size());
+
+    if (loaded && std::equal(hash.begin(), hash.end(), mIDHash.begin()))
+        return true;
+
+    if (CC_UNLIKELY(Properties::debugLevel & kDebugCaches)) {
+        ALOGW("ShaderCache::validateCache cache validation fails");
+    }
+    mBlobCache->clear();
+    return false;
+}
+
+void ShaderCache::initShaderDiskCache(const void* identity, ssize_t size) {
     ATRACE_NAME("initShaderDiskCache");
     std::lock_guard<std::mutex> lock(mMutex);
 
@@ -50,6 +85,7 @@
     // desktop / laptop GPUs. Thus, disable the shader disk cache for emulator builds.
     if (!Properties::runningInEmulator && mFilename.length() > 0) {
         mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename));
+        validateCache(identity, size);
         mInitialized = true;
     }
 }
@@ -104,6 +140,18 @@
     return SkData::MakeFromMalloc(valueBuffer, valueSize);
 }
 
+void ShaderCache::saveToDiskLocked() {
+    ATRACE_NAME("ShaderCache::saveToDiskLocked");
+    if (mInitialized && mBlobCache && mSavePending) {
+        if (mIDHash.size()) {
+            auto key = sIDKey;
+            mBlobCache->set(&key, sizeof(key), mIDHash.data(), mIDHash.size());
+        }
+        mBlobCache->writeToFile();
+    }
+    mSavePending = false;
+}
+
 void ShaderCache::store(const SkData& key, const SkData& data) {
     ATRACE_NAME("ShaderCache::store");
     std::lock_guard<std::mutex> lock(mMutex);
@@ -129,11 +177,7 @@
         std::thread deferredSaveThread([this]() {
             sleep(mDeferredSaveDelay);
             std::lock_guard<std::mutex> lock(mMutex);
-            ATRACE_NAME("ShaderCache::saveToDisk");
-            if (mInitialized && mBlobCache) {
-                mBlobCache->writeToFile();
-            }
-            mSavePending = false;
+            saveToDiskLocked();
         });
         deferredSaveThread.detach();
     }
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index 27473d6..82804cf 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -40,12 +40,21 @@
     ANDROID_API static ShaderCache& get();
 
     /**
-     * "initShaderDiskCache" loads the serialized cache contents from disk and puts the ShaderCache
-     * into an initialized state, such that it is able to insert and retrieve entries from the
-     * cache.  This should be called when HWUI pipeline is initialized.  When not in the initialized
-     * state the load and store methods will return without performing any cache operations.
+     * initShaderDiskCache" loads the serialized cache contents from disk,
+     * optionally checks that the on-disk cache matches a provided identity,
+     * and puts the ShaderCache into an initialized state, such that it is
+     * able to insert and retrieve entries from the cache. If identity is
+     * non-null and validation fails, the cache is initialized but contains
+     * no data. If size is less than zero, the cache is initilaized but
+     * contains no data.
+     *
+     * This should be called when HWUI pipeline is initialized. When not in
+     * the initialized state the load and store methods will return without
+     * performing any cache operations.
      */
-    virtual void initShaderDiskCache();
+    virtual void initShaderDiskCache(const void *identity, ssize_t size);
+
+    virtual void initShaderDiskCache() { initShaderDiskCache(nullptr, 0); }
 
     /**
      * "setFilename" sets the name of the file that should be used to store
@@ -83,6 +92,19 @@
     BlobCache* getBlobCacheLocked();
 
     /**
+     * "validateCache" updates the cache to match the given identity.  If the
+     * cache currently has the wrong identity, all entries in the cache are cleared.
+     */
+    bool validateCache(const void* identity, ssize_t size);
+
+    /**
+     * "saveToDiskLocked" attemps to save the current contents of the cache to
+     * disk. If the identity hash exists, we will insert the identity hash into
+     * the cache for next validation.
+     */
+    void saveToDiskLocked();
+
+    /**
      * "mInitialized" indicates whether the ShaderCache is in the initialized
      * state.  It is initialized to false at construction time, and gets set to
      * true when initialize is called.
@@ -111,6 +133,15 @@
     std::string mFilename;
 
     /**
+     * "mIDHash" is the current identity hash for the cache validation. It is
+     * initialized to an empty vector at construction time, and its content is
+     * generated in the call of the validateCache method. An empty vector
+     * indicates that cache validation is not performed, and the hash should
+     * not be stored on disk.
+     */
+    std::vector<uint8_t> mIDHash;
+
+    /**
      * "mSavePending" indicates whether or not a deferred save operation is
      * pending.  Each time a key/value pair is inserted into the cache via
      * load, a deferred save is initiated if one is not already pending.
@@ -140,6 +171,11 @@
      */
     static ShaderCache sCache;
 
+    /**
+     * "sIDKey" is the cache key of the identity hash
+     */
+    static constexpr uint8_t sIDKey = 0;
+
     friend class ShaderCacheTestUtils; //used for unit testing
 };
 
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index bec80b1e..82bfc49 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -50,7 +50,6 @@
     mVectorDrawableAtlas = new skiapipeline::VectorDrawableAtlas(
             mMaxSurfaceArea / 2,
             skiapipeline::VectorDrawableAtlas::StorageMode::disallowSharedSurface);
-    skiapipeline::ShaderCache::get().initShaderDiskCache();
 }
 
 void CacheManager::reset(sk_sp<GrContext> context) {
@@ -103,7 +102,7 @@
     }
 };
 
-void CacheManager::configureContext(GrContextOptions* contextOptions) {
+void CacheManager::configureContext(GrContextOptions* contextOptions, const void* identity, ssize_t size) {
     contextOptions->fAllowPathMaskCaching = true;
 
     float screenMP = mMaxSurfaceArea / 1024.0f / 1024.0f;
@@ -133,7 +132,9 @@
         contextOptions->fExecutor = mTaskProcessor.get();
     }
 
-    contextOptions->fPersistentCache = &skiapipeline::ShaderCache::get();
+    auto& cache = skiapipeline::ShaderCache::get();
+    cache.initShaderDiskCache(identity, size);
+    contextOptions->fPersistentCache = &cache;
     contextOptions->fGpuPathRenderers &= ~GpuPathRenderers::kCoverageCounting;
 }
 
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index 7d73352..35fc91a 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -44,7 +44,7 @@
 public:
     enum class TrimMemoryMode { Complete, UiHidden };
 
-    void configureContext(GrContextOptions* context);
+    void configureContext(GrContextOptions* context, const void* identity, ssize_t size);
     void trimMemory(TrimMemoryMode mode);
     void trimStaleResources();
     void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index c1284ec..36ffaee 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -191,7 +191,9 @@
     GrContextOptions options;
     options.fPreferExternalImagesOverES3 = true;
     options.fDisableDistanceFieldPaths = true;
-    cacheManager().configureContext(&options);
+    auto glesVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION));
+    auto size = glesVersion ? strlen(glesVersion) : -1;
+    cacheManager().configureContext(&options, glesVersion, size);
     sk_sp<GrContext> grContext(GrContext::MakeGL(std::move(glInterface), options));
     LOG_ALWAYS_FATAL_IF(!grContext.get());
     setGrContext(grContext);
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 1517f57..cc4b87a 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -391,7 +391,8 @@
 
     GrContextOptions options;
     options.fDisableDistanceFieldPaths = true;
-    mRenderThread.cacheManager().configureContext(&options);
+    // TODO: get a string describing the SPIR-V compiler version and use it here
+    mRenderThread.cacheManager().configureContext(&options, nullptr, 0);
     sk_sp<GrContext> grContext(GrContext::MakeVulkan(backendContext, options));
     LOG_ALWAYS_FATAL_IF(!grContext.get());
     mRenderThread.setGrContext(grContext);
diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp
index 43080a9..1433aa0 100644
--- a/libs/hwui/tests/unit/ShaderCacheTests.cpp
+++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp
@@ -48,11 +48,18 @@
      */
     static void terminate(ShaderCache& cache, bool saveContent) {
         std::lock_guard<std::mutex> lock(cache.mMutex);
-        if (cache.mInitialized && cache.mBlobCache && saveContent) {
-            cache.mBlobCache->writeToFile();
-        }
+        cache.mSavePending = saveContent;
+        cache.saveToDiskLocked();
         cache.mBlobCache = NULL;
     }
+
+    /**
+     *
+     */
+    template <typename T>
+    static bool validateCache(ShaderCache& cache, std::vector<T> hash) {
+        return cache.validateCache(hash.data(), hash.size() * sizeof(T));
+    }
 };
 
 } /* namespace skiapipeline */
@@ -75,26 +82,39 @@
     return false;
 }
 
-bool checkShader(const sk_sp<SkData>& shader, const char* program) {
-    sk_sp<SkData> shader2 = SkData::MakeWithCString(program);
-    return shader->size() == shader2->size()
-            && 0 == memcmp(shader->data(), shader2->data(), shader->size());
+inline bool
+checkShader(const sk_sp<SkData>& shader1, const sk_sp<SkData>& shader2) {
+    return nullptr != shader1 && nullptr != shader2 && shader1->size() == shader2->size()
+            && 0 == memcmp(shader1->data(), shader2->data(), shader1->size());
 }
 
-bool checkShader(const sk_sp<SkData>& shader, std::vector<char>& program) {
-    sk_sp<SkData> shader2 = SkData::MakeWithCopy(program.data(), program.size());
-    return shader->size() == shader2->size()
-            && 0 == memcmp(shader->data(), shader2->data(), shader->size());
+inline bool
+checkShader(const sk_sp<SkData>& shader, const char* program) {
+    sk_sp<SkData> shader2 = SkData::MakeWithCString(program);
+    return checkShader(shader, shader2);
+}
+
+template <typename T>
+bool checkShader(const sk_sp<SkData>& shader, std::vector<T>& program) {
+    sk_sp<SkData> shader2 = SkData::MakeWithCopy(program.data(), program.size() * sizeof(T));
+    return checkShader(shader, shader2);
 }
 
 void setShader(sk_sp<SkData>& shader, const char* program) {
     shader = SkData::MakeWithCString(program);
 }
 
-void setShader(sk_sp<SkData>& shader, std::vector<char>& program) {
-    shader = SkData::MakeWithCopy(program.data(), program.size());
+template <typename T>
+void setShader(sk_sp<SkData>& shader, std::vector<T>& buffer) {
+    shader = SkData::MakeWithCopy(buffer.data(), buffer.size() * sizeof(T));
 }
 
+template <typename T>
+void genRandomData(std::vector<T>& buffer) {
+    for (auto& data : buffer) {
+        data = T(std::rand());
+    }
+}
 
 
 #define GrProgramDescTest(a) (*SkData::MakeWithCString(#a).get())
@@ -110,6 +130,7 @@
     //remove any test files from previous test run
     int deleteFile = remove(cacheFile1.c_str());
     ASSERT_TRUE(0 == deleteFile || ENOENT == errno);
+    std::srand(0);
 
     //read the cache from a file that does not exist
     ShaderCache::get().setFilename(cacheFile1.c_str());
@@ -158,10 +179,8 @@
 
     //write and read big data chunk (50K)
     size_t dataSize = 50*1024;
-    std::vector<char> dataBuffer(dataSize);
-    for (size_t i = 0; i < dataSize; i++) {
-        dataBuffer[0] = dataSize % 256;
-    }
+    std::vector<uint8_t> dataBuffer(dataSize);
+    genRandomData(dataBuffer);
     setShader(inVS, dataBuffer);
     ShaderCache::get().store(GrProgramDescTest(432), *inVS.get());
     ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
@@ -173,4 +192,96 @@
     remove(cacheFile1.c_str());
 }
 
+TEST(ShaderCacheTest, testCacheValidation) {
+    if (!folderExist(getExternalStorageFolder())) {
+        //don't run the test if external storage folder is not available
+        return;
+    }
+    std::string cacheFile1 =  getExternalStorageFolder() + "/shaderCacheTest1";
+    std::string cacheFile2 =  getExternalStorageFolder() + "/shaderCacheTest2";
+
+    //remove any test files from previous test run
+    int deleteFile = remove(cacheFile1.c_str());
+    ASSERT_TRUE(0 == deleteFile || ENOENT == errno);
+    std::srand(0);
+
+    //generate identity and read the cache from a file that does not exist
+    ShaderCache::get().setFilename(cacheFile1.c_str());
+    ShaderCacheTestUtils::setSaveDelay(ShaderCache::get(), 0); //disable deferred save
+    std::vector<uint8_t> identity(1024);
+    genRandomData(identity);
+    ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() *
+                                           sizeof(decltype(identity)::value_type));
+
+    // generate random content in cache and store to disk
+    constexpr size_t numBlob(10);
+    constexpr size_t keySize(1024);
+    constexpr size_t dataSize(50 * 1024);
+
+    std::vector< std::pair<sk_sp<SkData>, sk_sp<SkData>> > blobVec(numBlob);
+    for (auto& blob : blobVec) {
+        std::vector<uint8_t> keyBuffer(keySize);
+        std::vector<uint8_t> dataBuffer(dataSize);
+        genRandomData(keyBuffer);
+        genRandomData(dataBuffer);
+
+        sk_sp<SkData> key, data;
+        setShader(key, keyBuffer);
+        setShader(data, dataBuffer);
+
+        blob = std::make_pair(key, data);
+        ShaderCache::get().store(*key.get(), *data.get());
+    }
+    ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
+
+    // change to a file that does not exist and verify validation fails
+    ShaderCache::get().setFilename(cacheFile2.c_str());
+    ShaderCache::get().initShaderDiskCache();
+    ASSERT_FALSE( ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity) );
+    ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
+
+    // restore the original file and verify validation succeeds
+    ShaderCache::get().setFilename(cacheFile1.c_str());
+    ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() *
+                                           sizeof(decltype(identity)::value_type));
+    ASSERT_TRUE( ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity) );
+    for (const auto& blob : blobVec) {
+        auto outVS = ShaderCache::get().load(*blob.first.get());
+        ASSERT_TRUE( checkShader(outVS, blob.second) );
+    }
+
+    // generate error identity and verify load fails
+    ShaderCache::get().initShaderDiskCache(identity.data(), -1);
+    for (const auto& blob : blobVec) {
+        ASSERT_EQ( ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>() );
+    }
+    ShaderCache::get().initShaderDiskCache(nullptr, identity.size() *
+                                           sizeof(decltype(identity)::value_type));
+    for (const auto& blob : blobVec) {
+        ASSERT_EQ( ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>() );
+    }
+
+    // verify the cache validation again after load fails
+    ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() *
+                                           sizeof(decltype(identity)::value_type));
+    ASSERT_TRUE( ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity) );
+    for (const auto& blob : blobVec) {
+        auto outVS = ShaderCache::get().load(*blob.first.get());
+        ASSERT_TRUE( checkShader(outVS, blob.second) );
+    }
+
+    // generate another identity and verify load fails
+    for (auto& data : identity) {
+        data += std::rand();
+    }
+    ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() *
+                                           sizeof(decltype(identity)::value_type));
+    for (const auto& blob : blobVec) {
+        ASSERT_EQ( ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>() );
+    }
+
+    ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
+    remove(cacheFile1.c_str());
+}
+
 }  // namespace