Skip to content

Commit 96a0370

Browse files
committed
[analyzer][test] Refactor smart pointer leak suppression and combine tests
- Applied requested refactoring in MallocChecker (API cleanup, code reuse, duplication reduction). - Merged unique_ptr and shared_ptr PR60896 tests into a single file.
1 parent 0cc838f commit 96a0370

File tree

3 files changed

+104
-138
lines changed

3 files changed

+104
-138
lines changed

clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp

Lines changed: 67 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,6 @@
5252
#include "clang/AST/DeclTemplate.h"
5353
#include "clang/AST/Expr.h"
5454
#include "clang/AST/ExprCXX.h"
55-
#include "clang/AST/TemplateBase.h"
56-
#include "clang/AST/Type.h"
5755

5856
#include "clang/AST/ParentMap.h"
5957
#include "clang/ASTMatchers/ASTMatchFinder.h"
@@ -1113,17 +1111,23 @@ class StopTrackingCallback final : public SymbolVisitor {
11131111
/// The visitor traverses reachable symbols from a given set of memory regions
11141112
/// (typically smart pointer field regions) and marks any allocated symbols as
11151113
/// escaped. Escaped symbols are not reported as leaks by checkDeadSymbols.
1116-
///
1117-
/// Usage:
1118-
/// auto Scan =
1119-
/// State->scanReachableSymbols<EscapeTrackedCallback>(RootRegions);
1120-
/// ProgramStateRef NewState = Scan.getState();
1121-
/// if (NewState != State) C.addTransition(NewState);
11221114
class EscapeTrackedCallback final : public SymbolVisitor {
11231115
ProgramStateRef State;
11241116

1125-
public:
11261117
explicit EscapeTrackedCallback(ProgramStateRef S) : State(std::move(S)) {}
1118+
1119+
public:
1120+
/// Escape tracked regions reachable from the given roots.
1121+
static ProgramStateRef
1122+
EscapeTrackedRegionsReachableFrom(ArrayRef<const MemRegion *> Roots,
1123+
ProgramStateRef State) {
1124+
EscapeTrackedCallback Visitor(State);
1125+
for (const MemRegion *R : Roots) {
1126+
State->scanReachableSymbols(loc::MemRegionVal(R), Visitor);
1127+
}
1128+
return Visitor.getState();
1129+
}
1130+
11271131
ProgramStateRef getState() const { return State; }
11281132

11291133
bool VisitSymbol(SymbolRef Sym) override {
@@ -3108,18 +3112,11 @@ void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper,
31083112
}
31093113

31103114
static QualType canonicalStrip(QualType QT) {
3111-
return QT.getCanonicalType().getUnqualifiedType();
3115+
return QT->getCanonicalTypeUnqualified();
31123116
}
31133117

3114-
static bool isInStdNamespace(const DeclContext *DC) {
3115-
while (DC) {
3116-
if (const auto *NS = dyn_cast<NamespaceDecl>(DC))
3117-
if (NS->isStdNamespace())
3118-
return true;
3119-
DC = DC->getParent();
3120-
}
3121-
return false;
3122-
}
3118+
// Use isWithinStdNamespace from CheckerHelpers.h instead of custom
3119+
// implementation
31233120

31243121
// Allowlist of owning smart pointers we want to recognize.
31253122
// Start with unique_ptr and shared_ptr. (intentionally exclude weak_ptr)
@@ -3138,8 +3135,7 @@ static bool isSmartOwningPtrType(QualType QT) {
31383135
return false;
31393136

31403137
// Check if it's in std namespace
3141-
const DeclContext *DC = ND->getDeclContext();
3142-
if (!isInStdNamespace(DC))
3138+
if (!isWithinStdNamespace(ND))
31433139
return false;
31443140

31453141
StringRef Name = ND->getName();
@@ -3162,6 +3158,44 @@ static bool isSmartOwningPtrType(QualType QT) {
31623158
return false;
31633159
}
31643160

3161+
static bool hasSmartPtrField(const CXXRecordDecl *CRD) {
3162+
return llvm::any_of(CRD->fields(), [](const FieldDecl *FD) {
3163+
return isSmartOwningPtrType(FD->getType());
3164+
});
3165+
}
3166+
3167+
static bool isRvalueByValueRecord(const Expr *AE) {
3168+
if (AE->isGLValue())
3169+
return false;
3170+
3171+
QualType T = AE->getType();
3172+
if (!T->isRecordType() || T->isReferenceType())
3173+
return false;
3174+
3175+
// Accept common temp/construct forms but don't overfit.
3176+
return isa<CXXTemporaryObjectExpr, MaterializeTemporaryExpr, CXXConstructExpr,
3177+
InitListExpr, ImplicitCastExpr, CXXBindTemporaryExpr>(AE);
3178+
}
3179+
3180+
static bool isRvalueByValueRecordWithSmartPtr(const Expr *AE) {
3181+
if (!isRvalueByValueRecord(AE))
3182+
return false;
3183+
3184+
const auto *CRD = AE->getType()->getAsCXXRecordDecl();
3185+
return CRD && hasSmartPtrField(CRD);
3186+
}
3187+
3188+
static ProgramStateRef escapeAllAllocatedSymbols(ProgramStateRef State) {
3189+
RegionStateTy RS = State->get<RegionState>();
3190+
ProgramStateRef NewState = State;
3191+
for (auto [Sym, RefSt] : RS) {
3192+
if (RefSt.isAllocated() || RefSt.isAllocatedOfSizeZero()) {
3193+
NewState = NewState->set<RegionState>(Sym, RefState::getEscaped(&RefSt));
3194+
}
3195+
}
3196+
return NewState;
3197+
}
3198+
31653199
static void collectDirectSmartOwningPtrFieldRegions(
31663200
const MemRegion *Base, QualType RecQT, CheckerContext &C,
31673201
SmallVectorImpl<const MemRegion *> &Out) {
@@ -3195,38 +3229,7 @@ void MallocChecker::checkPostCall(const CallEvent &Call,
31953229
continue;
31963230
AE = AE->IgnoreParenImpCasts();
31973231

3198-
QualType T = AE->getType();
3199-
3200-
// **Relaxation 1**: accept *any rvalue* by-value record (not only strict
3201-
// PRVALUE).
3202-
if (AE->isGLValue())
3203-
continue;
3204-
3205-
// By-value record only (no refs).
3206-
if (!T->isRecordType() || T->isReferenceType())
3207-
continue;
3208-
3209-
// **Relaxation 2**: accept common temp/construct forms but don't overfit.
3210-
const bool LooksLikeTemp =
3211-
isa<CXXTemporaryObjectExpr>(AE) || isa<MaterializeTemporaryExpr>(AE) ||
3212-
isa<CXXConstructExpr>(AE) || isa<InitListExpr>(AE) ||
3213-
isa<ImplicitCastExpr>(AE) || // handle common rvalue materializations
3214-
isa<CXXBindTemporaryExpr>(AE); // handle CXXBindTemporaryExpr
3215-
if (!LooksLikeTemp)
3216-
continue;
3217-
3218-
// Require at least one direct smart owning pointer field by type.
3219-
const auto *CRD = T->getAsCXXRecordDecl();
3220-
if (!CRD)
3221-
continue;
3222-
bool HasSmartPtrField = false;
3223-
for (const FieldDecl *FD : CRD->fields()) {
3224-
if (isSmartOwningPtrType(FD->getType())) {
3225-
HasSmartPtrField = true;
3226-
break;
3227-
}
3228-
}
3229-
if (!HasSmartPtrField)
3232+
if (!isRvalueByValueRecordWithSmartPtr(AE))
32303233
continue;
32313234

32323235
// Find a region for the argument.
@@ -3237,32 +3240,26 @@ void MallocChecker::checkPostCall(const CallEvent &Call,
32373240

32383241
const MemRegion *Base = RCall ? RCall : RExpr;
32393242
if (!Base) {
3240-
// Fallback: if we have a by-value record with unique_ptr fields but no
3243+
// Fallback: if we have a by-value record with smart pointer fields but no
32413244
// region, mark all allocated symbols as escaped
32423245
ProgramStateRef State = C.getState();
3243-
RegionStateTy RS = State->get<RegionState>();
3244-
ProgramStateRef NewState = State;
3245-
for (auto [Sym, RefSt] : RS) {
3246-
if (RefSt.isAllocated() || RefSt.isAllocatedOfSizeZero()) {
3247-
NewState =
3248-
NewState->set<RegionState>(Sym, RefState::getEscaped(&RefSt));
3249-
}
3250-
}
3246+
ProgramStateRef NewState = escapeAllAllocatedSymbols(State);
32513247
if (NewState != State)
32523248
C.addTransition(NewState);
32533249
continue;
32543250
}
32553251

32563252
// Push direct smart owning pointer field regions only (precise root set).
3257-
collectDirectSmartOwningPtrFieldRegions(Base, T, C, SmartPtrFieldRoots);
3253+
collectDirectSmartOwningPtrFieldRegions(Base, AE->getType(), C,
3254+
SmartPtrFieldRoots);
32583255
}
32593256

32603257
// Escape only from those field roots; do nothing if empty.
32613258
if (!SmartPtrFieldRoots.empty()) {
32623259
ProgramStateRef State = C.getState();
3263-
auto Scan =
3264-
State->scanReachableSymbols<EscapeTrackedCallback>(SmartPtrFieldRoots);
3265-
ProgramStateRef NewState = Scan.getState();
3260+
ProgramStateRef NewState =
3261+
EscapeTrackedCallback::EscapeTrackedRegionsReachableFrom(
3262+
SmartPtrFieldRoots, State);
32663263
if (NewState != State) {
32673264
C.addTransition(NewState);
32683265
} else {
@@ -3276,44 +3273,15 @@ void MallocChecker::checkPostCall(const CallEvent &Call,
32763273
continue;
32773274
AE = AE->IgnoreParenImpCasts();
32783275

3279-
if (AE->isGLValue())
3280-
continue;
3281-
QualType T = AE->getType();
3282-
if (!T->isRecordType() || T->isReferenceType())
3283-
continue;
3284-
3285-
const bool LooksLikeTemp =
3286-
isa<CXXTemporaryObjectExpr>(AE) ||
3287-
isa<MaterializeTemporaryExpr>(AE) || isa<CXXConstructExpr>(AE) ||
3288-
isa<InitListExpr>(AE) || isa<ImplicitCastExpr>(AE) ||
3289-
isa<CXXBindTemporaryExpr>(AE);
3290-
if (!LooksLikeTemp)
3291-
continue;
3292-
3293-
// Check if this record type has smart pointer fields
3294-
const auto *CRD = T->getAsCXXRecordDecl();
3295-
if (CRD) {
3296-
for (const FieldDecl *FD : CRD->fields()) {
3297-
if (isSmartOwningPtrType(FD->getType())) {
3298-
hasByValueRecordWithSmartPtr = true;
3299-
break;
3300-
}
3301-
}
3302-
}
3303-
if (hasByValueRecordWithSmartPtr)
3276+
if (isRvalueByValueRecordWithSmartPtr(AE)) {
3277+
hasByValueRecordWithSmartPtr = true;
33043278
break;
3279+
}
33053280
}
33063281

33073282
if (hasByValueRecordWithSmartPtr) {
33083283
ProgramStateRef State = C.getState();
3309-
RegionStateTy RS = State->get<RegionState>();
3310-
ProgramStateRef NewState = State;
3311-
for (auto [Sym, RefSt] : RS) {
3312-
if (RefSt.isAllocated() || RefSt.isAllocatedOfSizeZero()) {
3313-
NewState =
3314-
NewState->set<RegionState>(Sym, RefState::getEscaped(&RefSt));
3315-
}
3316-
}
3284+
ProgramStateRef NewState = escapeAllAllocatedSymbols(State);
33173285
if (NewState != State)
33183286
C.addTransition(NewState);
33193287
}
@@ -3439,7 +3407,6 @@ void MallocChecker::checkEscapeOnReturn(const ReturnStmt *S,
34393407
if (!Sym)
34403408
// If we are returning a field of the allocated struct or an array element,
34413409
// the callee could still free the memory.
3442-
// TODO: This logic should be a part of generic symbol escape callback.
34433410
if (const MemRegion *MR = RetVal.getAsRegion())
34443411
if (isa<FieldRegion, ElementRegion>(MR))
34453412
if (const SymbolicRegion *BMR =

clang/test/Analysis/NewDeleteLeaks-PR60896-shared.cpp

Lines changed: 0 additions & 37 deletions
This file was deleted.

clang/test/Analysis/NewDeleteLeaks-PR60896.cpp

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212
namespace unique_ptr_temporary_PR60896 {
1313

14-
// We use a custom implementation of unique_ptr for testing purposes
14+
// Custom unique_ptr implementation for testing
1515
template <typename T>
1616
struct unique_ptr {
1717
T* ptr;
@@ -42,3 +42,39 @@ void test() {
4242
}
4343

4444
} // namespace unique_ptr_temporary_PR60896
45+
46+
//===----------------------------------------------------------------------===//
47+
// Check that we don't report leaks for shared_ptr in temporary objects
48+
//===----------------------------------------------------------------------===//
49+
namespace shared_ptr_temporary_PR60896 {
50+
51+
// Custom shared_ptr implementation for testing
52+
template <typename T>
53+
struct shared_ptr {
54+
T* ptr;
55+
shared_ptr(T* p) : ptr(p) {}
56+
~shared_ptr() { delete ptr; }
57+
shared_ptr(shared_ptr&& other) : ptr(other.ptr) { other.ptr = nullptr; }
58+
T* get() const { return ptr; }
59+
};
60+
61+
template <typename T, typename... Args>
62+
shared_ptr<T> make_shared(Args&&... args) {
63+
return shared_ptr<T>(new T(args...));
64+
}
65+
66+
struct Foo {
67+
shared_ptr<int> i;
68+
};
69+
70+
void add(Foo foo) {
71+
// The shared_ptr destructor will be called when foo goes out of scope
72+
}
73+
74+
void test() {
75+
// No warning should be emitted for this - the memory is managed by shared_ptr
76+
// in the temporary Foo object, which will properly clean up the memory
77+
add({make_shared<int>(1)});
78+
}
79+
80+
} // namespace shared_ptr_temporary_PR60896

0 commit comments

Comments
 (0)