//=======- RetainPtrCtorAdoptChecker.cpp -------------------------*- C++ -*-==// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "ASTUtils.h" #include "PtrTypesSemantics.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Analysis/DomainSpecific/CocoaConventions.h" #include "clang/Analysis/RetainSummaryManager.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "llvm/ADT/DenseSet.h" #include using namespace clang; using namespace ento; namespace { class RetainPtrCtorAdoptChecker : public Checker> { private: BugType Bug; mutable BugReporter *BR = nullptr; mutable std::unique_ptr Summaries; mutable llvm::DenseSet CreateOrCopyOutArguments; mutable llvm::DenseSet CreateOrCopyFnCall; mutable RetainTypeChecker RTC; public: RetainPtrCtorAdoptChecker() : Bug(this, "Correct use of RetainPtr, adoptNS, and adoptCF", "WebKit coding guidelines") {} void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, BugReporter &BRArg) const { BR = &BRArg; // The calls to checkAST* from AnalysisConsumer don't // visit template instantiations or lambda classes. We // want to visit those, so we make our own RecursiveASTVisitor. struct LocalVisitor : public RecursiveASTVisitor { const RetainPtrCtorAdoptChecker *Checker; Decl *DeclWithIssue{nullptr}; using Base = RecursiveASTVisitor; explicit LocalVisitor(const RetainPtrCtorAdoptChecker *Checker) : Checker(Checker) { assert(Checker); } bool shouldVisitTemplateInstantiations() const { return true; } bool shouldVisitImplicitCode() const { return false; } bool TraverseDecl(Decl *D) { llvm::SaveAndRestore SavedDecl(DeclWithIssue); if (D && (isa(D) || isa(D))) DeclWithIssue = D; return Base::TraverseDecl(D); } bool TraverseClassTemplateDecl(ClassTemplateDecl *CTD) { if (isRetainPtrOrOSPtr(safeGetName(CTD))) return true; // Skip the contents of RetainPtr. return Base::TraverseClassTemplateDecl(CTD); } bool VisitTypedefDecl(TypedefDecl *TD) { Checker->RTC.visitTypedef(TD); return true; } bool VisitCallExpr(const CallExpr *CE) { Checker->visitCallExpr(CE, DeclWithIssue); return true; } bool VisitCXXConstructExpr(const CXXConstructExpr *CE) { Checker->visitConstructExpr(CE, DeclWithIssue); return true; } bool VisitObjCMessageExpr(const ObjCMessageExpr *ObjCMsgExpr) { Checker->visitObjCMessageExpr(ObjCMsgExpr, DeclWithIssue); return true; } bool VisitReturnStmt(const ReturnStmt *RS) { Checker->visitReturnStmt(RS, DeclWithIssue); return true; } bool VisitVarDecl(const VarDecl *VD) { Checker->visitVarDecl(VD); return true; } bool VisitBinaryOperator(const BinaryOperator *BO) { Checker->visitBinaryOperator(BO); return true; } }; LocalVisitor visitor(this); Summaries = std::make_unique( TUD->getASTContext(), true /* trackObjCAndCFObjects */, false /* trackOSObjects */); RTC.visitTranslationUnitDecl(TUD); visitor.TraverseDecl(const_cast(TUD)); } bool isAdoptFn(const Decl *FnDecl) const { return isAdoptFnName(safeGetName(FnDecl)); } bool isAdoptFnName(const std::string &Name) const { return isAdoptNS(Name) || Name == "adoptCF" || Name == "adoptCFArc" || Name == "adoptOSObject" || Name == "adoptOSObjectArc"; } bool isAdoptNS(const std::string &Name) const { return Name == "adoptNS" || Name == "adoptNSArc"; } void visitCallExpr(const CallExpr *CE, const Decl *DeclWithIssue) const { assert(BR && "expected nonnull BugReporter"); if (BR->getSourceManager().isInSystemHeader(CE->getExprLoc())) return; std::string FnName; if (auto *F = CE->getDirectCallee()) { FnName = safeGetName(F); if (isAdoptFnName(FnName)) checkAdoptCall(CE, FnName, DeclWithIssue); else { checkCreateOrCopyFunction(CE, DeclWithIssue); checkBridgingRelease(CE, F, DeclWithIssue); } return; } auto *CalleeExpr = CE->getCallee(); if (!CalleeExpr) return; CalleeExpr = CalleeExpr->IgnoreParenCasts(); if (auto *UnresolvedExpr = dyn_cast(CalleeExpr)) { auto Name = UnresolvedExpr->getName(); if (!Name.isIdentifier()) return; FnName = Name.getAsString(); if (isAdoptFnName(FnName)) checkAdoptCall(CE, FnName, DeclWithIssue); } checkCreateOrCopyFunction(CE, DeclWithIssue); } void checkAdoptCall(const CallExpr *CE, const std::string &FnName, const Decl *DeclWithIssue) const { if (!CE->getNumArgs()) return; auto *Arg = CE->getArg(0)->IgnoreParenCasts(); auto Result = isOwned(Arg); if (Result == IsOwnedResult::Unknown) Result = IsOwnedResult::NotOwned; const Expr *Inner = nullptr; if (isAllocInit(Arg, &Inner) || isCreateOrCopy(Arg)) { if (Inner) CreateOrCopyFnCall.insert(Inner); CreateOrCopyFnCall.insert(Arg); // Avoid double reporting. return; } if (Result == IsOwnedResult::Owned || Result == IsOwnedResult::Skip || isNullPtr(Arg)) { CreateOrCopyFnCall.insert(Arg); return; } if (auto *DRE = dyn_cast(Arg)) { if (CreateOrCopyOutArguments.contains(DRE->getDecl())) return; } if (RTC.isARCEnabled() && isAdoptFnName(FnName)) reportUseAfterFree(FnName, CE, DeclWithIssue, "when ARC is disabled"); else reportUseAfterFree(FnName, CE, DeclWithIssue); } void visitObjCMessageExpr(const ObjCMessageExpr *ObjCMsgExpr, const Decl *DeclWithIssue) const { if (BR->getSourceManager().isInSystemHeader(ObjCMsgExpr->getExprLoc())) return; auto Selector = ObjCMsgExpr->getSelector(); if (Selector.getAsString() == "autorelease") { auto *Receiver = ObjCMsgExpr->getInstanceReceiver()->IgnoreParenCasts(); if (!Receiver) return; ObjCMsgExpr = dyn_cast(Receiver); if (!ObjCMsgExpr) return; const Expr *Inner = nullptr; if (!isAllocInit(ObjCMsgExpr, &Inner)) return; CreateOrCopyFnCall.insert(ObjCMsgExpr); if (Inner) CreateOrCopyFnCall.insert(Inner); return; } const Expr *Inner = nullptr; if (!isAllocInit(ObjCMsgExpr, &Inner)) return; if (RTC.isARCEnabled()) return; // ARC never leaks. if (CreateOrCopyFnCall.contains(ObjCMsgExpr)) return; if (Inner) CreateOrCopyFnCall.insert(Inner); // Avoid double reporting. reportLeak(ObjCMsgExpr, DeclWithIssue); } void checkCreateOrCopyFunction(const CallExpr *CE, const Decl *DeclWithIssue) const { unsigned ArgCount = CE->getNumArgs(); auto *CalleeDecl = CE->getCalleeDecl(); auto *FnDecl = CalleeDecl ? CalleeDecl->getAsFunction() : nullptr; for (unsigned ArgIndex = 0; ArgIndex < ArgCount; ++ArgIndex) { auto *Arg = CE->getArg(ArgIndex)->IgnoreParenCasts(); auto *Unary = dyn_cast(Arg); if (!Unary) continue; if (Unary->getOpcode() != UO_AddrOf) continue; auto *SubExpr = Unary->getSubExpr(); if (!SubExpr) continue; auto *DRE = dyn_cast(SubExpr->IgnoreParenCasts()); if (!DRE) continue; auto *Decl = DRE->getDecl(); if (!Decl) continue; if (FnDecl && ArgIndex < FnDecl->getNumParams()) { // Manually check attributes on argumenet since RetainSummaryManager // basically ignores CF_RETRUNS_RETAINED on out arguments. auto *ParamDecl = FnDecl->getParamDecl(ArgIndex); if (ParamDecl->hasAttr()) CreateOrCopyOutArguments.insert(Decl); } else { // No callee or a variadic argument. // Conservatively assume it's an out argument. if (RTC.isUnretained(Decl->getType())) CreateOrCopyOutArguments.insert(Decl); } } auto Summary = Summaries->getSummary(AnyCall(CE)); switch (Summary->getRetEffect().getKind()) { case RetEffect::OwnedSymbol: case RetEffect::OwnedWhenTrackedReceiver: if (!CreateOrCopyFnCall.contains(CE)) reportLeak(CE, DeclWithIssue); break; default: break; } } void checkBridgingRelease(const CallExpr *CE, const FunctionDecl *Callee, const Decl *DeclWithIssue) const { if (safeGetName(Callee) != "CFBridgingRelease" || CE->getNumArgs() != 1) return; auto *Arg = CE->getArg(0)->IgnoreParenCasts(); auto *InnerCE = dyn_cast(Arg); if (!InnerCE) return; auto *InnerF = InnerCE->getDirectCallee(); if (!InnerF || !isCreateOrCopyFunction(InnerF)) return; CreateOrCopyFnCall.insert(InnerCE); } void visitConstructExpr(const CXXConstructExpr *CE, const Decl *DeclWithIssue) const { assert(BR && "expected nonnull BugReporter"); if (BR->getSourceManager().isInSystemHeader(CE->getExprLoc())) return; auto *Ctor = CE->getConstructor(); if (!Ctor) return; auto *Cls = Ctor->getParent(); if (!Cls) return; if (!isRetainPtrOrOSPtr(safeGetName(Cls)) || !CE->getNumArgs()) return; // Ignore RetainPtr construction inside adoptNS, adoptCF, and retainPtr. if (isAdoptFn(DeclWithIssue) || safeGetName(DeclWithIssue) == "retainPtr") return; std::string Name = "RetainPtr constructor"; auto *Arg = CE->getArg(0)->IgnoreParenCasts(); auto Result = isOwned(Arg); if (isCreateOrCopy(Arg)) CreateOrCopyFnCall.insert(Arg); // Avoid double reporting. const Expr *Inner = nullptr; if (isAllocInit(Arg, &Inner)) { CreateOrCopyFnCall.insert(Arg); if (Inner) CreateOrCopyFnCall.insert(Inner); } if (Result == IsOwnedResult::Skip) return; if (Result == IsOwnedResult::Unknown) Result = IsOwnedResult::NotOwned; if (Result == IsOwnedResult::Owned) reportLeak(Name, CE, DeclWithIssue); else if (RTC.isARCEnabled() && isAllocInit(Arg)) reportLeak(Name, CE, DeclWithIssue, "when ARC is disabled"); else if (isCreateOrCopy(Arg)) reportLeak(Name, CE, DeclWithIssue); } void visitVarDecl(const VarDecl *VD) const { auto *Init = VD->getInit(); if (!Init || !RTC.isARCEnabled()) return; Init = Init->IgnoreParenCasts(); const Expr *Inner = nullptr; if (isAllocInit(Init, &Inner)) { CreateOrCopyFnCall.insert(Init); if (Inner) CreateOrCopyFnCall.insert(Inner); } } void visitBinaryOperator(const BinaryOperator *BO) const { if (!BO->isAssignmentOp()) return; if (!isa(BO->getLHS())) return; auto *RHS = BO->getRHS()->IgnoreParenCasts(); const Expr *Inner = nullptr; if (isAllocInit(RHS, &Inner)) { CreateOrCopyFnCall.insert(RHS); if (Inner) CreateOrCopyFnCall.insert(Inner); } } void visitReturnStmt(const ReturnStmt *RS, const Decl *DeclWithIssue) const { if (!DeclWithIssue) return; auto *RetValue = RS->getRetValue(); if (!RetValue) return; RetValue = RetValue->IgnoreParenCasts(); std::optional retainsRet; if (auto *FnDecl = dyn_cast(DeclWithIssue)) retainsRet = retainsReturnValue(FnDecl); else if (auto *MethodDecl = dyn_cast(DeclWithIssue)) retainsRet = retainsReturnValue(MethodDecl); else return; if (!retainsRet || !*retainsRet) { // Under ARC, returning [[X alloc] init] doesn't leak X. if (RTC.isUnretained(RetValue->getType())) return; } if (auto *CE = dyn_cast(RetValue)) { auto *Callee = CE->getDirectCallee(); if (!Callee || !isCreateOrCopyFunction(Callee)) return; CreateOrCopyFnCall.insert(CE); return; } const Expr *Inner = nullptr; if (isAllocInit(RetValue, &Inner)) { CreateOrCopyFnCall.insert(RetValue); if (Inner) CreateOrCopyFnCall.insert(Inner); } } template std::optional retainsReturnValue(const CallableType *FnDecl) const { auto Summary = Summaries->getSummary(AnyCall(FnDecl)); auto RetEffect = Summary->getRetEffect(); switch (RetEffect.getKind()) { case RetEffect::NoRet: return std::nullopt; case RetEffect::OwnedSymbol: return true; case RetEffect::NotOwnedSymbol: return false; case RetEffect::OwnedWhenTrackedReceiver: return std::nullopt; case RetEffect::NoRetHard: return std::nullopt; } return std::nullopt; } bool isAllocInit(const Expr *E, const Expr **InnerExpr = nullptr) const { auto *ObjCMsgExpr = dyn_cast(E); if (auto *POE = dyn_cast(E)) { if (unsigned ExprCount = POE->getNumSemanticExprs()) { auto *Expr = POE->getSemanticExpr(ExprCount - 1)->IgnoreParenCasts(); ObjCMsgExpr = dyn_cast(Expr); if (InnerExpr) *InnerExpr = ObjCMsgExpr; } } if (!ObjCMsgExpr) return false; auto Selector = ObjCMsgExpr->getSelector(); auto NameForFirstSlot = Selector.getNameForSlot(0); if (NameForFirstSlot == "alloc" || NameForFirstSlot.starts_with("copy") || NameForFirstSlot.starts_with("mutableCopy")) return true; if (!NameForFirstSlot.starts_with("init") && !NameForFirstSlot.starts_with("_init")) return false; if (!ObjCMsgExpr->isInstanceMessage()) return false; auto *Receiver = ObjCMsgExpr->getInstanceReceiver(); if (!Receiver) return false; Receiver = Receiver->IgnoreParenCasts(); if (auto *Inner = dyn_cast(Receiver)) { if (InnerExpr) *InnerExpr = Inner; auto InnerSelector = Inner->getSelector(); return InnerSelector.getNameForSlot(0) == "alloc"; } else if (auto *CE = dyn_cast(Receiver)) { if (InnerExpr) *InnerExpr = CE; if (auto *Callee = CE->getDirectCallee()) { if (Callee->getDeclName().isIdentifier()) { auto CalleeName = Callee->getName(); return CalleeName.starts_with("alloc"); } } } return false; } bool isCreateOrCopy(const Expr *E) const { auto *CE = dyn_cast(E); if (!CE) return false; auto *Callee = CE->getDirectCallee(); if (!Callee) return false; return isCreateOrCopyFunction(Callee); } bool isCreateOrCopyFunction(const FunctionDecl *FnDecl) const { auto CalleeName = safeGetName(FnDecl); return CalleeName.find("Create") != std::string::npos || CalleeName.find("Copy") != std::string::npos; } enum class IsOwnedResult { Unknown, Skip, Owned, NotOwned }; IsOwnedResult isOwned(const Expr *E) const { while (1) { if (auto *POE = dyn_cast(E)) { if (unsigned SemanticExprCount = POE->getNumSemanticExprs()) { E = POE->getSemanticExpr(SemanticExprCount - 1); continue; } } if (isNullPtr(E)) return IsOwnedResult::NotOwned; if (auto *DRE = dyn_cast(E)) { auto QT = DRE->getType(); if (isRetainPtrOrOSPtrType(QT)) return IsOwnedResult::NotOwned; QT = QT.getCanonicalType(); if (RTC.isUnretained(QT, true /* ignoreARC */)) return IsOwnedResult::NotOwned; auto *PointeeType = QT->getPointeeType().getTypePtrOrNull(); if (PointeeType && PointeeType->isVoidType()) return IsOwnedResult::NotOwned; // Assume reading void* as +0. } if (auto *TE = dyn_cast(E)) { E = TE->getSubExpr(); continue; } if (auto *ObjCMsgExpr = dyn_cast(E)) { auto Summary = Summaries->getSummary(AnyCall(ObjCMsgExpr)); auto RetEffect = Summary->getRetEffect(); switch (RetEffect.getKind()) { case RetEffect::NoRet: return IsOwnedResult::Unknown; case RetEffect::OwnedSymbol: return IsOwnedResult::Owned; case RetEffect::NotOwnedSymbol: return IsOwnedResult::NotOwned; case RetEffect::OwnedWhenTrackedReceiver: if (auto *Receiver = ObjCMsgExpr->getInstanceReceiver()) { E = Receiver->IgnoreParenCasts(); continue; } return IsOwnedResult::Unknown; case RetEffect::NoRetHard: return IsOwnedResult::Unknown; } } if (auto *CXXCE = dyn_cast(E)) { if (auto *MD = CXXCE->getMethodDecl()) { auto *Cls = MD->getParent(); if (auto *CD = dyn_cast(MD)) { auto QT = CD->getConversionType().getCanonicalType(); auto *ResultType = QT.getTypePtrOrNull(); if (isRetainPtrOrOSPtr(safeGetName(Cls)) && ResultType && (ResultType->isPointerType() || ResultType->isReferenceType() || ResultType->isObjCObjectPointerType())) return IsOwnedResult::NotOwned; } if (safeGetName(MD) == "leakRef" && isRetainPtrOrOSPtr(safeGetName(Cls))) return IsOwnedResult::Owned; } } if (auto *CE = dyn_cast(E)) { if (auto *Callee = CE->getDirectCallee()) { if (isAdoptFn(Callee)) return IsOwnedResult::NotOwned; auto Name = safeGetName(Callee); if (Name == "__builtin___CFStringMakeConstantString") return IsOwnedResult::NotOwned; if ((Name == "checked_cf_cast" || Name == "dynamic_cf_cast" || Name == "checked_objc_cast" || Name == "dynamic_objc_cast") && CE->getNumArgs() == 1) { E = CE->getArg(0)->IgnoreParenCasts(); continue; } auto RetType = Callee->getReturnType(); if (isRetainPtrOrOSPtrType(RetType)) return IsOwnedResult::NotOwned; if (isCreateOrCopyFunction(Callee)) { CreateOrCopyFnCall.insert(CE); return IsOwnedResult::Owned; } } else if (auto *CalleeExpr = CE->getCallee()) { if (isa(CalleeExpr)) return IsOwnedResult::Skip; // Wait for instantiation. if (isa(CalleeExpr)) return IsOwnedResult::Skip; // Wait for instantiation. } auto Summary = Summaries->getSummary(AnyCall(CE)); auto RetEffect = Summary->getRetEffect(); switch (RetEffect.getKind()) { case RetEffect::NoRet: return IsOwnedResult::Unknown; case RetEffect::OwnedSymbol: return IsOwnedResult::Owned; case RetEffect::NotOwnedSymbol: return IsOwnedResult::NotOwned; case RetEffect::OwnedWhenTrackedReceiver: return IsOwnedResult::Unknown; case RetEffect::NoRetHard: return IsOwnedResult::Unknown; } } break; } return IsOwnedResult::Unknown; } void reportUseAfterFree(const std::string &Name, const CallExpr *CE, const Decl *DeclWithIssue, const char *condition = nullptr) const { SmallString<100> Buf; llvm::raw_svector_ostream Os(Buf); Os << "Incorrect use of " << Name << ". The argument is +0 and results in an use-after-free"; if (condition) Os << " " << condition; Os << "."; assert(BR && "expected nonnull BugReporter"); PathDiagnosticLocation BSLoc(CE->getSourceRange().getBegin(), BR->getSourceManager()); auto Report = std::make_unique(Bug, Os.str(), BSLoc); Report->addRange(CE->getSourceRange()); Report->setDeclWithIssue(DeclWithIssue); BR->emitReport(std::move(Report)); } void reportLeak(std::string &Name, const CXXConstructExpr *CE, const Decl *DeclWithIssue, const char *condition = nullptr) const { SmallString<100> Buf; llvm::raw_svector_ostream Os(Buf); Os << "Incorrect use of " << Name << ". The argument is +1 and results in a memory leak"; if (condition) Os << " " << condition; Os << "."; assert(BR && "expected nonnull BugReporter"); PathDiagnosticLocation BSLoc(CE->getSourceRange().getBegin(), BR->getSourceManager()); auto Report = std::make_unique(Bug, Os.str(), BSLoc); Report->addRange(CE->getSourceRange()); Report->setDeclWithIssue(DeclWithIssue); BR->emitReport(std::move(Report)); } template void reportLeak(const ExprType *E, const Decl *DeclWithIssue) const { SmallString<100> Buf; llvm::raw_svector_ostream Os(Buf); Os << "The return value is +1 and results in a memory leak."; PathDiagnosticLocation BSLoc(E->getSourceRange().getBegin(), BR->getSourceManager()); auto Report = std::make_unique(Bug, Os.str(), BSLoc); Report->addRange(E->getSourceRange()); Report->setDeclWithIssue(DeclWithIssue); BR->emitReport(std::move(Report)); } }; } // namespace void ento::registerRetainPtrCtorAdoptChecker(CheckerManager &Mgr) { Mgr.registerChecker(); } bool ento::shouldRegisterRetainPtrCtorAdoptChecker(const CheckerManager &mgr) { return true; }