[GWP-ASan] Mutex implementation [2].
am: 904d61c324

Change-Id: I8b3f25b0535d91e9c081307270eb788823aa7014
diff --git a/gwp_asan/mutex.h b/gwp_asan/mutex.h
new file mode 100644
index 0000000..c29df4c
--- /dev/null
+++ b/gwp_asan/mutex.h
@@ -0,0 +1,50 @@
+//===-- mutex.h -------------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef GWP_ASAN_MUTEX_H_
+#define GWP_ASAN_MUTEX_H_
+
+#ifdef __unix__
+#include <pthread.h>
+#else
+#error "GWP-ASan is not supported on this platform."
+#endif
+
+namespace gwp_asan {
+class Mutex {
+public:
+  constexpr Mutex() = default;
+  ~Mutex() = default;
+  Mutex(const Mutex &) = delete;
+  Mutex &operator=(const Mutex &) = delete;
+  // Lock the mutex.
+  void lock();
+  // Nonblocking trylock of the mutex. Returns true if the lock was acquired.
+  bool tryLock();
+  // Unlock the mutex.
+  void unlock();
+
+private:
+#ifdef __unix__
+  pthread_mutex_t Mu = PTHREAD_MUTEX_INITIALIZER;
+#endif // defined(__unix__)
+};
+
+class ScopedLock {
+public:
+  explicit ScopedLock(Mutex &Mx) : Mu(Mx) { Mu.lock(); }
+  ~ScopedLock() { Mu.unlock(); }
+  ScopedLock(const ScopedLock &) = delete;
+  ScopedLock &operator=(const ScopedLock &) = delete;
+
+private:
+  Mutex &Mu;
+};
+} // namespace gwp_asan
+
+#endif // GWP_ASAN_MUTEX_H_
diff --git a/gwp_asan/platform_specific/mutex_posix.cpp b/gwp_asan/platform_specific/mutex_posix.cpp
new file mode 100644
index 0000000..e15bca8
--- /dev/null
+++ b/gwp_asan/platform_specific/mutex_posix.cpp
@@ -0,0 +1,30 @@
+//===-- mutex_posix.cpp -----------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "mutex.h"
+
+#include <assert.h>
+#include <pthread.h>
+
+namespace gwp_asan {
+void Mutex::lock() {
+  int Status = pthread_mutex_lock(&Mu);
+  assert(Status == 0);
+  // Remove warning for non-debug builds.
+  (void)Status;
+}
+
+bool Mutex::tryLock() { return pthread_mutex_trylock(&Mu) == 0; }
+
+void Mutex::unlock() {
+  int Status = pthread_mutex_unlock(&Mu);
+  assert(Status == 0);
+  // Remove warning for non-debug builds.
+  (void)Status;
+}
+} // namespace gwp_asan
diff --git a/gwp_asan/tests/driver.cpp b/gwp_asan/tests/driver.cpp
new file mode 100644
index 0000000..b402cec
--- /dev/null
+++ b/gwp_asan/tests/driver.cpp
@@ -0,0 +1,14 @@
+//===-- driver.cpp ----------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "gtest/gtest.h"
+
+int main(int argc, char **argv) {
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/gwp_asan/tests/mutex_test.cpp b/gwp_asan/tests/mutex_test.cpp
new file mode 100644
index 0000000..36f7e1d
--- /dev/null
+++ b/gwp_asan/tests/mutex_test.cpp
@@ -0,0 +1,89 @@
+//===-- mutex_test.cpp ------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "gwp_asan/mutex.h"
+#include "gtest/gtest.h"
+
+#include <atomic>
+#include <mutex>
+#include <thread>
+#include <vector>
+
+using gwp_asan::Mutex;
+using gwp_asan::ScopedLock;
+
+TEST(GwpAsanMutexTest, LockUnlockTest) {
+  Mutex Mu;
+
+  ASSERT_TRUE(Mu.tryLock());
+  ASSERT_FALSE(Mu.tryLock());
+  Mu.unlock();
+
+  Mu.lock();
+  Mu.unlock();
+
+  // Ensure that the mutex actually unlocked.
+  ASSERT_TRUE(Mu.tryLock());
+  Mu.unlock();
+}
+
+TEST(GwpAsanMutexTest, ScopedLockUnlockTest) {
+  Mutex Mu;
+  { ScopedLock L(Mu); }
+  // Locking will fail here if the scoped lock failed to unlock.
+  EXPECT_TRUE(Mu.tryLock());
+  Mu.unlock();
+
+  {
+    ScopedLock L(Mu);
+    EXPECT_FALSE(Mu.tryLock()); // Check that the c'tor did lock.
+
+    // Manually unlock and check that this succeeds.
+    Mu.unlock();
+    EXPECT_TRUE(Mu.tryLock()); // Manually lock.
+  }
+  EXPECT_TRUE(Mu.tryLock()); // Assert that the scoped destructor did unlock.
+  Mu.unlock();
+}
+
+static void synchronousIncrementTask(std::atomic<bool> *StartingGun, Mutex *Mu,
+                                     unsigned *Counter,
+                                     unsigned NumIterations) {
+  while (!StartingGun) {
+    // Wait for starting gun.
+  }
+  for (unsigned i = 0; i < NumIterations; ++i) {
+    ScopedLock L(*Mu);
+    (*Counter)++;
+  }
+}
+
+static void runSynchronisedTest(unsigned NumThreads, unsigned CounterMax) {
+  std::vector<std::thread> Threads;
+
+  ASSERT_TRUE(CounterMax % NumThreads == 0);
+
+  std::atomic<bool> StartingGun{false};
+  Mutex Mu;
+  unsigned Counter = 0;
+
+  for (unsigned i = 0; i < NumThreads; ++i)
+    Threads.emplace_back(synchronousIncrementTask, &StartingGun, &Mu, &Counter,
+                         CounterMax / NumThreads);
+
+  StartingGun = true;
+  for (auto &T : Threads)
+    T.join();
+
+  EXPECT_EQ(CounterMax, Counter);
+}
+
+TEST(GwpAsanMutexTest, SynchronisedCounterTest) {
+  runSynchronisedTest(4, 100000);
+  runSynchronisedTest(1000, 1000000);
+}