[compiler-rt] scudo: Support free_sized and free_aligned_sized from C23 (PR #146556)

Justin King via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 2 07:41:33 PDT 2025


https://github.com/jcking updated https://github.com/llvm/llvm-project/pull/146556

>From 5615f324b0cb8d3cb054a90aedb829dfddc1ee67 Mon Sep 17 00:00:00 2001
From: Justin King <jcking at google.com>
Date: Tue, 17 Jun 2025 10:52:16 -0700
Subject: [PATCH] scudo: Support free_sized and free_aligned_sized from C23

Signed-off-by: Justin King <jcking at google.com>
---
 compiler-rt/lib/scudo/standalone/chunk.h      |   9 +-
 compiler-rt/lib/scudo/standalone/combined.h   |  87 +++++++--
 compiler-rt/lib/scudo/standalone/flags.inc    |  17 +-
 .../lib/scudo/standalone/internal_defs.h      |   8 +
 compiler-rt/lib/scudo/standalone/options.h    |   3 +
 compiler-rt/lib/scudo/standalone/report.cpp   |  61 +++++-
 compiler-rt/lib/scudo/standalone/report.h     |   4 +
 .../scudo/standalone/tests/combined_test.cpp  |  26 +--
 .../scudo/standalone/tests/report_test.cpp    |   2 +-
 .../scudo/standalone/tests/scudo_unit_test.h  |  10 +
 .../standalone/tests/scudo_unit_test_main.cpp |   8 -
 .../standalone/tests/wrappers_c_test.cpp      | 184 ++++++++++++++++++
 .../standalone/tests/wrappers_cpp_test.cpp    |  16 +-
 .../lib/scudo/standalone/wrappers_c.inc       |  28 ++-
 .../lib/scudo/standalone/wrappers_cpp.cpp     |  28 +--
 15 files changed, 409 insertions(+), 82 deletions(-)

diff --git a/compiler-rt/lib/scudo/standalone/chunk.h b/compiler-rt/lib/scudo/standalone/chunk.h
index 9da2dc57e71a1..c0b90a206c076 100644
--- a/compiler-rt/lib/scudo/standalone/chunk.h
+++ b/compiler-rt/lib/scudo/standalone/chunk.h
@@ -53,10 +53,11 @@ namespace Chunk {
 // but https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414 prevents it from
 // happening, as it will error, complaining the number of bits is not enough.
 enum Origin : u8 {
-  Malloc = 0,
-  New = 1,
-  NewArray = 2,
-  Memalign = 3,
+  Malloc = 0,       // malloc, calloc, realloc, free, free_sized
+  New = 1,          // operator new, operator delete
+  NewArray = 2,     // operator new [], operator delete []
+  AlignedAlloc = 3, // aligned_alloc, posix_memalign, memalign, pvalloc, valloc,
+                    // free_aligned_sized
 };
 
 enum State : u8 { Available = 0, Allocated = 1, Quarantined = 2 };
diff --git a/compiler-rt/lib/scudo/standalone/combined.h b/compiler-rt/lib/scudo/standalone/combined.h
index 87acdec2a3bac..8d38f26de9375 100644
--- a/compiler-rt/lib/scudo/standalone/combined.h
+++ b/compiler-rt/lib/scudo/standalone/combined.h
@@ -170,6 +170,12 @@ class Allocator {
       Primary.Options.set(OptionBit::DeallocTypeMismatch);
     if (getFlags()->delete_size_mismatch)
       Primary.Options.set(OptionBit::DeleteSizeMismatch);
+    if (getFlags()->free_size_mismatch)
+      Primary.Options.set(OptionBit::FreeSizeMismatch);
+    if (getFlags()->free_alignment_mismatch)
+      Primary.Options.set(OptionBit::FreeAlignmentMismatch);
+    if (getFlags()->delete_alignment_mismatch)
+      Primary.Options.set(OptionBit::DeleteAlignmentMismatch);
     if (allocatorSupportsMemoryTagging<AllocatorConfig>() &&
         systemSupportsMemoryTagging())
       Primary.Options.set(OptionBit::UseMemoryTagging);
@@ -433,7 +439,8 @@ class Allocator {
   }
 
   NOINLINE void deallocate(void *Ptr, Chunk::Origin Origin, uptr DeleteSize = 0,
-                           UNUSED uptr Alignment = MinAlignment) {
+                           bool HasDeleteSize = false, uptr DeleteAlignment = 0,
+                           bool HasDeleteAlignment = false) {
     if (UNLIKELY(!Ptr))
       return;
 
@@ -456,6 +463,9 @@ class Allocator {
     }
 #endif // GWP_ASAN_HOOKS
 
+    if (UNLIKELY(HasDeleteAlignment && !isPowerOfTwo(DeleteAlignment)))
+      reportAlignmentNotPowerOfTwo(DeleteAlignment);
+
     if (UNLIKELY(!isAligned(reinterpret_cast<uptr>(Ptr), MinAlignment)))
       reportMisalignedPointer(AllocatorAction::Deallocating, Ptr);
 
@@ -470,19 +480,41 @@ class Allocator {
 
     const Options Options = Primary.Options.load();
     if (Options.get(OptionBit::DeallocTypeMismatch)) {
-      if (UNLIKELY(Header.OriginOrWasZeroed != Origin)) {
-        // With the exception of memalign'd chunks, that can be still be free'd.
-        if (Header.OriginOrWasZeroed != Chunk::Origin::Memalign ||
-            Origin != Chunk::Origin::Malloc)
-          reportDeallocTypeMismatch(AllocatorAction::Deallocating, Ptr,
-                                    Header.OriginOrWasZeroed, Origin);
-      }
+      if (UNLIKELY(isOriginMismatch(
+              static_cast<Chunk::Origin>(Header.OriginOrWasZeroed), Origin,
+              HasDeleteSize)))
+        reportDeallocTypeMismatch(AllocatorAction::Deallocating, Ptr,
+                                  Header.OriginOrWasZeroed, Origin,
+                                  HasDeleteSize);
     }
 
     const uptr Size = getSize(Ptr, &Header);
-    if (DeleteSize && Options.get(OptionBit::DeleteSizeMismatch)) {
-      if (UNLIKELY(DeleteSize != Size))
-        reportDeleteSizeMismatch(Ptr, DeleteSize, Size);
+    switch (Origin) {
+    case Chunk::Origin::New:
+      FALLTHROUGH;
+    case Chunk::Origin::NewArray:
+      if (Options.get(OptionBit::DeleteSizeMismatch) && HasDeleteSize) {
+        if (UNLIKELY(DeleteSize != Size))
+          reportDeleteSizeMismatch(Ptr, DeleteSize, Size);
+      }
+      if (Options.get(OptionBit::DeleteAlignmentMismatch) &&
+          HasDeleteAlignment) {
+        if (UNLIKELY(!isAligned(reinterpret_cast<uptr>(Ptr), DeleteAlignment)))
+          reportDeleteAlignmentMismatch(Ptr, DeleteAlignment);
+      }
+      break;
+    case Chunk::Origin::AlignedAlloc:
+      if (Options.get(OptionBit::FreeAlignmentMismatch) && HasDeleteAlignment) {
+        if (UNLIKELY(!isAligned(reinterpret_cast<uptr>(Ptr), DeleteAlignment)))
+          reportFreeAlignmentMismatch(Ptr, DeleteAlignment);
+      }
+      FALLTHROUGH;
+    case Chunk::Origin::Malloc:
+      if (Options.get(OptionBit::FreeSizeMismatch) && HasDeleteSize) {
+        if (UNLIKELY(DeleteSize != Size))
+          reportFreeSizeMismatch(Ptr, DeleteSize, Size);
+      }
+      break;
     }
 
     quarantineOrDeallocateChunk(Options, TaggedPtr, &Header, Size);
@@ -529,14 +561,20 @@ class Allocator {
     if (UNLIKELY(Header.State != Chunk::State::Allocated))
       reportInvalidChunkState(AllocatorAction::Reallocating, OldPtr);
 
-    // Pointer has to be allocated with a malloc-type function. Some
-    // applications think that it is OK to realloc a memalign'ed pointer, which
-    // will trigger this check. It really isn't.
     if (Options.get(OptionBit::DeallocTypeMismatch)) {
-      if (UNLIKELY(Header.OriginOrWasZeroed != Chunk::Origin::Malloc))
-        reportDeallocTypeMismatch(AllocatorAction::Reallocating, OldPtr,
-                                  Header.OriginOrWasZeroed,
-                                  Chunk::Origin::Malloc);
+      // There is no language prohibiting the use of realloc with
+      // aligned_alloc/posix_memalign/memalign and etc. The outcome in
+      // practice is that the newly allocated memory will typically not
+      // have the same alignment but will have minimum alignment. With
+      // regards to operator new, there is no guarantee that the allocator
+      // being used with malloc is the same as operator new. There is also
+      // no guarantee that they share the same minimum alignment guarantees.
+      // So we reject these.
+      if (UNLIKELY(Header.OriginOrWasZeroed == Chunk::Origin::New ||
+                   Header.OriginOrWasZeroed == Chunk::Origin::NewArray))
+        reportDeallocTypeMismatch(
+            AllocatorAction::Reallocating, OldPtr, Header.OriginOrWasZeroed,
+            Chunk::Origin::Malloc, /*HasDeleteSize=*/false);
     }
 
     void *BlockBegin = getBlockBegin(OldTaggedPtr, &Header);
@@ -1746,6 +1784,19 @@ class Allocator {
     return (Bytes - sizeof(AllocationRingBuffer)) /
            sizeof(typename AllocationRingBuffer::Entry);
   }
+
+  static bool isOriginMismatch(Chunk::Origin Alloc, Chunk::Origin Dealloc,
+                               bool HasDeleteSize) {
+    if (Alloc == Dealloc) {
+      return false;
+    }
+    if (Alloc == Chunk::Origin::AlignedAlloc &&
+        Dealloc == Chunk::Origin::Malloc && !HasDeleteSize) {
+      // aligned_alloc with free is allowed, but not free_sized.
+      return false;
+    }
+    return true;
+  }
 };
 
 } // namespace scudo
diff --git a/compiler-rt/lib/scudo/standalone/flags.inc b/compiler-rt/lib/scudo/standalone/flags.inc
index ff0c28e1db7c4..690db4d68c1d1 100644
--- a/compiler-rt/lib/scudo/standalone/flags.inc
+++ b/compiler-rt/lib/scudo/standalone/flags.inc
@@ -24,7 +24,7 @@ SCUDO_FLAG(int, quarantine_max_chunk_size, 0,
            "Size (in bytes) up to which chunks will be quarantined (if lower "
            "than or equal to).")
 
-SCUDO_FLAG(bool, dealloc_type_mismatch, false,
+SCUDO_FLAG(bool, dealloc_type_mismatch, 0,
            "Terminate on a type mismatch in allocation-deallocation functions, "
            "eg: malloc/delete, new/free, new/delete[], etc.")
 
@@ -49,3 +49,18 @@ SCUDO_FLAG(int, release_to_os_interval_ms, 5000,
 SCUDO_FLAG(int, allocation_ring_buffer_size, 32768,
            "Entries to keep in the allocation ring buffer for scudo. "
            "Values less or equal to zero disable the buffer.")
+
+SCUDO_FLAG(bool, delete_alignment_mismatch, true,
+           "Terminate on an alignment mismatch between a aligned-delete and "
+           "the actual "
+           "alignment of a chunk (as provided to new/new[]).")
+
+SCUDO_FLAG(bool, free_size_mismatch, true,
+           "Terminate on a size mismatch between a free_sized and the actual "
+           "size of a chunk (as provided to malloc/calloc/realloc).")
+
+SCUDO_FLAG(bool, free_alignment_mismatch, true,
+           "Terminate on an alignment mismatch between a free_aligned_sized "
+           "and the actual "
+           "alignment of a chunk (as provided to "
+           "aligned_alloc/posix_memalign/memalign).")
diff --git a/compiler-rt/lib/scudo/standalone/internal_defs.h b/compiler-rt/lib/scudo/standalone/internal_defs.h
index 27c6b451ffe72..16dd404a308f0 100644
--- a/compiler-rt/lib/scudo/standalone/internal_defs.h
+++ b/compiler-rt/lib/scudo/standalone/internal_defs.h
@@ -48,6 +48,14 @@
 #define USED __attribute__((used))
 #define NOEXCEPT noexcept
 
+#if __has_cpp_attribute(clang::fallthrough)
+#define FALLTHROUGH [[clang::fallthrough]]
+#elif __has_cpp_attribute(fallthrough)
+#define FALLTHROUGH [[fallthrough]]
+#else
+#define FALLTHROUGH
+#endif
+
 // This check is only available on Clang. This is essentially an alias of
 // C++20's 'constinit' specifier which will take care of this when (if?) we can
 // ask all libc's that use Scudo to compile us with C++20. Dynamic
diff --git a/compiler-rt/lib/scudo/standalone/options.h b/compiler-rt/lib/scudo/standalone/options.h
index b20142a415903..d77b9111890cc 100644
--- a/compiler-rt/lib/scudo/standalone/options.h
+++ b/compiler-rt/lib/scudo/standalone/options.h
@@ -25,6 +25,9 @@ enum class OptionBit {
   UseOddEvenTags,
   UseMemoryTagging,
   AddLargeAllocationSlack,
+  DeleteAlignmentMismatch,
+  FreeSizeMismatch,
+  FreeAlignmentMismatch,
 };
 
 struct Options {
diff --git a/compiler-rt/lib/scudo/standalone/report.cpp b/compiler-rt/lib/scudo/standalone/report.cpp
index b97a74b078c2f..d690c5557de5a 100644
--- a/compiler-rt/lib/scudo/standalone/report.cpp
+++ b/compiler-rt/lib/scudo/standalone/report.cpp
@@ -142,13 +142,48 @@ void NORETURN reportMisalignedPointer(AllocatorAction Action, const void *Ptr) {
                 stringifyAction(Action), Ptr);
 }
 
+static const char *stringifyAllocOrigin(Chunk::Origin AllocOrigin) {
+  switch (AllocOrigin) {
+  case Chunk::Origin::Malloc:
+    return "malloc";
+  case Chunk::Origin::New:
+    return "operator new";
+  case Chunk::Origin::NewArray:
+    return "operator new []";
+  case Chunk::Origin::AlignedAlloc:
+    return "aligned_alloc";
+  }
+  return "<invalid origin>";
+}
+
+static const char *stringifyDeallocOrigin(Chunk::Origin DeallocOrigin,
+                                          bool HasDeleteSize) {
+  switch (DeallocOrigin) {
+  case Chunk::Origin::Malloc:
+    if (HasDeleteSize)
+      return "free_sized";
+    return "free";
+  case Chunk::Origin::New:
+    return "operator delete";
+  case Chunk::Origin::NewArray:
+    return "operator delete []";
+  case Chunk::Origin::AlignedAlloc:
+    return "free_aligned_sized";
+  }
+  return "<invalid origin>";
+}
+
 // The deallocation function used is at odds with the one used to allocate the
 // chunk (eg: new[]/delete or malloc/delete, and so on).
 void NORETURN reportDeallocTypeMismatch(AllocatorAction Action, const void *Ptr,
-                                        u8 TypeA, u8 TypeB) {
+                                        u8 TypeA, u8 TypeB,
+                                        bool HasDeleteSize) {
   ScopedErrorReport Report;
-  Report.append("allocation type mismatch when %s address %p (%d vs %d)\n",
-                stringifyAction(Action), Ptr, TypeA, TypeB);
+  Report.append(
+      "allocation type mismatch when %s address %p (%s vs %s)\n",
+      stringifyAction(Action), Ptr,
+      stringifyAllocOrigin(static_cast<Chunk::Origin>(TypeA)),
+      stringifyDeallocOrigin(static_cast<Chunk::Origin>(TypeB), HasDeleteSize));
 }
 
 // The size specified to the delete operator does not match the one that was
@@ -161,6 +196,26 @@ void NORETURN reportDeleteSizeMismatch(const void *Ptr, uptr Size,
       Size, ExpectedSize);
 }
 
+void NORETURN reportDeleteAlignmentMismatch(const void *Ptr, uptr Alignment) {
+  ScopedErrorReport Report;
+  Report.append("invalid aligned delete when deallocating address %p (%zu)\n",
+                Ptr, Alignment);
+}
+
+void NORETURN reportFreeSizeMismatch(const void *Ptr, uptr Size,
+                                     uptr ExpectedSize) {
+  ScopedErrorReport Report;
+  Report.append(
+      "invalid sized free when deallocating address %p (%zu vs %zu)\n", Ptr,
+      Size, ExpectedSize);
+}
+
+void NORETURN reportFreeAlignmentMismatch(const void *Ptr, uptr Alignment) {
+  ScopedErrorReport Report;
+  Report.append("invalid aligned free when deallocating address %p (%zu)\n",
+                Ptr, Alignment);
+}
+
 void NORETURN reportAlignmentNotPowerOfTwo(uptr Alignment) {
   ScopedErrorReport Report;
   Report.append(
diff --git a/compiler-rt/lib/scudo/standalone/report.h b/compiler-rt/lib/scudo/standalone/report.h
index c397dd3fc9c65..5cb59525784c8 100644
--- a/compiler-rt/lib/scudo/standalone/report.h
+++ b/compiler-rt/lib/scudo/standalone/report.h
@@ -47,6 +47,10 @@ void NORETURN reportDeallocTypeMismatch(AllocatorAction Action, const void *Ptr,
                                         u8 TypeA, u8 TypeB);
 void NORETURN reportDeleteSizeMismatch(const void *Ptr, uptr Size,
                                        uptr ExpectedSize);
+void NORETURN reportDeleteAlignmentMismatch(const void *Ptr, uptr Alignment);
+void NORETURN reportFreeSizeMismatch(const void *Ptr, uptr Size,
+                                     uptr ExpectedSize);
+void NORETURN reportFreeAlignmentMismatch(const void *Ptr, uptr Alignment);
 
 // C wrappers errors.
 void NORETURN reportAlignmentNotPowerOfTwo(uptr Alignment);
diff --git a/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp b/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp
index 7e8d5b4396d2e..6026c3b2e7f9d 100644
--- a/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp
+++ b/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp
@@ -327,7 +327,7 @@ void ScudoCombinedTest<Config>::BasicTest(scudo::uptr SizeLog) {
       EXPECT_LE(Size, Allocator->getUsableSize(P));
       memset(P, 0xaa, Size);
       checkMemoryTaggingMaybe(Allocator, P, Size, Align);
-      Allocator->deallocate(P, Origin, Size);
+      Allocator->deallocate(P, Origin, Size, true);
     }
   }
 
@@ -374,7 +374,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, ZeroContents) {
       for (scudo::uptr I = 0; I < Size; I++)
         ASSERT_EQ((reinterpret_cast<char *>(P))[I], '\0');
       memset(P, 0xaa, Size);
-      Allocator->deallocate(P, Origin, Size);
+      Allocator->deallocate(P, Origin, Size, true);
     }
   }
 }
@@ -392,7 +392,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, ZeroFill) {
       for (scudo::uptr I = 0; I < Size; I++)
         ASSERT_EQ((reinterpret_cast<char *>(P))[I], '\0');
       memset(P, 0xaa, Size);
-      Allocator->deallocate(P, Origin, Size);
+      Allocator->deallocate(P, Origin, Size, true);
     }
   }
 }
@@ -709,7 +709,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, ThreadedCombined) {
 
       while (!V.empty()) {
         auto Pair = V.back();
-        Allocator->deallocate(Pair.first, Origin, Pair.second);
+        Allocator->deallocate(Pair.first, Origin, Pair.second, true);
         V.pop_back();
       }
     });
