Fix program link after backend rejects program binary

If ANGLE believes the program binary is fine, it populates the program
executable.  If the backend then rejects the program binary, the
executable was not reset.  After the rejection, ANGLE proceeds to redo
the program link, in which case it fails in various ways (ASSERT
failures, incorrect data etc) as it tries to accumulate info on top of
the previous executable.

Bug: angleproject:8471
Change-Id: Ia4d626f5f9643c39a81062da3d5d58aa4c6be762
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/5189152
Reviewed-by: Quyen Le <lehoangquyen@chromium.org>
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
diff --git a/include/platform/autogen/FeaturesGL_autogen.h b/include/platform/autogen/FeaturesGL_autogen.h
index ce3de84..c38f023 100644
--- a/include/platform/autogen/FeaturesGL_autogen.h
+++ b/include/platform/autogen/FeaturesGL_autogen.h
@@ -710,6 +710,13 @@
         &members, "https://anglebug.com/8381"
     };
 
+    FeatureInfo corruptProgramBinaryForTesting = {
+        "corruptProgramBinaryForTesting",
+        FeatureCategory::OpenGLWorkarounds,
+        "Corrupt the program binary retrieved from the driver for testing purposes.",
+        &members, "https://anglebug.com/8471"
+    };
+
 };
 
 inline FeaturesGL::FeaturesGL()  = default;
diff --git a/include/platform/gl_features.json b/include/platform/gl_features.json
index 1f9f745..66505b9 100644
--- a/include/platform/gl_features.json
+++ b/include/platform/gl_features.json
@@ -769,6 +769,14 @@
                 "Resync depth range to apply clip control updates."
             ],
             "issue": "https://anglebug.com/8381"
+        },
+        {
+            "name": "corrupt_program_binary_for_testing",
+            "category": "Workarounds",
+            "description": [
+                "Corrupt the program binary retrieved from the driver for testing purposes."
+            ],
+            "issue": "https://anglebug.com/8471"
         }
     ]
 }
diff --git a/src/libANGLE/BlobCache.cpp b/src/libANGLE/BlobCache.cpp
index fe0e881..9d147c6 100644
--- a/src/libANGLE/BlobCache.cpp
+++ b/src/libANGLE/BlobCache.cpp
@@ -83,7 +83,7 @@
 
     if (zResult != Z_OK)
     {
-        ERR() << "Failed to decompress data: " << zResult << "\n";
+        WARN() << "Failed to decompress data: " << zResult << "\n";
         return false;
     }
 
diff --git a/src/libANGLE/BlobCache.h b/src/libANGLE/BlobCache.h
index 80afcbf..41f5a87 100644
--- a/src/libANGLE/BlobCache.h
+++ b/src/libANGLE/BlobCache.h
@@ -48,6 +48,18 @@
 namespace egl
 {
 
+// Used by MemoryProgramCache and MemoryShaderCache, this result indicates whether program/shader
+// cache load from blob was successful.
+enum class CacheGetResult
+{
+    // Binary blob was found and is valid
+    GetSuccess,
+    // Binary blob was not found
+    NotFound,
+    // Binary blob was found, but was rejected due to errors (corruption, version mismatch, etc)
+    Rejected,
+};
+
 bool CompressBlobCacheData(const size_t cacheSize,
                            const uint8_t *cacheData,
                            angle::MemoryBuffer *compressedData);
diff --git a/src/libANGLE/MemoryProgramCache.cpp b/src/libANGLE/MemoryProgramCache.cpp
index d05991c..7589a18 100644
--- a/src/libANGLE/MemoryProgramCache.cpp
+++ b/src/libANGLE/MemoryProgramCache.cpp
@@ -111,8 +111,10 @@
 angle::Result MemoryProgramCache::getProgram(const Context *context,
                                              Program *program,
                                              egl::BlobCache::Key *hashOut,
-                                             bool *successOut)
+                                             egl::CacheGetResult *resultOut)
 {
+    *resultOut = egl::CacheGetResult::NotFound;
+
     // If caching is effectively disabled, don't bother calculating the hash.
     if (!mBlobCache.isCachingEnabled())
     {
@@ -131,14 +133,20 @@
         case egl::BlobCache::GetAndDecompressResult::DecompressFailure:
             ANGLE_PERF_WARNING(context->getState().getDebug(), GL_DEBUG_SEVERITY_LOW,
                                "Error decompressing program binary data fetched from cache.");
+            remove(*hashOut);
+            // Consider this blob "not found".  As far as the rest of the code is considered,
+            // corrupted cache might as well not have existed.
             return angle::Result::Continue;
 
         case egl::BlobCache::GetAndDecompressResult::GetSuccess:
             ANGLE_TRY(program->loadBinary(context, uncompressedData.data(),
-                                          static_cast<int>(uncompressedData.size()), successOut));
+                                          static_cast<int>(uncompressedData.size()), resultOut));
+
+            // Result is either Success or Rejected
+            ASSERT(*resultOut != egl::CacheGetResult::NotFound);
 
             // If cache load failed, evict the entry
