| //=======- 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 "DiagOutputUtils.h" |
| #include "PtrTypesSemantics.h" |
| #include "clang/AST/CXXInheritance.h" |
| #include "clang/AST/RecursiveASTVisitor.h" |
| #include "clang/AST/StmtVisitor.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 "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
| #include "llvm/ADT/DenseSet.h" |
| #include "llvm/ADT/SetVector.h" |
| #include <optional> |
| |
| using namespace clang; |
| using namespace ento; |
| |
| namespace { |
| |
| class RetainPtrCtorAdoptChecker |
| : public Checker<check::ASTDecl<TranslationUnitDecl>> { |
| private: |
| BugType Bug; |
| mutable BugReporter *BR = nullptr; |
| mutable std::unique_ptr<RetainSummaryManager> Summaries; |
| mutable llvm::DenseSet<const ValueDecl *> CreateOrCopyOutArguments; |
| mutable llvm::DenseSet<const Expr *> 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<LocalVisitor> { |
| const RetainPtrCtorAdoptChecker *Checker; |
| Decl *DeclWithIssue{nullptr}; |
| |
| using Base = RecursiveASTVisitor<LocalVisitor>; |
| |
| 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<FunctionDecl>(D) || isa<ObjCMethodDecl>(D))) |
| DeclWithIssue = D; |
| return Base::TraverseDecl(D); |
| } |
| |
| bool TraverseClassTemplateDecl(ClassTemplateDecl *CTD) { |
| if (safeGetName(CTD) == "RetainPtr") |
| 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; |
| } |
| }; |
| |
| LocalVisitor visitor(this); |
| Summaries = std::make_unique<RetainSummaryManager>( |
| TUD->getASTContext(), true /* trackObjCAndCFObjects */, |
| false /* trackOSObjects */); |
| RTC.visitTranslationUnitDecl(TUD); |
| visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD)); |
| } |
| |
| bool isAdoptFn(const Decl *FnDecl) const { |
| auto Name = safeGetName(FnDecl); |
| return Name == "adoptNS" || Name == "adoptCF" || Name == "adoptNSArc" || |
| Name == "adoptCFArc"; |
| } |
| |
| bool isAdoptNS(const Decl *FnDecl) const { |
| auto Name = safeGetName(FnDecl); |
| 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; |
| |
| auto *F = CE->getDirectCallee(); |
| if (!F) |
| return; |
| |
| if (!isAdoptFn(F) || !CE->getNumArgs()) { |
| checkCreateOrCopyFunction(CE, F, DeclWithIssue); |
| return; |
| } |
| |
| auto *Arg = CE->getArg(0)->IgnoreParenCasts(); |
| auto Result = isOwned(Arg); |
| auto Name = safeGetName(F); |
| if (Result == IsOwnedResult::Unknown) |
| Result = IsOwnedResult::NotOwned; |
| if (isAllocInit(Arg) || isCreateOrCopy(Arg)) { |
| CreateOrCopyFnCall.insert(Arg); // Avoid double reporting. |
| return; |
| } |
| if (Result == IsOwnedResult::Owned || Result == IsOwnedResult::Skip) |
| return; |
| |
| if (auto *DRE = dyn_cast<DeclRefExpr>(Arg)) { |
| if (CreateOrCopyOutArguments.contains(DRE->getDecl())) |
| return; |
| } |
| if (RTC.isARCEnabled() && isAdoptNS(F)) |
| reportUseAfterFree(Name, CE, DeclWithIssue, "when ARC is disabled"); |
| else |
| reportUseAfterFree(Name, CE, DeclWithIssue); |
| } |
| |
| void checkCreateOrCopyFunction(const CallExpr *CE, const FunctionDecl *Callee, |
| const Decl *DeclWithIssue) const { |
| if (!isCreateOrCopyFunction(Callee)) |
| return; |
| |
| bool hasOutArgument = false; |
| unsigned ArgCount = CE->getNumArgs(); |
| for (unsigned ArgIndex = 0; ArgIndex < ArgCount; ++ArgIndex) { |
| auto *Arg = CE->getArg(ArgIndex)->IgnoreParenCasts(); |
| auto *Unary = dyn_cast<UnaryOperator>(Arg); |
| if (!Unary) |
| continue; |
| if (Unary->getOpcode() != UO_AddrOf) |
| continue; |
| auto *SubExpr = Unary->getSubExpr(); |
| if (!SubExpr) |
| continue; |
| auto *DRE = dyn_cast<DeclRefExpr>(SubExpr->IgnoreParenCasts()); |
| if (!DRE) |
| continue; |
| auto *Decl = DRE->getDecl(); |
| if (!Decl) |
| continue; |
| CreateOrCopyOutArguments.insert(Decl); |
| hasOutArgument = true; |
| } |
| if (!RTC.isUnretained(Callee->getReturnType())) |
| return; |
| if (!hasOutArgument && !CreateOrCopyFnCall.contains(CE)) |
| reportLeak(CE, DeclWithIssue); |
| } |
| |
| 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 (safeGetName(Cls) != "RetainPtr" || !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. |
| |
| 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); |
| } |
| |
| bool isAllocInit(const Expr *E) const { |
| auto *ObjCMsgExpr = dyn_cast<ObjCMessageExpr>(E); |
| if (auto *POE = dyn_cast<PseudoObjectExpr>(E)) { |
| if (unsigned ExprCount = POE->getNumSemanticExprs()) { |
| auto *Expr = POE->getSemanticExpr(ExprCount - 1)->IgnoreParenCasts(); |
| ObjCMsgExpr = dyn_cast<ObjCMessageExpr>(Expr); |
| } |
| } |
| 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")) |
| return false; |
| if (!ObjCMsgExpr->isInstanceMessage()) |
| return false; |
| auto *Receiver = ObjCMsgExpr->getInstanceReceiver()->IgnoreParenCasts(); |
| if (!Receiver) |
| return false; |
| if (auto *InnerObjCMsgExpr = dyn_cast<ObjCMessageExpr>(Receiver)) { |
| auto InnerSelector = InnerObjCMsgExpr->getSelector(); |
| return InnerSelector.getNameForSlot(0) == "alloc"; |
| } else if (auto *CE = dyn_cast<CallExpr>(Receiver)) { |
| if (auto *Callee = CE->getDirectCallee()) { |
| auto CalleeName = Callee->getName(); |
| return CalleeName.starts_with("alloc"); |
| } |
| } |
| return false; |
| } |
| |
| bool isCreateOrCopy(const Expr *E) const { |
| auto *CE = dyn_cast<CallExpr>(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<PseudoObjectExpr>(E)) { |
| if (unsigned SemanticExprCount = POE->getNumSemanticExprs()) { |
| E = POE->getSemanticExpr(SemanticExprCount - 1); |
| continue; |
| } |
| } |
| if (isa<CXXNullPtrLiteralExpr>(E)) |
| return IsOwnedResult::NotOwned; |
| if (auto *DRE = dyn_cast<DeclRefExpr>(E)) { |
| auto QT = DRE->getType(); |
| if (isRetainPtrType(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<CXXBindTemporaryExpr>(E)) { |
| E = TE->getSubExpr(); |
| continue; |
| } |
| if (auto *ObjCMsgExpr = dyn_cast<ObjCMessageExpr>(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<CXXMemberCallExpr>(E)) { |
| if (auto *MD = CXXCE->getMethodDecl()) { |
| auto *Cls = MD->getParent(); |
| if (auto *CD = dyn_cast<CXXConversionDecl>(MD)) { |
| auto QT = CD->getConversionType().getCanonicalType(); |
| auto *ResultType = QT.getTypePtrOrNull(); |
| if (safeGetName(Cls) == "RetainPtr" && ResultType && |
| (ResultType->isPointerType() || ResultType->isReferenceType() || |
| ResultType->isObjCObjectPointerType())) |
| return IsOwnedResult::NotOwned; |
| } |
| if (safeGetName(MD) == "leakRef" && safeGetName(Cls) == "RetainPtr") |
| return IsOwnedResult::Owned; |
| } |
| } |
| if (auto *CE = dyn_cast<CallExpr>(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 (isRetainPtrType(RetType)) |
| return IsOwnedResult::NotOwned; |
| if (isCreateOrCopyFunction(Callee)) { |
| CreateOrCopyFnCall.insert(CE); |
| return IsOwnedResult::Owned; |
| } |
| } else if (auto *CalleeExpr = CE->getCallee()) { |
| if (isa<CXXDependentScopeMemberExpr>(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(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<BasicBugReport>(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<BasicBugReport>(Bug, Os.str(), BSLoc); |
| Report->addRange(CE->getSourceRange()); |
| Report->setDeclWithIssue(DeclWithIssue); |
| BR->emitReport(std::move(Report)); |
| } |
| |
| void reportLeak(const CallExpr *CE, 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(CE->getSourceRange().getBegin(), |
| BR->getSourceManager()); |
| auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc); |
| Report->addRange(CE->getSourceRange()); |
| Report->setDeclWithIssue(DeclWithIssue); |
| BR->emitReport(std::move(Report)); |
| } |
| }; |
| } // namespace |
| |
| void ento::registerRetainPtrCtorAdoptChecker(CheckerManager &Mgr) { |
| Mgr.registerChecker<RetainPtrCtorAdoptChecker>(); |
| } |
| |
| bool ento::shouldRegisterRetainPtrCtorAdoptChecker(const CheckerManager &mgr) { |
| return true; |
| } |