| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // -*- Mode: C++ -*- |
| // |
| // Copyright (C) 2013-2020 Red Hat, Inc. |
| // |
| // Author: Dodji Seketeli |
| |
| /// @file |
| /// |
| /// This file contains definitions of diff objects filtering |
| /// facilities. |
| |
| #include "abg-internal.h" |
| #include <memory> |
| // <headers defining libabigail's API go under here> |
| ABG_BEGIN_EXPORT_DECLARATIONS |
| |
| #include "abg-comp-filter.h" |
| #include "abg-tools-utils.h" |
| |
| ABG_END_EXPORT_DECLARATIONS |
| // </headers defining libabigail's API> |
| |
| namespace abigail |
| { |
| namespace comparison |
| { |
| namespace filtering |
| { |
| |
| using std::dynamic_pointer_cast; |
| |
| /// Walk the diff sub-trees of a a @ref corpus_diff and apply a filter |
| /// to the nodes visted. The filter categorizes each node, assigning |
| /// it into one or several categories. |
| /// |
| /// @param filter the filter to apply to the diff nodes |
| /// |
| /// @param d the corpus diff to apply the filter to. |
| void |
| apply_filter(filter_base& filter, corpus_diff_sptr d) |
| { |
| bool s = d->context()->visiting_a_node_twice_is_forbidden(); |
| d->context()->forbid_visiting_a_node_twice(false); |
| d->traverse(filter); |
| d->context()->forbid_visiting_a_node_twice(s); |
| } |
| |
| /// Walk a diff sub-tree and apply a filter to the nodes visted. The |
| /// filter categorizes each node, assigning it into one or several |
| /// categories. |
| /// |
| /// Note that this function makes sure to avoid visiting a node (or |
| /// any other node equivalent to it) more than once. This helps avoid |
| /// infinite loops for diff trees that involve type changes that |
| /// reference themselves. |
| /// |
| /// @param filter the filter to apply to the nodes of the sub-tree. |
| /// |
| /// @param d the diff sub-tree to walk and apply the filter to. |
| void |
| apply_filter(filter_base& filter, diff_sptr d) |
| { |
| bool s = d->context()->visiting_a_node_twice_is_forbidden(); |
| d->context()->forbid_visiting_a_node_twice(true); |
| d->context()->forget_visited_diffs(); |
| d->traverse(filter); |
| d->context()->forbid_visiting_a_node_twice(s); |
| } |
| |
| /// Walk a diff sub-tree and apply a filter to the nodes visted. The |
| /// filter categorizes each node, assigning it into one or several |
| /// categories. |
| /// |
| /// Note that this function makes sure to avoid visiting a node (or |
| /// any other node equivalent to it) more than once. This helps avoid |
| /// infinite loops for diff trees that involve type changes that |
| /// reference themselves. |
| /// |
| /// @param filter the filter to apply to the nodes of the sub-tree. |
| /// |
| /// @param d the diff sub-tree to walk and apply the filter to. |
| void |
| apply_filter(filter_base_sptr filter, diff_sptr d) |
| {apply_filter(*filter, d);} |
| |
| /// Test if there is a class that is declaration-only among the two |
| /// classes in parameter. |
| /// |
| /// @param class1 the first class to consider. |
| /// |
| /// @param class2 the second class to consider. |
| /// |
| /// @return true if either classes are declaration-only, false |
| /// otherwise. |
| static bool |
| there_is_a_decl_only_class(const class_decl_sptr& class1, |
| const class_decl_sptr& class2) |
| { |
| if ((class1 && class1->get_is_declaration_only()) |
| || (class2 && class2->get_is_declaration_only())) |
| return true; |
| return false; |
| } |
| |
| /// Test if there is a enum that is declaration-only among the two |
| /// enums in parameter. |
| /// |
| /// @param enum1 the first enum to consider. |
| /// |
| /// @param enum2 the second enum to consider. |
| /// |
| /// @return true if either enums are declaration-only, false |
| /// otherwise. |
| static bool |
| there_is_a_decl_only_enum(const enum_type_decl_sptr& enum1, |
| const enum_type_decl_sptr& enum2) |
| { |
| if ((enum1 && enum1->get_is_declaration_only()) |
| || (enum2 && enum2->get_is_declaration_only())) |
| return true; |
| return false; |
| } |
| |
| /// Test if the diff involves a declaration-only class. |
| /// |
| /// @param diff the class diff to consider. |
| /// |
| /// @return true iff the diff involves a declaration-only class. |
| static bool |
| diff_involves_decl_only_class(const class_diff* diff) |
| { |
| if (diff && there_is_a_decl_only_class(diff->first_class_decl(), |
| diff->second_class_decl())) |
| return true; |
| return false; |
| } |
| |
| /// Tests if the size of a given type changed. |
| /// |
| /// @param f the first version of the type to consider. |
| /// |
| /// @param s the second version of the type to consider. |
| /// |
| /// @return true if the type size changed, false otherwise. |
| static bool |
| type_size_changed(const type_base_sptr f, const type_base_sptr s) |
| { |
| if (!f || !s |
| || f->get_size_in_bits() == 0 |
| || s->get_size_in_bits() == 0 |
| || there_is_a_decl_only_class(is_compatible_with_class_type(f), |
| is_compatible_with_class_type(s)) |
| || there_is_a_decl_only_enum(is_compatible_with_enum_type(f), |
| is_compatible_with_enum_type(s))) |
| return false; |
| |
| return f->get_size_in_bits() != s->get_size_in_bits(); |
| } |
| |
| /// Tests if the size of a given type changed. |
| /// |
| /// @param f the declaration of the first version of the type to |
| /// consider. |
| /// |
| /// @param s the declaration of the second version of the type to |
| /// consider. |
| /// |
| /// @return true if the type size changed, false otherwise. |
| static bool |
| type_size_changed(const decl_base_sptr f, const decl_base_sptr s) |
| {return type_size_changed(is_type(f), is_type(s));} |
| |
| /// Test if a given type diff node carries a type size change. |
| /// |
| /// @param diff the diff tree node to test. |
| /// |
| /// @return true if @p diff carries a type size change. |
| static bool |
| has_type_size_change(const diff* diff) |
| { |
| if (!diff) |
| return false; |
| |
| if (const fn_parm_diff* fn_parm_d = is_fn_parm_diff(diff)) |
| diff = fn_parm_d->type_diff().get(); |
| |
| type_base_sptr f = is_type(diff->first_subject()), |
| s = is_type(diff->second_subject()); |
| |
| if (!f || !s) |
| return false; |
| |
| return type_size_changed(f, s); |
| } |
| /// Tests if the access specifiers for a member declaration changed. |
| /// |
| /// @param f the declaration for the first version of the member |
| /// declaration to consider. |
| /// |
| /// @param s the declaration for the second version of the member |
| /// delcaration to consider. |
| /// |
| /// @return true iff the access specifier changed. |
| static bool |
| access_changed(const decl_base_sptr& f, const decl_base_sptr& s) |
| { |
| if (!is_member_decl(f) |
| || !is_member_decl(s)) |
| return false; |
| |
| access_specifier fa = get_member_access_specifier(f), |
| sa = get_member_access_specifier(s); |
| |
| if (sa != fa) |
| return true; |
| |
| return false; |
| } |
| |
| /// Test if there was a function or variable CRC change. |
| /// |
| /// @param f the first function or variable to consider. |
| /// |
| /// @param s the second function or variable to consider. |
| /// |
| /// @return true if the test is positive, false otherwise. |
| template <typename function_or_var_decl_sptr> |
| static bool |
| crc_changed(const function_or_var_decl_sptr& f, |
| const function_or_var_decl_sptr& s) |
| { |
| const auto symbol_f = f->get_symbol(), symbol_s = s->get_symbol(); |
| if (!symbol_f || !symbol_s) |
| return false; |
| const auto crc_f = symbol_f->get_crc(), crc_s = symbol_s->get_crc(); |
| return (crc_f != 0 && crc_s != 0 && crc_f != crc_s); |
| } |
| |
| /// Test if the current diff tree node carries a CRC change in either a |
| /// function or a variable. |
| /// |
| /// @param diff the diff tree node to consider. |
| /// |
| /// @return true if the test is positive, false otherwise. |
| static bool |
| crc_changed(const diff* diff) |
| { |
| if (const function_decl_diff* d = |
| dynamic_cast<const function_decl_diff*>(diff)) |
| return crc_changed(d->first_function_decl(), d->second_function_decl()); |
| if (const var_diff* d = dynamic_cast<const var_diff*>(diff)) |
| return crc_changed(d->first_var(), d->second_var()); |
| return false; |
| } |
| |
| /// Test if there was a function name change, but there there was no |
| /// change in name of the underlying symbol. IOW, if the name of a |
| /// function changed, but the symbol of the new function is equal to |
| /// the symbol of the old one, or is equal to an alians of the symbol |
| /// of the old function. |
| /// |
| /// @param f the first function to consider. |
| /// |
| /// @param s the second function to consider. |
| /// |
| /// @return true if the test is positive, false otherwise. |
| static bool |
| function_name_changed_but_not_symbol(const function_decl_sptr& f, |
| const function_decl_sptr& s) |
| { |
| if (!f || !s) |
| return false; |
| string fn = f->get_qualified_name(), |
| sn = s->get_qualified_name(); |
| |
| if (fn != sn) |
| { |
| elf_symbol_sptr fs = f->get_symbol(), ss = s->get_symbol(); |
| if (fs == ss) |
| return true; |
| if (!!fs != !!ss) |
| return false; |
| for (elf_symbol_sptr s = fs->get_next_alias(); |
| s && !s->is_main_symbol(); |
| s = s->get_next_alias()) |
| if (*s == *ss) |
| return true; |
| } |
| return false; |
| } |
| |
| /// Test if the current diff tree node carries a function name change, |
| /// in which there there was no change in the name of the underlying |
| /// symbol. IOW, if the name of a function changed, but the symbol of |
| /// the new function is equal to the symbol of the old one, or is |
| /// equal to an alians of the symbol of the old function. |
| /// |
| /// @param diff the diff tree node to consider. |
| /// |
| /// @return true if the test is positive, false otherwise. |
| static bool |
| function_name_changed_but_not_symbol(const diff* diff) |
| { |
| if (const function_decl_diff* d = |
| dynamic_cast<const function_decl_diff*>(diff)) |
| return function_name_changed_but_not_symbol(d->first_function_decl(), |
| d->second_function_decl()); |
| return false; |
| } |
| |
| /// Tests if the offset of a given data member changed. |
| /// |
| /// @param f the declaration for the first version of the data member to |
| /// consider. |
| /// |
| /// @param s the declaration for the second version of the data member |
| /// to consider. |
| /// |
| /// @return true iff the offset of the data member changed. |
| static bool |
| data_member_offset_changed(decl_base_sptr f, decl_base_sptr s) |
| { |
| if (!is_member_decl(f) |
| || !is_member_decl(s)) |
| return false; |
| |
| var_decl_sptr v0 = dynamic_pointer_cast<var_decl>(f), |
| v1 = dynamic_pointer_cast<var_decl>(s); |
| if (!v0 || !v1) |
| return false; |
| |
| if (get_data_member_offset(v0) != get_data_member_offset(v1)) |
| return true; |
| |
| return false; |
| } |
| |
| /// Test if the size of a non-static data member changed accross two |
| /// versions. |
| /// |
| /// @param f the first version of the non-static data member. |
| /// |
| /// @param s the second version of the non-static data member. |
| static bool |
| non_static_data_member_type_size_changed(const decl_base_sptr& f, |
| const decl_base_sptr& s) |
| { |
| if (!is_member_decl(f) |
| || !is_member_decl(s)) |
| return false; |
| |
| var_decl_sptr fv = dynamic_pointer_cast<var_decl>(f), |
| sv = dynamic_pointer_cast<var_decl>(s); |
| if (!fv |
| || !sv |
| || get_member_is_static(fv) |
| || get_member_is_static(sv)) |
| return false; |
| |
| return type_size_changed(fv->get_type(), sv->get_type()); |
| } |
| |
| /// Test if the size of a static data member changed accross two |
| /// versions. |
| /// |
| /// @param f the first version of the static data member. |
| /// |
| /// @param s the second version of the static data member. |
| static bool |
| static_data_member_type_size_changed(const decl_base_sptr& f, |
| const decl_base_sptr& s) |
| { |
| if (!is_member_decl(f) |
| || !is_member_decl(s)) |
| return false; |
| |
| var_decl_sptr fv = dynamic_pointer_cast<var_decl>(f), |
| sv = dynamic_pointer_cast<var_decl>(s); |
| if (!fv |
| || !sv |
| || !get_member_is_static(fv) |
| || !get_member_is_static(sv)) |
| return false; |
| |
| return type_size_changed(fv->get_type(), sv->get_type()); |
| } |
| |
| /// Test if two types are different but compatible. |
| /// |
| /// @param d1 the declaration of the first type to consider. |
| /// |
| /// @param d2 the declaration of the second type to consider. |
| /// |
| /// @return true if d1 and d2 are different but compatible. |
| static bool |
| is_compatible_change(const decl_base_sptr& d1, const decl_base_sptr& d2) |
| { |
| if ((d1 && d2) |
| && (d1 != d2) |
| && types_are_compatible(d1, d2)) |
| return true; |
| return false; |
| } |
| |
| /// Test if two decls have different names. |
| /// |
| /// @param d1 the first declaration to consider. |
| /// |
| /// @param d2 the second declaration to consider. |
| /// |
| /// @return true if d1 and d2 have different names. |
| static bool |
| decl_name_changed(const type_or_decl_base* a1, const type_or_decl_base *a2) |
| { |
| string d1_name, d2_name; |
| |
| const decl_base *d1 = dynamic_cast<const decl_base*>(a1); |
| if (d1 == 0) |
| return false; |
| |
| const decl_base *d2 = dynamic_cast<const decl_base*>(a2); |
| if (d2 == 0) |
| return false; |
| |
| if (d1) |
| d1_name = d1->get_qualified_name(); |
| if (d2) |
| d2_name = d2->get_qualified_name(); |
| |
| return d1_name != d2_name; |
| } |
| |
| /// Test if two decls have different names. |
| /// |
| /// @param d1 the first declaration to consider. |
| /// |
| /// @param d2 the second declaration to consider. |
| /// |
| /// @return true if d1 and d2 have different names. |
| static bool |
| decl_name_changed(const type_or_decl_base_sptr& d1, |
| const type_or_decl_base_sptr& d2) |
| {return decl_name_changed(d1.get(), d2.get());} |
| |
| /// Test if a diff nodes carries a changes in which two decls have |
| /// different names. |
| /// |
| /// @param d the diff node to consider. |
| /// |
| /// @return true iff d carries a changes in which two decls have |
| /// different names. |
| static bool |
| decl_name_changed(const diff *d) |
| {return decl_name_changed(d->first_subject(), d->second_subject());} |
| |
| /// Test if two decls represents a harmless name change. |
| /// |
| /// For now, a harmless name change is considered only for a typedef, |
| /// enum or a data member. |
| /// |
| /// @param f the first decl to consider in the comparison. |
| /// |
| /// @param s the second decl to consider in the comparison. |
| /// |
| /// @return true iff decl @p s represents a harmless change over @p f. |
| bool |
| has_harmless_name_change(const decl_base_sptr& f, const decl_base_sptr& s) |
| { |
| // So, a harmless name change is either ... |
| return (decl_name_changed(f, s) |
| && (// ... an anonymous decl name changed into another |
| // anonymous decl name ... |
| (f->get_is_anonymous() && s->get_is_anonymous()) |
| || |
| // ... an anonymous decl name changed harmlessly into |
| // another anonymous decl name ... |
| ((f->get_is_anonymous_or_has_anonymous_parent() |
| && s->get_is_anonymous_or_has_anonymous_parent()) |
| && tools_utils::decl_names_equal(f->get_qualified_name(), |
| s->get_qualified_name())) |
| // ... a typedef name change, without having the |
| // underlying type changed ... |
| || (is_typedef(f) |
| && is_typedef(s) |
| && (is_typedef(f)->get_underlying_type() |
| == is_typedef(s)->get_underlying_type())) |
| // .. or a data member name change, without having its |
| // type changed ... |
| || (is_data_member(f) |
| && is_data_member(s) |
| && (is_var_decl(f)->get_type() |
| == is_var_decl(s)->get_type())) |
| // .. an enum name change without having any other part |
| // of the enum to change. |
| || (is_enum_type(f) |
| && is_enum_type(s) |
| && !enum_has_non_name_change(*is_enum_type(f), |
| *is_enum_type(s), |
| 0)))); |
| } |
| |
| /// Test if two decls represents a harmful name change. |
| /// |
| /// A harmful name change is a name change that is not harmless, so |
| /// this function uses the function has_harmless_name_change. |
| /// |
| /// @param f the first decl to consider in the comparison. |
| /// |
| /// @param s the second decl to consider in the comparison. |
| /// |
| /// @return true iff decl @p s represents a harmful name change over |
| /// @p f. |
| bool |
| has_harmful_name_change(const decl_base_sptr& f, const decl_base_sptr& s) |
| {return decl_name_changed(f, s) && ! has_harmless_name_change(f, s);} |
| |
| /// Test if a diff node represents a harmful name change. |
| /// |
| /// A harmful name change is a name change that is not harmless, so |
| /// this function uses the function has_harmless_name_change. |
| /// |
| /// @param f the first decl to consider in the comparison. |
| /// |
| /// @param s the second decl to consider in the comparison. |
| /// |
| /// @return true iff decl @p s represents a harmful name change over |
| /// @p f. |
| bool |
| has_harmful_name_change(const diff* dif) |
| { |
| decl_base_sptr f = is_decl(dif->first_subject()), |
| s = is_decl(dif->second_subject()); |
| |
| return has_harmful_name_change(f, s); |
| } |
| |
| /// Test if a class_diff node has non-static members added or |
| /// removed. |
| /// |
| /// @param diff the diff node to consider. |
| /// |
| /// @return true iff the class_diff node has non-static members added |
| /// or removed. |
| static bool |
| non_static_data_member_added_or_removed(const class_diff* diff) |
| { |
| if (diff && !diff_involves_decl_only_class(diff)) |
| { |
| for (string_decl_base_sptr_map::const_iterator i = |
| diff->inserted_data_members().begin(); |
| i != diff->inserted_data_members().end(); |
| ++i) |
| if (!get_member_is_static(i->second)) |
| return true; |
| |
| for (string_decl_base_sptr_map::const_iterator i = |
| diff->deleted_data_members().begin(); |
| i != diff->deleted_data_members().end(); |
| ++i) |
| if (!get_member_is_static(i->second)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /// Test if a class_diff node has members added or removed. |
| /// |
| /// @param diff the diff node to consider. |
| /// |
| /// @return true iff the class_diff node has members added or removed. |
| static bool |
| non_static_data_member_added_or_removed(const diff* diff) |
| { |
| return non_static_data_member_added_or_removed |
| (dynamic_cast<const class_diff*>(diff)); |
| } |
| |
| /// Test if a @ref class_or_union_diff has a data member replaced by |
| /// an anonymous data member in a harmless way. That means, the new |
| /// anonymous data member somehow contains the replaced data member |
| /// and it doesn't break the layout of the containing class. |
| /// |
| /// @param diff the diff node to consider. |
| /// |
| /// @return true iff the @ref class_or_union_diff has a data member |
| /// harmlessly replaced by an anonymous data member. |
| bool |
| has_data_member_replaced_by_anon_dm(const diff* diff) |
| { |
| const class_or_union_diff *c = is_class_or_union_diff(diff); |
| |
| if (!c) |
| return false; |
| return !c->data_members_replaced_by_adms().empty(); |
| } |
| |
| /// Test if a class_diff node has static members added or removed. |
| /// |
| /// @param diff the diff node to consider. |
| /// |
| /// @return true iff the class_diff node has static members added |
| /// or removed. |
| static bool |
| static_data_member_added_or_removed(const class_diff* diff) |
| { |
| if (diff && !diff_involves_decl_only_class(diff)) |
| { |
| for (string_decl_base_sptr_map::const_iterator i = |
| diff->inserted_data_members().begin(); |
| i != diff->inserted_data_members().end(); |
| ++i) |
| if (get_member_is_static(i->second)) |
| return true; |
| |
| for (string_decl_base_sptr_map::const_iterator i = |
| diff->deleted_data_members().begin(); |
| i != diff->deleted_data_members().end(); |
| ++i) |
| if (get_member_is_static(i->second)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /// Test if a class_diff node has a harmless "One Definition Rule" |
| /// violation that will cause a diagnostic rule. |
| /// |
| /// The conditions this function looks for are: |
| /// |
| /// 1/ The two subject of the diff must be canonically different |
| /// |
| /// 2/ The two subjects of the diff must be structurally equal |
| /// |
| /// 3/ The canonical types of the subjects of the diff must be |
| /// structurally different. |
| /// |
| /// These conditions makes the diff node appears as it carries changes |
| /// (because of a ODR glitch present in the binary), but the glitch |
| /// has no effect on the structural equality of the subjects of the |
| /// diff. If we do not detect these conditions, we'd end up with a |
| /// diagnostic glitch where the reporter thinks there is an ABI change |
| /// (because of the canonical difference), but then it fails to give |
| /// any detail about it, because there is no structural change. |
| /// |
| /// @param diff the diff node to consider. |
| /// |
| /// @return true iff the the diff node has a harmless "One Definition |
| /// Rule" violation that cause an empty false positive. |
| static bool |
| class_diff_has_harmless_odr_violation_change(const diff* dif) |
| { |
| class_diff* d = dynamic_cast<class_diff*>(const_cast<diff*>(dif)); |
| if (!d || !d->has_changes()) |
| return false; |
| |
| class_decl_sptr first = d->first_class_decl(); |
| class_decl_sptr second = d->second_class_decl(); |
| |
| if (equals(*first, *second, 0)) |
| { |
| class_decl_sptr fc = is_class_type(first->get_canonical_type()); |
| class_decl_sptr sc = is_class_type(second->get_canonical_type()); |
| |
| if (!equals(*fc, *sc, 0)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /// Test if a class_diff node has static members added or |
| /// removed. |
| /// |
| /// @param diff the diff node to consider. |
| /// |
| /// @return true iff the class_diff node has static members added |
| /// or removed. |
| static bool |
| static_data_member_added_or_removed(const diff* diff) |
| { |
| return static_data_member_added_or_removed |
| (dynamic_cast<const class_diff*>(diff)); |
| } |
| |
| /// Test if the class_diff node has a change involving virtual member |
| /// functions. |
| /// |
| /// That means whether there is an added, removed or changed virtual |
| /// member function. |
| /// |
| /// @param diff the class_diff node to consider. |
| /// |
| /// @return true iff the class_diff node contains changes involving |
| /// virtual member functions. |
| static bool |
| has_virtual_mem_fn_change(const class_diff* diff) |
| { |
| if (!diff || diff_involves_decl_only_class(diff)) |
| return false; |
| |
| for (string_member_function_sptr_map::const_iterator i = |
| diff->deleted_member_fns().begin(); |
| i != diff->deleted_member_fns().end(); |
| ++i) |
| { |
| if (get_member_function_is_virtual(i->second)) |
| { |
| // Do not consider a virtual function that got deleted from |
| // an offset and re-inserted at the same offset as a |
| // "virtual member function change". |
| string_member_function_sptr_map::const_iterator j = |
| diff->inserted_member_fns().find(i->first); |
| if (j != diff->inserted_member_fns().end() |
| && (get_member_function_vtable_offset(i->second) |
| == get_member_function_vtable_offset(j->second))) |
| continue; |
| |
| return true; |
| } |
| } |
| |
| for (string_member_function_sptr_map::const_iterator i = |
| diff->inserted_member_fns().begin(); |
| i != diff->inserted_member_fns().end(); |
| ++i) |
| { |
| if (get_member_function_is_virtual(i->second)) |
| { |
| // Do not consider a virtual function that got deleted from |
| // an offset and re-inserted at the same offset as a |
| // "virtual member function change". |
| string_member_function_sptr_map::const_iterator j = |
| diff->deleted_member_fns().find(i->first); |
| if (j != diff->deleted_member_fns().end() |
| && (get_member_function_vtable_offset(i->second) |
| == get_member_function_vtable_offset(j->second))) |
| continue; |
| |
| return true; |
| } |
| } |
| |
| for (function_decl_diff_sptrs_type::const_iterator i = |
| diff->changed_member_fns().begin(); |
| i != diff->changed_member_fns().end(); |
| ++i) |
| if (get_member_function_is_virtual((*i)->first_function_decl()) |
| || get_member_function_is_virtual((*i)->second_function_decl())) |
| { |
| if (get_member_function_vtable_offset((*i)->first_function_decl()) |
| == get_member_function_vtable_offset((*i)->second_function_decl())) |
| continue; |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /// Test if the function_decl_diff node has a change involving virtual |
| /// member functions. |
| /// |
| /// That means whether there is an added, removed or changed virtual |
| /// member function. |
| /// |
| /// @param diff the function_decl_diff node to consider. |
| /// |
| /// @return true iff the function_decl_diff node contains changes |
| /// involving virtual member functions. |
| bool |
| has_virtual_mem_fn_change(const function_decl_diff* diff) |
| { |
| if (!diff) |
| return false; |
| |
| function_decl_sptr ff = diff->first_function_decl(), |
| sf = diff->second_function_decl(); |
| |
| if (!is_member_function(ff) |
| || !is_member_function(sf)) |
| return false; |
| |
| bool ff_is_virtual = get_member_function_is_virtual(ff), |
| sf_is_virtual = get_member_function_is_virtual(sf); |
| |
| if (ff_is_virtual != sf_is_virtual) |
| return true; |
| |
| size_t ff_vtable_offset = get_member_function_vtable_offset(ff), |
| sf_vtable_offset = get_member_function_vtable_offset(sf); |
| |
| if (ff_vtable_offset != sf_vtable_offset) |
| return true; |
| |
| return false; |
| } |
| |
| /// Test if the class_diff node has a change involving virtual member |
| /// functions. |
| /// |
| /// That means whether there is an added, removed or changed virtual |
| /// member function. |
| /// |
| /// @param diff the class_diff node to consider. |
| /// |
| /// @return true iff the class_diff node contains changes involving |
| /// virtual member functions. |
| static bool |
| has_virtual_mem_fn_change(const diff* diff) |
| { |
| return (has_virtual_mem_fn_change(dynamic_cast<const class_diff*>(diff)) |
| || has_virtual_mem_fn_change(dynamic_cast<const function_decl_diff*>(diff))); |
| } |
| |
| /// Test if the class_diff has changes to non virtual member |
| /// functions. |
| /// |
| ///@param diff the class_diff nod e to consider. |
| /// |
| /// @retrurn iff the class_diff node has changes to non virtual member |
| /// functions. |
| static bool |
| has_non_virtual_mem_fn_change(const class_diff* diff) |
| { |
| if (!diff || diff_involves_decl_only_class(diff)) |
| return false; |
| |
| for (string_member_function_sptr_map::const_iterator i = |
| diff->deleted_member_fns().begin(); |
| i != diff->deleted_member_fns().end(); |
| ++i) |
| if (!get_member_function_is_virtual(i->second)) |
| return true; |
| |
| for (string_member_function_sptr_map::const_iterator i = |
| diff->inserted_member_fns().begin(); |
| i != diff->inserted_member_fns().end(); |
| ++i) |
| if (!get_member_function_is_virtual(i->second)) |
| return true; |
| |
| for (function_decl_diff_sptrs_type::const_iterator i = |
| diff->changed_member_fns().begin(); |
| i != diff->changed_member_fns().end(); |
| ++i) |
| if(!get_member_function_is_virtual((*i)->first_function_decl()) |
| && !get_member_function_is_virtual((*i)->second_function_decl())) |
| return true; |
| |
| return false; |
| } |
| |
| /// Test if the class_diff has changes to non virtual member |
| /// functions. |
| /// |
| ///@param diff the class_diff nod e to consider. |
| /// |
| /// @retrurn iff the class_diff node has changes to non virtual member |
| /// functions. |
| static bool |
| has_non_virtual_mem_fn_change(const diff* diff) |
| {return has_non_virtual_mem_fn_change(dynamic_cast<const class_diff*>(diff));} |
| |
| /// Test if a class_diff carries base classes adding or removals. |
| /// |
| /// @param diff the class_diff to consider. |
| /// |
| /// @return true iff @p diff carries base classes adding or removals. |
| static bool |
| base_classes_added_or_removed(const class_diff* diff) |
| { |
| if (!diff) |
| return false; |
| return diff->deleted_bases().size() || diff->inserted_bases().size(); |
| } |
| |
| /// Test if a class_diff carries base classes adding or removals. |
| /// |
| /// @param diff the class_diff to consider. |
| /// |
| /// @return true iff @p diff carries base classes adding or removals. |
| static bool |
| base_classes_added_or_removed(const diff* diff) |
| {return base_classes_added_or_removed(dynamic_cast<const class_diff*>(diff));} |
| |
| /// Test if two classes that are decl-only (have the decl-only flag |
| /// and carry no data members) but are different just by their size. |
| /// |
| /// In some weird DWARF representation, it happens that a decl-only |
| /// class (with no data member) actually carries a non-zero size. |
| /// That shouldn't happen, but hey, we need to deal with real life. |
| /// So we need to detect that case first. |
| /// |
| /// @param first the first class or union to consider. |
| /// |
| /// @param seconf the second class or union to consider. |
| /// |
| /// @return true if the two classes are decl-only and differ in their |
| /// size. |
| bool |
| is_decl_only_class_with_size_change(const class_or_union& first, |
| const class_or_union& second) |
| { |
| if (first.get_qualified_name() != second.get_qualified_name()) |
| return false; |
| |
| if (!first.get_is_declaration_only() || !second.get_is_declaration_only()) |
| return false; |
| |
| bool f_is_empty = first.get_data_members().empty(); |
| bool s_is_empty = second.get_data_members().empty(); |
| |
| return f_is_empty && s_is_empty; |
| } |
| |
| /// Test if two classes that are decl-only (have the decl-only flag |
| /// and carry no data members) but are different just by their size. |
| /// |
| /// In some weird DWARF representation, it happens that a decl-only |
| /// class (with no data member) actually carries a non-zero size. |
| /// That shouldn't happen, but hey, we need to deal with real life. |
| /// So we need to detect that case first. |
| /// |
| /// @param first the first class or union to consider. |
| /// |
| /// @param seconf the second class or union to consider. |
| /// |
| /// @return true if the two classes are decl-only and differ in their |
| /// size. |
| bool |
| is_decl_only_class_with_size_change(const class_or_union_sptr& first, |
| const class_or_union_sptr& second) |
| { |
| if (!first || !second) |
| return false; |
| |
| class_or_union_sptr f = look_through_decl_only_class(first); |
| class_or_union_sptr s = look_through_decl_only_class(second); |
| |
| return is_decl_only_class_with_size_change(*f, *s); |
| } |
| |
| /// Test if a diff node is for two classes that are decl-only (have |
| /// the decl-only flag and carry no data members) but are different |
| /// just by their size. |
| /// |
| /// In some weird DWARF representation, it happens that a decl-only |
| /// class (with no data member) actually carries a non-zero size. |
| /// That shouldn't happen, but hey, we need to deal with real life. |
| /// So we need to detect that case first. |
| /// |
| /// @param diff the diff node to consider. |
| /// |
| /// @return true if the two classes are decl-only and differ in their |
| /// size. |
| bool |
| is_decl_only_class_with_size_change(const diff *diff) |
| { |
| const class_or_union_diff *d = dynamic_cast<const class_or_union_diff*>(diff); |
| if (!d) |
| return false; |
| |
| class_or_union_sptr f = |
| look_through_decl_only_class(d->first_class_or_union()); |
| class_or_union_sptr s = |
| look_through_decl_only_class(d->second_class_or_union()); |
| |
| return is_decl_only_class_with_size_change(f, s); |
| } |
| |
| /// Test if two @ref decl_base_sptr are different just by the |
| /// fact that one is decl-only and the other one is defined. |
| /// |
| /// @param first the first decl to consider. |
| /// |
| /// @param second the second decl to consider. |
| /// |
| /// @return true iff the two arguments are different just by the fact |
| /// that one is decl-only and the other one is defined. |
| bool |
| has_decl_only_def_change(const decl_base_sptr& first, |
| const decl_base_sptr& second) |
| { |
| if (!first || !second) |
| return false; |
| |
| decl_base_sptr f = |
| look_through_decl_only(first); |
| decl_base_sptr s = |
| look_through_decl_only(second); |
| |
| if (f->get_qualified_name() != s->get_qualified_name()) |
| return false; |
| |
| return f->get_is_declaration_only() != s->get_is_declaration_only(); |
| } |
| |
| /// Test if a diff carries a change in which the two decls are |
| /// different by the fact that one is a decl-only and the other one is |
| /// defined. |
| /// |
| /// @param diff the diff node to consider. |
| /// |
| /// @return true if the diff carries a change in which the two decls |
| /// are different by the fact that one is a decl-only and the other |
| /// one is defined. |
| bool |
| has_decl_only_def_change(const diff *d) |
| { |
| if (!d) |
| return false; |
| |
| decl_base_sptr f = |
| look_through_decl_only(is_decl(d->first_subject())); |
| decl_base_sptr s = |
| look_through_decl_only(is_decl(d->second_subject())); |
| |
| return has_decl_only_def_change(f, s); |
| } |
| |
| |
| /// Test if two @ref class_or_union_sptr are different just by the |
| /// fact that one is decl-only and the other one is defined. |
| /// |
| /// @param first the first class or union to consider. |
| /// |
| /// @param second the second class or union to consider. |
| /// |
| /// @return true iff the two arguments are different just by the fact |
| /// that one is decl-only and the other one is defined. |
| bool |
| has_class_decl_only_def_change(const class_or_union_sptr& first, |
| const class_or_union_sptr& second) |
| { |
| if (!first || !second) |
| return false; |
| |
| class_or_union_sptr f = |
| look_through_decl_only_class(first); |
| class_or_union_sptr s = |
| look_through_decl_only_class(second); |
| |
| if (f->get_qualified_name() != s->get_qualified_name()) |
| return false; |
| |
| return f->get_is_declaration_only() != s->get_is_declaration_only(); |
| } |
| |
| /// Test if two @ref enum_sptr are different just by the |
| /// fact that one is decl-only and the other one is defined. |
| /// |
| /// @param first the first enum to consider. |
| /// |
| /// @param second the second enum to consider. |
| /// |
| /// @return true iff the two arguments are different just by the fact |
| /// that one is decl-only and the other one is defined. |
| bool |
| has_enum_decl_only_def_change(const enum_type_decl_sptr& first, |
| const enum_type_decl_sptr& second) |
| { |
| if (!first || !second) |
| return false; |
| |
| enum_type_decl_sptr f = look_through_decl_only_enum(first); |
| enum_type_decl_sptr s = look_through_decl_only_enum(second); |
| |
| if (f->get_qualified_name() != s->get_qualified_name()) |
| return false; |
| |
| return f->get_is_declaration_only() != s->get_is_declaration_only(); |
| } |
| |
| /// Test if a class_or_union_diff carries a change in which the two |
| /// classes are different by the fact that one is a decl-only and the |
| /// other one is defined. |
| /// |
| /// @param diff the diff node to consider. |
| /// |
| /// @return true if the class_or_union_diff carries a change in which |
| /// the two classes are different by the fact that one is a decl-only |
| /// and the other one is defined. |
| bool |
| has_class_decl_only_def_change(const diff *diff) |
| { |
| const class_or_union_diff *d = dynamic_cast<const class_or_union_diff*>(diff); |
| if (!d) |
| return false; |
| |
| class_or_union_sptr f = |
| look_through_decl_only_class(d->first_class_or_union()); |
| class_or_union_sptr s = |
| look_through_decl_only_class(d->second_class_or_union()); |
| |
| return has_class_decl_only_def_change(f, s); |
| } |
| |
| /// Test if a enum_diff carries a change in which the two enums are |
| /// different by the fact that one is a decl-only and the other one is |
| /// defined. |
| /// |
| /// @param diff the diff node to consider. |
| /// |
| /// @return true if the enum_diff carries a change in which the two |
| /// enums are different by the fact that one is a decl-only and the |
| /// other one is defined. |
| bool |
| has_enum_decl_only_def_change(const diff *diff) |
| { |
| const enum_diff *d = dynamic_cast<const enum_diff*>(diff); |
| if (!d) |
| return false; |
| |
| enum_type_decl_sptr f = look_through_decl_only_enum(d->first_enum()); |
| enum_type_decl_sptr s = look_through_decl_only_enum(d->second_enum()); |
| |
| return has_enum_decl_only_def_change(f, s); |
| } |
| |
| /// Test if a diff node carries a basic type name change. |
| /// |
| /// @param d the diff node to consider. |
| /// |
| /// @return true iff the diff node carries a basic type name change. |
| bool |
| has_basic_type_name_change(const diff *d) |
| { |
| if (const type_decl_diff *dif = is_diff_of_basic_type(d)) |
| if (decl_name_changed(dif)) |
| return true; |
| |
| return false; |
| } |
| |
| /// Test if a diff node carries a class or union type name change. |
| /// |
| /// @param d the diff node to consider. |
| /// |
| /// @return true iff the diff node carries a class or union type name |
| /// change. |
| bool |
| has_class_or_union_type_name_change(const diff *d) |
| { |
| if (const class_or_union_diff *dif = is_diff_of_class_or_union_type(d)) |
| if (decl_name_changed(dif)) |
| return true; |
| |
| return false; |
| } |
| |
| /// Test if a diff node carries a basic or class type name change. |
| /// |
| /// @param d the diff node to consider. |
| /// |
| /// @return true iff the diff node carries a basic or class type name |
| /// change. |
| bool |
| has_basic_or_class_type_name_change(const diff *d) |
| { |
| return (has_basic_type_name_change(d) |
| || has_class_or_union_type_name_change(d)); |
| } |
| |
| /// Test if a diff node carries a distinct type change or a |
| /// pointer/reference/typedef to distinct type change. |
| /// |
| /// Note that a distinct type change is a change where the two |
| /// subjects of the change are not of the same kind, e.g, a basic type |
| /// that got changed into a qualified type. |
| /// |
| /// @param d the diff node to consider. |
| /// |
| /// @return true iff @p d is mostly a distinct diff. |
| bool |
| is_mostly_distinct_diff(const diff *d) |
| { |
| if (is_distinct_diff(d)) |
| return true; |
| |
| // Let's consider that 'd' is a type diff ... |
| diff * td = const_cast<type_diff_base*>(is_type_diff(d)); |
| if (!td) |
| { |
| // ... or a function parameter diff. In which case, let's get |
| // its child type diff ... |
| fn_parm_diff *pd = const_cast<fn_parm_diff*>(is_fn_parm_diff(d)); |
| if (pd) |
| { |
| td = const_cast<type_diff_base*>(is_type_diff(pd->type_diff().get())); |
| if (!td) |
| // if the diff of the fn_parm_diff is a a distinct diff |
| // then handle it. |
| td = const_cast<distinct_diff*> |
| (is_distinct_diff(pd->type_diff().get())); |
| } |
| else |
| return false; |
| } |
| |
| // At this point, if we are not looking at a type diff we must have |
| // bailed out already. |
| ABG_ASSERT(td); |
| |
| type_base_sptr first = is_type(td->first_subject()); |
| type_base_sptr second = is_type(td->second_subject()); |
| |
| first = peel_typedef_pointer_or_reference_type(first); |
| second = peel_typedef_pointer_or_reference_type(second); |
| ABG_ASSERT(first && second); |
| |
| return distinct_diff::entities_are_of_distinct_kinds(first, second); |
| } |
| |
| /// Test if a diff node carries a non-anonymous data member to |
| /// anonymous data member change, or vice-versa. |
| /// |
| /// @param d the diff node to consider. |
| /// |
| /// @return true iff @p d carries a non-anonymous to anonymous data |
| /// member change, or vice-versa. |
| bool |
| has_anonymous_data_member_change(const diff *d) |
| { |
| if (is_anonymous_data_member(d->first_subject()) |
| || is_anonymous_data_member(d->second_subject())) |
| return true; |
| return false; |
| } |
| |
| /// Test if a diff node carries a non-anonymous data member to |
| /// anonymous data member change, or vice-versa. |
| /// |
| /// @param d the diff node to consider. |
| /// |
| /// @return true iff @p d carries a non-anonymous to anonymous data |
| /// member change, or vice-versa. |
| bool |
| has_anonymous_data_member_change(const diff_sptr &d) |
| {return has_anonymous_data_member_change(d.get());} |
| |
| /// Test if an enum_diff carries an enumerator insertion. |
| /// |
| /// @param diff the enum_diff to consider. |
| /// |
| /// @return true iff @p diff carries an enumerator insertion. |
| static bool |
| has_enumerator_insertion(const diff* diff) |
| { |
| if (const enum_diff* d = dynamic_cast<const enum_diff*>(diff)) |
| return !d->inserted_enumerators().empty(); |
| return false; |
| } |
| |
| /// Test if an enum_diff carries an enumerator removal. |
| /// |
| /// @param diff the enum_diff to consider. |
| /// |
| /// @return true iff @p diff carries an enumerator removal or change. |
| static bool |
| has_enumerator_removal_or_change(const diff* diff) |
| { |
| if (const enum_diff* d = dynamic_cast<const enum_diff*>(diff)) |
| return (!d->deleted_enumerators().empty() |
| || !d->changed_enumerators().empty()); |
| return false; |
| } |
| |
| /// Test if an enum_diff carries a harmful change. |
| /// |
| /// @param diff the enum_diff to consider. |
| /// |
| /// @return true iff @p diff carries a harmful change. |
| static bool |
| has_harmful_enum_change(const diff* diff) |
| { |
| if (const enum_diff* d = dynamic_cast<const enum_diff*>(diff)) |
| return (has_enumerator_removal_or_change(d) |
| || has_type_size_change(d)); |
| return false; |
| } |
| |
| /// Test if a diff node carries a harmless change of an enum into an |
| /// integer (or vice-versa). |
| /// |
| /// The test takes into account the fact change we care about might be |
| /// wrapped into a typedef or qualified type diff. |
| /// |
| /// @param diff the diff node to consider. |
| /// |
| /// @return true if @p diff is a harmless enum to integer change. |
| static bool |
| has_harmless_enum_to_int_change(const diff* diff) |
| { |
| if (!diff) |
| return false; |
| |
| diff = peel_typedef_or_qualified_type_diff(diff); |
| |
| if (const distinct_diff *d = is_distinct_diff(diff)) |
| { |
| const enum_type_decl *enum_type = 0; |
| const type_base *integer_type = 0; |
| |
| type_base *first_type = |
| peel_qualified_or_typedef_type(is_type(d->first().get())); |
| type_base *second_type = |
| peel_qualified_or_typedef_type(is_type(d->second().get())); |
| |
| if (const enum_type_decl *e = is_enum_type(first_type)) |
| enum_type = e; |
| else if (const enum_type_decl *e = is_enum_type(second_type)) |
| enum_type = e; |
| |
| if (const type_base * i = is_type_decl(first_type)) |
| integer_type = i; |
| else if (const type_base *i = is_type_decl(second_type)) |
| integer_type = i; |
| |
| if (enum_type |
| && integer_type |
| && enum_type->get_size_in_bits() == integer_type->get_size_in_bits()) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /// Test if an @ref fn_parm_diff node has a top cv qualifier change on |
| /// the type of the function parameter. |
| /// |
| /// @param diff the diff node to consider. It should be a @ref |
| /// fn_parm_diff, otherwise the function returns 'false' directly. |
| /// |
| /// @return true iff @p diff is a @ref fn_parm_diff node that has a |
| /// top cv qualifier change on the type of the function parameter. |
| static bool |
| has_fn_parm_type_top_cv_qual_change(const diff* diff) |
| { |
| // is diff a "function parameter diff node? |
| const fn_parm_diff* parm_diff = is_fn_parm_diff(diff); |
| |
| if (!parm_diff || !parm_diff->has_changes()) |
| // diff either carries no change or is not a function parameter |
| // diff node. So bail out. |
| return false; |
| |
| function_decl::parameter_sptr first_parm = parm_diff->first_parameter(); |
| function_decl::parameter_sptr second_parm = parm_diff->second_parameter(); |
| |
| type_base_sptr first_parm_type = first_parm->get_type(); |
| type_base_sptr second_parm_type = second_parm->get_type(); |
| |
| if (!is_qualified_type(first_parm_type) |
| && !is_qualified_type(second_parm_type)) |
| // None of the parameter types is qualified. |
| return false; |
| |
| qualified_type_def::CV cv_quals_1 = qualified_type_def::CV_NONE; |
| qualified_type_def::CV cv_quals_2 = qualified_type_def::CV_NONE; |
| type_base_sptr peeled_type_1 = first_parm_type; |
| type_base_sptr peeled_type_2 = second_parm_type; |
| |
| if (qualified_type_def_sptr qtype1 = is_qualified_type(first_parm_type)) |
| { |
| cv_quals_1 = qtype1->get_cv_quals(); |
| peeled_type_1 = peel_qualified_type(qtype1); |
| } |
| |
| if (qualified_type_def_sptr qtype2 = is_qualified_type(second_parm_type)) |
| { |
| cv_quals_2 = qtype2->get_cv_quals(); |
| peeled_type_2 = peel_qualified_type(qtype2); |
| } |
| |
| if (peeled_type_1 |
| && peeled_type_2 |
| && get_type_name(peeled_type_1) == get_type_name(peeled_type_2) |
| && cv_quals_1 != cv_quals_2) |
| // The top-level CV qualifiers of the function type are different |
| // and the un-qualified variant (peeled) of said function types |
| // are equal. This means the only change the function types have |
| // are about top-level CV qualifiers. |
| return true; |
| |
| return false; |
| } |
| |
| /// Test if a type diff only carries a CV qualifier-only change. |
| /// |
| /// @param type_dif the type dif to consider. |
| /// |
| /// @return true iff the type_diff carries a CV qualifier only change. |
| static bool |
| type_diff_has_cv_qual_change_only(const diff *type_dif) |
| { |
| if (!is_type_diff(type_dif)) |
| return false; |
| |
| if (is_pointer_diff(type_dif)) |
| type_dif = peel_pointer_diff(type_dif); |
| else if (is_reference_diff(type_dif)) |
| type_dif = peel_reference_diff(type_dif); |
| |
| const type_base *f = 0; |
| const type_base *s = 0; |
| if (const distinct_diff *d = is_distinct_diff(type_dif)) |
| { |
| if (is_qualified_type(d->first()) == is_qualified_type(d->second())) |
| return false; |
| else |
| { |
| f = is_type(d->first()).get(); |
| s = is_type(d->second()).get(); |
| } |
| } |
| else if (const qualified_type_diff *d = is_qualified_type_diff(type_dif)) |
| { |
| f = is_type(d->first_qualified_type()).get(); |
| s = is_type(d->second_qualified_type()).get(); |
| } |
| else |
| return false; |
| |
| f = peel_qualified_type(f); |
| s = peel_qualified_type(s); |
| |
| // If f and s are arrays, note that they can differ only by the cv |
| // qualifier of the array element type. That cv qualifier is not |
| // removed by peel_qualified_type. So we need to test this case |
| // specifically. |
| if (array_type_def *f_a = is_array_type(f)) |
| if (array_type_def *s_a = is_array_type(s)) |
| return equals_modulo_cv_qualifier(f_a, s_a); |
| |
| return *f == *s; |
| } |
| |
| /// Test if an @ref fn_parm_diff node has a cv qualifier change on the |
| /// type of the function parameter. That is, we are looking for |
| /// changes like 'const char*' to 'char*'. |
| /// |
| /// @param diff the diff node to consider. It should be a @ref |
| /// fn_parm_diff, otherwise the function returns 'false' directly. |
| /// |
| /// @return true iff @p diff is a @ref fn_parm_diff node that has a |
| /// cv qualifier change on the type of the function parameter. |
| static bool |
| has_fn_parm_type_cv_qual_change(const diff* dif) |
| { |
| // is diff a "function parameter diff node? |
| const fn_parm_diff* parm_diff = is_fn_parm_diff(dif); |
| |
| if (!parm_diff || !parm_diff->has_changes()) |
| // diff either carries no change or is not a function parameter |
| // diff node. So bail out. |
| return false; |
| |
| const diff *type_dif = parm_diff->type_diff().get(); |
| return type_diff_has_cv_qual_change_only(type_dif); |
| } |
| |
| /// Test if a function type or decl diff node carries a CV |
| /// qualifier-only change on its return type. |
| /// |
| /// @param dif the diff node to consider. Note that if this is |
| /// neither a function type nor decl diff node, the function returns |
| /// false. |
| /// |
| /// @return true iff @p dif is a function decl or type diff node which |
| /// carries a CV qualifier-only change on its return type. |
| static bool |
| has_fn_return_type_cv_qual_change(const diff* dif) |
| { |
| const function_type_diff* fn_type_diff = is_function_type_diff(dif); |
| if (!fn_type_diff) |
| if (const function_decl_diff* fn_decl_diff = is_function_decl_diff(dif)) |
| fn_type_diff = fn_decl_diff->type_diff().get(); |
| |
| if (!fn_type_diff) |
| return false; |
| |
| const diff* return_type_diff = fn_type_diff->return_type_diff().get(); |
| return type_diff_has_cv_qual_change_only(return_type_diff); |
| } |
| |
| /// Test if a function type or decl diff node carries a function |
| /// parameter addition or removal. |
| /// |
| /// @param dif the diff node to consider. Note that if this is |
| /// neither a function type nor decl diff node, the function returns |
| /// false. |
| /// |
| /// @return true iff @p dif is a function decl or type diff node which |
| /// carries a function parameter addition or removal. |
| static bool |
| has_added_or_removed_function_parameters(const diff *dif) |
| { |
| const function_type_diff *fn_type_diff = is_function_type_diff(dif); |
| if (!fn_type_diff) |
| if (const function_decl_diff* fn_decl_diff = is_function_decl_diff(dif)) |
| fn_type_diff = fn_decl_diff->type_diff().get(); |
| |
| if (!fn_type_diff) |
| return false; |
| |
| if (!(fn_type_diff->sorted_deleted_parms().empty() |
| && fn_type_diff->sorted_added_parms().empty())) |
| return true; |
| |
| return false; |
| } |
| |
| /// Test if a variable diff node carries a CV qualifier change on its type. |
| /// |
| /// @param dif the diff node to consider. Note that if it's not of |
| /// var_diff type, the function returns false. |
| /// |
| /// @return true iff the @p dif carries a CV qualifier change on its |
| /// type. |
| static bool |
| has_var_type_cv_qual_change(const diff* dif) |
| { |
| const var_diff *var_dif = is_var_diff(dif); |
| if (!var_dif) |
| return false; |
| |
| { |
| // Make sure the variable diff does carry a type change at least |
| change_kind ch_kind = NO_CHANGE_KIND; |
| if (equals(*var_dif->first_var(), *var_dif->second_var(), &ch_kind)) |
| return false; |
| |
| if (!(ch_kind & LOCAL_TYPE_CHANGE_KIND || ch_kind & SUBTYPE_CHANGE_KIND)) |
| return false; |
| } |
| |
| diff *type_dif = var_dif->type_diff().get(); |
| ABG_ASSERT(type_dif); |
| |
| return type_diff_has_cv_qual_change_only(type_dif); |
| } |
| |
| /// Test if a diff node carries a void* to pointer type change. |
| /// |
| /// Note that this function looks through typedef and qualifier types |
| /// to find the void pointer. |
| /// |
| /// @param dif the diff node to consider. |
| /// |
| /// @return true iff @p dif carries a void* to pointer type change. |
| static bool |
| has_void_ptr_to_ptr_change(const diff* dif) |
| { |
| dif = peel_typedef_diff(dif); |
| |
| if (const distinct_diff *d = is_distinct_diff(dif)) |
| { |
| const type_base *f = is_type(d->first().get()); |
| const type_base *s = is_type(d->second().get()); |
| |
| f = peel_qualified_or_typedef_type(f); |
| s = peel_qualified_or_typedef_type(s); |
| |
| if (is_void_pointer_type(f) |
| && is_pointer_type(s) |
| && !is_void_pointer_type(s) |
| && f->get_size_in_bits() == s->get_size_in_bits()) |
| return true; |
| } |
| else if (const pointer_diff *d = is_pointer_diff(dif)) |
| { |
| const type_base *f = is_type(d->first_pointer()).get(); |
| const type_base *s = is_type(d->second_pointer()).get(); |
| |
| f = peel_qualified_or_typedef_type(f); |
| s = peel_qualified_or_typedef_type(s); |
| |
| if (is_void_pointer_type(f) |
| && is_pointer_type(s) |
| && !is_void_pointer_type(s) |
| && f->get_size_in_bits() == s->get_size_in_bits()) |
| return true; |
| } |
| else if (const qualified_type_diff *d = is_qualified_type_diff(dif)) |
| { |
| const type_base *f = is_type(d->first_qualified_type()).get(); |
| const type_base *s = is_type(d->second_qualified_type()).get(); |
| |
| f = peel_qualified_or_typedef_type(f); |
| s = peel_qualified_or_typedef_type(s); |
| |
| if (is_void_pointer_type(f) |
| && is_pointer_type(s) |
| && !is_void_pointer_type(s) |
| && f->get_size_in_bits() == s->get_size_in_bits()) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /// Test if a diff node carries a benign change to the size of a |
| /// variable of type array. |
| /// |
| /// A benign size change is a change in size (from or to infinite) of |
| /// the array as expressed by the debug info, but when the *ELF* size |
| /// (what really matters) of the variable object hasn't changed. This |
| /// happens when the debug info emitter did have trouble figuring out |
| /// the actual size of the array. |
| /// |
| /// @param dif the diff node to consider. |
| /// |
| /// @return true iff @p dif contains the benign array type size change. |
| static bool |
| has_benign_infinite_array_change(const diff* dif) |
| { |
| if (const var_diff* var_dif = is_var_diff(dif)) |
| { |
| if (!var_dif->first_var()->get_symbol() |
| || var_dif->second_var()->get_symbol()) |
| return false; |
| |
| if (var_dif->first_var()->get_symbol()->get_size() |
| != var_dif->second_var()->get_symbol()->get_size()) |
| return false; |
| |
| const diff *d = var_dif->type_diff().get(); |
| if (!d) |
| return false; |
| d = peel_qualified_diff(d); |
| if (const array_diff *a = is_array_diff(d)) |
| { |
| array_type_def_sptr f = a->first_array(), s = a->second_array(); |
| if (f->is_infinite() != s->is_infinite()) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /// Test if a union diff node does have changes that don't impact its |
| /// size. |
| /// |
| /// @param d the union diff node to consider. |
| /// |
| /// @return true iff @p d is a diff node which has changes that don't |
| /// impact its size. |
| bool |
| union_diff_has_harmless_changes(const diff *d) |
| { |
| if (is_union_diff(d) |
| && d->has_changes() |
| && !has_type_size_change(d)) |
| return true; |
| |
| return false; |
| } |
| |
| /// Detect if the changes carried by a given diff node are deemed |
| /// harmless and do categorize the diff node accordingly. |
| /// |
| /// @param d the diff node being visited. |
| /// |
| /// @param pre this is true iff the node is being visited *before* the |
| /// children nodes of @p d. |
| /// |
| /// @return true iff the traversal shall keep going after the |
| /// completion of this function. |
| static bool |
| categorize_harmless_diff_node(diff *d, bool pre) |
| { |
| if (!d->has_changes()) |
| return true; |
| |
| if (pre) |
| { |
| diff_category category = NO_CHANGE_CATEGORY; |
| |
| decl_base_sptr f = is_decl(d->first_subject()), |
| s = is_decl(d->second_subject()); |
| |
| if (has_class_decl_only_def_change(d) |
| || has_enum_decl_only_def_change(d)) |
| category |= TYPE_DECL_ONLY_DEF_CHANGE_CATEGORY; |
| |
| if (access_changed(f, s)) |
| category |= ACCESS_CHANGE_CATEGORY; |
| |
| if (is_compatible_change(f, s)) |
| category |= COMPATIBLE_TYPE_CHANGE_CATEGORY; |
| |
| if (has_harmless_name_change(f, s) |
| || class_diff_has_harmless_odr_violation_change(d)) |
| category |= HARMLESS_DECL_NAME_CHANGE_CATEGORY; |
| |
| if (union_diff_has_harmless_changes(d)) |
| category |= HARMLESS_UNION_CHANGE_CATEGORY; |
| |
| if (has_non_virtual_mem_fn_change(d)) |
| category |= NON_VIRT_MEM_FUN_CHANGE_CATEGORY; |
| |
| if (static_data_member_added_or_removed(d) |
| || static_data_member_type_size_changed(f, s)) |
| category |= STATIC_DATA_MEMBER_CHANGE_CATEGORY; |
| |
| if (has_data_member_replaced_by_anon_dm(d)) |
| category |= HARMLESS_DATA_MEMBER_CHANGE_CATEGORY; |
| |
| if ((has_enumerator_insertion(d) |
| && !has_harmful_enum_change(d)) |
| || has_harmless_enum_to_int_change(d)) |
| category |= HARMLESS_ENUM_CHANGE_CATEGORY; |
| |
| if (function_name_changed_but_not_symbol(d)) |
| category |= HARMLESS_SYMBOL_ALIAS_CHANGE_CATEGORY; |
| |
| if (has_fn_parm_type_top_cv_qual_change(d)) |
| category |= FN_PARM_TYPE_TOP_CV_CHANGE_CATEGORY; |
| |
| if (has_fn_parm_type_cv_qual_change(d)) |
| category |= FN_PARM_TYPE_CV_CHANGE_CATEGORY; |
| |
| if (has_fn_return_type_cv_qual_change(d)) |
| category |= FN_RETURN_TYPE_CV_CHANGE_CATEGORY; |
| |
| if (has_var_type_cv_qual_change(d)) |
| category |= VAR_TYPE_CV_CHANGE_CATEGORY; |
| |
| if (has_void_ptr_to_ptr_change(d)) |
| category |= VOID_PTR_TO_PTR_CHANGE_CATEGORY; |
| |
| if (has_benign_infinite_array_change(d)) |
| category |= BENIGN_INFINITE_ARRAY_CHANGE_CATEGORY; |
| |
| if (category) |
| { |
| d->add_to_local_and_inherited_categories(category); |
| // Also update the category of the canonical node. |
| if (diff * canonical = d->get_canonical_diff()) |
| canonical->add_to_local_and_inherited_categories(category); |
| } |
| } |
| |
| return true; |
| } |
| |
| /// Detect if the changes carried by a given diff node are deemed |
| /// harmful and do categorize the diff node accordingly. |
| /// |
| /// @param d the diff node being visited. |
| /// |
| /// @param pre this is true iff the node is being visited *before* the |
| /// children nodes of @p d. |
| /// |
| /// @return true iff the traversal shall keep going after the |
| /// completion of this function. |
| static bool |
| categorize_harmful_diff_node(diff *d, bool pre) |
| { |
| if (!d->has_changes()) |
| return true; |
| |
| if (pre) |
| { |
| diff_category category = NO_CHANGE_CATEGORY; |
| decl_base_sptr f = is_decl(d->first_subject()), |
| s = is_decl(d->second_subject()); |
| |
| // Detect size or offset changes as well as data member addition |
| // or removal. |
| // |
| // TODO: be more specific -- not all size changes are harmful. |
| if (!has_class_decl_only_def_change(d) |
| && !has_enum_decl_only_def_change(d) |
| && (type_size_changed(f, s) |
| || data_member_offset_changed(f, s) |
| || non_static_data_member_type_size_changed(f, s) |
| || non_static_data_member_added_or_removed(d) |
| || base_classes_added_or_removed(d) |
| || has_harmful_enum_change(d) |
| || crc_changed(d))) |
| category |= SIZE_OR_OFFSET_CHANGE_CATEGORY; |
| |
| if (has_virtual_mem_fn_change(d)) |
| category |= VIRTUAL_MEMBER_CHANGE_CATEGORY; |
| |
| if (has_added_or_removed_function_parameters(d)) |
| category |= FN_PARM_ADD_REMOVE_CHANGE_CATEGORY; |
| |
| if (category) |
| { |
| d->add_to_local_and_inherited_categories(category); |
| // Update the category of the canonical diff node too. |
| if (diff * canonical = d->get_canonical_diff()) |
| canonical->add_to_local_and_inherited_categories(category); |
| } |
| } |
| |
| return true; |
| } |
| |
| /// The visiting code of the harmless_harmful_filter. |
| /// |
| /// @param d the diff node being visited. |
| /// |
| /// @param pre this is true iff the node is being visited *before* the |
| /// children nodes of @p d. |
| /// |
| /// @return true iff the traversal shall keep going after the |
| /// completion of this function. |
| bool |
| harmless_harmful_filter::visit(diff* d, bool pre) |
| { |
| return (categorize_harmless_diff_node(d, pre) |
| && categorize_harmful_diff_node(d, pre)); |
| } |
| |
| /// Part of the visiting code of the harmless_harmful_filter. |
| /// |
| /// This function is called after the visiting of a given diff node. |
| /// Note that when this function is called, the visiting might not |
| /// have taken place *if* the node (or an equivalent node) has already |
| /// been visited. |
| /// |
| /// @param d the diff node that has either been visited or skipped |
| /// (because it has already been visited during this traversing). |
| void |
| harmless_harmful_filter::visit_end(diff* d) |
| { |
| if (d->context()->diff_has_been_visited(d)) |
| { |
| // This node or one of its equivalent node has already been |
| // visited. That means at this moment, |
| // harmless_harmful_filter::visit() has *not* been called prior |
| // to this harmless_harmful_filter::visit_end() is called. In |
| // other words, only harmless_harmful_filter::visit_begin() and |
| // harmless_harmful_filter::visit_end() are called. |
| // |
| // So let's update the category of this diff node from its |
| // canonical node. |
| if (diff* c = d->get_canonical_diff()) |
| d->add_to_local_and_inherited_categories(c->get_local_category()); |
| } |
| } |
| } // end namespace filtering |
| } // end namespace comparison |
| } // end namespace abigail |