Skip to content

Conversation

aaronpuchert
Copy link
Member

The check confusingly fires on non-exception classes if any base class has an alias in an exception class. In our case, the exception had an alias for an allocator interface, so every allocator inheriting from that interface was treated as an exception type. (But only when the header for the exception was included.)

The reason behind this is the odd (but documented) behavior of isDerivedFrom and similar matchers: it does not only iterate through the bases as written, but through all relevant nodes to check them for being a base. This makes the matcher also finds aliases of the base classes.

Only going through the bases as written can be done with hasAnyBase. However, that doesn't cover the class itself, and we have to check it separately. Since we're no longer looking through aliases via the matcher, and because we're apparently interested in the canonical type, we check that (see the test with "typedef std::exception ERROR_BASE;").

@llvmbot
Copy link
Member

llvmbot commented Sep 25, 2025

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

@llvm/pr-subscribers-clang-tidy

Author: Aaron Puchert (aaronpuchert)

Changes

The check confusingly fires on non-exception classes if any base class has an alias in an exception class. In our case, the exception had an alias for an allocator interface, so every allocator inheriting from that interface was treated as an exception type. (But only when the header for the exception was included.)

The reason behind this is the odd (but documented) behavior of isDerivedFrom and similar matchers: it does not only iterate through the bases as written, but through all relevant nodes to check them for being a base. This makes the matcher also finds aliases of the base classes.

Only going through the bases as written can be done with hasAnyBase. However, that doesn't cover the class itself, and we have to check it separately. Since we're no longer looking through aliases via the matcher, and because we're apparently interested in the canonical type, we check that (see the test with "typedef std::exception ERROR_BASE;").


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

2 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/bugprone/ThrowKeywordMissingCheck.cpp (+4-1)
  • (modified) clang-tools-extra/test/clang-tidy/checkers/bugprone/throw-keyword-missing.cpp (+8-1)
diff --git a/clang-tools-extra/clang-tidy/bugprone/ThrowKeywordMissingCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ThrowKeywordMissingCheck.cpp
index 89eafb15f2652..091e28f0519af 100644
--- a/clang-tools-extra/clang-tidy/bugprone/ThrowKeywordMissingCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/ThrowKeywordMissingCheck.cpp
@@ -18,7 +18,10 @@ void ThrowKeywordMissingCheck::registerMatchers(MatchFinder *Finder) {
   Finder->addMatcher(
       cxxConstructExpr(
           hasType(cxxRecordDecl(
-              isSameOrDerivedFrom(matchesName("[Ee]xception|EXCEPTION")))),
+              anyOf(matchesName("[Ee]xception$|EXCEPTION$"),
+                    hasAnyBase(hasType(hasCanonicalType(
+                        recordType(hasDeclaration(cxxRecordDecl(
+                            matchesName("[Ee]xception|EXCEPTION")))))))))),
           unless(anyOf(
               hasAncestor(
                   stmt(anyOf(cxxThrowExpr(), callExpr(), returnStmt()))),
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/throw-keyword-missing.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/throw-keyword-missing.cpp
index bafd3d19b5a31..6ddaf246a354e 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/throw-keyword-missing.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/throw-keyword-missing.cpp
@@ -32,8 +32,9 @@ struct runtime_error : public exception {
 
 } // namespace std
 
-// The usage of this class should never emit a warning.
+// The usage of these classes should never emit a warning.
 struct RegularClass {};
+struct RegularDerived : public RegularClass {};
 
 // Class name contains the substring "exception", in certain cases using this class should emit a warning.
 struct RegularException {
@@ -41,6 +42,8 @@ struct RegularException {
 
   // Constructors with a single argument are treated differently (cxxFunctionalCastExpr).
   RegularException(int) {}
+
+  typedef RegularClass RegularAlias;
 };
 
 // --------------
@@ -68,6 +71,10 @@ void regularClassNotThrownTest(int i) {
     RegularClass();
 }
 
+void regularClassWithAliasNotThrownTest(int i) {
+  RegularDerived();
+}
+
 void regularClassThrownTest(int i) {
   if (i < 0)
     throw RegularClass();

@aaronpuchert aaronpuchert force-pushed the bugprone-throw-keyword-missing-type-alias branch from c9d06e0 to 1c523e7 Compare September 25, 2025 15:20
Copy link

github-actions bot commented Sep 25, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@aaronpuchert aaronpuchert force-pushed the bugprone-throw-keyword-missing-type-alias branch from 1c523e7 to 600d240 Compare September 25, 2025 16:00
Copy link
Collaborator

@Xazax-hun Xazax-hun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

Copy link
Contributor

@vbvictor vbvictor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a release note in clang-tools-extra/docs/ReleaseNotes.rst, keeping lexicographical order.

The rest LGTM

The check confusingly fires on non-exception classes if any base class
has an alias in an exception class. In our case, the exception had an
alias for an allocator interface, so every allocator inheriting from
that interface was treated as an exception type. (But only when the
header for the exception was included.)

The reason behind this is the odd (but documented) behavior of
isDerivedFrom and similar matchers: it does not only iterate through the
bases as written, but through all relevant nodes to check them for being
a base. This makes the matcher also finds aliases of the base classes.

Only going through the bases as written can be done with `hasAnyBase`.
However, that doesn't cover the class itself, and we have to check it
separately. Since we're no longer looking through aliases via the
matcher, and because we're apparently interested in the canonical type,
we check that (see the test with "typedef std::exception ERROR_BASE;").
@aaronpuchert aaronpuchert force-pushed the bugprone-throw-keyword-missing-type-alias branch from 600d240 to f9679a2 Compare September 25, 2025 17:13
@aaronpuchert aaronpuchert merged commit 57330c8 into llvm:main Sep 25, 2025
11 checks passed
@aaronpuchert aaronpuchert deleted the bugprone-throw-keyword-missing-type-alias branch September 25, 2025 18:13
mahesh-attarde pushed a commit to mahesh-attarde/llvm-project that referenced this pull request Oct 3, 2025
…lvm#160725)

The check confusingly fires on non-exception classes if any base class
has an alias in an exception class. In our case, the exception had an
alias for an allocator interface, so every allocator inheriting from
that interface was treated as an exception type. (But only when the
header for the exception was included.)

The reason behind this is the odd (but documented) behavior of
isDerivedFrom and similar matchers: it does not only iterate through the
bases as written, but through all relevant nodes to check them for being
a base. This makes the matcher also finds aliases of the base classes.

Only going through the bases as written can be done with `hasAnyBase`.
However, that doesn't cover the class itself, and we have to check it
separately. Since we're no longer looking through aliases via the
matcher, and because we're apparently interested in the canonical type,
we check that (see the test with "typedef std::exception ERROR_BASE;").
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.

4 participants