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