//=======- 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 <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 (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<RetainSummaryManager>(
        TUD->getASTContext(), true /* trackObjCAndCFObjects */,
        false /* trackOSObjects */);
    RTC.visitTranslationUnitDecl(TUD);
    visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(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<UnresolvedLookupExpr>(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<DeclRefExpr>(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<ObjCMessageExpr>(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<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;
      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<CFReturnsRetainedAttr>())
          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<CallExpr>(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;
    auto *LHS = BO->getLHS();
    auto *RHS = BO->getRHS()->IgnoreParenCasts();
    if (isa<ObjCIvarRefExpr>(LHS)) {
      const Expr *Inner = nullptr;
      if (isAllocInit(RHS, &Inner)) {
        CreateOrCopyFnCall.insert(RHS);
        if (Inner)
          CreateOrCopyFnCall.insert(Inner);
      }
      return;
    }
    auto *UO = dyn_cast<UnaryOperator>(LHS);
    if (!UO)
      return;
    auto OpCode = UO->getOpcode();
    if (OpCode != UO_Deref)
      return;
    auto *DerefTarget = UO->getSubExpr();
    if (!DerefTarget)
      return;
    DerefTarget = DerefTarget->IgnoreParenCasts();
    auto *DRE = dyn_cast<DeclRefExpr>(DerefTarget);
    if (!DRE)
      return;
    auto *Decl = DRE->getDecl();
    if (!Decl)
      return;
    if (!isa<ParmVarDecl>(Decl) || !isCreateOrCopy(RHS))
      return;
    if (Decl->hasAttr<CFReturnsRetainedAttr>())
      CreateOrCopyFnCall.insert(RHS);
  }

  void visitReturnStmt(const ReturnStmt *RS, const Decl *DeclWithIssue) const {
    if (!DeclWithIssue)
      return;
    auto *RetValue = RS->getRetValue();
    if (!RetValue)
      return;
    RetValue = RetValue->IgnoreParenCasts();
    std::optional<bool> retainsRet;
    if (auto *FnDecl = dyn_cast<FunctionDecl>(DeclWithIssue))
      retainsRet = retainsReturnValue(FnDecl);
    else if (auto *MethodDecl = dyn_cast<ObjCMethodDecl>(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 (retainsRet && *retainsRet) {
      CreateOrCopyFnCall.insert(RetValue);
      return;
    }
    if (auto *CE = dyn_cast<CallExpr>(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 <typename CallableType>
  std::optional<bool> 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 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 (isNullPtr(E))
        return IsOwnedResult::NotOwned;
      if (auto *DRE = dyn_cast<DeclRefExpr>(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<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 (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<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 (isRetainPtrOrOSPtrType(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.
          if (isa<UnresolvedLookupExpr>(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<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));
  }

  template <typename ExprType>
  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<BasicBugReport>(Bug, Os.str(), BSLoc);
    Report->addRange(E->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;
}
