[clang] [clang][analyzer] Support `ownership_{returns,takes}` attributes (PR #98941)

Pavel Skripkin via cfe-commits cfe-commits at lists.llvm.org
Thu Jul 18 08:55:31 PDT 2024


https://github.com/pskrgag updated https://github.com/llvm/llvm-project/pull/98941

>From bb8a806c919715637e9d4877d02a8fc735c488a6 Mon Sep 17 00:00:00 2001
From: Pavel Skripkin <paskripkin at gmail.com>
Date: Wed, 17 Jul 2024 16:41:20 +0300
Subject: [PATCH 1/9] clang/sema: disallow more than one 'onweship_takes' with
 different classes

---
 clang/include/clang/Basic/DiagnosticSemaKinds.td |  4 ++++
 clang/lib/Sema/SemaDeclAttr.cpp                  | 10 ++++++++++
 clang/test/Sema/attr-ownership.c                 |  4 ++++
 3 files changed, 18 insertions(+)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index d60f32674ca3a..96a391d821f72 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3328,6 +3328,10 @@ def err_ownership_returns_index_mismatch : Error<
   "'ownership_returns' attribute index does not match; here it is %0">;
 def note_ownership_returns_index_mismatch : Note<
   "declared with index %0 here">;
+def err_ownership_takes_class_mismatch : Error<
+  "'ownership_takes' attribute class does not match; here it is '%0'">;
+def note_ownership_takes_class_mismatch : Note<
+  "declared with class '%0' here">;
 def err_format_strftime_third_parameter : Error<
   "strftime format attribute requires 3rd parameter to be 0">;
 def err_format_attribute_not : Error<"format argument not a string type">;
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 41295bfb3b94f..6364a58627085 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -1537,6 +1537,16 @@ static void handleOwnershipAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
                 << Idx.getSourceIndex() << Ex->getSourceRange();
           return;
         }
+      } else if (K == OwnershipAttr::Takes &&
+                 I->getOwnKind() == OwnershipAttr::Takes) {
+        if (I->getModule()->getName() != ModuleName) {
+          S.Diag(I->getLocation(), diag::err_ownership_takes_class_mismatch)
+              << I->getModule()->getName();
+          S.Diag(AL.getLoc(), diag::note_ownership_takes_class_mismatch)
+              << ModuleName << Ex->getSourceRange();
+
+          return;
+        }
       }
     }
     OwnershipArgs.push_back(Idx);
diff --git a/clang/test/Sema/attr-ownership.c b/clang/test/Sema/attr-ownership.c
index 8157ba7145a24..084624353315c 100644
--- a/clang/test/Sema/attr-ownership.c
+++ b/clang/test/Sema/attr-ownership.c
@@ -24,3 +24,7 @@ void f15(int, int)
 void f16(int *i, int *j) __attribute__((ownership_holds(foo, 1))) __attribute__((ownership_holds(foo, 1))); // OK, same index
 void f17(void*) __attribute__((ownership_takes(__, 1)));
 void f18() __attribute__((ownership_takes(foo, 1)));  // expected-warning {{'ownership_takes' attribute only applies to non-K&R-style functions}}
+
+int f19(void *)
+  __attribute__((ownership_takes(foo, 1)))    // expected-error {{'ownership_takes' attribute class does not match; here it is 'foo'}}
+  __attribute__((ownership_takes(foo1, 1)));  // expected-note {{declared with class 'foo1' here}}

>From 22e8c6bbb84860cdf207dae729b0abe78254ad0d Mon Sep 17 00:00:00 2001
From: Pavel Skripkin <paskripkin at gmail.com>
Date: Wed, 17 Jul 2024 18:38:28 +0300
Subject: [PATCH 2/9] csa/MallocChecker: turn llvm_unreachable into asserts

---
 .../StaticAnalyzer/Checkers/MallocChecker.cpp | 22 ++++++++++++-------
 1 file changed, 14 insertions(+), 8 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index fe202c79ed620..8ff71318073ce 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -1293,7 +1293,8 @@ void MallocChecker::checkCXXNewOrCXXDelete(const CallEvent &Call,
                        AF_CXXNewArray);
     break;
   default:
-    llvm_unreachable("not a new/delete operator");
+    assert(false && "not a new/delete operator");
+    return;
   }
 
   C.addTransition(State);
@@ -1489,8 +1490,10 @@ ProgramStateRef MallocChecker::ProcessZeroAllocCheck(
     } else {
       return State;
     }
-  } else
-    llvm_unreachable("not a CallExpr or CXXNewExpr");
+  } else {
+    assert(false && "not a CallExpr or CXXNewExpr");
+    return nullptr;
+  }
 
   assert(Arg);
 
@@ -1925,7 +1928,7 @@ static void printExpectedAllocName(raw_ostream &os, AllocationFamily Family) {
     case AF_IfNameIndex: os << "'if_nameindex()'"; return;
     case AF_InnerBuffer: os << "container-specific allocator"; return;
     case AF_Alloca:
-    case AF_None: llvm_unreachable("not a deallocation expression");
+    case AF_None: assert(false && "not a deallocation expression");
   }
 }
 
@@ -1937,7 +1940,7 @@ static void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family) {
     case AF_IfNameIndex: os << "'if_freenameindex()'"; return;
     case AF_InnerBuffer: os << "container-specific deallocator"; return;
     case AF_Alloca:
-    case AF_None: llvm_unreachable("suspicious argument");
+    case AF_None: assert(false && "suspicious argument");
   }
 }
 
@@ -2145,10 +2148,12 @@ MallocChecker::getCheckIfTracked(AllocationFamily Family,
     return std::nullopt;
   }
   case AF_None: {
-    llvm_unreachable("no family");
+    assert(false && "no family");
+    return std::nullopt;
   }
   }
-  llvm_unreachable("unhandled family");
+  assert(false && "unhandled family");
+  return std::nullopt;
 }
 
 std::optional<MallocChecker::CheckKind>
@@ -3528,7 +3533,8 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N,
           break;
         }
         case AF_None:
-          llvm_unreachable("Unhandled allocation family!");
+          assert(false && "Unhandled allocation family!");
+          return nullptr;
       }
 
       // See if we're releasing memory while inlining a destructor

>From 90be9b47a808fe22adb3b33c5463b34979f23b13 Mon Sep 17 00:00:00 2001
From: Pavel Skripkin <paskripkin at gmail.com>
Date: Wed, 17 Jul 2024 18:43:50 +0300
Subject: [PATCH 3/9] csa/MallocChecker: add support for custom allocation
 classes

Add support for custom allocation classes that could be specified by
ownership_returns attribute.

This patch adds basic support, so new class is not contructed anywhere
and handled as an error everywhere.
---
 .../StaticAnalyzer/Checkers/MallocChecker.cpp | 295 +++++++++++-------
 1 file changed, 180 insertions(+), 115 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index 8ff71318073ce..d6db1d2f0649c 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -103,14 +103,41 @@ using namespace std::placeholders;
 namespace {
 
 // Used to check correspondence between allocators and deallocators.
-enum AllocationFamily {
+enum AllocationFamilyKind {
   AF_None,
   AF_Malloc,
   AF_CXXNew,
   AF_CXXNewArray,
   AF_IfNameIndex,
   AF_Alloca,
-  AF_InnerBuffer
+  AF_InnerBuffer,
+  AF_Custom,
+};
+
+struct AllocationFamily {
+  AllocationFamilyKind Kind;
+  std::optional<StringRef> CustomName;
+
+  explicit AllocationFamily(AllocationFamilyKind kind,
+                            std::optional<StringRef> name = std::nullopt)
+      : Kind(kind), CustomName(name) {
+    assert(kind != AF_Custom || name != std::nullopt);
+  }
+
+  bool operator==(const AllocationFamily &Other) const {
+    return std::tie(Kind, CustomName) == std::tie(Other.Kind, Other.CustomName);
+  }
+
+  bool operator!=(const AllocationFamily &Other) const {
+    return !(*this == Other);
+  }
+
+  void Profile(llvm::FoldingSetNodeID &ID) const {
+    ID.AddInteger(Kind);
+
+    if (Kind == AF_Custom)
+      ID.AddString(CustomName.value());
+  }
 };
 
 } // end of anonymous namespace
@@ -158,7 +185,7 @@ class RefState {
 
   RefState(Kind k, const Stmt *s, AllocationFamily family)
       : S(s), K(k), Family(family) {
-    assert(family != AF_None);
+    assert(family.Kind != AF_None);
   }
 
 public:
@@ -194,7 +221,7 @@ class RefState {
   void Profile(llvm::FoldingSetNodeID &ID) const {
     ID.AddInteger(K);
     ID.AddPointer(S);
-    ID.AddInteger(Family);
+    Family.Profile(ID);
   }
 
   LLVM_DUMP_METHOD void dump(raw_ostream &OS) const {
@@ -899,7 +926,7 @@ class MallocBugVisitor final : public BugReporterVisitor {
     bool IsReleased =
         (RSCurr && RSCurr->isReleased()) && (!RSPrev || !RSPrev->isReleased());
     assert(!IsReleased || (isa_and_nonnull<CallExpr, CXXDeleteExpr>(Stmt)) ||
-           (!Stmt && RSCurr->getAllocationFamily() == AF_InnerBuffer));
+           (!Stmt && RSCurr->getAllocationFamily().Kind == AF_InnerBuffer));
     return IsReleased;
   }
 
@@ -1122,7 +1149,7 @@ MallocChecker::performKernelMalloc(const CallEvent &Call, CheckerContext &C,
   if (TrueState && !FalseState) {
     SVal ZeroVal = C.getSValBuilder().makeZeroVal(Ctx.CharTy);
     return MallocMemAux(C, Call, Call.getArgExpr(0), ZeroVal, TrueState,
-                        AF_Malloc);
+                        AllocationFamily(AF_Malloc));
   }
 
   return std::nullopt;
@@ -1143,7 +1170,7 @@ void MallocChecker::checkBasicAlloc(const CallEvent &Call,
                                     CheckerContext &C) const {
   ProgramStateRef State = C.getState();
   State = MallocMemAux(C, Call, Call.getArgExpr(0), UndefinedVal(), State,
-                       AF_Malloc);
+                       AllocationFamily(AF_Malloc));
   State = ProcessZeroAllocCheck(Call, 0, State);
   C.addTransition(State);
 }
@@ -1157,7 +1184,7 @@ void MallocChecker::checkKernelMalloc(const CallEvent &Call,
     State = *MaybeState;
   else
     State = MallocMemAux(C, Call, Call.getArgExpr(0), UndefinedVal(), State,
-                         AF_Malloc);
+                         AllocationFamily(AF_Malloc));
   C.addTransition(State);
 }
 
@@ -1194,7 +1221,8 @@ void MallocChecker::checkRealloc(const CallEvent &Call, CheckerContext &C,
     return;
 
   ProgramStateRef State = C.getState();
-  State = ReallocMemAux(C, Call, ShouldFreeOnFail, State, AF_Malloc);
+  State = ReallocMemAux(C, Call, ShouldFreeOnFail, State,
+                        AllocationFamily(AF_Malloc));
   State = ProcessZeroAllocCheck(Call, 1, State);
   C.addTransition(State);
 }
@@ -1214,7 +1242,7 @@ void MallocChecker::checkFree(const CallEvent &Call, CheckerContext &C) const {
   if (suppressDeallocationsInSuspiciousContexts(Call, C))
     return;
   State = FreeMemAux(C, Call, State, 0, false, IsKnownToBeAllocatedMemory,
-                     AF_Malloc);
+                     AllocationFamily(AF_Malloc));
   C.addTransition(State);
 }
 
