Re: [PATCH] analyzer: add dynamic_cast support [PR110578]
David Malcolm <[email protected]>
| Newsgroups | gmane.comp.gcc.patches |
|---|---|
| Message-ID | <[email protected]> |
On Thu, 2026-08-13 at 19:19 -0400, David Malcolm wrote: > On Sat, 2026-08-01 at 13:14 +0100, Egas Ribeiro wrote: > > Bootstrapped/regtested on x86_64-pc-linux-gnu. > > > > I cc'd Jakub and Jason so I could get input on my test coverage and > > on > > the implementation of evaluate_dyncast. Everything is done on > > GIMPLE > > other than some calls in impl_call_post, so its effectively just an > > implementation of static evaluation of dynamic_cast but relying on > > the > > analyzer region/store model to get some information. > > > > As I mentioned to Jakub on IRC, I found that on some test cases > > cxx_eval_dynamic_cast wasn't agreeing with the runtime > > dynamic_cast, > > so > > I also opened a BZ for that: > > https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126510 > > > > My implementation with the analyzer matches the runtime for that > > case. > > > > There was no particular way for me to reuse the methods that > > cxx_eval_dynamic_cast uses because not only are they in the > > frontend > > (so > > I can't access them) but also they consider more c++ semantics than > > I > > have to for what I am doing, so my solution was to reimplement the > > rules > > as per the c++ standard. All things considered the implementation > > isn't > > too extensive. Would like some feedback from David whether this is > > a > > good approach here. > > Sorry about the delay in reviewing this. > > Could you please split this patch up into two parts: > > (a) an initial part that just has these bits: > > gcc/analyzer/ChangeLog: > > * analyzer.cc (is_fndecl_in_toplevel_namespace_p): New > function, > factored out of... > (is_std_function_p): ...here. Call it. > (is_cxxabi_function_p): New function. > * common.h (is_cxxabi_function_p): New decl. > * known-function-manager.cc > (known_function_manager::get_match): > Also match functions declared in namespace __cxxabiv1. > > (b) everything else > > I think (a) is pretty-much ready to go. I'm attempting to review > (b), > trying to compare it to cxx_eval_dynamic_cast_fn, but I'm not very > familiar with C++'s constexpr code, sorry. > > I'll continue to look at (b). Some review of the (b) parts of the patch inline below: > > -- >8 -- > > > > 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 Nit: is this cxx_eval_dynamic_cast_fn? (with a "_fn" suffix) > > 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: > > > > * analyzer.cc (is_fndecl_in_toplevel_namespace_p): New > > function, > > factored out of... > > (is_std_function_p): ...here. Call it. > > (is_cxxabi_function_p): New function. > > * common.h (is_cxxabi_function_p): New decl. > > * 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". > > * known-function-manager.cc > > (known_function_manager::get_match): > > Also match functions declared in namespace __cxxabiv1. > > * 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/analyzer.cc | 26 +- > > gcc/analyzer/common.h | 1 + > > gcc/analyzer/kf-lang-cp.cc | 286 > > ++++++++++++++++++++++ > > gcc/analyzer/known-function-manager.cc | 10 +- > > gcc/analyzer/region-model.cc | 79 ++++-- > > 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 +++ > > 11 files changed, 657 insertions(+), 47 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 > > [...snip...] > > --- 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,287 @@ namespace ana { > > > > /* Implementations of specific functions. */ > > > > +/* Handler for __dynamic_cast. */ > > + > > +/* A candidate TYPE subobject found on one inheritance path. */ > > + > > +struct dyncast_subobject > > +{ > > + 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); > > +} Does this correspond to something in the C++ frontend, or in ipa- devirt.cc? > > + > > +/* 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; > > +} Likewise: does this correspond to something in the C++ frontend, or in ipa-devirt.cc? > > + > > +/* 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 = {.binfo = NULL_TREE, > > .accessible > > = false}; > > + > > + /* Start by assuming the path will be public. */ > > + auto_vec<dyncast_subobject> dst_matches; > > + dyncast_subobject md_subobject = {.binfo = md_binfo, .accessible > > = > > true}; Am I right in thinking that this is using the "designated initializer" syntax here? I believe this was added in C++20, but we're stuck using C++14. Would it make sense to add a default ctor to the struct? (not sure) > > + 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}; Likewise here. > > + 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 +654,9 @@ 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> ()); > > + > > + /* TODO: [add Itanium C++ ABI mention of __dynamic_cast here] > > */ > > + kfm.add ("__dynamic_cast", std::make_unique<kf_dynamic_cast> > > ()); > > } > > > > } // namespace ana > > diff --git a/gcc/analyzer/known-function-manager.cc > > b/gcc/analyzer/known-function-manager.cc > > index fcc9a618d15..679fe1a7015 100644 > > --- a/gcc/analyzer/known-function-manager.cc > > +++ b/gcc/analyzer/known-function-manager.cc > > @@ -120,9 +120,13 @@ known_function_manager::get_match (tree > > fndecl, > > const call_details &cd) const > > return nullptr; > > } > > > > - if (DECL_CONTEXT (fndecl) > > - && TREE_CODE (DECL_CONTEXT (fndecl)) != > > TRANSLATION_UNIT_DECL) > > - return nullptr; > > + /* Only match functions declared at global scope, or within > > namespace > > + __cxxabiv1 (e.g. __dynamic_cast). */ > > + if (!is_cxxabi_function_p (fndecl)) > > + if (DECL_CONTEXT (fndecl) > > + && TREE_CODE (DECL_CONTEXT (fndecl)) != > > TRANSLATION_UNIT_DECL) > > + return nullptr; > > + > > if (tree identifier = DECL_NAME (fndecl)) > > if (const known_function *candidate = get_by_identifier > > (identifier)) > > if (candidate->matches_call_types_p (cd)) > > diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region- > > model.cc > > index 8addf1d9a07..caa166497df 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,55 @@ 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) > > + { > > + 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; > > + *out = tree_to_uhwi (offset_const); > > + } I notice that the failure path here is now only checked/triggered when "out" is non-null. Is that intentional? > > > > + 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); Rather than passing &offset to get_vtable_from_obj and discarding the result, could we simply pass nullptr? > > if (!vtable) > > return NULL_TREE; > > [...snip...] Thanks Dave