[PATCH v2 2/2] analyzer: add dynamic_cast support [PR110578]

Egas Ribeiro <[email protected]>
Newsgroups gmane.comp.gcc.patches
Message-ID <[email protected]>
The analyzer doesn't currently understand dynamic_cast calls. This means
that the returned object from a call is always opaque, and thus we miss
a few classes of bugs such as dereferencing the result of a failed cast.

This patch implements a new known_function for dynamic_cast, and sets
the result of the callbased on the rules of [expr.dynamic.cast].

The implementation effectively evaluates the results of a dynamic_cast
cast call statically, similarly to what cxx_eval_dynamic_cast does in
the frontend, but without using frontend methods and trees.

It tries to derive the target object from the argumments passed to
__dynamic_cast calls by traversing BINFOs based on the rules of
[expr.dynamic.cast] and then calculating the relative offsets of the
resulting object from the BINFOs we found.

get_vtable_from_obj was also added so we can reuse the logic for
deriving the vtable from objects added with virtual function support.

gcc/analyzer/ChangeLog:

	* kf-lang-cp.cc: Include "cgraph.h" and "ipa-utils.h".
	(struct dyncast_subobject): New struct.
	(get_type_from_tinfo_arg): New function.
	(lookup_binfo_at_same_offset): New function.
	(lookup_subobject_matches): New function.
	(evaluate_dyncast): New function.
	(class kf_dynamic_cast): New class.
	(register_known_functions_lang_cp): Register "__dynamic_cast".
	* region-model.cc (region_model::get_vtable_from_obj): New function,
	factored out of...
	(region_model::get_fndecl_for_virtual_call): ...here.  Call it.
	Update comment.
	* region-model.h (region_model::get_vtable_from_obj): New decl.

gcc/testsuite/ChangeLog:

	* g++.dg/analyzer/dyncast-1.C: Rewrite to cover
	[expr.dynamic.cast]/9.1 and /9.2 over public, non-virtual
	inheritance.
	* g++.dg/analyzer/dyncast-2.C: New test.
	* g++.dg/analyzer/dyncast-3.C: New test.
	* g++.dg/analyzer/dyncast-4.C: New test.
	* g++.dg/analyzer/dyncast-5.C: New test.

Signed-off-by: Egas Ribeiro <[email protected]>
---
 gcc/analyzer/kf-lang-cp.cc                | 293 ++++++++++++++++++++++
 gcc/analyzer/region-model.cc              |  69 +++--
 gcc/analyzer/region-model.h               |   4 +
 gcc/testsuite/g++.dg/analyzer/dyncast-1.C |  78 ++++--
 gcc/testsuite/g++.dg/analyzer/dyncast-2.C |  57 +++++
 gcc/testsuite/g++.dg/analyzer/dyncast-3.C |  50 ++++
 gcc/testsuite/g++.dg/analyzer/dyncast-4.C |  83 ++++++
 gcc/testsuite/g++.dg/analyzer/dyncast-5.C |  30 +++
 8 files changed, 627 insertions(+), 37 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/analyzer/dyncast-2.C
 create mode 100644 gcc/testsuite/g++.dg/analyzer/dyncast-3.C
 create mode 100644 gcc/testsuite/g++.dg/analyzer/dyncast-4.C
 create mode 100644 gcc/testsuite/g++.dg/analyzer/dyncast-5.C

diff --git a/gcc/analyzer/kf-lang-cp.cc b/gcc/analyzer/kf-lang-cp.cc
index 5d57bcc5fcb..8b1a8bde720 100644
--- a/gcc/analyzer/kf-lang-cp.cc
+++ b/gcc/analyzer/kf-lang-cp.cc
@@ -19,6 +19,8 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "analyzer/common.h"
+#include "cgraph.h"
+#include "ipa-utils.h"
 
 #include "diagnostic.h"
 
