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   // this check should only be applied to ObjC sources.
81   if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) {
82     return;
83   }
84   Finder->addMatcher(
85       objcInterfaceDecl(
86           isSubclassOf(
87               objcInterfaceDecl(
88                   hasAnyName(
89                       std::vector<StringRef>(
90                           ForbiddenSuperClassNames.begin(),
91                           ForbiddenSuperClassNames.end())))
92               .bind("superclass")))
93       .bind("subclass"),
94       this);
95 }
96 
97 void ForbiddenSubclassingCheck::check(
98     const MatchFinder::MatchResult &Result) {
99   const auto *SubClass = Result.Nodes.getNodeAs<ObjCInterfaceDecl>(
100       "subclass");
101   assert(SubClass != nullptr);
102   const auto *SuperClass = Result.Nodes.getNodeAs<ObjCInterfaceDecl>(
103       "superclass");
104   assert(SuperClass != nullptr);
105   diag(SubClass->getLocation(),
106        "Objective-C interface %0 subclasses %1, which is not "
107        "intended to be subclassed")
108       << SubClass
109       << SuperClass;
110 }
111 
112 void ForbiddenSubclassingCheck::storeOptions(
113     ClangTidyOptions::OptionMap &Opts) {
114   Options.store(
115       Opts,
116       "ForbiddenSuperClassNames",
117       utils::options::serializeStringList(ForbiddenSuperClassNames));
118 }
119 
120 } // namespace objc
121 } // namespace tidy
122 } // namespace clang
123