[clang] ff9b296 - [analyzer] MallocChecker – Fix false positive leak for smart pointers in temporary objects (#152751)

via cfe-commits cfe-commits at lists.llvm.org
Fri Sep 5 01:57:05 PDT 2025


Author: Ivan Murashko
Date: 2025-09-05T09:57:02+01:00
New Revision: ff9b296b86ebf0603fa3577726a4a46416f80b0a

URL: https://github.com/llvm/llvm-project/commit/ff9b296b86ebf0603fa3577726a4a46416f80b0a
DIFF: https://github.com/llvm/llvm-project/commit/ff9b296b86ebf0603fa3577726a4a46416f80b0a.diff

LOG: [analyzer] MallocChecker – Fix false positive leak for smart pointers in temporary objects (#152751)

Fixes PR60896 - false positive leak reports in various smart pointer scenarios including temporaries, inheritance, direct constructor calls, and mixed ownership patterns. Previously, the analyzer had no smart pointer handling in
`checkPostCall`, causing it to report false positive leaks for memory properly managed by smart pointers while missing legitimate raw pointer leaks.

---------

Co-authored-by: Balazs Benics <benicsbalazs at gmail.com>
Co-authored-by: Donát Nagy <donat.nagy at ericsson.com>

Added: 
    clang/test/Analysis/NewDeleteLeaks-PR60896.cpp

Modified: 
    clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index efb980962e811..83d79b43afe9f 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -78,6 +78,7 @@
 #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
 #include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/Compiler.h"
@@ -421,6 +422,13 @@ class MallocChecker
   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
   void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
   bool evalCall(const CallEvent &Call, CheckerContext &C) const;
+
+  ProgramStateRef
+  handleSmartPointerConstructorArguments(const CallEvent &Call,
+                                         ProgramStateRef State) const;
+  ProgramStateRef handleSmartPointerRelatedCalls(const CallEvent &Call,
+                                                 CheckerContext &C,
+                                                 ProgramStateRef State) const;
   void checkNewAllocator(const CXXAllocatorCall &Call, CheckerContext &C) const;
   void checkPostObjCMessage(const ObjCMethodCall &Call, CheckerContext &C) const;
   void checkPostStmt(const BlockExpr *BE, CheckerContext &C) const;
@@ -1096,6 +1104,54 @@ class StopTrackingCallback final : public SymbolVisitor {
     return true;
   }
 };
+
+/// EscapeTrackedCallback - A SymbolVisitor that marks allocated symbols as
+/// escaped.
+///
+/// This visitor is used to suppress false positive leak reports when smart
+/// pointers are nested in temporary objects passed by value to functions. When
+/// the analyzer can't see the destructor calls for temporary objects, it may
+/// incorrectly report leaks for memory that will be properly freed by the smart
+/// pointer destructors.
+///
+/// The visitor traverses reachable symbols from a given set of memory regions
+/// (typically smart pointer field regions) and marks any allocated symbols as
+/// escaped. Escaped symbols are not reported as leaks by checkDeadSymbols.
+class EscapeTrackedCallback final : public SymbolVisitor {
+  ProgramStateRef State;
+
+  explicit EscapeTrackedCallback(ProgramStateRef S) : State(std::move(S)) {}
+
+public:
+  bool VisitSymbol(SymbolRef Sym) override {
+    if (const RefState *RS = State->get<RegionState>(Sym)) {
+      if (RS->isAllocated() || RS->isAllocatedOfSizeZero()) {
+        State = State->set<RegionState>(Sym, RefState::getEscaped(RS));
+      }
+    }
+    return true;
+  }
+
+  /// Escape tracked regions reachable from the given roots.
+  static ProgramStateRef
+  EscapeTrackedRegionsReachableFrom(ArrayRef<const MemRegion *> Roots,
+                                    ProgramStateRef State) {
+    if (Roots.empty())
+      return State;
+
+    // scanReachableSymbols is expensive, so we use a single visitor for all
+    // roots
+    SmallVector<const MemRegion *, 10> Regions;
+    EscapeTrackedCallback Visitor(State);
+    for (const MemRegion *R : Roots) {
+      Regions.push_back(R);
+    }
+    State->scanReachableSymbols(Regions, Visitor);
+    return Visitor.State;
+  }
+
+  friend class SymbolVisitor;
+};
 } // end anonymous namespace
 
 static bool isStandardNew(const FunctionDecl *FD) {
@@ -3068,12 +3124,260 @@ void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper,
   C.addTransition(state->set<RegionState>(RS), N);
 }
 