@@ -63,6 +65,292 @@ namespace ana {
 
 /* Implementations of specific functions.  */
 
+/* Handler for __dynamic_cast.  */
+
+/* A candidate TYPE subobject found on one inheritance path.  */
+
+struct dyncast_subobject
+{
+  dyncast_subobject () : binfo (NULL_TREE), accessible (false) {}
+  dyncast_subobject (tree binfo, bool accessible)
+    : binfo (binfo), accessible (accessible)
+  {}
+
+  tree binfo;	   /* the BINFO that represents our subobject.
+		      NULL_TREE if failed.  */
+  bool accessible; /* Every edge from the root was public.  */
+};
+
+/* Recover the class type from a type_info argument of __dynamic_cast, expected
+   to be &_ZTIxxx.  The C++ FE sets TREE_TYPE on the tinfo decl's DECL_NAME
+   identifier.  __dynamic_cast is callable directly, so a runtime tinfo pointer
+   (an SSA name) can exist.  Return NULL_TREE on any shape mismatch.  */
+
+static tree
+get_type_from_tinfo_arg (tree arg)
+{
+  if (!arg || TREE_CODE (arg) != ADDR_EXPR)
+    return NULL_TREE;
+  tree tinfo_decl = TREE_OPERAND (arg, 0);
+  if (!DECL_P (tinfo_decl) || !DECL_NAME (tinfo_decl))
+    return NULL_TREE;
+  tree type = TREE_TYPE (DECL_NAME (tinfo_decl));
+  if (!type || !RECORD_OR_UNION_TYPE_P (type))
+    return NULL_TREE;
+  return TYPE_MAIN_VARIANT (type);
+}
+
+/* Find the sub-BINFO of BINFO that has type TARGET_TYPE and sits at the same
+   address as BINFO itself (i.e. at relative offset 0) by descending through
+   every base at that same (absolute) offset.  */
+
+static tree
+lookup_binfo_at_same_offset (tree binfo, tree target_type)
+{
+  if (types_same_for_odr (BINFO_TYPE (binfo), target_type))
+    return binfo;
+
+  tree offset = BINFO_OFFSET (binfo);
+  tree base_binfo;
+  for (unsigned i = 0; BINFO_BASE_ITERATE (binfo, i, base_binfo); i++)
+    if (tree_int_cst_equal (BINFO_OFFSET (base_binfo), offset))
+      if (tree found = lookup_binfo_at_same_offset (base_binfo, target_type))
+	return found;
+  return NULL_TREE;
+}
+
+/* Look recursively for a BINFO that matches our DST_TYPE.  This method might
+   find multiple matches. If none is found, matches won't be changed.
+
+   Even if one path is private, it is still ambiguous according to the
+   definition, so that case still counts as having multiple matches.  That
+   means we ignore access specifiers when searching bases.
+
+   Morally virtual matches of the same type under different virtual ancestors
+   are distinct subobjects, i.e:
+
+			 class B0 {};
+	class V1 : B0 {};            class V2 : B0 {};
+    class B2 : virtual V1 {};    class B4 : virtual V2 {};
+		     class MD : B2, B3 {};
+
+   Here, each B0 is a different subobject, so we must account for this case
+   when checking virtual inheritance.  We compare the BINFO_OFFSET of all the
+   BINFOs that match (the offset is relative to our most-derived object) to
+   decide if they belong to the same suboject.  Note that if it is at the same
+   BINFO_OFFSET and has the same TREE_TYPE, it must necessarily be the same
+   subobject.  */
+
+static void
+lookup_subobject_matches (const_tree target_type, const dyncast_subobject match,
+			  auto_vec<dyncast_subobject> &matches)
+{
+  if (types_same_for_odr (BINFO_TYPE (match.binfo), target_type))
+    {
+      /* Check if we had already found this particular subobject.  */
+      tree match_offset = BINFO_OFFSET (match.binfo);
+      for (auto &subobject : matches)
+	if (tree_int_cst_equal (BINFO_OFFSET (subobject.binfo), match_offset))
+	  {
+	    subobject.accessible |= match.accessible;
+	    return; /* We are adding the same subobject, so skip it.  */
+	  }
+      matches.safe_push (match);
+      return;
+    }
+  tree parent_binfo = match.binfo;
+  tree base_binfo;
+  for (unsigned i = 0; BINFO_BASE_ITERATE (parent_binfo, i, base_binfo); i++)
+    {
+      dyncast_subobject child = match;
+      child.binfo = base_binfo;
+      /* If BINFO_BASE_ACCESSES is not present, public access is implied.  */
+      child.accessible
+	&= !BINFO_BASE_ACCESSES (parent_binfo)
+	   || BINFO_BASE_ACCESS (parent_binfo, i) == access_public_node;
+      /* Check if the next binfo might be our DST_TYPE binfo recursively.  */
+      lookup_subobject_matches (target_type, child, matches);
+    }
+}
+
+/* We implement the runtime check rules as per [expr.dynamic.cast]9.
+   As a general overview, those rules state:
+     [expr.dynamic.cast]/9.1: Does SRC_OBJ point to a public base subobject of
+       a DST_TYPE object?  And is there only one DST_TYPE object derived from
+       SRC_OBJ?
+       We expect the hierarchy to be something like:
+       SRC_TYPE -> ... -> DST_TYPE -> ... -> MD_TYPE.
+
+     [expr.dynamic.cast]/9.2: Otherwise, does SRC_OBJ point to a public base
+       subobject of a MDTYPE object?  And is DST_TYPE an unambiguous and public
+       base of MDTYPE?
+       We expect the hierarchy to be something like:
+       MD_TYPE -> ... -> DST_TYPE
+
+     [expr.dynamic.cast]/9.3: Otherwise, the runtime check fails.  */
+
+static dyncast_subobject
+evaluate_dyncast (tree dst_type, tree md_binfo, tree src_binfo)
+{
+  dyncast_subobject no_base_match;
+
+  /* Start by assuming the path will be public.  */
+  auto_vec<dyncast_subobject> dst_matches;
+  dyncast_subobject md_subobject = {md_binfo, /* accessible = */ true};
+  lookup_subobject_matches (dst_type, md_subobject, dst_matches);
+
+  /* Per [expr.dynamic.cast]/9.1:
+     Only one object of DST_TYPE can be derived from this SRC_OBJ and
+     The path from DST -> SRC must be public.  */
+  tree src_offset = BINFO_OFFSET (src_binfo);
+  tree src_type = BINFO_TYPE (src_binfo);
+
+  auto_vec<dyncast_subobject> clause1_matches;
+  for (const auto &dst_subobj : dst_matches)
+    {
+      auto_vec<dyncast_subobject> src_matches;
+      dyncast_subobject from_dst = {dst_subobj.binfo, /* accessible = */ true};
+      lookup_subobject_matches (src_type, from_dst, src_matches);
+      /* Only keep matches that derive from this src subobject.  */
+      for (const auto &src_subobj : src_matches)
+	if (tree_int_cst_equal (BINFO_OFFSET (src_subobj.binfo), src_offset))
+	  /* Keep dst BINFO but save whether SRC is a public base of DST.  */
+	  clause1_matches.safe_push ({dst_subobj.binfo, src_subobj.accessible});
+    }
+  if (clause1_matches.length () == 1 && clause1_matches[0].accessible)
+    return clause1_matches[0]; /* No ambiguity, only one public match.  */
+
+  /* No match or the match we found was private.  Try clause 2.  */
+
+  /* Otherwise, per [expr.dynamic.cast]/9.2:
+      Require a public path from MD_OBJ -> SRC_OBJ and
+      Require that DST_TYPE is an unambiguous and public base of MD_TYPE.  */
+  if (dst_matches.length () == 1 && dst_matches[0].accessible)
+    {
+      auto_vec<dyncast_subobject> src_matches;
+      lookup_subobject_matches (src_type, md_subobject, src_matches);
+      /* Find any public path from MD_OBJ -> SRC_OBJ.  */
+      for (const auto &src_subobj : src_matches)
+	if (src_subobj.accessible)
+	  return dst_matches[0]; /* Found a public match.  */
+    }
+  return no_base_match; /* No match or the match we found was private.  */
+}
+
+class kf_dynamic_cast : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &cd) const final override
+  {
+    /* A call will look something like:
+       Derived *d;
+       d = __dynamic_cast ((Base*) b, &_ZTI1Base, &_ZTI1Derived, 8);  */
+    return (cd.num_args () == 4 && POINTER_TYPE_P (cd.get_arg_type (0))
+	    && POINTER_TYPE_P (cd.get_arg_type (1))
+	    && POINTER_TYPE_P (cd.get_arg_type (2))
+	    && INTEGRAL_TYPE_P (cd.get_arg_type (3)));
+  }
+  void impl_call_post (const call_details &cd) const final override
+  {
+    region_model *model = cd.get_model ();
+    region_model_manager *mgr = cd.get_manager ();
+
+    cd.set_any_lhs_with_defaults ();
+
+    tree dst_ptr_type = cd.get_lhs_type ();
+    if (!dst_ptr_type)
+      return;
+
+    /* Recover the class types from the tinfo args.  */
+    tree src_type = get_type_from_tinfo_arg (cd.get_arg_tree (1));
+    tree dst_type = get_type_from_tinfo_arg (cd.get_arg_tree (2));
+    if (!src_type || !dst_type)
+      return;
+
+    /* Read the vptr binding of the object; VPTR_OFF selects the
+       sub-vtable within the vtable decl, so it identifies which subobject's
+       vptr we read.  */
+    tree src_obj = cd.get_arg_tree (0);
+    unsigned HOST_WIDE_INT vptr_off;
+    tree vtable
+      = model->get_vtable_from_obj (src_obj, src_type, mgr, nullptr, &vptr_off);
+    /* The class the vtable belongs to is the dynamic (most-derived) type of
+       the object.  VTABLE is whatever decl the vptr slot happened to point at,
+       so check it really is a vtable.  */
+    if (!vtable || !VAR_P (vtable) || !DECL_VIRTUAL_P (vtable))
+      return;
+    tree mdtype = DECL_CONTEXT (vtable);
+    if (!mdtype || !RECORD_OR_UNION_TYPE_P (mdtype))
+      return;
+    tree md_binfo = TYPE_BINFO (mdtype);
+    if (!md_binfo)
+      return;
+
+    /* Given the value stored to SRC_OBJ's vtpr field (&_ZTV* + offset), find
+       which subobject of this hierarchy would have this value written into its
+       vptr.  */
+    tree vtable_binfo
+      = subbinfo_with_vtable_at_offset (md_binfo, vptr_off, vtable);
+    if (!vtable_binfo)
+      return;
+    /* With a shared primary-base vtable the owning binfo may be an enclosing
+       type.  Consider:
+	 class A {};
+	 class B {};
+	 class C : B {};
+	 class D : A, C {};
+       Here the vptr value stored in the B-subobject's slot is owned by the C
+       binfo (C's sub-vtable group), and a lookup with B's vptr value returns
+       the C binfo, not B's (BINFO_VTABLE is only set on the owner, cf.
+       ipa-devirt.cc:61).  In this case, the src subobject sits on its primary
+       chain (relative offset 0), which lookup_binfo_at_same_offset finds.  */
+    tree src_binfo = lookup_binfo_at_same_offset (vtable_binfo, src_type);
+    if (!src_binfo)
+      return;
+
+    dyncast_subobject dst_match
+      = evaluate_dyncast (dst_type, md_binfo, src_binfo);
+
+    if (!dst_match.binfo)
+      { /* [expr.dynamic.cast]/9.3: Otherwise, the runtime check failed.  */
+	cd.maybe_set_lhs (mgr->get_or_create_null_ptr (dst_ptr_type));
+	return;
+      }
+
+    /* Build a pointer to the dst subobject.  Work in byte offsets relative to
+       SRC_REG's base region; we never need a region for the mdtype object
+       itself, only its start offset, recovered from where the src subobject
+       sits within MDTYPE.  */
+    const region *src_reg = cd.deref_ptr_arg (0);
+    region_offset off = src_reg->get_offset (mgr);
+    if (!off.concrete_p ())
+      return; /* Bail, leave lhs conjured.  */
+    byte_offset_t src_obj_start;
+    if (!off.get_concrete_byte_offset (&src_obj_start))
+      return;
+
+    HOST_WIDE_INT src_off_in_md = tree_to_shwi (BINFO_OFFSET (src_binfo));
+    HOST_WIDE_INT dst_off_in_md = tree_to_shwi (BINFO_OFFSET (dst_match.binfo));
+    HOST_WIDE_INT md_start_in_base = src_obj_start.to_shwi () - src_off_in_md;
+    if (md_start_in_base < 0)
+      return; /* Layout disagreement between the store and the binfo data;
+		 bail rather than build a negative-offset region.  */
+    HOST_WIDE_INT dst_off_in_base = md_start_in_base + dst_off_in_md;
+
+    /* BASE_REG is the outermost region, not necessarily the mdtype
+       object (it might sit at a nonzero offset inside BASE_REG, e.g. as an
+       array element or a member subobject).  The store binds values by byte
+       ranges within a base region, so a concrete offset_region aliases the
+       FE's field-path accesses to the same bytes.  */
+    const region *base_reg = off.get_base_region ();
+    const svalue *dst_off_sval
+      = mgr->get_or_create_int_cst (size_type_node, dst_off_in_base);
+    const region *dst_reg
+      = mgr->get_offset_region (base_reg, dst_type, dst_off_sval);
+    cd.maybe_set_lhs (mgr->get_ptr_svalue (dst_ptr_type, dst_reg));
+  }
+};
+
 /* Handler for "operator new" and "operator new []".  */
 
 class kf_operator_new : public known_function
