llvm-project/clang-tools-extra/clang-tidy/misc/DefinitionsInHeadersCheck.cpp

127 lines
4.7 KiB
C++

//===--- 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(NamedDecl, isHeaderFileExtension) {
SourceManager& SM = Finder->getASTContext().getSourceManager();
SourceLocation ExpansionLoc = SM.getExpansionLoc(Node.getLocStart());
StringRef Filename = SM.getFilename(ExpansionLoc);
return Filename.endswith(".h") || Filename.endswith(".hh") ||
Filename.endswith(".hpp") || Filename.endswith(".hxx") ||
llvm::sys::path::extension(Filename).empty();
}
} // namespace
DefinitionsInHeadersCheck::DefinitionsInHeadersCheck(
StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
UseHeaderFileExtension(Options.get("UseHeaderFileExtension", true)) {}
void DefinitionsInHeadersCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "UseHeaderFileExtension", UseHeaderFileExtension);
}
void DefinitionsInHeadersCheck::registerMatchers(MatchFinder *Finder) {
if (UseHeaderFileExtension) {
Finder->addMatcher(
namedDecl(anyOf(functionDecl(isDefinition()), varDecl(isDefinition())),
isHeaderFileExtension()).bind("name-decl"),
this);
} else {
Finder->addMatcher(
namedDecl(anyOf(functionDecl(isDefinition()), varDecl(isDefinition())),
anyOf(isHeaderFileExtension(),
unless(isExpansionInMainFile()))).bind("name-decl"),
this);
}
}
void DefinitionsInHeadersCheck::check(const MatchFinder::MatchResult &Result) {
// 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<NamedDecl>("name-decl");
assert(ND);
// 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.
if (ND->getLinkageInternal() == InternalLinkage)
return;
if (const auto *FD = dyn_cast<FunctionDecl>(ND)) {
// Inline functions are allowed.
if (FD->isInlined())
return;
// Function templates are allowed.
if (FD->getTemplatedKind() == FunctionDecl::TK_FunctionTemplate)
return;
// Function template full specialization is prohibited in header file.
if (FD->getTemplateSpecializationKind() == TSK_ImplicitInstantiation)
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<CXXMethodDecl>(FD)) {
const auto *DC = MD->getDeclContext();
while (DC->isRecord()) {
if (const auto *RD = dyn_cast<CXXRecordDecl>(DC))
if (RD->getDescribedClassTemplate())
return;
DC = DC->getParent();
}
}
diag(FD->getLocation(),
"function '%0' defined in a header file; "
"function definitions in header files can lead to ODR violations")
<< FD->getNameInfo().getName().getAsString()
<< FixItHint::CreateInsertion(FD->getSourceRange().getBegin(),
"inline ");
} else if (const auto *VD = dyn_cast<VarDecl>(ND)) {
// Static data members of a class template are allowed.
if (VD->getDeclContext()->isDependentContext() && VD->isStaticDataMember())
return;
if (VD->getTemplateSpecializationKind() == TSK_ImplicitInstantiation)
return;
// Ignore variable definition within function scope.
if (VD->hasLocalStorage() || VD->isStaticLocal())
return;
diag(VD->getLocation(),
"variable '%0' defined in a header file; "
"variable definitions in header files can lead to ODR violations")
<< VD->getName();
}
}
} // namespace misc
} // namespace tidy
} // namespace clang