158 lines
6.5 KiB
C++
158 lines
6.5 KiB
C++
|
//===--- 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()))
|
||
|
Missing.insert(spellHeader(
|
||
|
{Providers.front(), PP.getHeaderSearchInfo(), MainFile}));
|
||
|
});
|
||
|
|
||
|
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
|