xref: /llvm-project/clang-tools-extra/clang-tidy/modernize/DeprecatedHeadersCheck.cpp (revision 00e80fbfb9151a68e7383dcec7da69c867225e54)
1 //===--- DeprecatedHeadersCheck.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 "DeprecatedHeadersCheck.h"
10 #include "clang/AST/RecursiveASTVisitor.h"
11 #include "clang/Frontend/CompilerInstance.h"
12 #include "clang/Lex/PPCallbacks.h"
13 #include "clang/Lex/Preprocessor.h"
14 #include "llvm/ADT/StringMap.h"
15 #include "llvm/ADT/StringSet.h"
16 
17 #include <algorithm>
18 #include <vector>
19 
20 using IncludeMarker =
21     clang::tidy::modernize::DeprecatedHeadersCheck::IncludeMarker;
22 namespace clang::tidy::modernize {
23 namespace {
24 
25 class IncludeModernizePPCallbacks : public PPCallbacks {
26 public:
27   explicit IncludeModernizePPCallbacks(
28       std::vector<IncludeMarker> &IncludesToBeProcessed, LangOptions LangOpts,
29       const SourceManager &SM, bool CheckHeaderFile);
30 
31   void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
32                           StringRef FileName, bool IsAngled,
33                           CharSourceRange FilenameRange,
34                           OptionalFileEntryRef File, StringRef SearchPath,
35                           StringRef RelativePath, const Module *SuggestedModule,
36                           bool ModuleImported,
37                           SrcMgr::CharacteristicKind FileType) override;
38 
39 private:
40   std::vector<IncludeMarker> &IncludesToBeProcessed;
41   LangOptions LangOpts;
42   llvm::StringMap<std::string> CStyledHeaderToCxx;
43   llvm::StringSet<> DeleteHeaders;
44   const SourceManager &SM;
45   bool CheckHeaderFile;
46 };
47 
48 class ExternCRefutationVisitor
49     : public RecursiveASTVisitor<ExternCRefutationVisitor> {
50   std::vector<IncludeMarker> &IncludesToBeProcessed;
51   const SourceManager &SM;
52 
53 public:
ExternCRefutationVisitor(std::vector<IncludeMarker> & IncludesToBeProcessed,SourceManager & SM)54   ExternCRefutationVisitor(std::vector<IncludeMarker> &IncludesToBeProcessed,
55                            SourceManager &SM)
56       : IncludesToBeProcessed(IncludesToBeProcessed), SM(SM) {}
shouldWalkTypesOfTypeLocs() const57   bool shouldWalkTypesOfTypeLocs() const { return false; }
shouldVisitLambdaBody() const58   bool shouldVisitLambdaBody() const { return false; }
59 
VisitLinkageSpecDecl(LinkageSpecDecl * LinkSpecDecl) const60   bool VisitLinkageSpecDecl(LinkageSpecDecl *LinkSpecDecl) const {
61     if (LinkSpecDecl->getLanguage() != LinkageSpecLanguageIDs::C ||
62         !LinkSpecDecl->hasBraces())
63       return true;
64 
65     auto ExternCBlockBegin = LinkSpecDecl->getBeginLoc();
66     auto ExternCBlockEnd = LinkSpecDecl->getEndLoc();
67     auto IsWrapped = [=, &SM = SM](const IncludeMarker &Marker) -> bool {
68       return SM.isBeforeInTranslationUnit(ExternCBlockBegin, Marker.DiagLoc) &&
69              SM.isBeforeInTranslationUnit(Marker.DiagLoc, ExternCBlockEnd);
70     };
71 
72     llvm::erase_if(IncludesToBeProcessed, IsWrapped);
73     return true;
74   }
75 };
76 } // namespace
77 
DeprecatedHeadersCheck(StringRef Name,ClangTidyContext * Context)78 DeprecatedHeadersCheck::DeprecatedHeadersCheck(StringRef Name,
79                                                ClangTidyContext *Context)
80     : ClangTidyCheck(Name, Context),
81       CheckHeaderFile(Options.get("CheckHeaderFile", false)) {}
82 
storeOptions(ClangTidyOptions::OptionMap & Opts)83 void DeprecatedHeadersCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
84   Options.store(Opts, "CheckHeaderFile", CheckHeaderFile);
85 }
86 
registerPPCallbacks(const SourceManager & SM,Preprocessor * PP,Preprocessor * ModuleExpanderPP)87 void DeprecatedHeadersCheck::registerPPCallbacks(
88     const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
89   PP->addPPCallbacks(std::make_unique<IncludeModernizePPCallbacks>(
90       IncludesToBeProcessed, getLangOpts(), PP->getSourceManager(),
91       CheckHeaderFile));
92 }
registerMatchers(ast_matchers::MatchFinder * Finder)93 void DeprecatedHeadersCheck::registerMatchers(
94     ast_matchers::MatchFinder *Finder) {
95   // Even though the checker operates on a "preprocessor" level, we still need
96   // to act on a "TranslationUnit" to acquire the AST where we can walk each
97   // Decl and look for `extern "C"` blocks where we will suppress the report we
98   // collected during the preprocessing phase.
99   // The `onStartOfTranslationUnit()` won't suffice, since we need some handle
100   // to the `ASTContext`.
101   Finder->addMatcher(ast_matchers::translationUnitDecl().bind("TU"), this);
102 }
103 
onEndOfTranslationUnit()104 void DeprecatedHeadersCheck::onEndOfTranslationUnit() {
105   IncludesToBeProcessed.clear();
106 }
107 
check(const ast_matchers::MatchFinder::MatchResult & Result)108 void DeprecatedHeadersCheck::check(
109     const ast_matchers::MatchFinder::MatchResult &Result) {
110   SourceManager &SM = Result.Context->getSourceManager();
111 
112   // Suppress includes wrapped by `extern "C" { ... }` blocks.
113   ExternCRefutationVisitor Visitor(IncludesToBeProcessed, SM);
114   Visitor.TraverseAST(*Result.Context);
115 
116   // Emit all the remaining reports.
117   for (const IncludeMarker &Marker : IncludesToBeProcessed) {
118     if (Marker.Replacement.empty()) {
119       diag(Marker.DiagLoc,
120            "including '%0' has no effect in C++; consider removing it")
121           << Marker.FileName
122           << FixItHint::CreateRemoval(Marker.ReplacementRange);
123     } else {
124       diag(Marker.DiagLoc, "inclusion of deprecated C++ header "
125                            "'%0'; consider using '%1' instead")
126           << Marker.FileName << Marker.Replacement
127           << FixItHint::CreateReplacement(
128                  Marker.ReplacementRange,
129                  (llvm::Twine("<") + Marker.Replacement + ">").str());
130     }
131   }
132 }
133 
IncludeModernizePPCallbacks(std::vector<IncludeMarker> & IncludesToBeProcessed,LangOptions LangOpts,const SourceManager & SM,bool CheckHeaderFile)134 IncludeModernizePPCallbacks::IncludeModernizePPCallbacks(
135     std::vector<IncludeMarker> &IncludesToBeProcessed, LangOptions LangOpts,
136     const SourceManager &SM, bool CheckHeaderFile)
137     : IncludesToBeProcessed(IncludesToBeProcessed), LangOpts(LangOpts), SM(SM),
138       CheckHeaderFile(CheckHeaderFile) {
139   for (const auto &KeyValue :
140        std::vector<std::pair<llvm::StringRef, std::string>>(
141            {{"assert.h", "cassert"},
142             {"complex.h", "complex"},
143             {"ctype.h", "cctype"},
144             {"errno.h", "cerrno"},
145             {"float.h", "cfloat"},
146             {"limits.h", "climits"},
147             {"locale.h", "clocale"},
148             {"math.h", "cmath"},
149             {"setjmp.h", "csetjmp"},
150             {"signal.h", "csignal"},
151             {"stdarg.h", "cstdarg"},
152             {"stddef.h", "cstddef"},
153             {"stdio.h", "cstdio"},
154             {"stdlib.h", "cstdlib"},
155             {"string.h", "cstring"},
156             {"time.h", "ctime"},
157             {"wchar.h", "cwchar"},
158             {"wctype.h", "cwctype"}})) {
159     CStyledHeaderToCxx.insert(KeyValue);
160   }
161   // Add C++11 headers.
162   if (LangOpts.CPlusPlus11) {
163     for (const auto &KeyValue :
164          std::vector<std::pair<llvm::StringRef, std::string>>(
165              {{"fenv.h", "cfenv"},
166               {"stdint.h", "cstdint"},
167               {"inttypes.h", "cinttypes"},
168               {"tgmath.h", "ctgmath"},
169               {"uchar.h", "cuchar"}})) {
170       CStyledHeaderToCxx.insert(KeyValue);
171     }
172   }
173   for (const auto &Key :
174        std::vector<std::string>({"stdalign.h", "stdbool.h", "iso646.h"})) {
175     DeleteHeaders.insert(Key);
176   }
177 }
178 
InclusionDirective(SourceLocation HashLoc,const Token & IncludeTok,StringRef FileName,bool IsAngled,CharSourceRange FilenameRange,OptionalFileEntryRef File,StringRef SearchPath,StringRef RelativePath,const Module * SuggestedModule,bool ModuleImported,SrcMgr::CharacteristicKind FileType)179 void IncludeModernizePPCallbacks::InclusionDirective(
180     SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName,
181     bool IsAngled, CharSourceRange FilenameRange, OptionalFileEntryRef File,
182     StringRef SearchPath, StringRef RelativePath, const Module *SuggestedModule,
183     bool ModuleImported, SrcMgr::CharacteristicKind FileType) {
184 
185   // If we don't want to warn for non-main file reports and this is one, skip
186   // it.
187   if (!CheckHeaderFile && !SM.isInMainFile(HashLoc))
188     return;
189 
190   // Ignore system headers.
191   if (SM.isInSystemHeader(HashLoc))
192     return;
193 
194   // FIXME: Take care of library symbols from the global namespace.
195   //
196   // Reasonable options for the check:
197   //
198   // 1. Insert std prefix for every such symbol occurrence.
199   // 2. Insert `using namespace std;` to the beginning of TU.
200   // 3. Do nothing and let the user deal with the migration himself.
201   SourceLocation DiagLoc = FilenameRange.getBegin();
202   if (CStyledHeaderToCxx.count(FileName) != 0) {
203     IncludesToBeProcessed.emplace_back(
204         IncludeMarker{CStyledHeaderToCxx[FileName], FileName,
205                       FilenameRange.getAsRange(), DiagLoc});
206   } else if (DeleteHeaders.count(FileName) != 0) {
207     IncludesToBeProcessed.emplace_back(
208         // NOLINTNEXTLINE(modernize-use-emplace) - false-positive
209         IncludeMarker{std::string{}, FileName,
210                       SourceRange{HashLoc, FilenameRange.getEnd()}, DiagLoc});
211   }
212 }
213 
214 } // namespace clang::tidy::modernize
215