Add a basic TSan test for ref-counts.

Confirmed that, if the locks are commented out, TSan catches the threading
error.

Change-Id: I3e4ef9a7ca85fdbacf8c8b13694a5a54c6d5f99b
Reviewed-on: https://boringssl-review.googlesource.com/29924
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f58a7bb..104e50c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -268,6 +268,16 @@
   set(OPENSSL_NO_ASM "1")
 endif()
 
+if(TSAN)
+  if(NOT CLANG)
+    message(FATAL_ERROR "Cannot enable TSAN unless using Clang")
+  endif()
+
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=thread")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=thread")
+endif()
+
 if (GCOV)
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage")
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")
diff --git a/crypto/refcount_test.cc b/crypto/refcount_test.cc
index efa501a..6ce0746 100644
--- a/crypto/refcount_test.cc
+++ b/crypto/refcount_test.cc
@@ -16,6 +16,10 @@
 
 #include <gtest/gtest.h>
 
+#if !defined(OPENSSL_NO_THREADS)
+#include <thread>
+#endif
+
 
 TEST(RefCountTest, Basic) {
   CRYPTO_refcount_t count = 0;
@@ -38,3 +42,38 @@
   EXPECT_FALSE(CRYPTO_refcount_dec_and_test_zero(&count));
   EXPECT_EQ(1u, count);
 }
+
+#if !defined(OPENSSL_NO_THREADS)
+// This test is primarily intended to run under ThreadSanitizer.
+TEST(RefCountTest, Threads) {
+  CRYPTO_refcount_t count = 0;
+
+  // Race two increments.
+  {
+    std::thread thread([&] { CRYPTO_refcount_inc(&count); });
+    CRYPTO_refcount_inc(&count);
+    thread.join();
+    EXPECT_EQ(2u, count);
+  }
+
+  // Race an increment with a decrement.
+  {
+    std::thread thread([&] { CRYPTO_refcount_inc(&count); });
+    EXPECT_FALSE(CRYPTO_refcount_dec_and_test_zero(&count));
+    thread.join();
+    EXPECT_EQ(2u, count);
+  }
+
+  // Race two decrements.
+  {
+    bool thread_saw_zero;
+    std::thread thread(
+        [&] { thread_saw_zero = CRYPTO_refcount_dec_and_test_zero(&count); });
+    bool saw_zero = CRYPTO_refcount_dec_and_test_zero(&count);
+    thread.join();
+    EXPECT_EQ(0u, count);
+    // Exactly one thread should see zero.
+    EXPECT_NE(saw_zero, thread_saw_zero);
+  }
+}
+#endif