diff options
author | Michael Wyman <michael@mwyman.com> | 2020-04-05 00:28:34 -0700 |
---|---|---|
committer | Michael Wyman <michael@mwyman.com> | 2020-04-10 08:51:21 -0700 |
commit | 89f1321fe4ef203a4674213280b430a274dc2001 (patch) | |
tree | ae506c8975d8bf93c05602674e75af4b8cbfe6ef /clang-tools-extra/clang-tidy | |
parent | 65b8b643b4ba75a2712ddc210aeaa7c6527f3bb4 (diff) | |
download | llvm-89f1321fe4ef203a4674213280b430a274dc2001.zip llvm-89f1321fe4ef203a4674213280b430a274dc2001.tar.gz llvm-89f1321fe4ef203a4674213280b430a274dc2001.tar.bz2 |
[clang-tidy] Add check to find calls to NSInvocation methods under ARC that don't have proper object argument lifetimes.
Summary: This check is similar to an ARC Migration check that warned about this incorrect usage under ARC, but most projects are no longer undergoing migration from pre-ARC code. The documentation for NSInvocation is not explicit about these requirements and incorrect usage has been found in many of our projects.
Reviewers: stephanemoore, benhamilton, dmaclach, alexfh, aaron.ballman, hokein, njames93
Reviewed By: stephanemoore, benhamilton, aaron.ballman
Subscribers: xazax.hun, Eugene.Zelenko, mgorny, cfe-commits
Tags: #clang, #clang-tools-extra
Differential Revision: https://reviews.llvm.org/D77571
Diffstat (limited to 'clang-tools-extra/clang-tidy')
4 files changed, 189 insertions, 0 deletions
diff --git a/clang-tools-extra/clang-tidy/objc/CMakeLists.txt b/clang-tools-extra/clang-tidy/objc/CMakeLists.txt index 9b010f0..5c6c505 100644 --- a/clang-tools-extra/clang-tidy/objc/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/objc/CMakeLists.txt @@ -8,6 +8,7 @@ add_clang_library(clangTidyObjCModule DeallocInCategoryCheck.cpp ForbiddenSubclassingCheck.cpp MissingHashCheck.cpp + NSInvocationArgumentLifetimeCheck.cpp ObjCTidyModule.cpp PropertyDeclarationCheck.cpp SuperSelfCheck.cpp diff --git a/clang-tools-extra/clang-tidy/objc/NSInvocationArgumentLifetimeCheck.cpp b/clang-tools-extra/clang-tidy/objc/NSInvocationArgumentLifetimeCheck.cpp new file mode 100644 index 0000000..520bdc3 --- /dev/null +++ b/clang-tools-extra/clang-tidy/objc/NSInvocationArgumentLifetimeCheck.cpp @@ -0,0 +1,146 @@ +//===--- 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 diff --git a/clang-tools-extra/clang-tidy/objc/NSInvocationArgumentLifetimeCheck.h b/clang-tools-extra/clang-tidy/objc/NSInvocationArgumentLifetimeCheck.h new file mode 100644 index 0000000..6d0c30c --- /dev/null +++ b/clang-tools-extra/clang-tidy/objc/NSInvocationArgumentLifetimeCheck.h @@ -0,0 +1,39 @@ +//===--- NSInvocationArgumentLifetimeCheck.h - clang-tidy -------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_OBJC_NSINVOCATIONARGUMENTLIFETIMECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_OBJC_NSINVOCATIONARGUMENTLIFETIMECHECK_H + +#include "../ClangTidyCheck.h" +#include "clang/Basic/LangStandard.h" + +namespace clang { +namespace tidy { +namespace objc { + +/// Finds calls to NSInvocation methods under ARC that don't have proper +/// argument object lifetimes. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/objc-nsinvocation-argument-lifetime.html +class NSInvocationArgumentLifetimeCheck : public ClangTidyCheck { +public: + NSInvocationArgumentLifetimeCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.ObjC && LangOpts.ObjCAutoRefCount; + } + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace objc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_OBJC_NSINVOCATIONARGUMENTLIFETIMECHECK_H diff --git a/clang-tools-extra/clang-tidy/objc/ObjCTidyModule.cpp b/clang-tools-extra/clang-tidy/objc/ObjCTidyModule.cpp index ff220b8..56ab0ab 100644 --- a/clang-tools-extra/clang-tidy/objc/ObjCTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/objc/ObjCTidyModule.cpp @@ -13,6 +13,7 @@ #include "DeallocInCategoryCheck.h" #include "ForbiddenSubclassingCheck.h" #include "MissingHashCheck.h" +#include "NSInvocationArgumentLifetimeCheck.h" #include "PropertyDeclarationCheck.h" #include "SuperSelfCheck.h" @@ -33,6 +34,8 @@ public: "objc-forbidden-subclassing"); CheckFactories.registerCheck<MissingHashCheck>( "objc-missing-hash"); + CheckFactories.registerCheck<NSInvocationArgumentLifetimeCheck>( + "objc-nsinvocation-argument-lifetime"); CheckFactories.registerCheck<PropertyDeclarationCheck>( "objc-property-declaration"); CheckFactories.registerCheck<SuperSelfCheck>( |