[clang] [analyzer] MallocChecker – Fix false positive leak for smart pointers in temporary objects (PR #152751)
Ivan Murashko via cfe-commits
cfe-commits at lists.llvm.org
Fri Aug 15 04:49:42 PDT 2025
================
@@ -3068,11 +3107,217 @@ void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper,
C.addTransition(state->set<RegionState>(RS), N);
}
+static QualType canonicalStrip(QualType QT) {
+ return QT.getCanonicalType().getUnqualifiedType();
+}
+
+static bool isInStdNamespace(const DeclContext *DC) {
+ while (DC) {
+ if (const auto *NS = dyn_cast<NamespaceDecl>(DC))
+ if (NS->isStdNamespace())
+ return true;
+ DC = DC->getParent();
+ }
+ return false;
+}
+
+// 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 = canonicalStrip(QT);
+
+ // First try TemplateSpecializationType (for std smart pointers)
+ const auto *TST = QT->getAs<TemplateSpecializationType>();
+ if (TST) {
+ const TemplateDecl *TD = TST->getTemplateName().getAsTemplateDecl();
+ if (!TD)
+ return false;
+
+ const auto *ND = dyn_cast_or_null<NamedDecl>(TD->getTemplatedDecl());
+ if (!ND)
+ return false;
+
+ // Check if it's in std namespace
+ const DeclContext *DC = ND->getDeclContext();
+ if (!isInStdNamespace(DC))
+ return false;
+
+ StringRef Name = ND->getName();
+ return Name == "unique_ptr" || Name == "shared_ptr";
+ }
+
+ // Also try RecordType (for custom smart pointer implementations)
+ const auto *RT = QT->getAs<RecordType>();
+ if (RT) {
+ const auto *RD = RT->getDecl();
+ if (RD) {
+ StringRef Name = RD->getName();
+ if (Name == "unique_ptr" || Name == "shared_ptr") {
+ // Accept any custom unique_ptr or shared_ptr implementation
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+static void collectDirectSmartOwningPtrFieldRegions(
+ const MemRegion *Base, QualType RecQT, CheckerContext &C,
+ SmallVectorImpl<const MemRegion *> &Out) {
+ if (!Base)
+ return;
+ const auto *CRD = RecQT->getAsCXXRecordDecl();
+ if (!CRD)
+ return;
+
+ 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);
+ }
+}
+
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<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();
+
+ QualType T = AE->getType();
+
+ // **Relaxation 1**: accept *any rvalue* by-value record (not only strict
+ // PRVALUE).
+ if (AE->isGLValue())
+ continue;
+
+ // By-value record only (no refs).
+ if (!T->isRecordType() || T->isReferenceType())
+ continue;
+
+ // **Relaxation 2**: accept common temp/construct forms but don't overfit.
+ const bool LooksLikeTemp =
+ isa<CXXTemporaryObjectExpr>(AE) || isa<MaterializeTemporaryExpr>(AE) ||
+ isa<CXXConstructExpr>(AE) || isa<InitListExpr>(AE) ||
+ isa<ImplicitCastExpr>(AE) || // handle common rvalue materializations
+ isa<CXXBindTemporaryExpr>(AE); // handle CXXBindTemporaryExpr
+ if (!LooksLikeTemp)
+ continue;
+
+ // Require at least one direct smart owning pointer field by type.
+ const auto *CRD = T->getAsCXXRecordDecl();
+ if (!CRD)
+ continue;
+ bool HasSmartPtrField = false;
+ for (const FieldDecl *FD : CRD->fields()) {
+ if (isSmartOwningPtrType(FD->getType())) {
+ HasSmartPtrField = true;
+ break;
+ }
+ }
+ if (!HasSmartPtrField)
+ continue;
+
+ // Find a region for the argument.
+ SVal VCall = Call.getArgSVal(I);
+ SVal VExpr = C.getSVal(AE);
+ const MemRegion *RCall = VCall.getAsRegion();
+ const MemRegion *RExpr = VExpr.getAsRegion();
+
+ const MemRegion *Base = RCall ? RCall : RExpr;
+ if (!Base) {
+ // Fallback: if we have a by-value record with unique_ptr fields but no
+ // region, mark all allocated symbols as escaped
+ ProgramStateRef State = C.getState();
+ RegionStateTy RS = State->get<RegionState>();
+ ProgramStateRef NewState = State;
+ for (auto [Sym, RefSt] : RS) {
+ if (RefSt.isAllocated() || RefSt.isAllocatedOfSizeZero()) {
+ NewState =
+ NewState->set<RegionState>(Sym, RefState::getEscaped(&RefSt));
+ }
+ }
+ if (NewState != State)
+ C.addTransition(NewState);
+ continue;
+ }
+
+ // Push direct smart owning pointer field regions only (precise root set).
+ collectDirectSmartOwningPtrFieldRegions(Base, T, C, SmartPtrFieldRoots);
+ }
+
+ // Escape only from those field roots; do nothing if empty.
+ if (!SmartPtrFieldRoots.empty()) {
+ ProgramStateRef State = C.getState();
+ auto Scan =
+ State->scanReachableSymbols<EscapeTrackedCallback>(SmartPtrFieldRoots);
+ ProgramStateRef NewState = Scan.getState();
+ if (NewState != State) {
+ C.addTransition(NewState);
+ } else {
+ // Fallback: if we have by-value record arguments but no smart pointer
+ // fields detected, check if any of the arguments are by-value records
+ // with smart pointer fields
+ bool hasByValueRecordWithSmartPtr = false;
+ for (unsigned I = 0, E = Call.getNumArgs(); I != E; ++I) {
+ const Expr *AE = Call.getArgExpr(I);
+ if (!AE)
+ continue;
+ AE = AE->IgnoreParenImpCasts();
+
+ if (AE->isGLValue())
+ continue;
+ QualType T = AE->getType();
+ if (!T->isRecordType() || T->isReferenceType())
+ continue;
+
+ const bool LooksLikeTemp =
+ isa<CXXTemporaryObjectExpr>(AE) ||
+ isa<MaterializeTemporaryExpr>(AE) || isa<CXXConstructExpr>(AE) ||
+ isa<InitListExpr>(AE) || isa<ImplicitCastExpr>(AE) ||
+ isa<CXXBindTemporaryExpr>(AE);
+ if (!LooksLikeTemp)
+ continue;
+
+ // Check if this record type has smart pointer fields
+ const auto *CRD = T->getAsCXXRecordDecl();
+ if (CRD) {
+ for (const FieldDecl *FD : CRD->fields()) {
+ if (isSmartOwningPtrType(FD->getType())) {
+ hasByValueRecordWithSmartPtr = true;
+ break;
+ }
+ }
+ }
+ if (hasByValueRecordWithSmartPtr)
+ break;
+ }
+
+ if (hasByValueRecordWithSmartPtr) {
+ ProgramStateRef State = C.getState();
+ RegionStateTy RS = State->get<RegionState>();
+ ProgramStateRef NewState = State;
+ for (auto [Sym, RefSt] : RS) {
+ if (RefSt.isAllocated() || RefSt.isAllocatedOfSizeZero()) {
+ NewState =
+ NewState->set<RegionState>(Sym, RefState::getEscaped(&RefSt));
+ }
+ }
----------------
ivanmurashko wrote:
The code was refactored and duplication was removed
https://github.com/llvm/llvm-project/pull/152751
More information about the cfe-commits
mailing list