mirror of
https://github.com/SerenityOS/serenity
synced 2026-05-11 09:26:28 +02:00
There are three compiler bugs that influence this decision: - Clang writing to (validly) destroyed coroutine frame with -O0 and -fsanitize=null,address under some conditions (https://godbolt.org/z/17Efq5Ma5) (AK_COROUTINE_DESTRUCTION_BROKEN); - GCC being unable to handle statement expressions in coroutines (AK_COROUTINE_STATEMENT_EXPRS_BROKEN); - GCC being unable to deduce template type parameter for TryAwaiter with nested CO_TRYs (AK_COROUTINE_TYPE_DEDUCTION_BROKEN). Instead of growing an ifdef soup in AK/Coroutine.h and LibTest/AsyncTestCase.h, define three macros in AK/Platform.h that correspond to these bugs and use them accordingly in the said files.
117 lines
5.2 KiB
C++
117 lines
5.2 KiB
C++
/*
|
|
* Copyright (c) 2024, Dan Klishch <danilklishch@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <AK/Coroutine.h>
|
|
#include <LibCore/EventLoop.h>
|
|
#include <LibTest/TestCase.h>
|
|
|
|
#ifndef AK_COROUTINE_STATEMENT_EXPRS_BROKEN
|
|
# define CO_TRY_OR_FAIL(expression) \
|
|
({ \
|
|
/* Ignore -Wshadow to allow nesting the macro. */ \
|
|
AK_IGNORE_DIAGNOSTIC("-Wshadow", \
|
|
auto _temporary_result = (expression)); \
|
|
static_assert(!::AK::Detail::IsLvalueReference<decltype(_temporary_result.release_value())>, \
|
|
"Do not return a reference from a fallible expression"); \
|
|
if (_temporary_result.is_error()) [[unlikely]] { \
|
|
FAIL(_temporary_result.release_error()); \
|
|
co_return; \
|
|
} \
|
|
_temporary_result.release_value(); \
|
|
})
|
|
#elifndef AK_COROUTINE_DESTRUCTION_BROKEN
|
|
namespace AK::Detail::Test {
|
|
|
|
// FIXME: Straight-forward way to implement CO_TRY_OR_FAIL we use for Clang causes GCC to ICE.
|
|
template<typename T>
|
|
struct TryOrFailAwaiter {
|
|
TryOrFailAwaiter(T& expression)
|
|
: m_expression(&expression)
|
|
{
|
|
}
|
|
|
|
TryOrFailAwaiter(T&& expression)
|
|
: m_expression(&expression)
|
|
{
|
|
}
|
|
|
|
TryOrFailAwaiter(Coroutine<T>&&)
|
|
{
|
|
static_assert(DependentFalse<T>, "Despite the CO_ prefix in CO_TRY_OR_FAIL, you should co_await the argument of it.");
|
|
}
|
|
|
|
bool await_ready() { return false; }
|
|
|
|
template<typename U>
|
|
requires IsSpecializationOf<T, ErrorOr>
|
|
std::coroutine_handle<> await_suspend(std::coroutine_handle<U> handle)
|
|
{
|
|
if (!m_expression->is_error()) {
|
|
return handle;
|
|
} else {
|
|
auto awaiter = handle.promise().m_awaiter;
|
|
auto* coroutine = handle.promise().m_coroutine;
|
|
using ReturnType = RemoveReference<decltype(*coroutine)>::ReturnType;
|
|
static_assert(SameAs<ReturnType, void>,
|
|
"CO_TRY_OR_FAIL can only be used inside ASYNC_TEST_CASE functions");
|
|
|
|
// Fail the current test.
|
|
FAIL(m_expression->release_error());
|
|
|
|
// Forcefully stop coroutine.
|
|
coroutine->m_handle = {};
|
|
|
|
// Run destructors for locals in the coroutine that failed.
|
|
handle.destroy();
|
|
|
|
// Lastly, transfer control to the parent (or nothing, if parent is not yet suspended).
|
|
if (awaiter)
|
|
return awaiter;
|
|
return std::noop_coroutine();
|
|
}
|
|
}
|
|
|
|
decltype(auto) await_resume()
|
|
{
|
|
return m_expression->release_value();
|
|
}
|
|
|
|
T* m_expression { nullptr };
|
|
};
|
|
|
|
}
|
|
|
|
# define CO_TRY_OR_FAIL(expression) (co_await ::AK::Detail::Test::TryOrFailAwaiter { (expression) })
|
|
#else
|
|
# error Unable to work around compiler bugs in definiton of CO_TRY_OR_FAIL.
|
|
#endif
|
|
|
|
namespace Test {
|
|
|
|
#define __ASYNC_TESTCASE_FUNC(x) __async_test_##x
|
|
|
|
#define ASYNC_TEST_CASE(x) \
|
|
static Coroutine<void> __ASYNC_TESTCASE_FUNC(x)(); \
|
|
\
|
|
static void __TESTCASE_FUNC(x)() \
|
|
{ \
|
|
Core::run_async_in_new_event_loop(__ASYNC_TESTCASE_FUNC(x)); \
|
|
} \
|
|
\
|
|
struct __TESTCASE_TYPE(x) { \
|
|
__TESTCASE_TYPE(x) \
|
|
() \
|
|
{ \
|
|
add_test_case_to_suite(adopt_ref(*new ::Test::TestCase(#x, __TESTCASE_FUNC(x), false))); \
|
|
} \
|
|
}; \
|
|
static struct __TESTCASE_TYPE(x) __TESTCASE_TYPE(x); \
|
|
static Coroutine<void> __ASYNC_TESTCASE_FUNC(x)()
|
|
|
|
}
|