diff options
Diffstat (limited to 'clang-tools-extra/clangd/IncludeCleaner.cpp')
| -rw-r--r-- | clang-tools-extra/clangd/IncludeCleaner.cpp | 375 |
1 files changed, 88 insertions, 287 deletions
diff --git a/clang-tools-extra/clangd/IncludeCleaner.cpp b/clang-tools-extra/clangd/IncludeCleaner.cpp index 0d74b888557e..7d523d9a6364 100644 --- a/clang-tools-extra/clangd/IncludeCleaner.cpp +++ b/clang-tools-extra/clangd/IncludeCleaner.cpp @@ -8,55 +8,32 @@ #include "IncludeCleaner.h" #include "Config.h" -#include "Diagnostics.h" #include "Headers.h" #include "ParsedAST.h" #include "Protocol.h" #include "SourceCode.h" -#include "URI.h" #include "clang-include-cleaner/Analysis.h" #include "clang-include-cleaner/Types.h" #include "index/CanonicalIncludes.h" #include "support/Logger.h" -#include "support/Path.h" #include "support/Trace.h" #include "clang/AST/ASTContext.h" -#include "clang/AST/DeclCXX.h" -#include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/RecursiveASTVisitor.h" -#include "clang/AST/TemplateName.h" -#include "clang/AST/Type.h" -#include "clang/Basic/Diagnostic.h" -#include "clang/Basic/LLVM.h" #include "clang/Basic/SourceLocation.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/HeaderIncludes.h" -#include "clang/Tooling/Inclusions/IncludeStyle.h" -#include "clang/Tooling/Inclusions/StandardLibrary.h" #include "clang/Tooling/Syntax/Tokens.h" #include "llvm/ADT/ArrayRef.h" -#include "llvm/ADT/DenseSet.h" -#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/STLFunctionalExtras.h" #include "llvm/ADT/SmallString.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSet.h" -#include "llvm/Support/Casting.h" -#include "llvm/Support/Error.h" -#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" #include "llvm/Support/Regex.h" -#include <iterator> +#include <functional> #include <optional> -#include <string> -#include <vector> namespace clang { namespace clangd { @@ -281,17 +258,6 @@ void findReferencedMacros(const SourceManager &SM, Preprocessor &PP, } } -bool isFilteredByConfig(const Config &Cfg, llvm::StringRef HeaderPath) { - // Convert the path to Unix slashes and try to match against the filter. - llvm::SmallString<64> NormalizedPath(HeaderPath); - llvm::sys::path::native(NormalizedPath, llvm::sys::path::Style::posix); - for (auto &Filter : Cfg.Diagnostics.Includes.IgnoreHeader) { - if (Filter(NormalizedPath)) - return true; - } - return false; -} - static bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST, const Config &Cfg) { if (Inc.BehindPragmaKeep) @@ -322,10 +288,14 @@ static bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST, FE->getName()); return false; } - - if (isFilteredByConfig(Cfg, Inc.Resolved)) { - dlog("{0} header is filtered out by the configuration", FE->getName()); - return false; + for (auto &Filter : Cfg.Diagnostics.Includes.IgnoreHeader) { + // Convert the path to Unix slashes and try to match against the filter. + llvm::SmallString<64> Path(Inc.Resolved); + llvm::sys::path::native(Path, llvm::sys::path::Style::posix); + if (Filter(Path)) { + dlog("{0} header is filtered out by the configuration", FE->getName()); + return false; + } } return true; } @@ -355,195 +325,6 @@ FileID headerResponsible(FileID ID, const SourceManager &SM, return ID; } -include_cleaner::Includes -convertIncludes(const SourceManager &SM, - const llvm::ArrayRef<Inclusion> MainFileIncludes) { - include_cleaner::Includes Includes; - for (const Inclusion &Inc : MainFileIncludes) { - include_cleaner::Include TransformedInc; - llvm::StringRef WrittenRef = llvm::StringRef(Inc.Written); - TransformedInc.Spelled = WrittenRef.trim("\"<>"); - TransformedInc.HashLocation = - SM.getComposedLoc(SM.getMainFileID(), Inc.HashOffset); - TransformedInc.Line = Inc.HashLine + 1; - TransformedInc.Angled = WrittenRef.starts_with("<"); - auto FE = SM.getFileManager().getFile(Inc.Resolved); - if (!FE) { - elog("IncludeCleaner: Failed to get an entry for resolved path {0}: {1}", - Inc.Resolved, FE.getError().message()); - continue; - } - TransformedInc.Resolved = *FE; - Includes.add(std::move(TransformedInc)); - } - return Includes; -} - -std::string spellHeader(ParsedAST &AST, const FileEntry *MainFile, - include_cleaner::Header Provider) { - if (Provider.kind() == include_cleaner::Header::Physical) { - if (auto CanonicalPath = - getCanonicalPath(Provider.physical(), AST.getSourceManager())) { - std::string SpelledHeader = - llvm::cantFail(URI::includeSpelling(URI::create(*CanonicalPath))); - if (!SpelledHeader.empty()) - return SpelledHeader; - } - } - return include_cleaner::spellHeader( - Provider, AST.getPreprocessor().getHeaderSearchInfo(), MainFile); -} - -std::vector<include_cleaner::SymbolReference> -collectMacroReferences(ParsedAST &AST) { - const auto &SM = AST.getSourceManager(); - // FIXME: !!this is a hacky way to collect macro references. - std::vector<include_cleaner::SymbolReference> Macros; - auto &PP = AST.getPreprocessor(); - for (const syntax::Token &Tok : - AST.getTokens().spelledTokens(SM.getMainFileID())) { - auto Macro = locateMacroAt(Tok, PP); - if (!Macro) - continue; - if (auto DefLoc = Macro->Info->getDefinitionLoc(); DefLoc.isValid()) - Macros.push_back( - {Tok.location(), - include_cleaner::Macro{/*Name=*/PP.getIdentifierInfo(Tok.text(SM)), - DefLoc}, - include_cleaner::RefType::Explicit}); - } - return Macros; -} - -llvm::StringRef getResolvedPath(const include_cleaner::Header &SymProvider) { - switch (SymProvider.kind()) { - case include_cleaner::Header::Physical: - return SymProvider.physical()->tryGetRealPathName(); - case include_cleaner::Header::Standard: - return SymProvider.standard().name().trim("<>\""); - case include_cleaner::Header::Verbatim: - return SymProvider.verbatim().trim("<>\""); - } - llvm_unreachable("Unknown header kind"); -} - -std::string getSymbolName(const include_cleaner::Symbol &Sym) { - switch (Sym.kind()) { - case include_cleaner::Symbol::Macro: - return Sym.macro().Name->getName().str(); - case include_cleaner::Symbol::Declaration: - return llvm::dyn_cast<NamedDecl>(&Sym.declaration()) - ->getQualifiedNameAsString(); - } - llvm_unreachable("Unknown symbol kind"); -} - -std::vector<Diag> generateMissingIncludeDiagnostics( - ParsedAST &AST, llvm::ArrayRef<MissingIncludeDiagInfo> MissingIncludes, - llvm::StringRef Code) { - std::vector<Diag> Result; - const Config &Cfg = Config::current(); - if (Cfg.Diagnostics.MissingIncludes != Config::IncludesPolicy::Strict || - Cfg.Diagnostics.SuppressAll || - Cfg.Diagnostics.Suppress.contains("missing-includes")) { - return Result; - } - - const SourceManager &SM = AST.getSourceManager(); - const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID()); - - auto FileStyle = format::getStyle( - format::DefaultFormatStyle, AST.tuPath(), format::DefaultFallbackStyle, - Code, &SM.getFileManager().getVirtualFileSystem()); - if (!FileStyle) { - elog("Couldn't infer style", FileStyle.takeError()); - FileStyle = format::getLLVMStyle(); - } - - tooling::HeaderIncludes HeaderIncludes(AST.tuPath(), Code, - FileStyle->IncludeStyle); - for (const auto &SymbolWithMissingInclude : MissingIncludes) { - llvm::StringRef ResolvedPath = - getResolvedPath(SymbolWithMissingInclude.Providers.front()); - if (isFilteredByConfig(Cfg, ResolvedPath)) { - dlog("IncludeCleaner: not diagnosing missing include {0}, filtered by " - "config", - ResolvedPath); - continue; - } - - std::string Spelling = - spellHeader(AST, MainFile, SymbolWithMissingInclude.Providers.front()); - llvm::StringRef HeaderRef{Spelling}; - bool Angled = HeaderRef.starts_with("<"); - // We might suggest insertion of an existing include in edge cases, e.g., - // include is present in a PP-disabled region, or spelling of the header - // turns out to be the same as one of the unresolved includes in the - // main file. - std::optional<tooling::Replacement> Replacement = HeaderIncludes.insert( - HeaderRef.trim("\"<>"), Angled, tooling::IncludeDirective::Include); - if (!Replacement.has_value()) - continue; - - Diag &D = Result.emplace_back(); - D.Message = - llvm::formatv("No header providing \"{0}\" is directly included", - getSymbolName(SymbolWithMissingInclude.Symbol)); - D.Name = "missing-includes"; - D.Source = Diag::DiagSource::Clangd; - D.File = AST.tuPath(); - D.InsideMainFile = true; - D.Severity = DiagnosticsEngine::Warning; - D.Range = clangd::Range{ - offsetToPosition(Code, - SymbolWithMissingInclude.SymRefRange.beginOffset()), - offsetToPosition(Code, - SymbolWithMissingInclude.SymRefRange.endOffset())}; - auto &F = D.Fixes.emplace_back(); - F.Message = "#include " + Spelling; - TextEdit Edit = replacementToEdit(Code, *Replacement); - F.Edits.emplace_back(std::move(Edit)); - } - return Result; -} - -std::vector<Diag> generateUnusedIncludeDiagnostics( - PathRef FileName, llvm::ArrayRef<const Inclusion *> UnusedIncludes, - llvm::StringRef Code) { - std::vector<Diag> Result; - const Config &Cfg = Config::current(); - if (Cfg.Diagnostics.UnusedIncludes == Config::IncludesPolicy::None || - Cfg.Diagnostics.SuppressAll || - Cfg.Diagnostics.Suppress.contains("unused-includes")) { - return Result; - } - for (const auto *Inc : UnusedIncludes) { - Diag &D = Result.emplace_back(); - D.Message = - llvm::formatv("included header {0} is not used directly", - llvm::sys::path::filename( - Inc->Written.substr(1, Inc->Written.size() - 2), - llvm::sys::path::Style::posix)); - D.Name = "unused-includes"; - D.Source = Diag::DiagSource::Clangd; - D.File = FileName; - D.InsideMainFile = true; - D.Severity = DiagnosticsEngine::Warning; - D.Tags.push_back(Unnecessary); - D.Range = getDiagnosticRange(Code, Inc->HashOffset); - // FIXME(kirillbobyrev): Removing inclusion might break the code if the - // used headers are only reachable transitively through this one. Suggest - // including them directly instead. - // FIXME(kirillbobyrev): Add fix suggestion for adding IWYU pragmas - // (keep/export) remove the warning once we support IWYU pragmas. - auto &F = D.Fixes.emplace_back(); - F.Message = "remove #include directive"; - F.Edits.emplace_back(); - F.Edits.back().range.start.line = Inc->HashLine; - F.Edits.back().range.end.line = Inc->HashLine + 1; - } - return Result; -} } // namespace ReferencedLocations findReferencedLocations(ASTContext &Ctx, Preprocessor &PP, @@ -693,85 +474,105 @@ std::vector<const Inclusion *> computeUnusedIncludes(ParsedAST &AST) { translateToHeaderIDs(ReferencedFiles, AST.getIncludeStructure(), SM); return getUnused(AST, ReferencedHeaders, ReferencedFiles.SpelledUmbrellas); } - -IncludeCleanerFindings computeIncludeCleanerFindings(ParsedAST &AST) { +std::vector<const Inclusion *> +computeUnusedIncludesExperimental(ParsedAST &AST) { const auto &SM = AST.getSourceManager(); const auto &Includes = AST.getIncludeStructure(); - include_cleaner::Includes ConvertedIncludes = - convertIncludes(SM, Includes.MainFileIncludes); - const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID()); - std::vector<include_cleaner::SymbolReference> Macros = - collectMacroReferences(AST); - std::vector<MissingIncludeDiagInfo> MissingIncludes; + // FIXME: !!this is a hacky way to collect macro references. + std::vector<include_cleaner::SymbolReference> Macros; + auto &PP = AST.getPreprocessor(); + for (const syntax::Token &Tok : + AST.getTokens().spelledTokens(SM.getMainFileID())) { + auto Macro = locateMacroAt(Tok, PP); + if (!Macro) + continue; + if (auto DefLoc = Macro->Info->getDefinitionLoc(); DefLoc.isValid()) + Macros.push_back( + {Tok.location(), + include_cleaner::Macro{/*Name=*/PP.getIdentifierInfo(Tok.text(SM)), + DefLoc}, + include_cleaner::RefType::Explicit}); + } llvm::DenseSet<IncludeStructure::HeaderID> Used; - trace::Span Tracer("include_cleaner::walkUsed"); include_cleaner::walkUsed( AST.getLocalTopLevelDecls(), /*MacroRefs=*/Macros, AST.getPragmaIncludes(), SM, [&](const include_cleaner::SymbolReference &Ref, llvm::ArrayRef<include_cleaner::Header> Providers) { - bool Satisfied = false; for (const auto &H : Providers) { - if (H.kind() == include_cleaner::Header::Physical && - H.physical() == MainFile) { - Satisfied = true; - continue; - } - for (auto *Inc : ConvertedIncludes.match(H)) { - Satisfied = true; - auto HeaderID = Includes.getID(Inc->Resolved); - assert(HeaderID.has_value() && - "ConvertedIncludes only contains resolved includes."); - Used.insert(*HeaderID); + switch (H.kind()) { + case include_cleaner::Header::Physical: + if (auto HeaderID = Includes.getID(H.physical())) + Used.insert(*HeaderID); + break; + case include_cleaner::Header::Standard: + for (auto HeaderID : Includes.StdlibHeaders.lookup(H.standard())) + Used.insert(HeaderID); + break; + case include_cleaner::Header::Verbatim: + for (auto *Inc : + Includes.mainFileIncludesWithSpelling(H.verbatim())) { + if (!Inc->HeaderID.has_value()) + continue; + IncludeStructure::HeaderID ID = + static_cast<IncludeStructure::HeaderID>(*Inc->HeaderID); + Used.insert(ID); + } + break; } } - - if (Satisfied || Providers.empty() || - Ref.RT != include_cleaner::RefType::Explicit) - return; - - auto &Tokens = AST.getTokens(); - auto SpelledForExpanded = - Tokens.spelledForExpanded(Tokens.expandedTokens(Ref.RefLocation)); - if (!SpelledForExpanded) - return; - - auto Range = syntax::Token::range(SM, SpelledForExpanded->front(), - SpelledForExpanded->back()); - MissingIncludeDiagInfo DiagInfo{Ref.Target, Range, Providers}; - MissingIncludes.push_back(std::move(DiagInfo)); }); - std::vector<const Inclusion *> UnusedIncludes = - getUnused(AST, Used, /*ReferencedPublicHeaders*/ {}); - return {std::move(UnusedIncludes), std::move(MissingIncludes)}; + return getUnused(AST, Used, /*ReferencedPublicHeaders*/ {}); } -std::vector<Diag> issueIncludeCleanerDiagnostics(ParsedAST &AST, +std::vector<Diag> issueUnusedIncludesDiagnostics(ParsedAST &AST, llvm::StringRef Code) { + const Config &Cfg = Config::current(); + if (Cfg.Diagnostics.UnusedIncludes == Config::UnusedIncludesPolicy::None || + Cfg.Diagnostics.SuppressAll || + Cfg.Diagnostics.Suppress.contains("unused-includes")) + return {}; // Interaction is only polished for C/CPP. if (AST.getLangOpts().ObjC) return {}; - - trace::Span Tracer("IncludeCleaner::issueIncludeCleanerDiagnostics"); - - const Config &Cfg = Config::current(); - IncludeCleanerFindings Findings; - if (Cfg.Diagnostics.MissingIncludes == Config::IncludesPolicy::Strict || - Cfg.Diagnostics.UnusedIncludes == Config::IncludesPolicy::Experiment) { - // will need include-cleaner results, call it once - Findings = computeIncludeCleanerFindings(AST); + trace::Span Tracer("IncludeCleaner::issueUnusedIncludesDiagnostics"); + std::vector<Diag> Result; + std::string FileName = + AST.getSourceManager() + .getFileEntryRefForID(AST.getSourceManager().getMainFileID()) + ->getName() + .str(); + const auto &UnusedIncludes = + Cfg.Diagnostics.UnusedIncludes == Config::UnusedIncludesPolicy::Experiment + ? computeUnusedIncludesExperimental(AST) + : computeUnusedIncludes(AST); + for (const auto *Inc : UnusedIncludes) { + Diag D; + D.Message = + llvm::formatv("included header {0} is not used directly", + llvm::sys::path::filename( + Inc->Written.substr(1, Inc->Written.size() - 2), + llvm::sys::path::Style::posix)); + D.Name = "unused-includes"; + D.Source = Diag::DiagSource::Clangd; + D.File = FileName; + D.Severity = DiagnosticsEngine::Warning; + D.Tags.push_back(Unnecessary); + D.Range = getDiagnosticRange(Code, Inc->HashOffset); + // FIXME(kirillbobyrev): Removing inclusion might break the code if the + // used headers are only reachable transitively through this one. Suggest + // including them directly instead. + // FIXME(kirillbobyrev): Add fix suggestion for adding IWYU pragmas + // (keep/export) remove the warning once we support IWYU pragmas. + D.Fixes.emplace_back(); + D.Fixes.back().Message = "remove #include directive"; + D.Fixes.back().Edits.emplace_back(); + D.Fixes.back().Edits.back().range.start.line = Inc->HashLine; + D.Fixes.back().Edits.back().range.end.line = Inc->HashLine + 1; + D.InsideMainFile = true; + Result.push_back(std::move(D)); } - - std::vector<Diag> Result = generateUnusedIncludeDiagnostics( - AST.tuPath(), - Cfg.Diagnostics.UnusedIncludes == Config::IncludesPolicy::Strict - ? computeUnusedIncludes(AST) - : Findings.UnusedIncludes, - Code); - llvm::move( - generateMissingIncludeDiagnostics(AST, Findings.MissingIncludes, Code), - std::back_inserter(Result)); return Result; } |
