148 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 "DuplicateIncludeCheck.h"10#include "../utils/OptionsUtils.h"11#include "clang/Frontend/CompilerInstance.h"12#include "clang/Lex/Preprocessor.h"13#include "llvm/ADT/STLExtras.h"14#include "llvm/ADT/SmallVector.h"15#include "llvm/Support/Regex.h"16#include <memory>17 18namespace clang::tidy::readability {19 20static SourceLocation advanceBeyondCurrentLine(const SourceManager &SM,21 SourceLocation Start,22 int Offset) {23 const FileID Id = SM.getFileID(Start);24 const unsigned LineNumber = SM.getSpellingLineNumber(Start);25 while (SM.getFileID(Start) == Id &&26 SM.getSpellingLineNumber(Start.getLocWithOffset(Offset)) == LineNumber)27 Start = Start.getLocWithOffset(Offset);28 return Start;29}30 31namespace {32 33using FileList = SmallVector<StringRef>;34 35class DuplicateIncludeCallbacks : public PPCallbacks {36public:37 DuplicateIncludeCallbacks(DuplicateIncludeCheck &Check,38 const SourceManager &SM,39 llvm::ArrayRef<StringRef> IgnoredList);40 41 void FileChanged(SourceLocation Loc, FileChangeReason Reason,42 SrcMgr::CharacteristicKind FileType,43 FileID PrevFID) override;44 45 void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,46 StringRef FileName, bool IsAngled,47 CharSourceRange FilenameRange,48 OptionalFileEntryRef File, StringRef SearchPath,49 StringRef RelativePath, const Module *SuggestedModule,50 bool ModuleImported,51 SrcMgr::CharacteristicKind FileType) override;52 53 void MacroDefined(const Token &MacroNameTok,54 const MacroDirective *MD) override;55 56 void MacroUndefined(const Token &MacroNameTok, const MacroDefinition &MD,57 const MacroDirective *Undef) override;58 59private:60 // A list of included files is kept for each file we enter.61 SmallVector<FileList> Files;62 DuplicateIncludeCheck &Check;63 const SourceManager &SM;64 SmallVector<llvm::Regex> AllowedRegexes;65};66 67} // namespace68 69DuplicateIncludeCheck::DuplicateIncludeCheck(StringRef Name,70 ClangTidyContext *Context)71 : ClangTidyCheck(Name, Context),72 IgnoredFilesList(utils::options::parseStringList(73 Options.get("IgnoredFilesList", ""))) {}74 75DuplicateIncludeCallbacks::DuplicateIncludeCallbacks(76 DuplicateIncludeCheck &Check, const SourceManager &SM,77 llvm::ArrayRef<StringRef> IgnoredList)78 : Check(Check), SM(SM) {79 // The main file doesn't participate in the FileChanged notification.80 Files.emplace_back();81 82 AllowedRegexes.reserve(IgnoredList.size());83 for (const StringRef &It : IgnoredList) {84 if (!It.empty())85 AllowedRegexes.emplace_back(It);86 }87}88 89void DuplicateIncludeCallbacks::FileChanged(SourceLocation Loc,90 FileChangeReason Reason,91 SrcMgr::CharacteristicKind FileType,92 FileID PrevFID) {93 if (Reason == EnterFile)94 Files.emplace_back();95 else if (Reason == ExitFile)96 Files.pop_back();97}98 99void DuplicateIncludeCallbacks::InclusionDirective(100 SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName,101 bool IsAngled, CharSourceRange FilenameRange, OptionalFileEntryRef /*File*/,102 StringRef SearchPath, StringRef RelativePath, const Module *SuggestedModule,103 bool ModuleImported, SrcMgr::CharacteristicKind FileType) {104 // Skip includes behind macros105 if (FilenameRange.getBegin().isMacroID() ||106 FilenameRange.getEnd().isMacroID())107 return;108 if (llvm::is_contained(Files.back(), FileName)) {109 if (llvm::any_of(AllowedRegexes, [&FileName](const llvm::Regex &R) {110 return R.match(FileName);111 }))112 return;113 // We want to delete the entire line, so make sure that [Start,End] covers114 // everything.115 const SourceLocation Start =116 advanceBeyondCurrentLine(SM, HashLoc, -1).getLocWithOffset(-1);117 const SourceLocation End =118 advanceBeyondCurrentLine(SM, FilenameRange.getEnd(), 1);119 Check.diag(HashLoc, "duplicate include")120 << FixItHint::CreateRemoval(SourceRange{Start, End});121 } else122 Files.back().push_back(FileName);123}124 125void DuplicateIncludeCallbacks::MacroDefined(const Token &MacroNameTok,126 const MacroDirective *MD) {127 Files.back().clear();128}129 130void DuplicateIncludeCallbacks::MacroUndefined(const Token &MacroNameTok,131 const MacroDefinition &MD,132 const MacroDirective *Undef) {133 Files.back().clear();134}135 136void DuplicateIncludeCheck::registerPPCallbacks(137 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {138 PP->addPPCallbacks(139 std::make_unique<DuplicateIncludeCallbacks>(*this, SM, IgnoredFilesList));140}141 142void DuplicateIncludeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {143 Options.store(Opts, "IgnoredFilesList",144 utils::options::serializeStringList(IgnoredFilesList));145}146 147} // namespace clang::tidy::readability148