@@ -371,6 +659,11 @@ register_known_functions_lang_cp (known_function_manager &kfm)
   kfm.add ("__cxa_end_catch", std::make_unique<kf_cxa_end_catch> ());
   kfm.add ("__cxa_call_unexpected",
 	   std::make_unique<kf_cxa_call_unexpected> ());
+
+  /* Itanium C++ ABI's "The dynamic_cast Algorithm"
+     https://itanium-cxx-abi.github.io/cxx-abi/abi.html#dynamic_cast-algorithm
+   */
+  kfm.add ("__dynamic_cast", std::make_unique<kf_dynamic_cast> ());
 }
 
 } // namespace ana
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index f385e008fa7..2666c2cd651 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -6490,25 +6490,23 @@ 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.
+/* Recover the vtable OBJ's vptr (for OBJ_TYPE) actually points to, plus the
+   byte offset into it, so callers can recover the most-derived type from the
+   vtable's DECL_CONTEXT and BINFO.
 
-   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.  */
+   Only recognizes the shape we model for a vptr store, "vptr_field =
+   &vtable_decl + offset", i.e. a POINTER_PLUS_EXPR binop_svalue of a
+   region_svalue for the _ZTV* decl and a constant offset.  The offset is
+   nonzero when this vptr slot belongs to a non-primary base's own sub-vtable
+   group within the same decl; we return it via OUT for callers that need to
+   index into the vtable (e.g. gimple_get_virt_method_for_vtable).  */
 
 tree
