diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 369d6194dbb65..104be8bcbdcca 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" @@ -1096,6 +1097,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)) {} + + bool VisitSymbol(SymbolRef Sym) override { + if (const RefState *RS = State->get(Sym)) { + if (RS->isAllocated() || RS->isAllocatedOfSizeZero()) { + State = State->set(Sym, RefState::getEscaped(RS)); + } + } + return true; + } + +public: + /// Escape tracked regions reachable from the given roots. + static ProgramStateRef + EscapeTrackedRegionsReachableFrom(ArrayRef Roots, + ProgramStateRef State) { + if (Roots.empty()) + return State; + + // scanReachableSymbols is expensive, so we use a single visitor for all + // roots + SmallVector 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 +3117,167 @@ void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper, C.addTransition(state->set(RS), N); } +// Allowlist of owning smart pointers we want to recognize. +// Start with unique_ptr and shared_ptr. (intentionally exclude weak_ptr) +static bool isSmartOwningPtrType(QualType QT) { + QT = QT->getCanonicalTypeUnqualified(); + + auto isSmartPtrName = [](StringRef Name) { + return Name == "unique_ptr" || Name == "shared_ptr"; + }; + + // First try TemplateSpecializationType (for std smart pointers) + if (const auto *TST = QT->getAs()) { + const TemplateDecl *TD = TST->getTemplateName().getAsTemplateDecl(); + if (!TD) + return false; + + const auto *ND = dyn_cast_or_null(TD->getTemplatedDecl()); + if (!ND) + return false; + + // Check if it's in std namespace + if (!isWithinStdNamespace(ND)) + return false; + + return isSmartPtrName(ND->getName()); + } + + // Also try RecordType (for custom smart pointer implementations) + if (const auto *RD = QT->getAsCXXRecordDecl()) { + // Accept any custom unique_ptr or shared_ptr implementation + return (isSmartPtrName(RD->getName())); + } + + return false; +} + +static bool hasSmartPtrField(const CXXRecordDecl *CRD) { + // Check direct fields + if (llvm::any_of(CRD->fields(), [](const FieldDecl *FD) { + return isSmartOwningPtrType(FD->getType()); + })) + return true; + + // Check fields from base classes + for (const CXXBaseSpecifier &Base : CRD->bases()) { + if (const CXXRecordDecl *BaseDecl = Base.getType()->getAsCXXRecordDecl()) { + if (hasSmartPtrField(BaseDecl)) + return true; + } + } + return false; +} + +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(AE); +} + +static bool isRvalueByValueRecordWithSmartPtr(const Expr *AE) { + if (!isRvalueByValueRecord(AE)) + return false; + + const auto *CRD = AE->getType()->getAsCXXRecordDecl(); + return CRD && hasSmartPtrField(CRD); +} + +static ProgramStateRef escapeAllAllocatedSymbols(ProgramStateRef State) { + RegionStateTy RS = State->get(); + ProgramStateRef NewState = State; + for (auto [Sym, RefSt] : RS) { + if (RefSt.isAllocated() || RefSt.isAllocatedOfSizeZero()) { + NewState = NewState->set(Sym, RefState::getEscaped(&RefSt)); + } + } + return NewState; +} + +static void collectDirectSmartOwningPtrFieldRegions( + const MemRegion *Base, QualType RecQT, CheckerContext &C, + SmallVectorImpl &Out) { + if (!Base) + return; + const auto *CRD = RecQT->getAsCXXRecordDecl(); + if (!CRD) + return; + + // Collect direct fields + for (const FieldDecl *FD : CRD->fields()) { + if (!isSmartOwningPtrType(FD->getType())) + continue; + SVal L = C.getState()->getLValue(FD, loc::MemRegionVal(Base)); + if (const MemRegion *FR = L.getAsRegion()) + Out.push_back(FR); + } + + // Collect fields from base classes + for (const CXXBaseSpecifier &BaseSpec : CRD->bases()) { + if (const CXXRecordDecl *BaseDecl = + BaseSpec.getType()->getAsCXXRecordDecl()) { + // Get the base class region + SVal BaseL = C.getState()->getLValue(BaseDecl, Base->getAs(), + BaseSpec.isVirtual()); + if (const MemRegion *BaseRegion = BaseL.getAsRegion()) { + // Recursively collect fields from this base class + collectDirectSmartOwningPtrFieldRegions(BaseRegion, BaseSpec.getType(), + C, Out); + } + } + } +} + void MallocChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { + // Keep existing post-call handlers. if (const auto *PostFN = PostFnMap.lookup(Call)) { (*PostFN)(this, C.getState(), Call, C); - return; } + + SmallVector SmartPtrFieldRoots; + ProgramStateRef State = C.getState(); + + 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(); + if (!ArgRegion) { + // Fallback: if we have a by-value record with smart pointer fields but no + // region, mark all allocated symbols as escaped + State = escapeAllAllocatedSymbols(State); + continue; + } + + // Push direct smart owning pointer field regions only (precise root set). + collectDirectSmartOwningPtrFieldRegions(ArgRegion, AE->getType(), C, + SmartPtrFieldRoots); + } + + // Escape only from those field roots + if (!SmartPtrFieldRoots.empty()) { + State = EscapeTrackedCallback::EscapeTrackedRegionsReachableFrom( + SmartPtrFieldRoots, State); + } + + // Apply state changes - addTransition will check if State differs + // from current state + C.addTransition(State); } void MallocChecker::checkPreCall(const CallEvent &Call, @@ -3194,7 +3398,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(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..67f1e06d9d45d --- /dev/null +++ b/clang/test/Analysis/NewDeleteLeaks-PR60896.cpp @@ -0,0 +1,215 @@ +// RUN: %clang_analyze_cc1 -verify -analyzer-output=text %s \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-checker=cplusplus \ +// RUN: -analyzer-checker=unix \ +// RUN: -analyzer-checker=unix.Malloc + +#include "Inputs/system-header-simulator-for-malloc.h" + +//===----------------------------------------------------------------------===// +// Check that we report leaks for malloc when passing smart pointers +//===----------------------------------------------------------------------===// +namespace malloc_with_smart_ptr { + +// Custom unique_ptr implementation for testing +template +struct unique_ptr { + T* ptr; + unique_ptr(T* p) : ptr(p) {} + ~unique_ptr() { delete ptr; } + unique_ptr(unique_ptr&& other) : ptr(other.ptr) { other.ptr = nullptr; } + T* get() const { return ptr; } +}; + +template +unique_ptr make_unique(Args&&... args) { + return unique_ptr(new T(args...)); +} + +void add(unique_ptr ptr) { + // The unique_ptr destructor will be called when ptr goes out of scope +} + +int bar(void) { + void *ptr = malloc(4); // expected-note {{Memory is allocated}} + + add(make_unique(1)); + (void)ptr; + return 0; // expected-warning {{Potential leak of memory pointed to by 'ptr'}} expected-note {{Potential leak of memory pointed to by 'ptr'}} +} + +} // namespace malloc_with_smart_ptr + +//===----------------------------------------------------------------------===// +// Check that we don't report leaks for unique_ptr in temporary objects +//===----------------------------------------------------------------------===// +namespace unique_ptr_temporary_PR60896 { + +// Custom unique_ptr implementation for testing +template +struct unique_ptr { + T* ptr; + unique_ptr(T* p) : ptr(p) {} + ~unique_ptr() { delete ptr; } + unique_ptr(unique_ptr&& other) : ptr(other.ptr) { other.ptr = nullptr; } + T* get() const { return ptr; } +}; + +template +unique_ptr make_unique(Args&&... args) { + return unique_ptr(new T(args...)); +} + +// The test case that demonstrates the issue +struct Foo { + unique_ptr i; +}; + +void add(Foo foo) { + // The unique_ptr destructor will be called when foo goes out of scope +} + +void test() { + // 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({make_unique(1)}); +} + +} // namespace unique_ptr_temporary_PR60896 + +//===----------------------------------------------------------------------===// +// Check that we don't report leaks for shared_ptr in temporary objects +//===----------------------------------------------------------------------===// +namespace shared_ptr_temporary_PR60896 { + +// Custom shared_ptr implementation for testing +template +struct shared_ptr { + T* ptr; + shared_ptr(T* p) : ptr(p) {} + ~shared_ptr() { delete ptr; } + shared_ptr(shared_ptr&& other) : ptr(other.ptr) { other.ptr = nullptr; } + T* get() const { return ptr; } +}; + +template +shared_ptr make_shared(Args&&... args) { + return shared_ptr(new T(args...)); +} + +struct Foo { + shared_ptr i; +}; + +void add(Foo foo) { + // The shared_ptr destructor will be called when foo goes out of scope +} + +void test() { + // 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({make_shared(1)}); +} + +} // namespace shared_ptr_temporary_PR60896 + +//===----------------------------------------------------------------------===// +// Check that we don't report leaks for smart pointers in base class fields +//===----------------------------------------------------------------------===// +namespace base_class_smart_ptr_PR60896 { + +// Custom unique_ptr implementation for testing +template +struct unique_ptr { + T* ptr; + unique_ptr(T* p) : ptr(p) {} + ~unique_ptr() { delete ptr; } + unique_ptr(unique_ptr&& other) : ptr(other.ptr) { other.ptr = nullptr; } + T* get() const { return ptr; } +}; + +template +unique_ptr make_unique(Args&&... args) { + return unique_ptr(new T(args...)); +} + +// Base class with smart pointer field +struct Base { + unique_ptr base_ptr; + Base() : base_ptr(nullptr) {} + Base(unique_ptr&& ptr) : base_ptr(static_cast&&>(ptr)) {} +}; + +// Derived class that inherits the smart pointer field +struct Derived : public Base { + int derived_field; + Derived() : Base(), derived_field(0) {} + Derived(unique_ptr&& ptr, int field) : Base(static_cast&&>(ptr)), derived_field(field) {} +}; + +void add(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() { + // 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(make_unique(1), 42)); +} + +} // namespace base_class_smart_ptr_PR60896 + +//===----------------------------------------------------------------------===// +// Check that we don't report leaks for multiple owning arguments +//===----------------------------------------------------------------------===// +namespace multiple_owning_args_PR60896 { + +// Custom unique_ptr implementation for testing +template +struct unique_ptr { + T* ptr; + unique_ptr(T* p) : ptr(p) {} + ~unique_ptr() { delete ptr; } + unique_ptr(unique_ptr&& other) : ptr(other.ptr) { other.ptr = nullptr; } + T* get() const { return ptr; } +}; + +template +unique_ptr make_unique(Args&&... args) { + return unique_ptr(new T(args...)); +} + +// Struct with single smart pointer field +struct SinglePtr { + unique_ptr ptr; + SinglePtr(unique_ptr&& p) : ptr(static_cast&&>(p)) {} +}; + +// Struct with multiple smart pointer fields +struct MultiPtr { + unique_ptr ptr1; + unique_ptr ptr2; + unique_ptr ptr3; + + MultiPtr(unique_ptr&& p1, unique_ptr&& p2, unique_ptr&& p3) + : ptr1(static_cast&&>(p1)) + , ptr2(static_cast&&>(p2)) + , ptr3(static_cast&&>(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() { + // 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(1)), + MultiPtr(make_unique(2), make_unique(3), make_unique(4)) + ); +} + +} // namespace multiple_owning_args_PR60896