aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkadir çetinkaya <kadircet@google.com>2024-07-22 14:58:07 +0200
committerGitHub <noreply@github.com>2024-07-22 14:58:07 +0200
commit2f5dc596b5719e5ed7f6978dafbce994f425a033 (patch)
tree774cb6bc257346bdec36fb32a228fd5e37b5283b
parent5b8479bc28a8641f02be3d64f87770b9e0b1a427 (diff)
downloadllvm-2f5dc596b5719e5ed7f6978dafbce994f425a033.zip
llvm-2f5dc596b5719e5ed7f6978dafbce994f425a033.tar.gz
llvm-2f5dc596b5719e5ed7f6978dafbce994f425a033.tar.bz2
[IncludeCleaner] Also check for spellings of physical headers (#99843)
Some physical headers can have "conflicting" spellings, when same filename exists under two different include search paths. e.g. <stdlib.h> can be provided by both standard library and underlying libc headers. In such scenarios if the usage is from the latter include-search path, users can't spell it directly. This patch ensures we also consider spellings of such includes, in addition to their physical files to prevent conflicting suggestions.
-rw-r--r--clang-tools-extra/clangd/IncludeCleaner.cpp20
-rw-r--r--clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp22
-rw-r--r--clang-tools-extra/include-cleaner/lib/Analysis.cpp17
-rw-r--r--clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp26
4 files changed, 82 insertions, 3 deletions
diff --git a/clang-tools-extra/clangd/IncludeCleaner.cpp b/clang-tools-extra/clangd/IncludeCleaner.cpp
index dc5b7ec..e347061 100644
--- a/clang-tools-extra/clangd/IncludeCleaner.cpp
+++ b/clang-tools-extra/clangd/IncludeCleaner.cpp
@@ -401,6 +401,26 @@ computeIncludeCleanerFindings(ParsedAST &AST, bool AnalyzeAngledIncludes) {
Ref.RT != include_cleaner::RefType::Explicit)
return;
+ // 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.
+ // We postpone this check because spelling a header for every usage is
+ // expensive.
+ std::string Spelling = include_cleaner::spellHeader(
+ {Providers.front(), AST.getPreprocessor().getHeaderSearchInfo(),
+ MainFile});
+ for (auto *Inc :
+ ConvertedIncludes.match(include_cleaner::Header{Spelling})) {
+ Satisfied = true;
+ auto HeaderID =
+ AST.getIncludeStructure().getID(&Inc->Resolved->getFileEntry());
+ assert(HeaderID.has_value() &&
+ "ConvertedIncludes only contains resolved includes.");
+ Used.insert(*HeaderID);
+ }
+ if (Satisfied)
+ return;
+
// We actually always want to map usages to their spellings, but
// spelling locations can point into preamble section. Using these
// offsets could lead into crashes in presence of stale preambles. Hence
diff --git a/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
index 7027232..0ee748c 100644
--- a/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
+++ b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
@@ -644,6 +644,28 @@ TEST(IncludeCleaner, ResourceDirIsIgnored) {
EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
}
+TEST(IncludeCleaner, DifferentHeaderSameSpelling) {
+ // `foo` is declared in foo_inner/foo.h, but there's no way to spell it
+ // directly. Make sure we don't generate unusued/missing include findings in
+ // such cases.
+ auto TU = TestTU::withCode(R"cpp(
+ #include <foo.h>
+ void baz() {
+ foo();
+ }
+ )cpp");
+ TU.AdditionalFiles["foo/foo.h"] = guard("#include_next <foo.h>");
+ TU.AdditionalFiles["foo_inner/foo.h"] = guard(R"cpp(
+ void foo();
+ )cpp");
+ TU.ExtraArgs.push_back("-Ifoo");
+ TU.ExtraArgs.push_back("-Ifoo_inner");
+
+ auto AST = TU.build();
+ auto Findings = computeIncludeCleanerFindings(AST);
+ EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
+ EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
+}
} // namespace
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/include-cleaner/lib/Analysis.cpp b/clang-tools-extra/include-cleaner/lib/Analysis.cpp
index f1cd72f..68fe79d 100644
--- a/clang-tools-extra/include-cleaner/lib/Analysis.cpp
+++ b/clang-tools-extra/include-cleaner/lib/Analysis.cpp
@@ -105,9 +105,20 @@ analyze(llvm::ArrayRef<Decl *> ASTRoots,
}
if (!Satisfied && !Providers.empty() &&
Ref.RT == RefType::Explicit &&
- !HeaderFilter(Providers.front().resolvedPath()))
- Missing.insert(spellHeader(
- {Providers.front(), PP.getHeaderSearchInfo(), MainFile}));
+ !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;
diff --git a/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp b/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp
index 6558b68..5696c38 100644
--- a/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp
+++ b/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp
@@ -12,6 +12,7 @@
#include "clang-include-cleaner/Record.h"
#include "clang-include-cleaner/Types.h"
#include "clang/AST/ASTContext.h"
+#include "clang/AST/DeclBase.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/IdentifierTable.h"
#include "clang/Basic/SourceLocation.h"
@@ -296,6 +297,31 @@ TEST_F(AnalyzeTest, ResourceDirIsIgnored) {
EXPECT_THAT(Results.Missing, testing::IsEmpty());
}
+TEST_F(AnalyzeTest, DifferentHeaderSameSpelling) {
+ Inputs.ExtraArgs.push_back("-Ifoo");
+ Inputs.ExtraArgs.push_back("-Ifoo_inner");
+ // `foo` is declared in foo_inner/foo.h, but there's no way to spell it
+ // directly. Make sure we don't generate unusued/missing include findings in
+ // such cases.
+ Inputs.Code = R"cpp(
+ #include <foo.h>
+ void baz() {
+ foo();
+ }
+ )cpp";
+ Inputs.ExtraFiles["foo/foo.h"] = guard("#include_next <foo.h>");
+ Inputs.ExtraFiles["foo_inner/foo.h"] = guard(R"cpp(
+ void foo();
+ )cpp");
+ TestAST AST(Inputs);
+ std::vector<Decl *> DeclsInTU;
+ for (auto *D : AST.context().getTranslationUnitDecl()->decls())
+ DeclsInTU.push_back(D);
+ auto Results = analyze(DeclsInTU, {}, PP.Includes, &PI, AST.preprocessor());
+ EXPECT_THAT(Results.Unused, testing::IsEmpty());
+ EXPECT_THAT(Results.Missing, testing::IsEmpty());
+}
+
TEST(FixIncludes, Basic) {
llvm::StringRef Code = R"cpp(#include "d.h"
#include "a.h"