blob: f1cd72f877ca21bfa7eeab6b7278383350350369 [file] [log] [blame]
//===--- 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