-            if (!*successOut)
+            if (*resultOut == egl::CacheGetResult::Rejected)
             {
                 ANGLE_PERF_WARNING(context->getState().getDebug(), GL_DEBUG_SEVERITY_LOW,
                                    "Failed to load program binary from cache.");
diff --git a/src/libANGLE/MemoryProgramCache.h b/src/libANGLE/MemoryProgramCache.h
index 48bfaf8..9ca39d0 100644
--- a/src/libANGLE/MemoryProgramCache.h
+++ b/src/libANGLE/MemoryProgramCache.h
@@ -59,7 +59,7 @@
     angle::Result getProgram(const Context *context,
                              Program *program,
                              egl::BlobCache::Key *hashOut,
-                             bool *successOut);
+                             egl::CacheGetResult *resultOut);
 
     // Empty the cache.
     void clear();
diff --git a/src/libANGLE/Program.cpp b/src/libANGLE/Program.cpp
index 6819992..e5f856b 100644
--- a/src/libANGLE/Program.cpp
+++ b/src/libANGLE/Program.cpp
@@ -916,11 +916,8 @@
     onStateChange(angle::SubjectMessage::ProgramUnlinked);
 }
 
-angle::Result Program::link(const Context *context, angle::JobResultExpectancy resultExpectancy)
+void Program::setupExecutableForLink(const Context *context)
 {
-    auto *platform   = ANGLEPlatformCurrent();
-    double startTime = platform->currentTime(platform);
-
     // Create a new executable to hold the result of the link.  The previous executable may still be
     // referenced by the contexts the program is current on, and any program pipelines it may be
     // used in.  Once link succeeds, the users of the program are notified to update their
@@ -971,6 +968,14 @@
     mState.mExecutable->mPod.isSeparable                 = mState.mSeparable;
 
     mState.mInfoLog.reset();
+}
+
+angle::Result Program::link(const Context *context, angle::JobResultExpectancy resultExpectancy)
+{
+    auto *platform   = ANGLEPlatformCurrent();
+    double startTime = platform->currentTime(platform);
+
+    setupExecutableForLink(context);
 
     mProgramHash              = {0};
     MemoryProgramCache *cache = context->getMemoryProgramCache();
@@ -979,21 +984,33 @@
     if (cache && !isSeparable())
     {
         std::lock_guard<std::mutex> cacheLock(context->getProgramCacheMutex());
-        bool success = false;
-        ANGLE_TRY(cache->getProgram(context, this, &mProgramHash, &success));
+        egl::CacheGetResult result = egl::CacheGetResult::NotFound;
+        ANGLE_TRY(cache->getProgram(context, this, &mProgramHash, &result));
 
-        if (success)
+        switch (result)
         {
-            // No need to care about the compile jobs any more.
-            mState.mShaderCompileJobs = {};
+            case egl::CacheGetResult::GetSuccess:
+            {
+                // No need to care about the compile jobs any more.
+                mState.mShaderCompileJobs = {};
 
-            std::scoped_lock lock(mHistogramMutex);
-            // Succeeded in loading the binaries in the front-end, back end may still be loading
-            // asynchronously
-            double delta = platform->currentTime(platform) - startTime;
-            int us       = static_cast<int>(delta * 1000'000.0);
-            ANGLE_HISTOGRAM_COUNTS("GPU.ANGLE.ProgramCache.ProgramCacheHitTimeUS", us);
-            return angle::Result::Continue;
+                std::scoped_lock lock(mHistogramMutex);
+                // Succeeded in loading the binaries in the front-end, back end may still be loading
+                // asynchronously
+                double delta = platform->currentTime(platform) - startTime;
+                int us       = static_cast<int>(delta * 1000'000.0);
+                ANGLE_HISTOGRAM_COUNTS("GPU.ANGLE.ProgramCache.ProgramCacheHitTimeUS", us);
+                return angle::Result::Continue;
+            }
+            case egl::CacheGetResult::Rejected:
+                // If the program binary was found but rejected, the program executable may be in an
+                // inconsistent half-loaded state.  In that case, start over.
+                mLinkingState.reset();
+                setupExecutableForLink(context);
+                break;
+            case egl::CacheGetResult::NotFound:
+            default:
+                break;
         }
     }
 
@@ -1370,15 +1387,17 @@
 
     makeNewExecutable(context);
 
-    bool success = false;
-    return loadBinary(context, binary, length, &success);
+    egl::CacheGetResult result = egl::CacheGetResult::NotFound;
+    return loadBinary(context, binary, length, &result);
 }
 
 angle::Result Program::loadBinary(const Context *context,
                                   const void *binary,
                                   GLsizei length,
-                                  bool *successOut)
+                                  egl::CacheGetResult *resultOut)
 {
+    *resultOut = egl::CacheGetResult::Rejected;
+
     ASSERT(mLinkingState);
     unlink();
 
@@ -1403,8 +1422,8 @@
     // backend.  Returning to the caller results in link happening using the original shader
     // sources.
     std::shared_ptr<rx::LinkTask> loadTask;
-    ANGLE_TRY(mProgram->load(context, &stream, &loadTask, successOut));
-    if (!*successOut)
+    ANGLE_TRY(mProgram->load(context, &stream, &loadTask, resultOut));
+    if (*resultOut == egl::CacheGetResult::Rejected)
     {
         return angle::Result::Continue;
     }
@@ -1427,7 +1446,7 @@
     mLinkingState->linkingFromBinary = true;
     mLinkingState->linkEvent         = std::move(loadEvent);
 
-    *successOut = true;
+    *resultOut = egl::CacheGetResult::GetSuccess;
 
     return angle::Result::Continue;
 }
diff --git a/src/libANGLE/Program.h b/src/libANGLE/Program.h
index 68a32bd..fdc4508 100644
--- a/src/libANGLE/Program.h
+++ b/src/libANGLE/Program.h
@@ -392,7 +392,7 @@
     angle::Result loadBinary(const Context *context,
                              const void *binary,
                              GLsizei length,
-                             bool *successOut);
+                             egl::CacheGetResult *resultOut);
 
     InfoLog &getInfoLog() { return mState.mInfoLog; }
     int getInfoLogLength() const;
