[gcc r17-2927] analyzer: support exception subclass matching [PR analyzer/119697]

Egas Ribeiro via Gcc-cvs <[email protected]> Tue, 4 Aug 2026 09:05:18 +0000 (GMT)
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:672923ae05596d602c16f4bdb7449931c5e81cf5

commit r17-2927-g672923ae05596d602c16f4bdb7449931c5e81cf5
Author: Egas Ribeiro <[email protected]>
Date:   Fri Jun 19 19:19:51 2026 +0100

    analyzer: support exception subclass matching [PR analyzer/119697]
    
    The analyzer's exception_matches_type_p only treated an exception as
    caught when the handler type and exception type were identical, so a
    handler catching a base class did not match a thrown derived class.
    
    Add an exception_matches_type_p langhook returning whether a handler of
    one type catches an exception of another per the language's rules. The
    default returns false, preserving behavior for frontends without
    exception support (such as C). The C++ frontend implements it via
    can_convert_eh (now non-static). This keeps the analyzer language
    agnostic and the C++ catch-matching rules in the frontend.
    
            PR analyzer/119697
    
    gcc/ChangeLog:
            * langhooks.h (struct lang_hooks): Add exception_matches_type_p.
            * langhooks-def.h (LANG_HOOKS_EH_MAY_CATCH_P): Define as
            hook_bool_tree_tree_false.
            (LANG_HOOKS_INITIALIZER): Add it.
    
    gcc/cp/ChangeLog:
            * except.cc (can_convert_eh): Make non-static.
            * cp-tree.h (can_convert_eh): Declare.
            * cp-lang.cc (LANG_HOOKS_EH_MAY_CATCH_P): Define as
            can_convert_eh.
    
    gcc/analyzer/ChangeLog:
            * ops.cc: Include "langhooks.h".
            (exception_matches_type_p): Use the exception_matches_type_p
            langhook; fix catch/exception argument order.
    
    gcc/testsuite/ChangeLog:
            * g++.dg/analyzer/exception-dynamic-spec.C: Remove xfail.
            * g++.dg/analyzer/exception-subclass-1.C: Remove xfail.
            __analyzer_dump_path in the catch handler.
            * g++.dg/analyzer/exception-subclass-2.C: Add
            __analyzer_dump_path in the catch handler;
            Add __analyzer_eval to interprocedural call.
            * g++.dg/analyzer/exception-subclass-3.C: New test.
            * g++.dg/analyzer/exception-subclass-4.C: New test.
            * g++.dg/analyzer/multiple-inheritance-1.C: New test.
    
    Signed-off-by: Egas Ribeiro <[email protected]>

Diff:
---
 gcc/analyzer/ops.cc                                |  13 +-
 gcc/cp/cp-lang.cc                                  |   2 +
 gcc/cp/cp-tree.h                                   |   1 +
 gcc/cp/except.cc                                   |   2 +-
 gcc/langhooks-def.h                                |   2 +
 gcc/langhooks.h                                    |   4 +
 .../g++.dg/analyzer/exception-dynamic-spec.C       |   2 +-
 .../g++.dg/analyzer/exception-subclass-1.C         |   2 +-
 .../g++.dg/analyzer/exception-subclass-2.C         |   7 +-
 .../g++.dg/analyzer/exception-subclass-3.C         | 133 +++++++++++++++++++++
 .../g++.dg/analyzer/exception-subclass-4.C         |  16 +++
 .../g++.dg/analyzer/multiple-inheritance-1.C       |  14 +++
 12 files changed, 182 insertions(+), 16 deletions(-)

diff --git a/gcc/analyzer/ops.cc b/gcc/analyzer/ops.cc
index 898eaf56485d..382edf7144ef 100644
--- a/gcc/analyzer/ops.cc
+++ b/gcc/analyzer/ops.cc
@@ -28,6 +28,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "cgraph.h"
 #include "text-art/dump.h"
 #include "text-art/tree-widget.h"
+#include "langhooks.h"
 
 #include "analyzer/ops.h"
 #include "analyzer/call-details.h"
@@ -1950,18 +1951,10 @@ public:
 };
 
 static bool