-region_model::get_fndecl_for_virtual_call (const_tree obj_type_ref,
-					   region_model_context *ctxt)
+region_model::get_vtable_from_obj (tree obj, tree obj_type,
+				   region_model_manager *mgr,
+				   region_model_context *ctxt,
+				   unsigned HOST_WIDE_INT *out) const
 {
-  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);
@@ -6517,7 +6515,7 @@ region_model::get_fndecl_for_virtual_call (const_tree obj_type_ref,
 
   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 region *vptr_reg = 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 ())
@@ -6527,20 +6525,53 @@ region_model::get_fndecl_for_virtual_call (const_tree obj_type_ref,
   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);
+  if (out)
+    *out = tree_to_uhwi (offset_const);
 
+  vptr_sval = b->get_arg0 ();
   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 ();
+  return vtable;
+}
+
+/* Attempt to get the fndecl for a virtual call via OBJ_TYPE_REF, or
+   NULL_TREE if it can't be resolved.
+
+   A virtual call's callee is a GIMPLE OBJ_TYPE_REF:
+     OBJ_TYPE_REF(EXPR; (TYPE)OBJECT->TOKEN)
+   EXPR is the function pointer actually loaded and called; OBJECT, TYPE and
+   TOKEN are devirtualization metadata, not needed to perform the call itself.
+   OBJECT is the "this" pointer, TYPE its static type, TOKEN the vtable slot
+   index.  We ignore EXPR and instead resolve TOKEN against OBJECT's modeled
+   dynamic type rather than its static TYPE.
+
+   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);
+
+  unsigned HOST_WIDE_INT offset;
+  tree vtable = get_vtable_from_obj (obj, obj_type, m_mgr, ctxt, &offset);
   if (!vtable)
     return NULL_TREE;
 
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index d1e2b014d0e..22a57a84735 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -512,6 +512,10 @@ class region_model
   tree get_fndecl_for_virtual_call (const_tree fn_ptr,
 				    region_model_context *ctxt);
 
+  tree get_vtable_from_obj (tree obj, tree obj_type, region_model_manager *mgr,
+			    region_model_context *ctxt,
+			    unsigned HOST_WIDE_INT *out = nullptr) const;
+
   void get_regions_for_current_frame (auto_vec<const decl_region *> *out) const;
   static void append_regions_cb (const region *base_reg,
 				 struct append_regions_cb_data *data);
diff --git a/gcc/testsuite/g++.dg/analyzer/dyncast-1.C b/gcc/testsuite/g++.dg/analyzer/dyncast-1.C
index 14acb91ffaa..53d62f28027 100644
--- a/gcc/testsuite/g++.dg/analyzer/dyncast-1.C
+++ b/gcc/testsuite/g++.dg/analyzer/dyncast-1.C
@@ -1,21 +1,63 @@
+/* Basic runtime checks:
+   [expr.dynamic.cast]/9.1 and /9.2 over public, non-virtual inheritance.  */
+
 #include "../../gcc.dg/analyzer/analyzer-decls.h"
 
