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
9 #include "DefinitionsInHeadersCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12
13 using namespace clang::ast_matchers;
14
15 namespace clang {
16 namespace tidy {
17 namespace misc {
18
19 namespace {
20
AST_MATCHER_P(NamedDecl,usesHeaderFileExtension,utils::FileExtensionsSet,HeaderFileExtensions)21 AST_MATCHER_P(NamedDecl, usesHeaderFileExtension, utils::FileExtensionsSet,
22 HeaderFileExtensions) {
23 return utils::isExpansionLocInHeaderFile(
24 Node.getBeginLoc(), Finder->getASTContext().getSourceManager(),
25 HeaderFileExtensions);
26 }
27
28 } // namespace
29
DefinitionsInHeadersCheck(StringRef Name,ClangTidyContext * Context)30 DefinitionsInHeadersCheck::DefinitionsInHeadersCheck(StringRef Name,
31 ClangTidyContext *Context)
32 : ClangTidyCheck(Name, Context),
33 UseHeaderFileExtension(Options.get("UseHeaderFileExtension", true)),
34 RawStringHeaderFileExtensions(Options.getLocalOrGlobal(
35 "HeaderFileExtensions", utils::defaultHeaderFileExtensions())) {
36 if (!utils::parseFileExtensions(RawStringHeaderFileExtensions,
37 HeaderFileExtensions,
38 utils::defaultFileExtensionDelimiters())) {
39 this->configurationDiag("Invalid header file extension: '%0'")
40 << RawStringHeaderFileExtensions;
41 }
42 }
43
storeOptions(ClangTidyOptions::OptionMap & Opts)44 void DefinitionsInHeadersCheck::storeOptions(
45 ClangTidyOptions::OptionMap &Opts) {
46 Options.store(Opts, "UseHeaderFileExtension", UseHeaderFileExtension);
47 Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions);
48 }
49
registerMatchers(MatchFinder * Finder)50 void DefinitionsInHeadersCheck::registerMatchers(MatchFinder *Finder) {
51 auto DefinitionMatcher =
52 anyOf(functionDecl(isDefinition(), unless(isDeleted())),
53 varDecl(isDefinition()));
54 if (UseHeaderFileExtension) {
55 Finder->addMatcher(namedDecl(DefinitionMatcher,
56 usesHeaderFileExtension(HeaderFileExtensions))
57 .bind("name-decl"),
58 this);
59 } else {
60 Finder->addMatcher(
61 namedDecl(DefinitionMatcher,
62 anyOf(usesHeaderFileExtension(HeaderFileExtensions),
63 unless(isExpansionInMainFile())))
64 .bind("name-decl"),
65 this);
66 }
67 }
68
check(const MatchFinder::MatchResult & Result)69 void DefinitionsInHeadersCheck::check(const MatchFinder::MatchResult &Result) {
70 // Don't run the check in failing TUs.
71 if (Result.Context->getDiagnostics().hasUncompilableErrorOccurred())
72 return;
73
74 // C++ [basic.def.odr] p6:
75 // There can be more than one definition of a class type, enumeration type,
76 // inline function with external linkage, class template, non-static function
77 // template, static data member of a class template, member function of a
78 // class template, or template specialization for which some template
79 // parameters are not specifiedin a program provided that each definition
80 // appears in a different translation unit, and provided the definitions
81 // satisfy the following requirements.
82 const auto *ND = Result.Nodes.getNodeAs<NamedDecl>("name-decl");
83 assert(ND);
84 if (ND->isInvalidDecl())
85 return;
86
87 // Internal linkage variable definitions are ignored for now:
88 // const int a = 1;
89 // static int b = 1;
90 //
91 // Although these might also cause ODR violations, we can be less certain and
92 // should try to keep the false-positive rate down.
93 //
94 // FIXME: Should declarations in anonymous namespaces get the same treatment
95 // as static / const declarations?
96 if (!ND->hasExternalFormalLinkage() && !ND->isInAnonymousNamespace())
97 return;
98
99 if (const auto *FD = dyn_cast<FunctionDecl>(ND)) {
100 // Inline functions are allowed.
101 if (FD->isInlined())
102 return;
103 // Function templates are allowed.
104 if (FD->getTemplatedKind() == FunctionDecl::TK_FunctionTemplate)
105 return;
106 // Ignore instantiated functions.
107 if (FD->isTemplateInstantiation())
108 return;
109 // Member function of a class template and member function of a nested class
110 // in a class template are allowed.
111 if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
112 const auto *DC = MD->getDeclContext();
113 while (DC->isRecord()) {
114 if (const auto *RD = dyn_cast<CXXRecordDecl>(DC)) {
115 if (isa<ClassTemplatePartialSpecializationDecl>(RD))
116 return;
117 if (RD->getDescribedClassTemplate())
118 return;
119 }
120 DC = DC->getParent();
121 }
122 }
123
124 bool IsFullSpec = FD->getTemplateSpecializationKind() != TSK_Undeclared;
125 diag(FD->getLocation(),
126 "%select{function|full function template specialization}0 %1 defined "
127 "in a header file; function definitions in header files can lead to "
128 "ODR violations")
129 << IsFullSpec << FD;
130 diag(FD->getLocation(), /*FixDescription=*/"make as 'inline'",
131 DiagnosticIDs::Note)
132 << FixItHint::CreateInsertion(FD->getInnerLocStart(), "inline ");
133 } else if (const auto *VD = dyn_cast<VarDecl>(ND)) {
134 // C++14 variable templates are allowed.
135 if (VD->getDescribedVarTemplate())
136 return;
137 // Static data members of a class template are allowed.
138 if (VD->getDeclContext()->isDependentContext() && VD->isStaticDataMember())
139 return;
140 // Ignore instantiated static data members of classes.
141 if (isTemplateInstantiation(VD->getTemplateSpecializationKind()))
142 return;
143 // Ignore variable definition within function scope.
144 if (VD->hasLocalStorage() || VD->isStaticLocal())
145 return;
146 // Ignore inline variables.
147 if (VD->isInline())
148 return;
149
150 diag(VD->getLocation(),
151 "variable %0 defined in a header file; "
152 "variable definitions in header files can lead to ODR violations")
153 << VD;
154 }
155 }
156
157 } // namespace misc
158 } // namespace tidy
159 } // namespace clang
160