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 ); |
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> ; |
43 | llvm::StringSet<> ; |
44 | const SourceManager &SM; |
45 | bool ; |
46 | }; |
47 | |
48 | class ExternCRefutationVisitor |
49 | : public RecursiveASTVisitor<ExternCRefutationVisitor> { |
50 | std::vector<IncludeMarker> &IncludesToBeProcessed; |
51 | const SourceManager &SM; |
52 | |
53 | public: |
54 | (std::vector<IncludeMarker> &IncludesToBeProcessed, |
55 | SourceManager &SM) |
56 | : IncludesToBeProcessed(IncludesToBeProcessed), SM(SM) {} |
57 | bool shouldWalkTypesOfTypeLocs() const { return false; } |
58 | bool shouldVisitLambdaBody() const { return false; } |
59 | |
60 | 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(LHS: ExternCBlockBegin, RHS: Marker.DiagLoc) && |
69 | SM.isBeforeInTranslationUnit(LHS: Marker.DiagLoc, RHS: ExternCBlockEnd); |
70 | }; |
71 | |
72 | llvm::erase_if(C&: IncludesToBeProcessed, P: IsWrapped); |
73 | return true; |
74 | } |
75 | }; |
76 | } // namespace |
77 | |
78 | DeprecatedHeadersCheck::(StringRef Name, |
79 | ClangTidyContext *Context) |
80 | : ClangTidyCheck(Name, Context), |
81 | CheckHeaderFile(Options.get(LocalName: "CheckHeaderFile" , Default: false)) {} |
82 | |
83 | void DeprecatedHeadersCheck::(ClangTidyOptions::OptionMap &Opts) { |
84 | Options.store(Options&: Opts, LocalName: "CheckHeaderFile" , Value: CheckHeaderFile); |
85 | } |
86 | |
87 | void DeprecatedHeadersCheck::( |
88 | const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { |
89 | PP->addPPCallbacks(C: std::make_unique<IncludeModernizePPCallbacks>( |
90 | args&: IncludesToBeProcessed, args: getLangOpts(), args&: PP->getSourceManager(), |
91 | args&: CheckHeaderFile)); |
92 | } |
93 | void DeprecatedHeadersCheck::( |
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(NodeMatch: ast_matchers::translationUnitDecl().bind(ID: "TU" ), Action: this); |
102 | } |
103 | |
104 | void DeprecatedHeadersCheck::() { |
105 | IncludesToBeProcessed.clear(); |
106 | } |
107 | |
108 | void DeprecatedHeadersCheck::( |
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(AST&: *Result.Context); |
115 | |
116 | // Emit all the remaining reports. |
117 | for (const IncludeMarker &Marker : IncludesToBeProcessed) { |
118 | if (Marker.Replacement.empty()) { |
119 | diag(Loc: Marker.DiagLoc, |
120 | Description: "including '%0' has no effect in C++; consider removing it" ) |
121 | << Marker.FileName |
122 | << FixItHint::CreateRemoval(RemoveRange: Marker.ReplacementRange); |
123 | } else { |
124 | diag(Loc: Marker.DiagLoc, Description: "inclusion of deprecated C++ header " |
125 | "'%0'; consider using '%1' instead" ) |
126 | << Marker.FileName << Marker.Replacement |
127 | << FixItHint::CreateReplacement( |
128 | RemoveRange: Marker.ReplacementRange, |
129 | Code: (llvm::Twine("<" ) + Marker.Replacement + ">" ).str()); |
130 | } |
131 | } |
132 | } |
133 | |
134 | IncludeModernizePPCallbacks::( |
135 | std::vector<IncludeMarker> &IncludesToBeProcessed, LangOptions LangOpts, |
136 | const SourceManager &SM, bool ) |
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(KV: 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(KV: KeyValue); |
171 | } |
172 | } |
173 | for (const auto &Key : |
174 | std::vector<std::string>({"stdalign.h" , "stdbool.h" , "iso646.h" })) { |
175 | DeleteHeaders.insert(key: Key); |
176 | } |
177 | } |
178 | |
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(Loc: HashLoc)) |
188 | return; |
189 | |
190 | // Ignore system headers. |
191 | if (SM.isInSystemHeader(Loc: 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(Key: FileName) != 0) { |
203 | IncludesToBeProcessed.emplace_back( |
204 | args: IncludeMarker{.Replacement: CStyledHeaderToCxx[FileName], .FileName: FileName, |
205 | .ReplacementRange: FilenameRange.getAsRange(), .DiagLoc: DiagLoc}); |
206 | } else if (DeleteHeaders.count(Key: FileName) != 0) { |
207 | IncludesToBeProcessed.emplace_back( |
208 | // NOLINTNEXTLINE(modernize-use-emplace) - false-positive |
209 | args: IncludeMarker{.Replacement: std::string{}, .FileName: FileName, |
210 | .ReplacementRange: SourceRange{HashLoc, FilenameRange.getEnd()}, .DiagLoc: DiagLoc}); |
211 | } |
212 | } |
213 | |
214 | } // namespace clang::tidy::modernize |
215 | |