@@ -782,26 +782,26 @@ TEST(ScudoCombinedDeathTest, DeathCombined) {
   EXPECT_NE(P, nullptr);
 
   // Invalid sized deallocation.
-  EXPECT_DEATH(Allocator->deallocate(P, Origin, Size + 8U), "");
+  EXPECT_DEATH(Allocator->deallocate(P, Origin, Size + 8U, true), "");
 
   // Misaligned pointer. Potentially unused if EXPECT_DEATH isn't available.
   UNUSED void *MisalignedP =
       reinterpret_cast<void *>(reinterpret_cast<scudo::uptr>(P) | 1U);
-  EXPECT_DEATH(Allocator->deallocate(MisalignedP, Origin, Size), "");
-  EXPECT_DEATH(Allocator->reallocate(MisalignedP, Size * 2U), "");
+  EXPECT_DEATH(Allocator->deallocate(MisalignedP, Origin, Size, true), "");
+  EXPECT_DEATH(Allocator->reallocate(MisalignedP, Size * 2U, true), "");
 
   // Header corruption.
   scudo::u64 *H =
       reinterpret_cast<scudo::u64 *>(scudo::Chunk::getAtomicHeader(P));
   *H ^= 0x42U;
-  EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), "");
+  EXPECT_DEATH(Allocator->deallocate(P, Origin, Size, true), "");
   *H ^= 0x420042U;
