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