-struct base
-{
-  virtual ~base () {}
-};
-struct sub : public base
-{
-  int m_field;
-};
-
-int
-test_1 (base *p)
-{
-  if (sub *q = dynamic_cast <sub*> (p))
-    {
-      __analyzer_dump_path (); // { dg-message "path" }
-      return q->m_field;
-    }
-  return 0;
+struct A { virtual ~A () {} };
+struct B : A { };
+struct C : B { int m; };
+struct S { virtual ~S () {} };
+struct D : C, S { };
+
+/* /9.1: SRC is a public base subobject of a unique DST object.  */
+void test_downcast () {
+  C obj;
+  obj.m = 50;
+  A *a = &obj;
+
+  __analyzer_eval (dynamic_cast<B *> (a) != NULL); /* { dg-warning "TRUE" } */
+
+  C *c = dynamic_cast<C *> (a);
+  __analyzer_eval (c != NULL); /* { dg-warning "TRUE" } */
+  /* The result region must alias the FE's own field accesses.  */
+  __analyzer_eval (c->m == 50); /* { dg-warning "TRUE" } */
+  __analyzer_eval (c == &obj);	/* { dg-warning "TRUE" } */
+}
+
+/* The dynamic type is the SRC type itself: no DST above it.  */
+void test_no_derived_object () {
+  A obj;
+  A *a = &obj;
+  __analyzer_eval (dynamic_cast<C *> (a) == NULL); /* { dg-warning "TRUE" } */
+}
+
+/* SRC sits at a nonzero offset in MDTYPE.  */
+void test_from_secondary_base () {
+  D obj;
+  obj.m = 7;
+  S *s = &obj;
+  D *d = dynamic_cast<D *> (s);
+  __analyzer_eval (d != NULL); /* { dg-warning "TRUE" } */
+  __analyzer_eval (d->m == 7); /* { dg-warning "TRUE" } */
+}
+
+/* /9.2: A and S are unrelated, both public bases of D.  */
+void test_sidecast () {
+  D obj;
+  A *a = &obj;
+  S *s = dynamic_cast<S *> (a);
+  __analyzer_eval (s != NULL);	     /* { dg-warning "TRUE" } */
+  __analyzer_eval (s == (S *) &obj); /* { dg-warning "TRUE" } */
+}
+
+/* Nothing is known about the dynamic type.  */
+void test_symbolic (A *p) {
+  __analyzer_eval (dynamic_cast<S *> (p) == NULL); /* { dg-warning "UNKNOWN" } */
+  /* { dg-warning "TRUE" "" { target *-*-* } .-1 } */
+}
+
+/* /6: a null operand yields null with no runtime check.  */
+void test_null_operand () {
+  A *p = NULL;
+  __analyzer_eval (dynamic_cast<S *> (p) == NULL); /* { dg-warning "TRUE" } */
 }
diff --git a/gcc/testsuite/g++.dg/analyzer/dyncast-2.C b/gcc/testsuite/g++.dg/analyzer/dyncast-2.C
new file mode 100644
index 00000000000..907635ea911
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/dyncast-2.C
@@ -0,0 +1,57 @@
+/* Access control.
+   /9.1 constrains only the DST -> SRC path
+   /9.2 also requires SRC to be a public base subobject of MDTYPE.  */
+
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct P1 { virtual ~P1 () {} };
+struct P2 { virtual ~P2 () {} };
+struct B : private P1 { virtual ~B () {} };
+struct C { virtual ~C () {} };
+struct U { virtual ~U () {} };
+struct MD : B, C, protected P2 { };
+
+void test_nonpublic_src () {
+  MD obj;
+  P1 *p1 = (P1 *) &obj;
+  P2 *p2 = (P2 *) &obj;
+
+  /* /9.1 fails and /9.2's first premise fails.  */
+  __analyzer_eval (dynamic_cast<B *> (p1) == NULL); /* { dg-warning "TRUE" } */
+  __analyzer_eval (dynamic_cast<C *> (p1) == NULL); /* { dg-warning "TRUE" } */
+  __analyzer_eval (dynamic_cast<C *> (p2) == NULL); /* { dg-warning "TRUE" } */
+}
+
+void test_nonpublic_dst () {
+  MD obj;
+  B *b = &obj;
+  /* P2 is a protected, U is not a base.  */
+  __analyzer_eval (dynamic_cast<P2 *> (b) == NULL); /* { dg-warning "TRUE" } */
+  __analyzer_eval (dynamic_cast<U *> (b) == NULL);  /* { dg-warning "TRUE" } */
+}
+
+/* Private edge moved across the DST object.  */
+
+struct Base { virtual ~Base () {} };
+struct Mid : Base { };
+struct Outer : private Mid { };
+struct Mid2 : private Base { };
+struct Outer2 : Mid2 { };
+
+void test_private_above_dst () {
+  Outer obj;
+  Base *b = (Base *) (Mid *) &obj;
+  /* /9.1 succeeds: Base is a public base of a unique Mid object.
+     The private Mid -> Outer edge doesn't affect /9.1.  */
+  __analyzer_eval (dynamic_cast<Mid *> (b) != NULL);   /* { dg-warning "TRUE" } */
+  /* ... but Outer itself is unreachable.  */
+  __analyzer_eval (dynamic_cast<Outer *> (b) == NULL); /* { dg-warning "TRUE" } */
+}
+
+void test_private_below_dst () {
+  Outer2 obj;
+  Base *b = (Base *) (Mid2 *) &obj;
+  /* /9.1 fails: no DST object has Base as a public base subobject.  */
+  __analyzer_eval (dynamic_cast<Mid2 *> (b) == NULL);	/* { dg-warning "TRUE" } */
+  __analyzer_eval (dynamic_cast<Outer2 *> (b) == NULL);	/* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/dyncast-3.C b/gcc/testsuite/g++.dg/analyzer/dyncast-3.C
new file mode 100644
index 00000000000..8dce3158509
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/dyncast-3.C
@@ -0,0 +1,50 @@
+/* Ambiguity ignores access, and /9.1 is anchored at one SRC subobject
+   so a repeated base need not be ambiguous there.  */
+
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct A { virtual ~A () {} };
+struct C { virtual ~C () {} };
+struct P1 : C { };
+struct P2 : C { };
+struct MDpub : A, P1, P2 { };
+struct MDpriv : A, P1, private P2 { };
+
+/* /9.1 fails (A is below no C); /9.2 sees two C subobjects.  */
+void test_ambiguous_dst () {
+  MDpub obj;
+  A *a = &obj;
+  __analyzer_eval (dynamic_cast<C *> (a) == NULL); /* { dg-warning "TRUE" } */
+}
+
+/* One of the two C subobjects is only reachable privately: ambiguity
+   ignores access, so this is still null.  */
+void test_ambiguous_dst_mixed_access () {
+  MDpriv obj;
+  A *a = &obj;
+  __analyzer_eval (dynamic_cast<C *> (a) == NULL); /* { dg-warning "TRUE" } */
+}
+
+/* The SRC subobject selects which C encloses it.  */
+void test_repeated_base_anchored_at_src () {
+  MDpub obj;
+  C *c = (C *) (P1 *) &obj;
+
+  /* /9.1: exactly one P1, and one MDpub, derive from this C.  */
+  __analyzer_eval (dynamic_cast<P1 *> (c) != NULL);    /* { dg-warning "TRUE" } */
+  __analyzer_eval (dynamic_cast<MDpub *> (c) != NULL); /* { dg-warning "TRUE" } */
+
+  /* /9.1 fails for P2 (this C is not inside one),
+     but /9.2 succeeds: P2 is an unambiguous public base of MDpub.
+     Result is the other branch of the hierarchy.  */
+  __analyzer_eval (dynamic_cast<P2 *> (c) != NULL);	   /* { dg-warning "TRUE" } */
+  __analyzer_eval (dynamic_cast<P2 *> (c) == (P2 *) &obj); /* { dg-warning "TRUE" } */
+}
+
+/* Same, with P2 private: /9.2 now fails.  */
+void test_repeated_base_private_sibling () {
+  MDpriv obj;
+  C *c = (C *) (P1 *) &obj;
+  __analyzer_eval (dynamic_cast<P1 *> (c) != NULL); /* { dg-warning "TRUE" } */
+  __analyzer_eval (dynamic_cast<P2 *> (c) == NULL); /* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/dyncast-4.C b/gcc/testsuite/g++.dg/analyzer/dyncast-4.C
new file mode 100644
index 00000000000..127b52b9c49
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/dyncast-4.C
@@ -0,0 +1,83 @@
+/* Virtual bases.  Before C++26, a constexpr constructor/destructor in a
+   class with virtual bases is rejected outright (constexpr-dynamic10.C), so
+   this part of /9 has no pre-C++26 counterpart in g++.dg/cpp2a.  */
+
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct A { virtual ~A () {} };
+
+/* One shared V subobject, reached by two paths.  */
+struct V { virtual ~V () {} int m; };
+struct L : virtual V { };
+struct R : virtual V { };
+struct Diamond : A, L, R { };
+
+void test_shared_virtual_dst () {
+  Diamond obj;
+  obj.m = 5;
+  A *a = &obj;
+  V *v = dynamic_cast<V *> (a);
+  __analyzer_eval (v != NULL); /* { dg-warning "TRUE" } */
+  __analyzer_eval (v->m == 5); /* { dg-warning "TRUE" } */
+}
+
+/* Only one of the two paths to the shared V is public.  */
+struct LPub : public virtual V { };
+struct RPriv : private virtual V { };
+struct MixedDiamond : A, LPub, RPriv { };
+
+void test_shared_virtual_dst_mixed_access () {
+  MixedDiamond obj;
+  A *a = &obj;
+  /* [class.access.base]: one public path suffices.  */
+  __analyzer_eval (dynamic_cast<V *> (a) != NULL); /* { dg-warning "TRUE" } */
+}
+
+/* Two distinct B0 subobjects, each under its own virtual ancestor.  */
+struct B0 { virtual ~B0 () {} };
+struct V1 : B0 { };
+struct V2 : B0 { };
+struct D1 : virtual V1 { };
+struct D2 : virtual V2 { };
+struct TwoB0 : A, D1, D2 { };
+
+void test_distinct_virtual_subobjects () {
+  TwoB0 obj;
+  A *a = &obj;
+  __analyzer_eval (dynamic_cast<B0 *> (a) == NULL); /* { dg-warning "TRUE" } */
+}
+
+/* SRC is a shared virtual base with two enclosing C objects: /9.1's
+   uniqueness clause fails, and /9.2 then finds C ambiguous.  */
+struct S { virtual ~S () {} };
+struct C : virtual S { };
+struct CL : C { };
+struct CR : C { };
+struct TwoC : CL, CR { };
+
+void test_virtual_src_two_enclosing_dst () {
+  TwoC obj;
+  S *s = &obj;
+  __analyzer_eval (dynamic_cast<C *> (s) == NULL); /* { dg-warning "TRUE" } */
+}
+
+/* Exactly one C derives from the shared S, and it is private in the most
+   derived object: /9.1 succeeds where /9.2 cannot.  */
+struct OneC : private C { };
+
+void test_virtual_src_one_enclosing_dst () {
+  OneC obj;
+  S *s = (S *) &obj;
+  __analyzer_eval (dynamic_cast<C *> (s) != NULL); /* { dg-warning "TRUE" } */
+}
+
+/* As above, but the operand was formed through a sibling that is not a
+   C, so the enclosing C is off that path.  */
+struct NotC : virtual S { };
+struct SideC : NotC, private C { };
+
+void test_virtual_src_dst_off_the_path () {
+  SideC obj;
+  S *s = (S *) (NotC *) &obj;
+  __analyzer_eval (dynamic_cast<C *> (s) != NULL); /* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/dyncast-5.C b/gcc/testsuite/g++.dg/analyzer/dyncast-5.C
new file mode 100644
index 00000000000..2bdcfde0a3c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/dyncast-5.C
@@ -0,0 +1,30 @@
+/* /10: a failed cast to reference type throws std::bad_cast.  */
+
+#include <typeinfo>
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct A { virtual ~A () {} };
+struct B : A { int m; };
+
+void test_ref_success () {
+  B obj;
+  obj.m = 3;
+  A &a = obj;
+  B &b = dynamic_cast<B &> (a);
+  __analyzer_eval (b.m == 3);	/* { dg-warning "TRUE" } */
+}
+
+void test_ref_failure () {
+  A obj;
+  A &a = obj;
+  try
+    {
+      B &b = dynamic_cast<B &> (a);
+      __analyzer_dump_path ();	/* { dg-bogus "path" } */
+      (void) b;
+    }
+  catch (std::bad_cast &)
+    {
+      __analyzer_dump_path ();	/* { dg-message "path" } */
+    }
+}
-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.