blob: 1d41edd2083ee942d17a88e34e10c511c8b8e34d [file] [log] [blame]
//===--- DefinitionsInHeadersCheck.cpp - clang-tidy------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#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.getBeginLoc(), 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<NamedDecl>("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<FunctionDecl>(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<CXXMethodDecl>(FD)) {
const auto *DC = MD->getDeclContext();
while (DC->isRecord()) {
if (const auto *RD = dyn_cast<CXXRecordDecl>(DC)) {
if (isa<ClassTemplatePartialSpecializationDecl>(RD))
return;
if (RD->getDescribedClassTemplate())
return;
}
DC = DC->getParent();
}
}
bool IsFullSpec = 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")
<< IsFullSpec << FD;
diag(FD->getLocation(), /*FixDescription=*/"make as 'inline'",
DiagnosticIDs::Note)
<< FixItHint::CreateInsertion(FD->getInnerLocStart(), "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;
// 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