On Tue, 2026-07-14 at 12:32 +0100, Egas Ribeiro wrote: > The analyzer doesn't currently support virtual function call > resolution, > and has no way to devirtualize concrete calls. > > This patch adds support for devirtualization of virtual methods by > adding logic around OBJ_TYPE_REF calls and using the information it > tracks to link the calls back to the concrete subclass implementation > stored in the OBJ_TYPE_REF_OBJECT's vptr field. > > This approach relies on the pre-existing store binding mechanism to > keep > track of which _ZTV* instance the object's vptr field points to, and > thus doesn't require any additional modeling of the dynamic type of > the > object to resolve calls. > > can_throw_p is also extended to consider thunks that wrap virtual > methods when dealing with multiple inheritance, and now checks > TREE_NOTHROW for more accurate results (i.e considers noexcept > attributes on methods).
Thanks; this looks good to push, assuming it's been through a usual test cycle. Dave > > PR analyzer/97114 > > gcc/analyzer/ChangeLog: > > * region-model.cc (can_throw_p): improve NOTHROW detection > and > consider the thunk case. > (region_model::get_fndecl_for_virtual_call): New function. > (region_model::get_fndecl_for_call): Update to use > get_fndecl_for_virtual_call on OBJ_TYPE_REF. > * region-model.h (class region_model): New decl. > > gcc/testsuite/ChangeLog: > > * g++.dg/analyzer/devirt-1.C: New test. > * g++.dg/analyzer/devirt-multiple-inheritance-1.C: New test. > * g++.dg/analyzer/devirt-noexcept-1.C: New test. > * g++.dg/analyzer/devirt-unknown-1.C: New test. > > Signed-off-by: Egas Ribeiro <[email protected]> > --- > gcc/analyzer/region-model.cc | 86 > +++++++++++++++++++ > gcc/analyzer/region-model.h | 2 + > gcc/testsuite/g++.dg/analyzer/devirt-1.C | 23 +++++ > .../analyzer/devirt-multiple-inheritance-1.C | 31 +++++++ > .../g++.dg/analyzer/devirt-noexcept-1.C | 26 ++++++ > .../g++.dg/analyzer/devirt-unknown-1.C | 16 ++++ > 6 files changed, 184 insertions(+) > create mode 100644 gcc/testsuite/g++.dg/analyzer/devirt-1.C > create mode 100644 gcc/testsuite/g++.dg/analyzer/devirt-multiple- > inheritance-1.C > create mode 100644 gcc/testsuite/g++.dg/analyzer/devirt-noexcept-1.C > create mode 100644 gcc/testsuite/g++.dg/analyzer/devirt-unknown-1.C > > diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region- > model.cc > index d88651fc33b..8addf1d9a07 100644 > --- a/gcc/analyzer/region-model.cc > +++ b/gcc/analyzer/region-model.cc > @@ -48,6 +48,9 @@ along with GCC; see the file COPYING3. If not see > #include "channels.h" > #include "value-relation.h" > #include "range-op.h" > +#include "ipa-utils.h" > +#include "gimple-iterator.h" > +#include "gimple-fold.h" > > #include "text-art/tree-widget.h" > > @@ -2210,6 +2213,14 @@ can_throw_p (const gcall &call, tree fndecl) > > if (fndecl) > { > + /* If we are checking a thunk, we want to verify whether or > not the > + underlying function is nothrow. */ > + if (cgraph_node *n = cgraph_node::get (fndecl)) > + fndecl = n->function_symbol ()->decl; > + > + if (TREE_NOTHROW (fndecl)) > + return false; > + > const function_set fs = get_fns_assumed_not_to_throw (); > if (fs.contains_decl_p (fndecl)) > return false; > @@ -6479,6 +6490,75 @@ region_model::can_merge_with_p (const > region_model &other_model, > return true; > } > > +/* Attempt to get the fndecl for a virtual call via OBJ_TYPE_REF, or > + NULL_TREE if it can't be resolved. > + > + Reads the value bound to the object's vptr field > (OBJ_TYPE_REF_OBJECT's > + vfield). > + If that value has the form "&vtable_decl + constant" (a > region_svalue for a > + _ZTV* decl plus a byte offset), recover the vtable decl and > offset and use > + gimple_get_virt_method_for_vtable, together with > OBJ_TYPE_REF_TOKEN, to look > + up the concrete fndecl in the vtable's initializer. > + > + Relies on the store having bound the vptr field to the _ZTV* > instance, so no > + separate modeling of the object's dynamic type is needed. */ > + > +tree > +region_model::get_fndecl_for_virtual_call (const_tree obj_type_ref, > + region_model_context > *ctxt) > +{ > + tree obj = OBJ_TYPE_REF_OBJECT (obj_type_ref); > + tree obj_type = obj_type_ref_class (obj_type_ref); > + if (!obj_type) > + return NULL_TREE; > + tree vfield = TYPE_VFIELD (obj_type); > + if (!vfield) > + return NULL_TREE; > + > + const svalue *obj_sval = get_rvalue (obj, ctxt); > + const region *obj_reg = deref_rvalue (obj_sval, obj, ctxt); > + const region *vptr_reg = m_mgr->get_field_region (obj_reg, > vfield); > + > + const svalue *vptr_sval = get_store_value (vptr_reg, ctxt); > + while (const svalue *cast = vptr_sval->maybe_undo_cast ()) > + vptr_sval = cast; > + > + const binop_svalue *b = vptr_sval->dyn_cast_binop_svalue (); > + if (!b || b->get_op () != POINTER_PLUS_EXPR) > + return NULL_TREE; > + > + vptr_sval = b->get_arg0 (); > + const svalue *offset_sval = b->get_arg1 (); > + > + tree offset_const = offset_sval->maybe_get_constant (); > + if (!offset_const || TREE_CODE (offset_const) != INTEGER_CST) > + return NULL_TREE; > + unsigned HOST_WIDE_INT offset = tree_to_uhwi (offset_const); > + > + const region_svalue *vptr = vptr_sval->dyn_cast_region_svalue (); > + /* Give up if we have a conjured vptr. */ > + if (!vptr) > + return NULL_TREE; > + > + tree vtable = vptr->get_pointee ()->maybe_get_decl (); > + if (!vtable) > + return NULL_TREE; > + > + unsigned HOST_WIDE_INT token > + = tree_to_uhwi (OBJ_TYPE_REF_TOKEN (obj_type_ref)); > + bool can_refer; > + tree vptr_fn > + = gimple_get_virt_method_for_vtable (token, vtable, offset, > &can_refer); > + if (!vptr_fn || !can_refer) > + return NULL_TREE; > + > + if (cgraph_node *node = cgraph_node::get_create (vptr_fn)) > + if (const cgraph_node *ultimate = node->ultimate_alias_target > ()) > + return ultimate->decl; > + > + return NULL_TREE; > +} > + > /* Attempt to get the fndecl used at CALL, if known, or NULL_TREE > otherwise. */ > > @@ -6489,6 +6569,12 @@ region_model::get_fndecl_for_call (const gcall > &call, > tree fn_ptr = gimple_call_fn (&call); > if (fn_ptr == NULL_TREE) > return NULL_TREE; > + > + /* Handle OBJ_TYPE_REF so that we can try to find the definition > for a virtual > + call and treat it like any other call. */ > + if (TREE_CODE (fn_ptr) == OBJ_TYPE_REF) > + return get_fndecl_for_virtual_call (fn_ptr, ctxt); > + > const svalue *fn_ptr_sval = get_rvalue (fn_ptr, ctxt); > if (const region_svalue *fn_ptr_ptr > = fn_ptr_sval->dyn_cast_region_svalue ()) > diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region- > model.h > index fd947da65e8..d1e2b014d0e 100644 > --- a/gcc/analyzer/region-model.h > +++ b/gcc/analyzer/region-model.h > @@ -509,6 +509,8 @@ class region_model > > tree get_fndecl_for_call (const gcall &call, > region_model_context *ctxt); > + tree get_fndecl_for_virtual_call (const_tree fn_ptr, > + region_model_context *ctxt); > > void get_regions_for_current_frame (auto_vec<const decl_region *> > *out) const; > static void append_regions_cb (const region *base_reg, > diff --git a/gcc/testsuite/g++.dg/analyzer/devirt-1.C > b/gcc/testsuite/g++.dg/analyzer/devirt-1.C > new file mode 100644 > index 00000000000..8fd3b9fb0f7 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/analyzer/devirt-1.C > @@ -0,0 +1,23 @@ > +#include "../../gcc.dg/analyzer/analyzer-decls.h" > +struct Base > +{ > + virtual int not_overridden () { return 0; } > + virtual int overridden () { return 0; } > +}; > +struct Derived : public Base > +{ > + int overridden () { return 60; } > +}; > +__attribute__ ((noipa)) Base * > +make_derived () > +{ > + return new Derived (); > +} > +void > +test () > +{ > + Base *f = make_derived (); > + __analyzer_eval (f->not_overridden () == 0); // { dg-warning > "TRUE" } > + __analyzer_eval (f->overridden () == 60); // { dg-warning > "TRUE" } > + delete f; > +} > diff --git a/gcc/testsuite/g++.dg/analyzer/devirt-multiple- > inheritance-1.C b/gcc/testsuite/g++.dg/analyzer/devirt-multiple- > inheritance-1.C > new file mode 100644 > index 00000000000..656166bbcbd > --- /dev/null > +++ b/gcc/testsuite/g++.dg/analyzer/devirt-multiple-inheritance-1.C > @@ -0,0 +1,31 @@ > +#include "../../gcc.dg/analyzer/analyzer-decls.h" > + > +struct A > +{ > + virtual void f () {} > +}; > +struct B > +{ > + virtual void g () { throw 1; } > +}; > + > +struct C : public A, public B > +{ > + void f () {} > + void g () {} > +}; > + > +__attribute__ ((noipa)) C * > +make_c () > +{ > + return new C (); > +} > + > +void > +test () > +{ > + C *c = make_c (); > + B *b = c; > + b->g (); // { dg-bogus "leak" } > + delete c; > +} > diff --git a/gcc/testsuite/g++.dg/analyzer/devirt-noexcept-1.C > b/gcc/testsuite/g++.dg/analyzer/devirt-noexcept-1.C > new file mode 100644 > index 00000000000..d14762d8fbd > --- /dev/null > +++ b/gcc/testsuite/g++.dg/analyzer/devirt-noexcept-1.C > @@ -0,0 +1,26 @@ > +#include "../../gcc.dg/analyzer/analyzer-decls.h" > +// { dg-additional-options "-std=c++11" } > + > +struct Base > +{ > + virtual void __analyzer_foo () = 0; > +}; > + > +struct Derived : public Base > +{ > + void __analyzer_foo () noexcept override {} > +}; > + > +__attribute__ ((noipa)) Base * > +make_derived () > +{ > + return new Derived (); > +} > + > +void > +test () > +{ > + Base *f = make_derived (); > + f->__analyzer_foo (); // { dg-bogus "leak" } > + delete f; > +} > diff --git a/gcc/testsuite/g++.dg/analyzer/devirt-unknown-1.C > b/gcc/testsuite/g++.dg/analyzer/devirt-unknown-1.C > new file mode 100644 > index 00000000000..991159e57d7 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/analyzer/devirt-unknown-1.C > @@ -0,0 +1,16 @@ > +#include "../../gcc.dg/analyzer/analyzer-decls.h" > + > +struct Base > +{ > + virtual void __analyzer_foo () { throw 1; } > +}; > + > +extern Base *get_base (void); // unknown dynamic type > + > +void > +test () > +{ > + Base *f = get_base (); > + // vptr unknown (won't devirt) > + f->__analyzer_foo (); // { dg-bogus "leak" } > +}
