| 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 | |