@@ -511,6 +511,7 @@
     bool deserialize(const Context *context, BinaryInputStream &stream);
 
     void unlink();
+    void setupExecutableForLink(const Context *context);
     void deleteSelf(const Context *context);
 
     angle::Result linkJobImpl(const Caps &caps,
diff --git a/src/libANGLE/renderer/ProgramImpl.h b/src/libANGLE/renderer/ProgramImpl.h
index b05a418..6629b13 100644
--- a/src/libANGLE/renderer/ProgramImpl.h
+++ b/src/libANGLE/renderer/ProgramImpl.h
@@ -84,7 +84,7 @@
     virtual angle::Result load(const gl::Context *context,
                                gl::BinaryInputStream *stream,
                                std::shared_ptr<LinkTask> *loadTaskOut,
-                               bool *successOut)                                  = 0;
+                               egl::CacheGetResult *resultOut)                    = 0;
     virtual void save(const gl::Context *context, gl::BinaryOutputStream *stream) = 0;
     virtual void setBinaryRetrievableHint(bool retrievable)                       = 0;
     virtual void setSeparable(bool separable)                                     = 0;
diff --git a/src/libANGLE/renderer/d3d/ProgramD3D.cpp b/src/libANGLE/renderer/d3d/ProgramD3D.cpp
index 32f9df8..fbfdebe 100644
--- a/src/libANGLE/renderer/d3d/ProgramD3D.cpp
+++ b/src/libANGLE/renderer/d3d/ProgramD3D.cpp
@@ -587,7 +587,7 @@
 angle::Result ProgramD3D::load(const gl::Context *context,
                                gl::BinaryInputStream *stream,
                                std::shared_ptr<LinkTask> *loadTaskOut,
-                               bool *successOut)
+                               egl::CacheGetResult *resultOut)
 {
     if (!getExecutable()->load(context, mRenderer, stream))
     {
@@ -610,7 +610,7 @@
 
     // Note: pretty much all the above can also be moved to the task
     *loadTaskOut = std::shared_ptr<LinkTask>(new LoadTaskD3D(this, std::move(streamData)));
-    *successOut  = true;
+    *resultOut   = egl::CacheGetResult::GetSuccess;
 
     return angle::Result::Continue;
 }
diff --git a/src/libANGLE/renderer/d3d/ProgramD3D.h b/src/libANGLE/renderer/d3d/ProgramD3D.h
index 06acfc8..99b6fdd 100644
--- a/src/libANGLE/renderer/d3d/ProgramD3D.h
+++ b/src/libANGLE/renderer/d3d/ProgramD3D.h
@@ -81,7 +81,7 @@
     angle::Result load(const gl::Context *context,
                        gl::BinaryInputStream *stream,
                        std::shared_ptr<LinkTask> *loadTaskOut,
-                       bool *successOut) override;
+                       egl::CacheGetResult *resultOut) override;
     void save(const gl::Context *context, gl::BinaryOutputStream *stream) override;
     void setBinaryRetrievableHint(bool retrievable) override;
     void setSeparable(bool separable) override;
