147 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			147 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			C++
		
	
	
	
//===--- NSInvocationArgumentLifetimeCheck.cpp - clang-tidy ---------===//
 | 
						|
//
 | 
						|
// 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 "NSInvocationArgumentLifetimeCheck.h"
 | 
						|
#include "clang/AST/ASTContext.h"
 | 
						|
#include "clang/AST/Attrs.inc"
 | 
						|
#include "clang/AST/ComputeDependence.h"
 | 
						|
#include "clang/AST/Decl.h"
 | 
						|
#include "clang/AST/Expr.h"
 | 
						|
#include "clang/AST/ExprObjC.h"
 | 
						|
#include "clang/AST/Type.h"
 | 
						|
#include "clang/AST/TypeLoc.h"
 | 
						|
#include "clang/ASTMatchers/ASTMatchFinder.h"
 | 
						|
#include "clang/ASTMatchers/ASTMatchers.h"
 | 
						|
#include "clang/ASTMatchers/ASTMatchersMacros.h"
 | 
						|
#include "clang/Basic/Diagnostic.h"
 | 
						|
#include "clang/Basic/LLVM.h"
 | 
						|
#include "clang/Basic/LangOptions.h"
 | 
						|
#include "clang/Basic/SourceLocation.h"
 | 
						|
#include "clang/Basic/SourceManager.h"
 | 
						|
#include "llvm/ADT/None.h"
 | 
						|
#include "llvm/ADT/Optional.h"
 | 
						|
#include "llvm/ADT/StringRef.h"
 | 
						|
#include "llvm/Support/raw_ostream.h"
 | 
						|
 | 
						|
using namespace clang::ast_matchers;
 | 
						|
 | 
						|
