Jason: what do you think about the approach used by this patch? Specifically, is it OK with you if we add the new langhook to effectively expose can_convert_eh to the analyzer?
Thanks Dave On Fri, 2026-07-10 at 11:54 +0100, Egas Ribeiro wrote: > In the tests for this patch I did not cover the C++17 > function-pointer-conversion row of [except.handle], because it is > very > uncommon in practice and I didn't think it warranted its own test. > Let me know if I should add that and I can resubmit the patch. > > Bootstrapped and regtested on x86_64-pc-linux-gnu. > > -- >8 -- > > 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 eh_may_catch_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 eh_may_catch_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 eh_may_catch_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..4aac77d0c1e 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.eh_may_catch_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..f1096fece1e 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_EH_MAY_CATCH_P > +#define LANG_HOOKS_EH_MAY_CATCH_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..2980901b93f 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_EH_MAY_CATCH_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_EH_MAY_CATCH_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..921c1740fd1 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 (*eh_may_catch_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" } > +}