diff --git a/src/libANGLE/renderer/gl/ProgramGL.cpp b/src/libANGLE/renderer/gl/ProgramGL.cpp
index fcfe065..18ff4df 100644
--- a/src/libANGLE/renderer/gl/ProgramGL.cpp
+++ b/src/libANGLE/renderer/gl/ProgramGL.cpp
@@ -189,7 +189,7 @@
 angle::Result ProgramGL::load(const gl::Context *context,
                               gl::BinaryInputStream *stream,
                               std::shared_ptr<LinkTask> *loadTaskOut,
-                              bool *successOut)
+                              egl::CacheGetResult *resultOut)
 {
     ANGLE_TRACE_EVENT0("gpu.angle", "ProgramGL::load");
     ProgramExecutableGL *executableGL = getExecutable();
@@ -203,8 +203,10 @@
     // Load the binary
     mFunctions->programBinary(mProgramID, binaryFormat, binary, binaryLength);
 
-    // Verify that the program linked
-    if (!checkLinkStatus())
+    // Verify that the program linked.  Ensure failure if program binary is intentionally corrupted,
+    // even if the corruption didn't really cause a failure.
+    if (!checkLinkStatus() ||
+        GetImplAs<ContextGL>(context)->getFeaturesGL().corruptProgramBinaryForTesting.enabled)
     {
         return angle::Result::Continue;
     }
@@ -213,7 +215,7 @@
     reapplyUBOBindingsIfNeeded(context);
 
     *loadTaskOut = {};
-    *successOut  = true;
+    *resultOut   = egl::CacheGetResult::GetSuccess;
 
     return angle::Result::Continue;
 }
