[clang-tools-extra] 40f361a - [clangd] Include Cleaner: ignore headers with IWYU export pragmas
Kirill Bobyrev via cfe-commits
cfe-commits at lists.llvm.org
Mon May 16 01:14:09 PDT 2022
Author: Kirill Bobyrev
Date: 2022-05-16T10:13:47+02:00
New Revision: 40f361ace3e9a9c24bd99300216aeabd49ad99bb
URL: https://github.com/llvm/llvm-project/commit/40f361ace3e9a9c24bd99300216aeabd49ad99bb
DIFF: https://github.com/llvm/llvm-project/commit/40f361ace3e9a9c24bd99300216aeabd49ad99bb.diff
LOG: [clangd] Include Cleaner: ignore headers with IWYU export pragmas
Disable the warnings with `IWYU pragma: export` or `begin_exports` +
`end_exports` until we have support for these pragmas. There are too many
false-positive warnings for the headers that have the correct pragmas for now
and it makes the user experience very unpleasant.
Reviewed By: sammccall
Differential Revision: https://reviews.llvm.org/D125468
Added:
Modified:
clang-tools-extra/clangd/Headers.cpp
clang-tools-extra/clangd/Headers.h
clang-tools-extra/clangd/IncludeCleaner.cpp
clang-tools-extra/clangd/unittests/HeadersTests.cpp
clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
Removed:
################################################################################
diff --git a/clang-tools-extra/clangd/Headers.cpp b/clang-tools-extra/clangd/Headers.cpp
index 9484bb7afa15b..6b934eae7b358 100644
--- a/clang-tools-extra/clangd/Headers.cpp
+++ b/clang-tools-extra/clangd/Headers.cpp
@@ -24,6 +24,7 @@ namespace clangd {
const char IWYUPragmaKeep[] = "// IWYU pragma: keep";
const char IWYUPragmaExport[] = "// IWYU pragma: export";
+const char IWYUPragmaBeginExports[] = "// IWYU pragma: begin_exports";
class IncludeStructure::RecordHeaders : public PPCallbacks,
public CommentHandler {
@@ -127,31 +128,45 @@ class IncludeStructure::RecordHeaders : public PPCallbacks,
}
}
- // Given:
- //
- // #include "foo.h"
- // #include "bar.h" // IWYU pragma: keep
- //
- // The order in which the callbacks will be triggered:
- //
- // 1. InclusionDirective("foo.h")
- // 2. HandleComment("// IWYU pragma: keep")
- // 3. InclusionDirective("bar.h")
- //
- // HandleComment will store the last location of "IWYU pragma: keep" (or
- // export) comment in the main file, so that when InclusionDirective is
- // called, it will know that the next inclusion is behind the IWYU pragma.
bool HandleComment(Preprocessor &PP, SourceRange Range) override {
- if (!inMainFile() || Range.getBegin().isMacroID())
- return false;
bool Err = false;
llvm::StringRef Text = SM.getCharacterData(Range.getBegin(), &Err);
- if (Err && !Text.consume_front(IWYUPragmaKeep) &&
- !Text.consume_front(IWYUPragmaExport))
+ if (Err)
return false;
- unsigned Offset = SM.getFileOffset(Range.getBegin());
- LastPragmaKeepInMainFileLine =
- SM.getLineNumber(SM.getFileID(Range.getBegin()), Offset) - 1;
+ if (inMainFile()) {
+ // Given:
+ //
+ // #include "foo.h"
+ // #include "bar.h" // IWYU pragma: keep
+ //
+ // The order in which the callbacks will be triggered:
+ //
+ // 1. InclusionDirective("foo.h")
+ // 2. handleCommentInMainFile("// IWYU pragma: keep")
+ // 3. InclusionDirective("bar.h")
+ //
+ // This code stores the last location of "IWYU pragma: keep" (or export)
+ // comment in the main file, so that when InclusionDirective is called, it
+ // will know that the next inclusion is behind the IWYU pragma.
+ // FIXME: Support "IWYU pragma: begin_exports" and "IWYU pragma:
+ // end_exports".
+ if (!Text.startswith(IWYUPragmaExport) &&
+ !Text.startswith(IWYUPragmaKeep))
+ return false;
+ unsigned Offset = SM.getFileOffset(Range.getBegin());
+ LastPragmaKeepInMainFileLine =
+ SM.getLineNumber(SM.getMainFileID(), Offset) - 1;
+ } else {
+ // Memorize headers that that have export pragmas in them. Include Cleaner
+ // does not support them properly yet, so they will be not marked as
+ // unused.
+ // FIXME: Once IncludeCleaner supports export pragmas, remove this.
+ if (!Text.startswith(IWYUPragmaExport) &&
+ !Text.startswith(IWYUPragmaBeginExports))
+ return false;
+ Out->HasIWYUPragmas.insert(
+ *Out->getID(SM.getFileEntryForID(SM.getFileID(Range.getBegin()))));
+ }
return false;
}
@@ -237,8 +252,7 @@ IncludeStructure::getID(const FileEntry *Entry) const {
return It->second;
}
-IncludeStructure::HeaderID
-IncludeStructure::getOrCreateID(FileEntryRef Entry) {
+IncludeStructure::HeaderID IncludeStructure::getOrCreateID(FileEntryRef Entry) {
// Main file's FileEntry was not known at IncludeStructure creation time.
if (&Entry.getFileEntry() == MainFileEntry) {
if (RealPathNames.front().empty())
diff --git a/clang-tools-extra/clangd/Headers.h b/clang-tools-extra/clangd/Headers.h
index 5da0200afd1aa..b17107535e3d9 100644
--- a/clang-tools-extra/clangd/Headers.h
+++ b/clang-tools-extra/clangd/Headers.h
@@ -145,6 +145,10 @@ class IncludeStructure {
return !NonSelfContained.contains(ID);
}
+ bool hasIWYUExport(HeaderID ID) const {
+ return HasIWYUPragmas.contains(ID);
+ }
+
// Return all transitively reachable files.
llvm::ArrayRef<std::string> allHeaders() const { return RealPathNames; }
@@ -185,6 +189,9 @@ class IncludeStructure {
// Contains HeaderIDs of all non self-contained entries in the
// IncludeStructure.
llvm::DenseSet<HeaderID> NonSelfContained;
+ // Contains a set of headers that have either "IWYU pragma: export" or "IWYU
+ // pragma: begin_exports".
+ llvm::DenseSet<HeaderID> HasIWYUPragmas;
};
// Calculates insertion edit for including a new header in a file.
diff --git a/clang-tools-extra/clangd/IncludeCleaner.cpp b/clang-tools-extra/clangd/IncludeCleaner.cpp
index 5b7f01d82fbac..f2890369af32a 100644
--- a/clang-tools-extra/clangd/IncludeCleaner.cpp
+++ b/clang-tools-extra/clangd/IncludeCleaner.cpp
@@ -268,13 +268,17 @@ static bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST,
return true;
return false;
}
- // Headers without include guards have side effects and are not
- // self-contained, skip them.
assert(Inc.HeaderID);
+ auto HID = static_cast<IncludeStructure::HeaderID>(*Inc.HeaderID);
+ // FIXME: Ignore the headers with IWYU export pragmas for now, remove this
+ // check when we have more precise tracking of exported headers.
+ if (AST.getIncludeStructure().hasIWYUExport(HID))
+ return false;
auto FE = AST.getSourceManager().getFileManager().getFileRef(
- AST.getIncludeStructure().getRealPath(
- static_cast<IncludeStructure::HeaderID>(*Inc.HeaderID)));
+ AST.getIncludeStructure().getRealPath(HID));
assert(FE);
+ // Headers without include guards have side effects and are not
+ // self-contained, skip them.
if (!AST.getPreprocessor().getHeaderSearchInfo().isFileMultipleIncludeGuarded(
&FE->getFileEntry())) {
dlog("{0} doesn't have header guard and will not be considered unused",
diff --git a/clang-tools-extra/clangd/unittests/HeadersTests.cpp b/clang-tools-extra/clangd/unittests/HeadersTests.cpp
index 30bac23bc602c..a9872cfae2a02 100644
--- a/clang-tools-extra/clangd/unittests/HeadersTests.cpp
+++ b/clang-tools-extra/clangd/unittests/HeadersTests.cpp
@@ -406,7 +406,7 @@ void foo();
#define RECURSIVE_H
#include "recursive.h"
-
+
#endif // RECURSIVE_H
)cpp";
@@ -418,6 +418,33 @@ void foo();
EXPECT_FALSE(Includes.isSelfContained(getID("pp_depend.h", Includes)));
}
+TEST_F(HeadersTest, HasIWYUPragmas) {
+ FS.Files[MainFile] = R"cpp(
+#include "export.h"
+#include "begin_exports.h"
+#include "none.h"
+)cpp";
+ FS.Files["export.h"] = R"cpp(
+#pragma once
+#include "none.h" // IWYU pragma: export
+)cpp";
+ FS.Files["begin_exports.h"] = R"cpp(
+#pragma once
+// IWYU pragma: begin_exports
+#include "none.h"
+// IWYU pragma: end_exports
+)cpp";
+ FS.Files["none.h"] = R"cpp(
+#pragma once
+// Not a pragma.
+)cpp";
+
+ auto Includes = collectIncludes();
+ EXPECT_TRUE(Includes.hasIWYUExport(getID("export.h", Includes)));
+ EXPECT_TRUE(Includes.hasIWYUExport(getID("begin_exports.h", Includes)));
+ EXPECT_FALSE(Includes.hasIWYUExport(getID("none.h", Includes)));
+}
+
} // namespace
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
index c4e9095ac5aa1..527db2a5d2193 100644
--- a/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
+++ b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
@@ -544,8 +544,7 @@ TEST(IncludeCleaner, IWYUPragmas) {
EXPECT_TRUE(
ReferencedFiles.User.contains(AST.getSourceManager().getMainFileID()));
EXPECT_THAT(AST.getDiagnostics(), llvm::ValueIs(IsEmpty()));
- auto Unused = computeUnusedIncludes(AST);
- EXPECT_THAT(Unused, IsEmpty());
+ EXPECT_THAT(computeUnusedIncludes(AST), IsEmpty());
}
TEST(IncludeCleaner, RecursiveInclusion) {
@@ -576,8 +575,34 @@ TEST(IncludeCleaner, RecursiveInclusion) {
findReferencedLocations(AST), AST.getIncludeStructure(),
AST.getCanonicalIncludes(), AST.getSourceManager());
EXPECT_THAT(AST.getDiagnostics(), llvm::ValueIs(IsEmpty()));
- auto Unused = computeUnusedIncludes(AST);
- EXPECT_THAT(Unused, IsEmpty());
+ EXPECT_THAT(computeUnusedIncludes(AST), IsEmpty());
+}
+
+TEST(IncludeCleaner, IWYUPragmaExport) {
+ TestTU TU;
+ TU.Code = R"cpp(
+ #include "foo.h"
+ )cpp";
+ TU.AdditionalFiles["foo.h"] = R"cpp(
+ #ifndef FOO_H
+ #define FOO_H
+
+ #include "bar.h" // IWYU pragma: export
+
+ #endif
+ )cpp";
+ TU.AdditionalFiles["bar.h"] = guard(R"cpp(
+ void bar() {}
+ )cpp");
+ ParsedAST AST = TU.build();
+
+ auto ReferencedFiles = findReferencedFiles(
+ findReferencedLocations(AST), AST.getIncludeStructure(),
+ AST.getCanonicalIncludes(), AST.getSourceManager());
+ EXPECT_THAT(AST.getDiagnostics(), llvm::ValueIs(IsEmpty()));
+ // FIXME: This is not correct: foo.h is unused but is not diagnosed as such
+ // because we ignore headers with IWYU export pragmas for now.
+ EXPECT_THAT(computeUnusedIncludes(AST), IsEmpty());
}
} // namespace
More information about the cfe-commits
mailing list