From f21ee3342d305726fd86a0b92dda17482d9dedd3 Mon Sep 17 00:00:00 2001 From: WeiChungChang Date: Tue, 27 Jun 2023 16:24:32 +0000 Subject: [PATCH] This change allows the use of GoogleTest's ASSERT() and EXPECT() macros (#715) on multiple threads. The failures will be correctly reported and no longer crashes the app. When GoogleTest is used in multi-thread mode (i.e. its macros are used on thread other than the main thread), it may crash as GetTestPartResultReporterForCurrentThread() returns nullptr on a forked thread. This is due to that the ThreadLocal class in our GTEST_OS_STARBOARD implementation doesn't return the default value on newly created threads The ThreadLocal ctor accepts a default value of T to be returned on a thread where the thread local value hasn't been overridden. The existing implementation incorrectly returns T() on a new thread where the value has never been set. When T is a pointer, T() is nullptr and causes crash when dereferenced. Now the default value to ThreadLocal ctor is stored and a copy of it will be returned on a new thread when the thread local value hasn't been set. This ensures that GetTestPartResultReporterForCurrentThread() returns the correct TestPartResultReporterInterface*, and failures of GoogleTest's ASSERT and EXPECT macros on a different thread will be correctly reported and no longer crashes the app. b/230877781 Change-Id: Ibb5ced5611593092d6065af4474027404a783caa (cherry picked from commit 0c9c2cfe4d262111555ad087f7165364e66bc590) --- .../include/gtest/internal/gtest-port.h | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/third_party/googletest/src/googletest/include/gtest/internal/gtest-port.h b/third_party/googletest/src/googletest/include/gtest/internal/gtest-port.h index 90f55bdb658f..cb0847b03dbc 100644 --- a/third_party/googletest/src/googletest/include/gtest/internal/gtest-port.h +++ b/third_party/googletest/src/googletest/include/gtest/internal/gtest-port.h @@ -1200,6 +1200,104 @@ void ClearInjectableArgvs(); #endif // GTEST_HAS_DEATH_TEST // Defines synchronization primitives. +#if defined(GTEST_OS_STARBOARD) +class Mutex { + public: + enum MutexType { kStatic = 0, kDynamic = 1 }; + // We rely on kStaticMutex being 0 as it is to what the linker initializes + // type_ in static mutexes. critical_section_ will be initialized lazily + // in ThreadSafeLazyInit(). + enum StaticConstructorSelector { kStaticMutex = 0 }; + // This constructor intentionally does nothing. It relies on type_ being + // statically initialized to 0 (effectively setting it to kStatic) and on + // ThreadSafeLazyInit() to lazily initialize the rest of the members. + explicit Mutex(StaticConstructorSelector /*dummy*/) {} + Mutex() : type_(kDynamic) { SbMutexCreate(&mutex_); } + ~Mutex() { + if (type_ != kStatic) { + SbMutexDestroy(&mutex_); + } + } + void Lock() { + LazyInit(); + SbMutexAcquire(&mutex_); + } + void Unlock() { SbMutexRelease(&mutex_); } + void AssertHeld() const {} + private: + void LazyInit() { + if (type_ == kStatic && !initialized_) { + static starboard::SpinLock s_lock; + s_lock.Acquire(); + if (!initialized_) { + SbMutexCreate(&mutex_); + initialized_ = true; + } + s_lock.Release(); + } + } + SbMutex mutex_; + friend class GTestMutexLock; + bool initialized_ = false; + // For static mutexes, we rely on type_ member being initialized to zero + // by the linker. + MutexType type_; +}; +#define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ + extern ::testing::internal::Mutex mutex +#define GTEST_DEFINE_STATIC_MUTEX_(mutex) \ + ::testing::internal::Mutex mutex(::testing::internal::Mutex::kStaticMutex) +// We cannot name this class MutexLock because the ctor declaration would +// conflict with a macro named MutexLock, which is defined on some +// platforms. That macro is used as a defensive measure to prevent against +// inadvertent misuses of MutexLock like "MutexLock(&mu)" rather than +// "MutexLock l(&mu)". Hence the typedef trick below. +class GTestMutexLock { + public: + explicit GTestMutexLock(Mutex* mutex) : mutex_(mutex) { + mutex_->Lock(); + } // NOLINT + ~GTestMutexLock() { mutex_->Unlock(); } + private: + Mutex* mutex_; +}; +typedef GTestMutexLock MutexLock; +template +class ThreadLocal { + public: + ThreadLocal() { + key_ = SbThreadCreateLocalKey( + [](void* value) { delete static_cast(value); }); + SB_DCHECK(key_ != kSbThreadLocalKeyInvalid); + } + explicit ThreadLocal(const T& value) : ThreadLocal() { + default_value_ = value; + set(value); + } + ~ThreadLocal() { + SbThreadDestroyLocalKey(key_); + } + T* pointer() { return GetOrCreateValue(); } + const T* pointer() const { return GetOrCreateValue(); } + const T& get() const { return *pointer(); } + void set(const T& value) { *GetOrCreateValue() = value; } + private: + T* GetOrCreateValue() const { + T* ptr = static_cast(SbThreadGetLocalValue(key_)); + if (ptr) { + return ptr; + } else { + T* new_value = new T(default_value_); + bool is_set = SbThreadSetLocalValue(key_, new_value); + SB_CHECK(is_set); + return new_value; + } + } + T default_value_; + SbThreadLocalKey key_; +}; + +#else // GTEST_OS_STARBOARD #if GTEST_IS_THREADSAFE #if GTEST_OS_WINDOWS @@ -1888,6 +1986,7 @@ class GTEST_API_ ThreadLocal { }; #endif // GTEST_IS_THREADSAFE +#endif // GTEST_OS_STARBOARD // Returns the number of threads running in the process, or 0 to indicate that // we cannot detect it.