// // 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 #include "test_utils/ANGLETest.h" #include "test_utils/angle_test_configs.h" #include "test_utils/gl_raii.h" #include "util/EGLWindow.h" #include #include #include 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 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 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 lock(*mMutex); isAborting = *mCurrentStep != Step::Finish; if (isAborting) { *mCurrentStep = Step::Abort; } } mCondVar->notify_all(); } private: Step *mCurrentStep; std::mutex *mMutex; std::condition_variable *mCondVar; }; 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); // cleanup ASSERT_GL_NO_ERROR(); } } // anonymous namespace GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLContextASANTest); ANGLE_INSTANTIATE_TEST(EGLContextASANTest, ES2_D3D9(), ES2_D3D11(), ES3_D3D11(), ES2_OPENGL(), ES3_OPENGL(), ES2_VULKAN(), ES3_VULKAN());