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