-  EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), "");
+  EXPECT_DEATH(Allocator->deallocate(P, Origin, Size, true), "");
   *H ^= 0x420000U;
 
   // Invalid chunk state.
-  Allocator->deallocate(P, Origin, Size);
-  EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), "");
+  Allocator->deallocate(P, Origin, Size, true);
+  EXPECT_DEATH(Allocator->deallocate(P, Origin, Size, true), "");
   EXPECT_DEATH(Allocator->reallocate(P, Size * 2U), "");
   EXPECT_DEATH(Allocator->getUsableSize(P), "");
 }
@@ -908,13 +908,13 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, DisableMemInit) {
       memset(Ptrs[I], 0xaa, Size);
     }
     for (unsigned I = 0; I != Ptrs.size(); ++I)
-      Allocator->deallocate(Ptrs[I], Origin, Size);
+      Allocator->deallocate(Ptrs[I], Origin, Size, true);
     for (unsigned I = 0; I != Ptrs.size(); ++I) {
       Ptrs[I] = Allocator->allocate(Size - 8, Origin);
       memset(Ptrs[I], 0xbb, Size - 8);
     }
     for (unsigned I = 0; I != Ptrs.size(); ++I)
-      Allocator->deallocate(Ptrs[I], Origin, Size - 8);
+      Allocator->deallocate(Ptrs[I], Origin, Size - 8, true);
     for (unsigned I = 0; I != Ptrs.size(); ++I) {
       Ptrs[I] = Allocator->allocate(Size, Origin, 1U << MinAlignLog, true);
       for (scudo::uptr J = 0; J < Size; ++J)
diff --git a/compiler-rt/lib/scudo/standalone/tests/report_test.cpp b/compiler-rt/lib/scudo/standalone/tests/report_test.cpp
index 514837df1a43a..18c5717c11c2f 100644
--- a/compiler-rt/lib/scudo/standalone/tests/report_test.cpp
+++ b/compiler-rt/lib/scudo/standalone/tests/report_test.cpp
@@ -41,7 +41,7 @@ TEST(ScudoReportDeathTest, Generic) {
       scudo::reportMisalignedPointer(scudo::AllocatorAction::Deallocating, P),
       "Scudo ERROR.*deallocating.*42424242");
   EXPECT_DEATH(scudo::reportDeallocTypeMismatch(
-                   scudo::AllocatorAction::Reallocating, P, 0, 1),
+                   scudo::AllocatorAction::Reallocating, P, 0, 1, false),
                "Scudo ERROR.*reallocating.*42424242");
   EXPECT_DEATH(scudo::reportDeleteSizeMismatch(P, 123, 456),
                "Scudo ERROR.*42424242.*123.*456");
