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