On Tue, 2026-07-14 at 11:56 +0100, Egas Ribeiro wrote:
> 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.
Thanks. This patch looks good to me, apart from the need to rename the
hook in the commit message here:
> Add an eh_may_catch_p langhook returning whether a handler of one
^^^^^^^^^^^^^^
Dave
> 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]>
> ---
> 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(-)
> create mode 100644 gcc/testsuite/g++.dg/analyzer/exception-subclass-
> 3.C
> create mode 100644 gcc/testsuite/g++.dg/analyzer/exception-subclass-
> 4.C
> create mode 100644 gcc/testsuite/g++.dg/analyzer/multiple-
> inheritance-1.C
>
> diff --git a/gcc/analyzer/ops.cc b/gcc/analyzer/ops.cc
> index 898eaf56485..382edf7144e 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 c9d02cc7302..60fd114d35c 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 8dac8c98b7a..d4b3e7e5756 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -7856,6 +7856,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 c4b5c388f9b..9ec635af74d 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 33a99266187..9546b8f0d00 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 546d7ddcdfb..5af4efcb86c 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 98472037770..efaa447a919 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 79df33021dd..4b4881286af 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 e9fb61753bb..cd1b7ac570c 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 00000000000..687e565187f
> --- /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 00000000000..9debe34639e
> --- /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 00000000000..8c78d5fe75a
> --- /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" }
> +}