File: Analysis.cpp

package info (click to toggle)
llvm-toolchain-19 1%3A19.1.7-3
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 1,998,520 kB
  • sloc: cpp: 6,951,680; ansic: 1,486,157; asm: 913,598; python: 232,024; f90: 80,126; objc: 75,281; lisp: 37,276; pascal: 16,990; sh: 10,009; ml: 5,058; perl: 4,724; awk: 3,523; makefile: 3,167; javascript: 2,504; xml: 892; fortran: 664; cs: 573
file content (168 lines) | stat: -rw-r--r-- 7,168 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//===--- Analysis.cpp -----------------------------------------------------===//
//
// 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 "clang-include-cleaner/Analysis.h"
#include "AnalysisInternal.h"
#include "clang-include-cleaner/IncludeSpeller.h"
#include "clang-include-cleaner/Record.h"
#include "clang-include-cleaner/Types.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/Basic/DirectoryEntry.h"
#include "clang/Basic/FileEntry.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Format/Format.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Tooling/Core/Replacement.h"
#include "clang/Tooling/Inclusions/StandardLibrary.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include <cassert>
#include <climits>
#include <string>

namespace clang::include_cleaner {

namespace {
bool shouldIgnoreMacroReference(const Preprocessor &PP, const Macro &M) {
  auto *MI = PP.getMacroInfo(M.Name);
  // Macros that expand to themselves are confusing from user's point of view.
  // They usually aspect the usage to be attributed to the underlying decl and
  // not the macro definition. So ignore such macros (e.g. std{in,out,err} are
  // implementation defined macros, that just resolve to themselves in
  // practice).
  return MI && MI->getNumTokens() == 1 && MI->isObjectLike() &&
         MI->getReplacementToken(0).getIdentifierInfo() == M.Name;
}
} // namespace

void walkUsed(llvm::ArrayRef<Decl *> ASTRoots,
              llvm::ArrayRef<SymbolReference> MacroRefs,
              const PragmaIncludes *PI, const Preprocessor &PP,
              UsedSymbolCB CB) {
  const auto &SM = PP.getSourceManager();
  // This is duplicated in writeHTMLReport, changes should be mirrored there.
  tooling::stdlib::Recognizer Recognizer;
  for (auto *Root : ASTRoots) {
    walkAST(*Root, [&](SourceLocation Loc, NamedDecl &ND, RefType RT) {
      auto FID = SM.getFileID(SM.getSpellingLoc(Loc));
      if (FID != SM.getMainFileID() && FID != SM.getPreambleFileID())
        return;
      // FIXME: Most of the work done here is repetitive. It might be useful to
      // have a cache/batching.
      SymbolReference SymRef{ND, Loc, RT};
      return CB(SymRef, headersForSymbol(ND, SM, PI));
    });
  }
  for (const SymbolReference &MacroRef : MacroRefs) {
    assert(MacroRef.Target.kind() == Symbol::Macro);
    if (!SM.isWrittenInMainFile(SM.getSpellingLoc(MacroRef.RefLocation)) ||
        shouldIgnoreMacroReference(PP, MacroRef.Target.macro()))
      continue;
    CB(MacroRef, headersForSymbol(MacroRef.Target, SM, PI));
  }
}

AnalysisResults
analyze(llvm::ArrayRef<Decl *> ASTRoots,
        llvm::ArrayRef<SymbolReference> MacroRefs, const Includes &Inc,
        const PragmaIncludes *PI, const Preprocessor &PP,
        llvm::function_ref<bool(llvm::StringRef)> HeaderFilter) {
  auto &SM = PP.getSourceManager();
  const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID());
  llvm::DenseSet<const Include *> Used;
  llvm::StringSet<> Missing;
  if (!HeaderFilter)
    HeaderFilter = [](llvm::StringRef) { return false; };
  OptionalDirectoryEntryRef ResourceDir =
      PP.getHeaderSearchInfo().getModuleMap().getBuiltinDir();
  walkUsed(ASTRoots, MacroRefs, PI, PP,
           [&](const SymbolReference &Ref, llvm::ArrayRef<Header> Providers) {
             bool Satisfied = false;
             for (const Header &H : Providers) {
               if (H.kind() == Header::Physical &&
                   (H.physical() == MainFile ||
                    (ResourceDir && H.physical().getDir() == *ResourceDir))) {
                 Satisfied = true;
               }
               for (const Include *I : Inc.match(H)) {
                 Used.insert(I);
                 Satisfied = true;
               }
             }
             if (!Satisfied && !Providers.empty() &&
                 Ref.RT == RefType::Explicit &&
                 !HeaderFilter(Providers.front().resolvedPath())) {
               // Check if we have any headers with the same spelling, in edge
               // cases like `#include_next "foo.h"`, the user can't ever
               // include the physical foo.h, but can have a spelling that
               // refers to it.
               auto Spelling = spellHeader(
                   {Providers.front(), PP.getHeaderSearchInfo(), MainFile});
               for (const Include *I : Inc.match(Header{Spelling})) {
                 Used.insert(I);
                 Satisfied = true;
               }
               if (!Satisfied)
                 Missing.insert(std::move(Spelling));
             }
           });

  AnalysisResults Results;
  for (const Include &I : Inc.all()) {
    if (Used.contains(&I) || !I.Resolved ||
        HeaderFilter(I.Resolved->getFileEntry().tryGetRealPathName()) ||
        (ResourceDir && I.Resolved->getFileEntry().getDir() == *ResourceDir))
      continue;
    if (PI) {
      if (PI->shouldKeep(*I.Resolved))
        continue;
      // Check if main file is the public interface for a private header. If so
      // we shouldn't diagnose it as unused.
      if (auto PHeader = PI->getPublic(*I.Resolved); !PHeader.empty()) {
        PHeader = PHeader.trim("<>\"");
        // Since most private -> public mappings happen in a verbatim way, we
        // check textually here. This might go wrong in presence of symlinks or
        // header mappings. But that's not different than rest of the places.
        if (MainFile->tryGetRealPathName().ends_with(PHeader))
          continue;
      }
    }
    Results.Unused.push_back(&I);
  }
  for (llvm::StringRef S : Missing.keys())
    Results.Missing.push_back(S.str());
  llvm::sort(Results.Missing);
  return Results;
}

std::string fixIncludes(const AnalysisResults &Results,
                        llvm::StringRef FileName, llvm::StringRef Code,
                        const format::FormatStyle &Style) {
  assert(Style.isCpp() && "Only C++ style supports include insertions!");
  tooling::Replacements R;
  // Encode insertions/deletions in the magic way clang-format understands.
  for (const Include *I : Results.Unused)
    cantFail(R.add(tooling::Replacement(FileName, UINT_MAX, 1, I->quote())));
  for (llvm::StringRef Spelled : Results.Missing)
    cantFail(R.add(tooling::Replacement(FileName, UINT_MAX, 0,
                                        ("#include " + Spelled).str())));
  // "cleanup" actually turns the UINT_MAX replacements into concrete edits.
  auto Positioned = cantFail(format::cleanupAroundReplacements(Code, R, Style));
  return cantFail(tooling::applyAllReplacements(Code, Positioned));
}

} // namespace clang::include_cleaner