+// Allowlist of owning smart pointers we want to recognize.
+// Start with unique_ptr and shared_ptr; weak_ptr is excluded intentionally
+// because it does not own the pointee.
+static bool isSmartPtrName(StringRef Name) {
+  return Name == "unique_ptr" || Name == "shared_ptr";
+}
+
+// Check if a type is a smart owning pointer type.
+static bool isSmartPtrType(QualType QT) {
+  QT = QT->getCanonicalTypeUnqualified();
+
+  if (const auto *TST = QT->getAs<TemplateSpecializationType>()) {
+    const TemplateDecl *TD = TST->getTemplateName().getAsTemplateDecl();
+    if (!TD)
+      return false;
+
+    const auto *ND = dyn_cast_or_null<NamedDecl>(TD->getTemplatedDecl());
+    if (!ND)
+      return false;
+
+    // For broader coverage we recognize all template classes with names that
+    // match the allowlist even if they are not declared in namespace 'std'.
+    return isSmartPtrName(ND->getName());
+  }
+
+  return false;
+}
+
+/// Helper struct for collecting smart owning pointer field regions.
+/// This allows both hasSmartPtrField and
+/// collectSmartPtrFieldRegions to share the same traversal logic,
+/// ensuring consistency.
+struct FieldConsumer {
+  const MemRegion *Reg;
+  CheckerContext *C;
+  llvm::SmallPtrSetImpl<const MemRegion *> *Out;
+
+  FieldConsumer(const MemRegion *Reg, CheckerContext &C,
+                llvm::SmallPtrSetImpl<const MemRegion *> &Out)
+      : Reg(Reg), C(&C), Out(&Out) {}
+
+  void consume(const FieldDecl *FD) {
+    SVal L = C->getState()->getLValue(FD, loc::MemRegionVal(Reg));
+    if (const MemRegion *FR = L.getAsRegion())
+      Out->insert(FR);
+  }
+
+  std::optional<FieldConsumer> switchToBase(const CXXRecordDecl *BaseDecl,
+                                            bool IsVirtual) {
+    // Get the base class region
+    SVal BaseL =
+        C->getState()->getLValue(BaseDecl, Reg->getAs<SubRegion>(), IsVirtual);
+    if (const MemRegion *BaseObjRegion = BaseL.getAsRegion()) {
+      // Return a consumer for the base class
+      return FieldConsumer{BaseObjRegion, *C, *Out};
+    }
+    return std::nullopt;
+  }
+};
+
+/// Check if a record type has smart owning pointer fields (directly or in base
+/// classes). When FC is provided, also collect the field regions.
+///
+/// This function has dual behavior:
+/// - When FC is nullopt: Returns true if smart pointer fields are found
+/// - When FC is provided: Always returns false, but collects field regions
+///   as a side effect through the FieldConsumer
+///
+/// Note: When FC is provided, the return value should be ignored since the
+/// function performs full traversal for collection and always returns false
+/// to avoid early termination.
+static bool hasSmartPtrField(const CXXRecordDecl *CRD,
+                             std::optional<FieldConsumer> FC = std::nullopt) {
+  // Check direct fields
+  for (const FieldDecl *FD : CRD->fields()) {
+    if (isSmartPtrType(FD->getType())) {
+      if (!FC)
+        return true;
+      FC->consume(FD);
+    }
+  }
+
+  // Check fields from base classes
+  for (const CXXBaseSpecifier &BaseSpec : CRD->bases()) {
+    if (const CXXRecordDecl *BaseDecl =
+            BaseSpec.getType()->getAsCXXRecordDecl()) {
+      std::optional<FieldConsumer> NewFC;
+      if (FC) {
+        NewFC = FC->switchToBase(BaseDecl, BaseSpec.isVirtual());
+        if (!NewFC)
+          continue;
+      }
+      bool Found = hasSmartPtrField(BaseDecl, NewFC);
+      if (Found && !FC)
+        return true;
+    }
+  }
+  return false;
+}
+
+/// Check if an expression is an rvalue record type passed by value.
+static bool isRvalueByValueRecord(const Expr *AE) {
+  if (AE->isGLValue())
+    return false;
+
+  QualType T = AE->getType();
+  if (!T->isRecordType() || T->isReferenceType())
+    return false;
+
+  // Accept common temp/construct forms but don't overfit.
+  return isa<CXXTemporaryObjectExpr, MaterializeTemporaryExpr, CXXConstructExpr,
+             InitListExpr, ImplicitCastExpr, CXXBindTemporaryExpr>(AE);
+}
+
+/// Check if an expression is an rvalue record with smart owning pointer fields
+/// passed by value.
+static bool isRvalueByValueRecordWithSmartPtr(const Expr *AE) {
+  if (!isRvalueByValueRecord(AE))
+    return false;
+
+  const auto *CRD = AE->getType()->getAsCXXRecordDecl();
+  return CRD && hasSmartPtrField(CRD);
+}
+
+/// Check if a CXXRecordDecl has a name matching recognized smart pointer names.
+static bool isSmartPtrRecord(const CXXRecordDecl *RD) {
+  if (!RD)
+    return false;
+
+  // Check the record name directly and accept both std and custom smart pointer
+  // implementations for broader coverage
+  return isSmartPtrName(RD->getName());
+}
+
+/// Check if a call is a constructor of a smart owning pointer class that
+/// accepts pointer parameters.
+static bool isSmartPtrCall(const CallEvent &Call) {
+  // Only check for smart pointer constructor calls
+  const auto *CD = dyn_cast_or_null<CXXConstructorDecl>(Call.getDecl());
+  if (!CD)
+    return false;
+
+  const auto *RD = CD->getParent();
+  if (!isSmartPtrRecord(RD))
+    return false;
+
+  // Check if constructor takes a pointer parameter
+  for (const auto *Param : CD->parameters()) {
+    QualType ParamType = Param->getType();
+    if (ParamType->isPointerType() && !ParamType->isFunctionPointerType() &&
+        !ParamType->isVoidPointerType()) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+/// Collect memory regions of smart owning pointer fields from a record type
+/// (including fields from base classes).
+static void
+collectSmartPtrFieldRegions(const MemRegion *Reg, QualType RecQT,
+                            CheckerContext &C,
+                            llvm::SmallPtrSetImpl<const MemRegion *> &Out) {
+  if (!Reg)
+    return;
+
+  const auto *CRD = RecQT->getAsCXXRecordDecl();
+  if (!CRD)
+    return;
+
+  FieldConsumer FC{Reg, C, Out};
+  hasSmartPtrField(CRD, FC);
+}
+
+/// Handle smart pointer constructor calls by escaping allocated symbols
+/// that are passed as pointer arguments to the constructor.
+ProgramStateRef MallocChecker::handleSmartPointerConstructorArguments(
+    const CallEvent &Call, ProgramStateRef State) const {
+  const auto *CD = cast<CXXConstructorDecl>(Call.getDecl());
+  for (unsigned I = 0, E = std::min(Call.getNumArgs(), CD->getNumParams());
+       I != E; ++I) {
+    const Expr *ArgExpr = Call.getArgExpr(I);
+    if (!ArgExpr)
+      continue;
+
+    QualType ParamType = CD->getParamDecl(I)->getType();
+    if (ParamType->isPointerType() && !ParamType->isFunctionPointerType() &&
+        !ParamType->isVoidPointerType()) {
+      // This argument is a pointer being passed to smart pointer constructor
+      SVal ArgVal = Call.getArgSVal(I);
+      SymbolRef Sym = ArgVal.getAsSymbol();
+      if (Sym && State->contains<RegionState>(Sym)) {
+        const RefState *RS = State->get<RegionState>(Sym);
+        if (RS && (RS->isAllocated() || RS->isAllocatedOfSizeZero())) {
+          State = State->set<RegionState>(Sym, RefState::getEscaped(RS));
+        }
+      }
+    }
+  }
+  return State;
+}
+
+/// Handle all smart pointer related processing in function calls.
+/// This includes both direct smart pointer constructor calls and by-value
+/// arguments containing smart pointer fields.
+ProgramStateRef MallocChecker::handleSmartPointerRelatedCalls(
+    const CallEvent &Call, CheckerContext &C, ProgramStateRef State) const {
+
+  // Handle direct smart pointer constructor calls first
+  if (isSmartPtrCall(Call)) {
+    return handleSmartPointerConstructorArguments(Call, State);
+  }
+
+  // Handle smart pointer fields in by-value record arguments
+  llvm::SmallPtrSet<const MemRegion *, 8> SmartPtrFieldRoots;
+  for (unsigned I = 0, E = Call.getNumArgs(); I != E; ++I) {
+    const Expr *AE = Call.getArgExpr(I);
+    if (!AE)
+      continue;
+    AE = AE->IgnoreParenImpCasts();
+
+    if (!isRvalueByValueRecordWithSmartPtr(AE))
+      continue;
+
+    // Find a region for the argument.
+    SVal ArgVal = Call.getArgSVal(I);
+    const MemRegion *ArgRegion = ArgVal.getAsRegion();
+    // Collect direct smart owning pointer field regions
+    collectSmartPtrFieldRegions(ArgRegion, AE->getType(), C,
+                                SmartPtrFieldRoots);
+  }
+
+  // Escape symbols reachable from smart pointer fields
+  if (!SmartPtrFieldRoots.empty()) {
+    SmallVector<const MemRegion *, 8> SmartPtrFieldRootsVec(
+        SmartPtrFieldRoots.begin(), SmartPtrFieldRoots.end());
+    State = EscapeTrackedCallback::EscapeTrackedRegionsReachableFrom(
+        SmartPtrFieldRootsVec, State);
+  }
+
+  return State;
+}
+
 void MallocChecker::checkPostCall(const CallEvent &Call,
                                   CheckerContext &C) const {
+  // Handle existing post-call handlers first
   if (const auto *PostFN = PostFnMap.lookup(Call)) {
     (*PostFN)(this, C.getState(), Call, C);
-    return;
+    return; // Post-handler already called addTransition, we're done
   }
+
+  // Handle smart pointer related processing only if no post-handler was called
+  C.addTransition(handleSmartPointerRelatedCalls(Call, C, C.getState()));
 }
 
 void MallocChecker::checkPreCall(const CallEvent &Call,
@@ -3194,7 +3498,6 @@ void MallocChecker::checkEscapeOnReturn(const ReturnStmt *S,
   if (!Sym)
     // If we are returning a field of the allocated struct or an array element,
     // the callee could still free the memory.
-    // TODO: This logic should be a part of generic symbol escape callback.
     if (const MemRegion *MR = RetVal.getAsRegion())
       if (isa<FieldRegion, ElementRegion>(MR))
         if (const SymbolicRegion *BMR =

diff  --git a/clang/test/Analysis/NewDeleteLeaks-PR60896.cpp b/clang/test/Analysis/NewDeleteLeaks-PR60896.cpp
new file mode 100644
index 0000000000000..00dbbf2ec3c7e
--- /dev/null
+++ b/clang/test/Analysis/NewDeleteLeaks-PR60896.cpp
@@ -0,0 +1,252 @@
+// RUN: %clang_analyze_cc1 -verify -analyzer-output=text %s \
+// RUN:   -analyzer-checker=core \
+// RUN:   -analyzer-checker=cplusplus \
+// RUN:   -analyzer-checker=unix
+
+#include "Inputs/system-header-simulator-for-malloc.h"
+
+//===----------------------------------------------------------------------===//
+// unique_ptr test cases 
+//===----------------------------------------------------------------------===//
+namespace unique_ptr_tests {
+
+// Custom unique_ptr implementation for testing
+template <typename T>
+struct unique_ptr {
+  T* ptr;
+  unique_ptr(T* p) : ptr(p) {}
+  ~unique_ptr() {
+    // This destructor intentionally doesn't delete 'ptr' to validate that the
+    // heuristic trusts that smart pointers (based on their class name) will
+    // release the pointee even if it doesn't understand their destructor.
+  }
+  unique_ptr(unique_ptr&& other) : ptr(other.ptr) { other.ptr = nullptr; }
+  T* get() const { return ptr; }
+};
+
+template <typename T, typename... Args>
+unique_ptr<T> make_unique(Args&&... args) {
+  return unique_ptr<T>(new T(args...));
+}
+
+// Test 1: Check that we report leaks for malloc when passing smart pointers
+void add_unique_ptr(unique_ptr<int> ptr) {
+  // The unique_ptr destructor will be called when ptr goes out of scope
+}
+
+void test_malloc_with_smart_ptr() {
+  void *ptr = malloc(4); // expected-note {{Memory is allocated}}
+
+  add_unique_ptr(make_unique<int>(1));
+  (void)ptr;
+  // expected-warning at +1 {{Potential leak of memory pointed to by 'ptr'}} expected-note at +1 {{Potential leak of memory pointed to by 'ptr'}}
+}
+
+// Test 2: Check that we don't report leaks for unique_ptr in temporary objects
+struct Foo { 
+  unique_ptr<int> i;
+};
+
+void add_foo(Foo foo) {
+  // The unique_ptr destructor will be called when foo goes out of scope
+}
+
+void test_temporary_object() {
+  // No warning should be emitted for this - the memory is managed by unique_ptr 
+  // in the temporary Foo object, which will properly clean up the memory
+  add_foo({make_unique<int>(1)});
+}
+
+// Test 3: Check that we don't report leaks for smart pointers in base class fields
+struct Base {
+  unique_ptr<int> base_ptr;
+  Base() : base_ptr(nullptr) {}
+  Base(unique_ptr<int>&& ptr) : base_ptr(static_cast<unique_ptr<int>&&>(ptr)) {}
+};
+
+struct Derived : public Base {
+  int derived_field;
+  Derived() : Base(), derived_field(0) {}
+  Derived(unique_ptr<int>&& ptr, int field) : Base(static_cast<unique_ptr<int>&&>(ptr)), derived_field(field) {}
+};
+
+void add_derived(Derived derived) {
+  // The unique_ptr destructor will be called when derived goes out of scope
+  // This should include the base_ptr field from the base class
+}
+
+void test_base_class_smart_ptr() {
+  // No warning should be emitted for this - the memory is managed by unique_ptr 
+  // in the base class field of the temporary Derived object
+  add_derived(Derived(make_unique<int>(1), 42));
+}
+
+// Test 4: Check that we don't report leaks for multiple owning arguments
+struct SinglePtr {
+  unique_ptr<int> ptr;
+  SinglePtr(unique_ptr<int>&& p) : ptr(static_cast<unique_ptr<int>&&>(p)) {}
+};
+
+struct MultiPtr {
+  unique_ptr<int> ptr1;
+  unique_ptr<int> ptr2;
+  unique_ptr<int> ptr3;
+  
+  MultiPtr(unique_ptr<int>&& p1, unique_ptr<int>&& p2, unique_ptr<int>&& p3)
+    : ptr1(static_cast<unique_ptr<int>&&>(p1))
+    , ptr2(static_cast<unique_ptr<int>&&>(p2))
+    , ptr3(static_cast<unique_ptr<int>&&>(p3)) {}
+};
+
+void addMultiple(SinglePtr single, MultiPtr multi) {
+  // All unique_ptr destructors will be called when the objects go out of scope
+  // This tests handling of multiple by-value arguments with smart pointer fields
+}
+
+void test_multiple_owning_args() {
+  // No warning should be emitted - all memory is properly managed by unique_ptr
+  // in the temporary objects, which will properly clean up the memory
+  addMultiple(
+    SinglePtr(make_unique<int>(1)),
+    MultiPtr(make_unique<int>(2), make_unique<int>(3), make_unique<int>(4))
+  );
+}
+
+// Test 5: Check that we DO report leaks for raw pointers in mixed ownership scenarios
+struct MixedOwnership {
+  unique_ptr<int> smart_ptr;  // Should NOT leak (smart pointer managed)
+  int *raw_ptr;               // Should leak (raw pointer)
+
+  MixedOwnership() : smart_ptr(make_unique<int>(1)), raw_ptr(new int(42)) {} // expected-note {{Memory is allocated}}
+};
+
+void consume(MixedOwnership obj) {
+  // The unique_ptr destructor will be called when obj goes out of scope
+  // But raw_ptr will leak!
+}
+
+void test_mixed_ownership() {
+  // This should report a leak for raw_ptr but not for smart_ptr
+  consume(MixedOwnership()); // expected-note {{Calling default constructor for 'MixedOwnership'}} expected-note {{Returning from default constructor for 'MixedOwnership'}}
+} // expected-warning {{Potential memory leak}} expected-note {{Potential memory leak}}
+
+// Test 6: Check that we handle direct smart pointer constructor calls correctly
+void test_direct_constructor() {
+  // Direct constructor call - should not leak
+  int* raw_ptr = new int(42);
+  unique_ptr<int> smart(raw_ptr); // This should escape the raw_ptr symbol
+  // No leak should be reported here since smart pointer takes ownership
+}
+
+void test_mixed_direct_constructor() {
+  int* raw1 = new int(1); 
+  int* raw2 = new int(2); // expected-note {{Memory is allocated}}
+  
+  unique_ptr<int> smart(raw1); // This should escape raw1
+  // raw2 should leak since it's not managed by any smart pointer
+  int x = *raw2; // expected-warning {{Potential leak of memory pointed to by 'raw2'}} expected-note {{Potential leak of memory pointed to by 'raw2'}}
+}
+
+// Test 7: Multiple memory owning arguments - demonstrates addTransition API usage
+void addMultipleOwningArgs(
+  unique_ptr<int> ptr1,
+  unique_ptr<int> ptr2, 
+  unique_ptr<int> ptr3
+) {
+  // All unique_ptr destructors will be called when arguments go out of scope
+  // This tests handling of multiple smart pointer parameters in a single call
+}
+
+void test_multiple_memory_owning_arguments() {
+  // No warning should be emitted - all memory is properly managed by unique_ptr
+  // This test specifically exercises the addTransition API with multiple owning arguments
+  addMultipleOwningArgs(
+    make_unique<int>(1),
+    make_unique<int>(2), 
+    make_unique<int>(3)
+  );
+}
+
+} // namespace unique_ptr_tests
+
+//===----------------------------------------------------------------------===//
+// Variadic constructor test cases
+//===----------------------------------------------------------------------===//
+namespace variadic_constructor_tests {
+
+// Variadic constructor - test for potential out-of-bounds access
+// This is the only test in this namespace and tests a scenario where Call.getNumArgs() > CD->getNumParams()
+// We use a synthetic unique_ptr here to activate the specific logic in the MallocChecker that will test out of bounds
+template <typename T>
+struct unique_ptr {
+  T* ptr;
+
+  // Constructor with ellipsis - can receive more arguments than parameters  
+  unique_ptr(T* p, ...) : ptr(p) {}
+
+  ~unique_ptr() {
+    // This destructor intentionally doesn't delete 'ptr' to validate that the
+    // heuristic trusts that smart pointers (based on their class name) will
+    // release the pointee even if it doesn't understand their destructor.
+  }
+};
+
+void process_variadic_smart_ptr(unique_ptr<int> ptr) {
+  // Function body doesn't matter for this test
+}
+
+void test_variadic_constructor_bounds() {
+  void *malloc_ptr = malloc(4); // expected-note {{Memory is allocated}}
+  
+  // This call creates a smart pointer with more arguments than formal parameters
+  // The constructor has 1 formal parameter (T* p) plus ellipsis, but we pass multiple args
+  // This should trigger the bounds checking issue in handleSmartPointerConstructorArguments
+  int* raw_ptr = new int(42);
+  process_variadic_smart_ptr(unique_ptr<int>(raw_ptr, 1, 2, 3, 4, 5));
+  
+  (void)malloc_ptr;
+} // expected-warning {{Potential leak of memory pointed to by 'malloc_ptr'}}
+  // expected-note at -1 {{Potential leak of memory pointed to by 'malloc_ptr'}}
+
+} // namespace variadic_constructor_tests
+
+//===----------------------------------------------------------------------===//
+// shared_ptr test cases
+//===----------------------------------------------------------------------===//
+namespace shared_ptr_tests {
+
+// Custom shared_ptr implementation for testing
+template <typename T>
+struct shared_ptr {
+  T* ptr;
+  shared_ptr(T* p) : ptr(p) {}
+  ~shared_ptr() {
+    // This destructor intentionally doesn't delete 'ptr' to validate that the
+    // heuristic trusts that smart pointers (based on their class name) will
+    // release the pointee even if it doesn't understand their destructor.
+  }
+  shared_ptr(shared_ptr&& other) : ptr(other.ptr) { other.ptr = nullptr; }
+  T* get() const { return ptr; }
+};
+
+template <typename T, typename... Args>
+shared_ptr<T> make_shared(Args&&... args) {
+  return shared_ptr<T>(new T(args...));
+}
+
+// Test 1: Check that we don't report leaks for shared_ptr in temporary objects
+struct Foo { 
+  shared_ptr<int> i;
+};
+
+void add_foo(Foo foo) {
+  // The shared_ptr destructor will be called when foo goes out of scope
+}
+
+void test_temporary_object() {
+  // No warning should be emitted for this - the memory is managed by shared_ptr 
+  // in the temporary Foo object, which will properly clean up the memory
+  add_foo({make_shared<int>(1)});
+}
+
+} // namespace shared_ptr_tests


        


More information about the cfe-commits mailing list