[GWP-ASan] Guard against recursive allocs. Pack TLS for perf. am: 000701f219
am: 29b34e25e9

Change-Id: Ic9010f948ceffccbadc48d81027781e5a29eb3de
diff --git a/gwp_asan/guarded_pool_allocator.cpp b/gwp_asan/guarded_pool_allocator.cpp
index 93743cd..68ec00d 100644
--- a/gwp_asan/guarded_pool_allocator.cpp
+++ b/gwp_asan/guarded_pool_allocator.cpp
@@ -49,7 +49,9 @@
 
 void GuardedPoolAllocator::AllocationMetadata::RecordDeallocation() {
   IsDeallocated = true;
-  // TODO(hctim): Implement stack trace collection.
+  // TODO(hctim): Implement stack trace collection. Ensure that the unwinder is
+  // not called if we have our recursive flag called, otherwise non-reentrant
+  // unwinders may deadlock.
   DeallocationTrace.ThreadID = getThreadID();
 }
 
@@ -124,7 +126,28 @@
     installSignalHandlers();
 }
 
+namespace {
+class ScopedBoolean {
+public:
+  ScopedBoolean(bool &B) : Bool(B) { Bool = true; }
+  ~ScopedBoolean() { Bool = false; }
+
+private:
+  bool &Bool;
+};
+} // anonymous namespace
+
 void *GuardedPoolAllocator::allocate(size_t Size) {
+  // GuardedPagePoolEnd == 0 when GWP-ASan is disabled. If we are disabled, fall
+  // back to the supporting allocator.
+  if (GuardedPagePoolEnd == 0)
+    return nullptr;
+
+  // Protect against recursivity.
+  if (ThreadLocals.RecursiveGuard)
+    return nullptr;
+  ScopedBoolean SB(ThreadLocals.RecursiveGuard);
+
   if (Size == 0 || Size > maximumAllocationSize())
     return nullptr;
 
@@ -385,8 +408,7 @@
   options::Printf_t Printf;
 };
 
-void GuardedPoolAllocator::reportErrorInternal(uintptr_t AccessPtr,
-                                               Error E) {
+void GuardedPoolAllocator::reportErrorInternal(uintptr_t AccessPtr, Error E) {
   if (!pointerIsMine(reinterpret_cast<void *>(AccessPtr))) {
     return;
   }
@@ -395,6 +417,7 @@
   // This does not guarantee that there are no races, because another thread can
   // take the locks during the time that the signal handler is being called.
   PoolMutex.tryLock();
+  ThreadLocals.RecursiveGuard = true;
 
   Printf("*** GWP-ASan detected a memory error ***\n");
   ScopedEndOfReportDecorator Decorator(Printf);
@@ -429,5 +452,7 @@
   // TODO(hctim): Implement dumping here of allocation/deallocation traces.
 }
 
-TLS_INITIAL_EXEC uint64_t GuardedPoolAllocator::NextSampleCounter = 0;
+TLS_INITIAL_EXEC
+GuardedPoolAllocator::ThreadLocalPackedVariables
+    GuardedPoolAllocator::ThreadLocals;
 } // namespace gwp_asan
diff --git a/gwp_asan/guarded_pool_allocator.h b/gwp_asan/guarded_pool_allocator.h
index 50d9baf..9b77c00 100644
--- a/gwp_asan/guarded_pool_allocator.h
+++ b/gwp_asan/guarded_pool_allocator.h
@@ -96,14 +96,11 @@
   ALWAYS_INLINE bool shouldSample() {
     // NextSampleCounter == 0 means we "should regenerate the counter".
     //                   == 1 means we "should sample this allocation".
-    if (UNLIKELY(NextSampleCounter == 0)) {
-      // GuardedPagePoolEnd == 0 if GWP-ASan is disabled.
-      if (UNLIKELY(GuardedPagePoolEnd == 0))
-        return false;
-      NextSampleCounter = (getRandomUnsigned32() % AdjustedSampleRate) + 1;
-    }
+    if (UNLIKELY(ThreadLocals.NextSampleCounter == 0))
+      ThreadLocals.NextSampleCounter =
+          (getRandomUnsigned32() % AdjustedSampleRate) + 1;
 
-    return UNLIKELY(--NextSampleCounter == 0);
+    return UNLIKELY(--ThreadLocals.NextSampleCounter == 0);
   }
 
   // Returns whether the provided pointer is a current sampled allocation that
@@ -245,9 +242,23 @@
   // GWP-ASan is disabled, we wish to never spend wasted cycles recalculating
   // the sample rate.
   uint32_t AdjustedSampleRate = UINT32_MAX;
-  // Thread-local decrementing counter that indicates that a given allocation
-  // should be sampled when it reaches zero.
-  static TLS_INITIAL_EXEC uint64_t NextSampleCounter;
+
+  // Pack the thread local variables into a struct to ensure that they're in
+  // the same cache line for performance reasons. These are the most touched
+  // variables in GWP-ASan.
+  struct alignas(8) ThreadLocalPackedVariables {
+    constexpr ThreadLocalPackedVariables() {}
+    // Thread-local decrementing counter that indicates that a given allocation
+    // should be sampled when it reaches zero.
+    uint32_t NextSampleCounter = 0;
+    // Guard against recursivity. Unwinders often contain complex behaviour that
+    // may not be safe for the allocator (i.e. the unwinder calls dlopen(),
+    // which calls malloc()). When recursive behaviour is detected, we will
+    // automatically fall back to the supporting allocator to supply the
+    // allocation.
+    bool RecursiveGuard = false;
+  };
+  static TLS_INITIAL_EXEC ThreadLocalPackedVariables ThreadLocals;
 };
 } // namespace gwp_asan