199 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//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 19using IncludeMarker =20 clang::tidy::modernize::DeprecatedHeadersCheck::IncludeMarker;21namespace clang::tidy::modernize {22namespace {23 24class IncludeModernizePPCallbacks : public PPCallbacks {25public:26 explicit IncludeModernizePPCallbacks(27 std::vector<IncludeMarker> &IncludesToBeProcessed,28 const LangOptions &LangOpts, const SourceManager &SM,29 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 39private:40 std::vector<IncludeMarker> &IncludesToBeProcessed;41 llvm::StringMap<StringRef> CStyledHeaderToCxx;42 llvm::StringSet<> DeleteHeaders;43 const SourceManager &SM;44 bool CheckHeaderFile;45};46 47class ExternCRefutationVisitor48 : public RecursiveASTVisitor<ExternCRefutationVisitor> {49 std::vector<IncludeMarker> &IncludesToBeProcessed;50 const SourceManager &SM;51 52public:53 ExternCRefutationVisitor(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(ExternCBlockBegin, Marker.DiagLoc) &&68 SM.isBeforeInTranslationUnit(Marker.DiagLoc, ExternCBlockEnd);69 };70 71 llvm::erase_if(IncludesToBeProcessed, IsWrapped);72 return true;73 }74};75} // namespace76 77DeprecatedHeadersCheck::DeprecatedHeadersCheck(StringRef Name,78 ClangTidyContext *Context)79 : ClangTidyCheck(Name, Context),80 CheckHeaderFile(Options.get("CheckHeaderFile", false)) {}81 82void DeprecatedHeadersCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {83 Options.store(Opts, "CheckHeaderFile", CheckHeaderFile);84}85 86void DeprecatedHeadersCheck::registerPPCallbacks(87 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {88 PP->addPPCallbacks(std::make_unique<IncludeModernizePPCallbacks>(89 IncludesToBeProcessed, getLangOpts(), PP->getSourceManager(),90 CheckHeaderFile));91}92void DeprecatedHeadersCheck::registerMatchers(93 ast_matchers::MatchFinder *Finder) {94 // Even though the checker operates on a "preprocessor" level, we still need95 // to act on a "TranslationUnit" to acquire the AST where we can walk each96 // Decl and look for `extern "C"` blocks where we will suppress the report we97 // collected during the preprocessing phase.98 // The `onStartOfTranslationUnit()` won't suffice, since we need some handle99 // to the `ASTContext`.100 Finder->addMatcher(ast_matchers::translationUnitDecl().bind("TU"), this);101}102 103void DeprecatedHeadersCheck::onEndOfTranslationUnit() {104 IncludesToBeProcessed.clear();105}106 107void DeprecatedHeadersCheck::check(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(*Result.Context);114 115 // Emit all the remaining reports.116 for (const IncludeMarker &Marker : IncludesToBeProcessed) {117 if (Marker.Replacement.empty()) {118 diag(Marker.DiagLoc,119 "including '%0' has no effect in C++; consider removing it")120 << Marker.FileName121 << FixItHint::CreateRemoval(Marker.ReplacementRange);122 } else {123 diag(Marker.DiagLoc, "inclusion of deprecated C++ header "124 "'%0'; consider using '%1' instead")125 << Marker.FileName << Marker.Replacement126 << FixItHint::CreateReplacement(127 Marker.ReplacementRange,128 (llvm::Twine("<") + Marker.Replacement + ">").str());129 }130 }131}132 133IncludeModernizePPCallbacks::IncludeModernizePPCallbacks(134 std::vector<IncludeMarker> &IncludesToBeProcessed,135 const LangOptions &LangOpts, const SourceManager &SM, bool CheckHeaderFile)136 : IncludesToBeProcessed(IncludesToBeProcessed), SM(SM),137 CheckHeaderFile(CheckHeaderFile) {138 static constexpr std::pair<StringRef, StringRef> CXX98Headers[] = {139 {"assert.h", "cassert"}, {"complex.h", "complex"},140 {"ctype.h", "cctype"}, {"errno.h", "cerrno"},141 {"float.h", "cfloat"}, {"limits.h", "climits"},142 {"locale.h", "clocale"}, {"math.h", "cmath"},143 {"setjmp.h", "csetjmp"}, {"signal.h", "csignal"},144 {"stdarg.h", "cstdarg"}, {"stddef.h", "cstddef"},145 {"stdio.h", "cstdio"}, {"stdlib.h", "cstdlib"},146 {"string.h", "cstring"}, {"time.h", "ctime"},147 {"wchar.h", "cwchar"}, {"wctype.h", "cwctype"},148 };149 CStyledHeaderToCxx.insert(std::begin(CXX98Headers), std::end(CXX98Headers));150 151 static constexpr std::pair<StringRef, StringRef> CXX11Headers[] = {152 {"fenv.h", "cfenv"}, {"stdint.h", "cstdint"},153 {"inttypes.h", "cinttypes"}, {"tgmath.h", "ctgmath"},154 {"uchar.h", "cuchar"},155 };156 if (LangOpts.CPlusPlus11)157 CStyledHeaderToCxx.insert(std::begin(CXX11Headers), std::end(CXX11Headers));158 159 static constexpr StringRef HeadersToDelete[] = {"stdalign.h", "stdbool.h",160 "iso646.h"};161 DeleteHeaders.insert_range(HeadersToDelete);162}163 164void IncludeModernizePPCallbacks::InclusionDirective(165 SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName,166 bool IsAngled, CharSourceRange FilenameRange, OptionalFileEntryRef File,167 StringRef SearchPath, StringRef RelativePath, const Module *SuggestedModule,168 bool ModuleImported, SrcMgr::CharacteristicKind FileType) {169 // If we don't want to warn for non-main file reports and this is one, skip170 // it.171 if (!CheckHeaderFile && !SM.isInMainFile(HashLoc))172 return;173 174 // Ignore system headers.175 if (SM.isInSystemHeader(HashLoc))176 return;177 178 // FIXME: Take care of library symbols from the global namespace.179 //180 // Reasonable options for the check:181 //182 // 1. Insert std prefix for every such symbol occurrence.183 // 2. Insert `using namespace std;` to the beginning of TU.184 // 3. Do nothing and let the user deal with the migration himself.185 const SourceLocation DiagLoc = FilenameRange.getBegin();186 if (auto It = CStyledHeaderToCxx.find(FileName);187 It != CStyledHeaderToCxx.end()) {188 IncludesToBeProcessed.emplace_back(IncludeMarker{189 It->second, FileName, FilenameRange.getAsRange(), DiagLoc});190 } else if (DeleteHeaders.contains(FileName)) {191 IncludesToBeProcessed.emplace_back(192 // NOLINTNEXTLINE(modernize-use-emplace) - false-positive193 IncludeMarker{StringRef{}, FileName,194 SourceRange{HashLoc, FilenameRange.getEnd()}, DiagLoc});195 }196}197 198} // namespace clang::tidy::modernize199