llvm-project
143 строки · 5.7 Кб
1//===--- NSInvocationArgumentLifetimeCheck.cpp - clang-tidy ---------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "NSInvocationArgumentLifetimeCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/AST/ComputeDependence.h"12#include "clang/AST/Decl.h"13#include "clang/AST/Expr.h"14#include "clang/AST/ExprObjC.h"15#include "clang/AST/Type.h"16#include "clang/AST/TypeLoc.h"17#include "clang/ASTMatchers/ASTMatchFinder.h"18#include "clang/ASTMatchers/ASTMatchers.h"19#include "clang/ASTMatchers/ASTMatchersMacros.h"20#include "clang/Basic/Diagnostic.h"21#include "clang/Basic/LLVM.h"22#include "clang/Basic/LangOptions.h"23#include "clang/Basic/SourceLocation.h"24#include "clang/Basic/SourceManager.h"25#include "clang/Lex/Lexer.h"26#include "llvm/ADT/StringRef.h"27#include <optional>28
29using namespace clang::ast_matchers;30
31namespace clang::tidy::objc {32namespace {33
34static constexpr StringRef WeakText = "__weak";35static constexpr StringRef StrongText = "__strong";36static constexpr StringRef UnsafeUnretainedText = "__unsafe_unretained";37
38/// Matches ObjCIvarRefExpr, DeclRefExpr, or MemberExpr that reference
39/// Objective-C object (or block) variables or fields whose object lifetimes
40/// are not __unsafe_unretained.
41AST_POLYMORPHIC_MATCHER(isObjCManagedLifetime,42AST_POLYMORPHIC_SUPPORTED_TYPES(ObjCIvarRefExpr,43DeclRefExpr,44MemberExpr)) {45QualType QT = Node.getType();46return QT->isScalarType() &&47(QT->getScalarTypeKind() == Type::STK_ObjCObjectPointer ||48QT->getScalarTypeKind() == Type::STK_BlockPointer) &&49QT.getQualifiers().getObjCLifetime() > Qualifiers::OCL_ExplicitNone;50}
51
52static std::optional<FixItHint>53fixItHintReplacementForOwnershipString(StringRef Text, CharSourceRange Range,54StringRef Ownership) {55size_t Index = Text.find(Ownership);56if (Index == StringRef::npos)57return std::nullopt;58
59SourceLocation Begin = Range.getBegin().getLocWithOffset(Index);60SourceLocation End = Begin.getLocWithOffset(Ownership.size());61return FixItHint::CreateReplacement(SourceRange(Begin, End),62UnsafeUnretainedText);63}
64
65static std::optional<FixItHint>66fixItHintForVarDecl(const VarDecl *VD, const SourceManager &SM,67const LangOptions &LangOpts) {68assert(VD && "VarDecl parameter must not be null");69// Don't provide fix-its for any parameter variables at this time.70if (isa<ParmVarDecl>(VD))71return std::nullopt;72
73// Currently there is no way to directly get the source range for the74// __weak/__strong ObjC lifetime qualifiers, so it's necessary to string75// search in the source code.76CharSourceRange Range = Lexer::makeFileCharRange(77CharSourceRange::getTokenRange(VD->getSourceRange()), SM, LangOpts);78if (Range.isInvalid()) {79// An invalid range likely means inside a macro, in which case don't supply80// a fix-it.81return std::nullopt;82}83
84StringRef VarDeclText = Lexer::getSourceText(Range, SM, LangOpts);85if (std::optional<FixItHint> Hint =86fixItHintReplacementForOwnershipString(VarDeclText, Range, WeakText))87return Hint;88
89if (std::optional<FixItHint> Hint = fixItHintReplacementForOwnershipString(90VarDeclText, Range, StrongText))91return Hint;92
93return FixItHint::CreateInsertion(Range.getBegin(), "__unsafe_unretained ");94}
95
96} // namespace97
98void NSInvocationArgumentLifetimeCheck::registerMatchers(MatchFinder *Finder) {99Finder->addMatcher(100traverse(101TK_AsIs,102objcMessageExpr(103hasReceiverType(asString("NSInvocation *")),104anyOf(hasSelector("getArgument:atIndex:"),105hasSelector("getReturnValue:")),106hasArgument(1070,108anyOf(hasDescendant(memberExpr(isObjCManagedLifetime())),109hasDescendant(objcIvarRefExpr(isObjCManagedLifetime())),110hasDescendant(111// Reference to variables, but when dereferencing112// to ivars/fields a more-descendent variable113// reference (e.g. self) may match with strong114// object lifetime, leading to an incorrect match.115// Exclude these conditions.116declRefExpr(to(varDecl().bind("var")),117unless(hasParent(implicitCastExpr())),118isObjCManagedLifetime())))))119.bind("call")),120this);121}
122
123void NSInvocationArgumentLifetimeCheck::check(124const MatchFinder::MatchResult &Result) {125const auto *MatchedExpr = Result.Nodes.getNodeAs<ObjCMessageExpr>("call");126
127auto Diag = diag(MatchedExpr->getArg(0)->getBeginLoc(),128"NSInvocation %objcinstance0 should only pass pointers to "129"objects with ownership __unsafe_unretained")130<< MatchedExpr->getSelector();131
132// Only provide fix-it hints for references to local variables; fixes for133// instance variable references don't have as clear an automated fix.134const auto *VD = Result.Nodes.getNodeAs<VarDecl>("var");135if (!VD)136return;137
138if (auto Hint = fixItHintForVarDecl(VD, *Result.SourceManager,139Result.Context->getLangOpts()))140Diag << *Hint;141}
142
143} // namespace clang::tidy::objc144