1 //===--- DefinitionsInHeadersCheck.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 "DefinitionsInHeadersCheck.h" 11 #include "clang/AST/ASTContext.h" 12 #include "clang/ASTMatchers/ASTMatchFinder.h" 13 14 using namespace clang::ast_matchers; 15 16 namespace clang { 17 namespace tidy { 18 namespace misc { 19 20 namespace { 21 22 AST_MATCHER_P(NamedDecl, usesHeaderFileExtension, 23 utils::HeaderFileExtensionsSet, HeaderFileExtensions) { 24 return utils::isExpansionLocInHeaderFile( 25 Node.getLocStart(), Finder->getASTContext().getSourceManager(), 26 HeaderFileExtensions); 27 } 28 29 } // namespace 30 31 DefinitionsInHeadersCheck::DefinitionsInHeadersCheck(StringRef Name, 32 ClangTidyContext *Context) 33 : ClangTidyCheck(Name, Context), 34 UseHeaderFileExtension(Options.get("UseHeaderFileExtension", true)), 35 RawStringHeaderFileExtensions(Options.getLocalOrGlobal( 36 "HeaderFileExtensions", utils::defaultHeaderFileExtensions())) { 37 if (!utils::parseHeaderFileExtensions(RawStringHeaderFileExtensions, 38 HeaderFileExtensions, ',')) { 39 // FIXME: Find a more suitable way to handle invalid configuration 40 // options. 41 llvm::errs() << "Invalid header file extension: " 42 << RawStringHeaderFileExtensions << "\n"; 43 } 44 } 45 46 void DefinitionsInHeadersCheck::storeOptions( 47 ClangTidyOptions::OptionMap &Opts) { 48 Options.store(Opts, "UseHeaderFileExtension", UseHeaderFileExtension); 49 Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions); 50 } 51 52 void DefinitionsInHeadersCheck::registerMatchers(MatchFinder *Finder) { 53 if (!getLangOpts().CPlusPlus) 54 return; 55 auto DefinitionMatcher = 56 anyOf(functionDecl(isDefinition(), unless(isDeleted())), 57 varDecl(isDefinition())); 58 if (UseHeaderFileExtension) { 59 Finder->addMatcher(namedDecl(DefinitionMatcher, 60 usesHeaderFileExtension(HeaderFileExtensions)) 61 .bind("name-decl"), 62 this); 63 } else { 64 Finder->addMatcher( 65 namedDecl(DefinitionMatcher, 66 anyOf(usesHeaderFileExtension(HeaderFileExtensions), 67 unless(isExpansionInMainFile()))) 68 .bind("name-decl"), 69 this); 70 } 71 } 72 73 void DefinitionsInHeadersCheck::check(const MatchFinder::MatchResult &Result) { 74 // Don't run the check in failing TUs. 75 if (Result.Context->getDiagnostics().hasUncompilableErrorOccurred()) 76 return; 77 78 // C++ [basic.def.odr] p6: 79 // There can be more than one definition of a class type, enumeration type, 80 // inline function with external linkage, class template, non-static function 81 // template, static data member of a class template, member function of a 82 // class template, or template specialization for which some template 83 // parameters are not specifiedin a program provided that each definition 84 // appears in a different translation unit, and provided the definitions 85 // satisfy the following requirements. 86 const auto *ND = Result.Nodes.getNodeAs<NamedDecl>("name-decl"); 87 assert(ND); 88 if (ND->isInvalidDecl()) 89 return; 90 91 // Internal linkage variable definitions are ignored for now: 92 // const int a = 1; 93 // static int b = 1; 94 // 95 // Although these might also cause ODR violations, we can be less certain and 96 // should try to keep the false-positive rate down. 97 // 98 // FIXME: Should declarations in anonymous namespaces get the same treatment 99 // as static / const declarations? 100 if (!ND->hasExternalFormalLinkage() && !ND->isInAnonymousNamespace()) 101 return; 102 103 if (const auto *FD = dyn_cast<FunctionDecl>(ND)) { 104 // Inline functions are allowed. 105 if (FD->isInlined()) 106 return; 107 // Function templates are allowed. 108 if (FD->getTemplatedKind() == FunctionDecl::TK_FunctionTemplate) 109 return; 110 // Ignore instantiated functions. 111 if (FD->isTemplateInstantiation()) 112 return; 113 // Member function of a class template and member function of a nested class 114 // in a class template are allowed. 115 if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) { 116 const auto *DC = MD->getDeclContext(); 117 while (DC->isRecord()) { 118 if (const auto *RD = dyn_cast<CXXRecordDecl>(DC)) { 119 if (isa<ClassTemplatePartialSpecializationDecl>(RD)) 120 return; 121 if (RD->getDescribedClassTemplate()) 122 return; 123 } 124 DC = DC->getParent(); 125 } 126 } 127 128 bool is_full_spec = FD->getTemplateSpecializationKind() != TSK_Undeclared; 129 diag(FD->getLocation(), 130 "%select{function|full function template specialization}0 %1 defined " 131 "in a header file; function definitions in header files can lead to " 132 "ODR violations") 133 << is_full_spec << FD << FixItHint::CreateInsertion( 134 FD->getReturnTypeSourceRange().getBegin(), "inline "); 135 } else if (const auto *VD = dyn_cast<VarDecl>(ND)) { 136 // Static data members of a class template are allowed. 137 if (VD->getDeclContext()->isDependentContext() && VD->isStaticDataMember()) 138 return; 139 // Ignore instantiated static data members of classes. 140 if (isTemplateInstantiation(VD->getTemplateSpecializationKind())) 141 return; 142 // Ignore variable definition within function scope. 143 if (VD->hasLocalStorage() || VD->isStaticLocal()) 144 return; 145 // Ignore inline variables. 146 if (VD->isInline()) 147 return; 148 149 diag(VD->getLocation(), 150 "variable %0 defined in a header file; " 151 "variable definitions in header files can lead to ODR violations") 152 << VD; 153 } 154 } 155 156 } // namespace misc 157 } // namespace tidy 158 } // namespace clang 159