Skip to content

[include-cleaner] Attribute inherited member operators to the derived type - #240052

Open
Spikhalskiy wants to merge 2 commits into
llvm:mainfrom
Spikhalskiy:include-cleaner-inherited-member-operator
Open

Spikhalskiy wants to merge 2 commits into
llvm:mainfrom
Spikhalskiy:include-cleaner-inherited-member-operator

Conversation

@Spikhalskiy

Copy link
Copy Markdown
Contributor

A member operator call reported the provider of getArg(0)->getType(). For an operator inherited from a base class, that argument is a derived-to-base implicit cast, so the use was attributed to the base class instead of the type the operator was called on. VisitMemberExpr already strips implicit casts, so p.get() and p->x disagreed.

With libstdc++, std::shared_ptr inherits operator-> and operator* from std::__shared_ptr_access in bits/shared_ptr_base.h, so misc-include-cleaner reported <memory> as unused after p->x, while libc++, which declares them on std::shared_ptr, did not.

This strips implicit casts from the object argument the same way, and adds WalkAST tests for inherited operator+, operator-> and operator*.

Fixes #240048

… type

A member operator call reported the provider of `getArg(0)->getType()`. For an operator inherited from a base class, that argument is a derived-to-base implicit cast, so the use was attributed to the base class instead of the type the operator was called on. `VisitMemberExpr` already strips implicit casts, so `p.get()` and `p->x` disagreed.

With libstdc++, `std::shared_ptr` inherits `operator->` and `operator*` from `std::__shared_ptr_access` in `bits/shared_ptr_base.h`, so `misc-include-cleaner` reported `<memory>` as unused after `p->x`, while libc++, which declares them on `std::shared_ptr`, did not.

This strips implicit casts from the object argument the same way, and adds `WalkAST` tests for inherited `operator+`, `operator->` and `operator*`.

Fixes llvm#240048
@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-clang-tools-extra

Author: Dmitry Spikhalsky (Spikhalskiy)

Changes

A member operator call reported the provider of getArg(0)-&gt;getType(). For an operator inherited from a base class, that argument is a derived-to-base implicit cast, so the use was attributed to the base class instead of the type the operator was called on. VisitMemberExpr already strips implicit casts, so p.get() and p-&gt;x disagreed.

With libstdc++, std::shared_ptr inherits operator-&gt; and operator* from std::__shared_ptr_access in bits/shared_ptr_base.h, so misc-include-cleaner reported &lt;memory&gt; as unused after p-&gt;x, while libc++, which declares them on std::shared_ptr, did not.

This strips implicit casts from the object argument the same way, and adds WalkAST tests for inherited operator+, operator-&gt; and operator*.

Fixes #240048


Full diff: https://github.com/llvm/llvm-project/pull/240052.diff

3 Files Affected:

  • (modified) clang-tools-extra/docs/ReleaseNotes.md (+6)
  • (modified) clang-tools-extra/include-cleaner/lib/WalkAST.cpp (+4-2)
  • (modified) clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp (+15)
diff --git a/clang-tools-extra/docs/ReleaseNotes.md b/clang-tools-extra/docs/ReleaseNotes.md
index de205ae3343db9..c3481b91e6da87 100644
--- a/clang-tools-extra/docs/ReleaseNotes.md
+++ b/clang-tools-extra/docs/ReleaseNotes.md
@@ -283,6 +283,12 @@ infrastructure are described first, followed by tool-specific sections.
   {option}`IgnoreHeaders` patterns containing `/` against header paths on
   Windows.
 
+- Improved {doc}`misc-include-cleaner
+  <clang-tidy/checks/misc/include-cleaner>` check by attributing a member
+  operator inherited from a base class to the derived type, so the derived
+  type's header is no longer reported as unused. This made `<memory>` look
+  unused after `p->x` on a `std::shared_ptr` with libstdc++ but not libc++.
+
 - Fixed an infinite loop in {doc}`misc-multiple-inheritance
   <clang-tidy/checks/misc/multiple-inheritance>` when checking a class that
   inherits from itself or has a circular inheritance graph.
diff --git a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp
index 5449411371e601..68a2cef03d86f6 100644
--- a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp
+++ b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp
@@ -161,8 +161,10 @@ class ASTWalker : public RecursiveASTVisitor<ASTWalker> {
       return false;
     if (auto *CD = S->getCalleeDecl()) {
       if (isa<CXXMethodDecl>(CD)) {
-        // Treat this as a regular member reference.
-        report(S->getOperatorLoc(), getMemberProvider(S->getArg(0)->getType()),
+        // Treat this as a regular member reference. Like VisitMemberExpr, look
+        // through the derived-to-base cast an inherited operator adds.
+        report(S->getOperatorLoc(),
+               getMemberProvider(S->getArg(0)->IgnoreImpCasts()->getType()),
                RefType::Implicit);
       } else {
         report(S->getOperatorLoc(), dyn_cast<NamedDecl>(CD), RefType::Implicit);
diff --git a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp
index c9519c2e91b68a..87e78eb7750885 100644
--- a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp
+++ b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp
@@ -502,6 +502,21 @@ TEST(WalkAST, Operator) {
   testWalk(
       "struct string {int operator+(string); }; using $implicit^foo = string;",
       "int k = foo() ^+ string();");
+  // Inherited member operators are attributed to the derived type, like other
+  // inherited members.
+  testWalk("struct Base { int operator+(Base); }; "
+           "struct $implicit^Derived : Base {};",
+           "int k = Derived() ^+ Derived();");
+  testWalk(R"cpp(
+      template <typename T> struct PtrBase { T *operator->(); T &operator*(); };
+      template <typename T> struct $implicit^Ptr : PtrBase<T> {};
+      struct Foo { int a; };)cpp",
+           "void test(Ptr<Foo> &V) { V^->a; }");
+  testWalk(R"cpp(
+      template <typename T> struct PtrBase { T *operator->(); T &operator*(); };
+      template <typename T> struct $implicit^Ptr : PtrBase<T> {};
+      struct Foo { int a; };)cpp",
+           "void test(Ptr<Foo> &V) { (^*V).a; }");
 }
 
 TEST(WalkAST, VarDecls) {

@github-actions

github-actions Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

🪟 Windows x64 Test Results

  • 3452 tests passed
  • 38 tests skipped

✅ The build succeeded and all tests passed.

@github-actions

github-actions Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

🐧 Linux x64 Test Results

  • 5632 tests passed
  • 8 tests skipped

✅ The build succeeded and all tests passed.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[include-cleaner] Inherited member operators are attributed to the base class, so misc-include-cleaner disagrees between libstdc++ and libc++

1 participant