-exception_matches_type_p (tree exception_type,
-			  tree catch_type)
+exception_matches_type_p (tree handler_type, tree exception_type)
 {
-  if (catch_type == exception_type)
+  if (lang_hooks.exception_matches_type_p (handler_type, exception_type))
     return true;
-
-  /* TODO (PR analyzer/119697): we should also handle subclasses etc;
-     see the rules in https://en.cppreference.com/w/cpp/language/catch
-
-     It looks like we should be calling (or emulating)
-     can_convert_eh from the C++ FE, but that's specific to the C++ FE.  */
-
   return false;
 }
 
diff --git a/gcc/cp/cp-lang.cc b/gcc/cp/cp-lang.cc
index c9d02cc73024..60fd114d35c1 100644
--- a/gcc/cp/cp-lang.cc
+++ b/gcc/cp/cp-lang.cc
@@ -78,6 +78,8 @@ static const char *cp_get_sarif_source_language (const char *);
 #define LANG_HOOKS_EH_PERSONALITY cp_eh_personality
 #undef LANG_HOOKS_EH_RUNTIME_TYPE
 #define LANG_HOOKS_EH_RUNTIME_TYPE build_eh_type_type
+#undef LANG_HOOKS_EXCEPTION_MATCHES_TYPE_P
+#define LANG_HOOKS_EXCEPTION_MATCHES_TYPE_P can_convert_eh
 #undef LANG_HOOKS_ENUM_UNDERLYING_BASE_TYPE
 #define LANG_HOOKS_ENUM_UNDERLYING_BASE_TYPE cxx_enum_underlying_base_type
 #undef LANG_HOOKS_PREPROCESS_MAIN_FILE
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index a8af4d389450..69ce5e2e8e77 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7872,6 +7872,7 @@ extern tree build_exc_ptr			(void);
 extern tree build_throw				(location_t, tree,
 						 tsubst_flags_t);
 extern int nothrow_libfn_p			(const_tree);
+extern bool can_convert_eh			(tree, tree);
 extern void check_handlers			(tree);
 extern tree finish_noexcept_expr		(tree, tsubst_flags_t);
 extern bool expr_noexcept_p			(tree, tsubst_flags_t);
diff --git a/gcc/cp/except.cc b/gcc/cp/except.cc
index c4b5c388f9b8..9ec635af74d4 100644
--- a/gcc/cp/except.cc
+++ b/gcc/cp/except.cc
@@ -973,7 +973,7 @@ nothrow_libfn_p (const_tree fn)
 /* Returns nonzero if an exception of type FROM will be caught by a
    handler for type TO, as per [except.handle].  */
 