@@ -230,6 +232,14 @@
 
     stream->writeInt(binaryFormat);
     stream->writeInt(binaryLength);
+
+    if (GetImplAs<ContextGL>(context)->getFeaturesGL().corruptProgramBinaryForTesting.enabled)
+    {
+        // Random corruption of the binary data.  Corrupting the first byte has proven to be enough
+        // to later cause the binary load to fail on most platforms.
+        ++binary[0];
+    }
+
     stream->writeBytes(binary.data(), binaryLength);
 
     reapplyUBOBindingsIfNeeded(context);
diff --git a/src/libANGLE/renderer/gl/ProgramGL.h b/src/libANGLE/renderer/gl/ProgramGL.h
index 9af8e27..70c7618 100644
--- a/src/libANGLE/renderer/gl/ProgramGL.h
+++ b/src/libANGLE/renderer/gl/ProgramGL.h
@@ -42,7 +42,7 @@
     angle::Result load(const gl::Context *context,
                        gl::BinaryInputStream *stream,
                        std::shared_ptr<LinkTask> *loadTaskOut,
-                       bool *successOut) override;
+                       egl::CacheGetResult *resultOut) override;
     void save(const gl::Context *context, gl::BinaryOutputStream *stream) override;
     void setBinaryRetrievableHint(bool retrievable) override;
     void setSeparable(bool separable) override;
diff --git a/src/libANGLE/renderer/metal/ProgramMtl.h b/src/libANGLE/renderer/metal/ProgramMtl.h
index 69399b7..8700d32 100644
--- a/src/libANGLE/renderer/metal/ProgramMtl.h
+++ b/src/libANGLE/renderer/metal/ProgramMtl.h
@@ -38,7 +38,7 @@
     angle::Result load(const gl::Context *context,
                        gl::BinaryInputStream *stream,
                        std::shared_ptr<LinkTask> *loadTaskOut,
-                       bool *successOut) override;
+                       egl::CacheGetResult *resultOut) override;
     void save(const gl::Context *context, gl::BinaryOutputStream *stream) override;
     void setBinaryRetrievableHint(bool retrievable) override;
     void setSeparable(bool separable) override;
diff --git a/src/libANGLE/renderer/metal/ProgramMtl.mm b/src/libANGLE/renderer/metal/ProgramMtl.mm
index 1aee0f9..7aaa046 100644
--- a/src/libANGLE/renderer/metal/ProgramMtl.mm
+++ b/src/libANGLE/renderer/metal/ProgramMtl.mm
@@ -177,7 +177,7 @@
 angle::Result ProgramMtl::load(const gl::Context *context,
                                gl::BinaryInputStream *stream,
                                std::shared_ptr<LinkTask> *loadTaskOut,
-                               bool *successOut)
+                               egl::CacheGetResult *resultOut)
 {
 
     ContextMtl *contextMtl = mtl::GetImpl(context);
@@ -191,7 +191,7 @@
     ANGLE_TRY(compileMslShaderLibs(context, &subTasks));
 
     *loadTaskOut = std::shared_ptr<LinkTask>(new LoadTaskMtl(std::move(subTasks)));
-    *successOut  = true;
+    *resultOut   = egl::CacheGetResult::GetSuccess;
 
     return angle::Result::Continue;
 }
diff --git a/src/libANGLE/renderer/null/ProgramNULL.cpp b/src/libANGLE/renderer/null/ProgramNULL.cpp
index 3b26393..d05f5bd 100644
--- a/src/libANGLE/renderer/null/ProgramNULL.cpp
+++ b/src/libANGLE/renderer/null/ProgramNULL.cpp
@@ -39,10 +39,10 @@
 angle::Result ProgramNULL::load(const gl::Context *context,
                                 gl::BinaryInputStream *stream,
                                 std::shared_ptr<LinkTask> *loadTaskOut,
-                                bool *successOut)
+                                egl::CacheGetResult *resultOut)
 {
     *loadTaskOut = {};
-    *successOut  = true;
+    *resultOut   = egl::CacheGetResult::GetSuccess;
     return angle::Result::Continue;
 }
 