diff --git a/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test.h b/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test.h
index 27c0e591a2099..a7e614676d8e2 100644
--- a/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test.h
+++ b/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test.h
@@ -58,4 +58,14 @@ using Test = ::testing::Test;
 #define SCUDO_NO_TEST_MAIN
 #endif
 
+// Match Android's default configuration, which disables Scudo's mismatch
+// allocation check, as it is being triggered by some third party code.
+#if SCUDO_ANDROID
+#define DEALLOC_TYPE_MISMATCH "false"
+#define SKIP_ON_ANDROID(T) DISABLED_##T
+#else
+#define DEALLOC_TYPE_MISMATCH "true"
+#define SKIP_ON_ANDROID(T) T
+#endif
+
 extern bool UseQuarantine;
diff --git a/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test_main.cpp b/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test_main.cpp
index 881e0265bb341..d672abb99db0c 100644
--- a/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test_main.cpp
+++ b/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test_main.cpp
@@ -9,14 +9,6 @@
 #include "memtag.h"
 #include "tests/scudo_unit_test.h"
 
-// Match Android's default configuration, which disables Scudo's mismatch
-// allocation check, as it is being triggered by some third party code.
-#if SCUDO_ANDROID
-#define DEALLOC_TYPE_MISMATCH "false"
-#else
-#define DEALLOC_TYPE_MISMATCH "true"
-#endif
-
 static void EnableMemoryTaggingIfSupported() {
   if (!scudo::archSupportsMemoryTagging())
     return;
diff --git a/compiler-rt/lib/scudo/standalone/tests/wrappers_c_test.cpp b/compiler-rt/lib/scudo/standalone/tests/wrappers_c_test.cpp
index f5e17d7214863..2db8416d7bdd6 100644
--- a/compiler-rt/lib/scudo/standalone/tests/wrappers_c_test.cpp
+++ b/compiler-rt/lib/scudo/standalone/tests/wrappers_c_test.cpp
@@ -45,6 +45,8 @@ int malloc_iterate(uintptr_t base, size_t size,
                    void *arg);
 void *valloc(size_t size);
 void *pvalloc(size_t size);
+void free_sized(void *ptr, size_t size);
+void free_aligned_sized(void *ptr, size_t alignment, size_t size);
 
 #ifndef SCUDO_ENABLE_HOOKS_TESTS
 #define SCUDO_ENABLE_HOOKS_TESTS 0
@@ -186,6 +188,188 @@ TEST_F(ScudoWrappersCDeathTest, Malloc) {
   EXPECT_EQ(errno, ENOMEM);
 }
 
+TEST_F(ScudoWrappersCTest, MallocFreeSized) {
+  void *P = malloc(Size);
+  EXPECT_NE(P, nullptr);
+  EXPECT_LE(Size, malloc_usable_size(P));
+  EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % FIRST_32_SECOND_64(8U, 16U), 0U);
+  verifyAllocHookPtr(P);
+  verifyAllocHookSize(Size);
+
+  if (!getenv("SKIP_TYPE_MISMATCH")) {
+    EXPECT_DEATH(free_sized(P, Size - 1), "");
+    EXPECT_DEATH(free_sized(P, Size + 1), "");
+  }
+
+  // An update to this warning in Clang now triggers in this line, but it's ok
+  // because the check is expecting a bad pointer and should fail.
+#if defined(__has_warning) && __has_warning("-Wfree-nonheap-object")
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfree-nonheap-object"
+#endif
+  EXPECT_DEATH(
+      free_sized(reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(P) | 1U),
+                 Size),
+      "");
+#if defined(__has_warning) && __has_warning("-Wfree-nonheap-object")
+#pragma GCC diagnostic pop
+#endif
+
+  free_sized(P, Size);
+  verifyDeallocHookPtr(P);
+  EXPECT_DEATH(free_sized(P, Size), "");
+}
+
+TEST_F(ScudoWrappersCTest, AlignedAllocFreeAlignedSized) {
+  const size_t Alignment = 4096U;
+  const size_t Size = Alignment * 4U;
+  void *P = aligned_alloc(Alignment, Size);
+  EXPECT_NE(P, nullptr);
+  EXPECT_LE(Size, malloc_usable_size(P));
+  EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U);
+  verifyAllocHookPtr(P);
+  verifyAllocHookSize(Size);
+
+  if (!getenv("SKIP_TYPE_MISMATCH")) {
+    EXPECT_DEATH(free_aligned_sized(P, Alignment, Size - 1), "");
+    EXPECT_DEATH(free_aligned_sized(P, Alignment, Size + 1), "");
+    EXPECT_DEATH(
+        free_aligned_sized(P, size_t{1} << (sizeof(size_t) * 8 - 1), Size), "");
+  }
+
+  // An update to this warning in Clang now triggers in this line, but it's ok
+  // because the check is expecting a bad pointer and should fail.
+#if defined(__has_warning) && __has_warning("-Wfree-nonheap-object")
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfree-nonheap-object"
+#endif
+  EXPECT_DEATH(free_aligned_sized(reinterpret_cast<void *>(
+                                      reinterpret_cast<uintptr_t>(P) | 1U),
+                                  Alignment, Size),
+               "");
+#if defined(__has_warning) && __has_warning("-Wfree-nonheap-object")
+#pragma GCC diagnostic pop
+#endif
+
+  free_aligned_sized(P, Alignment, Size);
+  verifyDeallocHookPtr(P);
+  EXPECT_DEATH(free_aligned_sized(P, Alignment, Size), "");
+}
+
+TEST_F(ScudoWrappersCDeathTest, SKIP_ON_ANDROID(MallocFreeAlignedSized)) {
+  if (getenv("SKIP_TYPE_MISMATCH"))
+    GTEST_SKIP();
+
+  void *P = malloc(Size);
+  EXPECT_NE(P, nullptr);
+  EXPECT_LE(Size, malloc_usable_size(P));
+  EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % FIRST_32_SECOND_64(8U, 16U), 0U);
+  verifyAllocHookPtr(P);
+  verifyAllocHookSize(Size);
+
+  EXPECT_DEATH(free_aligned_sized(P, 8, Size), "");
+  EXPECT_DEATH(free_aligned_sized(P, alignof(std::max_align_t), Size), "");
+
+  free_sized(P, Size);
+  verifyDeallocHookPtr(P);
+}
+
+TEST_F(ScudoWrappersCDeathTest, SKIP_ON_ANDROID(AlignedAllocFreeSized)) {
+  if (getenv("SKIP_TYPE_MISMATCH"))
+    GTEST_SKIP();
+
+  const size_t Alignment = 4096U;
+  const size_t Size = Alignment * 4U;
+  void *P = aligned_alloc(Alignment, Size);
+  EXPECT_NE(P, nullptr);
+  EXPECT_LE(Size, malloc_usable_size(P));
+  EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U);
+  verifyAllocHookPtr(P);
+  verifyAllocHookSize(Size);
+
+  EXPECT_DEATH(free_sized(P, Size), "");
+
+  free_aligned_sized(P, Alignment, Size);
+  verifyDeallocHookPtr(P);
+}
+
+TEST_F(ScudoWrappersCDeathTest, SKIP_ON_ANDROID(PosixMemalignFreeSized)) {
+  if (getenv("SKIP_TYPE_MISMATCH"))
+    GTEST_SKIP();
+
+  const size_t Alignment = 4096U;
+  const size_t Size = Alignment * 4U;
+  void *P;
+  EXPECT_EQ(posix_memalign(&P, Alignment, Size), 0);
+  EXPECT_NE(P, nullptr);
+  EXPECT_LE(Size, malloc_usable_size(P));
+  EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U);
+  verifyAllocHookPtr(P);
+  verifyAllocHookSize(Size);
+
+  EXPECT_DEATH(free_sized(P, Size), "");
+
+  free(P);
+  verifyDeallocHookPtr(P);
+}
+
+TEST_F(ScudoWrappersCDeathTest, SKIP_ON_ANDROID(MemalignFreeSized)) {
+  if (getenv("SKIP_TYPE_MISMATCH"))
+    GTEST_SKIP();
+
+  const size_t Alignment = 4096U;
+  const size_t Size = Alignment * 4U;
+  void *P = memalign(Alignment, Size);
+  EXPECT_NE(P, nullptr);
+  EXPECT_LE(Size, malloc_usable_size(P));
+  EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U);
+  verifyAllocHookPtr(P);
+  verifyAllocHookSize(Size);
+
+  EXPECT_DEATH(free_sized(P, Size), "");
+
+  free(P);
+  verifyDeallocHookPtr(P);
+}
+
+TEST_F(ScudoWrappersCDeathTest, SKIP_ON_ANDROID(PvallocFreeSized)) {
+  if (getenv("SKIP_TYPE_MISMATCH"))
+    GTEST_SKIP();
+
+  const size_t PageSize = static_cast<size_t>(sysconf(_SC_PAGESIZE));
+
+  const size_t Size = PageSize;
+  void *P = pvalloc(Size);
+  EXPECT_NE(P, nullptr);
+  EXPECT_LE(Size, malloc_usable_size(P));
+  verifyAllocHookPtr(P);
+  verifyAllocHookSize(Size);
+
+  EXPECT_DEATH(free_sized(P, Size), "");
+
+  free(P);
+  verifyDeallocHookPtr(P);
+}
+
+TEST_F(ScudoWrappersCDeathTest, SKIP_ON_ANDROID(VallocFreeSized)) {
+  if (getenv("SKIP_TYPE_MISMATCH"))
+    GTEST_SKIP();
+
+  const size_t PageSize = static_cast<size_t>(sysconf(_SC_PAGESIZE));
+
+  const size_t Size = PageSize;
+  void *P = valloc(Size);
+  EXPECT_NE(P, nullptr);
+  EXPECT_LE(Size, malloc_usable_size(P));
+  verifyAllocHookPtr(P);
+  verifyAllocHookSize(Size);
+
+  EXPECT_DEATH(free_sized(P, Size), "");
+
+  free(P);
+  verifyDeallocHookPtr(P);
+}
+
 TEST_F(ScudoWrappersCTest, Calloc) {
   void *P = calloc(1U, Size);
   EXPECT_NE(P, nullptr);
diff --git a/compiler-rt/lib/scudo/standalone/tests/wrappers_cpp_test.cpp b/compiler-rt/lib/scudo/standalone/tests/wrappers_cpp_test.cpp
index c802ed22fbad0..76be524a7aa8f 100644
--- a/compiler-rt/lib/scudo/standalone/tests/wrappers_cpp_test.cpp
+++ b/compiler-rt/lib/scudo/standalone/tests/wrappers_cpp_test.cpp
@@ -17,13 +17,6 @@
 #include <thread>
 #include <vector>
 
-// Android does not support checking for new/delete mismatches.
-#if SCUDO_ANDROID
-#define SKIP_MISMATCH_TESTS 1
-#else
-#define SKIP_MISMATCH_TESTS 0
-#endif
-
 void operator delete(void *, size_t) noexcept;
 void operator delete[](void *, size_t) noexcept;
 
@@ -142,11 +135,10 @@ class Pixel {
 // Note that every Cxx allocation function in the test binary will be fulfilled
 // by Scudo. See the comment in the C counterpart of this file.
 
-TEST_F(ScudoWrappersCppDeathTest, New) {
-  if (getenv("SKIP_TYPE_MISMATCH") || SKIP_MISMATCH_TESTS) {
-    printf("Skipped type mismatch tests.\n");
-    return;
-  }
+TEST_F(ScudoWrappersCppDeathTest, SKIP_ON_ANDROID(New)) {
+  if (getenv("SKIP_TYPE_MISMATCH"))
+    GTEST_SKIP();
+
   testCxxNew<bool>();
   testCxxNew<uint8_t>();
   testCxxNew<uint16_t>();
diff --git a/compiler-rt/lib/scudo/standalone/wrappers_c.inc b/compiler-rt/lib/scudo/standalone/wrappers_c.inc
index 59f3fb0962f8b..936fe84ec9c41 100644
--- a/compiler-rt/lib/scudo/standalone/wrappers_c.inc
+++ b/compiler-rt/lib/scudo/standalone/wrappers_c.inc
@@ -68,6 +68,18 @@ INTERFACE WEAK void SCUDO_PREFIX(free)(void *ptr) {
   SCUDO_ALLOCATOR.deallocate(ptr, scudo::Chunk::Origin::Malloc);
 }
 
+INTERFACE WEAK void SCUDO_PREFIX(free_sized)(void *ptr, size_t size) {
+  reportDeallocation(ptr);
+  SCUDO_ALLOCATOR.deallocate(ptr, scudo::Chunk::Origin::Malloc, size, true);
+}
+
+INTERFACE WEAK void
+SCUDO_PREFIX(free_aligned_sized)(void *ptr, size_t alignment, size_t size) {
+  reportDeallocation(ptr);
+  SCUDO_ALLOCATOR.deallocate(ptr, scudo::Chunk::Origin::AlignedAlloc, size,
+                             true, alignment, true);
+}
+
 INTERFACE WEAK struct SCUDO_MALLINFO SCUDO_PREFIX(mallinfo)(void) {
   struct SCUDO_MALLINFO Info = {};
   scudo::StatCounters Stats;
@@ -139,8 +151,8 @@ INTERFACE WEAK void *SCUDO_PREFIX(memalign)(size_t alignment, size_t size) {
       scudo::reportAlignmentNotPowerOfTwo(alignment);
     }
   }
-  void *Ptr =
-      SCUDO_ALLOCATOR.allocate(size, scudo::Chunk::Origin::Memalign, alignment);
+  void *Ptr = SCUDO_ALLOCATOR.allocate(size, scudo::Chunk::Origin::AlignedAlloc,
+                                       alignment);
   reportAllocation(Ptr, size);
   return Ptr;
 }
@@ -152,8 +164,8 @@ INTERFACE WEAK int SCUDO_PREFIX(posix_memalign)(void **memptr, size_t alignment,
       scudo::reportInvalidPosixMemalignAlignment(alignment);
     return EINVAL;
   }
-  void *Ptr =
-      SCUDO_ALLOCATOR.allocate(size, scudo::Chunk::Origin::Memalign, alignment);
+  void *Ptr = SCUDO_ALLOCATOR.allocate(size, scudo::Chunk::Origin::AlignedAlloc,
+                                       alignment);
   if (UNLIKELY(!Ptr))
     return ENOMEM;
   reportAllocation(Ptr, size);
@@ -174,7 +186,7 @@ INTERFACE WEAK void *SCUDO_PREFIX(pvalloc)(size_t size) {
   // pvalloc(0) should allocate one page.
   void *Ptr =
       SCUDO_ALLOCATOR.allocate(size ? scudo::roundUp(size, PageSize) : PageSize,
-                               scudo::Chunk::Origin::Memalign, PageSize);
+                               scudo::Chunk::Origin::AlignedAlloc, PageSize);
   reportAllocation(Ptr, scudo::roundUp(size, PageSize));
 
   return scudo::setErrnoOnNull(Ptr);
@@ -218,7 +230,7 @@ INTERFACE WEAK void *SCUDO_PREFIX(realloc)(void *ptr, size_t size) {
 }
 
 INTERFACE WEAK void *SCUDO_PREFIX(valloc)(size_t size) {
-  void *Ptr = SCUDO_ALLOCATOR.allocate(size, scudo::Chunk::Origin::Memalign,
+  void *Ptr = SCUDO_ALLOCATOR.allocate(size, scudo::Chunk::Origin::AlignedAlloc,
                                        scudo::getPageSizeCached());
   reportAllocation(Ptr, size);
 
@@ -307,8 +319,8 @@ INTERFACE WEAK void *SCUDO_PREFIX(aligned_alloc)(size_t alignment,
     scudo::reportInvalidAlignedAllocAlignment(alignment, size);
   }
 
-  void *Ptr =
-      SCUDO_ALLOCATOR.allocate(size, scudo::Chunk::Origin::Malloc, alignment);
+  void *Ptr = SCUDO_ALLOCATOR.allocate(size, scudo::Chunk::Origin::AlignedAlloc,
+                                       alignment);
   reportAllocation(Ptr, size);
 
   return scudo::setErrnoOnNull(Ptr);
diff --git a/compiler-rt/lib/scudo/standalone/wrappers_cpp.cpp b/compiler-rt/lib/scudo/standalone/wrappers_cpp.cpp
index 098d4f71acc4a..ac51082d10120 100644
--- a/compiler-rt/lib/scudo/standalone/wrappers_cpp.cpp
+++ b/compiler-rt/lib/scudo/standalone/wrappers_cpp.cpp
@@ -104,47 +104,47 @@ INTERFACE WEAK void operator delete[](void *ptr,
 }
 INTERFACE WEAK void operator delete(void *ptr, size_t size) NOEXCEPT {
   reportDeallocation(ptr);
-  Allocator.deallocate(ptr, scudo::Chunk::Origin::New, size);
+  Allocator.deallocate(ptr, scudo::Chunk::Origin::New, size, true);
 }
 INTERFACE WEAK void operator delete[](void *ptr, size_t size) NOEXCEPT {
   reportDeallocation(ptr);
-  Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, size);
+  Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, size, true);
 }
 INTERFACE WEAK void operator delete(void *ptr,
                                     std::align_val_t align) NOEXCEPT {
   reportDeallocation(ptr);
-  Allocator.deallocate(ptr, scudo::Chunk::Origin::New, 0,
-                       static_cast<scudo::uptr>(align));
+  Allocator.deallocate(ptr, scudo::Chunk::Origin::New, 0, false,
+                       static_cast<scudo::uptr>(align), true);
 }
 INTERFACE WEAK void operator delete[](void *ptr,
                                       std::align_val_t align) NOEXCEPT {
   reportDeallocation(ptr);
-  Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, 0,
-                       static_cast<scudo::uptr>(align));
+  Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, 0, false,
+                       static_cast<scudo::uptr>(align), true);
 }
 INTERFACE WEAK void operator delete(void *ptr, std::align_val_t align,
                                     std::nothrow_t const &) NOEXCEPT {
   reportDeallocation(ptr);
-  Allocator.deallocate(ptr, scudo::Chunk::Origin::New, 0,
-                       static_cast<scudo::uptr>(align));
+  Allocator.deallocate(ptr, scudo::Chunk::Origin::New, 0, false,
+                       static_cast<scudo::uptr>(align), true);
 }
 INTERFACE WEAK void operator delete[](void *ptr, std::align_val_t align,
                                       std::nothrow_t const &) NOEXCEPT {
   reportDeallocation(ptr);
-  Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, 0,
-                       static_cast<scudo::uptr>(align));
+  Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, 0, false,
+                       static_cast<scudo::uptr>(align), true);
 }
 INTERFACE WEAK void operator delete(void *ptr, size_t size,
                                     std::align_val_t align) NOEXCEPT {
   reportDeallocation(ptr);
-  Allocator.deallocate(ptr, scudo::Chunk::Origin::New, size,
-                       static_cast<scudo::uptr>(align));
+  Allocator.deallocate(ptr, scudo::Chunk::Origin::New, size, true,
+                       static_cast<scudo::uptr>(align), true);
 }
 INTERFACE WEAK void operator delete[](void *ptr, size_t size,
                                       std::align_val_t align) NOEXCEPT {
   reportDeallocation(ptr);
-  Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, size,
-                       static_cast<scudo::uptr>(align));
+  Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, size, true,
+                       static_cast<scudo::uptr>(align), true);
 }
 
 #endif // !SCUDO_ANDROID || !_BIONIC



More information about the llvm-commits mailing list