|
| 1 | +//===--- OverrideWithDifferentVisibilityCheck.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 "OverrideWithDifferentVisibilityCheck.h" |
| 10 | +#include "../utils/Matchers.h" |
| 11 | +#include "../utils/OptionsUtils.h" |
| 12 | +#include "clang/ASTMatchers/ASTMatchFinder.h" |
| 13 | + |
| 14 | +using namespace clang::ast_matchers; |
| 15 | +using namespace clang; |
| 16 | + |
| 17 | +namespace { |
| 18 | + |
| 19 | +AST_MATCHER(NamedDecl, isOperatorDecl) { |
| 20 | + DeclarationName::NameKind const NK = Node.getDeclName().getNameKind(); |
| 21 | + return NK != DeclarationName::Identifier && |
| 22 | + NK != DeclarationName::CXXConstructorName && |
| 23 | + NK != DeclarationName::CXXDestructorName; |
| 24 | +} |
| 25 | + |
| 26 | +} // namespace |
| 27 | + |
| 28 | +namespace clang::tidy { |
| 29 | + |
| 30 | +template <> |
| 31 | +struct OptionEnumMapping< |
| 32 | + misc::OverrideWithDifferentVisibilityCheck::ChangeKind> { |
| 33 | + static llvm::ArrayRef<std::pair< |
| 34 | + misc::OverrideWithDifferentVisibilityCheck::ChangeKind, StringRef>> |
| 35 | + getEnumMapping() { |
| 36 | + static constexpr std::pair< |
| 37 | + misc::OverrideWithDifferentVisibilityCheck::ChangeKind, StringRef> |
| 38 | + Mapping[] = { |
| 39 | + {misc::OverrideWithDifferentVisibilityCheck::ChangeKind::Any, |
| 40 | + "any"}, |
| 41 | + {misc::OverrideWithDifferentVisibilityCheck::ChangeKind::Widening, |
| 42 | + "widening"}, |
| 43 | + {misc::OverrideWithDifferentVisibilityCheck::ChangeKind::Narrowing, |
| 44 | + "narrowing"}, |
| 45 | + }; |
| 46 | + return {Mapping}; |
| 47 | + } |
| 48 | +}; |
| 49 | + |
| 50 | +namespace misc { |
| 51 | + |
| 52 | +OverrideWithDifferentVisibilityCheck::OverrideWithDifferentVisibilityCheck( |
| 53 | + StringRef Name, ClangTidyContext *Context) |
| 54 | + : ClangTidyCheck(Name, Context), |
| 55 | + DetectVisibilityChange( |
| 56 | + Options.get("DisallowedVisibilityChange", ChangeKind::Any)), |
| 57 | + CheckDestructors(Options.get("CheckDestructors", false)), |
| 58 | + CheckOperators(Options.get("CheckOperators", false)), |
| 59 | + IgnoredFunctions(utils::options::parseStringList( |
| 60 | + Options.get("IgnoredFunctions", ""))) {} |
| 61 | + |
| 62 | +void OverrideWithDifferentVisibilityCheck::storeOptions( |
| 63 | + ClangTidyOptions::OptionMap &Opts) { |
| 64 | + Options.store(Opts, "DisallowedVisibilityChange", DetectVisibilityChange); |
| 65 | + Options.store(Opts, "CheckDestructors", CheckDestructors); |
| 66 | + Options.store(Opts, "CheckOperators", CheckOperators); |
| 67 | + Options.store(Opts, "IgnoredFunctions", |
| 68 | + utils::options::serializeStringList(IgnoredFunctions)); |
| 69 | +} |
| 70 | + |
| 71 | +void OverrideWithDifferentVisibilityCheck::registerMatchers( |
| 72 | + MatchFinder *Finder) { |
| 73 | + const auto IgnoredDecl = |
| 74 | + namedDecl(matchers::matchesAnyListedName(IgnoredFunctions)); |
| 75 | + const auto FilterDestructors = |
| 76 | + CheckDestructors ? decl() : decl(unless(cxxDestructorDecl())); |
| 77 | + const auto FilterOperators = |
| 78 | + CheckOperators ? namedDecl() : namedDecl(unless(isOperatorDecl())); |
| 79 | + Finder->addMatcher( |
| 80 | + cxxMethodDecl( |
| 81 | + isVirtual(), FilterDestructors, FilterOperators, |
| 82 | + ofClass( |
| 83 | + cxxRecordDecl(unless(isExpansionInSystemHeader())).bind("class")), |
| 84 | + forEachOverridden(cxxMethodDecl(ofClass(cxxRecordDecl().bind("base")), |
| 85 | + unless(IgnoredDecl)) |
| 86 | + .bind("base_func"))) |
| 87 | + .bind("func"), |
| 88 | + this); |
| 89 | +} |
| 90 | + |
| 91 | +void OverrideWithDifferentVisibilityCheck::check( |
| 92 | + const MatchFinder::MatchResult &Result) { |
| 93 | + const auto *const MatchedFunction = |
| 94 | + Result.Nodes.getNodeAs<FunctionDecl>("func"); |
| 95 | + if (!MatchedFunction->isCanonicalDecl()) |
| 96 | + return; |
| 97 | + |
| 98 | + const auto *const ParentClass = |
| 99 | + Result.Nodes.getNodeAs<CXXRecordDecl>("class"); |
| 100 | + const auto *const BaseClass = Result.Nodes.getNodeAs<CXXRecordDecl>("base"); |
| 101 | + CXXBasePaths Paths; |
| 102 | + if (!ParentClass->isDerivedFrom(BaseClass, Paths)) |
| 103 | + return; |
| 104 | + |
| 105 | + const auto *const OverriddenFunction = |
| 106 | + Result.Nodes.getNodeAs<FunctionDecl>("base_func"); |
| 107 | + AccessSpecifier const ActualAccess = MatchedFunction->getAccess(); |
| 108 | + AccessSpecifier OverriddenAccess = OverriddenFunction->getAccess(); |
| 109 | + |
| 110 | + const CXXBaseSpecifier *InheritanceWithStrictVisibility = nullptr; |
| 111 | + for (const CXXBasePath &Path : Paths) { |
| 112 | + for (const CXXBasePathElement &Elem : Path) { |
| 113 | + if (Elem.Base->getAccessSpecifier() > OverriddenAccess) { |
| 114 | + OverriddenAccess = Elem.Base->getAccessSpecifier(); |
| 115 | + InheritanceWithStrictVisibility = Elem.Base; |
| 116 | + } |
| 117 | + } |
| 118 | + } |
| 119 | + |
| 120 | + if (ActualAccess != OverriddenAccess) { |
| 121 | + if (DetectVisibilityChange == ChangeKind::Widening && |
| 122 | + ActualAccess > OverriddenAccess) |
| 123 | + return; |
| 124 | + if (DetectVisibilityChange == ChangeKind::Narrowing && |
| 125 | + ActualAccess < OverriddenAccess) |
| 126 | + return; |
| 127 | + |
| 128 | + if (InheritanceWithStrictVisibility) { |
| 129 | + diag(MatchedFunction->getLocation(), |
| 130 | + "visibility of function %0 is changed from %1 (through %1 " |
| 131 | + "inheritance of class %2) to %3") |
| 132 | + << MatchedFunction << OverriddenAccess |
| 133 | + << InheritanceWithStrictVisibility->getType() << ActualAccess; |
| 134 | + diag(InheritanceWithStrictVisibility->getBeginLoc(), |
| 135 | + "%0 is inherited as %1 here", DiagnosticIDs::Note) |
| 136 | + << InheritanceWithStrictVisibility->getType() << OverriddenAccess; |
| 137 | + } else { |
| 138 | + diag(MatchedFunction->getLocation(), |
| 139 | + "visibility of function %0 is changed from %1 in class %2 to %3") |
| 140 | + << MatchedFunction << OverriddenAccess << BaseClass << ActualAccess; |
| 141 | + } |
| 142 | + diag(OverriddenFunction->getLocation(), "function declared here as %0", |
| 143 | + DiagnosticIDs::Note) |
| 144 | + << OverriddenFunction->getAccess(); |
| 145 | + } |
| 146 | +} |
| 147 | + |
| 148 | +} // namespace misc |
| 149 | + |
| 150 | +} // namespace clang::tidy |
0 commit comments