1 //===--- ForbiddenSubclassingCheck.cpp - clang-tidy -----------------------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "ForbiddenSubclassingCheck.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "llvm/ADT/Hashing.h"
14 #include "llvm/ADT/SmallVector.h"
15 #include "../utils/OptionsUtils.h"
16 
17 using namespace clang::ast_matchers;
18 
19 namespace clang {
20 namespace tidy {
21 namespace objc {
22 
23 namespace {
24 
25 constexpr char DefaultForbiddenSuperClassNames[] =
26     "ABNewPersonViewController;"
27     "ABPeoplePickerNavigationController;"
28     "ABPersonViewController;"
29     "ABUnknownPersonViewController;"
30     "NSHashTable;"
31     "NSMapTable;"
32     "NSPointerArray;"
33     "NSPointerFunctions;"
34     "NSTimer;"
35     "UIActionSheet;"
36     "UIAlertView;"
37     "UIImagePickerController;"
38     "UITextInputMode;"
39     "UIWebView";
40 
41 /// \brief Matches Objective-C classes that directly or indirectly
42 /// have a superclass matching \c Base.
43 ///
44 /// Note that a class is not considered to be a subclass of itself.
45 ///
46 /// Example matches Y, Z
47 /// (matcher = objcInterfaceDecl(hasName("X")))
48 /// \code
49 ///   @interface X
50 ///   @end
51 ///   @interface Y : X  // directly derived
52 ///   @end
53 ///   @interface Z : Y  // indirectly derived
54 ///   @end
55 /// \endcode
56 AST_MATCHER_P(ObjCInterfaceDecl, isSubclassOf,
57               ast_matchers::internal::Matcher<ObjCInterfaceDecl>, Base) {
58   for (const auto *SuperClass = Node.getSuperClass();
59        SuperClass != nullptr;
60        SuperClass = SuperClass->getSuperClass()) {
61     if (Base.matches(*SuperClass, Finder, Builder)) {
62       return true;
63     }
64   }
65   return false;
66 }
67 
68 } // namespace
69 
70 ForbiddenSubclassingCheck::ForbiddenSubclassingCheck(
71     StringRef Name,
72     ClangTidyContext *Context)
73     : ClangTidyCheck(Name, Context),
74       ForbiddenSuperClassNames(
75           utils::options::parseStringList(
76               Options.get("ClassNames", DefaultForbiddenSuperClassNames))) {
77 }
78 
79 void ForbiddenSubclassingCheck::registerMatchers(MatchFinder *Finder) {
80   Finder->addMatcher(
81       objcInterfaceDecl(
82           isSubclassOf(
83               objcInterfaceDecl(
84                   hasAnyName(
85                       std::vector<StringRef>(
86                           ForbiddenSuperClassNames.begin(),
87                           ForbiddenSuperClassNames.end())))
88               .bind("superclass")))
89       .bind("subclass"),
90       this);
91 }
92 
93 void ForbiddenSubclassingCheck::check(
94     const MatchFinder::MatchResult &Result) {
95   const auto *SubClass = Result.Nodes.getNodeAs<ObjCInterfaceDecl>(
96       "subclass");
97   assert(SubClass != nullptr);
98   const auto *SuperClass = Result.Nodes.getNodeAs<ObjCInterfaceDecl>(
99       "superclass");
100   assert(SuperClass != nullptr);
101   diag(SubClass->getLocation(),
102        "Objective-C interface %0 subclasses %1, which is not "
103        "intended to be subclassed")
104       << SubClass
105       << SuperClass;
106 }
107 
108 void ForbiddenSubclassingCheck::storeOptions(
109     ClangTidyOptions::OptionMap &Opts) {
110   Options.store(
111       Opts,
112       "ForbiddenSuperClassNames",
113       utils::options::serializeStringList(ForbiddenSuperClassNames));
114 }
115 
116 } // namespace objc
117 } // namespace tidy
118 } // namespace clang
119