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).

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

Reply via email to