|
78 | 78 | #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" |
79 | 79 | #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" |
80 | 80 | #include "llvm/ADT/STLExtras.h" |
| 81 | +#include "llvm/ADT/SmallVector.h" |
81 | 82 | #include "llvm/ADT/StringExtras.h" |
82 | 83 | #include "llvm/Support/Casting.h" |
83 | 84 | #include "llvm/Support/Compiler.h" |
@@ -421,6 +422,13 @@ class MallocChecker |
421 | 422 | void checkPreCall(const CallEvent &Call, CheckerContext &C) const; |
422 | 423 | void checkPostCall(const CallEvent &Call, CheckerContext &C) const; |
423 | 424 | bool evalCall(const CallEvent &Call, CheckerContext &C) const; |
| 425 | + |
| 426 | + ProgramStateRef |
| 427 | + handleSmartPointerConstructorArguments(const CallEvent &Call, |
| 428 | + ProgramStateRef State) const; |
| 429 | + ProgramStateRef handleSmartPointerRelatedCalls(const CallEvent &Call, |
| 430 | + CheckerContext &C, |
| 431 | + ProgramStateRef State) const; |
424 | 432 | void checkNewAllocator(const CXXAllocatorCall &Call, CheckerContext &C) const; |
425 | 433 | void checkPostObjCMessage(const ObjCMethodCall &Call, CheckerContext &C) const; |
426 | 434 | void checkPostStmt(const BlockExpr *BE, CheckerContext &C) const; |
@@ -1096,6 +1104,54 @@ class StopTrackingCallback final : public SymbolVisitor { |
1096 | 1104 | return true; |
1097 | 1105 | } |
1098 | 1106 | }; |
| 1107 | + |
| 1108 | +/// EscapeTrackedCallback - A SymbolVisitor that marks allocated symbols as |
| 1109 | +/// escaped. |
| 1110 | +/// |
| 1111 | +/// This visitor is used to suppress false positive leak reports when smart |
| 1112 | +/// pointers are nested in temporary objects passed by value to functions. When |
| 1113 | +/// the analyzer can't see the destructor calls for temporary objects, it may |
| 1114 | +/// incorrectly report leaks for memory that will be properly freed by the smart |
| 1115 | +/// pointer destructors. |
| 1116 | +/// |
| 1117 | +/// The visitor traverses reachable symbols from a given set of memory regions |
| 1118 | +/// (typically smart pointer field regions) and marks any allocated symbols as |
| 1119 | +/// escaped. Escaped symbols are not reported as leaks by checkDeadSymbols. |
| 1120 | +class EscapeTrackedCallback final : public SymbolVisitor { |
| 1121 | + ProgramStateRef State; |
| 1122 | + |
| 1123 | + explicit EscapeTrackedCallback(ProgramStateRef S) : State(std::move(S)) {} |
| 1124 | + |
| 1125 | +public: |
| 1126 | + bool VisitSymbol(SymbolRef Sym) override { |
| 1127 | + if (const RefState *RS = State->get<RegionState>(Sym)) { |
| 1128 | + if (RS->isAllocated() || RS->isAllocatedOfSizeZero()) { |
| 1129 | + State = State->set<RegionState>(Sym, RefState::getEscaped(RS)); |
| 1130 | + } |
| 1131 | + } |
| 1132 | + return true; |
| 1133 | + } |
| 1134 | + |
| 1135 | + /// Escape tracked regions reachable from the given roots. |
| 1136 | + static ProgramStateRef |
| 1137 | + EscapeTrackedRegionsReachableFrom(ArrayRef<const MemRegion *> Roots, |
| 1138 | + ProgramStateRef State) { |
| 1139 | + if (Roots.empty()) |
| 1140 | + return State; |
| 1141 | + |
| 1142 | + // scanReachableSymbols is expensive, so we use a single visitor for all |
| 1143 | + // roots |
| 1144 | + SmallVector<const MemRegion *, 10> Regions; |
| 1145 | + EscapeTrackedCallback Visitor(State); |
| 1146 | + for (const MemRegion *R : Roots) { |
| 1147 | + Regions.push_back(R); |
| 1148 | + } |
| 1149 | + State->scanReachableSymbols(Regions, Visitor); |
| 1150 | + return Visitor.State; |
| 1151 | + } |
| 1152 | + |
| 1153 | + friend class SymbolVisitor; |
| 1154 | +}; |
1099 | 1155 | } // end anonymous namespace |
1100 | 1156 |
|
1101 | 1157 | static bool isStandardNew(const FunctionDecl *FD) { |
@@ -3068,12 +3124,260 @@ void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper, |
3068 | 3124 | C.addTransition(state->set<RegionState>(RS), N); |
3069 | 3125 | } |
3070 | 3126 |
|
| 3127 | +// Allowlist of owning smart pointers we want to recognize. |
| 3128 | +// Start with unique_ptr and shared_ptr; weak_ptr is excluded intentionally |
| 3129 | +// because it does not own the pointee. |
| 3130 | +static bool isSmartPtrName(StringRef Name) { |
| 3131 | + return Name == "unique_ptr" || Name == "shared_ptr"; |
| 3132 | +} |
| 3133 | + |
| 3134 | +// Check if a type is a smart owning pointer type. |
| 3135 | +static bool isSmartPtrType(QualType QT) { |
| 3136 | + QT = QT->getCanonicalTypeUnqualified(); |
| 3137 | + |
| 3138 | + if (const auto *TST = QT->getAs<TemplateSpecializationType>()) { |
| 3139 | + const TemplateDecl *TD = TST->getTemplateName().getAsTemplateDecl(); |
| 3140 | + if (!TD) |
| 3141 | + return false; |
| 3142 | + |
| 3143 | + const auto *ND = dyn_cast_or_null<NamedDecl>(TD->getTemplatedDecl()); |
| 3144 | + if (!ND) |
| 3145 | + return false; |
| 3146 | + |
| 3147 | + // For broader coverage we recognize all template classes with names that |
| 3148 | + // match the allowlist even if they are not declared in namespace 'std'. |
| 3149 | + return isSmartPtrName(ND->getName()); |
| 3150 | + } |
| 3151 | + |
| 3152 | + return false; |
| 3153 | +} |
| 3154 | + |
| 3155 | +/// Helper struct for collecting smart owning pointer field regions. |
| 3156 | +/// This allows both hasSmartPtrField and |
| 3157 | +/// collectSmartPtrFieldRegions to share the same traversal logic, |
| 3158 | +/// ensuring consistency. |
| 3159 | +struct FieldConsumer { |
| 3160 | + const MemRegion *Reg; |
| 3161 | + CheckerContext *C; |
| 3162 | + llvm::SmallPtrSetImpl<const MemRegion *> *Out; |
| 3163 | + |
| 3164 | + FieldConsumer(const MemRegion *Reg, CheckerContext &C, |
| 3165 | + llvm::SmallPtrSetImpl<const MemRegion *> &Out) |
| 3166 | + : Reg(Reg), C(&C), Out(&Out) {} |
| 3167 | + |
| 3168 | + void consume(const FieldDecl *FD) { |
| 3169 | + SVal L = C->getState()->getLValue(FD, loc::MemRegionVal(Reg)); |
| 3170 | + if (const MemRegion *FR = L.getAsRegion()) |
| 3171 | + Out->insert(FR); |
| 3172 | + } |
| 3173 | + |
| 3174 | + std::optional<FieldConsumer> switchToBase(const CXXRecordDecl *BaseDecl, |
| 3175 | + bool IsVirtual) { |
| 3176 | + // Get the base class region |
| 3177 | + SVal BaseL = |
| 3178 | + C->getState()->getLValue(BaseDecl, Reg->getAs<SubRegion>(), IsVirtual); |
| 3179 | + if (const MemRegion *BaseObjRegion = BaseL.getAsRegion()) { |
| 3180 | + // Return a consumer for the base class |
| 3181 | + return FieldConsumer{BaseObjRegion, *C, *Out}; |
| 3182 | + } |
| 3183 | + return std::nullopt; |
| 3184 | + } |
| 3185 | +}; |
| 3186 | + |
| 3187 | +/// Check if a record type has smart owning pointer fields (directly or in base |
| 3188 | +/// classes). When FC is provided, also collect the field regions. |
| 3189 | +/// |
| 3190 | +/// This function has dual behavior: |
| 3191 | +/// - When FC is nullopt: Returns true if smart pointer fields are found |
| 3192 | +/// - When FC is provided: Always returns false, but collects field regions |
| 3193 | +/// as a side effect through the FieldConsumer |
| 3194 | +/// |
| 3195 | +/// Note: When FC is provided, the return value should be ignored since the |
| 3196 | +/// function performs full traversal for collection and always returns false |
| 3197 | +/// to avoid early termination. |
| 3198 | +static bool hasSmartPtrField(const CXXRecordDecl *CRD, |
| 3199 | + std::optional<FieldConsumer> FC = std::nullopt) { |
| 3200 | + // Check direct fields |
| 3201 | + for (const FieldDecl *FD : CRD->fields()) { |
| 3202 | + if (isSmartPtrType(FD->getType())) { |
| 3203 | + if (!FC) |
| 3204 | + return true; |
| 3205 | + FC->consume(FD); |
| 3206 | + } |
| 3207 | + } |
| 3208 | + |
| 3209 | + // Check fields from base classes |
| 3210 | + for (const CXXBaseSpecifier &BaseSpec : CRD->bases()) { |
| 3211 | + if (const CXXRecordDecl *BaseDecl = |
| 3212 | + BaseSpec.getType()->getAsCXXRecordDecl()) { |
| 3213 | + std::optional<FieldConsumer> NewFC; |
| 3214 | + if (FC) { |
| 3215 | + NewFC = FC->switchToBase(BaseDecl, BaseSpec.isVirtual()); |
| 3216 | + if (!NewFC) |
| 3217 | + continue; |
| 3218 | + } |
| 3219 | + bool Found = hasSmartPtrField(BaseDecl, NewFC); |
| 3220 | + if (Found && !FC) |
| 3221 | + return true; |
| 3222 | + } |
| 3223 | + } |
| 3224 | + return false; |
| 3225 | +} |
| 3226 | + |
| 3227 | +/// Check if an expression is an rvalue record type passed by value. |
| 3228 | +static bool isRvalueByValueRecord(const Expr *AE) { |
| 3229 | + if (AE->isGLValue()) |
| 3230 | + return false; |
| 3231 | + |
| 3232 | + QualType T = AE->getType(); |
| 3233 | + if (!T->isRecordType() || T->isReferenceType()) |
| 3234 | + return false; |
| 3235 | + |
| 3236 | + // Accept common temp/construct forms but don't overfit. |
| 3237 | + return isa<CXXTemporaryObjectExpr, MaterializeTemporaryExpr, CXXConstructExpr, |
| 3238 | + InitListExpr, ImplicitCastExpr, CXXBindTemporaryExpr>(AE); |
| 3239 | +} |
| 3240 | + |
| 3241 | +/// Check if an expression is an rvalue record with smart owning pointer fields |
| 3242 | +/// passed by value. |
| 3243 | +static bool isRvalueByValueRecordWithSmartPtr(const Expr *AE) { |
| 3244 | + if (!isRvalueByValueRecord(AE)) |
| 3245 | + return false; |
| 3246 | + |
| 3247 | + const auto *CRD = AE->getType()->getAsCXXRecordDecl(); |
| 3248 | + return CRD && hasSmartPtrField(CRD); |
| 3249 | +} |
| 3250 | + |
| 3251 | +/// Check if a CXXRecordDecl has a name matching recognized smart pointer names. |
| 3252 | +static bool isSmartPtrRecord(const CXXRecordDecl *RD) { |
| 3253 | + if (!RD) |
| 3254 | + return false; |
| 3255 | + |
| 3256 | + // Check the record name directly and accept both std and custom smart pointer |
| 3257 | + // implementations for broader coverage |
| 3258 | + return isSmartPtrName(RD->getName()); |
| 3259 | +} |
| 3260 | + |
| 3261 | +/// Check if a call is a constructor of a smart owning pointer class that |
| 3262 | +/// accepts pointer parameters. |
| 3263 | +static bool isSmartPtrCall(const CallEvent &Call) { |
| 3264 | + // Only check for smart pointer constructor calls |
| 3265 | + const auto *CD = dyn_cast_or_null<CXXConstructorDecl>(Call.getDecl()); |
| 3266 | + if (!CD) |
| 3267 | + return false; |
| 3268 | + |
| 3269 | + const auto *RD = CD->getParent(); |
| 3270 | + if (!isSmartPtrRecord(RD)) |
| 3271 | + return false; |
| 3272 | + |
| 3273 | + // Check if constructor takes a pointer parameter |
| 3274 | + for (const auto *Param : CD->parameters()) { |
| 3275 | + QualType ParamType = Param->getType(); |
| 3276 | + if (ParamType->isPointerType() && !ParamType->isFunctionPointerType() && |
| 3277 | + !ParamType->isVoidPointerType()) { |
| 3278 | + return true; |
| 3279 | + } |
| 3280 | + } |
| 3281 | + |
| 3282 | + return false; |
| 3283 | +} |
| 3284 | + |
| 3285 | +/// Collect memory regions of smart owning pointer fields from a record type |
| 3286 | +/// (including fields from base classes). |
| 3287 | +static void |
| 3288 | +collectSmartPtrFieldRegions(const MemRegion *Reg, QualType RecQT, |
| 3289 | + CheckerContext &C, |
| 3290 | + llvm::SmallPtrSetImpl<const MemRegion *> &Out) { |
| 3291 | + if (!Reg) |
| 3292 | + return; |
| 3293 | + |
| 3294 | + const auto *CRD = RecQT->getAsCXXRecordDecl(); |
| 3295 | + if (!CRD) |
| 3296 | + return; |
| 3297 | + |
| 3298 | + FieldConsumer FC{Reg, C, Out}; |
| 3299 | + hasSmartPtrField(CRD, FC); |
| 3300 | +} |
| 3301 | + |
| 3302 | +/// Handle smart pointer constructor calls by escaping allocated symbols |
| 3303 | +/// that are passed as pointer arguments to the constructor. |
| 3304 | +ProgramStateRef MallocChecker::handleSmartPointerConstructorArguments( |
| 3305 | + const CallEvent &Call, ProgramStateRef State) const { |
| 3306 | + const auto *CD = cast<CXXConstructorDecl>(Call.getDecl()); |
| 3307 | + for (unsigned I = 0, E = std::min(Call.getNumArgs(), CD->getNumParams()); |
| 3308 | + I != E; ++I) { |
| 3309 | + const Expr *ArgExpr = Call.getArgExpr(I); |
| 3310 | + if (!ArgExpr) |
| 3311 | + continue; |
| 3312 | + |
| 3313 | + QualType ParamType = CD->getParamDecl(I)->getType(); |
| 3314 | + if (ParamType->isPointerType() && !ParamType->isFunctionPointerType() && |
| 3315 | + !ParamType->isVoidPointerType()) { |
| 3316 | + // This argument is a pointer being passed to smart pointer constructor |
| 3317 | + SVal ArgVal = Call.getArgSVal(I); |
| 3318 | + SymbolRef Sym = ArgVal.getAsSymbol(); |
| 3319 | + if (Sym && State->contains<RegionState>(Sym)) { |
| 3320 | + const RefState *RS = State->get<RegionState>(Sym); |
| 3321 | + if (RS && (RS->isAllocated() || RS->isAllocatedOfSizeZero())) { |
| 3322 | + State = State->set<RegionState>(Sym, RefState::getEscaped(RS)); |
| 3323 | + } |
| 3324 | + } |
| 3325 | + } |
| 3326 | + } |
| 3327 | + return State; |
| 3328 | +} |
| 3329 | + |
| 3330 | +/// Handle all smart pointer related processing in function calls. |
| 3331 | +/// This includes both direct smart pointer constructor calls and by-value |
| 3332 | +/// arguments containing smart pointer fields. |
| 3333 | +ProgramStateRef MallocChecker::handleSmartPointerRelatedCalls( |
| 3334 | + const CallEvent &Call, CheckerContext &C, ProgramStateRef State) const { |
| 3335 | + |
| 3336 | + // Handle direct smart pointer constructor calls first |
| 3337 | + if (isSmartPtrCall(Call)) { |
| 3338 | + return handleSmartPointerConstructorArguments(Call, State); |
| 3339 | + } |
| 3340 | + |
| 3341 | + // Handle smart pointer fields in by-value record arguments |
| 3342 | + llvm::SmallPtrSet<const MemRegion *, 8> SmartPtrFieldRoots; |
| 3343 | + for (unsigned I = 0, E = Call.getNumArgs(); I != E; ++I) { |
| 3344 | + const Expr *AE = Call.getArgExpr(I); |
| 3345 | + if (!AE) |
| 3346 | + continue; |
| 3347 | + AE = AE->IgnoreParenImpCasts(); |
| 3348 | + |
| 3349 | + if (!isRvalueByValueRecordWithSmartPtr(AE)) |
| 3350 | + continue; |
| 3351 | + |
| 3352 | + // Find a region for the argument. |
| 3353 | + SVal ArgVal = Call.getArgSVal(I); |
| 3354 | + const MemRegion *ArgRegion = ArgVal.getAsRegion(); |
| 3355 | + // Collect direct smart owning pointer field regions |
| 3356 | + collectSmartPtrFieldRegions(ArgRegion, AE->getType(), C, |
| 3357 | + SmartPtrFieldRoots); |
| 3358 | + } |
| 3359 | + |
| 3360 | + // Escape symbols reachable from smart pointer fields |
| 3361 | + if (!SmartPtrFieldRoots.empty()) { |
| 3362 | + SmallVector<const MemRegion *, 8> SmartPtrFieldRootsVec( |
| 3363 | + SmartPtrFieldRoots.begin(), SmartPtrFieldRoots.end()); |
| 3364 | + State = EscapeTrackedCallback::EscapeTrackedRegionsReachableFrom( |
| 3365 | + SmartPtrFieldRootsVec, State); |
| 3366 | + } |
| 3367 | + |
| 3368 | + return State; |
| 3369 | +} |
| 3370 | + |
3071 | 3371 | void MallocChecker::checkPostCall(const CallEvent &Call, |
3072 | 3372 | CheckerContext &C) const { |
| 3373 | + // Handle existing post-call handlers first |
3073 | 3374 | if (const auto *PostFN = PostFnMap.lookup(Call)) { |
3074 | 3375 | (*PostFN)(this, C.getState(), Call, C); |
3075 | | - return; |
| 3376 | + return; // Post-handler already called addTransition, we're done |
3076 | 3377 | } |
| 3378 | + |
| 3379 | + // Handle smart pointer related processing only if no post-handler was called |
| 3380 | + C.addTransition(handleSmartPointerRelatedCalls(Call, C, C.getState())); |
3077 | 3381 | } |
3078 | 3382 |
|
3079 | 3383 | void MallocChecker::checkPreCall(const CallEvent &Call, |
@@ -3194,7 +3498,6 @@ void MallocChecker::checkEscapeOnReturn(const ReturnStmt *S, |
3194 | 3498 | if (!Sym) |
3195 | 3499 | // If we are returning a field of the allocated struct or an array element, |
3196 | 3500 | // the callee could still free the memory. |
3197 | | - // TODO: This logic should be a part of generic symbol escape callback. |
3198 | 3501 | if (const MemRegion *MR = RetVal.getAsRegion()) |
3199 | 3502 | if (isa<FieldRegion, ElementRegion>(MR)) |
3200 | 3503 | if (const SymbolicRegion *BMR = |
|
0 commit comments