diff --git a/src/libANGLE/renderer/null/ProgramNULL.h b/src/libANGLE/renderer/null/ProgramNULL.h
index bb803b4..97024ee 100644
--- a/src/libANGLE/renderer/null/ProgramNULL.h
+++ b/src/libANGLE/renderer/null/ProgramNULL.h
@@ -24,7 +24,7 @@
     angle::Result load(const gl::Context *context,
                        gl::BinaryInputStream *stream,
                        std::shared_ptr<LinkTask> *loadTaskOut,
-                       bool *successOut) override;
+                       egl::CacheGetResult *resultOut) override;
     void save(const gl::Context *context, gl::BinaryOutputStream *stream) override;
     void setBinaryRetrievableHint(bool retrievable) override;
     void setSeparable(bool separable) override;
diff --git a/src/libANGLE/renderer/vulkan/ProgramExecutableVk.cpp b/src/libANGLE/renderer/vulkan/ProgramExecutableVk.cpp
index c677c32..53a522d 100644
--- a/src/libANGLE/renderer/vulkan/ProgramExecutableVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ProgramExecutableVk.cpp
@@ -566,7 +566,7 @@
 angle::Result ProgramExecutableVk::load(ContextVk *contextVk,
                                         bool isSeparable,
                                         gl::BinaryInputStream *stream,
-                                        bool *successOut)
+                                        egl::CacheGetResult *resultOut)
 {
     mVariableInfoMap.load(stream);
     mOriginalShaderInfo.load(stream);
@@ -607,7 +607,7 @@
     ANGLE_TRY(initializeDescriptorPools(contextVk, &contextVk->getDescriptorSetLayoutCache(),
                                         &contextVk->getMetaDescriptorPools()));
 
-    *successOut = true;
+    *resultOut = egl::CacheGetResult::GetSuccess;
     return angle::Result::Continue;
 }
 
diff --git a/src/libANGLE/renderer/vulkan/ProgramExecutableVk.h b/src/libANGLE/renderer/vulkan/ProgramExecutableVk.h
index 1c49743..19dcf0c 100644
--- a/src/libANGLE/renderer/vulkan/ProgramExecutableVk.h
+++ b/src/libANGLE/renderer/vulkan/ProgramExecutableVk.h
@@ -124,7 +124,7 @@
     angle::Result load(ContextVk *contextVk,
                        bool isSeparable,
                        gl::BinaryInputStream *stream,
-                       bool *successOut);
+                       egl::CacheGetResult *resultOut);
 
     void setUniform1fv(GLint location, GLsizei count, const GLfloat *v) override;
     void setUniform2fv(GLint location, GLsizei count, const GLfloat *v) override;
diff --git a/src/libANGLE/renderer/vulkan/ProgramVk.cpp b/src/libANGLE/renderer/vulkan/ProgramVk.cpp
index 9e210ed..f1d8c2b 100644
--- a/src/libANGLE/renderer/vulkan/ProgramVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ProgramVk.cpp
@@ -420,14 +420,14 @@
 angle::Result ProgramVk::load(const gl::Context *context,
                               gl::BinaryInputStream *stream,
                               std::shared_ptr<LinkTask> *loadTaskOut,
-                              bool *successOut)
+                              egl::CacheGetResult *resultOut)
 {
     ContextVk *contextVk = vk::GetImpl(context);
 
     // TODO: parallelize program load.  http://anglebug.com/8297
     *loadTaskOut = {};
 
-    return getExecutable()->load(contextVk, mState.isSeparable(), stream, successOut);
+    return getExecutable()->load(contextVk, mState.isSeparable(), stream, resultOut);
 }
 
 void ProgramVk::save(const gl::Context *context, gl::BinaryOutputStream *stream)