-static bool
+bool
 can_convert_eh (tree to, tree from)
 {
   to = non_reference (to);
diff --git a/gcc/langhooks-def.h b/gcc/langhooks-def.h
index 33a99266187c..9546b8f0d003 100644
--- a/gcc/langhooks-def.h
+++ b/gcc/langhooks-def.h
@@ -149,6 +149,7 @@ extern const char *lhd_get_sarif_source_language (const char *);
 #define LANG_HOOKS_INIT_TS		lhd_do_nothing
 #define LANG_HOOKS_EH_PERSONALITY	lhd_gcc_personality
 #define LANG_HOOKS_EH_RUNTIME_TYPE	lhd_pass_through_t
+#define LANG_HOOKS_EXCEPTION_MATCHES_TYPE_P hook_bool_tree_tree_false
 #define LANG_HOOKS_EH_PROTECT_CLEANUP_ACTIONS	NULL
 #define LANG_HOOKS_BLOCK_MAY_FALLTHRU	hook_bool_const_tree_true
 #define LANG_HOOKS_EH_USE_CXA_END_CLEANUP	false
@@ -406,6 +407,7 @@ extern void lhd_end_section (void);
   LANG_HOOKS_EXPR_TO_DECL, \
   LANG_HOOKS_EH_PERSONALITY, \
   LANG_HOOKS_EH_RUNTIME_TYPE, \
+  LANG_HOOKS_EXCEPTION_MATCHES_TYPE_P, \
   LANG_HOOKS_EH_PROTECT_CLEANUP_ACTIONS, \
   LANG_HOOKS_BLOCK_MAY_FALLTHRU, \
   LANG_HOOKS_EH_USE_CXA_END_CLEANUP, \
diff --git a/gcc/langhooks.h b/gcc/langhooks.h
index 546d7ddcdfb9..5af4efcb86c6 100644
--- a/gcc/langhooks.h
+++ b/gcc/langhooks.h
@@ -644,6 +644,10 @@ struct lang_hooks
   /* Map a type to a runtime object to match type.  */
   tree (*eh_runtime_type) (tree);
 
+  /* Return true if a handler of HANDLER_TYPE can catch an exception
+     of EXCEPTION_TYPE, per the language's exception-matching rules.  */
+  bool (*exception_matches_type_p) (tree handler_type, tree exception_type);
+
   /* If non-NULL, this is a function that returns a function decl to be
      executed if an unhandled exception is propagated out of a cleanup
      region.  For example, in C++, an exception thrown by a destructor
diff --git a/gcc/testsuite/g++.dg/analyzer/exception-dynamic-spec.C b/gcc/testsuite/g++.dg/analyzer/exception-dynamic-spec.C
index 984720377704..efaa447a919c 100644
--- a/gcc/testsuite/g++.dg/analyzer/exception-dynamic-spec.C
+++ b/gcc/testsuite/g++.dg/analyzer/exception-dynamic-spec.C
@@ -26,7 +26,7 @@ void test_2 (int flag) throw (io_error) // { dg-warning "throwing exception of u
 
 // Valid intraprocedural with subclass:
 
-void test_3 (int flag) throw (io_error) // { dg-bogus "throwing exception of unexpected type 'file_io_error' from 'test_3'" "PR analyzer/119697" { xfail *-*-* } }
+void test_3 (int flag) throw (io_error) // { dg-bogus "throwing exception of unexpected type 'file_io_error' from 'test_3'" }
 {
     if (flag)
       throw file_io_error();
diff --git a/gcc/testsuite/g++.dg/analyzer/exception-subclass-1.C b/gcc/testsuite/g++.dg/analyzer/exception-subclass-1.C
index 79df33021dd3..4b4881286afc 100644
--- a/gcc/testsuite/g++.dg/analyzer/exception-subclass-1.C
+++ b/gcc/testsuite/g++.dg/analyzer/exception-subclass-1.C
@@ -13,7 +13,7 @@ int test ()
   try {
     throw io_error();
   } catch (exception &exc) {
-    __analyzer_dump_path (); // { dg-message "path" "PR analyzer/119697" { xfail *-*-* } }
+    __analyzer_dump_path (); // { dg-message "path" }
     return -1;
   }
   __analyzer_dump_path (); // { dg-bogus "path" }
diff --git a/gcc/testsuite/g++.dg/analyzer/exception-subclass-2.C b/gcc/testsuite/g++.dg/analyzer/exception-subclass-2.C
index e9fb61753bba..cd1b7ac570cb 100644
--- a/gcc/testsuite/g++.dg/analyzer/exception-subclass-2.C
+++ b/gcc/testsuite/g++.dg/analyzer/exception-subclass-2.C
@@ -11,15 +11,16 @@ class io_error : public exception
 int __analyzer_inner ()
 {
   try {
-    throw io_error();
+    throw io_error ();
   } catch (exception &exc) {
+    __analyzer_dump_path (); // { dg-message "path" }
     return -1;
   }
   __analyzer_dump_path (); // { dg-bogus "path" }
   return 0;
 }
 
-int test ()
+void test ()
 {
-  return __analyzer_inner (); // { dg-message "path" "PR analyzer/119697" { xfail *-*-* } }
+  __analyzer_eval (__analyzer_inner () == -1); /* { dg-warning "TRUE" } */
 }
