xref: /llvm-project/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp (revision 222dd235ffc39b3695a3c002593097bec216a8fa)
1c28506baSViktoriia Bakalova //===--- IncludeCleanerCheck.cpp - clang-tidy -----------------------------===//
2c28506baSViktoriia Bakalova //
3c28506baSViktoriia Bakalova // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4c28506baSViktoriia Bakalova // See https://llvm.org/LICENSE.txt for license information.
5c28506baSViktoriia Bakalova // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6c28506baSViktoriia Bakalova //
7c28506baSViktoriia Bakalova //===----------------------------------------------------------------------===//
8c28506baSViktoriia Bakalova 
9c28506baSViktoriia Bakalova #include "IncludeCleanerCheck.h"
10c28506baSViktoriia Bakalova #include "../ClangTidyCheck.h"
11c28506baSViktoriia Bakalova #include "../ClangTidyDiagnosticConsumer.h"
12c28506baSViktoriia Bakalova #include "../ClangTidyOptions.h"
13c28506baSViktoriia Bakalova #include "../utils/OptionsUtils.h"
14c28506baSViktoriia Bakalova #include "clang-include-cleaner/Analysis.h"
1590c5fe98SViktoriia Bakalova #include "clang-include-cleaner/IncludeSpeller.h"
16c28506baSViktoriia Bakalova #include "clang-include-cleaner/Record.h"
17c28506baSViktoriia Bakalova #include "clang-include-cleaner/Types.h"
18c28506baSViktoriia Bakalova #include "clang/AST/ASTContext.h"
19c28506baSViktoriia Bakalova #include "clang/AST/Decl.h"
20c28506baSViktoriia Bakalova #include "clang/AST/DeclBase.h"
21c28506baSViktoriia Bakalova #include "clang/ASTMatchers/ASTMatchFinder.h"
22c28506baSViktoriia Bakalova #include "clang/ASTMatchers/ASTMatchers.h"
23c28506baSViktoriia Bakalova #include "clang/Basic/Diagnostic.h"
24c28506baSViktoriia Bakalova #include "clang/Basic/FileEntry.h"
25c28506baSViktoriia Bakalova #include "clang/Basic/LLVM.h"
26c28506baSViktoriia Bakalova #include "clang/Basic/LangOptions.h"
27c28506baSViktoriia Bakalova #include "clang/Basic/SourceLocation.h"
28c28506baSViktoriia Bakalova #include "clang/Format/Format.h"
2943c20367SViktoriia Bakalova #include "clang/Lex/HeaderSearchOptions.h"
30c28506baSViktoriia Bakalova #include "clang/Lex/Preprocessor.h"
31c28506baSViktoriia Bakalova #include "clang/Tooling/Core/Replacement.h"
32c28506baSViktoriia Bakalova #include "clang/Tooling/Inclusions/HeaderIncludes.h"
3377feba5aSkadir çetinkaya #include "clang/Tooling/Inclusions/StandardLibrary.h"
34c28506baSViktoriia Bakalova #include "llvm/ADT/DenseSet.h"
35c28506baSViktoriia Bakalova #include "llvm/ADT/STLExtras.h"
36c28506baSViktoriia Bakalova #include "llvm/ADT/SmallVector.h"
37c28506baSViktoriia Bakalova #include "llvm/ADT/StringRef.h"
38b0831c39SCongcong Cai #include "llvm/ADT/StringSet.h"
39c28506baSViktoriia Bakalova #include "llvm/Support/ErrorHandling.h"
402444fb96SHaojian Wu #include "llvm/Support/Path.h"
41c28506baSViktoriia Bakalova #include "llvm/Support/Regex.h"
42c28506baSViktoriia Bakalova #include <optional>
43c28506baSViktoriia Bakalova #include <string>
44c28506baSViktoriia Bakalova #include <vector>
45c28506baSViktoriia Bakalova 
46c28506baSViktoriia Bakalova using namespace clang::ast_matchers;
47c28506baSViktoriia Bakalova 
48c28506baSViktoriia Bakalova namespace clang::tidy::misc {
49c28506baSViktoriia Bakalova 
50c28506baSViktoriia Bakalova namespace {
51c28506baSViktoriia Bakalova struct MissingIncludeInfo {
52eed4a4d0SHaojian Wu   include_cleaner::SymbolReference SymRef;
53c28506baSViktoriia Bakalova   include_cleaner::Header Missing;
54c28506baSViktoriia Bakalova };
55c28506baSViktoriia Bakalova } // namespace
56c28506baSViktoriia Bakalova 
57c28506baSViktoriia Bakalova IncludeCleanerCheck::IncludeCleanerCheck(StringRef Name,
58c28506baSViktoriia Bakalova                                          ClangTidyContext *Context)
59c28506baSViktoriia Bakalova     : ClangTidyCheck(Name, Context),
60*222dd235SCongcong Cai       IgnoreHeaders(
61*222dd235SCongcong Cai           utils::options::parseStringList(Options.get("IgnoreHeaders", ""))),
62*222dd235SCongcong Cai       DeduplicateFindings(Options.get("DeduplicateFindings", true)) {
63c28506baSViktoriia Bakalova   for (const auto &Header : IgnoreHeaders) {
64c28506baSViktoriia Bakalova     if (!llvm::Regex{Header}.isValid())
65c28506baSViktoriia Bakalova       configurationDiag("Invalid ignore headers regex '%0'") << Header;
66c28506baSViktoriia Bakalova     std::string HeaderSuffix{Header.str()};
67c28506baSViktoriia Bakalova     if (!Header.ends_with("$"))
68c28506baSViktoriia Bakalova       HeaderSuffix += "$";
69c28506baSViktoriia Bakalova     IgnoreHeadersRegex.emplace_back(HeaderSuffix);
70c28506baSViktoriia Bakalova   }
71c28506baSViktoriia Bakalova }
72c28506baSViktoriia Bakalova 
73c28506baSViktoriia Bakalova void IncludeCleanerCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
74c28506baSViktoriia Bakalova   Options.store(Opts, "IgnoreHeaders",
75c28506baSViktoriia Bakalova                 utils::options::serializeStringList(IgnoreHeaders));
7689d0a76bSKadir Cetinkaya   Options.store(Opts, "DeduplicateFindings", DeduplicateFindings);
77c28506baSViktoriia Bakalova }
78c28506baSViktoriia Bakalova 
79c28506baSViktoriia Bakalova bool IncludeCleanerCheck::isLanguageVersionSupported(
80c28506baSViktoriia Bakalova     const LangOptions &LangOpts) const {
81c28506baSViktoriia Bakalova   return !LangOpts.ObjC;
82c28506baSViktoriia Bakalova }
83c28506baSViktoriia Bakalova 
84c28506baSViktoriia Bakalova void IncludeCleanerCheck::registerMatchers(MatchFinder *Finder) {
85c28506baSViktoriia Bakalova   Finder->addMatcher(translationUnitDecl().bind("top"), this);
86c28506baSViktoriia Bakalova }
87c28506baSViktoriia Bakalova 
88c28506baSViktoriia Bakalova void IncludeCleanerCheck::registerPPCallbacks(const SourceManager &SM,
89c28506baSViktoriia Bakalova                                               Preprocessor *PP,
90c28506baSViktoriia Bakalova                                               Preprocessor *ModuleExpanderPP) {
91c28506baSViktoriia Bakalova   PP->addPPCallbacks(RecordedPreprocessor.record(*PP));
9290ecaddeSKadir Cetinkaya   this->PP = PP;
93c28506baSViktoriia Bakalova   RecordedPI.record(*PP);
94c28506baSViktoriia Bakalova }
95c28506baSViktoriia Bakalova 
96c28506baSViktoriia Bakalova bool IncludeCleanerCheck::shouldIgnore(const include_cleaner::Header &H) {
97c28506baSViktoriia Bakalova   return llvm::any_of(IgnoreHeadersRegex, [&H](const llvm::Regex &R) {
98c28506baSViktoriia Bakalova     switch (H.kind()) {
99c28506baSViktoriia Bakalova     case include_cleaner::Header::Standard:
10077feba5aSkadir çetinkaya       // We don't trim angle brackets around standard library headers
10177feba5aSkadir çetinkaya       // deliberately, so that they are only matched as <vector>, otherwise
10277feba5aSkadir çetinkaya       // having just `.*/vector` might yield false positives.
103c28506baSViktoriia Bakalova       return R.match(H.standard().name());
104c28506baSViktoriia Bakalova     case include_cleaner::Header::Verbatim:
10577feba5aSkadir çetinkaya       return R.match(H.verbatim().trim("<>\""));
106c28506baSViktoriia Bakalova     case include_cleaner::Header::Physical:
10798e6deb6SJan Svoboda       return R.match(H.physical().getFileEntry().tryGetRealPathName());
108c28506baSViktoriia Bakalova     }
109c28506baSViktoriia Bakalova     llvm_unreachable("Unknown Header kind.");
110c28506baSViktoriia Bakalova   });
111c28506baSViktoriia Bakalova }
112c28506baSViktoriia Bakalova 
113c28506baSViktoriia Bakalova void IncludeCleanerCheck::check(const MatchFinder::MatchResult &Result) {
114c28506baSViktoriia Bakalova   const SourceManager *SM = Result.SourceManager;
115c28506baSViktoriia Bakalova   const FileEntry *MainFile = SM->getFileEntryForID(SM->getMainFileID());
116c28506baSViktoriia Bakalova   llvm::DenseSet<const include_cleaner::Include *> Used;
117c28506baSViktoriia Bakalova   std::vector<MissingIncludeInfo> Missing;
118c28506baSViktoriia Bakalova   llvm::SmallVector<Decl *> MainFileDecls;
119c28506baSViktoriia Bakalova   for (Decl *D : Result.Nodes.getNodeAs<TranslationUnitDecl>("top")->decls()) {
120c28506baSViktoriia Bakalova     if (!SM->isWrittenInMainFile(SM->getExpansionLoc(D->getLocation())))
121c28506baSViktoriia Bakalova       continue;
122c28506baSViktoriia Bakalova     // FIXME: Filter out implicit template specializations.
123c28506baSViktoriia Bakalova     MainFileDecls.push_back(D);
124c28506baSViktoriia Bakalova   }
12589d0a76bSKadir Cetinkaya   llvm::DenseSet<include_cleaner::Symbol> SeenSymbols;
126cb92511cSJan Svoboda   OptionalDirectoryEntryRef ResourceDir =
12743c20367SViktoriia Bakalova       PP->getHeaderSearchInfo().getModuleMap().getBuiltinDir();
128c28506baSViktoriia Bakalova   // FIXME: Find a way to have less code duplication between include-cleaner
129c28506baSViktoriia Bakalova   // analysis implementation and the below code.
130c28506baSViktoriia Bakalova   walkUsed(MainFileDecls, RecordedPreprocessor.MacroReferences, &RecordedPI,
13190ecaddeSKadir Cetinkaya            *PP,
132c28506baSViktoriia Bakalova            [&](const include_cleaner::SymbolReference &Ref,
133c28506baSViktoriia Bakalova                llvm::ArrayRef<include_cleaner::Header> Providers) {
13489d0a76bSKadir Cetinkaya              // Process each symbol once to reduce noise in the findings.
13589d0a76bSKadir Cetinkaya              // Tidy checks are used in two different workflows:
13689d0a76bSKadir Cetinkaya              // - Ones that show all the findings for a given file. For such
13789d0a76bSKadir Cetinkaya              // workflows there is not much point in showing all the occurences,
13889d0a76bSKadir Cetinkaya              // as one is enough to indicate the issue.
13989d0a76bSKadir Cetinkaya              // - Ones that show only the findings on changed pieces. For such
14089d0a76bSKadir Cetinkaya              // workflows it's useful to show findings on every reference of a
14189d0a76bSKadir Cetinkaya              // symbol as otherwise tools might give incosistent results
14289d0a76bSKadir Cetinkaya              // depending on the parts of the file being edited. But it should
14389d0a76bSKadir Cetinkaya              // still help surface findings for "new violations" (i.e.
14489d0a76bSKadir Cetinkaya              // dependency did not exist in the code at all before).
14589d0a76bSKadir Cetinkaya              if (DeduplicateFindings && !SeenSymbols.insert(Ref.Target).second)
14689d0a76bSKadir Cetinkaya                return;
147c28506baSViktoriia Bakalova              bool Satisfied = false;
148c28506baSViktoriia Bakalova              for (const include_cleaner::Header &H : Providers) {
149c28506baSViktoriia Bakalova                if (H.kind() == include_cleaner::Header::Physical &&
15043c20367SViktoriia Bakalova                    (H.physical() == MainFile ||
15198e6deb6SJan Svoboda                     H.physical().getDir() == ResourceDir)) {
152c28506baSViktoriia Bakalova                  Satisfied = true;
15343c20367SViktoriia Bakalova                  continue;
15443c20367SViktoriia Bakalova                }
155c28506baSViktoriia Bakalova 
156c28506baSViktoriia Bakalova                for (const include_cleaner::Include *I :
157c28506baSViktoriia Bakalova                     RecordedPreprocessor.Includes.match(H)) {
158c28506baSViktoriia Bakalova                  Used.insert(I);
159c28506baSViktoriia Bakalova                  Satisfied = true;
160c28506baSViktoriia Bakalova                }
161c28506baSViktoriia Bakalova              }
162c28506baSViktoriia Bakalova              if (!Satisfied && !Providers.empty() &&
163c28506baSViktoriia Bakalova                  Ref.RT == include_cleaner::RefType::Explicit &&
164c28506baSViktoriia Bakalova                  !shouldIgnore(Providers.front()))
165eed4a4d0SHaojian Wu                Missing.push_back({Ref, Providers.front()});
166c28506baSViktoriia Bakalova            });
167c28506baSViktoriia Bakalova 
168c28506baSViktoriia Bakalova   std::vector<const include_cleaner::Include *> Unused;
169c28506baSViktoriia Bakalova   for (const include_cleaner::Include &I :
170c28506baSViktoriia Bakalova        RecordedPreprocessor.Includes.all()) {
17143c20367SViktoriia Bakalova     if (Used.contains(&I) || !I.Resolved || I.Resolved->getDir() == ResourceDir)
172c28506baSViktoriia Bakalova       continue;
17343974333SKadir Cetinkaya     if (RecordedPI.shouldKeep(*I.Resolved))
174c28506baSViktoriia Bakalova       continue;
175c28506baSViktoriia Bakalova     // Check if main file is the public interface for a private header. If so
176c28506baSViktoriia Bakalova     // we shouldn't diagnose it as unused.
177f6307b26SSam McCall     if (auto PHeader = RecordedPI.getPublic(*I.Resolved); !PHeader.empty()) {
178c28506baSViktoriia Bakalova       PHeader = PHeader.trim("<>\"");
179c28506baSViktoriia Bakalova       // Since most private -> public mappings happen in a verbatim way, we
180c28506baSViktoriia Bakalova       // check textually here. This might go wrong in presence of symlinks or
181c28506baSViktoriia Bakalova       // header mappings. But that's not different than rest of the places.
18276bbbcb4SKazu Hirata       if (getCurrentMainFile().ends_with(PHeader))
183c28506baSViktoriia Bakalova         continue;
184c28506baSViktoriia Bakalova     }
18577feba5aSkadir çetinkaya     auto StdHeader = tooling::stdlib::Header::named(
18677feba5aSkadir çetinkaya         I.quote(), PP->getLangOpts().CPlusPlus ? tooling::stdlib::Lang::CXX
18777feba5aSkadir çetinkaya                                                : tooling::stdlib::Lang::C);
18877feba5aSkadir çetinkaya     if (StdHeader && shouldIgnore(*StdHeader))
18977feba5aSkadir çetinkaya       continue;
19077feba5aSkadir çetinkaya     if (shouldIgnore(*I.Resolved))
19177feba5aSkadir çetinkaya       continue;
192c28506baSViktoriia Bakalova     Unused.push_back(&I);
193c28506baSViktoriia Bakalova   }
194c28506baSViktoriia Bakalova 
195c28506baSViktoriia Bakalova   llvm::StringRef Code = SM->getBufferData(SM->getMainFileID());
196c28506baSViktoriia Bakalova   auto FileStyle =
197c28506baSViktoriia Bakalova       format::getStyle(format::DefaultFormatStyle, getCurrentMainFile(),
198c28506baSViktoriia Bakalova                        format::DefaultFallbackStyle, Code,
199c28506baSViktoriia Bakalova                        &SM->getFileManager().getVirtualFileSystem());
200c28506baSViktoriia Bakalova   if (!FileStyle)
201c28506baSViktoriia Bakalova     FileStyle = format::getLLVMStyle();
202c28506baSViktoriia Bakalova 
203c28506baSViktoriia Bakalova   for (const auto *Inc : Unused) {
204c28506baSViktoriia Bakalova     diag(Inc->HashLocation, "included header %0 is not used directly")
2052444fb96SHaojian Wu         << llvm::sys::path::filename(Inc->Spelled,
2062444fb96SHaojian Wu                                      llvm::sys::path::Style::posix)
207c28506baSViktoriia Bakalova         << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
208c28506baSViktoriia Bakalova                SM->translateLineCol(SM->getMainFileID(), Inc->Line, 1),
209c28506baSViktoriia Bakalova                SM->translateLineCol(SM->getMainFileID(), Inc->Line + 1, 1)));
210c28506baSViktoriia Bakalova   }
211c28506baSViktoriia Bakalova 
212c28506baSViktoriia Bakalova   tooling::HeaderIncludes HeaderIncludes(getCurrentMainFile(), Code,
213c28506baSViktoriia Bakalova                                          FileStyle->IncludeStyle);
214b0831c39SCongcong Cai   // Deduplicate insertions when running in bulk fix mode.
215b0831c39SCongcong Cai   llvm::StringSet<> InsertedHeaders{};
216c28506baSViktoriia Bakalova   for (const auto &Inc : Missing) {
21790ecaddeSKadir Cetinkaya     std::string Spelling = include_cleaner::spellHeader(
21890ecaddeSKadir Cetinkaya         {Inc.Missing, PP->getHeaderSearchInfo(), MainFile});
219c28506baSViktoriia Bakalova     bool Angled = llvm::StringRef{Spelling}.starts_with("<");
220c28506baSViktoriia Bakalova     // We might suggest insertion of an existing include in edge cases, e.g.,
221c28506baSViktoriia Bakalova     // include is present in a PP-disabled region, or spelling of the header
222c28506baSViktoriia Bakalova     // turns out to be the same as one of the unresolved includes in the
223c28506baSViktoriia Bakalova     // main file.
224c28506baSViktoriia Bakalova     if (auto Replacement =
225c28506baSViktoriia Bakalova             HeaderIncludes.insert(llvm::StringRef{Spelling}.trim("\"<>"),
226b0831c39SCongcong Cai                                   Angled, tooling::IncludeDirective::Include)) {
227b0831c39SCongcong Cai       DiagnosticBuilder DB =
228eed4a4d0SHaojian Wu           diag(SM->getSpellingLoc(Inc.SymRef.RefLocation),
229eed4a4d0SHaojian Wu                "no header providing \"%0\" is directly included")
230b0831c39SCongcong Cai           << Inc.SymRef.Target.name();
231b0831c39SCongcong Cai       if (areDiagsSelfContained() ||
232b0831c39SCongcong Cai           InsertedHeaders.insert(Replacement->getReplacementText()).second) {
233b0831c39SCongcong Cai         DB << FixItHint::CreateInsertion(
234b0831c39SCongcong Cai             SM->getComposedLoc(SM->getMainFileID(), Replacement->getOffset()),
235c28506baSViktoriia Bakalova             Replacement->getReplacementText());
236c28506baSViktoriia Bakalova       }
237c28506baSViktoriia Bakalova     }
238b0831c39SCongcong Cai   }
239b0831c39SCongcong Cai }
240c28506baSViktoriia Bakalova 
241c28506baSViktoriia Bakalova } // namespace clang::tidy::misc
242