//===--- DefinitionsInHeadersCheck.cpp - clang-tidy------------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "DefinitionsInHeadersCheck.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" using namespace clang::ast_matchers; namespace clang { namespace tidy { namespace misc { namespace { AST_MATCHER_P(NamedDecl, usesHeaderFileExtension, utils::HeaderFileExtensionsSet, HeaderFileExtensions) { return utils::isExpansionLocInHeaderFile( Node.getLocStart(), Finder->getASTContext().getSourceManager(), HeaderFileExtensions); } } // namespace DefinitionsInHeadersCheck::DefinitionsInHeadersCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), UseHeaderFileExtension(Options.get("UseHeaderFileExtension", true)), RawStringHeaderFileExtensions(Options.getLocalOrGlobal( "HeaderFileExtensions", utils::defaultHeaderFileExtensions())) { if (!utils::parseHeaderFileExtensions(RawStringHeaderFileExtensions, HeaderFileExtensions, ',')) { // FIXME: Find a more suitable way to handle invalid configuration // options. llvm::errs() << "Invalid header file extension: " << RawStringHeaderFileExtensions << "\n"; } } void DefinitionsInHeadersCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "UseHeaderFileExtension", UseHeaderFileExtension); Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions); } void DefinitionsInHeadersCheck::registerMatchers(MatchFinder *Finder) { if (!getLangOpts().CPlusPlus) return; auto DefinitionMatcher = anyOf(functionDecl(isDefinition(), unless(isDeleted())), varDecl(isDefinition())); if (UseHeaderFileExtension) { Finder->addMatcher(namedDecl(DefinitionMatcher, usesHeaderFileExtension(HeaderFileExtensions)) .bind("name-decl"), this); } else { Finder->addMatcher( namedDecl(DefinitionMatcher, anyOf(usesHeaderFileExtension(HeaderFileExtensions), unless(isExpansionInMainFile()))) .bind("name-decl"), this); } } void DefinitionsInHeadersCheck::check(const MatchFinder::MatchResult &Result) { // Don't run the check in failing TUs. if (Result.Context->getDiagnostics().hasUncompilableErrorOccurred()) return; // C++ [basic.def.odr] p6: // There can be more than one definition of a class type, enumeration type, // inline function with external linkage, class template, non-static function // template, static data member of a class template, member function of a // class template, or template specialization for which some template // parameters are not specifiedin a program provided that each definition // appears in a different translation unit, and provided the definitions // satisfy the following requirements. const auto *ND = Result.Nodes.getNodeAs("name-decl"); assert(ND); if (ND->isInvalidDecl()) return; // Internal linkage variable definitions are ignored for now: // const int a = 1; // static int b = 1; // // Although these might also cause ODR violations, we can be less certain and // should try to keep the false-positive rate down. // // FIXME: Should declarations in anonymous namespaces get the same treatment // as static / const declarations? if (!ND->hasExternalFormalLinkage() && !ND->isInAnonymousNamespace()) return; if (const auto *FD = dyn_cast(ND)) { // Inline functions are allowed. if (FD->isInlined()) return; // Function templates are allowed. if (FD->getTemplatedKind() == FunctionDecl::TK_FunctionTemplate) return; // Ignore instantiated functions. if (FD->isTemplateInstantiation()) return; // Member function of a class template and member function of a nested class // in a class template are allowed. if (const auto *MD = dyn_cast(FD)) { const auto *DC = MD->getDeclContext(); while (DC->isRecord()) { if (const auto *RD = dyn_cast(DC)) { if (isa(RD)) return; if (RD->getDescribedClassTemplate()) return; } DC = DC->getParent(); } } bool is_full_spec = FD->getTemplateSpecializationKind() != TSK_Undeclared; diag(FD->getLocation(), "%select{function|full function template specialization}0 %1 defined " "in a header file; function definitions in header files can lead to " "ODR violations") << is_full_spec << FD << FixItHint::CreateInsertion( FD->getReturnTypeSourceRange().getBegin(), "inline "); } else if (const auto *VD = dyn_cast(ND)) { // Static data members of a class template are allowed. if (VD->getDeclContext()->isDependentContext() && VD->isStaticDataMember()) return; // Ignore instantiated static data members of classes. if (isTemplateInstantiation(VD->getTemplateSpecializationKind())) return; // Ignore variable definition within function scope. if (VD->hasLocalStorage() || VD->isStaticLocal()) return; // Ignore inline variables. if (VD->isInline()) return; diag(VD->getLocation(), "variable %0 defined in a header file; " "variable definitions in header files can lead to ODR violations") << VD; } } } // namespace misc } // namespace tidy } // namespace clang