blob: 8d98a27f1a661d34bf641b0bfc8286433c8468e1 [file] [log] [blame]
//
// 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>
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(&currentStep, &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(&currentStep, &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();
}
} // 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());