diff --git a/gcc/testsuite/g++.dg/analyzer/exception-subclass-3.C b/gcc/testsuite/g++.dg/analyzer/exception-subclass-3.C
new file mode 100644
index 000000000000..687e565187f3
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/exception-subclass-3.C
@@ -0,0 +1,133 @@
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct Base {};
+struct Derived : Base {};
+struct Unrelated {};
+
+struct Child : Base {};
+struct Grandchild : Child {};
+
+struct SiblingA : Base {};
+struct SiblingB : Base {};
+
+struct B1 {};
+struct B2 {};
+struct MultiDerived : B1, B2 {};
+
+struct Amb {};
+struct Mid1 : Amb {};
+struct Mid2 : Amb {};
+struct AmbDerived : Mid1, Mid2 {};
+
+struct PrivDerived : private Base {};
+
+void test_unrelated ()
+{
+  try {
+    throw Derived ();
+  }
+  catch (Unrelated &) {
+    __analyzer_dump_path (); // { dg-bogus "path" }
+  }
+}
+
+void test_object_vs_pointer ()
+{
+  try {
+    throw Derived ();
+  }
+  catch (Base *) {
+    __analyzer_dump_path (); // { dg-bogus "path" }
+  }
+}
+
+void test_wrong_direction ()
+{
+  try {
+    throw Base ();
+  }
+  catch (Derived &) {
+    __analyzer_dump_path (); // { dg-bogus "path" }
+  }
+}
+
+void test_pointer_base ()
+{
+  static Derived d;
+  try {
+    throw &d;
+  }
+  catch (Base *) {
+    __analyzer_dump_path (); // { dg-message "path" }
+  }
+}
+
+void test_grandchild ()
+{
+  try {
+    throw Grandchild ();
+  }
+  catch (Base &) {
+    __analyzer_dump_path (); // { dg-message "path" }
+  }
+}
+
+void test_sibling ()
+{
+  try {
+    throw SiblingA ();
+  }
+  catch (SiblingB &) {
+    __analyzer_dump_path (); // { dg-bogus "path" }
+  }
+}
+
+void test_multiple_inheritance ()
+{
+  try {
+    throw MultiDerived ();
+  }
+  catch (B1 &) {
+    __analyzer_dump_path (); // { dg-message "path" }
+  }
+}
+
+void test_multiple_inheritance_b2 ()
+{
+  try {
+    throw MultiDerived ();
+  }
+  catch (B2 &) {
+    __analyzer_dump_path (); // { dg-message "path" }
+  }
+}
+
+void test_ambiguous_base ()
+{
+  try {
+    throw AmbDerived ();
+  }
+  catch (Amb &) {
+    __analyzer_dump_path (); // { dg-bogus "path" }
+  }
+}
+
+void test_private_base ()
+{
+  try {
+    throw PrivDerived ();
+  }
+  catch (Base &) {
+    __analyzer_dump_path (); // { dg-bogus "path" }
+  }
+}
+
+void test_cv_qualified ()
+{
+  try {
+    throw Derived ();
+  }
+  catch (const Base &) {
+    __analyzer_dump_path (); // { dg-message "path" }
+  }
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/exception-subclass-4.C b/gcc/testsuite/g++.dg/analyzer/exception-subclass-4.C
new file mode 100644
index 000000000000..9debe34639ea
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/exception-subclass-4.C
@@ -0,0 +1,16 @@
+// { dg-additional-options "-std=c++11" }
+
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct Base {};
+struct Derived : Base {};
+
+void test_nullptr ()
+{
+  try {
+    throw nullptr;
+  }
+  catch (Base *) {
+    __analyzer_dump_path (); // { dg-message "path" }
+  }
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/multiple-inheritance-1.C b/gcc/testsuite/g++.dg/analyzer/multiple-inheritance-1.C
new file mode 100644
index 000000000000..8c78d5fe75a1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/multiple-inheritance-1.C
@@ -0,0 +1,14 @@
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct B1 { int x; };
+struct B2 { int y; };
+struct MultiDerived : B1, B2 {};
+
+void test_nonfirst_base_field ()
+{
+  MultiDerived d;
+  d.y = 20;
+  B2 *p = &d;
+  __analyzer_eval (p->y == 20);   // { dg-warning "TRUE" }
+  __analyzer_eval (p->y == d.y);  // { dg-warning "TRUE" }
+}