| // | |
| // Copyright 2020 The ANGLE Project Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // | |
| // EGLContextASANTest.cpp: | |
| // Tests relating to ASAN errors regarding context. | |
| #include <gtest/gtest.h> | |
| #include "test_utils/ANGLETest.h" | |
| #include "test_utils/angle_test_configs.h" | |
| #include "test_utils/gl_raii.h" | |
| #include "util/EGLWindow.h" | |
| #include <condition_variable> | |
| #include <mutex> | |
| #include <thread> | |
| #include <vector> | |
| using namespace angle; | |
| namespace | |
| { | |
| EGLBoolean SafeDestroyContext(EGLDisplay display, EGLContext &context) | |
| { | |
| EGLBoolean result = EGL_TRUE; | |
| if (context != EGL_NO_CONTEXT) | |
| { | |
| result = eglDestroyContext(display, context); | |
| context = EGL_NO_CONTEXT; | |
| } | |
| return result; | |
| } | |
| class EGLContextASANTest : public ANGLETest<> | |
| { | |
| public: | |
| EGLContextASANTest() {} | |
| }; | |
| // Tests that creating resources works after freeing the share context. | |
| TEST_P(EGLContextASANTest, DestroyContextInUse) | |
| { | |
| ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading()); | |
| EGLDisplay display = getEGLWindow()->getDisplay(); | |
| EGLConfig config = getEGLWindow()->getConfig(); | |
| EGLSurface surface = getEGLWindow()->getSurface(); | |
| const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, | |
| getEGLWindow()->getClientMajorVersion(), EGL_NONE}; | |
| EGLContext context = eglCreateContext(display, config, nullptr, contextAttribs); | |
| ASSERT_EGL_SUCCESS(); | |
| ASSERT_TRUE(context != EGL_NO_CONTEXT); | |
| // Synchronization tools to ensure the two threads are interleaved as designed by this test. | |
| std::mutex mutex; | |
| std::condition_variable condVar; | |
| enum class Step | |
| { | |
| Start, | |
| Thread1Draw, | |
| Thread0Delete, | |
| Thread1Draw2, | |
| Finish, | |
| Abort, | |
| }; | |
| Step currentStep = Step::Start; | |
| // Helper functions to synchronize the threads so that the operations are executed in the | |
| // specific order the test is written for. | |
| auto waitForStep = [&](Step waitStep) -> bool { | |
| std::unique_lock<std::mutex> lock(mutex); | |
| while (currentStep != waitStep) | |
| { | |
| // If necessary, abort execution as the other thread has encountered a GL error. | |
| if (currentStep == Step::Abort) | |
| { | |
| return false; | |
| } | |
| condVar.wait(lock); | |
| } | |
| return true; | |
| }; | |
| auto nextStep = [&](Step newStep) { | |
| { | |
| std::unique_lock<std::mutex> lock(mutex); | |
| currentStep = newStep; | |
| } | |
| condVar.notify_one(); | |
| }; | |
| class AbortOnFailure | |
| { | |
| public: | |
| AbortOnFailure(Step *currentStep, std::mutex *mutex, std::condition_variable *condVar) | |
| : mCurrentStep(currentStep), mMutex(mutex), mCondVar(condVar) | |
| {} | |
| ~AbortOnFailure() | |
| { | |
| bool isAborting = false; | |
| { | |
| std::unique_lock<std::mutex> lock(*mMutex); | |
| isAborting = *mCurrentStep != Step::Finish; | |
| if (isAborting) | |
| { | |
| *mCurrentStep = Step::Abort; | |
| } | |
| } | |
| mCondVar->notify_all(); | |
| } | |
| private: | |
| Step *mCurrentStep; | |
| std::mutex *mMutex; | |
| std::condition_variable *mCondVar; | |
| }; | |
| // Unmake current to release the "surface". | |
| EXPECT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); | |
| EXPECT_EGL_SUCCESS(); | |
| std::thread deletingThread = std::thread([&]() { | |
| AbortOnFailure abortOnFailure(¤tStep, &mutex, &condVar); | |
| EXPECT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); | |
| EXPECT_EGL_SUCCESS(); | |
| EXPECT_GL_NO_ERROR(); | |
| // Wait for other thread to draw | |
| ASSERT_TRUE(waitForStep(Step::Thread1Draw)); | |
| // Delete the context, if implemented properly this is a no-op because the context is | |
| // current in another thread. | |
| SafeDestroyContext(display, context); | |
| // Wait for the other thread to use context again | |
| nextStep(Step::Thread0Delete); | |
| ASSERT_TRUE(waitForStep(Step::Finish)); | |
| }); | |
| std::thread continuingThread = std::thread([&]() { | |
| EGLContext localContext = context; | |
| AbortOnFailure abortOnFailure(¤tStep, &mutex, &condVar); | |
| EXPECT_EGL_TRUE(eglMakeCurrent(display, surface, surface, localContext)); | |
| EXPECT_EGL_SUCCESS(); | |
| constexpr GLsizei kTexSize = 1; | |
| const GLColor kTexData = GLColor::red; | |
| GLTexture tex; | |
| glBindTexture(GL_TEXTURE_2D, tex); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, | |
| &kTexData); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
| GLProgram program; | |
| program.makeRaster(essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D()); | |
| ASSERT_TRUE(program.valid()); | |
| // Draw using the texture. | |
| drawQuad(program.get(), essl1_shaders::PositionAttrib(), 0.5f); | |
| EXPECT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, localContext)); | |
| EXPECT_EGL_SUCCESS(); | |
| // Wait for the other thread to delete the context. | |
| nextStep(Step::Thread1Draw); | |
| ASSERT_TRUE(waitForStep(Step::Thread0Delete)); | |
| EXPECT_EGL_TRUE(eglMakeCurrent(display, surface, surface, localContext)); | |
| EXPECT_EGL_SUCCESS(); | |
| // Draw again. If the context has been inappropriately deleted in thread0 this will cause a | |
| // use-after-free error. | |
| drawQuad(program.get(), essl1_shaders::PositionAttrib(), 0.5f); | |
| nextStep(Step::Finish); | |
| EXPECT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); | |
| EXPECT_EGL_SUCCESS(); | |
| }); | |
| deletingThread.join(); | |
| continuingThread.join(); | |
| ASSERT_NE(currentStep, Step::Abort); | |
| // Make default context and surface current again. | |
| EXPECT_TRUE(getEGLWindow()->makeCurrent()); | |
| EXPECT_EGL_SUCCESS(); | |
| // cleanup | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| // Tests that destroying a context with cached robust resource initialization depth PBOs does | |
| // not cause a use-after-free during destruction. | |
| // https://crbug.com/550379413 | |
| TEST_P(EGLContextASANTest, DestroyContextWithRobustDepthInitPBO) | |
| { | |
| EGLDisplay display = getEGLWindow()->getDisplay(); | |
| EGLConfig config = getEGLWindow()->getConfig(); | |
| EGLSurface surface = getEGLWindow()->getSurface(); | |
| ANGLE_SKIP_TEST_IF( | |
| !IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_robust_resource_initialization")); | |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3); | |
| // Unmake default context | |
| EXPECT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); | |
| EXPECT_EGL_SUCCESS(); | |
| std::vector<EGLint> contextAttribs = { | |
| EGL_CONTEXT_CLIENT_VERSION, | |
| 3, | |
| EGL_ROBUST_RESOURCE_INITIALIZATION_ANGLE, | |
| EGL_TRUE, | |
| }; | |
| if (IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_context_virtualization")) | |
| { | |
| contextAttribs.push_back(EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE); | |
| contextAttribs.push_back(1); | |
| } | |
| contextAttribs.push_back(EGL_NONE); | |
| EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs.data()); | |
| ASSERT_EGL_SUCCESS(); | |
| ASSERT_NE(context, EGL_NO_CONTEXT); | |
| EXPECT_EGL_TRUE(eglMakeCurrent(display, surface, surface, context)); | |
| EXPECT_EGL_SUCCESS(); | |
| // Create an uninitialized depth texture | |
| GLTexture depthTex; | |
| glBindTexture(GL_TEXTURE_2D, depthTex); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, 64, 64, 0, GL_DEPTH_COMPONENT, | |
| GL_UNSIGNED_INT, nullptr); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LESS); | |
| ASSERT_GL_NO_ERROR(); | |
| constexpr char kVS[] = R"(#version 300 es | |
| void main() { | |
| gl_Position = vec4(0.0, 0.0, 0.0, 1.0); | |
| gl_PointSize = 1.0; | |
| })"; | |
| constexpr char kFS[] = R"(#version 300 es | |
| uniform highp sampler2DShadow u; | |
| out highp vec4 o; | |
| void main() { | |
| o = vec4(texture(u, vec3(0.5, 0.5, 0.5)), 0.0, 0.0, 1.0); | |
| })"; | |
| GLProgram program; | |
| program.makeRaster(kVS, kFS); | |
| ASSERT_TRUE(program.valid()); | |
| glUseProgram(program); | |
| glUniform1i(glGetUniformLocation(program, "u"), 0); | |
| GLFramebuffer fbo; | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); | |
| GLTexture colorTex; | |
| glBindTexture(GL_TEXTURE_2D, colorTex); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); | |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTex, 0); | |
| ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); | |
| glActiveTexture(GL_TEXTURE0); | |
| glBindTexture(GL_TEXTURE_2D, depthTex); | |
| glDrawArrays(GL_POINTS, 0, 1); | |
| ASSERT_GL_NO_ERROR(); | |
| glFlush(); | |
| // Unmake current and destroy context. | |
| EXPECT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); | |
| EXPECT_EGL_SUCCESS(); | |
| EXPECT_EGL_TRUE(SafeDestroyContext(display, context)); | |
| EXPECT_EGL_SUCCESS(); | |
| // Make default context and surface current again. | |
| EXPECT_TRUE(getEGLWindow()->makeCurrent()); | |
| EXPECT_EGL_SUCCESS(); | |
| } | |
| } // anonymous namespace | |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLContextASANTest); | |
| ANGLE_INSTANTIATE_TEST(EGLContextASANTest, | |
| ES2_D3D11(), | |
| ES3_D3D11(), | |
| ES2_OPENGL(), | |
| ES3_OPENGL(), | |
| ES2_VULKAN(), | |
| ES3_VULKAN()); |