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" }
> +}

Reply via email to