@@ -1222,7 +1250,7 @@ void MallocChecker::checkAlloca(const CallEvent &Call,
                                 CheckerContext &C) const {
   ProgramStateRef State = C.getState();
   State = MallocMemAux(C, Call, Call.getArgExpr(0), UndefinedVal(), State,
-                       AF_Alloca);
+                       AllocationFamily(AF_Alloca));
   State = ProcessZeroAllocCheck(Call, 0, State);
   C.addTransition(State);
 }
@@ -1233,7 +1261,7 @@ void MallocChecker::checkStrdup(const CallEvent &Call,
   const auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
   if (!CE)
     return;
-  State = MallocUpdateRefState(C, CE, State, AF_Malloc);
+  State = MallocUpdateRefState(C, CE, State, AllocationFamily(AF_Malloc));
 
   C.addTransition(State);
 }
@@ -1243,8 +1271,8 @@ void MallocChecker::checkIfNameIndex(const CallEvent &Call,
   ProgramStateRef State = C.getState();
   // Should we model this differently? We can allocate a fixed number of
   // elements with zeros in the last one.
-  State =
-      MallocMemAux(C, Call, UnknownVal(), UnknownVal(), State, AF_IfNameIndex);
+  State = MallocMemAux(C, Call, UnknownVal(), UnknownVal(), State,
+                       AllocationFamily(AF_IfNameIndex));
 
   C.addTransition(State);
 }
@@ -1254,7 +1282,7 @@ void MallocChecker::checkIfFreeNameIndex(const CallEvent &Call,
   ProgramStateRef State = C.getState();
   bool IsKnownToBeAllocatedMemory = false;
   State = FreeMemAux(C, Call, State, 0, false, IsKnownToBeAllocatedMemory,
-                     AF_IfNameIndex);
+                     AllocationFamily(AF_IfNameIndex));
   C.addTransition(State);
 }
 
@@ -1275,22 +1303,22 @@ void MallocChecker::checkCXXNewOrCXXDelete(const CallEvent &Call,
   const FunctionDecl *FD = C.getCalleeDecl(CE);
   switch (FD->getOverloadedOperator()) {
   case OO_New:
-    State =
-        MallocMemAux(C, Call, CE->getArg(0), UndefinedVal(), State, AF_CXXNew);
+    State = MallocMemAux(C, Call, CE->getArg(0), UndefinedVal(), State,
+                         AllocationFamily(AF_CXXNew));
     State = ProcessZeroAllocCheck(Call, 0, State);
     break;
   case OO_Array_New:
     State = MallocMemAux(C, Call, CE->getArg(0), UndefinedVal(), State,
-                         AF_CXXNewArray);
+                         AllocationFamily(AF_CXXNewArray));
     State = ProcessZeroAllocCheck(Call, 0, State);
     break;
   case OO_Delete:
     State = FreeMemAux(C, Call, State, 0, false, IsKnownToBeAllocatedMemory,
-                       AF_CXXNew);
+                       AllocationFamily(AF_CXXNew));
     break;
   case OO_Array_Delete:
     State = FreeMemAux(C, Call, State, 0, false, IsKnownToBeAllocatedMemory,
-                       AF_CXXNewArray);
+                       AllocationFamily(AF_CXXNewArray));
     break;
   default:
     assert(false && "not a new/delete operator");
@@ -1305,7 +1333,8 @@ void MallocChecker::checkGMalloc0(const CallEvent &Call,
   ProgramStateRef State = C.getState();
   SValBuilder &svalBuilder = C.getSValBuilder();
   SVal zeroVal = svalBuilder.makeZeroVal(svalBuilder.getContext().CharTy);
-  State = MallocMemAux(C, Call, Call.getArgExpr(0), zeroVal, State, AF_Malloc);
+  State = MallocMemAux(C, Call, Call.getArgExpr(0), zeroVal, State,
+                       AllocationFamily(AF_Malloc));
   State = ProcessZeroAllocCheck(Call, 0, State);
   C.addTransition(State);
 }
