forked from OSchip/llvm-project
158 lines
5.8 KiB
C++
158 lines
5.8 KiB
C++
//===--- 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 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<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
|