xref: /llvm-project/clang-tools-extra/include-cleaner/lib/Analysis.cpp (revision ec6c3448d31056db5d63d7aed3e9f207edb49321)
1ce286eccSKadir Cetinkaya //===--- Analysis.cpp -----------------------------------------------------===//
2ce286eccSKadir Cetinkaya //
3ce286eccSKadir Cetinkaya // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4ce286eccSKadir Cetinkaya // See https://llvm.org/LICENSE.txt for license information.
5ce286eccSKadir Cetinkaya // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6ce286eccSKadir Cetinkaya //
7ce286eccSKadir Cetinkaya //===----------------------------------------------------------------------===//
8ce286eccSKadir Cetinkaya 
9ce286eccSKadir Cetinkaya #include "clang-include-cleaner/Analysis.h"
10ce286eccSKadir Cetinkaya #include "AnalysisInternal.h"
1190c5fe98SViktoriia Bakalova #include "clang-include-cleaner/IncludeSpeller.h"
1229a8eec1SKadir Cetinkaya #include "clang-include-cleaner/Record.h"
13d19ba74dSKadir Cetinkaya #include "clang-include-cleaner/Types.h"
1429a8eec1SKadir Cetinkaya #include "clang/AST/Decl.h"
15749c6a70SKadir Cetinkaya #include "clang/AST/DeclBase.h"
1643c20367SViktoriia Bakalova #include "clang/Basic/DirectoryEntry.h"
1790c5fe98SViktoriia Bakalova #include "clang/Basic/FileEntry.h"
18ce286eccSKadir Cetinkaya #include "clang/Basic/SourceManager.h"
191a8dd742SSam McCall #include "clang/Format/Format.h"
201a8dd742SSam McCall #include "clang/Lex/HeaderSearch.h"
2190ecaddeSKadir Cetinkaya #include "clang/Lex/Preprocessor.h"
221a8dd742SSam McCall #include "clang/Tooling/Core/Replacement.h"
23ce286eccSKadir Cetinkaya #include "clang/Tooling/Inclusions/StandardLibrary.h"
24ce286eccSKadir Cetinkaya #include "llvm/ADT/ArrayRef.h"
25f5b6e9b6SKadir Cetinkaya #include "llvm/ADT/DenseSet.h"
26749c6a70SKadir Cetinkaya #include "llvm/ADT/STLExtras.h"
2790c5fe98SViktoriia Bakalova #include "llvm/ADT/STLFunctionalExtras.h"
28ce286eccSKadir Cetinkaya #include "llvm/ADT/SmallVector.h"
294ef77d61Skadir çetinkaya #include "llvm/ADT/StringMap.h"
30749c6a70SKadir Cetinkaya #include "llvm/ADT/StringRef.h"
31f5b6e9b6SKadir Cetinkaya #include "llvm/Support/Error.h"
3290c5fe98SViktoriia Bakalova #include "llvm/Support/ErrorHandling.h"
3390ecaddeSKadir Cetinkaya #include <cassert>
3490ecaddeSKadir Cetinkaya #include <climits>
35f5b6e9b6SKadir Cetinkaya #include <string>
36ce286eccSKadir Cetinkaya 
37ce286eccSKadir Cetinkaya namespace clang::include_cleaner {
3838cccb90SSam McCall 
3990ecaddeSKadir Cetinkaya namespace {
4090ecaddeSKadir Cetinkaya bool shouldIgnoreMacroReference(const Preprocessor &PP, const Macro &M) {
4190ecaddeSKadir Cetinkaya   auto *MI = PP.getMacroInfo(M.Name);
4290ecaddeSKadir Cetinkaya   // Macros that expand to themselves are confusing from user's point of view.
4390ecaddeSKadir Cetinkaya   // They usually aspect the usage to be attributed to the underlying decl and
4490ecaddeSKadir Cetinkaya   // not the macro definition. So ignore such macros (e.g. std{in,out,err} are
4590ecaddeSKadir Cetinkaya   // implementation defined macros, that just resolve to themselves in
4690ecaddeSKadir Cetinkaya   // practice).
4790ecaddeSKadir Cetinkaya   return MI && MI->getNumTokens() == 1 && MI->isObjectLike() &&
4890ecaddeSKadir Cetinkaya          MI->getReplacementToken(0).getIdentifierInfo() == M.Name;
4990ecaddeSKadir Cetinkaya }
5090ecaddeSKadir Cetinkaya } // namespace
5190ecaddeSKadir Cetinkaya 
52c333b92bSSam McCall void walkUsed(llvm::ArrayRef<Decl *> ASTRoots,
53c333b92bSSam McCall               llvm::ArrayRef<SymbolReference> MacroRefs,
5490ecaddeSKadir Cetinkaya               const PragmaIncludes *PI, const Preprocessor &PP,
5577955480SHaojian Wu               UsedSymbolCB CB) {
5690ecaddeSKadir Cetinkaya   const auto &SM = PP.getSourceManager();
5726757732SSam McCall   // This is duplicated in writeHTMLReport, changes should be mirrored there.
58ce286eccSKadir Cetinkaya   tooling::stdlib::Recognizer Recognizer;
59ce286eccSKadir Cetinkaya   for (auto *Root : ASTRoots) {
60d19ba74dSKadir Cetinkaya     walkAST(*Root, [&](SourceLocation Loc, NamedDecl &ND, RefType RT) {
61481f8885SViktoriia Bakalova       auto FID = SM.getFileID(SM.getSpellingLoc(Loc));
62481f8885SViktoriia Bakalova       if (FID != SM.getMainFileID() && FID != SM.getPreambleFileID())
63bf6e6551SHaojian Wu         return;
64481f8885SViktoriia Bakalova       // FIXME: Most of the work done here is repetitive. It might be useful to
6529a8eec1SKadir Cetinkaya       // have a cache/batching.
663b59842aSHaojian Wu       SymbolReference SymRef{ND, Loc, RT};
67*ec6c3448Skadir çetinkaya       return CB(SymRef, headersForSymbol(ND, PP, PI));
68ce286eccSKadir Cetinkaya     });
69ce286eccSKadir Cetinkaya   }
7038cccb90SSam McCall   for (const SymbolReference &MacroRef : MacroRefs) {
7138cccb90SSam McCall     assert(MacroRef.Target.kind() == Symbol::Macro);
7290ecaddeSKadir Cetinkaya     if (!SM.isWrittenInMainFile(SM.getSpellingLoc(MacroRef.RefLocation)) ||
7390ecaddeSKadir Cetinkaya         shouldIgnoreMacroReference(PP, MacroRef.Target.macro()))
74bf6e6551SHaojian Wu       continue;
75*ec6c3448Skadir çetinkaya     CB(MacroRef, headersForSymbol(MacroRef.Target, PP, PI));
7638cccb90SSam McCall   }
77ce286eccSKadir Cetinkaya }
78ce286eccSKadir Cetinkaya 
79507d766dSHaojian Wu AnalysisResults
80507d766dSHaojian Wu analyze(llvm::ArrayRef<Decl *> ASTRoots,
81507d766dSHaojian Wu         llvm::ArrayRef<SymbolReference> MacroRefs, const Includes &Inc,
8290ecaddeSKadir Cetinkaya         const PragmaIncludes *PI, const Preprocessor &PP,
83507d766dSHaojian Wu         llvm::function_ref<bool(llvm::StringRef)> HeaderFilter) {
8490ecaddeSKadir Cetinkaya   auto &SM = PP.getSourceManager();
8564d97136Skadir çetinkaya   const auto MainFile = *SM.getFileEntryRefForID(SM.getMainFileID());
861a8dd742SSam McCall   llvm::DenseSet<const Include *> Used;
874ef77d61Skadir çetinkaya   llvm::StringMap<Header> Missing;
8835c5e56bSBenjamin Kramer   constexpr auto DefaultHeaderFilter = [](llvm::StringRef) { return false; };
89507d766dSHaojian Wu   if (!HeaderFilter)
9035c5e56bSBenjamin Kramer     HeaderFilter = DefaultHeaderFilter;
91cb92511cSJan Svoboda   OptionalDirectoryEntryRef ResourceDir =
9243c20367SViktoriia Bakalova       PP.getHeaderSearchInfo().getModuleMap().getBuiltinDir();
9390ecaddeSKadir Cetinkaya   walkUsed(ASTRoots, MacroRefs, PI, PP,
941a8dd742SSam McCall            [&](const SymbolReference &Ref, llvm::ArrayRef<Header> Providers) {
951a8dd742SSam McCall              bool Satisfied = false;
961a8dd742SSam McCall              for (const Header &H : Providers) {
9743c20367SViktoriia Bakalova                if (H.kind() == Header::Physical &&
9843c20367SViktoriia Bakalova                    (H.physical() == MainFile ||
9964d97136Skadir çetinkaya                     H.physical().getDir() == ResourceDir)) {
1001a8dd742SSam McCall                  Satisfied = true;
10143c20367SViktoriia Bakalova                }
1021a8dd742SSam McCall                for (const Include *I : Inc.match(H)) {
1031a8dd742SSam McCall                  Used.insert(I);
1041a8dd742SSam McCall                  Satisfied = true;
1051a8dd742SSam McCall                }
1061a8dd742SSam McCall              }
10764d97136Skadir çetinkaya              // Bail out if we can't (or need not) insert an include.
10864d97136Skadir çetinkaya              if (Satisfied || Providers.empty() || Ref.RT != RefType::Explicit)
10964d97136Skadir çetinkaya                return;
11064d97136Skadir çetinkaya              if (HeaderFilter(Providers.front().resolvedPath()))
11164d97136Skadir çetinkaya                return;
1122f5dc596Skadir çetinkaya              // Check if we have any headers with the same spelling, in edge
1132f5dc596Skadir çetinkaya              // cases like `#include_next "foo.h"`, the user can't ever
1142f5dc596Skadir çetinkaya              // include the physical foo.h, but can have a spelling that
1152f5dc596Skadir çetinkaya              // refers to it.
1162f5dc596Skadir çetinkaya              auto Spelling = spellHeader(
1172f5dc596Skadir çetinkaya                  {Providers.front(), PP.getHeaderSearchInfo(), MainFile});
1182f5dc596Skadir çetinkaya              for (const Include *I : Inc.match(Header{Spelling})) {
1192f5dc596Skadir çetinkaya                Used.insert(I);
1202f5dc596Skadir çetinkaya                Satisfied = true;
1212f5dc596Skadir çetinkaya              }
1222f5dc596Skadir çetinkaya              if (!Satisfied)
1234ef77d61Skadir çetinkaya                Missing.try_emplace(std::move(Spelling), Providers.front());
1241a8dd742SSam McCall            });
1251a8dd742SSam McCall 
1261a8dd742SSam McCall   AnalysisResults Results;
12743fcfdb1SKadir Cetinkaya   for (const Include &I : Inc.all()) {
128507d766dSHaojian Wu     if (Used.contains(&I) || !I.Resolved ||
12964d97136Skadir çetinkaya         HeaderFilter(I.Resolved->getName()) ||
13064d97136Skadir çetinkaya         I.Resolved->getDir() == ResourceDir)
13143fcfdb1SKadir Cetinkaya       continue;
13243fcfdb1SKadir Cetinkaya     if (PI) {
13343974333SKadir Cetinkaya       if (PI->shouldKeep(*I.Resolved))
13443fcfdb1SKadir Cetinkaya         continue;
13543fcfdb1SKadir Cetinkaya       // Check if main file is the public interface for a private header. If so
13643fcfdb1SKadir Cetinkaya       // we shouldn't diagnose it as unused.
137f6307b26SSam McCall       if (auto PHeader = PI->getPublic(*I.Resolved); !PHeader.empty()) {
13843fcfdb1SKadir Cetinkaya         PHeader = PHeader.trim("<>\"");
13943fcfdb1SKadir Cetinkaya         // Since most private -> public mappings happen in a verbatim way, we
14043fcfdb1SKadir Cetinkaya         // check textually here. This might go wrong in presence of symlinks or
14143fcfdb1SKadir Cetinkaya         // header mappings. But that's not different than rest of the places.
14264d97136Skadir çetinkaya         if (MainFile.getName().ends_with(PHeader))
14343fcfdb1SKadir Cetinkaya           continue;
14443fcfdb1SKadir Cetinkaya       }
14543fcfdb1SKadir Cetinkaya     }
1461a8dd742SSam McCall     Results.Unused.push_back(&I);
14743fcfdb1SKadir Cetinkaya   }
1484ef77d61Skadir çetinkaya   for (auto &E : Missing)
1494ef77d61Skadir çetinkaya     Results.Missing.emplace_back(E.first().str(), E.second);
1501a8dd742SSam McCall   llvm::sort(Results.Missing);
1511a8dd742SSam McCall   return Results;
1521a8dd742SSam McCall }
1531a8dd742SSam McCall 
1547f3d2cd7SHaojian Wu std::string fixIncludes(const AnalysisResults &Results,
1557f3d2cd7SHaojian Wu                         llvm::StringRef FileName, llvm::StringRef Code,
1561a8dd742SSam McCall                         const format::FormatStyle &Style) {
1571a8dd742SSam McCall   assert(Style.isCpp() && "Only C++ style supports include insertions!");
1581a8dd742SSam McCall   tooling::Replacements R;
1591a8dd742SSam McCall   // Encode insertions/deletions in the magic way clang-format understands.
1601a8dd742SSam McCall   for (const Include *I : Results.Unused)
1617f3d2cd7SHaojian Wu     cantFail(R.add(tooling::Replacement(FileName, UINT_MAX, 1, I->quote())));
1624ef77d61Skadir çetinkaya   for (auto &[Spelled, _] : Results.Missing)
1634ef77d61Skadir çetinkaya     cantFail(R.add(
1644ef77d61Skadir çetinkaya         tooling::Replacement(FileName, UINT_MAX, 0, "#include " + Spelled)));
1651a8dd742SSam McCall   // "cleanup" actually turns the UINT_MAX replacements into concrete edits.
1661a8dd742SSam McCall   auto Positioned = cantFail(format::cleanupAroundReplacements(Code, R, Style));
1671a8dd742SSam McCall   return cantFail(tooling::applyAllReplacements(Code, Positioned));
1681a8dd742SSam McCall }
1691a8dd742SSam McCall 
170ce286eccSKadir Cetinkaya } // namespace clang::include_cleaner
171