diff --git a/src/libANGLE/renderer/vulkan/ProgramVk.h b/src/libANGLE/renderer/vulkan/ProgramVk.h
index 6e77d55..18973d3 100644
--- a/src/libANGLE/renderer/vulkan/ProgramVk.h
+++ b/src/libANGLE/renderer/vulkan/ProgramVk.h
@@ -32,7 +32,7 @@
     angle::Result load(const gl::Context *context,
                        gl::BinaryInputStream *stream,
                        std::shared_ptr<LinkTask> *loadTaskOut,
-                       bool *successOut) override;
+                       egl::CacheGetResult *resultOut) override;
     void save(const gl::Context *context, gl::BinaryOutputStream *stream) override;
     void setBinaryRetrievableHint(bool retrievable) override;
     void setSeparable(bool separable) override;
diff --git a/src/tests/egl_tests/EGLBlobCacheTest.cpp b/src/tests/egl_tests/EGLBlobCacheTest.cpp
index 463a07d..293c5c0 100644
--- a/src/tests/egl_tests/EGLBlobCacheTest.cpp
+++ b/src/tests/egl_tests/EGLBlobCacheTest.cpp
@@ -64,6 +64,29 @@
     gLastCacheOpResult = CacheOpResult::SetSuccess;
 }
 
+void SetCorruptedBlob(const void *key,
+                      EGLsizeiANDROID keySize,
+                      const void *value,
+                      EGLsizeiANDROID valueSize)
+{
+    std::vector<uint8_t> keyVec(keySize);
+    memcpy(keyVec.data(), key, keySize);
+
+    std::vector<uint8_t> valueVec(valueSize);
+    memcpy(valueVec.data(), value, valueSize);
+
+    // Corrupt the data
+    ++valueVec[valueVec.size() / 2];
+    ++valueVec[valueVec.size() / 3];
+    ++valueVec[valueVec.size() / 4];
+    ++valueVec[2 * valueVec.size() / 3];
+    ++valueVec[3 * valueVec.size() / 4];
+
+    gApplicationCache[keyVec] = valueVec;
+
+    gLastCacheOpResult = CacheOpResult::SetSuccess;
+}
+
 EGLsizeiANDROID GetBlob(const void *key,
                         EGLsizeiANDROID keySize,
                         void *value,
@@ -419,6 +442,95 @@
     EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
 }
 
