clang-tools 17.0.0git
DefinitionsInHeadersCheck.cpp
Go to the documentation of this file.
1//===--- DefinitionsInHeadersCheck.cpp - clang-tidy------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10#include "clang/AST/ASTContext.h"
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12
13using namespace clang::ast_matchers;
14
15namespace clang::tidy::misc {
16
17namespace {
18
19AST_MATCHER_P(NamedDecl, usesHeaderFileExtension, FileExtensionsSet,
20 HeaderFileExtensions) {
22 Node.getBeginLoc(), Finder->getASTContext().getSourceManager(),
23 HeaderFileExtensions);
24}
25
26} // namespace
27
29 ClangTidyContext *Context)
30 : ClangTidyCheck(Name, Context),
31 UseHeaderFileExtension(Options.get("UseHeaderFileExtension", true)) {
32 std::optional<StringRef> HeaderFileExtensionsOption =
33 Options.get("HeaderFileExtensions");
34 RawStringHeaderFileExtensions =
35 HeaderFileExtensionsOption.value_or(utils::defaultHeaderFileExtensions());
36 if (HeaderFileExtensionsOption) {
37 if (!utils::parseFileExtensions(RawStringHeaderFileExtensions,
38 HeaderFileExtensions,
40 this->configurationDiag("Invalid header file extension: '%0'")
41 << RawStringHeaderFileExtensions;
42 }
43 } else
44 HeaderFileExtensions = Context->getHeaderFileExtensions();
45}
46
49 Options.store(Opts, "UseHeaderFileExtension", UseHeaderFileExtension);
50 Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions);
51}
52
54 auto DefinitionMatcher =
55 anyOf(functionDecl(isDefinition(), unless(isDeleted())),
56 varDecl(isDefinition()));
57 if (UseHeaderFileExtension) {
58 Finder->addMatcher(namedDecl(DefinitionMatcher,
59 usesHeaderFileExtension(HeaderFileExtensions))
60 .bind("name-decl"),
61 this);
62 } else {
63 Finder->addMatcher(
64 namedDecl(DefinitionMatcher,
65 anyOf(usesHeaderFileExtension(HeaderFileExtensions),
66 unless(isExpansionInMainFile())))
67 .bind("name-decl"),
68 this);
69 }
70}
71
72void DefinitionsInHeadersCheck::check(const MatchFinder::MatchResult &Result) {
73 // Don't run the check in failing TUs.
74 if (Result.Context->getDiagnostics().hasUncompilableErrorOccurred())
75 return;
76
77 // C++ [basic.def.odr] p6:
78 // There can be more than one definition of a class type, enumeration type,
79 // inline function with external linkage, class template, non-static function
80 // template, static data member of a class template, member function of a
81 // class template, or template specialization for which some template
82 // parameters are not specifiedin a program provided that each definition
83 // appears in a different translation unit, and provided the definitions
84 // satisfy the following requirements.
85 const auto *ND = Result.Nodes.getNodeAs<NamedDecl>("name-decl");
86 assert(ND);
87 if (ND->isInvalidDecl())
88 return;
89
90 // Internal linkage variable definitions are ignored for now:
91 // const int a = 1;
92 // static int b = 1;
93 // namespace { int c = 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 if (!ND->hasExternalFormalLinkage() || ND->isInAnonymousNamespace())
98 return;
99
100 if (const auto *FD = dyn_cast<FunctionDecl>(ND)) {
101 // Inline functions are allowed.
102 if (FD->isInlined())
103 return;
104 // Function templates are allowed.
105 if (FD->getTemplatedKind() == FunctionDecl::TK_FunctionTemplate)
106 return;
107 // Ignore instantiated functions.
108 if (FD->isTemplateInstantiation())
109 return;
110 // Member function of a class template and member function of a nested class
111 // in a class template are allowed.
112 if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
113 const auto *DC = MD->getDeclContext();
114 while (DC->isRecord()) {
115 if (const auto *RD = dyn_cast<CXXRecordDecl>(DC)) {
116 if (isa<ClassTemplatePartialSpecializationDecl>(RD))
117 return;
118 if (RD->getDescribedClassTemplate())
119 return;
120 }
121 DC = DC->getParent();
122 }
123 }
124
125 bool IsFullSpec = FD->getTemplateSpecializationKind() != TSK_Undeclared;
126 diag(FD->getLocation(),
127 "%select{function|full function template specialization}0 %1 defined "
128 "in a header file; function definitions in header files can lead to "
129 "ODR violations")
130 << IsFullSpec << FD;
131 // inline is not allowed for main function.
132 if (FD->isMain())
133 return;
134 diag(FD->getLocation(), /*Description=*/"make as 'inline'",
135 DiagnosticIDs::Note)
136 << FixItHint::CreateInsertion(FD->getInnerLocStart(), "inline ");
137 } else if (const auto *VD = dyn_cast<VarDecl>(ND)) {
138 // C++14 variable templates are allowed.
139 if (VD->getDescribedVarTemplate())
140 return;
141 // Static data members of a class template are allowed.
142 if (VD->getDeclContext()->isDependentContext() && VD->isStaticDataMember())
143 return;
144 // Ignore instantiated static data members of classes.
145 if (isTemplateInstantiation(VD->getTemplateSpecializationKind()))
146 return;
147 // Ignore variable definition within function scope.
148 if (VD->hasLocalStorage() || VD->isStaticLocal())
149 return;
150 // Ignore inline variables.
151 if (VD->isInline())
152 return;
153 // Ignore partial specializations.
154 if (isa<VarTemplatePartialSpecializationDecl>(VD))
155 return;
156
157 diag(VD->getLocation(),
158 "variable %0 defined in a header file; "
159 "variable definitions in header files can lead to ODR violations")
160 << VD;
161 }
162}
163
164} // namespace clang::tidy::misc
Token Name
std::optional< StringRef > get(StringRef LocalName) const
Read a named option from the Context.
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
DiagnosticBuilder configurationDiag(StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning) const
Adds a diagnostic to report errors in the check's configuration.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
DefinitionsInHeadersCheck(StringRef Name, ClangTidyContext *Context)
AST_MATCHER_P(FunctionDecl, parameterCountGE, unsigned, N)
Matches functions that have at least the specified amount of parameters.
bool isExpansionLocInHeaderFile(SourceLocation Loc, const SourceManager &SM, const FileExtensionsSet &HeaderFileExtensions)
Checks whether expansion location of Loc is in header file.
StringRef defaultHeaderFileExtensions()
Returns recommended default value for the list of header file extensions.
StringRef defaultFileExtensionDelimiters()
Returns recommended default value for the list of file extension delimiters.
bool parseFileExtensions(StringRef AllFileExtensions, FileExtensionsSet &FileExtensions, StringRef Delimiters)
Parses header file extensions from a semicolon-separated list.
llvm::SmallSet< llvm::StringRef, 5 > FileExtensionsSet
llvm::StringMap< ClangTidyValue > OptionMap