@@ -1313,8 +1342,8 @@ void MallocChecker::checkGMalloc0(const CallEvent &Call,
 void MallocChecker::checkGMemdup(const CallEvent &Call,
                                  CheckerContext &C) const {
   ProgramStateRef State = C.getState();
-  State =
-      MallocMemAux(C, Call, Call.getArgExpr(1), UnknownVal(), State, AF_Malloc);
+  State = MallocMemAux(C, Call, Call.getArgExpr(1), UnknownVal(), State,
+                       AllocationFamily(AF_Malloc));
   State = ProcessZeroAllocCheck(Call, 1, State);
   C.addTransition(State);
 }
@@ -1324,7 +1353,8 @@ void MallocChecker::checkGMallocN(const CallEvent &Call,
   ProgramStateRef State = C.getState();
   SVal Init = UndefinedVal();
   SVal TotalSize = evalMulForBufferSize(C, Call.getArgExpr(0), Call.getArgExpr(1));
-  State = MallocMemAux(C, Call, TotalSize, Init, State, AF_Malloc);
+  State = MallocMemAux(C, Call, TotalSize, Init, State,
+                       AllocationFamily(AF_Malloc));
   State = ProcessZeroAllocCheck(Call, 0, State);
   State = ProcessZeroAllocCheck(Call, 1, State);
   C.addTransition(State);
@@ -1336,7 +1366,8 @@ void MallocChecker::checkGMallocN0(const CallEvent &Call,
   SValBuilder &SB = C.getSValBuilder();
   SVal Init = SB.makeZeroVal(SB.getContext().CharTy);
   SVal TotalSize = evalMulForBufferSize(C, Call.getArgExpr(0), Call.getArgExpr(1));
-  State = MallocMemAux(C, Call, TotalSize, Init, State, AF_Malloc);
+  State = MallocMemAux(C, Call, TotalSize, Init, State,
+                       AllocationFamily(AF_Malloc));
   State = ProcessZeroAllocCheck(Call, 0, State);
   State = ProcessZeroAllocCheck(Call, 1, State);
   C.addTransition(State);
@@ -1366,7 +1397,8 @@ void MallocChecker::preGetdelim(const CallEvent &Call,
   // of reporting any violation of the preconditions.
   bool IsKnownToBeAllocated = false;
   State = FreeMemAux(C, Call.getArgExpr(0), Call, State, false,
-                     IsKnownToBeAllocated, AF_Malloc, false, LinePtr);
+                     IsKnownToBeAllocated, AllocationFamily(AF_Malloc), false,
+                     LinePtr);
   if (State)
     C.addTransition(State);
 }
@@ -1395,13 +1427,15 @@ void MallocChecker::checkGetdelim(const CallEvent &Call,
     return;
 
   State = setDynamicExtent(State, LinePtr->getAsRegion(), *Size, SVB);
-  C.addTransition(MallocUpdateRefState(C, CE, State, AF_Malloc, *LinePtr));
+  C.addTransition(MallocUpdateRefState(C, CE, State,
+                                       AllocationFamily(AF_Malloc), *LinePtr));
 }
 
 void MallocChecker::checkReallocN(const CallEvent &Call,
                                   CheckerContext &C) const {
   ProgramStateRef State = C.getState();
-  State = ReallocMemAux(C, Call, /*ShouldFreeOnFail=*/false, State, AF_Malloc,
+  State = ReallocMemAux(C, Call, /*ShouldFreeOnFail=*/false, State,
+                        AllocationFamily(AF_Malloc),
                         /*SuffixWithN=*/true);
   State = ProcessZeroAllocCheck(Call, 1, State);
   State = ProcessZeroAllocCheck(Call, 2, State);
@@ -1601,7 +1635,8 @@ MallocChecker::processNewAllocation(const CXXAllocatorCall &Call,
   SVal Target = Call.getObjectUnderConstruction();
   if (Call.getOriginExpr()->isArray()) {
     if (auto SizeEx = NE->getArraySize())
-      checkTaintedness(C, Call, C.getSVal(*SizeEx), State, AF_CXXNewArray);
+      checkTaintedness(C, Call, C.getSVal(*SizeEx), State,
+                       AllocationFamily(AF_CXXNewArray));
   }
 
   State = MallocUpdateRefState(C, NE, State, Family, Target);
@@ -1614,7 +1649,8 @@ void MallocChecker::checkNewAllocator(const CXXAllocatorCall &Call,
   if (!C.wasInlined) {
     ProgramStateRef State = processNewAllocation(
         Call, C,
-        (Call.getOriginExpr()->isArray() ? AF_CXXNewArray : AF_CXXNew));
+        AllocationFamily(Call.getOriginExpr()->isArray() ? AF_CXXNewArray
+                                                         : AF_CXXNew));
     C.addTransition(State);
   }
 }
@@ -1658,10 +1694,10 @@ void MallocChecker::checkPostObjCMessage(const ObjCMethodCall &Call,
     return;
 
   bool IsKnownToBeAllocatedMemory;
-  ProgramStateRef State =
-      FreeMemAux(C, Call.getArgExpr(0), Call, C.getState(),
-                 /*Hold=*/true, IsKnownToBeAllocatedMemory, AF_Malloc,
-                 /*ReturnsNullOnFailure=*/true);
+  ProgramStateRef State = FreeMemAux(C, Call.getArgExpr(0), Call, C.getState(),
+                                     /*Hold=*/true, IsKnownToBeAllocatedMemory,
+                                     AllocationFamily(AF_Malloc),
+                                     /*ReturnsNullOnFailure=*/true);
 
   C.addTransition(State);
 }
@@ -1679,9 +1715,10 @@ MallocChecker::MallocMemReturnsAttr(CheckerContext &C, const CallEvent &Call,
   if (!Att->args().empty()) {
     return MallocMemAux(C, Call,
                         Call.getArgExpr(Att->args_begin()->getASTIndex()),
-                        UndefinedVal(), State, AF_Malloc);
+                        UndefinedVal(), State, AllocationFamily(AF_Malloc));
   }
-  return MallocMemAux(C, Call, UnknownVal(), UndefinedVal(), State, AF_Malloc);
+  return MallocMemAux(C, Call, UnknownVal(), UndefinedVal(), State,
+                      AllocationFamily(AF_Malloc));
 }
 
 ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C,
@@ -1771,10 +1808,10 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C,
   unsigned Count = C.blockCount();
   SValBuilder &SVB = C.getSValBuilder();
   const LocationContext *LCtx = C.getPredecessor()->getLocationContext();
-  DefinedSVal RetVal =
-      ((Family == AF_Alloca) ? SVB.getAllocaRegionVal(CE, LCtx, Count)
-                             : SVB.getConjuredHeapSymbolVal(CE, LCtx, Count)
-                                   .castAs<DefinedSVal>());
+  DefinedSVal RetVal = ((Family.Kind == AF_Alloca)
+                            ? SVB.getAllocaRegionVal(CE, LCtx, Count)
+                            : SVB.getConjuredHeapSymbolVal(CE, LCtx, Count)
+                                  .castAs<DefinedSVal>());
   State = State->BindExpr(CE, C.getLocationContext(), RetVal);
 
   // Fill the region with the initialization value.
@@ -1784,7 +1821,7 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C,
   if (Size.isUndef())
     Size = UnknownVal();
 
-  checkTaintedness(C, Call, Size, State, AF_Malloc);
+  checkTaintedness(C, Call, Size, State, AllocationFamily(AF_Malloc));
 
   // Set the region's extent.
   State = setDynamicExtent(State, RetVal.getAsRegion(),
@@ -1842,7 +1879,7 @@ ProgramStateRef MallocChecker::FreeMemAttr(CheckerContext &C,
     ProgramStateRef StateI =
         FreeMemAux(C, Call, State, Arg.getASTIndex(),
                    Att->getOwnKind() == OwnershipAttr::Holds,
-                   IsKnownToBeAllocated, AF_Malloc);
+                   IsKnownToBeAllocated, AllocationFamily(AF_Malloc));
     if (StateI)
       State = StateI;
   }
@@ -1921,26 +1958,50 @@ static bool printMemFnName(raw_ostream &os, CheckerContext &C, const Expr *E) {
 
 static void printExpectedAllocName(raw_ostream &os, AllocationFamily Family) {
 
-  switch(Family) {
-    case AF_Malloc: os << "malloc()"; return;
-    case AF_CXXNew: os << "'new'"; return;
-    case AF_CXXNewArray: os << "'new[]'"; return;
-    case AF_IfNameIndex: os << "'if_nameindex()'"; return;
-    case AF_InnerBuffer: os << "container-specific allocator"; return;
-    case AF_Alloca:
-    case AF_None: assert(false && "not a deallocation expression");
+  switch (Family.Kind) {
+  case AF_Malloc:
+    os << "malloc()";
+    return;
+  case AF_CXXNew:
+    os << "'new'";
+    return;
+  case AF_CXXNewArray:
+    os << "'new[]'";
+    return;
+  case AF_IfNameIndex:
+    os << "'if_nameindex()'";
+    return;
+  case AF_InnerBuffer:
+    os << "container-specific allocator";
+    return;
+  case AF_Alloca:
+  case AF_Custom:
+  case AF_None:
+    assert(false && "not a deallocation expression");
   }
 }
 
 static void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family) {
-  switch(Family) {
-    case AF_Malloc: os << "free()"; return;
-    case AF_CXXNew: os << "'delete'"; return;
-    case AF_CXXNewArray: os << "'delete[]'"; return;
-    case AF_IfNameIndex: os << "'if_freenameindex()'"; return;
-    case AF_InnerBuffer: os << "container-specific deallocator"; return;
-    case AF_Alloca:
-    case AF_None: assert(false && "suspicious argument");
+  switch (Family.Kind) {
+  case AF_Malloc:
+    os << "free()";
+    return;
+  case AF_CXXNew:
+    os << "'delete'";
+    return;
+  case AF_CXXNewArray:
+    os << "'delete[]'";
+    return;
+  case AF_IfNameIndex:
+    os << "'if_freenameindex()'";
+    return;
+  case AF_InnerBuffer:
+    os << "container-specific deallocator";
+    return;
+  case AF_Alloca:
+  case AF_Custom:
+  case AF_None:
+    assert(false && "not a deallocation expression");
   }
 }
 
@@ -1994,7 +2055,7 @@ MallocChecker::FreeMemAux(CheckerContext &C, const Expr *ArgExpr,
     // code. In that case, the ZERO_SIZE_PTR defines a special value used for a
     // zero-sized memory block which is allowed to be freed, despite not being a
     // null pointer.
-    if (Family != AF_Malloc || !isArgZERO_SIZE_PTR(State, C, ArgVal))
+    if (Family.Kind != AF_Malloc || !isArgZERO_SIZE_PTR(State, C, ArgVal))
       HandleNonHeapDealloc(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr,
                            Family);
     return nullptr;
@@ -2044,7 +2105,7 @@ MallocChecker::FreeMemAux(CheckerContext &C, const Expr *ArgExpr,
   if (RsBase) {
 
     // Memory returned by alloca() shouldn't be freed.
-    if (RsBase->getAllocationFamily() == AF_Alloca) {
+    if (RsBase->getAllocationFamily().Kind == AF_Alloca) {
       HandleFreeAlloca(C, ArgVal, ArgExpr->getSourceRange());
       return nullptr;
     }
@@ -2122,7 +2183,7 @@ MallocChecker::FreeMemAux(CheckerContext &C, const Expr *ArgExpr,
 std::optional<MallocChecker::CheckKind>
 MallocChecker::getCheckIfTracked(AllocationFamily Family,
                                  bool IsALeakCheck) const {
-  switch (Family) {
+  switch (Family.Kind) {
   case AF_Malloc:
   case AF_Alloca:
   case AF_IfNameIndex: {
@@ -2147,6 +2208,7 @@ MallocChecker::getCheckIfTracked(AllocationFamily Family,
       return CK_InnerPointerChecker;
     return std::nullopt;
   }
+  case AF_Custom:
   case AF_None: {
     assert(false && "no family");
     return std::nullopt;
@@ -2470,7 +2532,7 @@ void MallocChecker::HandleUseAfterFree(CheckerContext &C, SourceRange Range,
 
     auto R = std::make_unique<PathSensitiveBugReport>(
         *BT_UseFree[*CheckKind],
-        AF == AF_InnerBuffer
+        AF.Kind == AF_InnerBuffer
             ? "Inner pointer of container used after re/deallocation"
             : "Use of memory after it is freed",
         N);
@@ -2479,7 +2541,7 @@ void MallocChecker::HandleUseAfterFree(CheckerContext &C, SourceRange Range,
     R->addRange(Range);
     R->addVisitor<MallocBugVisitor>(Sym);
 
-    if (AF == AF_InnerBuffer)
+    if (AF.Kind == AF_InnerBuffer)
       R->addVisitor(allocation_state::getInnerPointerBRVisitor(Sym));
 
     C.emitReport(std::move(R));
@@ -2738,7 +2800,8 @@ ProgramStateRef MallocChecker::CallocMem(CheckerContext &C,
   SVal TotalSize =
       evalMulForBufferSize(C, Call.getArgExpr(0), Call.getArgExpr(1));
 
-  return MallocMemAux(C, Call, TotalSize, zeroVal, State, AF_Malloc);
+  return MallocMemAux(C, Call, TotalSize, zeroVal, State,
+                      AllocationFamily(AF_Malloc));
 }
 
 MallocChecker::LeakInfo MallocChecker::getAllocationSite(const ExplodedNode *N,
@@ -2793,7 +2856,7 @@ void MallocChecker::HandleLeak(SymbolRef Sym, ExplodedNode *N,
   assert(RS && "cannot leak an untracked symbol");
   AllocationFamily Family = RS->getAllocationFamily();
 
-  if (Family == AF_Alloca)
+  if (Family.Kind == AF_Alloca)
     return;
 
   std::optional<MallocChecker::CheckKind> CheckKind =
@@ -2920,9 +2983,10 @@ void MallocChecker::checkPreCall(const CallEvent &Call,
 
     ProgramStateRef State = C.getState();
     bool IsKnownToBeAllocated;
-    State = FreeMemAux(C, DE->getArgument(), Call, State,
-                       /*Hold*/ false, IsKnownToBeAllocated,
-                       (DE->isArrayForm() ? AF_CXXNewArray : AF_CXXNew));
+    State = FreeMemAux(
+        C, DE->getArgument(), Call, State,
+        /*Hold*/ false, IsKnownToBeAllocated,
+        AllocationFamily(DE->isArrayForm() ? AF_CXXNewArray : AF_CXXNew));
 
     C.addTransition(State);
     return;
@@ -3354,8 +3418,8 @@ ProgramStateRef MallocChecker::checkConstPointerEscape(ProgramStateRef State,
 }
 
 static bool checkIfNewOrNewArrayFamily(const RefState *RS) {
-  return (RS->getAllocationFamily() == AF_CXXNewArray ||
-          RS->getAllocationFamily() == AF_CXXNew);
+  return (RS->getAllocationFamily().Kind == AF_CXXNewArray ||
+          RS->getAllocationFamily().Kind == AF_CXXNew);
 }
 
 ProgramStateRef MallocChecker::checkPointerEscapeAux(
@@ -3436,7 +3500,7 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N,
   const Stmt *S = N->getStmtForDiagnostics();
   // When dealing with containers, we sometimes want to give a note
   // even if the statement is missing.
-  if (!S && (!RSCurr || RSCurr->getAllocationFamily() != AF_InnerBuffer))
+  if (!S && (!RSCurr || RSCurr->getAllocationFamily().Kind != AF_InnerBuffer))
     return nullptr;
 
   const LocationContext *CurrentLC = N->getLocationContext();
@@ -3488,54 +3552,55 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N,
           Sym, "Returned allocated memory");
     } else if (isReleased(RSCurr, RSPrev, S)) {
       const auto Family = RSCurr->getAllocationFamily();
-      switch (Family) {
-        case AF_Alloca:
-        case AF_Malloc:
-        case AF_CXXNew:
-        case AF_CXXNewArray:
-        case AF_IfNameIndex:
-          Msg = "Memory is released";
+      switch (Family.Kind) {
+      case AF_Alloca:
+      case AF_Malloc:
+      case AF_CXXNew:
+      case AF_CXXNewArray:
+      case AF_IfNameIndex:
+        Msg = "Memory is released";
+        StackHint = std::make_unique<StackHintGeneratorForSymbol>(
+            Sym, "Returning; memory was released");
+        break;
+      case AF_InnerBuffer: {
+        const MemRegion *ObjRegion =
+            allocation_state::getContainerObjRegion(statePrev, Sym);
+        const auto *TypedRegion = cast<TypedValueRegion>(ObjRegion);
+        QualType ObjTy = TypedRegion->getValueType();
+        OS << "Inner buffer of '" << ObjTy << "' ";
+
+        if (N->getLocation().getKind() == ProgramPoint::PostImplicitCallKind) {
+          OS << "deallocated by call to destructor";
           StackHint = std::make_unique<StackHintGeneratorForSymbol>(
-              Sym, "Returning; memory was released");
-          break;
-        case AF_InnerBuffer: {
-          const MemRegion *ObjRegion =
-              allocation_state::getContainerObjRegion(statePrev, Sym);
-          const auto *TypedRegion = cast<TypedValueRegion>(ObjRegion);
-          QualType ObjTy = TypedRegion->getValueType();
-          OS << "Inner buffer of '" << ObjTy << "' ";
-
-          if (N->getLocation().getKind() == ProgramPoint::PostImplicitCallKind) {
-            OS << "deallocated by call to destructor";
-            StackHint = std::make_unique<StackHintGeneratorForSymbol>(
-                Sym, "Returning; inner buffer was deallocated");
-          } else {
-            OS << "reallocated by call to '";
-            const Stmt *S = RSCurr->getStmt();
-            if (const auto *MemCallE = dyn_cast<CXXMemberCallExpr>(S)) {
-              OS << MemCallE->getMethodDecl()->getDeclName();
-            } else if (const auto *OpCallE = dyn_cast<CXXOperatorCallExpr>(S)) {
-              OS << OpCallE->getDirectCallee()->getDeclName();
-            } else if (const auto *CallE = dyn_cast<CallExpr>(S)) {
-              auto &CEMgr = BRC.getStateManager().getCallEventManager();
-              CallEventRef<> Call =
-                  CEMgr.getSimpleCall(CallE, state, CurrentLC, {nullptr, 0});
-              if (const auto *D = dyn_cast_or_null<NamedDecl>(Call->getDecl()))
-                OS << D->getDeclName();
-              else
-                OS << "unknown";
-            }
-            OS << "'";
-            StackHint = std::make_unique<StackHintGeneratorForSymbol>(
-                Sym, "Returning; inner buffer was reallocated");
+              Sym, "Returning; inner buffer was deallocated");
+        } else {
+          OS << "reallocated by call to '";
+          const Stmt *S = RSCurr->getStmt();
+          if (const auto *MemCallE = dyn_cast<CXXMemberCallExpr>(S)) {
+            OS << MemCallE->getMethodDecl()->getDeclName();
+          } else if (const auto *OpCallE = dyn_cast<CXXOperatorCallExpr>(S)) {
+            OS << OpCallE->getDirectCallee()->getDeclName();
+          } else if (const auto *CallE = dyn_cast<CallExpr>(S)) {
+            auto &CEMgr = BRC.getStateManager().getCallEventManager();
+            CallEventRef<> Call =
+                CEMgr.getSimpleCall(CallE, state, CurrentLC, {nullptr, 0});
+            if (const auto *D = dyn_cast_or_null<NamedDecl>(Call->getDecl()))
+              OS << D->getDeclName();
+            else
+              OS << "unknown";
           }
-          Msg = OS.str();
-          break;
+          OS << "'";
+          StackHint = std::make_unique<StackHintGeneratorForSymbol>(
+              Sym, "Returning; inner buffer was reallocated");
+        }
+        Msg = OS.str();
+        break;
         }
+        case AF_Custom:
         case AF_None:
           assert(false && "Unhandled allocation family!");
           return nullptr;
-      }
+        }
 
       // See if we're releasing memory while inlining a destructor
       // (or one of its callees). This turns on various common
@@ -3609,7 +3674,7 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N,
   // Generate the extra diagnostic.
   PathDiagnosticLocation Pos;
   if (!S) {
-    assert(RSCurr->getAllocationFamily() == AF_InnerBuffer);
+    assert(RSCurr->getAllocationFamily().Kind == AF_InnerBuffer);
     auto PostImplCall = N->getLocation().getAs<PostImplicitCall>();
     if (!PostImplCall)
       return nullptr;
@@ -3656,7 +3721,7 @@ namespace allocation_state {
 
 ProgramStateRef
 markReleased(ProgramStateRef State, SymbolRef Sym, const Expr *Origin) {
-  AllocationFamily Family = AF_InnerBuffer;
+  AllocationFamily Family(AF_InnerBuffer);
   return State->set<RegionState>(Sym, RefState::getReleased(Family, Origin));
 }
 

>From d16440a09512ca979ea600413e121c7f1a5942d0 Mon Sep 17 00:00:00 2001
From: Pavel Skripkin <paskripkin at gmail.com>
Date: Wed, 17 Jul 2024 18:49:13 +0300
Subject: [PATCH 4/9] csa/MismatchedDeallocator: handle custom allocation
 clasess

Patch introduces support for custom allocation classes in
MismatchedDeallocator.

Patch preserves previous behavior, in which 'malloc' class was handled
as AF_Malloc type.
---
 .../StaticAnalyzer/Checkers/MallocChecker.cpp | 69 ++++++++++++++-----
 1 file changed, 53 insertions(+), 16 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index d6db1d2f0649c..b5209fc21ffaf 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -118,10 +118,17 @@ struct AllocationFamily {
   AllocationFamilyKind Kind;
   std::optional<StringRef> CustomName;
 
-  explicit AllocationFamily(AllocationFamilyKind kind,
-                            std::optional<StringRef> name = std::nullopt)
-      : Kind(kind), CustomName(name) {
-    assert(kind != AF_Custom || name != std::nullopt);
+  explicit AllocationFamily(AllocationFamilyKind Kind,
+                            std::optional<StringRef> Name = std::nullopt)
+      : Kind(Kind), CustomName(Name) {
+    assert((Kind != AF_Custom || Name.has_value()) &&
+           "Custom family must specify also the name");
+
+    // Preseve previous behavior when "malloc" class means AF_Malloc
+    if (Kind == AF_Custom && CustomName.value() == "malloc") {
+      Kind = AF_Malloc;
+      CustomName = std::nullopt;
+    }
   }
 
   bool operator==(const AllocationFamily &Other) const {
@@ -1709,16 +1716,15 @@ MallocChecker::MallocMemReturnsAttr(CheckerContext &C, const CallEvent &Call,
   if (!State)
     return nullptr;
 
-  if (Att->getModule()->getName() != "malloc")
-    return nullptr;
+  auto attrClassName = Att->getModule()->getName();
+  auto Family = AllocationFamily(AF_Custom, attrClassName);
 
   if (!Att->args().empty()) {
     return MallocMemAux(C, Call,
                         Call.getArgExpr(Att->args_begin()->getASTIndex()),
-                        UndefinedVal(), State, AllocationFamily(AF_Malloc));
+                        UndefinedVal(), State, Family);
   }
-  return MallocMemAux(C, Call, UnknownVal(), UndefinedVal(), State,
-                      AllocationFamily(AF_Malloc));
+  return MallocMemAux(C, Call, UnknownVal(), UndefinedVal(), State, Family);
 }
 
 ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C,
@@ -1870,8 +1876,8 @@ ProgramStateRef MallocChecker::FreeMemAttr(CheckerContext &C,
   if (!State)
     return nullptr;
 
-  if (Att->getModule()->getName() != "malloc")
-    return nullptr;
+  auto attrClassName = Att->getModule()->getName();
+  auto Family = AllocationFamily(AF_Custom, attrClassName);
 
   bool IsKnownToBeAllocated = false;
 
@@ -1879,7 +1885,7 @@ ProgramStateRef MallocChecker::FreeMemAttr(CheckerContext &C,
     ProgramStateRef StateI =
         FreeMemAux(C, Call, State, Arg.getASTIndex(),
                    Att->getOwnKind() == OwnershipAttr::Holds,
-                   IsKnownToBeAllocated, AllocationFamily(AF_Malloc));
+                   IsKnownToBeAllocated, Family);
     if (StateI)
       State = StateI;
   }
@@ -1917,6 +1923,30 @@ static bool didPreviousFreeFail(ProgramStateRef State,
   return false;
 }
 
+static void printOwnershipTakesList(raw_ostream &os, CheckerContext &C,
+                                    const Expr *E) {
+  if (const CallExpr *CE = dyn_cast<CallExpr>(E)) {
+    const FunctionDecl *FD = CE->getDirectCallee();
+    if (!FD)
+      return;
+
+    if (!FD->hasAttrs())
+      return;
+
+    // Only one ownership_takes attribute is allowed
+    for (const auto *I : FD->specific_attrs<OwnershipAttr>()) {
+      OwnershipAttr::OwnershipKind OwnKind = I->getOwnKind();
+
+      if (OwnKind != OwnershipAttr::Takes)
+        continue;
+
+      os << ", which takes ownership of " << '\'' << I->getModule()->getName()
+         << '\'';
+      break;
+    }
+  }
+}
+
 static bool printMemFnName(raw_ostream &os, CheckerContext &C, const Expr *E) {
   if (const CallExpr *CE = dyn_cast<CallExpr>(E)) {
     // FIXME: This doesn't handle indirect calls.
@@ -1974,8 +2004,10 @@ static void printExpectedAllocName(raw_ostream &os, AllocationFamily Family) {
   case AF_InnerBuffer:
     os << "container-specific allocator";
     return;
-  case AF_Alloca:
   case AF_Custom:
+    os << Family.CustomName.value();
+    return;
+  case AF_Alloca:
   case AF_None:
     assert(false && "not a deallocation expression");
   }
@@ -1998,8 +2030,11 @@ static void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family) {
   case AF_InnerBuffer:
     os << "container-specific deallocator";
     return;
-  case AF_Alloca:
   case AF_Custom:
+    os << "function that takes ownership of '" << Family.CustomName.value()
+       << "\'";
+    return;
+  case AF_Alloca:
   case AF_None:
     assert(false && "not a deallocation expression");
   }
@@ -2186,6 +2221,7 @@ MallocChecker::getCheckIfTracked(AllocationFamily Family,
   switch (Family.Kind) {
   case AF_Malloc:
   case AF_Alloca:
+  case AF_Custom:
   case AF_IfNameIndex: {
     if (ChecksEnabled[CK_MallocChecker])
       return CK_MallocChecker;
@@ -2208,7 +2244,6 @@ MallocChecker::getCheckIfTracked(AllocationFamily Family,
       return CK_InnerPointerChecker;
     return std::nullopt;
   }
-  case AF_Custom:
   case AF_None: {
     assert(false && "no family");
     return std::nullopt;
@@ -2440,6 +2475,8 @@ void MallocChecker::HandleMismatchedDealloc(CheckerContext &C,
 
         if (printMemFnName(DeallocOs, C, DeallocExpr))
           os << ", not " << DeallocOs.str();
+
+        printOwnershipTakesList(os, C, DeallocExpr);
     }
 
     auto R = std::make_unique<PathSensitiveBugReport>(*BT_MismatchedDealloc,
@@ -3555,6 +3592,7 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N,
       switch (Family.Kind) {
       case AF_Alloca:
       case AF_Malloc:
+      case AF_Custom:
       case AF_CXXNew:
       case AF_CXXNewArray:
       case AF_IfNameIndex:
@@ -3596,7 +3634,6 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N,
         Msg = OS.str();
         break;
         }
-        case AF_Custom:
         case AF_None:
           assert(false && "Unhandled allocation family!");
           return nullptr;

>From 95e3bdbef095cfa4d314d308037993d946122a05 Mon Sep 17 00:00:00 2001
From: Pavel Skripkin <paskripkin at gmail.com>
Date: Wed, 17 Jul 2024 16:52:41 +0300
Subject: [PATCH 5/9] csa/MallocChecker: quote funtion names in error output

---
 .../StaticAnalyzer/Checkers/MallocChecker.cpp | 13 +++--
 .../expected-plists/plist-macros.cpp.plist    |  6 +--
 ...Malloc+MismatchedDeallocator+NewDelete.cpp | 10 ++--
 .../MismatchedDeallocator-checker-test.mm     | 36 +++++++-------
 .../test/Analysis/NewDelete-intersections.mm  |  6 +--
 clang/test/Analysis/free.c                    | 24 +++++-----
 clang/test/Analysis/free.cpp                  | 48 +++++++++----------
 clang/test/Analysis/getline-alloc.c           |  8 ++--
 clang/test/Analysis/kmalloc-linux.c           |  2 +-
 clang/test/Analysis/malloc-fnptr-plist.c      |  2 +-
 clang/test/Analysis/malloc-std-namespace.cpp  |  4 +-
 clang/test/Analysis/malloc.c                  | 36 +++++++-------
 clang/test/Analysis/malloc.mm                 |  2 +-
 clang/test/Analysis/plist-macros.cpp          |  2 +-
 clang/test/Analysis/weak-functions.c          |  2 +-
 15 files changed, 102 insertions(+), 99 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index b5209fc21ffaf..0db0e622d2b0d 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -1954,9 +1954,12 @@ static bool printMemFnName(raw_ostream &os, CheckerContext &C, const Expr *E) {
     if (!FD)
       return false;
 
-    os << *FD;
+    os << '\'' << *FD;
+
     if (!FD->isOverloadedOperator())
       os << "()";
+
+    os << '\'';
     return true;
   }
 
@@ -1990,7 +1993,7 @@ static void printExpectedAllocName(raw_ostream &os, AllocationFamily Family) {
 
   switch (Family.Kind) {
   case AF_Malloc:
-    os << "malloc()";
+    os << "'malloc()'";
     return;
   case AF_CXXNew:
     os << "'new'";
@@ -2016,7 +2019,7 @@ static void printExpectedAllocName(raw_ostream &os, AllocationFamily Family) {
 static void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family) {
   switch (Family.Kind) {
   case AF_Malloc:
-    os << "free()";
+    os << "'free()'";
     return;
   case AF_CXXNew:
     os << "'delete'";
@@ -2418,11 +2421,11 @@ void MallocChecker::HandleFreeAlloca(CheckerContext &C, SVal ArgVal,
   if (ExplodedNode *N = C.generateErrorNode()) {
     if (!BT_FreeAlloca[*CheckKind])
       BT_FreeAlloca[*CheckKind].reset(new BugType(
-          CheckNames[*CheckKind], "Free alloca()", categories::MemoryError));
+          CheckNames[*CheckKind], "Free 'alloca()'", categories::MemoryError));
 
     auto R = std::make_unique<PathSensitiveBugReport>(
         *BT_FreeAlloca[*CheckKind],
-        "Memory allocated by alloca() should not be deallocated", N);
+        "Memory allocated by 'alloca()' should not be deallocated", N);
     R->markInteresting(ArgVal.getAsRegion());
     R->addRange(Range);
     C.emitReport(std::move(R));
diff --git a/clang/test/Analysis/Inputs/expected-plists/plist-macros.cpp.plist b/clang/test/Analysis/Inputs/expected-plists/plist-macros.cpp.plist
index 9981fc6a1f517..13645d47f8cdc 100644
--- a/clang/test/Analysis/Inputs/expected-plists/plist-macros.cpp.plist
+++ b/clang/test/Analysis/Inputs/expected-plists/plist-macros.cpp.plist
@@ -130,12 +130,12 @@
      </array>
      <key>depth</key><integer>0</integer>
      <key>extended_message</key>
-     <string>Memory allocated by malloc() should be deallocated by free(), not 'delete'</string>
+     <string>Memory allocated by 'malloc()' should be deallocated by 'free()', not 'delete'</string>
      <key>message</key>
-     <string>Memory allocated by malloc() should be deallocated by free(), not 'delete'</string>
+     <string>Memory allocated by 'malloc()' should be deallocated by 'free()', not 'delete'</string>
     </dict>
    </array>
-   <key>description</key><string>Memory allocated by malloc() should be deallocated by free(), not 'delete'</string>
+   <key>description</key><string>Memory allocated by 'malloc()' should be deallocated by 'free()', not 'delete'</string>
    <key>category</key><string>Memory error</string>
    <key>type</key><string>Bad deallocator</string>
    <key>check_name</key><string>unix.MismatchedDeallocator</string>
diff --git a/clang/test/Analysis/Malloc+MismatchedDeallocator+NewDelete.cpp b/clang/test/Analysis/Malloc+MismatchedDeallocator+NewDelete.cpp
index b5e47b3355da3..6c20b4ba53dae 100644
--- a/clang/test/Analysis/Malloc+MismatchedDeallocator+NewDelete.cpp
+++ b/clang/test/Analysis/Malloc+MismatchedDeallocator+NewDelete.cpp
@@ -24,12 +24,12 @@ void testMallocUseAfterFree() {
 
 void testMallocBadFree() {
   int i;
-  free(&i); // expected-warning{{Argument to free() is the address of the local variable 'i', which is not memory allocated by malloc()}}
+  free(&i); // expected-warning{{Argument to 'free()' is the address of the local variable 'i', which is not memory allocated by 'malloc()'}}
 }
 
 void testMallocOffsetFree() {
   int *p = (int *)malloc(sizeof(int));
-  free(++p); // expected-warning{{Argument to free() is offset by 4 bytes from the start of memory allocated by malloc()}}
+  free(++p); // expected-warning{{Argument to 'free()' is offset by 4 bytes from the start of memory allocated by 'malloc()'}}
 }
 
 //-----------------------------------------------------------------
@@ -37,7 +37,7 @@ void testMallocOffsetFree() {
 //-----------------------------------------------------------------
 void testMismatchedDeallocator() {
   int *x = (int *)malloc(sizeof(int));
-  delete x; // expected-warning{{Memory allocated by malloc() should be deallocated by free(), not 'delete'}}
+  delete x; // expected-warning{{Memory allocated by 'malloc()' should be deallocated by 'free()', not 'delete'}}
 }
 
 //----------------------------------------------------------------
@@ -69,7 +69,7 @@ void testNewBadFree() {
 
 void testNewOffsetFree() {
   int *p = new int;
-  operator delete(++p); // expected-warning{{Argument to operator delete is offset by 4 bytes from the start of memory allocated by 'new'}}
+  operator delete(++p); // expected-warning{{Argument to 'operator delete' is offset by 4 bytes from the start of memory allocated by 'new'}}
 }
 
 //----------------------------------------------------------------
@@ -88,7 +88,7 @@ void testMismatchedChangePtrThroughCall() {
 void testMismatchedChangePointeeThroughCall() {
   int *p = (int*)malloc(sizeof(int)*4);
   changePointee(p);
-  delete p; // expected-warning{{Memory allocated by malloc() should be deallocated by free(), not 'delete'}}
+  delete p; // expected-warning{{Memory allocated by 'malloc()' should be deallocated by 'free()', not 'delete'}}
 }
 
 void testShouldReportDoubleFreeNotMismatched() {
diff --git a/clang/test/Analysis/MismatchedDeallocator-checker-test.mm b/clang/test/Analysis/MismatchedDeallocator-checker-test.mm
index 013d677e515cf..bbf24837f9da8 100644
--- a/clang/test/Analysis/MismatchedDeallocator-checker-test.mm
+++ b/clang/test/Analysis/MismatchedDeallocator-checker-test.mm
@@ -21,79 +21,79 @@
 //--------------- test malloc family
 void testMalloc1() {
   int *p = (int *)malloc(sizeof(int));
-  delete p; // expected-warning{{Memory allocated by malloc() should be deallocated by free(), not 'delete'}}
+  delete p; // expected-warning{{Memory allocated by 'malloc()' should be deallocated by 'free()', not 'delete'}}
 }
 
 void testMalloc2() {
   int *p = (int *)malloc(8);
   int *q = (int *)realloc(p, 16);
-  delete q; // expected-warning{{Memory allocated by realloc() should be deallocated by free(), not 'delete'}}
+  delete q; // expected-warning{{Memory allocated by 'realloc()' should be deallocated by 'free()', not 'delete'}}
 }
 
 void testMalloc3() {
   int *p = (int *)calloc(1, sizeof(int));
-  delete p; // expected-warning{{Memory allocated by calloc() should be deallocated by free(), not 'delete'}}
+  delete p; // expected-warning{{Memory allocated by 'calloc()' should be deallocated by 'free()', not 'delete'}}
 }
 
 void testMalloc4(const char *s) {
   char *p = strdup(s);
-  delete p; // expected-warning{{Memory allocated by strdup() should be deallocated by free(), not 'delete'}}
+  delete p; // expected-warning{{Memory allocated by 'strdup()' should be deallocated by 'free()', not 'delete'}}
 }
 
 void testMalloc5() {
   int *p = (int *)my_malloc(sizeof(int));
-  delete p; // expected-warning{{Memory allocated by my_malloc() should be deallocated by free(), not 'delete'}}
+  delete p; // expected-warning{{Memory allocated by 'my_malloc()' should be deallocated by 'free()', not 'delete'}}
 }
 
 void testMalloc6() {
   int *p = (int *)malloc(sizeof(int));
-  operator delete(p); // expected-warning{{Memory allocated by malloc() should be deallocated by free(), not operator delete}}
+  operator delete(p); // expected-warning{{Memory allocated by 'malloc()' should be deallocated by 'free()', not 'operator delete'}}
 }
 
 void testMalloc7() {
   int *p = (int *)malloc(sizeof(int));
-  delete[] p; // expected-warning{{Memory allocated by malloc() should be deallocated by free(), not 'delete[]'}}
+  delete[] p; // expected-warning{{Memory allocated by 'malloc()' should be deallocated by 'free()', not 'delete[]'}}
 }
 
 void testMalloc8() {
   int *p = (int *)malloc(sizeof(int));
-  operator delete[](p); // expected-warning{{Memory allocated by malloc() should be deallocated by free(), not operator delete[]}}
+  operator delete[](p); // expected-warning{{Memory allocated by 'malloc()' should be deallocated by 'free()', not 'operator delete[]'}}
 }
 
 void testAlloca() {
   int *p = (int *)__builtin_alloca(sizeof(int));
-  delete p; // expected-warning{{Memory allocated by alloca() should not be deallocated}}
+  delete p; // expected-warning{{Memory allocated by 'alloca()' should not be deallocated}}
 }
 
 //--------------- test new family
 void testNew1() {
   int *p = new int;
-  free(p); // expected-warning{{Memory allocated by 'new' should be deallocated by 'delete', not free()}}
+  free(p); // expected-warning{{Memory allocated by 'new' should be deallocated by 'delete', not 'free()'}}
 }
 
 void testNew2() {
   int *p = (int *)operator new(0);
-  free(p); // expected-warning{{Memory allocated by operator new should be deallocated by 'delete', not free()}}
+  free(p); // expected-warning{{Memory allocated by 'operator new' should be deallocated by 'delete', not 'free()'}}
 }
 
 void testNew3() {
   int *p = new int[1];
-  free(p); // expected-warning{{Memory allocated by 'new[]' should be deallocated by 'delete[]', not free()}}
+  free(p); // expected-warning{{Memory allocated by 'new[]' should be deallocated by 'delete[]', not 'free()'}}
 }
 
 void testNew4() {
   int *p = new int;
-  realloc(p, sizeof(long)); // expected-warning{{Memory allocated by 'new' should be deallocated by 'delete', not realloc()}}
+  realloc(p, sizeof(long)); // expected-warning{{Memory allocated by 'new' should be deallocated by 'delete', not 'realloc()'}}
 }
 
 void testNew5() {
   int *p = (int *)operator new(0);
-  realloc(p, sizeof(long)); // expected-warning{{Memory allocated by operator new should be deallocated by 'delete', not realloc()}}
+  realloc(p, sizeof(long)); // expected-warning{{Memory allocated by 'operator new' should be deallocated by 'delete', not 'realloc()'}}
 }
 
 void testNew6() {
   int *p = new int[1];
-  realloc(p, sizeof(long)); // expected-warning{{Memory allocated by 'new[]' should be deallocated by 'delete[]', not realloc()}}
+  realloc(p, sizeof(long)); // expected-warning{{Memory allocated by 'new[]' should be deallocated by 'delete[]', not 'realloc()'}}
 }
 
 int *allocInt() {
@@ -106,7 +106,7 @@ void testNew7() {
 
 void testNew8() {
   int *p = (int *)operator new(0);
-  delete[] p; // expected-warning{{Memory allocated by operator new should be deallocated by 'delete', not 'delete[]'}}
+  delete[] p; // expected-warning{{Memory allocated by 'operator new' should be deallocated by 'delete', not 'delete[]'}}
 }
 
 int *allocIntArray(unsigned c) {
@@ -120,7 +120,7 @@ void testNew9() {
 
 void testNew10() {
   int *p = (int *)operator new[](0);
-  delete p; // expected-warning{{Memory allocated by operator new[] should be deallocated by 'delete[]', not 'delete'}}
+  delete p; // expected-warning{{Memory allocated by 'operator new[]' should be deallocated by 'delete[]', not 'delete'}}
 }
 
 void testNew11(NSUInteger dataLength) {
@@ -208,7 +208,7 @@ explicit SimpleSmartPointer(T *p = 0) : ptr(p) {}
   ~SimpleSmartPointer() {
     delete ptr;
     // expected-warning at -1 {{Memory allocated by 'new[]' should be deallocated by 'delete[]', not 'delete'}}
-    // expected-warning at -2 {{Memory allocated by malloc() should be deallocated by free(), not 'delete'}}
+    // expected-warning at -2 {{Memory allocated by 'malloc()' should be deallocated by 'free()', not 'delete'}}
   }
 };
 
diff --git a/clang/test/Analysis/NewDelete-intersections.mm b/clang/test/Analysis/NewDelete-intersections.mm
index 6f81034ee349f..9ac471600e8b9 100644
--- a/clang/test/Analysis/NewDelete-intersections.mm
+++ b/clang/test/Analysis/NewDelete-intersections.mm
@@ -44,7 +44,7 @@ void testMallocFreeNoWarn() {
 void testDeleteMalloced() {
   int *p1 = (int *)malloc(sizeof(int));
   delete p1;
-  // mismatch-warning at -1{{Memory allocated by malloc() should be deallocated by free(), not 'delete'}}
+  // mismatch-warning at -1{{Memory allocated by 'malloc()' should be deallocated by 'free()', not 'delete'}}
 
   int *p2 = (int *)__builtin_alloca(sizeof(int));
   delete p2; // no warn
@@ -59,13 +59,13 @@ void testUseZeroAllocatedMalloced() {
 void testFreeOpNew() {
   void *p = operator new(0);
   free(p);
-  // mismatch-warning at -1{{Memory allocated by operator new should be deallocated by 'delete', not free()}}
+  // mismatch-warning at -1{{Memory allocated by 'operator new' should be deallocated by 'delete', not 'free()'}}
 }
 
 void testFreeNewExpr() {
   int *p = new int;
   free(p);
-  // mismatch-warning at -1{{Memory allocated by 'new' should be deallocated by 'delete', not free()}}
+  // mismatch-warning at -1{{Memory allocated by 'new' should be deallocated by 'delete', not 'free()'}}
   free(p);
 }
 
diff --git a/clang/test/Analysis/free.c b/clang/test/Analysis/free.c
index 50c1efdfb1309..5fd956a4ce110 100644
--- a/clang/test/Analysis/free.c
+++ b/clang/test/Analysis/free.c
@@ -13,21 +13,21 @@ void *alloca(size_t);
 void t1 (void) {
   int a[] = { 1 };
   free(a);
-  // expected-warning at -1{{Argument to free() is the address of the local variable 'a', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the local variable 'a', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object 'a'}}
 }
 
 void t2 (void) {
   int a = 1;
   free(&a);
-  // expected-warning at -1{{Argument to free() is the address of the local variable 'a', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the local variable 'a', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object 'a'}}
 }
 
 void t3 (void) {
   static int a[] = { 1 };
   free(a);
-  // expected-warning at -1{{Argument to free() is the address of the static variable 'a', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the static variable 'a', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object 'a'}}
 }
 
@@ -42,7 +42,7 @@ void t5 (void) {
 
 void t6 (void) {
   free((void*)1000);
-  // expected-warning at -1{{Argument to free() is a constant address (1000), which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is a constant address (1000), which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object '(void *)1000'}}
 }
 
@@ -58,42 +58,42 @@ void t8 (char **x) {
 void t9 (void) {
 label:
   free(&&label);
-  // expected-warning at -1{{Argument to free() is the address of the label 'label', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the label 'label', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object 'label'}}
 }
 
 void t10 (void) {
   free((void*)&t10);
-  // expected-warning at -1{{Argument to free() is the address of the function 't10', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the function 't10', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object 't10'}}
 }
 
 void t11 (void) {
   char *p = (char*)alloca(2);
-  free(p); // expected-warning {{Memory allocated by alloca() should not be deallocated}}
+  free(p); // expected-warning {{Memory allocated by 'alloca()' should not be deallocated}}
 }
 
 void t12 (void) {
   char *p = (char*)__builtin_alloca(2);
-  free(p); // expected-warning {{Memory allocated by alloca() should not be deallocated}}
+  free(p); // expected-warning {{Memory allocated by 'alloca()' should not be deallocated}}
 }
 
 void t13 (void) {
   free(^{return;});
-  // expected-warning at -1{{Argument to free() is a block, which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is a block, which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object: block expression}}
 }
 
 void t14 (char a) {
   free(&a);
-  // expected-warning at -1{{Argument to free() is the address of the parameter 'a', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the parameter 'a', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object 'a'}}
 }
 
 static int someGlobal[2];
 void t15 (void) {
   free(someGlobal);
-  // expected-warning at -1{{Argument to free() is the address of the global variable 'someGlobal', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the global variable 'someGlobal', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object 'someGlobal'}}
 }
 
@@ -105,7 +105,7 @@ void t16 (char **x, int offset) {
 int *iptr(void);
 void t17(void) {
   free(iptr); // Oops, forgot to call iptr().
-  // expected-warning at -1{{Argument to free() is the address of the function 'iptr', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the function 'iptr', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object 'iptr'}}
 }
 
diff --git a/clang/test/Analysis/free.cpp b/clang/test/Analysis/free.cpp
index a812a22c47d39..b7f2e49855cf1 100644
--- a/clang/test/Analysis/free.cpp
+++ b/clang/test/Analysis/free.cpp
@@ -17,42 +17,42 @@ extern "C" void *alloca(std::size_t);
 void t1a () {
   int a[] = { 1 };
   free(a);
-  // expected-warning at -1{{Argument to free() is the address of the local variable 'a', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the local variable 'a', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object 'a'}}
 }
 
 void t1b () {
   int a[] = { 1 };
   std::free(a);
-  // expected-warning at -1{{Argument to free() is the address of the local variable 'a', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the local variable 'a', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call std::free on non-heap object 'a'}}
 }
 
 void t2a () {
   int a = 1;
   free(&a);
-  // expected-warning at -1{{Argument to free() is the address of the local variable 'a', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the local variable 'a', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object 'a'}}
 }
 
 void t2b () {
   int a = 1;
   std::free(&a);
-  // expected-warning at -1{{Argument to free() is the address of the local variable 'a', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the local variable 'a', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call std::free on non-heap object 'a'}}
 }
 
 void t3a () {
   static int a[] = { 1 };
   free(a);
-  // expected-warning at -1{{Argument to free() is the address of the static variable 'a', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the static variable 'a', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object 'a'}}
 }
 
 void t3b () {
   static int a[] = { 1 };
   std::free(a);
-  // expected-warning at -1{{Argument to free() is the address of the static variable 'a', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the static variable 'a', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call std::free on non-heap object 'a'}}
 }
 
@@ -76,13 +76,13 @@ void t5b () {
 
 void t6a () {
   free((void*)1000);
-  // expected-warning at -1{{Argument to free() is a constant address (1000), which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is a constant address (1000), which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object '(void *)1000'}}
 }
 
 void t6b () {
   std::free((void*)1000);
-  // expected-warning at -1{{Argument to free() is a constant address (1000), which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is a constant address (1000), which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call std::free on non-heap object '(void *)1000'}}
 }
 
@@ -107,95 +107,95 @@ void t8b (char **x) {
 void t9a () {
 label:
   free(&&label);
-  // expected-warning at -1{{Argument to free() is the address of the label 'label', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the label 'label', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object 'label'}}
 }
 
 void t9b () {
 label:
   std::free(&&label);
-  // expected-warning at -1{{Argument to free() is the address of the label 'label', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the label 'label', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call std::free on non-heap object 'label'}}
 }
 
 void t10a () {
   free((void*)&t10a);
-  // expected-warning at -1{{Argument to free() is the address of the function 't10a', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the function 't10a', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object 't10a'}}
 }
 
 void t10b () {
   std::free((void*)&t10b);
-  // expected-warning at -1{{Argument to free() is the address of the function 't10b', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the function 't10b', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call std::free on non-heap object 't10b'}}
 }
 
 void t11a () {
   char *p = (char*)alloca(2);
-  free(p); // expected-warning {{Memory allocated by alloca() should not be deallocated}}
+  free(p); // expected-warning {{Memory allocated by 'alloca()' should not be deallocated}}
 }
 
 void t11b () {
   char *p = (char*)alloca(2);
-  std::free(p); // expected-warning {{Memory allocated by alloca() should not be deallocated}}
+  std::free(p); // expected-warning {{Memory allocated by 'alloca()' should not be deallocated}}
 }
 
 void t12a () {
   char *p = (char*)__builtin_alloca(2);
-  free(p); // expected-warning {{Memory allocated by alloca() should not be deallocated}}
+  free(p); // expected-warning {{Memory allocated by 'alloca()' should not be deallocated}}
 }
 
 void t12b () {
   char *p = (char*)__builtin_alloca(2);
-  std::free(p); // expected-warning {{Memory allocated by alloca() should not be deallocated}}
+  std::free(p); // expected-warning {{Memory allocated by 'alloca()' should not be deallocated}}
 }
 
 void t13a () {
   free(^{return;});
-  // expected-warning at -1{{Argument to free() is a block, which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is a block, which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object: block expression}}
 }
 
 void t13b () {
   std::free(^{return;});
-  // expected-warning at -1{{Argument to free() is a block, which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is a block, which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call std::free on non-heap object: block expression}}
 }
 
 void t14a () {
   free((void *)+[]{ return; });
-  // expected-warning at -1{{Argument to free() is the address of the function '__invoke', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the function '__invoke', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object: lambda-to-function-pointer conversion}}
 }
 
 void t14b () {
   std::free((void *)+[]{ return; });
-  // expected-warning at -1{{Argument to free() is the address of the function '__invoke', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the function '__invoke', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call std::free on non-heap object: lambda-to-function-pointer conversion}}
 }
 
 void t15a (char a) {
   free(&a);
-  // expected-warning at -1{{Argument to free() is the address of the parameter 'a', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the parameter 'a', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object 'a'}}
 }
 
 void t15b (char a) {
   std::free(&a);
-  // expected-warning at -1{{Argument to free() is the address of the parameter 'a', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the parameter 'a', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call std::free on non-heap object 'a'}}
 }
 
 static int someGlobal[2];
 void t16a () {
   free(someGlobal);
-  // expected-warning at -1{{Argument to free() is the address of the global variable 'someGlobal', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the global variable 'someGlobal', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object 'someGlobal'}}
 }
 
 void t16b () {
   std::free(someGlobal);
-  // expected-warning at -1{{Argument to free() is the address of the global variable 'someGlobal', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the global variable 'someGlobal', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call std::free on non-heap object 'someGlobal'}}
 }
 
diff --git a/clang/test/Analysis/getline-alloc.c b/clang/test/Analysis/getline-alloc.c
index 5b5c716cb605a..74a40a11b9782 100644
--- a/clang/test/Analysis/getline-alloc.c
+++ b/clang/test/Analysis/getline-alloc.c
@@ -40,7 +40,7 @@ void test_getline_alloca() {
     return;
   size_t n = 10;
   char *buffer = alloca(n);
-  getline(&buffer, &n, F1); // expected-warning {{Memory allocated by alloca() should not be deallocated}}
+  getline(&buffer, &n, F1); // expected-warning {{Memory allocated by 'alloca()' should not be deallocated}}
   fclose(F1);
 }
 
@@ -50,7 +50,7 @@ void test_getline_invalid_ptr() {
     return;
   size_t n = 10;
   char *buffer = (char*)test_getline_invalid_ptr;
-  getline(&buffer, &n, F1); // expected-warning {{Argument to getline() is the address of the function 'test_getline_invalid_ptr', which is not memory allocated by malloc()}}
+  getline(&buffer, &n, F1); // expected-warning {{Argument to 'getline()' is the address of the function 'test_getline_invalid_ptr', which is not memory allocated by 'malloc()'}}
   fclose(F1);
 }
 
@@ -79,7 +79,7 @@ void test_getline_stack() {
   if (!F1)
     return;
 
-  getline(&ptr, &n, F1); // expected-warning {{Argument to getline() is the address of the local variable 'buffer', which is not memory allocated by malloc()}}
+  getline(&ptr, &n, F1); // expected-warning {{Argument to 'getline()' is the address of the local variable 'buffer', which is not memory allocated by 'malloc()'}}
 }
 
 void test_getline_static() {
@@ -91,5 +91,5 @@ void test_getline_static() {
   if (!F1)
     return;
 
-  getline(&ptr, &n, F1); // expected-warning {{Argument to getline() is the address of the static variable 'buffer', which is not memory allocated by malloc()}}
+  getline(&ptr, &n, F1); // expected-warning {{Argument to 'getline()' is the address of the static variable 'buffer', which is not memory allocated by 'malloc()'}}
 }
diff --git a/clang/test/Analysis/kmalloc-linux.c b/clang/test/Analysis/kmalloc-linux.c
index af1af24126b6a..0114c4ef000e2 100644
--- a/clang/test/Analysis/kmalloc-linux.c
+++ b/clang/test/Analysis/kmalloc-linux.c
@@ -133,5 +133,5 @@ void test_kfree_ZERO_SIZE_PTR(void) {
 
 void test_kfree_other_constant_value(void) {
   void *ptr = (void *)1;
-  kfree(ptr); // expected-warning{{Argument to kfree() is a constant address (1)}}
+  kfree(ptr); // expected-warning{{Argument to 'kfree()' is a constant address (1)}}
 }
diff --git a/clang/test/Analysis/malloc-fnptr-plist.c b/clang/test/Analysis/malloc-fnptr-plist.c
index e3482980dfbf6..9e82406459234 100644
--- a/clang/test/Analysis/malloc-fnptr-plist.c
+++ b/clang/test/Analysis/malloc-fnptr-plist.c
@@ -5,7 +5,7 @@ void free(void *);
 void (*fnptr)(int);
 void foo(void) {
   free((void *)fnptr);
-  // expected-warning at -1{{Argument to free() is a function pointer}}
+  // expected-warning at -1{{Argument to 'free()' is a function pointer}}
   // expected-warning at -2{{attempt to call free on non-heap object '(void *)fnptr'}}
 }
 
diff --git a/clang/test/Analysis/malloc-std-namespace.cpp b/clang/test/Analysis/malloc-std-namespace.cpp
index d4e397bb812aa..21566ad617920 100644
--- a/clang/test/Analysis/malloc-std-namespace.cpp
+++ b/clang/test/Analysis/malloc-std-namespace.cpp
@@ -18,7 +18,7 @@ void no_leak() {
 void invalid_free() {
   int i;
   int *p = &i;
-  //expected-note at +2{{Argument to free() is the address of the local variable 'i', which is not memory allocated by malloc()}}
-  //expected-warning at +1{{Argument to free() is the address of the local variable 'i', which is not memory allocated by malloc()}}
+  //expected-note at +2{{Argument to 'free()' is the address of the local variable 'i', which is not memory allocated by 'malloc()'}}
+  //expected-warning at +1{{Argument to 'free()' is the address of the local variable 'i', which is not memory allocated by 'malloc()'}}
   std::free(p);
 }
diff --git a/clang/test/Analysis/malloc.c b/clang/test/Analysis/malloc.c
index 8ee29aeb324f7..9c7ca43bfbc5a 100644
--- a/clang/test/Analysis/malloc.c
+++ b/clang/test/Analysis/malloc.c
@@ -732,7 +732,7 @@ void mallocCastToFP(void) {
   free(p);
 }
 
-// This tests that malloc() buffers are undefined by default
+// This tests that 'malloc()' buffers are undefined by default
 char mallocGarbage (void) {
 	char *buf = malloc(2);
 	char result = buf[1]; // expected-warning{{undefined}}
@@ -778,17 +778,17 @@ void paramFree(int *p) {
 
 void allocaFree(void) {
   int *p = alloca(sizeof(int));
-  free(p); // expected-warning {{Memory allocated by alloca() should not be deallocated}}
+  free(p); // expected-warning {{Memory allocated by 'alloca()' should not be deallocated}}
 }
 
 void allocaFreeBuiltin(void) {
   int *p = __builtin_alloca(sizeof(int));
-  free(p); // expected-warning {{Memory allocated by alloca() should not be deallocated}}
+  free(p); // expected-warning {{Memory allocated by 'alloca()' should not be deallocated}}
 }
 
 void allocaFreeBuiltinAlign(void) {
   int *p = __builtin_alloca_with_align(sizeof(int), 64);
-  free(p); // expected-warning {{Memory allocated by alloca() should not be deallocated}}
+  free(p); // expected-warning {{Memory allocated by 'alloca()' should not be deallocated}}
 }
 
 
@@ -1327,7 +1327,7 @@ void radar10978247_positive(int myValueSize) {
   else
     return; // expected-warning {{leak}}
 }
-// Previously this triggered a false positive because malloc() is known to
+// Previously this triggered a false positive because 'malloc()' is known to
 // return uninitialized memory and the binding of 'o' to 'p->n' was not getting
 // propertly handled. Now we report a leak.
 struct rdar11269741_a_t {
@@ -1656,26 +1656,26 @@ void testOffsetDeallocate(int *memoryBlock) {
 void testOffsetOfRegionFreed(void) {
   __int64_t * array = malloc(sizeof(__int64_t)*2);
   array += 1;
-  free(&array[0]); // expected-warning{{Argument to free() is offset by 8 bytes from the start of memory allocated by malloc()}}
+  free(&array[0]); // expected-warning{{Argument to 'free()' is offset by 8 bytes from the start of memory allocated by 'malloc()'}}
 }
 
 void testOffsetOfRegionFreed2(void) {
   __int64_t *p = malloc(sizeof(__int64_t)*2);
   p += 1;
-  free(p); // expected-warning{{Argument to free() is offset by 8 bytes from the start of memory allocated by malloc()}}
+  free(p); // expected-warning{{Argument to 'free()' is offset by 8 bytes from the start of memory allocated by 'malloc()'}}
 }
 
 void testOffsetOfRegionFreed3(void) {
   char *r = malloc(sizeof(char));
   r = r - 10;
-  free(r); // expected-warning {{Argument to free() is offset by -10 bytes from the start of memory allocated by malloc()}}
+  free(r); // expected-warning {{Argument to 'free()' is offset by -10 bytes from the start of memory allocated by 'malloc()'}}
 }
 
 void testOffsetOfRegionFreedAfterFunctionCall(void) {
   int *p = malloc(sizeof(int)*2);
   p += 1;
   myfoo(p);
-  free(p); // expected-warning{{Argument to free() is offset by 4 bytes from the start of memory allocated by malloc()}}
+  free(p); // expected-warning{{Argument to 'free()' is offset by 4 bytes from the start of memory allocated by 'malloc()'}}
 }
 
 void testFixManipulatedPointerBeforeFree(void) {
@@ -1695,7 +1695,7 @@ void freeOffsetPointerPassedToFunction(void) {
   p[1] = 0;
   p += 1;
   myfooint(*p); // not passing the pointer, only a value pointed by pointer
-  free(p); // expected-warning {{Argument to free() is offset by 8 bytes from the start of memory allocated by malloc()}}
+  free(p); // expected-warning {{Argument to 'free()' is offset by 8 bytes from the start of memory allocated by 'malloc()'}}
 }
 
 int arbitraryInt(void);
@@ -1709,13 +1709,13 @@ void testFreeNonMallocPointerWithNoOffset(void) {
   char c;
   char *r = &c;
   r = r + 10;
-  free(r-10); // expected-warning {{Argument to free() is the address of the local variable 'c', which is not memory allocated by malloc()}}
+  free(r-10); // expected-warning {{Argument to 'free()' is the address of the local variable 'c', which is not memory allocated by 'malloc()'}}
 }
 
 void testFreeNonMallocPointerWithOffset(void) {
   char c;
   char *r = &c;
-  free(r+1); // expected-warning {{Argument to free() is the address of the local variable 'c', which is not memory allocated by malloc()}}
+  free(r+1); // expected-warning {{Argument to 'free()' is the address of the local variable 'c', which is not memory allocated by 'malloc()'}}
 }
 
 void testOffsetZeroDoubleFree(void) {
@@ -1735,14 +1735,14 @@ void testOffsetPassedToStrlenThenFree(void) {
   char * string = malloc(sizeof(char)*10);
   string += 1;
   int length = strlen(string);
-  free(string); // expected-warning {{Argument to free() is offset by 1 byte from the start of memory allocated by malloc()}}
+  free(string); // expected-warning {{Argument to 'free()' is offset by 1 byte from the start of memory allocated by 'malloc()'}}
 }
 
 void testOffsetPassedAsConst(void) {
   char * string = malloc(sizeof(char)*10);
   string += 1;
   passConstPtr(string);
-  free(string); // expected-warning {{Argument to free() is offset by 1 byte from the start of memory allocated by malloc()}}
+  free(string); // expected-warning {{Argument to 'free()' is offset by 1 byte from the start of memory allocated by 'malloc()'}}
 }
 
 char **_vectorSegments;
@@ -1842,12 +1842,12 @@ int testNoCheckerDataPropogationFromLogicalOpOperandToOpResult(void) {
 void (*fnptr)(int);
 void freeIndirectFunctionPtr(void) {
   void *p = (void *)fnptr;
-  free(p); // expected-warning {{Argument to free() is a function pointer}}
+  free(p); // expected-warning {{Argument to 'free()' is a function pointer}}
 }
 
 void freeFunctionPtr(void) {
   free((void *)fnptr);
-  // expected-warning at -1{{Argument to free() is a function pointer}}
+  // expected-warning at -1{{Argument to 'free()' is a function pointer}}
   // expected-warning at -2{{attempt to call free on non-heap object '(void *)fnptr'}}
 }
 
@@ -1905,8 +1905,8 @@ enum { BUFSIZE = 256 };
 void MEM34_C(void) {
   char buf[BUFSIZE];
   char *p = (char *)realloc(buf, 2 * BUFSIZE);
-  // expected-warning at -1{{Argument to realloc() is the address of the local \
-variable 'buf', which is not memory allocated by malloc() [unix.Malloc]}}
+  // expected-warning at -1{{Argument to 'realloc()' is the address of the local \
+variable 'buf', which is not memory allocated by 'malloc()' [unix.Malloc]}}
   if (p == NULL) {
     /* Handle error */
   }
diff --git a/clang/test/Analysis/malloc.mm b/clang/test/Analysis/malloc.mm
index 94a46d731090b..5b816a1524aec 100644
--- a/clang/test/Analysis/malloc.mm
+++ b/clang/test/Analysis/malloc.mm
@@ -89,7 +89,7 @@ void testNSStringFreeWhenDoneNO2(NSUInteger dataLength) {
 
 void testOffsetFree() {
   int *p = (int *)malloc(sizeof(int));
-  NSData *nsdata = [NSData dataWithBytesNoCopy:++p length:sizeof(int) freeWhenDone:1]; // expected-warning{{Argument to +dataWithBytesNoCopy:length:freeWhenDone: is offset by 4 bytes from the start of memory allocated by malloc()}}
+  NSData *nsdata = [NSData dataWithBytesNoCopy:++p length:sizeof(int) freeWhenDone:1]; // expected-warning{{Argument to +dataWithBytesNoCopy:length:freeWhenDone: is offset by 4 bytes from the start of memory allocated by 'malloc()'}}
 }
 
 void testRelinquished1() {
diff --git a/clang/test/Analysis/plist-macros.cpp b/clang/test/Analysis/plist-macros.cpp
index 94f8e514c8b39..d7b3c7cc1c86f 100644
--- a/clang/test/Analysis/plist-macros.cpp
+++ b/clang/test/Analysis/plist-macros.cpp
@@ -13,7 +13,7 @@ void noteOnMacro(int y) {
   mallocmemory
   y++; 
   y++;
-  delete x; // expected-warning {{Memory allocated by malloc() should be deallocated by free(), not 'delete'}}
+  delete x; // expected-warning {{Memory allocated by 'malloc()' should be deallocated by 'free()', not 'delete'}}
 }
 
 void macroIsFirstInFunction(int y) {
diff --git a/clang/test/Analysis/weak-functions.c b/clang/test/Analysis/weak-functions.c
index 26cbfb3523a92..5bdb411fcbf4f 100644
--- a/clang/test/Analysis/weak-functions.c
+++ b/clang/test/Analysis/weak-functions.c
@@ -72,7 +72,7 @@ void free(void *) __attribute__((weak_import));
 
 void t10 (void) {
   free((void*)&t10);
-  // expected-warning at -1{{Argument to free() is the address of the function 't10', which is not memory allocated by malloc()}}
+  // expected-warning at -1{{Argument to 'free()' is the address of the function 't10', which is not memory allocated by 'malloc()'}}
   // expected-warning at -2{{attempt to call free on non-heap object 't10'}}
 }
 

>From 277a732edac8d82a75de3afec181d3da67066edb Mon Sep 17 00:00:00 2001
From: Pavel Skripkin <paskripkin at gmail.com>
Date: Mon, 15 Jul 2024 00:57:21 +0300
Subject: [PATCH 6/9] csa/tests: add test suite for custom allocation classes

---
 .../MismatchedDeallocator-checker-test.mm     | 48 +++++++++++++++++++
 1 file changed, 48 insertions(+)

diff --git a/clang/test/Analysis/MismatchedDeallocator-checker-test.mm b/clang/test/Analysis/MismatchedDeallocator-checker-test.mm
index bbf24837f9da8..ef8b24ba8de32 100644
--- a/clang/test/Analysis/MismatchedDeallocator-checker-test.mm
+++ b/clang/test/Analysis/MismatchedDeallocator-checker-test.mm
@@ -14,6 +14,19 @@
 void free(void *);
 void __attribute((ownership_takes(malloc, 1))) my_free(void *);
 
+void __attribute((ownership_returns(malloc1))) *my_malloc1(size_t);
+void __attribute((ownership_takes(malloc1, 1))) my_free1(void *);
+
+void __attribute((ownership_returns(malloc2))) *my_malloc2(size_t);
+
+// The order of these declarations are important to verify that analisys still works even
+// if there are less specific declarations of the same functions
+void __attribute((ownership_returns(malloc3))) *my_malloc3(size_t);
+void *my_malloc3(size_t);
+
+void *my_malloc4(size_t);
+void __attribute((ownership_returns(malloc4))) *my_malloc4(size_t);
+
 //---------------------------------------------------------------
 // Test if an allocation function matches deallocation function
 //---------------------------------------------------------------
@@ -60,6 +73,41 @@ void testMalloc8() {
   operator delete[](p); // expected-warning{{Memory allocated by 'malloc()' should be deallocated by 'free()', not 'operator delete[]'}}
 }
 
+void testMalloc9() {
+  int *p = (int *)my_malloc(sizeof(int));
+  my_free(p); // no warning
+}
+
+void testMalloc10() {
+  int *p = (int *)my_malloc1(sizeof(int));
+  my_free1(p); // no warning
+}
+
+void testMalloc11() {
+  int *p = (int *)my_malloc1(sizeof(int));
+  my_free(p); // expected-warning{{Memory allocated by 'my_malloc1()' should be deallocated by function that takes ownership of 'malloc1', not 'my_free()', which takes ownership of 'malloc'}}
+}
+
+void testMalloc12() {
+  int *p = (int *)my_malloc2(sizeof(int));
+  my_free1(p); // expected-warning{{Memory allocated by 'my_malloc2()' should be deallocated by function that takes ownership of 'malloc2', not 'my_free1()', which takes ownership of 'malloc1'}}
+}
+
+void testMalloc13() {
+  int *p = (int *)my_malloc1(sizeof(int));
+  free(p); // expected-warning{{Memory allocated by 'my_malloc1()' should be deallocated by function that takes ownership of 'malloc1', not 'free()'}}
+}
+
+void testMalloc14() {
+  int *p = (int *)my_malloc3(sizeof(int));
+  free(p); // expected-warning{{Memory allocated by 'my_malloc3()' should be deallocated by function that takes ownership of 'malloc3', not 'free()'}}
+}
+
+void testMalloc15() {
+  int *p = (int *)my_malloc4(sizeof(int));
+  free(p); // expected-warning{{Memory allocated by 'my_malloc4()' should be deallocated by function that takes ownership of 'malloc4', not 'free()'}}
+}
+
 void testAlloca() {
   int *p = (int *)__builtin_alloca(sizeof(int));
   delete p; // expected-warning{{Memory allocated by 'alloca()' should not be deallocated}}

>From 9a8756693ee26172d5eedcc2040c18cafb046419 Mon Sep 17 00:00:00 2001
From: Pavel Skripkin <paskripkin at gmail.com>
Date: Thu, 18 Jul 2024 10:23:57 +0300
Subject: [PATCH 7/9] csa/MallocChecker: fix style and unexpected shadowing

---
 .../StaticAnalyzer/Checkers/MallocChecker.cpp | 33 +++++++++----------
 1 file changed, 15 insertions(+), 18 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index 0db0e622d2b0d..95ec28bfd20da 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -118,10 +118,10 @@ struct AllocationFamily {
   AllocationFamilyKind Kind;
   std::optional<StringRef> CustomName;
 
-  explicit AllocationFamily(AllocationFamilyKind Kind,
+  explicit AllocationFamily(AllocationFamilyKind AKind,
                             std::optional<StringRef> Name = std::nullopt)
-      : Kind(Kind), CustomName(Name) {
-    assert((Kind != AF_Custom || Name.has_value()) &&
+      : Kind(AKind), CustomName(Name) {
+    assert((Kind != AF_Custom || CustomName.has_value()) &&
            "Custom family must specify also the name");
 
     // Preseve previous behavior when "malloc" class means AF_Malloc
@@ -1925,25 +1925,22 @@ static bool didPreviousFreeFail(ProgramStateRef State,
 
 static void printOwnershipTakesList(raw_ostream &os, CheckerContext &C,
                                     const Expr *E) {
-  if (const CallExpr *CE = dyn_cast<CallExpr>(E)) {
-    const FunctionDecl *FD = CE->getDirectCallee();
-    if (!FD)
-      return;
+  const CallExpr *CE = dyn_cast<CallExpr>(E);
 
-    if (!FD->hasAttrs())
-      return;
+  if (!CE)
+    return;
 
-    // Only one ownership_takes attribute is allowed
-    for (const auto *I : FD->specific_attrs<OwnershipAttr>()) {
-      OwnershipAttr::OwnershipKind OwnKind = I->getOwnKind();
+  const FunctionDecl *FD = CE->getDirectCallee();
+  if (!FD)
+    return;
 
-      if (OwnKind != OwnershipAttr::Takes)
-        continue;
+  // Only one ownership_takes attribute is allowed.
+  for (const auto *I : FD->specific_attrs<OwnershipAttr>()) {
+    if (I->getOwnKind() != OwnershipAttr::Takes)
+      continue;
 
-      os << ", which takes ownership of " << '\'' << I->getModule()->getName()
-         << '\'';
-      break;
-    }
+    os << ", which takes ownership of '" << I->getModule()->getName() << '\'';
+    break;
   }
 }
 

>From 2e55fa2456e66d30fe9ce4e89a24099db5c80fac Mon Sep 17 00:00:00 2001
From: Pavel Skripkin <paskripkin at gmail.com>
Date: Thu, 18 Jul 2024 17:19:36 +0300
Subject: [PATCH 8/9] add release note

---
 clang/docs/ReleaseNotes.rst | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index e0e86af257a19..b8faae8f82406 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -535,6 +535,9 @@ Removed Compiler Flags
 
 Attribute Changes in Clang
 --------------------------
+- Clang now disallows more than one ``__attribute__((ownership_returns(class, idx)))`` with
+  different class names attached to one function.
+
 - Introduced a new function attribute ``__attribute__((amdgpu_max_num_work_groups(x, y, z)))`` or
   ``[[clang::amdgpu_max_num_work_groups(x, y, z)]]`` for the AMDGPU target. This attribute can be
   attached to HIP or OpenCL kernel function definitions to provide an optimization hint. The parameters
@@ -1284,6 +1287,11 @@ Static Analyzer
 New features
 ^^^^^^^^^^^^
 
+- MallocChecker now checks for ``ownership_returns(class, idx)`` and ``ownership_takes(class, idx)``
+  attributes with class names different from "malloc". Clang static analyzer now reports an error
+  if class of allocation and deallocation function mismatches.
+  `Documentation <https://clang.llvm.org/docs/analyzer/checkers.html#unix-mismatcheddeallocator-c-c>`__.
+
 - The attribute ``[[clang::suppress]]`` can now be applied to declarations.
   (#GH80371)
 

>From 180e8913d8d7abd8ab8ced90f010cc67d0626fc4 Mon Sep 17 00:00:00 2001
From: Pavel Skripkin <paskripkin at gmail.com>
Date: Thu, 18 Jul 2024 18:52:38 +0300
Subject: [PATCH 9/9] add example for mismatched deallocation class in
 documentation

---
 .../checkers/mismatched_deallocator_example.cpp        | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/clang/docs/analyzer/checkers/mismatched_deallocator_example.cpp b/clang/docs/analyzer/checkers/mismatched_deallocator_example.cpp
index 2a4103240fe81..3b7c7d99faef3 100644
--- a/clang/docs/analyzer/checkers/mismatched_deallocator_example.cpp
+++ b/clang/docs/analyzer/checkers/mismatched_deallocator_example.cpp
@@ -6,6 +6,10 @@ void test() {
 
 // C, C++
 void __attribute((ownership_returns(malloc))) *user_malloc(size_t);
+void __attribute((ownership_takes(malloc, 1))) *user_free(void *);
+
+void __attribute((ownership_returns(malloc1))) *user_malloc1(size_t);
+void __attribute((ownership_takes(malloc1, 1))) *user_free1(void *);
 
 void test() {
   int *p = (int *)user_malloc(sizeof(int));
@@ -24,6 +28,12 @@ void test() {
   realloc(p, sizeof(long)); // warn
 }
 
+// C, C++
+void test() {
+  int *p = user_malloc(10);
+  user_free1(p); // warn
+}
+
 // C, C++
 template <typename T>
 struct SimpleSmartPointer {



More information about the cfe-commits mailing list