+// Makes sure ANGLE recovers from corrupted cache.
+TEST_P(EGLBlobCacheTest, CacheCorruption)
+{
+    EGLDisplay display = getEGLWindow()->getDisplay();
+
+    EXPECT_TRUE(mHasBlobCache);
+    eglSetBlobCacheFuncsANDROID(display, SetCorruptedBlob, GetBlob);
+    ASSERT_EGL_SUCCESS();
+
+    ANGLE_SKIP_TEST_IF(!programBinaryAvailable());
+
+    // Compile the program once and draw with it
+    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
+    glUseProgram(program);
+
+    const GLint colorUniformLocation =
+        glGetUniformLocation(program, angle::essl1_shaders::ColorUniform());
+    ASSERT_NE(colorUniformLocation, -1);
+
+    glUniform4f(colorUniformLocation, 1, 0, 0, 1);
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+    EXPECT_GL_NO_ERROR();
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
+
+    EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
+    gLastCacheOpResult = CacheOpResult::ValueNotSet;
+
+    // Compile/link the same program again, so it would try to retrieve it from the cache.  GetBlob
+    // should return success, but because the cache is corrupt, ANGLE should redo the compile/link
+    // and set the blob again.
+    program.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
+    ASSERT_TRUE(program.valid());
+    glUseProgram(program);
+
+    glUniform4f(colorUniformLocation, 0, 1, 0, 1);
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+    EXPECT_GL_NO_ERROR();
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
+
+    EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
+}
+
+class EGLBlobCacheInternalRejectionTest : public EGLBlobCacheTest
+{};
+
+// Makes sure ANGLE recovers from internal (backend) rejection of the program blob, while everything
+// seems fine to ANGLE.
+TEST_P(EGLBlobCacheInternalRejectionTest, Functional)
+{
+    EGLDisplay display = getEGLWindow()->getDisplay();
+
+    EXPECT_TRUE(mHasBlobCache);
+    eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
+    ASSERT_EGL_SUCCESS();
+
+    ANGLE_SKIP_TEST_IF(!programBinaryAvailable());
+
+    // Compile the program once and draw with it
+    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
+    glUseProgram(program);
+
+    const GLint colorUniformLocation =
+        glGetUniformLocation(program, angle::essl1_shaders::ColorUniform());
+    ASSERT_NE(colorUniformLocation, -1);
+
+    glUniform4f(colorUniformLocation, 1, 0, 0, 1);
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+    EXPECT_GL_NO_ERROR();
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
+
+    EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
+    gLastCacheOpResult = CacheOpResult::ValueNotSet;
+
+    // Compile/link the same program again, so it would try to retrieve it from the cache.  GetBlob
+    // should return success, and ANGLE would think the program is fine.  After ANGLE internal
+    // updates, the backend should reject the program binary, at which point ANGLE should redo the
+    // compile/link and set the blob again.
+    program.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
+    ASSERT_TRUE(program.valid());
+    glUseProgram(program);
+
+    glUniform4f(colorUniformLocation, 0, 1, 0, 1);
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+    EXPECT_GL_NO_ERROR();
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
+
+    EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
+}
+
 ANGLE_INSTANTIATE_TEST(EGLBlobCacheTest,
                        ES2_D3D9(),
                        ES2_D3D11(),
@@ -459,3 +571,8 @@
                            .enable(Feature::AsyncCommandQueue)
                            .enable(Feature::DisablePipelineCacheLoadForTesting)
                            .disable(Feature::SyncMonolithicPipelinesToBlobCache));
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLBlobCacheInternalRejectionTest);
+ANGLE_INSTANTIATE_TEST(EGLBlobCacheInternalRejectionTest,
+                       ES2_OPENGL().enable(Feature::CorruptProgramBinaryForTesting),
+                       ES2_OPENGLES().enable(Feature::CorruptProgramBinaryForTesting));
diff --git a/util/autogen/angle_features_autogen.cpp b/util/autogen/angle_features_autogen.cpp
index f8f2601..db0fad5 100644
--- a/util/autogen/angle_features_autogen.cpp
+++ b/util/autogen/angle_features_autogen.cpp
@@ -70,6 +70,7 @@
     {Feature::CompressVertexData, "compressVertexData"},
     {Feature::CopyIOSurfaceToNonIOSurfaceForReadOptimization, "copyIOSurfaceToNonIOSurfaceForReadOptimization"},
     {Feature::CopyTextureToBufferForReadOptimization, "copyTextureToBufferForReadOptimization"},
+    {Feature::CorruptProgramBinaryForTesting, "corruptProgramBinaryForTesting"},
     {Feature::DecodeEncodeSRGBForGenerateMipmap, "decodeEncodeSRGBForGenerateMipmap"},
     {Feature::DeferFlushUntilEndRenderPass, "deferFlushUntilEndRenderPass"},
     {Feature::DepthStencilBlitExtraCopy, "depthStencilBlitExtraCopy"},
diff --git a/util/autogen/angle_features_autogen.h b/util/autogen/angle_features_autogen.h
index 525a4d3..d6e97ed 100644
--- a/util/autogen/angle_features_autogen.h
+++ b/util/autogen/angle_features_autogen.h
@@ -70,6 +70,7 @@
     CompressVertexData,
     CopyIOSurfaceToNonIOSurfaceForReadOptimization,
     CopyTextureToBufferForReadOptimization,
+    CorruptProgramBinaryForTesting,
     DecodeEncodeSRGBForGenerateMipmap,
     DeferFlushUntilEndRenderPass,
     DepthStencilBlitExtraCopy,