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