namespace clang {
 | 
						|
namespace tidy {
 | 
						|
namespace objc {
 | 
						|
namespace {
 | 
						|
 | 
						|
static constexpr StringRef WeakText = "__weak";
 | 
						|
static constexpr StringRef StrongText = "__strong";
 | 
						|
static constexpr StringRef UnsafeUnretainedText = "__unsafe_unretained";
 | 
						|
 | 
						|
/// Matches ObjCIvarRefExpr, DeclRefExpr, or MemberExpr that reference
 | 
						|
/// Objective-C object (or block) variables or fields whose object lifetimes
 | 
						|
/// are not __unsafe_unretained.
 | 
						|
AST_POLYMORPHIC_MATCHER(isObjCManagedLifetime,
 | 
						|
                        AST_POLYMORPHIC_SUPPORTED_TYPES(ObjCIvarRefExpr,
 | 
						|
                                                        DeclRefExpr,
 | 
						|
                                                        MemberExpr)) {
 | 
						|
  QualType QT = Node.getType();
 | 
						|
  return QT->isScalarType() &&
 | 
						|
         (QT->getScalarTypeKind() == Type::STK_ObjCObjectPointer ||
 | 
						|
          QT->getScalarTypeKind() == Type::STK_BlockPointer) &&
 | 
						|
         QT.getQualifiers().getObjCLifetime() > Qualifiers::OCL_ExplicitNone;
 | 
						|
}
 | 
						|
 | 
						|
static llvm::Optional<FixItHint>
 | 
						|
fixItHintReplacementForOwnershipString(StringRef Text, CharSourceRange Range,
 | 
						|
                                       StringRef Ownership) {
 | 
						|
  size_t Index = Text.find(Ownership);
 | 
						|
  if (Index == StringRef::npos)
 | 
						|
    return llvm::None;
 | 
						|
 | 
						|
  SourceLocation Begin = Range.getBegin().getLocWithOffset(Index);
 | 
						|
  SourceLocation End = Begin.getLocWithOffset(Ownership.size());
 | 
						|
  return FixItHint::CreateReplacement(SourceRange(Begin, End),
 | 
						|
                                      UnsafeUnretainedText);
 | 
						|
}
 | 
						|
 | 
						|
static llvm::Optional<FixItHint>
 | 
						|
fixItHintForVarDecl(const VarDecl *VD, const SourceManager &SM,
 | 
						|
                    const LangOptions &LangOpts) {
 | 
						|
  assert(VD && "VarDecl parameter must not be null");
 | 
						|
  // Don't provide fix-its for any parameter variables at this time.
 | 
						|
  if (isa<ParmVarDecl>(VD))
 | 
						|
    return llvm::None;
 | 
						|
 | 
						|
  // Currently there is no way to directly get the source range for the
 | 
						|
  // __weak/__strong ObjC lifetime qualifiers, so it's necessary to string
 | 
						|
  // search in the source code.
 | 
						|
  CharSourceRange Range = Lexer::makeFileCharRange(
 | 
						|
      CharSourceRange::getTokenRange(VD->getSourceRange()), SM, LangOpts);
 | 
						|
  if (Range.isInvalid()) {
 | 
						|
    // An invalid range likely means inside a macro, in which case don't supply
 | 
						|
    // a fix-it.
 | 
						|
    return llvm::None;
 | 
						|
  }
 | 
						|
 | 
						|
  StringRef VarDeclText = Lexer::getSourceText(Range, SM, LangOpts);
 | 
						|
  if (llvm::Optional<FixItHint> Hint =
 | 
						|
          fixItHintReplacementForOwnershipString(VarDeclText, Range, WeakText))
 | 
						|
    return Hint;
 | 
						|
 | 
						|
  if (llvm::Optional<FixItHint> Hint = fixItHintReplacementForOwnershipString(
 | 
						|
          VarDeclText, Range, StrongText))
 | 
						|
    return Hint;
 | 
						|
 | 
						|
  return FixItHint::CreateInsertion(Range.getBegin(), "__unsafe_unretained ");
 | 
						|
}
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
void NSInvocationArgumentLifetimeCheck::registerMatchers(MatchFinder *Finder) {
 | 
						|
  Finder->addMatcher(
 | 
						|
      objcMessageExpr(
 | 
						|
          hasReceiverType(asString("NSInvocation *")),
 | 
						|
          anyOf(hasSelector("getArgument:atIndex:"),
 | 
						|
                hasSelector("getReturnValue:")),
 | 
						|
          hasArgument(
 | 
						|
              0, anyOf(hasDescendant(memberExpr(isObjCManagedLifetime())),
 | 
						|
                       hasDescendant(objcIvarRefExpr(isObjCManagedLifetime())),
 | 
						|
                       hasDescendant(
 | 
						|
                           // Reference to variables, but when dereferencing
 | 
						|
                           // to ivars/fields a more-descendent variable
 | 
						|
                           // reference (e.g. self) may match with strong
 | 
						|
                           // object lifetime, leading to an incorrect match.
 | 
						|
                           // Exclude these conditions.
 | 
						|
                           declRefExpr(to(varDecl().bind("var")),
 | 
						|
                                       unless(hasParent(implicitCastExpr())),
 | 
						|
                                       isObjCManagedLifetime())))))
 | 
						|
          .bind("call"),
 | 
						|
      this);
 | 
						|
}
 | 
						|
 | 
						|
void NSInvocationArgumentLifetimeCheck::check(
 | 
						|
    const MatchFinder::MatchResult &Result) {
 | 
						|
  const auto *MatchedExpr = Result.Nodes.getNodeAs<ObjCMessageExpr>("call");
 | 
						|
 | 
						|
  auto Diag = diag(MatchedExpr->getArg(0)->getBeginLoc(),
 | 
						|
                   "NSInvocation %objcinstance0 should only pass pointers to "
 | 
						|
                   "objects with ownership __unsafe_unretained")
 | 
						|
              << MatchedExpr->getSelector();
 | 
						|
 | 
						|
  // Only provide fix-it hints for references to local variables; fixes for
 | 
						|
  // instance variable references don't have as clear an automated fix.
 | 
						|
  const auto *VD = Result.Nodes.getNodeAs<VarDecl>("var");
 | 
						|
  if (!VD)
 | 
						|
    return;
 | 
						|
 | 
						|
  if (auto Hint = fixItHintForVarDecl(VD, *Result.SourceManager,
 | 
						|
                                      Result.Context->getLangOpts()))
 | 
						|
    Diag << *Hint;
 | 
						|
}
 | 
						|
 | 
						|
} // namespace objc
 | 
						|
} // namespace tidy
 | 
						|
} // namespace clang
 |