blob: 7d4659c1eaac43eb140e4dc281819ab3eb97b9ac [file] [log] [blame]
//===--- DeprecatedHeadersCheck.cpp - clang-tidy---------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "DeprecatedHeadersCheck.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/PPCallbacks.h"
#include "clang/Lex/Preprocessor.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringSet.h"
#include <algorithm>
#include <vector>
using IncludeMarker =
clang::tidy::modernize::DeprecatedHeadersCheck::IncludeMarker;
namespace clang::tidy::modernize {
namespace {
class IncludeModernizePPCallbacks : public PPCallbacks {
public:
explicit IncludeModernizePPCallbacks(
std::vector<IncludeMarker> &IncludesToBeProcessed, LangOptions LangOpts,
const SourceManager &SM, bool CheckHeaderFile);
void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
StringRef FileName, bool IsAngled,
CharSourceRange FilenameRange,
OptionalFileEntryRef File, StringRef SearchPath,
StringRef RelativePath, const Module *Imported,
SrcMgr::CharacteristicKind FileType) override;
private:
std::vector<IncludeMarker> &IncludesToBeProcessed;
LangOptions LangOpts;
llvm::StringMap<std::string> CStyledHeaderToCxx;
llvm::StringSet<> DeleteHeaders;
const SourceManager &SM;
bool CheckHeaderFile;
};
class ExternCRefutationVisitor
: public RecursiveASTVisitor<ExternCRefutationVisitor> {
std::vector<IncludeMarker> &IncludesToBeProcessed;
const SourceManager &SM;
public:
ExternCRefutationVisitor(std::vector<IncludeMarker> &IncludesToBeProcessed,
SourceManager &SM)
: IncludesToBeProcessed(IncludesToBeProcessed), SM(SM) {}
bool shouldWalkTypesOfTypeLocs() const { return false; }
bool shouldVisitLambdaBody() const { return false; }
bool VisitLinkageSpecDecl(LinkageSpecDecl *LinkSpecDecl) const {
if (LinkSpecDecl->getLanguage() != LinkageSpecDecl::lang_c ||
!LinkSpecDecl->hasBraces())
return true;
auto ExternCBlockBegin = LinkSpecDecl->getBeginLoc();
auto ExternCBlockEnd = LinkSpecDecl->getEndLoc();
auto IsWrapped = [=, &SM = SM](const IncludeMarker &Marker) -> bool {
return SM.isBeforeInTranslationUnit(ExternCBlockBegin, Marker.DiagLoc) &&
SM.isBeforeInTranslationUnit(Marker.DiagLoc, ExternCBlockEnd);
};
llvm::erase_if(IncludesToBeProcessed, IsWrapped);
return true;
}
};
} // namespace
DeprecatedHeadersCheck::DeprecatedHeadersCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
CheckHeaderFile(Options.get("CheckHeaderFile", false)) {}
void DeprecatedHeadersCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "CheckHeaderFile", CheckHeaderFile);
}
void DeprecatedHeadersCheck::registerPPCallbacks(
const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
PP->addPPCallbacks(std::make_unique<IncludeModernizePPCallbacks>(
IncludesToBeProcessed, getLangOpts(), PP->getSourceManager(),
CheckHeaderFile));
}
void DeprecatedHeadersCheck::registerMatchers(
ast_matchers::MatchFinder *Finder) {
// Even though the checker operates on a "preprocessor" level, we still need
// to act on a "TranslationUnit" to acquire the AST where we can walk each
// Decl and look for `extern "C"` blocks where we will suppress the report we
// collected during the preprocessing phase.
// The `onStartOfTranslationUnit()` won't suffice, since we need some handle
// to the `ASTContext`.
Finder->addMatcher(ast_matchers::translationUnitDecl().bind("TU"), this);
}
void DeprecatedHeadersCheck::onEndOfTranslationUnit() {
IncludesToBeProcessed.clear();
}
void DeprecatedHeadersCheck::check(
const ast_matchers::MatchFinder::MatchResult &Result) {
SourceManager &SM = Result.Context->getSourceManager();
// Suppress includes wrapped by `extern "C" { ... }` blocks.
ExternCRefutationVisitor Visitor(IncludesToBeProcessed, SM);
Visitor.TraverseAST(*Result.Context);
// Emit all the remaining reports.
for (const IncludeMarker &Marker : IncludesToBeProcessed) {
if (Marker.Replacement.empty()) {
diag(Marker.DiagLoc,
"including '%0' has no effect in C++; consider removing it")
<< Marker.FileName
<< FixItHint::CreateRemoval(Marker.ReplacementRange);
} else {
diag(Marker.DiagLoc, "inclusion of deprecated C++ header "
"'%0'; consider using '%1' instead")
<< Marker.FileName << Marker.Replacement
<< FixItHint::CreateReplacement(
Marker.ReplacementRange,
(llvm::Twine("<") + Marker.Replacement + ">").str());
}
}
}
IncludeModernizePPCallbacks::IncludeModernizePPCallbacks(
std::vector<IncludeMarker> &IncludesToBeProcessed, LangOptions LangOpts,
const SourceManager &SM, bool CheckHeaderFile)
: IncludesToBeProcessed(IncludesToBeProcessed), LangOpts(LangOpts), SM(SM),
CheckHeaderFile(CheckHeaderFile) {
for (const auto &KeyValue :
std::vector<std::pair<llvm::StringRef, std::string>>(
{{"assert.h", "cassert"},
{"complex.h", "complex"},
{"ctype.h", "cctype"},
{"errno.h", "cerrno"},
{"float.h", "cfloat"},
{"limits.h", "climits"},
{"locale.h", "clocale"},
{"math.h", "cmath"},
{"setjmp.h", "csetjmp"},
{"signal.h", "csignal"},
{"stdarg.h", "cstdarg"},
{"stddef.h", "cstddef"},
{"stdio.h", "cstdio"},
{"stdlib.h", "cstdlib"},
{"string.h", "cstring"},
{"time.h", "ctime"},
{"wchar.h", "cwchar"},
{"wctype.h", "cwctype"}})) {
CStyledHeaderToCxx.insert(KeyValue);
}
// Add C++ 11 headers.
if (LangOpts.CPlusPlus11) {
for (const auto &KeyValue :
std::vector<std::pair<llvm::StringRef, std::string>>(
{{"fenv.h", "cfenv"},
{"stdint.h", "cstdint"},
{"inttypes.h", "cinttypes"},
{"tgmath.h", "ctgmath"},
{"uchar.h", "cuchar"}})) {
CStyledHeaderToCxx.insert(KeyValue);
}
}
for (const auto &Key :
std::vector<std::string>({"stdalign.h", "stdbool.h", "iso646.h"})) {
DeleteHeaders.insert(Key);
}
}
void IncludeModernizePPCallbacks::InclusionDirective(
SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName,
bool IsAngled, CharSourceRange FilenameRange, OptionalFileEntryRef File,
StringRef SearchPath, StringRef RelativePath, const Module *Imported,
SrcMgr::CharacteristicKind FileType) {
// If we don't want to warn for non-main file reports and this is one, skip
// it.
if (!CheckHeaderFile && !SM.isInMainFile(HashLoc))
return;
// Ignore system headers.
if (SM.isInSystemHeader(HashLoc))
return;
// FIXME: Take care of library symbols from the global namespace.
//
// Reasonable options for the check:
//
// 1. Insert std prefix for every such symbol occurrence.
// 2. Insert `using namespace std;` to the beginning of TU.
// 3. Do nothing and let the user deal with the migration himself.
SourceLocation DiagLoc = FilenameRange.getBegin();
if (CStyledHeaderToCxx.count(FileName) != 0) {
IncludesToBeProcessed.push_back(
IncludeMarker{CStyledHeaderToCxx[FileName], FileName,
FilenameRange.getAsRange(), DiagLoc});
} else if (DeleteHeaders.count(FileName) != 0) {
IncludesToBeProcessed.push_back(
IncludeMarker{std::string{}, FileName,
SourceRange{HashLoc, FilenameRange.getEnd()}, DiagLoc});
}
}
} // namespace clang::tidy::modernize