// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // -*- Mode: C++ -*- // // Copyright (C) 2013-2022 Red Hat, Inc. // // Author: Dodji Seketeli /// @file /// /// This contains the implementation of the comparison engine of /// libabigail. #include #include #include #include #include "abg-comparison-priv.h" #include "abg-reporter-priv.h" namespace abigail { namespace comparison { /// /// ///@defgroup DiffNode Internal Representation of the comparison engine /// @{ /// /// @brief How changes are represented in libabigail's comparison engine. /// ///@par diff nodes /// /// The internal representation of the comparison engine is basically /// a graph of @ref instances of @ref diff node. We refer to these /// just as diff nodes. A diff node represents a change /// between two ABI artifacts represented by instances of types of the /// abigail::ir namespace. These two artifacts that are being /// compared are called the subjects of the diff. /// /// The types of that IR are in the abigail::comparison namespace. /// ///@par comparing diff nodes /// /// Comparing two instances of @ref diff nodes amounts to comparing /// the subject of the diff. In other words, two @ref diff nodes are /// equal if and only if their subjects are equal. Thus, two @ref /// diff nodes can have different memory addresses and yet be equal. /// ///@par diff reporting and context /// /// A diff node can be serialized to an output stream to express, in /// a human-readable textual form, the different changes that exist /// between its two subjects. This is done by invoking the /// diff::report() method. That reporting is controlled by several /// parameters that are conceptually part of the context of the diff. /// That context is materialized by an instance of the @ref /// diff_context type. /// /// Please note that the role of the instance(s) of @ref diff_context /// is boreader than just controlling the reporting of @ref diff /// nodes. Basically, a @ref diff node itself is created following /// behaviours that are controlled by a particular instance of /// diff_context. A diff node is created in a particular diff /// context, so to speak. /// /// @} /// /// ///@defgroup CanonicalDiff Canonical diff tree nodes /// @{ /// /// @brief How equivalent diff nodes are quickly spotted. /// /// @par Equivalence of diff nodes. /// /// Each @ref diff node has a property named Canonical Diff /// Node. If \c D is a diff node, the canonical diff node of @c /// D, noted @c C(D) is a particular diff node that is equal to @c D. /// Thus, a fast way to compare two @ref diff node is to perform a /// pointer comparison of their canonical diff nodes. /// /// A set of equivalent @ref diff nodes is a set of diff nodes that /// all have the same canonical node. All the nodes of that set are /// equal. /// /// A canonical node is registereded for a given diff node by invoking /// the method diff_context::initialize_canonical_diff(). /// /// Please note that the diff_context holds all the canonical diffs /// that got registered through it. Thus, the life time of all of /// canonical diff objects is the same as the life time of the @ref /// diff_context they relate to. /// /// @} /// // ----------------------------------------- // // ----------------------------------------- /// Sort a map of enumerators by their value. /// /// @param enumerators_map the map to sort. /// /// @param sorted the resulting vector of sorted enumerators. void sort_enumerators(const string_enumerator_map& enumerators_map, enum_type_decl::enumerators& sorted) { for (string_enumerator_map::const_iterator i = enumerators_map.begin(); i != enumerators_map.end(); ++i) sorted.push_back(i->second); enumerator_value_comp comp; std::sort(sorted.begin(), sorted.end(), comp); } /// Sort a map of changed enumerators. /// /// @param enumerators_map the map to sort. /// ///@param output parameter. The resulting sorted enumerators. void sort_changed_enumerators(const string_changed_enumerator_map& enumerators_map, changed_enumerators_type& sorted) { for (string_changed_enumerator_map::const_iterator i = enumerators_map.begin(); i != enumerators_map.end(); ++i) sorted.push_back(i->second); changed_enumerator_comp comp; std::sort(sorted.begin(), sorted.end(), comp); } /// Sort a map of data members by the offset of their initial value. /// /// @param data_members the map of changed data members to sort. /// /// @param sorted the resulting vector of sorted changed data members. void sort_data_members(const string_decl_base_sptr_map &data_members, vector& sorted) { sorted.reserve(data_members.size()); for (string_decl_base_sptr_map::const_iterator i = data_members.begin(); i != data_members.end(); ++i) sorted.push_back(i->second); data_member_comp comp; std::sort(sorted.begin(), sorted.end(), comp); } /// Sort (in place) a vector of changed data members. /// /// @param to_sort the vector to sort. void sort_changed_data_members(changed_var_sptrs_type& to_sort) { data_member_comp comp; std::sort(to_sort.begin(), to_sort.end(), comp); } /// Sort an instance of @ref string_function_ptr_map map and stuff a /// resulting sorted vector of pointers to function_decl. /// /// @param map the map to sort. /// /// @param sorted the resulting sorted vector. void sort_string_function_ptr_map(const string_function_ptr_map& map, vector& sorted) { sorted.reserve(map.size()); for (string_function_ptr_map::const_iterator i = map.begin(); i != map.end(); ++i) sorted.push_back(i->second); function_comp comp; std::sort(sorted.begin(), sorted.end(), comp); } /// Sort a map that's an instance of @ref /// string_member_function_sptr_map and fill a vector of member /// functions with the sorted result. /// /// @param map the map to sort. /// /// @param sorted the resulting sorted vector. void sort_string_member_function_sptr_map(const string_member_function_sptr_map& map, class_or_union::member_functions& sorted) { sorted.reserve(map.size()); for (string_member_function_sptr_map::const_iterator i = map.begin(); i != map.end(); ++i) sorted.push_back(i->second); function_comp comp; std::sort(sorted.begin(), sorted.end(), comp); } /// Sort the values of a @ref string_function_decl_diff_sptr_map map /// and store the result in a vector of @ref function_decl_diff_sptr /// objects. /// /// @param map the map whose values to store. /// /// @param sorted the vector of function_decl_diff_sptr to store the /// result of the sort into. void sort_string_function_decl_diff_sptr_map (const string_function_decl_diff_sptr_map& map, function_decl_diff_sptrs_type& sorted) { sorted.reserve(map.size()); for (string_function_decl_diff_sptr_map::const_iterator i = map.begin(); i != map.end(); ++i) sorted.push_back(i->second); function_decl_diff_comp comp; std::sort(sorted.begin(), sorted.end(), comp); } /// Sort of an instance of @ref string_var_diff_sptr_map map. /// /// @param map the input map to sort. /// /// @param sorted the ouptut sorted vector of @ref var_diff_sptr. /// It's populated with the sorted content. void sort_string_var_diff_sptr_map(const string_var_diff_sptr_map& map, var_diff_sptrs_type& sorted) { sorted.reserve(map.size()); for (string_var_diff_sptr_map::const_iterator i = map.begin(); i != map.end(); ++i) sorted.push_back(i->second); var_diff_sptr_comp comp; std::sort(sorted.begin(), sorted.end(), comp); } /// Sort a map of string -> pointer to @ref elf_symbol. /// /// The result is a vector of @ref elf_symbol_sptr sorted by the /// name of the symbol. /// /// @param map the map to sort. /// /// @param sorted out parameter; the sorted vector of @ref /// elf_symbol_sptr. void sort_string_elf_symbol_map(const string_elf_symbol_map& map, vector& sorted) { for (string_elf_symbol_map::const_iterator i = map.begin(); i!= map.end(); ++i) sorted.push_back(i->second); elf_symbol_comp comp; std::sort(sorted.begin(), sorted.end(), comp); } /// Sort a map of string -> pointer to @ref var_decl. /// /// The result is a vector of var_decl* sorted by the qualified name /// of the variables. /// /// @param map the map to sort. /// /// @param sorted out parameter; the sorted vector of @ref var_decl. void sort_string_var_ptr_map(const string_var_ptr_map& map, vector& sorted) { for (string_var_ptr_map::const_iterator i = map.begin(); i != map.end(); ++i) sorted.push_back(i->second); var_comp comp; std::sort(sorted.begin(), sorted.end(), comp); } /// Sort the values of a string_var_diff_sptr_map and store the result /// in a vector of var_diff_sptr. /// /// @param map the map of changed data members to sort. /// /// @param sorted the resulting vector of var_diff_sptr. void sort_string_data_member_diff_sptr_map(const string_var_diff_sptr_map& map, var_diff_sptrs_type& sorted) { sorted.reserve(map.size()); for (string_var_diff_sptr_map::const_iterator i = map.begin(); i != map.end(); ++i) sorted.push_back(i->second); data_member_diff_comp comp; std::sort(sorted.begin(), sorted.end(), comp); } /// Sort the values of a unsigned_var_diff_sptr_map map and store the /// result into a vector of var_diff_sptr. /// /// @param map the map of changed data members to sort. /// /// @param sorted the resulting vector of sorted var_diff_sptr. void sort_unsigned_data_member_diff_sptr_map(const unsigned_var_diff_sptr_map map, var_diff_sptrs_type& sorted) { sorted.reserve(map.size()); for (unsigned_var_diff_sptr_map::const_iterator i = map.begin(); i != map.end(); ++i) sorted.push_back(i->second); data_member_diff_comp comp; std::sort(sorted.begin(), sorted.end(), comp); } /// Sort an map of string -> virtual member function into a vector of /// virtual member functions. The virtual member functions are sorted /// by increasing order of their virtual index. /// /// @param map the input map. /// /// @param sorted the resulting sorted vector of virtual function /// member. void sort_string_virtual_member_function_diff_sptr_map (const string_function_decl_diff_sptr_map& map, function_decl_diff_sptrs_type& sorted) { sorted.reserve(map.size()); for (string_function_decl_diff_sptr_map::const_iterator i = map.begin(); i != map.end(); ++i) sorted.push_back(i->second); virtual_member_function_diff_comp comp; sort(sorted.begin(), sorted.end(), comp); } /// Sort a map ofg string -> @ref diff_sptr into a vector of @ref /// diff_sptr. The diff_sptr are sorted lexicographically wrt /// qualified names of their first subjects. /// /// @param map the map to sort. /// /// @param sorted the resulting sorted vector. void sort_string_diff_sptr_map(const string_diff_sptr_map& map, diff_sptrs_type& sorted) { sorted.reserve(map.size()); for (string_diff_sptr_map::const_iterator i = map.begin(); i != map.end(); ++i) sorted.push_back(i->second); diff_comp comp; sort(sorted.begin(), sorted.end(), comp); } /// Sort a map ofg string -> @ref diff* into a vector of @ref /// diff_ptr. The diff_ptr are sorted lexicographically wrt /// qualified names of their first subjects. /// /// @param map the map to sort. /// /// @param sorted the resulting sorted vector. void sort_string_diff_ptr_map(const string_diff_ptr_map& map, diff_ptrs_type& sorted) { sorted.reserve(map.size()); for (string_diff_ptr_map::const_iterator i = map.begin(); i != map.end(); ++i) sorted.push_back(i->second); diff_comp comp; sort(sorted.begin(), sorted.end(), comp); } /// Sort a map of string -> base_diff_sptr into a sorted vector of /// base_diff_sptr. The base_diff_sptr are sorted by increasing value /// of their offset in their containing type. /// /// @param map the input map to sort. /// /// @param sorted the resulting sorted vector. void sort_string_base_diff_sptr_map(const string_base_diff_sptr_map& map, base_diff_sptrs_type& sorted) { for (string_base_diff_sptr_map::const_iterator i = map.begin(); i != map.end(); ++i) sorted.push_back(i->second); base_diff_comp comp; sort(sorted.begin(), sorted.end(), comp); } /// Lexicographically sort base specifications found /// in instances of string_base_sptr_map. void sort_string_base_sptr_map(const string_base_sptr_map& m, class_decl::base_specs& sorted) { for (string_base_sptr_map::const_iterator i = m.begin(); i != m.end(); ++i) sorted.push_back(i->second); base_spec_comp comp; std::sort(sorted.begin(), sorted.end(), comp); } /// Sort a map of @ref fn_parm_diff by the indexes of the function /// parameters. /// /// @param map the map to sort. /// /// @param sorted the resulting sorted vector of changed function /// parms. void sort_string_fn_parm_diff_sptr_map(const unsigned_fn_parm_diff_sptr_map& map, vector& sorted) { sorted.reserve(map.size()); for (unsigned_fn_parm_diff_sptr_map::const_iterator i = map.begin(); i != map.end(); ++i) sorted.push_back(i->second); fn_parm_diff_comp comp; std::sort(sorted.begin(), sorted.end(), comp); } /// Sort a map of changed function parameters by the indexes of the /// function parameters. /// /// @param map the map to sort. /// /// @param sorted the resulting sorted vector of instances of @ref /// fn_parm_diff_sptr void sort_string_fn_parm_diff_sptr_map(const string_fn_parm_diff_sptr_map& map, vector& sorted) { sorted.reserve(map.size()); for (string_fn_parm_diff_sptr_map::const_iterator i = map.begin(); i != map.end(); ++i) sorted.push_back(i->second); fn_parm_diff_comp comp; std::sort(sorted.begin(), sorted.end(), comp); } /// Sort a map of string -> function parameters. /// /// @param map the map to sort. /// /// @param sorted the resulting sorted vector of /// @ref vector void sort_string_parm_map(const string_parm_map& map, vector& sorted) { for (string_parm_map::const_iterator i = map.begin(); i != map.end(); ++i) sorted.push_back(i->second); parm_comp comp; std::sort(sorted.begin(), sorted.end(), comp); } /// Sort the set of ABI artifacts contained in a @ref /// artifact_sptr_set_type. /// /// @param set the set of ABI artifacts to sort. /// /// @param output parameter the vector containing the sorted ABI /// artifacts. void sort_artifacts_set(const artifact_sptr_set_type& set, vector& sorted) { for (artifact_sptr_set_type::const_iterator it = set.begin(); it != set.end(); ++it) sorted.push_back(*it); type_or_decl_base_comp comp; std::sort(sorted.begin(), sorted.end(), comp); } /// Sort a map of string to type_base_sptr entities. /// /// The entries are sorted based on the lexicographic order of the /// pretty representation of the type_sptr_sptr. The sorted result is /// put in a vector of type_base_sptr. /// /// @param map the map to sort. /// /// @param sorted the resulting vector of type_base_sptr /// lexicographically sorted using their pretty representation. void sort_string_type_base_sptr_map(string_type_base_sptr_map& map, vector& sorted) { for (string_type_base_sptr_map::const_iterator i = map.begin(); i != map.end(); ++i) sorted.push_back(i->second); type_or_decl_base_comp comp; std::sort(sorted.begin(), sorted.end(), comp); } /// Return the first underlying type that is not a qualified type. /// @param t the qualified type to consider. /// /// @return the first underlying type that is not a qualified type, or /// NULL if t is NULL. type_base_sptr get_leaf_type(qualified_type_def_sptr t) { if (!t) return type_base_sptr(); type_base_sptr ut = t->get_underlying_type(); qualified_type_def_sptr qut = dynamic_pointer_cast(ut); if (!qut) return ut; return get_leaf_type(qut); } /// Tests if a given diff node is to represent the changes between two /// gobal decls. /// /// @param d the diff node to consider. /// /// @return true iff @p d represents the changes between two global /// decls. bool is_diff_of_global_decls(const diff* d) { ABG_ASSERT(d != 0); if (d == 0) return false; type_or_decl_base_sptr first = d->first_subject(); ABG_ASSERT(first); type_or_decl_base_sptr second = d->first_subject(); ABG_ASSERT(second); if (decl_base_sptr decl = is_decl(first)) if (is_at_global_scope(decl)) if ((decl = is_decl(second))) if (is_at_global_scope(decl)) return true; return false; } // ----------------------------------------- // // ----------------------------------------- /// The overloaded or operator for @ref visiting_kind. visiting_kind operator|(visiting_kind l, visiting_kind r) {return static_cast(static_cast(l) | static_cast(r));} /// The overloaded and operator for @ref visiting_kind. visiting_kind operator&(visiting_kind l, visiting_kind r) { return static_cast(static_cast(l) & static_cast(r)); } /// The overloaded 'bit inversion' operator for @ref visiting_kind. visiting_kind operator~(visiting_kind l) {return static_cast(~static_cast(l));} /// Test if a diff node is about differences between types. /// /// @param diff the diff node to test. /// /// @return a pointer to the actual type_diff_base* that @p diff /// extends, iff it is about differences between types. const type_diff_base* is_type_diff(const diff* diff) {return dynamic_cast(diff);} /// Test if a diff node is about differences between declarations. /// /// @param diff the diff node to test. /// /// @return a pointer to the actual decl_diff_base @p diff extends, /// iff it is about differences between declarations. const decl_diff_base* is_decl_diff(const diff* diff) {return dynamic_cast(diff);} /// Test if a diff node is a @ref class_diff node. /// /// @param diff the diff node to consider. /// /// @return a non-nil pointer to a @ref class_diff iff @p diff is a /// @ref class_diff node. const class_diff* is_class_diff(const diff* diff) {return dynamic_cast(diff);} /// Test if a diff node is a @ref enum_diff node. /// /// @param diff the diff node to consider. /// /// @return a non-nil pointer to ad @ref enum_diff node iff @p diff is /// a @ref enum_diff node. const enum_diff* is_enum_diff(const diff *diff) {return dynamic_cast(diff);} /// Test if a diff node is a @ref union_diff node. /// /// @param diff the diff node to consider. /// /// @return a non-nil pointer to a @ref union_diff iff @p diff is a /// @ref union_diff node. const union_diff* is_union_diff(const diff* diff) {return dynamic_cast(diff);} /// Test if a diff node is a @ref class_or_union_diff node. /// /// @param d the diff node to consider. /// /// @return a non-nil pointer to the @ref class_or_union_diff denoted /// by @p d iff @p d is a @ref class_or_union_diff. const class_or_union_diff* is_class_or_union_diff(const diff* d) {return dynamic_cast(d);} /// Test if a diff node is a @ref class_or_union_diff between two /// anonymous classes or unions. /// /// @param d the diff node to consider. /// /// @return a non-nil pointer to the @ref class_or_union_diff iff @p /// denoted by @p d iff @p is pointer to an anonymous class or union /// diff. const class_or_union_diff* is_anonymous_class_or_union_diff(const diff* d) { if (const class_or_union_diff *dif = is_class_or_union_diff(d)) if (dif->first_class_or_union()->get_is_anonymous()) return dif; return 0; } /// Test if a diff node is a @ref typedef_diff node. /// /// @param diff the diff node to consider. /// /// @return a non-nil pointer to a @ref typedef_diff iff @p diff is a /// @ref typedef_diff node. const typedef_diff* is_typedef_diff(const diff *diff) {return dynamic_cast(diff);} /// Test if a diff node is a @ref array_diff node. /// /// @param diff the diff node to consider. /// /// @return a non-nil pointer to a @ref array_diff iff @p diff is a /// @ref array_diff node. const array_diff* is_array_diff(const diff* diff) {return dynamic_cast(diff);} /// Test if a diff node is a @ref function_type_diff node. /// /// @param diff the diff node to consider. /// /// @return a non-nil pointer to a @ref function_type_diff iff @p diff is a /// @ref function_type_diff node. const function_type_diff* is_function_type_diff(const diff* diff) {return dynamic_cast(diff);} /// Test if a given diff node carries a function type change with /// local changes. /// /// @param diff the diff node to consider. /// /// @return a non-nil pointer to a @ref function_type_diff iff @p diff /// is a function_type_diff node that carries a local change. const function_type_diff* is_function_type_diff_with_local_changes(const diff* diff) { if (const function_type_diff* d = is_function_type_diff(diff)) if (d->has_local_changes()) return d; return 0; } /// Test if a diff node is about differences between variables. /// /// @param diff the diff node to test. /// /// @return a pointer to the actual var_diff that @p diff is a type /// of, iff it is about differences between variables. const var_diff* is_var_diff(const diff* diff) { const var_diff* d = dynamic_cast(diff); if (d) ABG_ASSERT(is_decl_diff(diff)); return d; } /// Test if a diff node is about differences between functions. /// /// @param diff the diff node to test. /// /// @return a pointer to the actual var_diff that @p diff is a type /// of, iff it is about differences between variables. const function_decl_diff* is_function_decl_diff(const diff* diff) { const function_decl_diff *d = dynamic_cast(diff); if (d) ABG_ASSERT(is_decl_diff(diff)); return d; } /// Test if a diff node is about differences between two pointers. /// /// @param diff the diff node to consider. /// /// @return the @p diff converted into an instance of @ref /// pointer_diff iff @p diff is about differences between two /// pointers. const pointer_diff* is_pointer_diff(const diff* diff) {return dynamic_cast(diff);} /// Test if a diff node is about differences between two references. /// /// @param diff the diff node to consider. /// /// @return the @p diff converted into an instance of @ref /// reference_diff iff @p diff is about differences between two /// references. const reference_diff* is_reference_diff(const diff* diff) {return dynamic_cast(diff);} /// Test if a diff node is about differences between two qualified /// types. /// /// @param diff the diff node to consider. /// /// @return @p diff converted into an instance of @ref /// qualified_type_diff iff @p diff is about differences between two /// qualified types. const qualified_type_diff* is_qualified_type_diff(const diff* diff) {return dynamic_cast(diff);} /// Test if a diff node is a reference or pointer diff node to a /// change that is neither basic type change nor distinct type change. /// /// Note that this function also works on diffs of typedefs of /// reference or pointer. /// /// @param diff the diff node to consider. /// /// @return true iff @p diff is a eference or pointer diff node to a /// change that is neither basic type change nor distinct type change. bool is_reference_or_ptr_diff_to_non_basic_nor_distinct_types(const diff* diff) { diff = peel_typedef_diff(diff); if (const reference_diff* d = is_reference_diff(diff)) { diff = peel_reference_diff(d); if (is_diff_of_basic_type(diff) || is_distinct_diff(diff)) return false; return true; } else if (const pointer_diff *d = is_pointer_diff(diff)) { diff = peel_pointer_diff(d); if (is_diff_of_basic_type(diff) || is_distinct_diff(diff)) return false; return true; } return false; } /// Test if a diff node is about differences between two function /// parameters. /// /// @param diff the diff node to consider. /// /// @return the @p diff converted into an instance of @ref /// reference_diff iff @p diff is about differences between two /// function parameters. const fn_parm_diff* is_fn_parm_diff(const diff* diff) {return dynamic_cast(diff);} /// Test if a diff node is about differences between two base class /// specifiers. /// /// @param diff the diff node to consider. /// /// @return the @p diff converted into an instance of @ref base_diff /// iff @p diff is about differences between two base class /// specifiers. const base_diff* is_base_diff(const diff* diff) {return dynamic_cast(diff);} /// Test if a diff node is about differences between two diff nodes of /// different kinds. /// /// @param diff the diff node to consider. /// /// @return the @p diff converted into an instance of @ref /// distintc_diff iff @p diff is about differences between two diff /// nodes of different kinds. const distinct_diff* is_distinct_diff(const diff *diff) {return dynamic_cast(diff);} /// Test if a diff node is a @ref corpus_diff node. /// /// @param diff the diff node to consider. /// /// @return a non-nil pointer to a @ref corpus_diff iff @p diff is a /// @ref corpus_diff node. const corpus_diff* is_corpus_diff(const diff* diff) {return dynamic_cast(diff);} /// Test if a diff node is a child node of a function parameter diff node. /// /// @param diff the diff node to test. /// /// @return true iff @p diff is a child node of a function parameter /// diff node. bool is_child_node_of_function_parm_diff(const diff* diff) {return diff && is_fn_parm_diff(diff->parent_node());} /// Test if a diff node is a child node of a base diff node. /// /// @param diff the diff node to test. /// /// @return true iff @p diff is a child node of a base diff node. bool is_child_node_of_base_diff(const diff* diff) {return diff && is_base_diff(diff->parent_node());} /// The default traverse function. /// /// @return true. bool diff_traversable_base::traverse(diff_node_visitor&) {return true;} diff_context::diff_context() : priv_(new diff_context::priv) { // Setup all the diff output filters we have. filtering::filter_base_sptr f; f.reset(new filtering::harmless_harmful_filter); add_diff_filter(f); // f.reset(new filtering::harmless_filter); // add_diff_filter(f); // f.reset(new filtering::harmful_filter); // add_diff_filter(f); } diff_context::~diff_context() = default; /// Set the corpus diff relevant to this context. /// /// @param d the corpus_diff we are interested in. void diff_context::set_corpus_diff(const corpus_diff_sptr& d) {priv_->corpus_diff_ = d;} /// Get the corpus diff for the current context. /// /// @return the corpus diff of this context. const corpus_diff_sptr& diff_context::get_corpus_diff() const {return priv_->corpus_diff_;} /// Getter for the first corpus of the corpus diff of the current context. /// /// @return the first corpus of the corpus diff of the current /// context, if no corpus diff is associated to the context. corpus_sptr diff_context::get_first_corpus() const { if (priv_->corpus_diff_) return priv_->corpus_diff_->first_corpus(); return corpus_sptr(); } /// Getter for the second corpus of the corpus diff of the current /// context. /// /// @return the second corpus of the corpus diff of the current /// context, if no corpus diff is associated to the context. corpus_sptr diff_context::get_second_corpus() const { if (priv_->corpus_diff_) return priv_->corpus_diff_->second_corpus(); return corpus_sptr(); } /// Getter of the reporter to be used in this context. /// /// @return the reporter to be used in this context. reporter_base_sptr diff_context::get_reporter() const { if (!priv_->reporter_) { if (show_leaf_changes_only()) priv_->reporter_.reset(new leaf_reporter); else priv_->reporter_.reset(new default_reporter); } ABG_ASSERT(priv_->reporter_); return priv_->reporter_; } /// Setter of the reporter to be used in this context. /// /// @param r the reporter to be used in this context. void diff_context::set_reporter(reporter_base_sptr& r) {priv_->reporter_ = r;} /// Tests if the current diff context already has a diff for two decls. /// /// @param first the first decl to consider. /// /// @param second the second decl to consider. /// /// @return a pointer to the diff for @p first @p second if found, /// null otherwise. diff_sptr diff_context::has_diff_for(const type_or_decl_base_sptr first, const type_or_decl_base_sptr second) const { types_or_decls_diff_map_type::const_iterator i = priv_->types_or_decls_diff_map.find(std::make_pair(first, second)); if (i != priv_->types_or_decls_diff_map.end()) return i->second; return diff_sptr(); } /// Tests if the current diff context already has a diff for two types. /// /// @param first the first type to consider. /// /// @param second the second type to consider. /// /// @return a pointer to the diff for @p first @p second if found, /// null otherwise. diff_sptr diff_context::has_diff_for_types(const type_base_sptr first, const type_base_sptr second) const {return has_diff_for(first, second);} /// Tests if the current diff context already has a given diff. /// ///@param d the diff to consider. /// /// @return a pointer to the diff found for @p d const diff* diff_context::has_diff_for(const diff* d) const {return has_diff_for(d->first_subject(), d->second_subject()).get();} /// Tests if the current diff context already has a given diff. /// ///@param d the diff to consider. /// /// @return a pointer to the diff found for @p d diff_sptr diff_context::has_diff_for(const diff_sptr d) const {return has_diff_for(d->first_subject(), d->second_subject());} /// Getter for the bitmap that represents the set of categories that /// the user wants to see reported. /// /// @return a bitmap that represents the set of categories that the /// user wants to see reported. diff_category diff_context::get_allowed_category() const {return priv_->allowed_category_;} /// Setter for the bitmap that represents the set of categories that /// the user wants to see reported. /// /// @param c a bitmap that represents the set of categories that the /// user wants to see represented. void diff_context::set_allowed_category(diff_category c) {priv_->allowed_category_ = c;} /// Setter for the bitmap that represents the set of categories that /// the user wants to see reported /// /// This function perform a bitwise or between the new set of /// categories and the current ones, and then sets the current /// categories to the result of the or. /// /// @param c a bitmap that represents the set of categories that the /// user wants to see represented. void diff_context::switch_categories_on(diff_category c) {priv_->allowed_category_ = priv_->allowed_category_ | c;} /// Setter for the bitmap that represents the set of categories that /// the user wants to see reported /// /// This function actually unsets bits from the current categories. /// /// @param c a bitmap that represents the set of categories to unset /// from the current categories. void diff_context::switch_categories_off(diff_category c) {priv_->allowed_category_ = priv_->allowed_category_ & ~c;} /// Add a diff for two decls to the cache of the current diff_context. /// /// Doing this allows to later find the added diff from its two /// subject decls. /// /// @param first the first decl to consider. /// /// @param second the second decl to consider. /// /// @param the diff to add. void diff_context::add_diff(type_or_decl_base_sptr first, type_or_decl_base_sptr second, const diff_sptr d) {priv_->types_or_decls_diff_map[std::make_pair(first, second)] = d;} /// Add a diff tree node to the cache of the current diff_context /// /// @param d the diff tree node to add. void diff_context::add_diff(const diff* d) { if (d) { diff_sptr dif(const_cast(d), noop_deleter()); add_diff(d->first_subject(), d->second_subject(), dif); } } /// Add a diff tree node to the cache of the current diff_context /// /// @param d the diff tree node to add. void diff_context::add_diff(const diff_sptr d) { if (d) add_diff(d->first_subject(), d->second_subject(), d); } /// Getter for the @ref CanonicalDiff "canonical diff node" for the /// @ref diff represented by their two subjects. /// /// @param first the first subject of the diff. /// /// @param second the second subject of the diff. /// /// @return the canonical diff for the diff node represented by the /// two diff subjects @p first and @p second. If no canonical diff /// node was registered for these subjects, then a nil node is /// returned. diff_sptr diff_context::get_canonical_diff_for(const type_or_decl_base_sptr first, const type_or_decl_base_sptr second) const {return has_diff_for(first, second);} /// Getter for the @ref CanonicalDiff "canonical diff node" for the /// @ref diff represented by the two subjects of a given diff node. /// /// @param d the diff node to get the canonical node for. /// /// @return the canonical diff for the diff node represented by the /// two diff subjects of @p d. If no canonical diff node was /// registered for these subjects, then a nil node is returned. diff_sptr diff_context::get_canonical_diff_for(const diff_sptr d) const {return has_diff_for(d);} /// Setter for the @ref CanonicalDiff "canonical diff node" for the /// @ref diff represented by their two subjects. /// /// @param first the first subject of the diff. /// /// @param second the second subject of the diff. /// /// @param d the new canonical diff. void diff_context::set_canonical_diff_for(const type_or_decl_base_sptr first, const type_or_decl_base_sptr second, const diff_sptr d) { ABG_ASSERT(d); if (!has_diff_for(first, second)) { add_diff(first, second, d); priv_->canonical_diffs.push_back(d); } } /// If there is is a @ref CanonicalDiff "canonical diff node" /// registered for two diff subjects, return it. Otherwise, register /// a canonical diff node for these two diff subjects and return it. /// /// @param first the first subject of the diff. /// /// @param second the second subject of the diff. /// /// @param d the new canonical diff node. /// /// @return the canonical diff node. diff_sptr diff_context::set_or_get_canonical_diff_for(const type_or_decl_base_sptr first, const type_or_decl_base_sptr second, const diff_sptr canonical_diff) { ABG_ASSERT(canonical_diff); diff_sptr canonical = get_canonical_diff_for(first, second); if (!canonical) { canonical = canonical_diff; set_canonical_diff_for(first, second, canonical); } return canonical; } /// Set the canonical diff node property of a given diff node /// appropriately. /// /// For a given diff node that has no canonical diff node, retrieve /// the canonical diff node (by looking at its diff subjects and at /// the current context) and set the canonical diff node property of /// the diff node to that canonical diff node. If no canonical diff /// node has been registered to the diff context for the subjects of /// the diff node then, register the canonical diff node as being the /// diff node itself; and set its canonical diff node property as /// such. Otherwise, if the diff node already has a canonical diff /// node, do nothing. /// /// @param diff the diff node to initialize the canonical diff node /// property for. void diff_context::initialize_canonical_diff(const diff_sptr diff) { if (diff->get_canonical_diff() == 0) { diff_sptr canonical = set_or_get_canonical_diff_for(diff->first_subject(), diff->second_subject(), diff); diff->set_canonical_diff(canonical.get()); } } /// Add a diff node to the set of diff nodes that are kept alive for /// the life time of the current instance of diff_context. /// /// Note that diff added to the diff cache are kept alive as well, and /// don't need to be passed to this function to be kept alive. /// /// @param d the diff node to be kept alive during the life time of /// the current instance of @ref diff_context. void diff_context::keep_diff_alive(diff_sptr& d) {priv_->live_diffs_.insert(d);} /// Test if a diff node has been traversed. /// /// @param d the diff node to consider. /// /// @return the first diff node against which @p d is redundant. diff* diff_context::diff_has_been_visited(const diff* d) const { const diff* canonical = d->get_canonical_diff(); ABG_ASSERT(canonical); size_t ptr_value = reinterpret_cast(canonical); pointer_map::iterator it = priv_->visited_diff_nodes_.find(ptr_value); if (it != priv_->visited_diff_nodes_.end()) return reinterpret_cast(it->second); else return 0; } /// Test if a diff node has been traversed. /// /// @param d the diff node to consider. /// /// @return the first diff node against which @p d is redundant. diff_sptr diff_context::diff_has_been_visited(const diff_sptr d) const { diff_sptr diff(diff_has_been_visited(d.get())); return diff; } /// Mark a diff node as traversed by a traversing algorithm. /// /// Actually, it's the @ref CanonicalDiff "canonical diff" of this /// node that is marked as traversed. /// /// Subsequent invocations of diff_has_been_visited() on the diff node /// will yield true. void diff_context::mark_diff_as_visited(const diff* d) { if (diff_has_been_visited(d)) return; const diff* canonical = d->get_canonical_diff(); ABG_ASSERT(canonical); size_t canonical_ptr_value = reinterpret_cast(canonical); size_t diff_ptr_value = reinterpret_cast(d); priv_->visited_diff_nodes_[canonical_ptr_value] = diff_ptr_value; } /// Unmark all the diff nodes that were marked as being traversed. void diff_context::forget_visited_diffs() {priv_->visited_diff_nodes_.clear();} /// This sets a flag that, if it's true, then during the traversing of /// a diff nodes tree each node is visited at most once. /// /// @param f if true then during the traversing of a diff nodes tree /// each node is visited at most once. /// void diff_context::forbid_visiting_a_node_twice(bool f) {priv_->forbid_visiting_a_node_twice_ = f;} /// This function sets a flag os that if @ref /// forbid_visiting_a_node_twice() returns true, then each time the /// node visitor starts visiting a new interface, it resets the /// memory the systems has about already visited node. /// /// @param f the flag to set. void diff_context::forbid_visiting_a_node_twice_per_interface(bool f) {priv_->reset_visited_diffs_for_each_interface_ = f;} /// Return a flag that, if true, then during the traversing of a diff /// nodes tree each node is visited at most once. /// /// @return the boolean flag. bool diff_context::visiting_a_node_twice_is_forbidden() const {return priv_->forbid_visiting_a_node_twice_;} /// Return a flag that, if true, then during the traversing of a diff /// nodes tree each node is visited at most once, while visiting the /// diff tree underneath a given interface (public function or /// variable). Each time a new interface is visited, the nodes /// visited while visiting previous interfaces can be visited again. /// /// @return the boolean flag. /// /// @return the boolean flag. bool diff_context::visiting_a_node_twice_is_forbidden_per_interface() const { return (priv_->forbid_visiting_a_node_twice_ && priv_->reset_visited_diffs_for_each_interface_); } /// Getter for the diff tree nodes filters to apply to diff sub-trees. /// /// @return the vector of tree filters to apply to diff sub-trees. const filtering::filters& diff_context::diff_filters() const {return priv_->filters_;} /// Setter for the diff filters to apply to a given diff sub-tree. /// /// @param f the new diff filter to add to the vector of diff filters /// to apply to diff sub-trees. void diff_context::add_diff_filter(filtering::filter_base_sptr f) {priv_->filters_.push_back(f);} /// Apply the diff filters to a given diff sub-tree. /// /// If the current context is instructed to filter out some categories /// then this function walks the given sub-tree and categorizes its /// nodes by using the filters held by the context. /// /// @param diff the diff sub-tree to apply the filters to. void diff_context::maybe_apply_filters(diff_sptr diff) { if (!diff) return; if (get_allowed_category() == EVERYTHING_CATEGORY) return; if (!diff->has_changes()) return; for (filtering::filters::const_iterator i = diff_filters().begin(); i != diff_filters().end(); ++i) { filtering::apply_filter(*i, diff); propagate_categories(diff); } } /// Apply the diff filters to the diff nodes of a @ref corpus_diff /// instance. /// /// If the current context is instructed to filter out some categories /// then this function walks the diff tree and categorizes its nodes /// by using the filters held by the context. /// /// @param diff the corpus diff to apply the filters to. void diff_context::maybe_apply_filters(corpus_diff_sptr diff) { if (!diff || !diff->has_changes()) return; for (filtering::filters::const_iterator i = diff_filters().begin(); i != diff_filters().end(); ++i) { filtering::apply_filter(**i, diff); propagate_categories(diff); } } /// Getter for the vector of suppressions that specify which diff node /// reports should be dropped on the floor. /// /// @return the set of suppressions. suppressions_type& diff_context::suppressions() const {return priv_->suppressions_;} /// Add a new suppression specification that specifies which diff node /// reports should be dropped on the floor. /// /// @param suppr the new suppression specification to add to the /// existing set of suppressions specifications of the diff context. void diff_context::add_suppression(const suppression_sptr suppr) {priv_->suppressions_.push_back(suppr);} /// Add new suppression specifications that specify which diff node /// reports should be dropped on the floor. /// /// @param supprs the new suppression specifications to add to the /// existing set of suppression specifications of the diff context. void diff_context::add_suppressions(const suppressions_type& supprs) { priv_->suppressions_.insert(priv_->suppressions_.end(), supprs.begin(), supprs.end()); } /// Set the flag that indicates if the diff using this context should /// show only leaf changes or not. /// /// @param f the new value of the flag that indicates if the diff /// using this context should show only leaf changes or not. void diff_context::show_leaf_changes_only(bool f) { // This function can be called only if the reporter hasn't yet been // created. Once it's been created, we are supposed to live with // it. ABG_ASSERT(priv_->reporter_ == 0); priv_->leaf_changes_only_ = f; } /// Get the flag that indicates if the diff using this context should /// show only leaf changes or not. /// /// @return the value of the flag that indicates if the diff using /// this context should show only leaf changes or not. bool diff_context::show_leaf_changes_only() const {return priv_->leaf_changes_only_;} /// Get the flag that indicates if the diff reports using this context /// should show sizes and offsets in an hexadecimal base or not. If /// not, then they are to be shown in a decimal base. /// /// @return true iff sizes and offsets are to be shown in an /// hexadecimal base. bool diff_context::show_hex_values() const {return priv_->hex_values_;} /// Set the flag that indicates if diff reports using this context /// should show sizes and offsets in an hexadecimal base or not. If /// not, then they are to be shown in a decimal base. /// /// @param f if true then sizes and offsets are to be shown in an /// hexadecimal base. void diff_context::show_hex_values(bool f) {priv_->hex_values_ = f;} /// Get the flag that indicates if diff reports using this context /// should show sizes and offsets in bits, rather than bytes. /// /// @return true iff sizes and offsets are to be shown in bits. /// Otherwise they are to be shown in bytes. bool diff_context::show_offsets_sizes_in_bits() const {return priv_->show_offsets_sizes_in_bits_;} /// Set the flag that indicates if diff reports using this context /// should show sizes and offsets in bits, rather than bytes. /// /// @param f if true then sizes and offsets are to be shown in bits. /// Otherwise they are to be shown in bytes. void diff_context::show_offsets_sizes_in_bits(bool f) {priv_->show_offsets_sizes_in_bits_ = f;} /// Set a flag saying if offset changes should be reported in a /// relative way. That is, if the report should say how of many bits /// a class/struct data member did move. /// /// @param f the new boolean value of the flag. void diff_context::show_relative_offset_changes(bool f) {priv_->show_relative_offset_changes_ = f;} /// Get the flag saying if offset changes should be reported in a /// relative way. That is, if the report should say how of many bits /// a class/struct data member did move. /// /// @return the boolean value of the flag. bool diff_context::show_relative_offset_changes(void) {return priv_->show_relative_offset_changes_;} /// Set a flag saying if the comparison module should only show the /// diff stats. /// /// @param f the flag to set. void diff_context::show_stats_only(bool f) {priv_->show_stats_only_ = f;} /// Test if the comparison module should only show the diff stats. /// /// @return true if the comparison module should only show the diff /// stats, false otherwise. bool diff_context::show_stats_only() const {return priv_->show_stats_only_;} /// Setter for the property that says if the comparison module should /// show the soname changes in its report. /// /// @param f the new value of the property. void diff_context::show_soname_change(bool f) {priv_->show_soname_change_ = f;} /// Getter for the property that says if the comparison module should /// show the soname changes in its report. /// /// @return the value of the property. bool diff_context::show_soname_change() const {return priv_->show_soname_change_;} /// Setter for the property that says if the comparison module should /// show the architecture changes in its report. /// /// @param f the new value of the property. void diff_context::show_architecture_change(bool f) {priv_->show_architecture_change_ = f;} /// Getter for the property that says if the comparison module should /// show the architecture changes in its report. /// /// @return the value of the property. bool diff_context::show_architecture_change() const {return priv_->show_architecture_change_;} /// Set a flag saying to show the deleted functions. /// /// @param f true to show deleted functions. void diff_context::show_deleted_fns(bool f) {priv_->show_deleted_fns_ = f;} /// @return true if we want to show the deleted functions, false /// otherwise. bool diff_context::show_deleted_fns() const {return priv_->show_deleted_fns_;} /// Set a flag saying to show the changed functions. /// /// @param f true to show the changed functions. void diff_context::show_changed_fns(bool f) {priv_->show_changed_fns_ = f;} /// @return true if we want to show the changed functions, false otherwise. bool diff_context::show_changed_fns() const {return priv_->show_changed_fns_;} /// Set a flag saying to show the added functions. /// /// @param f true to show the added functions. void diff_context::show_added_fns(bool f) {priv_->show_added_fns_ = f;} /// @return true if we want to show the added functions, false /// otherwise. bool diff_context::show_added_fns() const {return priv_->show_added_fns_;} /// Set a flag saying to show the deleted variables. /// /// @param f true to show the deleted variables. void diff_context::show_deleted_vars(bool f) {priv_->show_deleted_vars_ = f;} /// @return true if we want to show the deleted variables, false /// otherwise. bool diff_context::show_deleted_vars() const {return priv_->show_deleted_vars_;} /// Set a flag saying to show the changed variables. /// /// @param f true to show the changed variables. void diff_context::show_changed_vars(bool f) {priv_->show_changed_vars_ = f;} /// @return true if we want to show the changed variables, false otherwise. bool diff_context::show_changed_vars() const {return priv_->show_changed_vars_;} /// Set a flag saying to show the added variables. /// /// @param f true to show the added variables. void diff_context::show_added_vars(bool f) {priv_->show_added_vars_ = f;} /// @return true if we want to show the added variables, false /// otherwise. bool diff_context::show_added_vars() const {return priv_->show_added_vars_;} bool diff_context::show_linkage_names() const {return priv_->show_linkage_names_;} void diff_context::show_linkage_names(bool f) {priv_->show_linkage_names_= f;} /// Set a flag saying to show location information. /// /// @param f true to show location information. void diff_context::show_locs(bool f) {priv_->show_locs_= f;} /// @return true if we want to show location information, false /// otherwise. bool diff_context::show_locs() const {return priv_->show_locs_;} /// A getter for the flag that says if we should report about /// functions or variables diff nodes that have *exclusively* /// redundant diff tree children nodes. /// /// @return the flag. bool diff_context::show_redundant_changes() const {return priv_->show_redundant_changes_;} /// A setter for the flag that says if we should report about /// functions or variables diff nodes that have *exclusively* /// redundant diff tree children nodes. /// /// @param f the flag to set. void diff_context::show_redundant_changes(bool f) {priv_->show_redundant_changes_ = f;} /// Getter for the flag that indicates if symbols not referenced by /// any debug info are to be compared and reported about. /// /// @return the boolean flag. bool diff_context::show_symbols_unreferenced_by_debug_info() const {return priv_->show_syms_unreferenced_by_di_;} /// Setter for the flag that indicates if symbols not referenced by /// any debug info are to be compared and reported about. /// /// @param f the new flag to set. void diff_context::show_symbols_unreferenced_by_debug_info(bool f) {priv_->show_syms_unreferenced_by_di_ = f;} /// Getter for the flag that indicates if symbols not referenced by /// any debug info and that got added are to be reported about. /// /// @return true iff symbols not referenced by any debug info and that /// got added are to be reported about. bool diff_context::show_added_symbols_unreferenced_by_debug_info() const {return priv_->show_added_syms_unreferenced_by_di_;} /// Setter for the flag that indicates if symbols not referenced by /// any debug info and that got added are to be reported about. /// /// @param f the new flag that says if symbols not referenced by any /// debug info and that got added are to be reported about. void diff_context::show_added_symbols_unreferenced_by_debug_info(bool f) {priv_->show_added_syms_unreferenced_by_di_ = f;} /// Setter for the flag that indicates if changes on types unreachable /// from global functions and variables are to be reported. /// /// @param f if true, then changes on types unreachable from global /// functions and variables are to be reported. void diff_context::show_unreachable_types(bool f) {priv_->show_unreachable_types_ = f;} /// Getter for the flag that indicates if changes on types unreachable /// from global functions and variables are to be reported. /// /// @return true iff changes on types unreachable from global /// functions and variables are to be reported. bool diff_context::show_unreachable_types() {return priv_->show_unreachable_types_;} /// Getter of the flag that indicates if the leaf reporter should /// display a summary of the interfaces impacted by a given leaf /// change or not. /// /// @return the flag that indicates if the leaf reporter should /// display a summary of the interfaces impacted by a given leaf /// change or not. bool diff_context::show_impacted_interfaces() const {return priv_->show_impacted_interfaces_;} /// Setter of the flag that indicates if the leaf reporter should /// display a summary of the interfaces impacted by a given leaf /// change or not. /// /// @param f the new value of the flag that indicates if the leaf /// reporter should display a summary of the interfaces impacted by a /// given leaf change or not. void diff_context::show_impacted_interfaces(bool f) {priv_->show_impacted_interfaces_ = f;} /// Setter for the default output stream used by code of the /// comparison engine. By default the default output stream is a NULL /// pointer. /// /// @param o a pointer to the default output stream. void diff_context::default_output_stream(ostream* o) {priv_->default_output_stream_ = o;} /// Getter for the default output stream used by code of the /// comparison engine. By default the default output stream is a NULL /// pointer. /// /// @return a pointer to the default output stream. ostream* diff_context::default_output_stream() {return priv_->default_output_stream_;} /// Setter for the errror output stream used by code of the comparison /// engine. By default the error output stream is a NULL pointer. /// /// @param o a pointer to the error output stream. void diff_context::error_output_stream(ostream* o) {priv_->error_output_stream_ = o;} /// Getter for the errror output stream used by code of the comparison /// engine. By default the error output stream is a NULL pointer. /// /// @return a pointer to the error output stream. ostream* diff_context::error_output_stream() const {return priv_->error_output_stream_;} /// Test if the comparison engine should dump the diff tree for the /// changed functions and variables it has. /// /// @return true if after the comparison, the engine should dump the /// diff tree for the changed functions and variables it has. bool diff_context::dump_diff_tree() const {return priv_->dump_diff_tree_;} /// Set if the comparison engine should dump the diff tree for the /// changed functions and variables it has. /// /// @param f true if after the comparison, the engine should dump the /// diff tree for the changed functions and variables it has. void diff_context::dump_diff_tree(bool f) {priv_->dump_diff_tree_ = f;} /// Emit a textual representation of a diff tree to the error output /// stream of the current context, for debugging purposes. /// /// @param d the diff tree to serialize to the error output associated /// to the current instance of @ref diff_context. void diff_context::do_dump_diff_tree(const diff_sptr d) const { if (error_output_stream()) print_diff_tree(d, *error_output_stream()); } /// Emit a textual representation of a @ref corpus_diff tree to the error /// output stream of the current context, for debugging purposes. /// /// @param d the @ref corpus_diff tree to serialize to the error /// output associated to the current instance of @ref diff_context. void diff_context::do_dump_diff_tree(const corpus_diff_sptr d) const { if (error_output_stream()) print_diff_tree(d, *error_output_stream()); } // // /// Constructor for the @ref diff type. /// /// This constructs a diff between two subjects that are actually /// declarations; the first and the second one. /// /// @param first_subject the first decl (subject) of the diff. /// /// @param second_subject the second decl (subject) of the diff. diff::diff(type_or_decl_base_sptr first_subject, type_or_decl_base_sptr second_subject) : priv_(new priv(first_subject, second_subject, diff_context_sptr(), NO_CHANGE_CATEGORY, /*reported_once=*/false, /*currently_reporting=*/false)) {} /// Constructor for the @ref diff type. /// /// This constructs a diff between two subjects that are actually /// declarations; the first and the second one. /// /// @param first_subject the first decl (subject) of the diff. /// /// @param second_subject the second decl (subject) of the diff. /// /// @param ctxt the context of the diff. Note that this context /// object must stay alive during the entire life time of the current /// instance of @ref diff. Otherwise, memory corruption issues occur. diff::diff(type_or_decl_base_sptr first_subject, type_or_decl_base_sptr second_subject, diff_context_sptr ctxt) : priv_(new priv(first_subject, second_subject, ctxt, NO_CHANGE_CATEGORY, /*reported_once=*/false, /*currently_reporting=*/false)) {} /// Flag a given diff node as being traversed. /// /// For certain diff nodes like @ref class_diff, it's important to /// avoid traversing the node again while it's already being /// traversed; otherwise this leads to infinite loops. So the /// diff::begin_traversing() and diff::end_traversing() methods flag a /// given node as being traversed (or not), so that /// diff::is_traversing() can tell if the node is being traversed. /// /// Note that traversing a node means visiting it *and* visiting its /// children nodes. /// /// The canonical node is marked as being traversed too. /// /// These functions are called by the traversing code. void diff::begin_traversing() { ABG_ASSERT(!is_traversing()); if (priv_->canonical_diff_) priv_->canonical_diff_->priv_->traversing_ = true; priv_->traversing_ = true; } /// Tell if a given node is being traversed or not. /// /// Note that traversing a node means visiting it *and* visiting its /// children nodes. /// /// It's the canonical node which is looked at, actually. /// /// Please read the comments for the diff::begin_traversing() for mode /// context. /// /// @return true if the current instance of @diff is being traversed. bool diff::is_traversing() const { if (priv_->canonical_diff_) return priv_->canonical_diff_->priv_->traversing_; return priv_->traversing_; } /// Flag a given diff node as not being traversed anymore. /// /// Note that traversing a node means visiting it *and* visiting its /// children nodes. /// /// Please read the comments of the function diff::begin_traversing() /// for mode context. void diff::end_traversing() { ABG_ASSERT(is_traversing()); if (priv_->canonical_diff_) priv_->canonical_diff_->priv_->traversing_ = false; priv_->traversing_ = false; } /// Finish the insertion of a diff tree node into the diff graph. /// /// This function might be called several times. It must perform the /// insertion only once. /// /// For instance, certain kinds of diff tree node have specific /// children nodes that are populated after the constructor of the /// diff tree node has been called. In that case, calling overloads /// of this method ensures that these children nodes are properly /// gathered and setup. void diff::finish_diff_type() { if (diff::priv_->finished_) return; chain_into_hierarchy(); diff::priv_->finished_ = true; } /// Getter of the first subject of the diff. /// /// @return the first subject of the diff. type_or_decl_base_sptr diff::first_subject() const {return dynamic_pointer_cast(priv_->first_subject_);} /// Getter of the second subject of the diff. /// /// @return the second subject of the diff. type_or_decl_base_sptr diff::second_subject() const {return dynamic_pointer_cast(priv_->second_subject_);} /// Getter for the children nodes of the current @ref diff node. /// /// @return a vector of the children nodes. const vector& diff::children_nodes() const {return priv_->children_;} /// Getter for the parent node of the current @ref diff node. /// /// @return the parent node of the current @ref diff node. const diff* diff::parent_node() const {return priv_->parent_;} /// Getter for the canonical diff of the current instance of @ref /// diff. /// /// Note that the canonical diff node for the current instanc eof diff /// node must have been set by invoking /// class_diff::initialize_canonical_diff() on the current instance of /// diff node. /// /// @return the canonical diff node or null if none was set. diff* diff::get_canonical_diff() const {return priv_->canonical_diff_;} /// Setter for the canonical diff of the current instance of @ref /// diff. /// /// @param d the new canonical node to set. void diff::set_canonical_diff(diff * d) {priv_->canonical_diff_ = d;} /// Add a new child node to the vector of children nodes for the /// current @ref diff node. /// /// @param d the new child node to add to the children nodes. void diff::append_child_node(diff_sptr d) { ABG_ASSERT(d); // Ensure 'd' is kept alive for the life time of the context of this // diff. context()->keep_diff_alive(d); // Add the underlying pointer of 'd' to the vector of children. // Note that this vector holds no reference to 'd'. This is to avoid // reference cycles. The reference to 'd' is held by the context of // this diff, thanks to the call to context()->keep_diff_alive(d) // above. priv_->children_.push_back(d.get()); d->priv_->parent_ = this; } /// Getter of the context of the current diff. /// /// @return the context of the current diff. const diff_context_sptr diff::context() const {return priv_->get_context();} /// Setter of the context of the current diff. /// /// @param c the new context to set. void diff::context(diff_context_sptr c) {priv_->ctxt_ = c;} /// Tests if we are currently in the middle of emitting a report for /// this diff. /// /// @return true if we are currently emitting a report for the /// current diff, false otherwise. bool diff::currently_reporting() const { if (priv_->canonical_diff_) return priv_->canonical_diff_->priv_->currently_reporting_; return priv_->currently_reporting_; } /// Sets a flag saying if we are currently in the middle of emitting /// a report for this diff. /// /// @param f true if we are currently emitting a report for the /// current diff, false otherwise. void diff::currently_reporting(bool f) const { if (priv_->canonical_diff_) priv_->canonical_diff_->priv_->currently_reporting_ = f; priv_->currently_reporting_ = f; } /// Tests if a report has already been emitted for the current diff. /// /// @return true if a report has already been emitted for the /// current diff, false otherwise. bool diff::reported_once() const { ABG_ASSERT(priv_->canonical_diff_); return priv_->canonical_diff_->priv_->reported_once_; } /// The generic traversing code that walks a given diff sub-tree. /// /// Note that there is a difference between traversing a diff node and /// visiting it. Basically, traversing a diff node means visiting it /// and visiting its children nodes too. So one can visit a node /// without traversing it. But traversing a node without visiting it /// is not possible. /// /// Note that the insertion of the "generic view" of the diff node /// into the graph being traversed is done "on the fly". The /// insertion of the "typed view" of the diff node into the graph is /// done implicitely. To learn more about the generic and typed view /// of the diff node, please read the introductory comments of the /// @ref diff class. /// /// Note that by default this traversing code visits a given class of /// equivalence of a diff node only once. This behaviour can been /// changed by calling /// diff_context::visiting_a_node_twice_is_forbidden(), but this is /// very risky as it might create endless loops while visiting a diff /// tree graph that has changes that refer to themselves; that is, /// diff tree graphs with cycles. /// /// When a diff node is encountered, the /// diff_node_visitor::visit_begin() method is invoked on the diff /// node first. /// /// If the diff node has already been visited, then /// node_visitor::visit_end() is called on it and the node traversing /// is done; the children of the diff node are not visited in this /// case. /// /// If the diff node has *NOT* been visited yet, then the /// diff_node_visitor::visit() method is invoked with it's 'pre' /// argument set to true. Then if the diff_node_visitor::visit() /// returns true, then the children nodes of the diff node are /// visited. Otherwise, no children nodes of the diff node is /// visited and the diff_node_visitor::visit_end() is called. /// After the children nodes are visited (and only if they are /// visited) the diff_node_visitor::visit() method is invoked with /// it's 'pre' argument set to false. And then the /// diff_node_visitor::visit_end() is called. /// /// @param v the entity that visits each node of the diff sub-tree. /// /// @return true to tell the caller that all of the sub-tree could be /// walked. This instructs the caller to keep walking the rest of the /// tree. Return false otherwise. bool diff::traverse(diff_node_visitor& v) { // Insert the "generic view" of the diff node into its graph. finish_diff_type(); v.visit_begin(this); bool already_visited = false; if (context()->visiting_a_node_twice_is_forbidden() && context()->diff_has_been_visited(this)) already_visited = true; bool mark_visited_nodes_as_traversed = !(v.get_visiting_kind() & DO_NOT_MARK_VISITED_NODES_AS_VISITED); if (!already_visited && !v.visit(this, /*pre=*/true)) { v.visit_end(this); if (mark_visited_nodes_as_traversed) context()->mark_diff_as_visited(this); return false; } if (!(v.get_visiting_kind() & SKIP_CHILDREN_VISITING_KIND) && !is_traversing() && !already_visited) { begin_traversing(); for (vector::const_iterator i = children_nodes().begin(); i != children_nodes().end(); ++i) { if (!(*i)->traverse(v)) { v.visit_end(this); if (mark_visited_nodes_as_traversed) context()->mark_diff_as_visited(this); end_traversing(); return false; } } end_traversing(); } if (!v.visit(this, /*pref=*/false)) { v.visit_end(this); if (mark_visited_nodes_as_traversed) context()->mark_diff_as_visited(this); return false; } v.visit_end(this); if (!already_visited && mark_visited_nodes_as_traversed) context()->mark_diff_as_visited(this); return true; } /// Sets a flag saying if a report has already been emitted for the /// current diff. /// /// @param f true if a report has already been emitted for the /// current diff, false otherwise. void diff::reported_once(bool f) const { ABG_ASSERT(priv_->canonical_diff_); priv_->canonical_diff_->priv_->reported_once_ = f; priv_->reported_once_ = f; } /// Getter for the local category of the current diff tree node. /// /// The local category represents the set of categories of a diff /// node, not taking in account the categories inherited from its /// children nodes. /// /// @return the local category of the current diff tree node. diff_category diff::get_local_category() const {return priv_->local_category_;} /// Getter of the category of the class of equivalence of the current /// diff tree node. /// /// That is, if the current diff tree node has a canonical node, /// return the category of that canonical node. Otherwise, return the /// category of the current node. /// /// @return the category of the class of equivalence of the current /// tree node. diff_category diff::get_class_of_equiv_category() const { diff* canonical = get_canonical_diff(); return canonical ? canonical->get_category() : get_category(); } /// Getter for the category of the current diff tree node. /// /// This category represents the union of the local category and the /// categories inherited from the children diff nodes. /// /// @return the category of the current diff tree node. diff_category diff::get_category() const {return priv_->category_;} /// Adds the current diff tree node to an additional set of /// categories. Note that the categories include thoses inherited /// from the children nodes of this diff node. /// /// @param c a bit-map representing the set of categories to add the /// current diff tree node to. /// /// @return the resulting bit-map representing the categories this /// current diff tree node belongs to, including those inherited from /// its children nodes. diff_category diff::add_to_category(diff_category c) { priv_->category_ = priv_->category_ | c; return priv_->category_; } /// Adds the current diff tree node to the categories resulting from /// the local changes of the current diff node. /// /// @param c a bit-map representing the set of categories to add the /// current diff tree node to. /// /// @return the resulting bit-map representing the categories this /// current diff tree node belongs to. diff_category diff::add_to_local_category(diff_category c) { priv_->local_category_ = priv_->local_category_ | c; return priv_->local_category_; } /// Adds the current diff tree node to the categories resulting from /// the local and inherited changes of the current diff node. /// /// @param c a bit-map representing the set of categories to add the /// current diff tree node to. void diff::add_to_local_and_inherited_categories(diff_category c) { add_to_local_category(c); add_to_category(c); } /// Remove the current diff tree node from an a existing sef of /// categories. The categories include those inherited from the /// children nodes of the current diff node. /// /// @param c a bit-map representing the set of categories to add the /// current diff tree node to. /// /// @return the resulting bit-map representing the categories this /// current diff tree onde belongs to, including the categories /// inherited from the children nodes of the current diff node. diff_category diff::remove_from_category(diff_category c) { priv_->category_ = priv_->category_ & ~c; return priv_->category_; } /// Remove the current diff tree node from the categories resulting /// from the local changes. /// /// @param c a bit-map representing the set of categories to add the /// current diff tree node to. /// /// @return the resulting bit-map representing the categories this /// current diff tree onde belongs to. diff_category diff::remove_from_local_category(diff_category c) { priv_->local_category_ = priv_->local_category_ & ~c; return priv_->local_category_; } /// Set the category of the current @ref diff node. This category /// includes the categories inherited from the children nodes of the /// current diff node. /// /// @param c the new category for the current diff node. void diff::set_category(diff_category c) {priv_->category_ = c;} /// Set the local category of the current @ref diff node. /// /// @param c the new category for the current diff node. void diff::set_local_category(diff_category c) {priv_->local_category_ = c;} /// Test if this diff tree node is to be filtered out for reporting /// purposes. /// /// The function tests if the categories of the diff tree node are /// "forbidden" by the context or not. /// /// @return true iff the current diff node should NOT be reported. bool diff::is_filtered_out() const { if (diff * canonical = get_canonical_diff()) if (canonical->get_category() & SUPPRESSED_CATEGORY || canonical->get_category() & PRIVATE_TYPE_CATEGORY) // The canonical type was suppressed either by a user-provided // suppression specification or by a "private-type" suppression // specification.. This means all the class of equivalence of // that canonical type was suppressed. So this node should be // suppressed too. return true; return priv_->is_filtered_out(get_category()); } /// Test if this diff tree node is to be filtered out for reporting /// purposes, but by considering only the categories that were *NOT* /// inherited from its children nodes. /// /// The function tests if the local categories of the diff tree node /// are "forbidden" by the context or not. /// /// @return true iff the current diff node should NOT be reported, /// with respect to its local categories. bool diff::is_filtered_out_wrt_non_inherited_categories() const {return priv_->is_filtered_out(get_local_category());} /// Test if the current diff node has been suppressed by a /// user-provided suppression specification. /// /// @return true if the current diff node has been suppressed by a /// user-provided suppression list. bool diff::is_suppressed() const { bool is_private = false; return is_suppressed(is_private); } /// Test if the current diff node has been suppressed by a /// user-provided suppression specification or by an auto-generated /// "private type" suppression specification. /// /// Note that private type suppressions are auto-generated from the /// path to where public headers are, as given by the user. /// /// @param is_private_type out parameter if the current diff node was /// suppressed because it's a private type then this parameter is set /// to true. /// /// @return true if the current diff node has been suppressed by a /// user-provided suppression list. bool diff::is_suppressed(bool &is_private_type) const { const suppressions_type& suppressions = context()->suppressions(); for (suppressions_type::const_iterator i = suppressions.begin(); i != suppressions.end(); ++i) { if ((*i)->suppresses_diff(this)) { if (is_private_type_suppr_spec(*i)) is_private_type = true; return true; } } return false; } /// Test if this diff tree node should be reported. /// /// @return true iff the current node should be reported. bool diff::to_be_reported() const { if (has_changes() && !is_filtered_out()) return true; return false; } /// Test if this diff tree node should be reported when considering /// the categories that were *NOT* inherited from its children nodes. /// /// @return true iff the current node should be reported. bool diff::has_local_changes_to_be_reported() const { if (has_local_changes() && !is_filtered_out_wrt_non_inherited_categories()) return true; return false; } /// Get a pretty representation of the current @ref diff node. /// /// This is suitable for e.g. emitting debugging traces for the diff /// tree nodes. /// /// @return the pretty representation of the diff node. const string& diff::get_pretty_representation() const { if (priv_->pretty_representation_.empty()) priv_->pretty_representation_ = "empty_diff"; return priv_->pretty_representation_; } /// Default implementation of the hierachy chaining virtual function. /// /// There are several types of diff nodes that have logical children /// nodes; for instance, a typedef_diff has the diff of the underlying /// type as a child node. A var_diff has the diff of the types of the /// variables as a child node, etc. /// /// But because the @ref diff base has a generic representation for /// children nodes of the all the types of @ref diff nodes (regardless /// of the specific most-derived type of diff node) that one can get /// using the method diff::children_nodes(), one need to populate that /// vector of children node. /// /// Populating that vector of children node is done by this function; /// it must be overloaded by each most-derived type of diff node that /// extends the @ref diff type. void diff::chain_into_hierarchy() {} // // type_diff_base::type_diff_base(type_base_sptr first_subject, type_base_sptr second_subject, diff_context_sptr ctxt) : diff(first_subject, second_subject, ctxt), priv_(new priv) {} type_diff_base::~type_diff_base() {} // // /// Constructor of @ref decl_diff_base. /// /// @param first_subject the first subject of the diff. /// /// @param second_subject the second subject of the diff. /// /// @param ctxt the context of the diff. This object must stay alive /// at least during the life time of the current instance of @ref /// decl_diff_base, otherwise, memory corruption issues occur. decl_diff_base::decl_diff_base(decl_base_sptr first_subject, decl_base_sptr second_subject, diff_context_sptr ctxt) : diff(first_subject, second_subject, ctxt), priv_(new priv) {} decl_diff_base::~decl_diff_base() {} // // /// @return a pretty representation for the @ref distinct_diff node. const string& distinct_diff::get_pretty_representation() const { if (diff::priv_->pretty_representation_.empty()) { std::ostringstream o; o << "distinct_diff["; if (first_subject()) o << first_subject()->get_pretty_representation(); else o << "null"; o << ", "; if (second_subject()) o << second_subject()->get_pretty_representation() ; else o << "null"; o << "]" ; diff::priv_->pretty_representation_ = o.str(); } return diff::priv_->pretty_representation_; } /// Populate the vector of children node of the @ref diff base type /// sub-object of this instance of @distinct_diff. /// /// The children nodes can then later be retrieved using /// diff::children_nodes(). void distinct_diff::chain_into_hierarchy() { ABG_ASSERT(entities_are_of_distinct_kinds(first(), second())); if (diff_sptr d = compatible_child_diff()) append_child_node(d); } /// Constructor for @ref distinct_diff. /// /// Note that the two entities considered for the diff (and passed in /// parameter) must be of different kinds. /// /// @param first the first entity to consider for the diff. /// /// @param second the second entity to consider for the diff. /// /// @param ctxt the context of the diff. Note that this context /// object must stay alive at least during the life time of the /// current instance of @ref distinct_diff. Otherwise memory /// corruption issues occur. distinct_diff::distinct_diff(type_or_decl_base_sptr first, type_or_decl_base_sptr second, diff_context_sptr ctxt) : diff(first, second, ctxt), priv_(new priv) {ABG_ASSERT(entities_are_of_distinct_kinds(first, second));} /// Getter for the first subject of the diff. /// /// @return the first subject of the diff. const type_or_decl_base_sptr distinct_diff::first() const {return first_subject();} /// Getter for the second subject of the diff. /// /// @return the second subject of the diff. const type_or_decl_base_sptr distinct_diff::second() const {return second_subject();} /// Getter for the child diff of this distinct_diff instance. /// /// When a distinct_diff has two subjects that are different but /// compatible, then the distinct_diff instance has a child diff node /// (named the compatible child diff) that is the diff between the two /// subjects stripped from their typedefs. Otherwise, the compatible /// child diff is nul. /// /// Note that two diff subjects (that compare different) are /// considered compatible if stripping typedefs out of them makes them /// comparing equal. /// /// @return the compatible child diff node, if any. Otherwise, null. const diff_sptr distinct_diff::compatible_child_diff() const { if (!priv_->compatible_child_diff) { type_base_sptr fs = strip_typedef(is_type(first())), ss = strip_typedef(is_type(second())); if (fs && ss && !entities_are_of_distinct_kinds(get_type_declaration(fs), get_type_declaration(ss))) priv_->compatible_child_diff = compute_diff(get_type_declaration(fs), get_type_declaration(ss), context()); } return priv_->compatible_child_diff; } /// Test if the two arguments are of different kind, or that are both /// NULL. /// /// @param first the first argument to test for similarity in kind. /// /// @param second the second argument to test for similarity in kind. /// /// @return true iff the two arguments are of different kind. bool distinct_diff::entities_are_of_distinct_kinds(type_or_decl_base_sptr first, type_or_decl_base_sptr second) { if (!!first != !!second) return true; if (!first && !second) // We do consider diffs of two empty decls as a diff of distinct // kinds, for now. return true; if (first == second) return false; const type_or_decl_base &f = *first, &s = *second; return typeid(f) != typeid(s); } /// @return true if the two subjects of the diff are different, false /// otherwise. bool distinct_diff::has_changes() const {return first() != second();} /// @return the kind of local change carried by the current diff node. /// The value returned is zero if the current node carries no local /// change. enum change_kind distinct_diff::has_local_changes() const { // Changes on a distinct_diff are all local. if (has_changes()) return LOCAL_TYPE_CHANGE_KIND; return NO_CHANGE_KIND; } /// Emit a report about the current diff instance. /// /// @param out the output stream to send the diff report to. /// /// @param indent the indentation string to use in the report. void distinct_diff::report(ostream& out, const string& indent) const { context()->get_reporter()->report(*this, out, indent); } /// Try to diff entities that are of distinct kinds. /// /// @param first the first entity to consider for the diff. /// /// @param second the second entity to consider for the diff. /// /// @param ctxt the context of the diff. /// /// @return a non-null diff if a diff object could be built, null /// otherwise. distinct_diff_sptr compute_diff_for_distinct_kinds(const type_or_decl_base_sptr first, const type_or_decl_base_sptr second, diff_context_sptr ctxt) { if (!distinct_diff::entities_are_of_distinct_kinds(first, second)) return distinct_diff_sptr(); distinct_diff_sptr result(new distinct_diff(first, second, ctxt)); ctxt->initialize_canonical_diff(result); return result; } /// /// Try to compute a diff on two instances of DiffType representation. /// /// The function template performs the diff if and only if the decl /// representations are of a DiffType. /// /// @tparm DiffType the type of instances to diff. /// /// @param first the first representation of decl to consider in the /// diff computation. /// /// @param second the second representation of decl to consider in the /// diff computation. /// /// @param ctxt the diff context to use. /// ///@return the diff of the two types @p first and @p second if and ///only if they represent the parametrized type DiffType. Otherwise, ///returns a NULL pointer value. template diff_sptr try_to_diff(const type_or_decl_base_sptr first, const type_or_decl_base_sptr second, diff_context_sptr ctxt) { if (shared_ptr f = dynamic_pointer_cast(first)) { shared_ptr s = dynamic_pointer_cast(second); if (!s) return diff_sptr(); return compute_diff(f, s, ctxt); } return diff_sptr(); } /// This is a specialization of @ref try_to_diff() template to diff /// instances of @ref class_decl. /// /// @param first the first representation of decl to consider in the /// diff computation. /// /// @param second the second representation of decl to consider in the /// diff computation. /// /// @param ctxt the diff context to use. template<> diff_sptr try_to_diff(const type_or_decl_base_sptr first, const type_or_decl_base_sptr second, diff_context_sptr ctxt) { if (class_decl_sptr f = dynamic_pointer_cast(first)) { class_decl_sptr s = dynamic_pointer_cast(second); if (!s) return diff_sptr(); if (f->get_is_declaration_only()) { class_decl_sptr f2 = is_class_type (f->get_definition_of_declaration()); if (f2) f = f2; } if (s->get_is_declaration_only()) { class_decl_sptr s2 = is_class_type(s->get_definition_of_declaration()); if (s2) s = s2; } return compute_diff(f, s, ctxt); } return diff_sptr(); } /// Try to diff entities that are of distinct kinds. /// /// @param first the first entity to consider for the diff. /// /// @param second the second entity to consider for the diff. /// /// @param ctxt the context of the diff. /// /// @return a non-null diff if a diff object could be built, null /// otherwise. static diff_sptr try_to_diff_distinct_kinds(const type_or_decl_base_sptr first, const type_or_decl_base_sptr second, diff_context_sptr ctxt) {return compute_diff_for_distinct_kinds(first, second, ctxt);} /// Compute the difference between two types. /// /// The function considers every possible types known to libabigail /// and runs the appropriate diff function on them. /// /// Whenever a new kind of type decl is supported by abigail, if we /// want to be able to diff two instances of it, we need to update /// this function to support it. /// /// @param first the first type decl to consider for the diff /// /// @param second the second type decl to consider for the diff. /// /// @param ctxt the diff context to use. /// /// @return the resulting diff. It's a pointer to a descendent of /// abigail::comparison::diff. static diff_sptr compute_diff_for_types(const type_or_decl_base_sptr& first, const type_or_decl_base_sptr& second, const diff_context_sptr& ctxt) { type_or_decl_base_sptr f = first; type_or_decl_base_sptr s = second; diff_sptr d; ((d = try_to_diff(f, s, ctxt)) ||(d = try_to_diff(f, s, ctxt)) ||(d = try_to_diff(f, s,ctxt)) ||(d = try_to_diff(f, s,ctxt)) ||(d = try_to_diff(f, s, ctxt)) ||(d = try_to_diff(f, s, ctxt)) ||(d = try_to_diff(f, s, ctxt)) ||(d = try_to_diff(f, s, ctxt)) ||(d = try_to_diff(f, s, ctxt)) ||(d = try_to_diff(f, s, ctxt)) ||(d = try_to_diff_distinct_kinds(f, s, ctxt))); ABG_ASSERT(d); return d; } diff_category operator|(diff_category c1, diff_category c2) {return static_cast(static_cast(c1) | static_cast(c2));} diff_category& operator|=(diff_category& c1, diff_category c2) { c1 = c1 | c2; return c1; } diff_category& operator&=(diff_category& c1, diff_category c2) { c1 = c1 & c2; return c1; } diff_category operator^(diff_category c1, diff_category c2) {return static_cast(static_cast(c1) ^ static_cast(c2));} diff_category operator&(diff_category c1, diff_category c2) {return static_cast(static_cast(c1) & static_cast(c2));} diff_category operator~(diff_category c) {return static_cast(~static_cast(c));} /// Getter of a bitmap made of the set of change categories that are /// considered harmless. /// /// @return the bitmap made of the set of change categories that are /// considered harmless. diff_category get_default_harmless_categories_bitmap() { return (abigail::comparison::ACCESS_CHANGE_CATEGORY | abigail::comparison::COMPATIBLE_TYPE_CHANGE_CATEGORY | abigail::comparison::HARMLESS_DECL_NAME_CHANGE_CATEGORY | abigail::comparison::NON_VIRT_MEM_FUN_CHANGE_CATEGORY | abigail::comparison::STATIC_DATA_MEMBER_CHANGE_CATEGORY | abigail::comparison::HARMLESS_ENUM_CHANGE_CATEGORY | abigail::comparison::HARMLESS_SYMBOL_ALIAS_CHANGE_CATEGORY | abigail::comparison::HARMLESS_UNION_CHANGE_CATEGORY | abigail::comparison::HARMLESS_DATA_MEMBER_CHANGE_CATEGORY | abigail::comparison::TYPE_DECL_ONLY_DEF_CHANGE_CATEGORY | abigail::comparison::FN_PARM_TYPE_TOP_CV_CHANGE_CATEGORY | abigail::comparison::FN_PARM_TYPE_CV_CHANGE_CATEGORY | abigail::comparison::FN_RETURN_TYPE_CV_CHANGE_CATEGORY | abigail::comparison::VAR_TYPE_CV_CHANGE_CATEGORY | abigail::comparison::VOID_PTR_TO_PTR_CHANGE_CATEGORY | abigail::comparison::BENIGN_INFINITE_ARRAY_CHANGE_CATEGORY); } /// Getter of a bitmap made of the set of change categories that are /// considered harmful. /// /// @return the bitmap made of the set of change categories that are /// considered harmful. diff_category get_default_harmful_categories_bitmap() { return (abigail::comparison::SIZE_OR_OFFSET_CHANGE_CATEGORY | abigail::comparison::VIRTUAL_MEMBER_CHANGE_CATEGORY | abigail::comparison::FN_PARM_ADD_REMOVE_CHANGE_CATEGORY); } /// Serialize an instance of @ref diff_category to an output stream. /// /// @param o the output stream to serialize @p c to. /// /// @param c the instance of diff_category to serialize. /// /// @return the output stream to serialize @p c to. ostream& operator<<(ostream& o, diff_category c) { bool emitted_a_category = false; if (c == NO_CHANGE_CATEGORY) { o << "NO_CHANGE_CATEGORY"; emitted_a_category = true; } if (c & ACCESS_CHANGE_CATEGORY) { if (emitted_a_category) o << "|"; o << "ACCESS_CHANGE_CATEGORY"; emitted_a_category |= true; } if (c & COMPATIBLE_TYPE_CHANGE_CATEGORY) { if (emitted_a_category) o << "|"; o << "COMPATIBLE_TYPE_CHANGE_CATEGORY"; emitted_a_category |= true; } if (c & HARMLESS_DECL_NAME_CHANGE_CATEGORY) { if (emitted_a_category) o << "|"; o << "HARMLESS_DECL_NAME_CHANGE_CATEGORY"; emitted_a_category |= true; } if (c & NON_VIRT_MEM_FUN_CHANGE_CATEGORY) { if (emitted_a_category) o << "|"; o << "NON_VIRT_MEM_FUN_CHANGE_CATEGORY"; emitted_a_category |= true; } if (c & STATIC_DATA_MEMBER_CHANGE_CATEGORY) { if (emitted_a_category) o << "|"; o << "STATIC_DATA_MEMBER_CHANGE_CATEGORY"; emitted_a_category |= true; } if (c & HARMLESS_ENUM_CHANGE_CATEGORY) { if (emitted_a_category) o << "|"; o << "HARMLESS_ENUM_CHANGE_CATEGORY"; emitted_a_category |= true; } if (c & HARMLESS_DATA_MEMBER_CHANGE_CATEGORY) { if (emitted_a_category) o << "|"; o << "HARMLESS_DATA_MEMBER_CHANGE_CATEGORY"; emitted_a_category |= true; } if (c & HARMLESS_SYMBOL_ALIAS_CHANGE_CATEGORY) { if (emitted_a_category) o << "|"; o << "HARMLESS_SYMBOL_ALIAS_CHANGE_CATEGORY"; emitted_a_category |= true; } if (c & HARMLESS_UNION_CHANGE_CATEGORY) { if (emitted_a_category) o << "|"; o << "HARMLESS_UNION_CHANGE_CATEGORY"; emitted_a_category |= true; } if (c & SUPPRESSED_CATEGORY) { if (emitted_a_category) o << "|"; o << "SUPPRESSED_CATEGORY"; emitted_a_category |= true; } if (c & PRIVATE_TYPE_CATEGORY) { if (emitted_a_category) o << "|"; o << "PRIVATE_TYPE_CATEGORY"; emitted_a_category |= true; } if (c & SIZE_OR_OFFSET_CHANGE_CATEGORY) { if (emitted_a_category) o << "|"; o << "SIZE_OR_OFFSET_CHANGE_CATEGORY"; emitted_a_category |= true; } if (c & VIRTUAL_MEMBER_CHANGE_CATEGORY) { if (emitted_a_category) o << "|"; o << "VIRTUAL_MEMBER_CHANGE_CATEGORY"; emitted_a_category |= true; } if (c & REDUNDANT_CATEGORY) { if (emitted_a_category) o << "|"; o << "REDUNDANT_CATEGORY"; emitted_a_category |= true; } if (c & TYPE_DECL_ONLY_DEF_CHANGE_CATEGORY) { if (emitted_a_category) o << "|"; o << "TYPE_DECL_ONLY_DEF_CHANGE_CATEGORY"; emitted_a_category |= true; } if (c & FN_PARM_TYPE_TOP_CV_CHANGE_CATEGORY) { if (emitted_a_category) o << "|"; o << "FN_PARM_TYPE_TOP_CV_CHANGE_CATEGORY"; emitted_a_category |= true; } if (c & FN_PARM_TYPE_CV_CHANGE_CATEGORY) { if (emitted_a_category) o << "|"; o << "FN_PARM_TYPE_CV_CHANGE_CATEGORY"; emitted_a_category |= true; } if (c & FN_RETURN_TYPE_CV_CHANGE_CATEGORY) { if (emitted_a_category) o << "|"; o << "FN_RETURN_TYPE_CV_CHANGE_CATEGORY"; emitted_a_category |= true; } if (c & FN_PARM_ADD_REMOVE_CHANGE_CATEGORY) { if (emitted_a_category) o << "|"; o << "FN_PARM_ADD_REMOVE_CHANGE_CATEGORY"; emitted_a_category |= true; } if (c & VAR_TYPE_CV_CHANGE_CATEGORY) { if (emitted_a_category) o << "|"; o << "VAR_TYPE_CV_CHANGE_CATEGORY"; emitted_a_category |= true; } if (c & VOID_PTR_TO_PTR_CHANGE_CATEGORY) { if (emitted_a_category) o << "|"; o << "VOID_PTR_TO_PTR_CHANGE_CATEGORY"; emitted_a_category |= true; } if (c & BENIGN_INFINITE_ARRAY_CHANGE_CATEGORY) { if (emitted_a_category) o << "|"; o << "BENIGN_INFINITE_ARRAY_CHANGE_CATEGORY"; emitted_a_category |= true; } return o; } /// Compute the difference between two decls. /// /// The function consider every possible decls known to libabigail and /// runs the appropriate diff function on them. /// /// Whenever a new kind of non-type decl is supported by abigail, if /// we want to be able to diff two instances of it, we need to update /// this function to support it. /// /// @param first the first decl to consider for the diff /// /// @param second the second decl to consider for the diff. /// /// @param ctxt the diff context to use. /// /// @return the resulting diff. static diff_sptr compute_diff_for_decls(const decl_base_sptr first, const decl_base_sptr second, diff_context_sptr ctxt) { diff_sptr d; ((d = try_to_diff(first, second, ctxt)) || (d = try_to_diff(first, second, ctxt)) || (d = try_to_diff_distinct_kinds(first, second, ctxt))); ABG_ASSERT(d); return d; } /// Compute the difference between two decls. The decls can represent /// either type declarations, or non-type declaration. /// /// Note that the two decls must have been created in the same @ref /// environment, otherwise, this function aborts. /// /// @param first the first decl to consider. /// /// @param second the second decl to consider. /// /// @param ctxt the diff context to use. /// /// @return the resulting diff, or NULL if the diff could not be /// computed. diff_sptr compute_diff(const decl_base_sptr first, const decl_base_sptr second, diff_context_sptr ctxt) { if (!first || !second) return diff_sptr(); diff_sptr d; if (is_type(first) && is_type(second)) d = compute_diff_for_types(first, second, ctxt); else d = compute_diff_for_decls(first, second, ctxt); ABG_ASSERT(d); return d; } /// Compute the difference between two types. /// /// Note that the two types must have been created in the same @ref /// environment, otherwise, this function aborts. /// /// @param first the first type to consider. /// /// @param second the second type to consider. /// /// @param ctxt the diff context to use. /// /// @return the resulting diff, or NULL if the diff couldn't be /// computed. diff_sptr compute_diff(const type_base_sptr first, const type_base_sptr second, diff_context_sptr ctxt) { decl_base_sptr f = get_type_declaration(first), s = get_type_declaration(second); diff_sptr d = compute_diff_for_types(f,s, ctxt); ABG_ASSERT(d); return d; } /// Get a copy of the pretty representation of a diff node. /// /// @param d the diff node to consider. /// /// @return the pretty representation string. string get_pretty_representation(diff* d) { if (!d) return ""; string prefix= "diff of "; return prefix + get_pretty_representation(d->first_subject()); } // /// Populate the vector of children node of the @ref diff base type /// sub-object of this instance of @ref var_diff. /// /// The children node can then later be retrieved using /// diff::children_node(). void var_diff::chain_into_hierarchy() {append_child_node(type_diff());} /// @return the pretty representation for this current instance of /// @ref var_diff. const string& var_diff::get_pretty_representation() const { if (diff::priv_->pretty_representation_.empty()) { std::ostringstream o; o << "var_diff[" << first_subject()->get_pretty_representation() << ", " << second_subject()->get_pretty_representation() << "]"; diff::priv_->pretty_representation_ = o.str(); } return diff::priv_->pretty_representation_; } /// Constructor for @ref var_diff. /// /// @param first the first instance of @ref var_decl to consider in /// the diff. /// /// @param second the second instance of @ref var_decl to consider in /// the diff. /// /// @param type_diff the diff between types of the instances of /// var_decl. /// /// @param ctxt the diff context to use. var_diff::var_diff(var_decl_sptr first, var_decl_sptr second, diff_sptr type_diff, diff_context_sptr ctxt) : decl_diff_base(first, second, ctxt), priv_(new priv) {priv_->type_diff_ = type_diff;} /// Getter for the first @ref var_decl of the diff. /// /// @return the first @ref var_decl of the diff. var_decl_sptr var_diff::first_var() const {return dynamic_pointer_cast(first_subject());} /// Getter for the second @ref var_decl of the diff. /// /// @return the second @ref var_decl of the diff. var_decl_sptr var_diff::second_var() const {return dynamic_pointer_cast(second_subject());} /// Getter for the diff of the types of the instances of @ref /// var_decl. /// /// @return the diff of the types of the instances of @ref var_decl. diff_sptr var_diff::type_diff() const { if (diff_sptr result = priv_->type_diff_.lock()) return result; else { result = compute_diff(first_var()->get_type(), second_var()->get_type(), context()); context()->keep_diff_alive(result); priv_->type_diff_ = result; return result; } } /// Return true iff the diff node has a change. /// /// @return true iff the diff node has a change. bool var_diff::has_changes() const {return *first_var() != *second_var();} /// @return the kind of local change carried by the current diff node. /// The value returned is zero if the current node carries no local /// change. enum change_kind var_diff::has_local_changes() const { ir::change_kind k = ir::NO_CHANGE_KIND; if (!equals(*first_var(), *second_var(), &k)) return k & ir::ALL_LOCAL_CHANGES_MASK; return ir::NO_CHANGE_KIND; } /// Report the diff in a serialized form. /// /// @param out the stream to serialize the diff to. /// /// @param indent the prefix to use for the indentation of this /// serialization. void var_diff::report(ostream& out, const string& indent) const { context()->get_reporter()->report(*this, out, indent); } /// Compute the diff between two instances of @ref var_decl. /// /// Note that the two decls must have been created in the same @ref /// environment, otherwise, this function aborts. /// /// @param first the first @ref var_decl to consider for the diff. /// /// @param second the second @ref var_decl to consider for the diff. /// /// @param ctxt the diff context to use. /// /// @return the resulting diff between the two @ref var_decl. var_diff_sptr compute_diff(const var_decl_sptr first, const var_decl_sptr second, diff_context_sptr ctxt) { var_diff_sptr d(new var_diff(first, second, diff_sptr(), ctxt)); ctxt->initialize_canonical_diff(d); return d; } // // /// Populate the vector of children node of the @ref diff base type /// sub-object of this instance of @ref pointer_diff. /// /// The children node can then later be retrieved using /// diff::children_node(). void pointer_diff::chain_into_hierarchy() {append_child_node(underlying_type_diff());} /// Constructor for a pointer_diff. /// /// @param first the first pointer to consider for the diff. /// /// @param second the secon pointer to consider for the diff. /// /// @param ctxt the diff context to use. pointer_diff::pointer_diff(pointer_type_def_sptr first, pointer_type_def_sptr second, diff_sptr underlying, diff_context_sptr ctxt) : type_diff_base(first, second, ctxt), priv_(new priv(underlying)) {} /// Getter for the first subject of a pointer diff /// /// @return the first pointer considered in this pointer diff. const pointer_type_def_sptr pointer_diff::first_pointer() const {return dynamic_pointer_cast(first_subject());} /// Getter for the second subject of a pointer diff /// /// @return the second pointer considered in this pointer diff. const pointer_type_def_sptr pointer_diff::second_pointer() const {return dynamic_pointer_cast(second_subject());} /// @return the pretty represenation for the current instance of @ref /// pointer_diff. const string& pointer_diff::get_pretty_representation() const { if (diff::priv_->pretty_representation_.empty()) { std::ostringstream o; o << "pointer_diff[" << first_subject()->get_pretty_representation() << ", " << second_subject()->get_pretty_representation() << "]"; diff::priv_->pretty_representation_ = o.str(); } return diff::priv_->pretty_representation_; } /// Return true iff the current diff node carries a change. /// /// @return true iff the current diff node carries a change. bool pointer_diff::has_changes() const {return first_pointer() != second_pointer();} /// @return the kind of local change carried by the current diff node. /// The value returned is zero if the current node carries no local /// change. enum change_kind pointer_diff::has_local_changes() const { ir::change_kind k = ir::NO_CHANGE_KIND; if (!equals(*first_pointer(), *second_pointer(), &k)) return k & ir::ALL_LOCAL_CHANGES_MASK; return ir::NO_CHANGE_KIND; } /// Getter for the diff between the pointed-to types of the pointers /// of this diff. /// /// @return the diff between the pointed-to types. diff_sptr pointer_diff::underlying_type_diff() const {return priv_->underlying_type_diff_;} /// Setter for the diff between the pointed-to types of the pointers /// of this diff. /// /// @param d the new diff between the pointed-to types of the pointers /// of this diff. void pointer_diff::underlying_type_diff(const diff_sptr d) {priv_->underlying_type_diff_ = d;} /// Report the diff in a serialized form. /// /// @param out the stream to serialize the diff to. /// /// @param indent the prefix to use for the indentation of this /// serialization. void pointer_diff::report(ostream& out, const string& indent) const { context()->get_reporter()->report(*this, out, indent); } /// Compute the diff between between two pointers. /// /// Note that the two types must have been created in the same @ref /// environment, otherwise, this function aborts. /// /// @param first the pointer to consider for the diff. /// /// @param second the pointer to consider for the diff. /// /// @return the resulting diff between the two pointers. /// /// @param ctxt the diff context to use. pointer_diff_sptr compute_diff(pointer_type_def_sptr first, pointer_type_def_sptr second, diff_context_sptr ctxt) { diff_sptr d = compute_diff_for_types(first->get_pointed_to_type(), second->get_pointed_to_type(), ctxt); pointer_diff_sptr result(new pointer_diff(first, second, d, ctxt)); ctxt->initialize_canonical_diff(result); return result; } // // /// Populate the vector of children node of the @ref diff base type /// sub-object of this instance of @ref array_diff. /// /// The children node can then later be retrieved using /// diff::children_node(). void array_diff::chain_into_hierarchy() {append_child_node(element_type_diff());} /// Constructor for array_diff /// /// @param first the first array_type of the diff. /// /// @param second the second array_type of the diff. /// /// @param element_type_diff the diff between the two array element /// types. /// /// @param ctxt the diff context to use. array_diff::array_diff(const array_type_def_sptr first, const array_type_def_sptr second, diff_sptr element_type_diff, diff_context_sptr ctxt) : type_diff_base(first, second, ctxt), priv_(new priv(element_type_diff)) {} /// Getter for the first array of the diff. /// /// @return the first array of the diff. const array_type_def_sptr array_diff::first_array() const {return dynamic_pointer_cast(first_subject());} /// Getter for the second array of the diff. /// /// @return for the second array of the diff. const array_type_def_sptr array_diff::second_array() const {return dynamic_pointer_cast(second_subject());} /// Getter for the diff between the two types of array elements. /// /// @return the diff between the two types of array elements. const diff_sptr& array_diff::element_type_diff() const {return priv_->element_type_diff_;} /// Setter for the diff between the two array element types. /// /// @param d the new diff betweend the two array element types. void array_diff::element_type_diff(diff_sptr d) {priv_->element_type_diff_ = d;} /// @return the pretty representation for the current instance of @ref /// array_diff. const string& array_diff::get_pretty_representation() const { if (diff::priv_->pretty_representation_.empty()) { std::ostringstream o; o << "array_diff[" << first_subject()->get_pretty_representation() << ", " << second_subject()->get_pretty_representation() << "]"; diff::priv_->pretty_representation_ = o.str(); } return diff::priv_->pretty_representation_; } /// Return true iff the current diff node carries a change. /// /// @return true iff the current diff node carries a change. bool array_diff::has_changes() const { bool l = false; // the array element types match check for differing dimensions // etc... array_type_def_sptr f = dynamic_pointer_cast(first_subject()), s = dynamic_pointer_cast(second_subject()); if (f->get_name() != s->get_name()) l |= true; if (f->get_size_in_bits() != s->get_size_in_bits()) l |= true; if (f->get_alignment_in_bits() != s->get_alignment_in_bits()) l |= true; l |= element_type_diff() ? element_type_diff()->has_changes() : false; return l; } /// @return the kind of local change carried by the current diff node. /// The value returned is zero if the current node carries no local /// change. enum change_kind array_diff::has_local_changes() const { ir::change_kind k = ir::NO_CHANGE_KIND; if (!equals(*first_array(), *second_array(), &k)) return k & ir::ALL_LOCAL_CHANGES_MASK; return ir::NO_CHANGE_KIND; } /// Report the diff in a serialized form. /// /// @param out the output stream to serialize the dif to. /// /// @param indent the string to use for indenting the report. void array_diff::report(ostream& out, const string& indent) const { context()->get_reporter()->report(*this, out, indent); } /// Compute the diff between two arrays. /// /// Note that the two types must have been created in the same @ref /// environment, otherwise, this function aborts. /// /// @param first the first array to consider for the diff. /// /// @param second the second array to consider for the diff. /// /// @param ctxt the diff context to use. array_diff_sptr compute_diff(array_type_def_sptr first, array_type_def_sptr second, diff_context_sptr ctxt) { diff_sptr d = compute_diff_for_types(first->get_element_type(), second->get_element_type(), ctxt); array_diff_sptr result(new array_diff(first, second, d, ctxt)); ctxt->initialize_canonical_diff(result); return result; } // // /// Populate the vector of children node of the @ref diff base type /// sub-object of this instance of @ref reference_diff. /// /// The children node can then later be retrieved using /// diff::children_node(). void reference_diff::chain_into_hierarchy() {append_child_node(underlying_type_diff());} /// Constructor for reference_diff /// /// @param first the first reference_type of the diff. /// /// @param second the second reference_type of the diff. /// /// @param ctxt the diff context to use. reference_diff::reference_diff(const reference_type_def_sptr first, const reference_type_def_sptr second, diff_sptr underlying, diff_context_sptr ctxt) : type_diff_base(first, second, ctxt), priv_(new priv(underlying)) {} /// Getter for the first reference of the diff. /// /// @return the first reference of the diff. reference_type_def_sptr reference_diff::first_reference() const {return dynamic_pointer_cast(first_subject());} /// Getter for the second reference of the diff. /// /// @return for the second reference of the diff. reference_type_def_sptr reference_diff::second_reference() const {return dynamic_pointer_cast(second_subject());} /// Getter for the diff between the two referred-to types. /// /// @return the diff between the two referred-to types. const diff_sptr& reference_diff::underlying_type_diff() const {return priv_->underlying_type_diff_;} /// Setter for the diff between the two referred-to types. /// /// @param d the new diff betweend the two referred-to types. diff_sptr& reference_diff::underlying_type_diff(diff_sptr d) { priv_->underlying_type_diff_ = d; return priv_->underlying_type_diff_; } /// @return the pretty representation for the current instance of @ref /// reference_diff. const string& reference_diff::get_pretty_representation() const { if (diff::priv_->pretty_representation_.empty()) { std::ostringstream o; o << "reference_diff[" << first_subject()->get_pretty_representation() << ", " << second_subject()->get_pretty_representation() << "]"; diff::priv_->pretty_representation_ = o.str(); } return diff::priv_->pretty_representation_; } /// Return true iff the current diff node carries a change. /// /// @return true iff the current diff node carries a change. bool reference_diff::has_changes() const { return first_reference() != second_reference(); } /// @return the kind of local change carried by the current diff node. /// The value returned is zero if the current node carries no local /// change. enum change_kind reference_diff::has_local_changes() const { ir::change_kind k = ir::NO_CHANGE_KIND; if (!equals(*first_reference(), *second_reference(), &k)) return k & ir::ALL_LOCAL_CHANGES_MASK; return ir::NO_CHANGE_KIND; } /// Report the diff in a serialized form. /// /// @param out the output stream to serialize the dif to. /// /// @param indent the string to use for indenting the report. void reference_diff::report(ostream& out, const string& indent) const { context()->get_reporter()->report(*this, out, indent); } /// Compute the diff between two references. /// /// Note that the two types must have been created in the same @ref /// environment, otherwise, this function aborts. /// /// @param first the first reference to consider for the diff. /// /// @param second the second reference to consider for the diff. /// /// @param ctxt the diff context to use. reference_diff_sptr compute_diff(reference_type_def_sptr first, reference_type_def_sptr second, diff_context_sptr ctxt) { diff_sptr d = compute_diff_for_types(first->get_pointed_to_type(), second->get_pointed_to_type(), ctxt); reference_diff_sptr result(new reference_diff(first, second, d, ctxt)); ctxt->initialize_canonical_diff(result); return result; } // // /// Populate the vector of children node of the @ref diff base type /// sub-object of this instance of @ref qualified_type_diff. /// /// The children node can then later be retrieved using /// diff::children_node(). void qualified_type_diff::chain_into_hierarchy() {append_child_node(leaf_underlying_type_diff());} /// Constructor for qualified_type_diff. /// /// @param first the first qualified type of the diff. /// /// @param second the second qualified type of the diff. /// /// @param ctxt the diff context to use. qualified_type_diff::qualified_type_diff(qualified_type_def_sptr first, qualified_type_def_sptr second, diff_sptr under, diff_context_sptr ctxt) : type_diff_base(first, second, ctxt), priv_(new priv(under)) {} /// Getter for the first qualified type of the diff. /// /// @return the first qualified type of the diff. const qualified_type_def_sptr qualified_type_diff::first_qualified_type() const {return dynamic_pointer_cast(first_subject());} /// Getter for the second qualified type of the diff. /// /// @return the second qualified type of the diff. const qualified_type_def_sptr qualified_type_diff::second_qualified_type() const {return dynamic_pointer_cast(second_subject());} /// Getter for the diff between the underlying types of the two /// qualified types. /// /// @return the diff between the underlying types of the two qualified /// types. diff_sptr qualified_type_diff::underlying_type_diff() const {return priv_->underlying_type_diff;} /// Getter for the diff between the most underlying non-qualified /// types of two qualified types. /// /// @return the diff between the most underlying non-qualified types /// of two qualified types. diff_sptr qualified_type_diff::leaf_underlying_type_diff() const { if (!priv_->leaf_underlying_type_diff) priv_->leaf_underlying_type_diff = compute_diff_for_types(get_leaf_type(first_qualified_type()), get_leaf_type(second_qualified_type()), context()); return priv_->leaf_underlying_type_diff; } /// Setter for the diff between the underlying types of the two /// qualified types. /// /// @return the diff between the underlying types of the two qualified /// types. void qualified_type_diff::underlying_type_diff(const diff_sptr d) {priv_->underlying_type_diff = d;} /// @return the pretty representation of the current instance of @ref /// qualified_type_diff. const string& qualified_type_diff::get_pretty_representation() const { if (diff::priv_->pretty_representation_.empty()) { std::ostringstream o; o << "qualified_type_diff[" << first_subject()->get_pretty_representation() << ", " << second_subject()->get_pretty_representation() << "]"; diff::priv_->pretty_representation_ = o.str(); } return diff::priv_->pretty_representation_; } /// Return true iff the current diff node carries a change. /// /// @return true iff the current diff node carries a change. bool qualified_type_diff::has_changes() const {return first_qualified_type() != second_qualified_type();} /// @return the kind of local change carried by the current diff node. /// The value returned is zero if the current node carries no local /// change. enum change_kind qualified_type_diff::has_local_changes() const { ir::change_kind k = ir::NO_CHANGE_KIND; if (!equals(*first_qualified_type(), *second_qualified_type(), &k)) return k & ir::ALL_LOCAL_CHANGES_MASK; return ir::NO_CHANGE_KIND; } /// Report the diff in a serialized form. /// /// @param out the output stream to serialize to. /// /// @param indent the string to use to indent the lines of the report. void qualified_type_diff::report(ostream& out, const string& indent) const { context()->get_reporter()->report(*this, out, indent); } /// Compute the diff between two qualified types. /// /// Note that the two types must have been created in the same @ref /// environment, otherwise, this function aborts. /// /// @param first the first qualified type to consider for the diff. /// /// @param second the second qualified type to consider for the diff. /// /// @param ctxt the diff context to use. qualified_type_diff_sptr compute_diff(const qualified_type_def_sptr first, const qualified_type_def_sptr second, diff_context_sptr ctxt) { diff_sptr d = compute_diff_for_types(first->get_underlying_type(), second->get_underlying_type(), ctxt); qualified_type_diff_sptr result(new qualified_type_diff(first, second, d, ctxt)); ctxt->initialize_canonical_diff(result); return result; } // // /// Clear the lookup tables useful for reporting an enum_diff. /// /// This function must be updated each time a lookup table is added or /// removed from the class_diff::priv. void enum_diff::clear_lookup_tables() { priv_->deleted_enumerators_.clear(); priv_->inserted_enumerators_.clear(); priv_->changed_enumerators_.clear(); } /// Tests if the lookup tables are empty. /// /// @return true if the lookup tables are empty, false otherwise. bool enum_diff::lookup_tables_empty() const { return (priv_->deleted_enumerators_.empty() && priv_->inserted_enumerators_.empty() && priv_->changed_enumerators_.empty()); } /// If the lookup tables are not yet built, walk the differences and /// fill the lookup tables. void enum_diff::ensure_lookup_tables_populated() { if (!lookup_tables_empty()) return; { edit_script e = priv_->enumerators_changes_; for (vector::const_iterator it = e.deletions().begin(); it != e.deletions().end(); ++it) { unsigned i = it->index(); const enum_type_decl::enumerator& n = first_enum()->get_enumerators()[i]; const string& name = n.get_name(); ABG_ASSERT(priv_->deleted_enumerators_.find(n.get_name()) == priv_->deleted_enumerators_.end()); priv_->deleted_enumerators_[name] = n; } for (vector::const_iterator it = e.insertions().begin(); it != e.insertions().end(); ++it) { for (vector::const_iterator iit = it->inserted_indexes().begin(); iit != it->inserted_indexes().end(); ++iit) { unsigned i = *iit; const enum_type_decl::enumerator& n = second_enum()->get_enumerators()[i]; const string& name = n.get_name(); ABG_ASSERT(priv_->inserted_enumerators_.find(n.get_name()) == priv_->inserted_enumerators_.end()); string_enumerator_map::const_iterator j = priv_->deleted_enumerators_.find(name); if (j == priv_->deleted_enumerators_.end()) priv_->inserted_enumerators_[name] = n; else { if (j->second != n) priv_->changed_enumerators_[j->first] = std::make_pair(j->second, n); priv_->deleted_enumerators_.erase(j); } } } } } /// Populate the vector of children node of the @ref diff base type /// sub-object of this instance of @ref enum_diff. /// /// The children node can then later be retrieved using /// diff::children_node(). void enum_diff::chain_into_hierarchy() {append_child_node(underlying_type_diff());} /// Constructor for enum_diff. /// /// @param first the first enum type of the diff. /// /// @param second the second enum type of the diff. /// /// @param underlying_type_diff the diff of the two underlying types /// of the two enum types. /// /// @param ctxt the diff context to use. enum_diff::enum_diff(const enum_type_decl_sptr first, const enum_type_decl_sptr second, const diff_sptr underlying_type_diff, const diff_context_sptr ctxt) : type_diff_base(first, second, ctxt), priv_(new priv(underlying_type_diff)) {} /// @return the first enum of the diff. const enum_type_decl_sptr enum_diff::first_enum() const {return dynamic_pointer_cast(first_subject());} /// @return the second enum of the diff. const enum_type_decl_sptr enum_diff::second_enum() const {return dynamic_pointer_cast(second_subject());} /// @return the diff of the two underlying enum types. diff_sptr enum_diff::underlying_type_diff() const {return priv_->underlying_type_diff_;} /// @return a map of the enumerators that were deleted. const string_enumerator_map& enum_diff::deleted_enumerators() const {return priv_->deleted_enumerators_;} /// @return a map of the enumerators that were inserted const string_enumerator_map& enum_diff::inserted_enumerators() const {return priv_->inserted_enumerators_;} /// @return a map of the enumerators that were changed const string_changed_enumerator_map& enum_diff::changed_enumerators() const {return priv_->changed_enumerators_;} /// @return the pretty representation of the current instance of @ref /// enum_diff. const string& enum_diff::get_pretty_representation() const { if (diff::priv_->pretty_representation_.empty()) { std::ostringstream o; o << "enum_diff[" << first_subject()->get_pretty_representation() << ", " << second_subject()->get_pretty_representation() << "]"; diff::priv_->pretty_representation_ = o.str(); } return diff::priv_->pretty_representation_; } /// Return true iff the current diff node carries a change. /// /// @return true iff the current diff node carries a change. bool enum_diff::has_changes() const {return first_enum() != second_enum();} /// @return the kind of local change carried by the current diff node. /// The value returned is zero if the current node carries no local /// change. enum change_kind enum_diff::has_local_changes() const { ir::change_kind k = ir::NO_CHANGE_KIND; if (!equals(*first_enum(), *second_enum(), &k)) return k & ir::ALL_LOCAL_CHANGES_MASK; return ir::NO_CHANGE_KIND; } /// Report the differences between the two enums. /// /// @param out the output stream to send the report to. /// /// @param indent the string to use for indentation. void enum_diff::report(ostream& out, const string& indent) const { context()->get_reporter()->report(*this, out, indent); } /// Compute the set of changes between two instances of @ref /// enum_type_decl. /// /// Note that the two types must have been created in the same @ref /// environment, otherwise, this function aborts. /// /// @param first a pointer to the first enum_type_decl to consider. /// /// @param second a pointer to the second enum_type_decl to consider. /// /// @return the resulting diff of the two enums @p first and @p /// second. /// /// @param ctxt the diff context to use. enum_diff_sptr compute_diff(const enum_type_decl_sptr first, const enum_type_decl_sptr second, diff_context_sptr ctxt) { diff_sptr ud = compute_diff_for_types(first->get_underlying_type(), second->get_underlying_type(), ctxt); enum_diff_sptr d(new enum_diff(first, second, ud, ctxt)); if (first != second) { compute_diff(first->get_enumerators().begin(), first->get_enumerators().end(), second->get_enumerators().begin(), second->get_enumerators().end(), d->priv_->enumerators_changes_); d->ensure_lookup_tables_populated(); } ctxt->initialize_canonical_diff(d); return d; } // // /// Test if the current diff node carries a member type change for a /// member type which name is the same as the name of a given type /// declaration. /// /// @param d the type declaration which name should be equal to the /// name of the member type that might have changed. /// /// @return the member type that has changed, iff there were a member /// type (which name is the same as the name of @p d) that changed. /// Note that the member type that is returned is the new value of the /// member type that changed. type_or_decl_base_sptr class_or_union_diff::priv::member_type_has_changed(decl_base_sptr d) const { string qname = d->get_qualified_name(); string_diff_sptr_map::const_iterator it = changed_member_types_.find(qname); return ((it == changed_member_types_.end()) ? type_or_decl_base_sptr() : it->second->second_subject()); } /// Test if the current diff node carries a data member change for a /// data member which name is the same as the name of a given type /// declaration. /// /// @param d the type declaration which name should be equal to the /// name of the data member that might have changed. /// /// @return the data member that has changed, iff there were a data /// member type (which name is the same as the name of @p d) that /// changed. Note that the data member that is returned is the new /// value of the data member that changed. decl_base_sptr class_or_union_diff::priv::subtype_changed_dm(decl_base_sptr d) const { string qname = d->get_qualified_name(); string_var_diff_sptr_map::const_iterator it = subtype_changed_dm_.find(qname); if (it == subtype_changed_dm_.end()) return decl_base_sptr(); return it->second->second_var(); } /// Test if the current diff node carries a member class template /// change for a member class template which name is the same as the /// name of a given type declaration. /// /// @param d the type declaration which name should be equal to the /// name of the member class template that might have changed. /// /// @return the member class template that has changed, iff there were /// a member class template (which name is the same as the name of @p /// d) that changed. Note that the member class template that is /// returned is the new value of the member class template that /// changed. decl_base_sptr class_or_union_diff::priv::member_class_tmpl_has_changed(decl_base_sptr d) const { string qname = d->get_qualified_name(); string_diff_sptr_map::const_iterator it = changed_member_class_tmpls_.find(qname); return ((it == changed_member_class_tmpls_.end()) ? decl_base_sptr() : dynamic_pointer_cast(it->second->second_subject())); } /// Get the number of non static data members that were deleted. /// /// @return the number of non static data members that were deleted. size_t class_or_union_diff::priv::get_deleted_non_static_data_members_number() const { size_t result = 0; for (string_decl_base_sptr_map::const_iterator i = deleted_data_members_.begin(); i != deleted_data_members_.end(); ++i) if (is_member_decl(i->second) && !get_member_is_static(i->second)) ++result; return result; } /// Get the number of non static data members that were inserted. /// /// @return the number of non static data members that were inserted. size_t class_or_union_diff::priv::get_inserted_non_static_data_members_number() const { size_t result = 0; for (string_decl_base_sptr_map::const_iterator i = inserted_data_members_.begin(); i != inserted_data_members_.end(); ++i) if (is_member_decl(i->second) && !get_member_is_static(i->second)) ++result; return result; } /// Get the number of data member sub-type changes carried by the /// current diff node that were filtered out. /// /// @param local_only if true, it means that only (filtered) local /// changes are considered. /// /// @return the number of data member sub-type changes carried by the /// current diff node that were filtered out. size_t class_or_union_diff::priv::count_filtered_subtype_changed_dm(bool local_only) { size_t num_filtered= 0; for (var_diff_sptrs_type::const_iterator i = sorted_subtype_changed_dm_.begin(); i != sorted_subtype_changed_dm_.end(); ++i) { if (local_only) { if ((*i)->has_changes() && !(*i)->has_local_changes_to_be_reported()) ++num_filtered; } else { if ((*i)->is_filtered_out()) ++num_filtered; } } return num_filtered; } /// Get the number of data member changes carried by the current diff /// node that were filtered out. /// /// @param local_only if true, it means that only (filtered) local /// changes are considered. /// /// @return the number of data member changes carried by the current /// diff node that were filtered out. size_t class_or_union_diff::priv::count_filtered_changed_dm(bool local_only) { size_t num_filtered= 0; for (unsigned_var_diff_sptr_map::const_iterator i = changed_dm_.begin(); i != changed_dm_.end(); ++i) { diff_sptr diff = i->second; if (local_only) { if ((diff->has_changes() && !diff->has_local_changes_to_be_reported()) || diff->is_filtered_out()) ++num_filtered; } else { if (diff->is_filtered_out()) ++num_filtered; } } return num_filtered; } /// Skip the processing of the current member function if its /// virtual-ness is disallowed by the user. /// /// This is to be used in the member functions below that are used to /// count the number of filtered inserted, deleted and changed member /// functions. #define SKIP_MEM_FN_IF_VIRTUALITY_DISALLOWED \ do { \ if (get_member_function_is_virtual(f) \ || get_member_function_is_virtual(s)) \ { \ if (!(allowed_category | VIRTUAL_MEMBER_CHANGE_CATEGORY)) \ continue; \ } \ else \ { \ if (!(allowed_category | NON_VIRT_MEM_FUN_CHANGE_CATEGORY)) \ continue; \ } \ } while (false) /// Get the number of member functions changes carried by the current /// diff node that were filtered out. /// /// @return the number of member functions changes carried by the /// current diff node that were filtered out. size_t class_or_union_diff::priv::count_filtered_changed_mem_fns (const diff_context_sptr& ctxt) { size_t count = 0; diff_category allowed_category = ctxt->get_allowed_category(); for (function_decl_diff_sptrs_type::const_iterator i = sorted_changed_member_functions_.begin(); i != sorted_changed_member_functions_.end(); ++i) { method_decl_sptr f = dynamic_pointer_cast ((*i)->first_function_decl()); ABG_ASSERT(f); method_decl_sptr s = dynamic_pointer_cast ((*i)->second_function_decl()); ABG_ASSERT(s); SKIP_MEM_FN_IF_VIRTUALITY_DISALLOWED; diff_sptr diff = *i; ctxt->maybe_apply_filters(diff); if (diff->is_filtered_out()) ++count; } return count; } /// Get the number of member functions insertions carried by the current /// diff node that were filtered out. /// /// @return the number of member functions insertions carried by the /// current diff node that were filtered out. size_t class_or_union_diff::priv::count_filtered_inserted_mem_fns (const diff_context_sptr& ctxt) { size_t count = 0; diff_category allowed_category = ctxt->get_allowed_category(); for (string_member_function_sptr_map::const_iterator i = inserted_member_functions_.begin(); i != inserted_member_functions_.end(); ++i) { method_decl_sptr f = i->second, s = i->second; SKIP_MEM_FN_IF_VIRTUALITY_DISALLOWED; diff_sptr diff = compute_diff_for_decls(f, s, ctxt); ctxt->maybe_apply_filters(diff); if (diff->get_category() != NO_CHANGE_CATEGORY && diff->is_filtered_out()) ++count; } return count; } /// Get the number of member functions deletions carried by the current /// diff node that were filtered out. /// /// @return the number of member functions deletions carried by the /// current diff node that were filtered out. size_t class_or_union_diff::priv::count_filtered_deleted_mem_fns (const diff_context_sptr& ctxt) { size_t count = 0; diff_category allowed_category = ctxt->get_allowed_category(); for (string_member_function_sptr_map::const_iterator i = deleted_member_functions_.begin(); i != deleted_member_functions_.end(); ++i) { method_decl_sptr f = i->second, s = i->second; SKIP_MEM_FN_IF_VIRTUALITY_DISALLOWED; diff_sptr diff = compute_diff_for_decls(f, s, ctxt); ctxt->maybe_apply_filters(diff); if (diff->get_category() != NO_CHANGE_CATEGORY && diff->is_filtered_out()) ++count; } return count; } /// Clear the lookup tables useful for reporting. /// /// This function must be updated each time a lookup table is added or /// removed from the class_or_union_diff::priv. void class_or_union_diff::clear_lookup_tables() { priv_->deleted_member_types_.clear(); priv_->inserted_member_types_.clear(); priv_->changed_member_types_.clear(); priv_->deleted_data_members_.clear(); priv_->inserted_data_members_.clear(); priv_->subtype_changed_dm_.clear(); priv_->deleted_member_functions_.clear(); priv_->inserted_member_functions_.clear(); priv_->changed_member_functions_.clear(); priv_->deleted_member_class_tmpls_.clear(); priv_->inserted_member_class_tmpls_.clear(); priv_->changed_member_class_tmpls_.clear(); } /// Tests if the lookup tables are empty. /// /// @return true if the lookup tables are empty, false otherwise. bool class_or_union_diff::lookup_tables_empty(void) const { return (priv_->deleted_member_types_.empty() && priv_->inserted_member_types_.empty() && priv_->changed_member_types_.empty() && priv_->deleted_data_members_.empty() && priv_->inserted_data_members_.empty() && priv_->subtype_changed_dm_.empty() && priv_->inserted_member_functions_.empty() && priv_->deleted_member_functions_.empty() && priv_->changed_member_functions_.empty() && priv_->deleted_member_class_tmpls_.empty() && priv_->inserted_member_class_tmpls_.empty() && priv_->changed_member_class_tmpls_.empty()); } /// If the lookup tables are not yet built, walk the differences and /// fill them. void class_or_union_diff::ensure_lookup_tables_populated(void) const { { edit_script& e = priv_->member_types_changes_; for (vector::const_iterator it = e.deletions().begin(); it != e.deletions().end(); ++it) { unsigned i = it->index(); decl_base_sptr d = get_type_declaration(first_class_or_union()->get_member_types()[i]); class_or_union_sptr record_type = is_class_or_union_type(d); if (record_type && record_type->get_is_declaration_only()) continue; string name = d->get_name(); priv_->deleted_member_types_[name] = d; } for (vector::const_iterator it = e.insertions().begin(); it != e.insertions().end(); ++it) { for (vector::const_iterator iit = it->inserted_indexes().begin(); iit != it->inserted_indexes().end(); ++iit) { unsigned i = *iit; decl_base_sptr d = get_type_declaration(second_class_or_union()->get_member_types()[i]); class_or_union_sptr record_type = is_class_or_union_type(d); if (record_type && record_type->get_is_declaration_only()) continue; string name = d->get_name(); string_decl_base_sptr_map::const_iterator j = priv_->deleted_member_types_.find(name); if (j != priv_->deleted_member_types_.end()) { if (*j->second != *d) priv_->changed_member_types_[name] = compute_diff(j->second, d, context()); priv_->deleted_member_types_.erase(j); } else priv_->inserted_member_types_[name] = d; } } } { edit_script& e = priv_->data_members_changes_; for (vector::const_iterator it = e.deletions().begin(); it != e.deletions().end(); ++it) { unsigned i = it->index(); var_decl_sptr data_member = is_var_decl(first_class_or_union()->get_non_static_data_members()[i]); string name = data_member->get_anon_dm_reliable_name(); ABG_ASSERT(priv_->deleted_data_members_.find(name) == priv_->deleted_data_members_.end()); priv_->deleted_data_members_[name] = data_member; } for (vector::const_iterator it = e.insertions().begin(); it != e.insertions().end(); ++it) { for (vector::const_iterator iit = it->inserted_indexes().begin(); iit != it->inserted_indexes().end(); ++iit) { unsigned i = *iit; decl_base_sptr d = second_class_or_union()->get_non_static_data_members()[i]; var_decl_sptr added_dm = is_var_decl(d); string name = added_dm->get_anon_dm_reliable_name(); ABG_ASSERT(priv_->inserted_data_members_.find(name) == priv_->inserted_data_members_.end()); bool ignore_added_anonymous_data_member = false; if (is_anonymous_data_member(added_dm)) { // // Handle insertion of anonymous data member to // replace existing data members. // // For instance consider this: // struct S // { // int a; // int b; // int c; // };// end struct S // // Where the data members 'a' and 'b' are replaced // by an anonymous data member without changing the // effective bit layout of the structure: // // struct S // { // struct // { // union // { // int a; // char a_1; // }; // union // { // int b; // char b_1; // }; // }; // int c; // }; // end struct S // var_decl_sptr replaced_dm, replacing_dm; bool added_anon_dm_changes_dm = false; // The vector of data members replaced by anonymous // data members. vector dms_replaced_by_anon_dm; // // Let's start collecting the set of data members // which have been replaced by anonymous types in a // harmless way. These are going to be collected into // dms_replaced_by_anon_dm and, ultimately, into // priv_->dms_replaced_by_adms_ // for (string_decl_base_sptr_map::const_iterator it = priv_->deleted_data_members_.begin(); it != priv_->deleted_data_members_.end(); ++it) { // We don't support this pattern for anonymous // data members themselves being replaced. If // that occurs then we'll just report it verbatim. if (is_anonymous_data_member(it->second)) continue; string deleted_dm_name = it->second->get_name(); if ((replacing_dm = find_data_member_from_anonymous_data_member(added_dm, deleted_dm_name))) { // So it looks like replacing_dm might have // replaced the data member which name is // 'deleted_dm_name'. Let's look deeper to be // sure. // // Note that replacing_dm is part (member) of // an anonymous data member that might replace // replaced_dm. // So let's get that replaced data member. replaced_dm = is_var_decl(it->second); size_t replaced_dm_offset = get_data_member_offset(replaced_dm), replacing_dm_offset = get_absolute_data_member_offset(replacing_dm); if (replaced_dm_offset != replacing_dm_offset) { // So the replacing data member and the // replaced data member don't have the // same offset. This is not the pattern we // are looking for. Rather, it looks like // the anonymous data member has *changed* // the data member. added_anon_dm_changes_dm = true; break; } if (replaced_dm->get_type()->get_size_in_bits() == replaced_dm->get_type()->get_size_in_bits()) dms_replaced_by_anon_dm.push_back(replaced_dm); else { added_anon_dm_changes_dm = true; break; } } } // Now walk dms_replaced_by_anon_dm to fill up // priv_->dms_replaced_by_adms_ with the set of data // members replaced by anonymous data members. if (!added_anon_dm_changes_dm && !dms_replaced_by_anon_dm.empty()) { // See if the added data member isn't too big. type_base_sptr added_dm_type = added_dm->get_type(); ABG_ASSERT(added_dm_type); var_decl_sptr new_next_dm = get_next_data_member(second_class_or_union(), added_dm); var_decl_sptr old_next_dm = first_class_or_union()->find_data_member(new_next_dm); if (!old_next_dm || (old_next_dm && (get_absolute_data_member_offset(old_next_dm) == get_absolute_data_member_offset(new_next_dm)))) { // None of the data members that are replaced // by the added union should be considered as // having been deleted. ignore_added_anonymous_data_member = true; for (vector::const_iterator i = dms_replaced_by_anon_dm.begin(); i != dms_replaced_by_anon_dm.end(); ++i) { string n = (*i)->get_name(); priv_->dms_replaced_by_adms_[n] = added_dm; priv_->deleted_data_members_.erase(n); } } } } if (!ignore_added_anonymous_data_member) { // Detect changed data members. // // A changed data member (that we shall name D) is a data // member that satisfies the conditions below: // // 1/ It must have been added. // // 2/ It must have been deleted as well. // // 3/ It there must be a non-empty difference between the // deleted D and the added D. string_decl_base_sptr_map::const_iterator j = priv_->deleted_data_members_.find(name); if (j != priv_->deleted_data_members_.end()) { if (*j->second != *d) { var_decl_sptr old_dm = is_var_decl(j->second); priv_->subtype_changed_dm_[name]= compute_diff(old_dm, added_dm, context()); } priv_->deleted_data_members_.erase(j); } else priv_->inserted_data_members_[name] = d; } } } // Now detect when a data member is deleted from offset N and // another one is added to offset N. In that case, we want to be // able to say that the data member at offset N changed. for (string_decl_base_sptr_map::const_iterator i = priv_->deleted_data_members_.begin(); i != priv_->deleted_data_members_.end(); ++i) { unsigned offset = get_data_member_offset(i->second); priv_->deleted_dm_by_offset_[offset] = i->second; } for (string_decl_base_sptr_map::const_iterator i = priv_->inserted_data_members_.begin(); i != priv_->inserted_data_members_.end(); ++i) { unsigned offset = get_data_member_offset(i->second); priv_->inserted_dm_by_offset_[offset] = i->second; } for (unsigned_decl_base_sptr_map::const_iterator i = priv_->inserted_dm_by_offset_.begin(); i != priv_->inserted_dm_by_offset_.end(); ++i) { unsigned_decl_base_sptr_map::const_iterator j = priv_->deleted_dm_by_offset_.find(i->first); if (j != priv_->deleted_dm_by_offset_.end()) { var_decl_sptr old_dm = is_var_decl(j->second); var_decl_sptr new_dm = is_var_decl(i->second); priv_->changed_dm_[i->first] = compute_diff(old_dm, new_dm, context()); } } for (unsigned_var_diff_sptr_map::const_iterator i = priv_->changed_dm_.begin(); i != priv_->changed_dm_.end(); ++i) { priv_->deleted_dm_by_offset_.erase(i->first); priv_->inserted_dm_by_offset_.erase(i->first); priv_->deleted_data_members_.erase (i->second->first_var()->get_anon_dm_reliable_name()); priv_->inserted_data_members_.erase (i->second->second_var()->get_anon_dm_reliable_name()); } } sort_string_data_member_diff_sptr_map(priv_->subtype_changed_dm_, priv_->sorted_subtype_changed_dm_); sort_unsigned_data_member_diff_sptr_map(priv_->changed_dm_, priv_->sorted_changed_dm_); { edit_script& e = priv_->member_class_tmpls_changes_; for (vector::const_iterator it = e.deletions().begin(); it != e.deletions().end(); ++it) { unsigned i = it->index(); decl_base_sptr d = first_class_or_union()->get_member_class_templates()[i]-> as_class_tdecl(); string name = d->get_name(); ABG_ASSERT(priv_->deleted_member_class_tmpls_.find(name) == priv_->deleted_member_class_tmpls_.end()); priv_->deleted_member_class_tmpls_[name] = d; } for (vector::const_iterator it = e.insertions().begin(); it != e.insertions().end(); ++it) { for (vector::const_iterator iit = it->inserted_indexes().begin(); iit != it->inserted_indexes().end(); ++iit) { unsigned i = *iit; decl_base_sptr d = second_class_or_union()->get_member_class_templates()[i]-> as_class_tdecl(); string name = d->get_name(); ABG_ASSERT(priv_->inserted_member_class_tmpls_.find(name) == priv_->inserted_member_class_tmpls_.end()); string_decl_base_sptr_map::const_iterator j = priv_->deleted_member_class_tmpls_.find(name); if (j != priv_->deleted_member_class_tmpls_.end()) { if (*j->second != *d) priv_->changed_member_types_[name]= compute_diff(j->second, d, context()); priv_->deleted_member_class_tmpls_.erase(j); } else priv_->inserted_member_class_tmpls_[name] = d; } } } sort_string_diff_sptr_map(priv_->changed_member_types_, priv_->sorted_changed_member_types_); } /// Allocate the memory for the priv_ pimpl data member of the @ref /// class_or_union_diff class. void class_or_union_diff::allocate_priv_data() { if (!priv_) priv_.reset(new priv); } /// Constructor for the @ref class_or_union_diff class. /// /// @param first_scope the first @ref class_or_union of the diff node. /// /// @param second_scope the second @ref class_or_union of the diff node. /// /// @param ctxt the context of the diff. class_or_union_diff::class_or_union_diff(class_or_union_sptr first_scope, class_or_union_sptr second_scope, diff_context_sptr ctxt) : type_diff_base(first_scope, second_scope, ctxt) //priv_(new priv) {} /// Getter of the private data of the @ref class_or_union_diff type. /// /// Note that due to an optimization, the private data of @ref /// class_or_union_diff can be shared among several instances of /// class_or_union_diff, so you should never try to access /// class_or_union_diff::priv directly. /// /// When class_or_union_diff::priv is shared, this function returns /// the correct shared one. /// /// @return the (possibly) shared private data of the current instance /// of @ref class_or_union_diff. const class_or_union_diff::priv_ptr& class_or_union_diff::get_priv() const { if (priv_) return priv_; // If the current class_or_union_diff::priv member is empty, then look for // the shared one, from the canonical type. class_or_union_diff *canonical = dynamic_cast(get_canonical_diff()); ABG_ASSERT(canonical); ABG_ASSERT(canonical->priv_); return canonical->priv_; } /// Destructor of class_or_union_diff. class_or_union_diff::~class_or_union_diff() { } /// @return the first @ref class_or_union involved in the diff. class_or_union_sptr class_or_union_diff::first_class_or_union() const {return is_class_or_union_type(first_subject());} /// @return the second @ref class_or_union involved in the diff. class_or_union_sptr class_or_union_diff::second_class_or_union() const {return is_class_or_union_type(second_subject());} /// @return the edit script of the member types of the two @ref /// class_or_union. const edit_script& class_or_union_diff::member_types_changes() const {return get_priv()->member_types_changes_;} /// @return the edit script of the member types of the two @ref /// class_or_union. edit_script& class_or_union_diff::member_types_changes() {return get_priv()->member_types_changes_;} /// @return the edit script of the data members of the two @ref /// class_or_union. const edit_script& class_or_union_diff::data_members_changes() const {return get_priv()->data_members_changes_;} /// @return the edit script of the data members of the two @ref /// class_or_union. edit_script& class_or_union_diff::data_members_changes() {return get_priv()->data_members_changes_;} /// Getter for the data members that got inserted. /// /// @return a map of data members that got inserted. const string_decl_base_sptr_map& class_or_union_diff::inserted_data_members() const {return get_priv()->inserted_data_members_;} /// Getter for the data members that got deleted. /// /// @return a map of data members that got deleted. const string_decl_base_sptr_map& class_or_union_diff::deleted_data_members() const {return get_priv()->deleted_data_members_;} /// @return the edit script of the member functions of the two @ref /// class_or_union. const edit_script& class_or_union_diff::member_fns_changes() const {return get_priv()->member_fns_changes_;} /// Getter for the virtual members functions that have had a change in /// a sub-type, without having a change in their symbol name. /// /// @return a sorted vector of virtual member functions that have a /// sub-type change. const function_decl_diff_sptrs_type& class_or_union_diff::changed_member_fns() const {return get_priv()->sorted_changed_member_functions_;} /// @return the edit script of the member functions of the two /// classes. edit_script& class_or_union_diff::member_fns_changes() {return get_priv()->member_fns_changes_;} /// @return a map of member functions that got deleted. const string_member_function_sptr_map& class_or_union_diff::deleted_member_fns() const {return get_priv()->deleted_member_functions_;} /// @return a map of member functions that got inserted. const string_member_function_sptr_map& class_or_union_diff::inserted_member_fns() const {return get_priv()->inserted_member_functions_;} /// Getter of the sorted vector of data members that got replaced by /// another data member. /// /// @return sorted vector of changed data member. const var_diff_sptrs_type& class_or_union_diff::sorted_changed_data_members() const {return get_priv()->sorted_changed_dm_;} /// Count the number of /filtered/ data members that got replaced by /// another data member. /// /// @return the number of changed data member that got filtered out. size_t class_or_union_diff::count_filtered_changed_data_members(bool local) const {return get_priv()->count_filtered_changed_dm(local);} /// Getter of the sorted vector of data members with a (sub-)type change. /// /// @return sorted vector of changed data member. const var_diff_sptrs_type& class_or_union_diff::sorted_subtype_changed_data_members() const {return get_priv()->sorted_subtype_changed_dm_;} /// Count the number of /filtered/ data members with a sub-type change. /// /// @return the number of changed data member that got filtered out. size_t class_or_union_diff::count_filtered_subtype_changed_data_members(bool local) const {return get_priv()->count_filtered_subtype_changed_dm(local);} /// Get the map of data members that got replaced by anonymous data /// members. /// /// The key of a map entry is the name of the replaced data member and /// the value is the anonymous data member that replaces it. /// /// @return the map of data members replaced by anonymous data /// members. const string_decl_base_sptr_map& class_or_union_diff::data_members_replaced_by_adms() const {return get_priv()->dms_replaced_by_adms_;} /// Get an ordered vector of of data members that got replaced by /// anonymous data members. /// /// This returns a vector of pair of two data members: the one that /// was replaced, and the anonymous data member that replaced it. /// /// @return the sorted vector data members replaced by anonymous data members. const changed_var_sptrs_type& class_or_union_diff::ordered_data_members_replaced_by_adms() const { if (priv_->dms_replaced_by_adms_ordered_.empty()) { for (string_decl_base_sptr_map::const_iterator it = priv_->dms_replaced_by_adms_.begin(); it != priv_->dms_replaced_by_adms_.end(); ++it) { const var_decl_sptr dm = first_class_or_union()->find_data_member(it->first); ABG_ASSERT(dm); changed_var_sptr changed_dm(dm, is_data_member(it->second)); priv_->dms_replaced_by_adms_ordered_.push_back(changed_dm); } sort_changed_data_members(priv_->dms_replaced_by_adms_ordered_); } return priv_->dms_replaced_by_adms_ordered_; } /// @return the edit script of the member function templates of the two /// @ref class_or_union. const edit_script& class_or_union_diff::member_fn_tmpls_changes() const {return get_priv()->member_fn_tmpls_changes_;} /// @return the edit script of the member function templates of the /// two @ref class_or_union. edit_script& class_or_union_diff::member_fn_tmpls_changes() {return get_priv()->member_fn_tmpls_changes_;} /// @return the edit script of the member class templates of the two /// @ref class_or_union. const edit_script& class_or_union_diff::member_class_tmpls_changes() const {return get_priv()->member_class_tmpls_changes_;} /// @return the edit script of the member class templates of the two /// @ref class_or_union. edit_script& class_or_union_diff::member_class_tmpls_changes() {return get_priv()->member_class_tmpls_changes_;} /// Test if the current diff node carries a change. bool class_or_union_diff::has_changes() const {return first_class_or_union() != second_class_or_union();} /// @return the kind of local change carried by the current diff node. /// The value returned is zero if the current node carries no local /// change. enum change_kind class_or_union_diff::has_local_changes() const { ir::change_kind k = ir::NO_CHANGE_KIND; if (!equals(*first_class_or_union(), *second_class_or_union(), &k)) return k & ir::ALL_LOCAL_CHANGES_MASK; return ir::NO_CHANGE_KIND; } /// Report the changes carried by the current @ref class_or_union_diff /// node in a textual format. /// /// @param out the output stream to write the textual report to. /// /// @param indent the number of white space to use as indentation. void class_or_union_diff::report(ostream& out, const string& indent) const { context()->get_reporter()->report(*this, out, indent); } /// Populate the vector of children node of the @ref diff base type /// sub-object of this instance of @ref class_or_union_diff. /// /// The children node can then later be retrieved using /// diff::children_node(). void class_or_union_diff::chain_into_hierarchy() { // data member changes for (var_diff_sptrs_type::const_iterator i = get_priv()->sorted_subtype_changed_dm_.begin(); i != get_priv()->sorted_subtype_changed_dm_.end(); ++i) if (diff_sptr d = *i) append_child_node(d); for (var_diff_sptrs_type::const_iterator i = get_priv()->sorted_changed_dm_.begin(); i != get_priv()->sorted_changed_dm_.end(); ++i) if (diff_sptr d = *i) append_child_node(d); // member types changes for (diff_sptrs_type::const_iterator i = get_priv()->sorted_changed_member_types_.begin(); i != get_priv()->sorted_changed_member_types_.end(); ++i) if (diff_sptr d = *i) append_child_node(d); // member function changes for (function_decl_diff_sptrs_type::const_iterator i = get_priv()->sorted_changed_member_functions_.begin(); i != get_priv()->sorted_changed_member_functions_.end(); ++i) if (diff_sptr d = *i) append_child_node(d); } // // /// Clear the lookup tables useful for reporting. /// /// This function must be updated each time a lookup table is added or /// removed from the class_diff::priv. void class_diff::clear_lookup_tables(void) { priv_->deleted_bases_.clear(); priv_->inserted_bases_.clear(); priv_->changed_bases_.clear(); } /// Tests if the lookup tables are empty. /// /// @return true if the lookup tables are empty, false otherwise. bool class_diff::lookup_tables_empty(void) const { return (priv_->deleted_bases_.empty() && priv_->inserted_bases_.empty() && priv_->changed_bases_.empty()); } /// Find a virtual destructor in a map of member functions /// /// @param map the map of member functions. Note that the key of the /// map is the member function name. The key is the member function. /// /// @return an iterator to the destructor found or, if no virtual destructor /// was found, return map.end() static string_member_function_sptr_map::const_iterator find_virtual_dtor_in_map(const string_member_function_sptr_map& map) { for (string_member_function_sptr_map::const_iterator i = map.begin(); i !=map.end(); ++i) { if (get_member_function_is_dtor(i->second) && get_member_function_is_virtual(i->second)) return i; } return map.end(); } /// If the lookup tables are not yet built, walk the differences and /// fill them. void class_diff::ensure_lookup_tables_populated(void) const { class_or_union_diff::ensure_lookup_tables_populated(); if (!lookup_tables_empty()) return; { edit_script& e = get_priv()->base_changes_; for (vector::const_iterator it = e.deletions().begin(); it != e.deletions().end(); ++it) { unsigned i = it->index(); class_decl::base_spec_sptr b = first_class_decl()->get_base_specifiers()[i]; string name = b->get_base_class()->get_qualified_name(); ABG_ASSERT(get_priv()->deleted_bases_.find(name) == get_priv()->deleted_bases_.end()); get_priv()->deleted_bases_[name] = b; } for (vector::const_iterator it = e.insertions().begin(); it != e.insertions().end(); ++it) { for (vector::const_iterator iit = it->inserted_indexes().begin(); iit != it->inserted_indexes().end(); ++iit) { unsigned i = *iit; class_decl::base_spec_sptr b = second_class_decl()->get_base_specifiers()[i]; string name = b->get_base_class()->get_qualified_name(); ABG_ASSERT(get_priv()->inserted_bases_.find(name) == get_priv()->inserted_bases_.end()); string_base_sptr_map::const_iterator j = get_priv()->deleted_bases_.find(name); if (j != get_priv()->deleted_bases_.end()) { if (j->second != b) get_priv()->changed_bases_[name] = compute_diff(j->second, b, context()); else // The base class changed place. IOW, the base // classes got re-arranged. Let's keep track of the // base classes that moved. get_priv()->moved_bases_.push_back(b); get_priv()->deleted_bases_.erase(j); } else get_priv()->inserted_bases_[name] = b; } } } sort_string_base_sptr_map(get_priv()->deleted_bases_, get_priv()->sorted_deleted_bases_); sort_string_base_sptr_map(get_priv()->inserted_bases_, get_priv()->sorted_inserted_bases_); sort_string_base_diff_sptr_map(get_priv()->changed_bases_, get_priv()->sorted_changed_bases_); { const class_or_union_diff::priv_ptr &p = class_or_union_diff::get_priv(); edit_script& e = p->member_fns_changes_; for (vector::const_iterator it = e.deletions().begin(); it != e.deletions().end(); ++it) { unsigned i = it->index(); method_decl_sptr mem_fn = first_class_decl()->get_virtual_mem_fns()[i]; string name = mem_fn->get_linkage_name(); if (name.empty()) name = mem_fn->get_pretty_representation(); ABG_ASSERT(!name.empty()); if (p->deleted_member_functions_.find(name) != p->deleted_member_functions_.end()) continue; p->deleted_member_functions_[name] = mem_fn; } for (vector::const_iterator it = e.insertions().begin(); it != e.insertions().end(); ++it) { for (vector::const_iterator iit = it->inserted_indexes().begin(); iit != it->inserted_indexes().end(); ++iit) { unsigned i = *iit; method_decl_sptr mem_fn = second_class_decl()->get_virtual_mem_fns()[i]; string name = mem_fn->get_linkage_name(); if (name.empty()) name = mem_fn->get_pretty_representation(); ABG_ASSERT(!name.empty()); if (p->inserted_member_functions_.find(name) != p->inserted_member_functions_.end()) continue; string_member_function_sptr_map::const_iterator j = p->deleted_member_functions_.find(name); if (j != p->deleted_member_functions_.end()) { if (*j->second != *mem_fn) p->changed_member_functions_[name] = compute_diff(static_pointer_cast(j->second), static_pointer_cast(mem_fn), context()); p->deleted_member_functions_.erase(j); } else p->inserted_member_functions_[name] = mem_fn; } } // Now walk the allegedly deleted member functions; check if their // underlying symbols are deleted as well; otherwise, consider // that the member function in question hasn't been deleted. // Also, while walking the deleted member functions, we attend at // a particular cleanup business related to (virtual) C++ // destructors: // // In the binary, there can be at least three types of // destructors, defined in the document // https://itanium-cxx-abi.github.io/cxx-abi/abi.html#definitions: // // 1/ Base object destructor (aka D2 destructor): // // "A function that runs the destructors for non-static data // members of T and non-virtual direct base classes of T. " // // 2/ Complete object destructor (aka D1 destructor): // // "A function that, in addition to the actions required of a // base object destructor, runs the destructors for the // virtual base classes of T." // // 3/ Deleting destructor (aka D0 destructor): // // "A function that, in addition to the actions required of a // complete object destructor, calls the appropriate // deallocation function (i.e,. operator delete) for T." // // With binaries generated by GCC, these destructors might be ELF // clones of each others, meaning, their ELF symbols can be // aliases. // // Also, note that because the actual destructor invoked by user // code is virtual, it's invoked through the vtable. So the // presence of the underlying D0, D1, D2 in the binary might vary // without that variation being an ABI issue, provided that the // destructor invoked through the vtable is present. // // So, a particular virtual destructor implementation for a class // might disapear and be replaced by another one in a subsequent // version of the binary. If all versions of the binary have an // actual virtual destructor, things might be considered fine. vector to_delete; corpus_sptr f = context()->get_first_corpus(), s = context()->get_second_corpus(); if (s) for (string_member_function_sptr_map::const_iterator i = deleted_member_fns().begin(); i != deleted_member_fns().end(); ++i) { if (get_member_function_is_virtual(i->second)) { if (get_member_function_is_dtor(i->second)) { // If a particular virtual destructor is deleted, // but the new binary still have a virtual // destructor for that class we consider that things // are fine. For instance, in the // tests/data/test-diff-pkg/tbb-4.1-9.20130314.fc22.x86_64--tbb-4.3-3.20141204.fc23.x86_64-report-0.txt // test, a new D4 destructor replaces the old ones. // But because the virtual destructor is still // there, this is not an ABI issue. So let's detect // this case. auto it = find_virtual_dtor_in_map(p->inserted_member_functions_); if (it != p->inserted_member_functions_.end()) { // So the deleted virtual destructor is not // really deleted, because a proper virtual // destructor was added to the new version. // Let's remove the deleted/added virtual // destructor then. string name = (!i->second->get_linkage_name().empty()) ? i->second->get_linkage_name() : i->second->get_pretty_representation(); to_delete.push_back(name); p->inserted_member_functions_.erase(it); } } continue; } // We assume that all non-virtual member functions functions // we look at here have ELF symbols. if (!i->second->get_symbol() || s->lookup_function_symbol(*i->second->get_symbol())) to_delete.push_back(i->first); } for (vector::const_iterator i = to_delete.begin(); i != to_delete.end(); ++i) p->deleted_member_functions_.erase(*i); // Do something similar for added functions. to_delete.clear(); if (f) for (string_member_function_sptr_map::const_iterator i = inserted_member_fns().begin(); i != inserted_member_fns().end(); ++i) { if (get_member_function_is_virtual(i->second)) continue; // We assume that all non-virtual member functions functions // we look at here have ELF symbols. if (!i->second->get_symbol() || f->lookup_function_symbol(*i->second->get_symbol())) to_delete.push_back(i->first); } for (vector::const_iterator i = to_delete.begin(); i != to_delete.end(); ++i) p->inserted_member_functions_.erase(*i); sort_string_member_function_sptr_map(p->deleted_member_functions_, p->sorted_deleted_member_functions_); sort_string_member_function_sptr_map(p->inserted_member_functions_, p->sorted_inserted_member_functions_); sort_string_virtual_member_function_diff_sptr_map (p->changed_member_functions_, p->sorted_changed_member_functions_); } } /// Allocate the memory for the priv_ pimpl data member of the @ref /// class_diff class. void class_diff::allocate_priv_data() { class_or_union_diff::allocate_priv_data(); if (!priv_) priv_.reset(new priv); } /// Test whether a given base class has changed. A base class has /// changed if it's in both in deleted *and* inserted bases. /// ///@param d the declaration for the base class to consider. /// /// @return the new base class if the given base class has changed, or /// NULL if it hasn't. class_decl::base_spec_sptr class_diff::priv::base_has_changed(class_decl::base_spec_sptr d) const { string qname = d->get_base_class()->get_qualified_name(); string_base_diff_sptr_map::const_iterator it = changed_bases_.find(qname); return (it == changed_bases_.end()) ? class_decl::base_spec_sptr() : it->second->second_base(); } /// Count the number of bases classes whose changes got filtered out. /// /// @return the number of bases classes whose changes got filtered /// out. size_t class_diff::priv::count_filtered_bases() { size_t num_filtered = 0; for (base_diff_sptrs_type::const_iterator i = sorted_changed_bases_.begin(); i != sorted_changed_bases_.end(); ++i) { diff_sptr diff = *i; if (diff && diff->is_filtered_out()) ++num_filtered; } return num_filtered; } /// Populate the vector of children node of the @ref diff base type /// sub-object of this instance of @ref class_diff. /// /// The children node can then later be retrieved using /// diff::children_node(). void class_diff::chain_into_hierarchy() { class_or_union_diff::chain_into_hierarchy(); // base class changes. for (base_diff_sptrs_type::const_iterator i = get_priv()->sorted_changed_bases_.begin(); i != get_priv()->sorted_changed_bases_.end(); ++i) if (diff_sptr d = *i) append_child_node(d); } /// Constructor of class_diff /// /// @param first_scope the first class of the diff. /// /// @param second_scope the second class of the diff. /// /// @param ctxt the diff context to use. class_diff::class_diff(class_decl_sptr first_scope, class_decl_sptr second_scope, diff_context_sptr ctxt) : class_or_union_diff(first_scope, second_scope, ctxt) // We don't initialize the priv_ data member here. This is an // optimization to reduce memory consumption (and also execution // time) for cases where there are a lot of instances of // class_diff in the same equivalence class. In compute_diff(), // the priv_ is set to the priv_ of the canonical diff node. // See PR libabigail/17948. {} class_diff::~class_diff() {} /// Getter of the private data of the @ref class_diff type. /// /// Note that due to an optimization, the private data of @ref /// class_diff can be shared among several instances of class_diff, so /// you should never try to access class_diff::priv directly. /// /// When class_diff::priv is shared, this function returns the correct /// shared one. /// /// @return the (possibly) shared private data of the current instance /// of class_diff. const class_diff::priv_ptr& class_diff::get_priv() const { if (priv_) return priv_; // If the current class_diff::priv member is empty, then look for // the shared one, from the canonical type. class_diff *canonical = dynamic_cast(get_canonical_diff()); ABG_ASSERT(canonical); ABG_ASSERT(canonical->priv_); return canonical->priv_; } /// @return the pretty representation of the current instance of @ref /// class_diff. const string& class_diff::get_pretty_representation() const { if (diff::priv_->pretty_representation_.empty()) { std::ostringstream o; o << "class_diff[" << first_subject()->get_pretty_representation() << ", " << second_subject()->get_pretty_representation() << "]"; diff::priv_->pretty_representation_ = o.str(); } return diff::priv_->pretty_representation_; } /// Return true iff the current diff node carries a change. /// /// @return true iff the current diff node carries a change. bool class_diff::has_changes() const {return (first_class_decl() != second_class_decl());} /// @return the kind of local change carried by the current diff node. /// The value returned is zero if the current node carries no local /// change. enum change_kind class_diff::has_local_changes() const { ir::change_kind k = ir::NO_CHANGE_KIND; if (!equals(*first_class_decl(), *second_class_decl(), &k)) return k & ir::ALL_LOCAL_CHANGES_MASK; return ir::NO_CHANGE_KIND; } /// @return the first class invoveld in the diff. shared_ptr class_diff::first_class_decl() const {return dynamic_pointer_cast(first_subject());} /// Getter of the second class involved in the diff. /// /// @return the second class invoveld in the diff shared_ptr class_diff::second_class_decl() const {return dynamic_pointer_cast(second_subject());} /// @return the edit script of the bases of the two classes. const edit_script& class_diff::base_changes() const {return get_priv()->base_changes_;} /// Getter for the deleted base classes of the diff. /// /// @return a map containing the deleted base classes, keyed with /// their pretty representation. const string_base_sptr_map& class_diff::deleted_bases() const {return get_priv()->deleted_bases_;} /// Getter for the inserted base classes of the diff. /// /// @return a map containing the inserted base classes, keyed with /// their pretty representation. const string_base_sptr_map& class_diff::inserted_bases() const {return get_priv()->inserted_bases_;} /// Getter for the changed base classes of the diff. /// /// @return a sorted vector containing the changed base classes const base_diff_sptrs_type& class_diff::changed_bases() {return get_priv()->sorted_changed_bases_;} /// Getter for the vector of bases that "moved". /// That is, the vector of base types which position changed. If this /// vector is not empty, it means the bases of the underlying class /// type got re-ordered. /// /// @return the vector of bases that moved. const vector& class_diff::moved_bases() const {return get_priv()->moved_bases_;} /// @return the edit script of the bases of the two classes. edit_script& class_diff::base_changes() {return get_priv()->base_changes_;} /// Produce a basic report about the changes between two class_decl. /// /// @param out the output stream to report the changes to. /// /// @param indent the string to use as an indentation prefix in the /// report. void class_diff::report(ostream& out, const string& indent) const { context()->get_reporter()->report(*this, out, indent); } /// Compute the set of changes between two instances of class_decl. /// /// Note that the two types must have been created in the same @ref /// environment, otherwise, this function aborts. /// /// @param first the first class_decl to consider. /// /// @param second the second class_decl to consider. /// /// @return changes the resulting changes. /// /// @param ctxt the diff context to use. class_diff_sptr compute_diff(const class_decl_sptr first, const class_decl_sptr second, diff_context_sptr ctxt) { class_decl_sptr f = is_class_type(look_through_decl_only_class(first)), s = is_class_type(look_through_decl_only_class(second)); class_diff_sptr changes(new class_diff(f, s, ctxt)); ctxt->initialize_canonical_diff(changes); ABG_ASSERT(changes->get_canonical_diff()); if (!ctxt->get_canonical_diff_for(first, second)) { // Either first or second is a decl-only class; let's set the // canonical diff here in that case. diff_sptr canonical_diff = ctxt->get_canonical_diff_for(changes); ABG_ASSERT(canonical_diff); ctxt->set_canonical_diff_for(first, second, canonical_diff); } // Ok, so this is an optimization. Do not freak out if it looks // weird, because, well, it does look weird. This speeds up // greatly, for instance, the test case given at PR // libabigail/17948. // // We are setting the private data of the new instance of class_diff // (which is 'changes') to the private data of its canonical // instance. That is, we are sharing the private data of 'changes' // with the private data of its canonical instance to consume less // memory in cases where the equivalence class of 'changes' is huge. // // But if changes is its own canonical instance, then we initialize // its private data properly if (is_class_diff(changes->get_canonical_diff()) == changes.get()) // changes is its own canonical instance, so it gets a brand new // private data. changes->allocate_priv_data(); else { // changes has a non-empty equivalence class so it's going to // share its private data with its canonical instance. Next // time class_diff::get_priv() is invoked, it's going to return // the shared private data of the canonical instance. return changes; } // Compare base specs compute_diff(f->get_base_specifiers().begin(), f->get_base_specifiers().end(), s->get_base_specifiers().begin(), s->get_base_specifiers().end(), changes->base_changes()); // Do *not* compare member types because it generates lots of noise // and I doubt it's really useful. #if 0 compute_diff(f->get_member_types().begin(), f->get_member_types().end(), s->get_member_types().begin(), s->get_member_types().end(), changes->member_types_changes()); #endif // Compare data member compute_diff(f->get_non_static_data_members().begin(), f->get_non_static_data_members().end(), s->get_non_static_data_members().begin(), s->get_non_static_data_members().end(), changes->data_members_changes()); // Compare virtual member functions compute_diff(f->get_virtual_mem_fns().begin(), f->get_virtual_mem_fns().end(), s->get_virtual_mem_fns().begin(), s->get_virtual_mem_fns().end(), changes->member_fns_changes()); // Compare member function templates compute_diff(f->get_member_function_templates().begin(), f->get_member_function_templates().end(), s->get_member_function_templates().begin(), s->get_member_function_templates().end(), changes->member_fn_tmpls_changes()); // Likewise, do not compare member class templates #if 0 compute_diff(f->get_member_class_templates().begin(), f->get_member_class_templates().end(), s->get_member_class_templates().begin(), s->get_member_class_templates().end(), changes->member_class_tmpls_changes()); #endif changes->ensure_lookup_tables_populated(); return changes; } // // /// Populate the vector of children node of the @ref diff base type /// sub-object of this instance of @ref base_diff. /// /// The children node can then later be retrieved using /// diff::children_node(). void base_diff::chain_into_hierarchy() {append_child_node(get_underlying_class_diff());} /// @param first the first base spec to consider. /// /// @param second the second base spec to consider. /// /// @param ctxt the context of the diff. Note that this context /// object must stay alive at least during the life time of the /// current instance of @ref base_diff. Otherwise memory corruption /// issues occur. base_diff::base_diff(class_decl::base_spec_sptr first, class_decl::base_spec_sptr second, class_diff_sptr underlying, diff_context_sptr ctxt) : diff(first, second, ctxt), priv_(new priv(underlying)) {} /// Getter for the first base spec of the diff object. /// /// @return the first base specifier for the diff object. class_decl::base_spec_sptr base_diff::first_base() const {return dynamic_pointer_cast(first_subject());} /// Getter for the second base spec of the diff object. /// /// @return the second base specifier for the diff object. class_decl::base_spec_sptr base_diff::second_base() const {return dynamic_pointer_cast(second_subject());} /// Getter for the diff object for the diff of the underlying base /// classes. /// /// @return the diff object for the diff of the underlying base /// classes. const class_diff_sptr base_diff::get_underlying_class_diff() const {return priv_->underlying_class_diff_;} /// Setter for the diff object for the diff of the underlyng base /// classes. /// /// @param d the new diff object for the diff of the underlying base /// classes. void base_diff::set_underlying_class_diff(class_diff_sptr d) {priv_->underlying_class_diff_ = d;} /// @return the pretty representation for the current instance of @ref /// base_diff. const string& base_diff::get_pretty_representation() const { if (diff::priv_->pretty_representation_.empty()) { std::ostringstream o; o << "base_diff[" << first_subject()->get_pretty_representation() << ", " << second_subject()->get_pretty_representation() << "]"; diff::priv_->pretty_representation_ = o.str(); } return diff::priv_->pretty_representation_; } /// Return true iff the current diff node carries a change. /// /// Return true iff the current diff node carries a change. bool base_diff::has_changes() const {return first_base() != second_base();} /// @return the kind of local change carried by the current diff node. /// The value returned is zero if the current node carries no local /// change. enum change_kind base_diff::has_local_changes() const { ir::change_kind k = ir::NO_CHANGE_KIND; if (!equals(*first_base(), *second_base(), &k)) return k & ir::ALL_LOCAL_CHANGES_MASK; return ir::NO_CHANGE_KIND; } /// Generates a report for the current instance of base_diff. /// /// @param out the output stream to send the report to. /// /// @param indent the string to use for indentation. void base_diff::report(ostream& out, const string& indent) const { context()->get_reporter()->report(*this, out, indent); } /// Constructs the diff object representing a diff between two base /// class specifications. /// /// Note that the two artifacts must have been created in the same /// @ref environment, otherwise, this function aborts. /// /// @param first the first base class specification. /// /// @param second the second base class specification. /// /// @param ctxt the content of the diff. /// /// @return the resulting diff object. base_diff_sptr compute_diff(const class_decl::base_spec_sptr first, const class_decl::base_spec_sptr second, diff_context_sptr ctxt) { class_diff_sptr cl = compute_diff(first->get_base_class(), second->get_base_class(), ctxt); base_diff_sptr changes(new base_diff(first, second, cl, ctxt)); ctxt->initialize_canonical_diff(changes); return changes; } // // /// Clear the lookup tables useful for reporting. /// /// This function must be updated each time a lookup table is added or /// removed from the union_diff::priv. void union_diff::clear_lookup_tables(void) {class_or_union_diff::clear_lookup_tables();} /// Tests if the lookup tables are empty. /// /// @return true if the lookup tables are empty, false otherwise. bool union_diff::lookup_tables_empty(void) const {return class_or_union_diff::lookup_tables_empty();} /// If the lookup tables are not yet built, walk the differences and /// fill them. void union_diff::ensure_lookup_tables_populated(void) const {class_or_union_diff::ensure_lookup_tables_populated();} /// Allocate the memory for the priv_ pimpl data member of the @ref /// union_diff class. void union_diff::allocate_priv_data() { class_or_union_diff::allocate_priv_data(); } /// Constructor for the @ref union_diff type. /// /// @param first_union the first object of the comparison. /// /// @param second_union the second object of the comparison. /// /// @param ctxt the context of the comparison. union_diff::union_diff(union_decl_sptr first_union, union_decl_sptr second_union, diff_context_sptr ctxt) : class_or_union_diff(first_union, second_union, ctxt) {} /// Destructor of the union_diff node. union_diff::~union_diff() {} /// @return the first object of the comparison. union_decl_sptr union_diff::first_union_decl() const {return is_union_type(first_subject());} /// @return the second object of the comparison. union_decl_sptr union_diff::second_union_decl() const {return is_union_type(second_subject());} /// @return the pretty representation of the current diff node. const string& union_diff::get_pretty_representation() const { if (diff::priv_->pretty_representation_.empty()) { std::ostringstream o; o << "union_diff[" << first_subject()->get_pretty_representation() << ", " << second_subject()->get_pretty_representation() << "]"; diff::priv_->pretty_representation_ = o.str(); } return diff::priv_->pretty_representation_; } /// Report the changes carried by the current @ref union_diff node in /// a textual format. /// /// @param out the output stream to write the textual report to. /// /// @param indent the number of white space to use as indentation. void union_diff::report(ostream& out, const string& indent) const { context()->get_reporter()->report(*this, out, indent); } /// Compute the difference between two @ref union_decl types. /// /// Note that the two types must hav been created in the same /// environment, otherwise, this function aborts. /// /// @param first the first @ref union_decl to consider. /// /// @param second the second @ref union_decl to consider. /// /// @param ctxt the context of the diff to use. union_diff_sptr compute_diff(const union_decl_sptr first, const union_decl_sptr second, diff_context_sptr ctxt) { union_diff_sptr changes(new union_diff(first, second, ctxt)); ctxt->initialize_canonical_diff(changes); ABG_ASSERT(changes->get_canonical_diff()); // Ok, so this is an optimization. Do not freak out if it looks // weird, because, well, it does look weird. This speeds up // greatly, for instance, the test case given at PR // libabigail/17948. // // We are setting the private data of the new instance of class_diff // (which is 'changes') to the private data of its canonical // instance. That is, we are sharing the private data of 'changes' // with the private data of its canonical instance to consume less // memory in cases where the equivalence class of 'changes' is huge. // // But if changes is its own canonical instance, then we initialize // its private data properly. if (is_union_diff(changes->get_canonical_diff()) == changes.get()) // changes is its own canonical instance, so it gets a brand new // private data. changes->allocate_priv_data(); else { // changes has a non-empty equivalence class so it's going to // share its private data with its canonical instance. Next // time class_diff::get_priv() is invoked, it's going to return // the shared private data of the canonical instance. return changes; } // Compare data member compute_diff(first->get_non_static_data_members().begin(), first->get_non_static_data_members().end(), second->get_non_static_data_members().begin(), second->get_non_static_data_members().end(), changes->data_members_changes()); #if 0 // Compare member functions compute_diff(first->get_mem_fns().begin(), first->get_mem_fns().end(), second->get_mem_fns().begin(), second->get_mem_fns().end(), changes->member_fns_changes()); // Compare member function templates compute_diff(first->get_member_function_templates().begin(), first->get_member_function_templates().end(), second->get_member_function_templates().begin(), second->get_member_function_templates().end(), changes->member_fn_tmpls_changes()); #endif changes->ensure_lookup_tables_populated(); return changes; } // // /// Clear the lookup tables that are useful for reporting. /// /// This function must be updated each time a lookup table is added or /// removed. void scope_diff::clear_lookup_tables() { priv_->deleted_types_.clear(); priv_->deleted_decls_.clear(); priv_->inserted_types_.clear(); priv_->inserted_decls_.clear(); priv_->changed_types_.clear(); priv_->changed_decls_.clear(); priv_->removed_types_.clear(); priv_->removed_decls_.clear(); priv_->added_types_.clear(); priv_->added_decls_.clear(); } /// Tests if the lookup tables are empty. /// /// This function must be updated each time a lookup table is added or /// removed. /// /// @return true iff all the lookup tables are empty. bool scope_diff::lookup_tables_empty() const { return (priv_->deleted_types_.empty() && priv_->deleted_decls_.empty() && priv_->inserted_types_.empty() && priv_->inserted_decls_.empty() && priv_->changed_types_.empty() && priv_->changed_decls_.empty() && priv_->removed_types_.empty() && priv_->removed_decls_.empty() && priv_->added_types_.empty() && priv_->added_decls_.empty()); } /// If the lookup tables are not yet built, walk the member_changes_ /// member and fill the lookup tables. void scope_diff::ensure_lookup_tables_populated() { if (!lookup_tables_empty()) return; edit_script& e = priv_->member_changes_; // Populate deleted types & decls lookup tables. for (vector::const_iterator i = e.deletions().begin(); i != e.deletions().end(); ++i) { decl_base_sptr decl = deleted_member_at(i); string qname = decl->get_qualified_name(); if (is_type(decl)) { class_decl_sptr klass_decl = dynamic_pointer_cast(decl); if (klass_decl && klass_decl->get_is_declaration_only()) continue; ABG_ASSERT(priv_->deleted_types_.find(qname) == priv_->deleted_types_.end()); priv_->deleted_types_[qname] = decl; } else { ABG_ASSERT(priv_->deleted_decls_.find(qname) == priv_->deleted_decls_.end()); priv_->deleted_decls_[qname] = decl; } } // Populate inserted types & decls as well as chagned types & decls // lookup tables. for (vector::const_iterator it = e.insertions().begin(); it != e.insertions().end(); ++it) { for (vector::const_iterator i = it->inserted_indexes().begin(); i != it->inserted_indexes().end(); ++i) { decl_base_sptr decl = inserted_member_at(i); string qname = decl->get_qualified_name(); if (is_type(decl)) { class_decl_sptr klass_decl = dynamic_pointer_cast(decl); if (klass_decl && klass_decl->get_is_declaration_only()) continue; ABG_ASSERT(priv_->inserted_types_.find(qname) == priv_->inserted_types_.end()); string_decl_base_sptr_map::const_iterator j = priv_->deleted_types_.find(qname); if (j != priv_->deleted_types_.end()) { if (*j->second != *decl) priv_->changed_types_[qname] = compute_diff(j->second, decl, context()); priv_->deleted_types_.erase(j); } else priv_->inserted_types_[qname] = decl; } else { ABG_ASSERT(priv_->inserted_decls_.find(qname) == priv_->inserted_decls_.end()); string_decl_base_sptr_map::const_iterator j = priv_->deleted_decls_.find(qname); if (j != priv_->deleted_decls_.end()) { if (*j->second != *decl) priv_->changed_decls_[qname] = compute_diff(j->second, decl, context()); priv_->deleted_decls_.erase(j); } else priv_->inserted_decls_[qname] = decl; } } } sort_string_diff_sptr_map(priv_->changed_decls_, priv_->sorted_changed_decls_); sort_string_diff_sptr_map(priv_->changed_types_, priv_->sorted_changed_types_); // Populate removed types/decls lookup tables for (string_decl_base_sptr_map::const_iterator i = priv_->deleted_types_.begin(); i != priv_->deleted_types_.end(); ++i) { string_decl_base_sptr_map::const_iterator r = priv_->inserted_types_.find(i->first); if (r == priv_->inserted_types_.end()) priv_->removed_types_[i->first] = i->second; } for (string_decl_base_sptr_map::const_iterator i = priv_->deleted_decls_.begin(); i != priv_->deleted_decls_.end(); ++i) { string_decl_base_sptr_map::const_iterator r = priv_->inserted_decls_.find(i->first); if (r == priv_->inserted_decls_.end()) priv_->removed_decls_[i->first] = i->second; } // Populate added types/decls. for (string_decl_base_sptr_map::const_iterator i = priv_->inserted_types_.begin(); i != priv_->inserted_types_.end(); ++i) { string_decl_base_sptr_map::const_iterator r = priv_->deleted_types_.find(i->first); if (r == priv_->deleted_types_.end()) priv_->added_types_[i->first] = i->second; } for (string_decl_base_sptr_map::const_iterator i = priv_->inserted_decls_.begin(); i != priv_->inserted_decls_.end(); ++i) { string_decl_base_sptr_map::const_iterator r = priv_->deleted_decls_.find(i->first); if (r == priv_->deleted_decls_.end()) priv_->added_decls_[i->first] = i->second; } } /// Populate the vector of children node of the @ref diff base type /// sub-object of this instance of @ref scope_diff. /// /// The children node can then later be retrieved using /// diff::children_node(). void scope_diff::chain_into_hierarchy() { for (diff_sptrs_type::const_iterator i = changed_types().begin(); i != changed_types().end(); ++i) if (*i) append_child_node(*i); for (diff_sptrs_type::const_iterator i = changed_decls().begin(); i != changed_decls().end(); ++i) if (*i) append_child_node(*i); } /// Constructor for scope_diff /// /// @param first_scope the first scope to consider for the diff. /// /// @param second_scope the second scope to consider for the diff. /// /// @param ctxt the diff context to use. Note that this context /// object must stay alive at least during the life time of the /// current instance of @ref scope_diff. Otherwise memory corruption /// issues occur. scope_diff::scope_diff(scope_decl_sptr first_scope, scope_decl_sptr second_scope, diff_context_sptr ctxt) : diff(first_scope, second_scope, ctxt), priv_(new priv) {} /// Getter for the first scope of the diff. /// /// @return the first scope of the diff. const scope_decl_sptr scope_diff::first_scope() const {return dynamic_pointer_cast(first_subject());} /// Getter for the second scope of the diff. /// /// @return the second scope of the diff. const scope_decl_sptr scope_diff::second_scope() const {return dynamic_pointer_cast(second_subject());} /// Accessor of the edit script of the members of a scope. /// /// This edit script is computed using the equality operator that /// applies to shared_ptr. /// /// That has interesting consequences. For instance, consider two /// scopes S0 and S1. S0 contains a class C0 and S1 contains a class /// S0'. C0 and C0' have the same qualified name, but have different /// members. The edit script will consider that C0 has been deleted /// from S0 and that S0' has been inserted. This is a low level /// canonical representation of the changes; a higher level /// representation would give us a simpler way to say "the class C0 /// has been modified into C0'". But worry not. We do have such /// higher representation as well; that is what changed_types() and /// changed_decls() is for. /// /// @return the edit script of the changes encapsulatd in this /// instance of scope_diff. const edit_script& scope_diff::member_changes() const {return priv_->member_changes_;} /// Accessor of the edit script of the members of a scope. /// /// This edit script is computed using the equality operator that /// applies to shared_ptr. /// /// That has interesting consequences. For instance, consider two /// scopes S0 and S1. S0 contains a class C0 and S1 contains a class /// S0'. C0 and C0' have the same qualified name, but have different /// members. The edit script will consider that C0 has been deleted /// from S0 and that S0' has been inserted. This is a low level /// canonical representation of the changes; a higher level /// representation would give us a simpler way to say "the class C0 /// has been modified into C0'". But worry not. We do have such /// higher representation as well; that is what changed_types() and /// changed_decls() is for. /// /// @return the edit script of the changes encapsulatd in this /// instance of scope_diff. edit_script& scope_diff::member_changes() {return priv_->member_changes_;} /// Accessor that eases the manipulation of the edit script associated /// to this instance. It returns the scope member that is reported /// (in the edit script) as deleted at a given index. /// /// @param i the index (in the edit script) of an element of the first /// scope that has been reported as being delete. /// /// @return the scope member that has been reported by the edit script /// as being deleted at index i. const decl_base_sptr scope_diff::deleted_member_at(unsigned i) const { scope_decl_sptr scope = dynamic_pointer_cast(first_subject()); return scope->get_member_decls()[i]; } /// Accessor that eases the manipulation of the edit script associated /// to this instance. It returns the scope member (of the first scope /// of this diff instance) that is reported (in the edit script) as /// deleted at a given iterator. /// /// @param i the iterator of an element of the first scope that has /// been reported as being delete. /// /// @return the scope member of the first scope of this diff that has /// been reported by the edit script as being deleted at iterator i. const decl_base_sptr scope_diff::deleted_member_at(vector::const_iterator i) const {return deleted_member_at(i->index());} /// Accessor that eases the manipulation of the edit script associated /// to this instance. It returns the scope member (of the second /// scope of this diff instance) that is reported as being inserted /// from a given index. /// /// @param i the index of an element of the second scope this diff /// that has been reported by the edit script as being inserted. /// /// @return the scope member of the second scope of this diff that has /// been reported as being inserted from index i. const decl_base_sptr scope_diff::inserted_member_at(unsigned i) { scope_decl_sptr scope = dynamic_pointer_cast(second_subject()); return scope->get_member_decls()[i]; } /// Accessor that eases the manipulation of the edit script associated /// to this instance. It returns the scope member (of the second /// scope of this diff instance) that is reported as being inserted /// from a given iterator. /// /// @param i the iterator of an element of the second scope this diff /// that has been reported by the edit script as being inserted. /// /// @return the scope member of the second scope of this diff that has /// been reported as being inserted from iterator i. const decl_base_sptr scope_diff::inserted_member_at(vector::const_iterator i) {return inserted_member_at(*i);} /// @return a sorted vector of the types which content has changed /// from the first scope to the other. const diff_sptrs_type& scope_diff::changed_types() const {return priv_->sorted_changed_types_;} /// @return a sorted vector of the decls which content has changed /// from the first scope to the other. const diff_sptrs_type& scope_diff::changed_decls() const {return priv_->sorted_changed_decls_;} const string_decl_base_sptr_map& scope_diff::removed_types() const {return priv_->removed_types_;} const string_decl_base_sptr_map& scope_diff::removed_decls() const {return priv_->removed_decls_;} const string_decl_base_sptr_map& scope_diff::added_types() const {return priv_->added_types_;} const string_decl_base_sptr_map& scope_diff::added_decls() const {return priv_->added_decls_;} /// @return the pretty representation for the current instance of @ref /// scope_diff. const string& scope_diff::get_pretty_representation() const { if (diff::priv_->pretty_representation_.empty()) { std::ostringstream o; o << "scope_diff[" << first_subject()->get_pretty_representation() << ", " << second_subject()->get_pretty_representation() << "]"; diff::priv_->pretty_representation_ = o.str(); } return diff::priv_->pretty_representation_; } /// Return true iff the current diff node carries a change. /// /// Return true iff the current diff node carries a change. bool scope_diff::has_changes() const { // TODO: add the number of really removed/added stuff. return changed_types().size() + changed_decls().size(); } /// @return the kind of local change carried by the current diff node. /// The value returned is zero if the current node carries no local /// change. enum change_kind scope_diff::has_local_changes() const { ir::change_kind k = ir::NO_CHANGE_KIND; if (!equals(*first_scope(), *second_scope(), &k)) return k & ir::ALL_LOCAL_CHANGES_MASK; return ir::NO_CHANGE_KIND; } /// Report the changes of one scope against another. /// /// @param out the out stream to report the changes to. /// /// @param indent the string to use for indentation. void scope_diff::report(ostream& out, const string& indent) const { context()->get_reporter()->report(*this, out, indent); } /// Compute the diff between two scopes. /// /// Note that the two decls must have been created in the same @ref /// environment, otherwise, this function aborts. /// /// @param first the first scope to consider in computing the diff. /// /// @param second the second scope to consider in the diff /// computation. The second scope is diffed against the first scope. /// /// @param d a pointer to the diff object to populate with the /// computed diff. /// /// @return return the populated \a d parameter passed to this /// function. /// /// @param ctxt the diff context to use. scope_diff_sptr compute_diff(const scope_decl_sptr first, const scope_decl_sptr second, scope_diff_sptr d, diff_context_sptr ctxt) { ABG_ASSERT(d->first_scope() == first && d->second_scope() == second); compute_diff(first->get_member_decls().begin(), first->get_member_decls().end(), second->get_member_decls().begin(), second->get_member_decls().end(), d->member_changes()); d->ensure_lookup_tables_populated(); d->context(ctxt); return d; } /// Compute the diff between two scopes. /// /// Note that the two decls must have been created in the same @ref /// environment, otherwise, this function aborts. /// /// @param first_scope the first scope to consider in computing the diff. /// /// @param second_scope the second scope to consider in the diff /// computation. The second scope is diffed against the first scope. /// /// @param ctxt the diff context to use. /// /// @return return the resulting diff scope_diff_sptr compute_diff(const scope_decl_sptr first_scope, const scope_decl_sptr second_scope, diff_context_sptr ctxt) { scope_diff_sptr d(new scope_diff(first_scope, second_scope, ctxt)); d = compute_diff(first_scope, second_scope, d, ctxt); ctxt->initialize_canonical_diff(d); return d; } // // /// Constructor for the fn_parm_diff type. /// /// @param first the first subject of the diff. /// /// @param second the second subject of the diff. /// /// @param ctxt the context of the diff. Note that this context /// object must stay alive at least during the life time of the /// current instance of @ref fn_parm_diff. Otherwise memory /// corruption issues occur. fn_parm_diff::fn_parm_diff(const function_decl::parameter_sptr first, const function_decl::parameter_sptr second, diff_context_sptr ctxt) : decl_diff_base(first, second, ctxt), priv_(new priv) { ABG_ASSERT(first->get_index() == second->get_index()); priv_->type_diff = compute_diff(first->get_type(), second->get_type(), ctxt); ABG_ASSERT(priv_->type_diff); } /// Getter for the first subject of this diff node. /// /// @return the first function_decl::parameter_sptr subject of this /// diff node. const function_decl::parameter_sptr fn_parm_diff::first_parameter() const {return dynamic_pointer_cast(first_subject());} /// Getter for the second subject of this diff node. /// /// @return the second function_decl::parameter_sptr subject of this /// diff node. const function_decl::parameter_sptr fn_parm_diff::second_parameter() const {return dynamic_pointer_cast(second_subject());} /// Getter for the diff representing the changes on the type of the /// function parameter involved in the current instance of @ref /// fn_parm_diff. /// /// @return a diff_sptr representing the changes on the type of the /// function parameter we are interested in. diff_sptr fn_parm_diff::type_diff() const {return priv_->type_diff;} /// Build and return a textual representation of the current instance /// of @ref fn_parm_diff. /// /// @return the string representing the current instance of /// fn_parm_diff. const string& fn_parm_diff::get_pretty_representation() const { if (diff::priv_->pretty_representation_.empty()) { std::ostringstream o; o << "function_parameter_diff[" << first_subject()->get_pretty_representation() << ", " << second_subject()->get_pretty_representation() << "]"; diff::priv_->pretty_representation_ = o.str(); } return diff::priv_->pretty_representation_; } /// Return true iff the current diff node carries a change. /// /// @return true iff the current diff node carries a change. bool fn_parm_diff::has_changes() const {return *first_parameter() != *second_parameter();} /// Check if the current diff node carries a local change. /// /// @return the kind of local change carried by the current diff node. /// The value returned is zero if the current node carries no local /// change. enum change_kind fn_parm_diff::has_local_changes() const { ir::change_kind k = ir::NO_CHANGE_KIND; if (!equals(*first_parameter(), *second_parameter(), &k)) return k & ir::ALL_LOCAL_CHANGES_MASK; return ir::NO_CHANGE_KIND; } /// Emit a textual report about the current fn_parm_diff instance. /// /// @param out the output stream to emit the textual report to. /// /// @param indent the indentation string to use in the report. void fn_parm_diff::report(ostream& out, const string& indent) const { context()->get_reporter()->report(*this, out, indent); } /// Populate the vector of children nodes of the @ref diff base type /// sub-object of this instance of @ref fn_parm_diff. /// /// The children nodes can then later be retrieved using /// diff::children_nodes() void fn_parm_diff::chain_into_hierarchy() { if (type_diff()) append_child_node(type_diff()); } /// Compute the difference between two function_decl::parameter_sptr; /// that is, between two function parameters. Return a resulting /// fn_parm_diff_sptr that represents the changes. /// /// Note that the two decls must have been created in the same @ref /// environment, otherwise, this function aborts. /// /// @param first the first subject of the diff. /// /// @param second the second subject of the diff. /// /// @param ctxt the context of the diff. /// /// @return fn_parm_diff_sptr the resulting diff node. fn_parm_diff_sptr compute_diff(const function_decl::parameter_sptr first, const function_decl::parameter_sptr second, diff_context_sptr ctxt) { if (!first || !second) return fn_parm_diff_sptr(); fn_parm_diff_sptr result(new fn_parm_diff(first, second, ctxt)); ctxt->initialize_canonical_diff(result); return result; } // // void function_type_diff::ensure_lookup_tables_populated() { priv_->return_type_diff_ = compute_diff(first_function_type()->get_return_type(), second_function_type()->get_return_type(), context()); string parm_name; function_decl::parameter_sptr parm; for (vector::const_iterator i = priv_->parm_changes_.deletions().begin(); i != priv_->parm_changes_.deletions().end(); ++i) { parm = *(first_function_type()->get_first_parm() + i->index()); parm_name = parm->get_name_id(); // If for a reason the type name is empty we want to know and // fix that. ABG_ASSERT(!parm_name.empty()); priv_->deleted_parms_[parm_name] = parm; priv_->deleted_parms_by_id_[parm->get_index()] = parm; } for (vector::const_iterator i = priv_->parm_changes_.insertions().begin(); i != priv_->parm_changes_.insertions().end(); ++i) { for (vector::const_iterator j = i->inserted_indexes().begin(); j != i->inserted_indexes().end(); ++j) { parm = *(second_function_type()->get_first_parm() + *j); parm_name = parm->get_name_id(); // If for a reason the type name is empty we want to know and // fix that. ABG_ASSERT(!parm_name.empty()); { string_parm_map::const_iterator k = priv_->deleted_parms_.find(parm_name); if (k != priv_->deleted_parms_.end()) { if (*k->second != *parm) priv_->subtype_changed_parms_[parm_name] = compute_diff(k->second, parm, context()); priv_->deleted_parms_.erase(parm_name); } else priv_->added_parms_[parm_name] = parm; } { unsigned_parm_map::const_iterator k = priv_->deleted_parms_by_id_.find(parm->get_index()); if (k != priv_->deleted_parms_by_id_.end()) { if (*k->second != *parm && (k->second->get_name_id() != parm_name)) priv_->changed_parms_by_id_[parm->get_index()] = compute_diff(k->second, parm, context()); priv_->added_parms_.erase(parm_name); priv_->deleted_parms_.erase(k->second->get_name_id()); priv_->deleted_parms_by_id_.erase(parm->get_index()); } else priv_->added_parms_by_id_[parm->get_index()] = parm; } } } sort_string_fn_parm_diff_sptr_map(priv_->subtype_changed_parms_, priv_->sorted_subtype_changed_parms_); sort_string_fn_parm_diff_sptr_map(priv_->changed_parms_by_id_, priv_->sorted_changed_parms_by_id_); sort_string_parm_map(priv_->deleted_parms_, priv_->sorted_deleted_parms_); sort_string_parm_map(priv_->added_parms_, priv_->sorted_added_parms_); } /// In the vector of deleted parameters, get the one that is at a given /// index. /// /// @param i the index of the deleted parameter to get. /// /// @return the parameter returned. const function_decl::parameter_sptr function_type_diff::deleted_parameter_at(int i) const {return first_function_type()->get_parameters()[i];} /// Getter for the sorted vector of deleted parameters. /// /// @return the sorted vector of deleted parameters. const vector& function_type_diff::sorted_deleted_parms() const {return priv_->sorted_deleted_parms_;} /// Getter for the sorted vector of added parameters . /// /// @return the sorted vector of added parameters. const vector& function_type_diff::sorted_added_parms() const {return priv_->sorted_added_parms_;} /// In the vector of inserted parameters, get the one that is at a /// given index. /// /// @param i the index of the inserted parameter to get. /// /// @return the parameter returned. const function_decl::parameter_sptr function_type_diff::inserted_parameter_at(int i) const {return second_function_type()->get_parameters()[i];} /// Consutrctor of the @ref function_type type. /// /// @param first the first @ref function_type subject of the diff to /// create. /// /// @param second the second @ref function_type subject of the diff to /// create. /// /// @param ctxt the diff context to be used by the newly created /// instance of function_type_diff. Note that this context object /// must stay alive at least during the life time of the current /// instance of @ref function_type_diff. Otherwise memory corruption /// issues occur. function_type_diff::function_type_diff(const function_type_sptr first, const function_type_sptr second, diff_context_sptr ctxt) : type_diff_base(first, second, ctxt), priv_(new priv) {} /// Getter for the first subject of the diff. /// /// @return the first function type involved in the diff. const function_type_sptr function_type_diff::first_function_type() const {return dynamic_pointer_cast(first_subject());} /// Getter for the second subject of the diff. /// /// @return the second function type involved in the diff. const function_type_sptr function_type_diff::second_function_type() const {return dynamic_pointer_cast(second_subject());} /// Getter for the diff of the return types of the two function types /// of the current diff. /// /// @return the diff of the return types of the two function types of /// the current diff. const diff_sptr function_type_diff::return_type_diff() const {return priv_->return_type_diff_;} /// Getter for the map of function parameter changes of the current diff. /// /// @return a map of function parameter changes of the current diff. const string_fn_parm_diff_sptr_map& function_type_diff::subtype_changed_parms() const {return priv_->subtype_changed_parms_;} /// Getter for the map of parameters that got removed. /// /// @return the map of parameters that got removed. const string_parm_map& function_type_diff::removed_parms() const {return priv_->deleted_parms_;} /// Getter for the map of parameters that got added. /// /// @return the map of parameters that got added. const string_parm_map& function_type_diff::added_parms() const {return priv_->added_parms_;} /// Build and return a copy of a pretty representation of the current /// instance of @ref function_type_diff. /// /// @return a copy of the pretty representation of the current /// instance of @ref function_type_diff. const string& function_type_diff::get_pretty_representation() const { if (diff::priv_->pretty_representation_.empty()) { std::ostringstream o; o << "function_type_diff[" << abigail::ir::get_pretty_representation(first_function_type()) << ", " << abigail::ir::get_pretty_representation(second_function_type()) << "]"; diff::priv_->pretty_representation_ = o.str(); } return diff::priv_->pretty_representation_; } /// Test if the current diff node carries changes. /// /// @return true iff the current diff node carries changes. bool function_type_diff::has_changes() const {return *first_function_type() != *second_function_type();} /// Test if the current diff node carries local changes. /// /// A local change is a change that is carried by this diff node, not /// by any of its children nodes. /// /// @return the kind of local change carried by the current diff node. /// The value returned is zero if the current node carries no local /// change. enum change_kind function_type_diff::has_local_changes() const { ir::change_kind k = ir::NO_CHANGE_KIND; if (!equals(*first_function_type(), *second_function_type(), &k)) return k & ir::ALL_LOCAL_CHANGES_MASK; return ir::NO_CHANGE_KIND; } /// Build and emit a textual report about the current @ref /// function_type_diff instance. /// /// @param out the output stream. /// /// @param indent the indentation string to use. void function_type_diff::report(ostream& out, const string& indent) const { context()->get_reporter()->report(*this, out, indent); } /// Populate the vector of children node of the @ref diff base type /// sub-object of this instance of @ref function_type_diff. /// /// The children node can then later be retrieved using /// diff::children_node(). void function_type_diff::chain_into_hierarchy() { if (diff_sptr d = return_type_diff()) append_child_node(d); for (vector::const_iterator i = priv_->sorted_subtype_changed_parms_.begin(); i != priv_->sorted_subtype_changed_parms_.end(); ++i) if (diff_sptr d = *i) append_child_node(d); for (vector::const_iterator i = priv_->sorted_changed_parms_by_id_.begin(); i != priv_->sorted_changed_parms_by_id_.end(); ++i) if (diff_sptr d = *i) append_child_node(d); } /// Compute the diff between two instances of @ref function_type. /// /// Note that the two types must have been created in the same @ref /// environment, otherwise, this function aborts. /// /// @param first the first @ref function_type to consider for the diff. /// /// @param second the second @ref function_type to consider for the diff. /// /// @param ctxt the diff context to use. /// /// @return the resulting diff between the two @ref function_type. function_type_diff_sptr compute_diff(const function_type_sptr first, const function_type_sptr second, diff_context_sptr ctxt) { if (!first || !second) { // TODO: implement this for either first or second being NULL. return function_type_diff_sptr(); } function_type_diff_sptr result(new function_type_diff(first, second, ctxt)); diff_utils::compute_diff(first->get_first_parm(), first->get_parameters().end(), second->get_first_parm(), second->get_parameters().end(), result->priv_->parm_changes_); result->ensure_lookup_tables_populated(); ctxt->initialize_canonical_diff(result); return result; } // // /// Build the lookup tables of the diff, if necessary. void function_decl_diff::ensure_lookup_tables_populated() { } /// Populate the vector of children node of the @ref diff base type /// sub-object of this instance of @ref function_decl_diff. /// /// The children node can then later be retrieved using /// diff::children_node(). void function_decl_diff::chain_into_hierarchy() { if (diff_sptr d = type_diff()) append_child_node(d); } /// Constructor for function_decl_diff /// /// @param first the first function considered by the diff. /// /// @param second the second function considered by the diff. /// /// @param ctxt the context of the diff. Note that this context /// object must stay alive at least during the life time of the /// current instance of @ref function_decl_diff. Otherwise memory /// corruption issues occur. function_decl_diff::function_decl_diff(const function_decl_sptr first, const function_decl_sptr second, diff_context_sptr ctxt) : decl_diff_base(first, second, ctxt), priv_(new priv) { } /// @return the first function considered by the diff. const function_decl_sptr function_decl_diff::first_function_decl() const {return dynamic_pointer_cast(first_subject());} /// @return the second function considered by the diff. const function_decl_sptr function_decl_diff::second_function_decl() const {return dynamic_pointer_cast(second_subject());} const function_type_diff_sptr function_decl_diff::type_diff() const {return priv_->type_diff_;} /// @return the pretty representation for the current instance of @ref /// function_decl_diff. const string& function_decl_diff::get_pretty_representation() const { if (diff::priv_->pretty_representation_.empty()) { std::ostringstream o; o << "function_diff[" << first_subject()->get_pretty_representation() << ", " << second_subject()->get_pretty_representation() << "]"; diff::priv_->pretty_representation_ = o.str(); } return diff::priv_->pretty_representation_; } /// Return true iff the current diff node carries a change. /// /// @return true iff the current diff node carries a change. bool function_decl_diff::has_changes() const {return *first_function_decl() != *second_function_decl();} /// @return the kind of local change carried by the current diff node. /// The value returned is zero if the current node carries no local /// change. enum change_kind function_decl_diff::has_local_changes() const { ir::change_kind k = ir::NO_CHANGE_KIND; if (!equals(*first_function_decl(), *second_function_decl(), &k)) return k & ir::ALL_LOCAL_CHANGES_MASK; return ir::NO_CHANGE_KIND; } /// Serialize a report of the changes encapsulated in the current /// instance of @ref function_decl_diff over to an output stream. /// /// @param out the output stream to serialize the report to. /// /// @param indent the string to use an an indentation prefix. void function_decl_diff::report(ostream& out, const string& indent) const { context()->get_reporter()->report(*this, out, indent); } /// Compute the diff between two function_decl. /// /// Note that the two decls must have been created in the same @ref /// environment, otherwise, this function aborts. /// /// @param first the first function_decl to consider for the diff /// /// @param second the second function_decl to consider for the diff /// /// @param ctxt the diff context to use. /// /// @return the computed diff function_decl_diff_sptr compute_diff(const function_decl_sptr first, const function_decl_sptr second, diff_context_sptr ctxt) { if (!first || !second) { // TODO: implement this for either first or second being NULL. return function_decl_diff_sptr(); } function_type_diff_sptr type_diff = compute_diff(first->get_type(), second->get_type(), ctxt); function_decl_diff_sptr result(new function_decl_diff(first, second, ctxt)); result->priv_->type_diff_ = type_diff; result->ensure_lookup_tables_populated(); ctxt->initialize_canonical_diff(result); return result; } // // /// Constructor for type_decl_diff. /// /// @param first the first subject of the diff. /// /// @param second the second subject of the diff. /// /// @param ctxt the context of the diff. Note that this context /// object must stay alive at least during the life time of the /// current instance of @ref type_decl_diff. Otherwise memory /// corruption issues occur. type_decl_diff::type_decl_diff(const type_decl_sptr first, const type_decl_sptr second, diff_context_sptr ctxt) : type_diff_base(first, second, ctxt) {} /// Getter for the first subject of the type_decl_diff. /// /// @return the first type_decl involved in the diff. const type_decl_sptr type_decl_diff::first_type_decl() const {return dynamic_pointer_cast(first_subject());} /// Getter for the second subject of the type_decl_diff. /// /// @return the second type_decl involved in the diff. const type_decl_sptr type_decl_diff::second_type_decl() const {return dynamic_pointer_cast(second_subject());} /// @return the pretty representation for the current instance of @ref /// type_decl_diff. const string& type_decl_diff::get_pretty_representation() const { if (diff::priv_->pretty_representation_.empty()) { std::ostringstream o; o << "type_decl_diff[" << first_subject()->get_pretty_representation() << ", " << second_subject()->get_pretty_representation() << "]"; diff::priv_->pretty_representation_ = o.str(); } return diff::priv_->pretty_representation_; } /// Return true iff the current diff node carries a change. /// /// @return true iff the current diff node carries a change. bool type_decl_diff::has_changes() const {return first_type_decl() != second_type_decl();} /// @return the kind of local change carried by the current diff node. /// The value returned is zero if the current node carries no local /// change. enum change_kind type_decl_diff::has_local_changes() const { ir::change_kind k = ir::NO_CHANGE_KIND; if (!equals(*first_type_decl(), *second_type_decl(), &k)) return k & ir::ALL_LOCAL_CHANGES_MASK; return ir::NO_CHANGE_KIND; } /// Ouputs a report of the differences between of the two type_decl /// involved in the type_decl_diff. /// /// @param out the output stream to emit the report to. /// /// @param indent the string to use for indentatino indent. void type_decl_diff::report(ostream& out, const string& indent) const { context()->get_reporter()->report(*this, out, indent); } /// Compute a diff between two type_decl. /// /// Note that the two types must have been created in the same @ref /// environment, otherwise, this function aborts. /// /// This function doesn't actually compute a diff. As a type_decl is /// very simple (unlike compound constructs like function_decl or /// class_decl) it's easy to just compare the components of the /// type_decl to know what has changed. Thus this function just /// builds and return a type_decl_diff object. The /// type_decl_diff::report function will just compare the components /// of the the two type_decl and display where and how they differ. /// /// @param first a pointer to the first type_decl to /// consider. /// /// @param second a pointer to the second type_decl to consider. /// /// @param ctxt the diff context to use. /// /// @return a pointer to the resulting type_decl_diff. type_decl_diff_sptr compute_diff(const type_decl_sptr first, const type_decl_sptr second, diff_context_sptr ctxt) { type_decl_diff_sptr result(new type_decl_diff(first, second, ctxt)); // We don't need to actually compute a diff here as a type_decl // doesn't have complicated sub-components. type_decl_diff::report // just walks the members of the type_decls and display information // about the ones that have changed. On a similar note, // type_decl_diff::length returns 0 if the two type_decls are equal, // and 1 otherwise. ctxt->initialize_canonical_diff(result); return result; } // // /// Populate the vector of children node of the @ref diff base type /// sub-object of this instance of @ref typedef_diff. /// /// The children node can then later be retrieved using /// diff::children_node(). void typedef_diff::chain_into_hierarchy() {append_child_node(underlying_type_diff());} /// Constructor for typedef_diff. /// /// @param first the first subject of the diff. /// /// @param second the second subject of the diff. /// /// @param underlying the underlying diff of the @ref typedef_diff. /// That is the diff between the underlying types of @p first and @p /// second. /// /// @param ctxt the context of the diff. Note that this context /// object must stay alive at least during the life time of the /// current instance of @ref typedef_diff. Otherwise memory /// corruption issues occur. typedef_diff::typedef_diff(const typedef_decl_sptr first, const typedef_decl_sptr second, const diff_sptr underlying, diff_context_sptr ctxt) : type_diff_base(first, second, ctxt), priv_(new priv(underlying)) {} /// Getter for the firt typedef_decl involved in the diff. /// /// @return the first subject of the diff. const typedef_decl_sptr typedef_diff::first_typedef_decl() const {return dynamic_pointer_cast(first_subject());} /// Getter for the second typedef_decl involved in the diff. /// /// @return the second subject of the diff. const typedef_decl_sptr typedef_diff::second_typedef_decl() const {return dynamic_pointer_cast(second_subject());} /// Getter for the diff between the two underlying types of the /// typedefs. /// /// @return the diff object reprensenting the difference between the /// two underlying types of the typedefs. const diff_sptr typedef_diff::underlying_type_diff() const {return priv_->underlying_type_diff_;} /// Setter for the diff between the two underlying types of the /// typedefs. /// /// @param d the new diff object reprensenting the difference between /// the two underlying types of the typedefs. void typedef_diff::underlying_type_diff(const diff_sptr d) {priv_->underlying_type_diff_ = d;} /// @return the pretty representation for the current instance of @ref /// typedef_diff. const string& typedef_diff::get_pretty_representation() const { if (diff::priv_->pretty_representation_.empty()) { std::ostringstream o; o << "typedef_diff[" << first_subject()->get_pretty_representation() << ", " << second_subject()->get_pretty_representation() << "]"; diff::priv_->pretty_representation_ = o.str(); } return diff::priv_->pretty_representation_; } /// Return true iff the current diff node carries a change. /// /// @return true iff the current diff node carries a change. bool typedef_diff::has_changes() const { decl_base_sptr second = second_typedef_decl(); return !(*first_typedef_decl() == *second); } /// @return the kind of local change carried by the current diff node. /// The value returned is zero if the current node carries no local /// change. enum change_kind typedef_diff::has_local_changes() const { ir::change_kind k = ir::NO_CHANGE_KIND; if (!equals(*first_typedef_decl(), *second_typedef_decl(), &k)) return k & ir::ALL_LOCAL_CHANGES_MASK; return ir::NO_CHANGE_KIND; } /// Reports the difference between the two subjects of the diff in a /// serialized form. /// /// @param out the output stream to emit the report to. /// /// @param indent the indentation string to use. void typedef_diff::report(ostream& out, const string& indent) const { context()->get_reporter()->report(*this, out, indent); } /// Compute a diff between two typedef_decl. /// /// Note that the two types must have been created in the same @ref /// environment, otherwise, this function aborts. /// /// @param first a pointer to the first typedef_decl to consider. /// /// @param second a pointer to the second typedef_decl to consider. /// /// @param ctxt the diff context to use. /// /// @return a pointer to the the resulting typedef_diff. typedef_diff_sptr compute_diff(const typedef_decl_sptr first, const typedef_decl_sptr second, diff_context_sptr ctxt) { diff_sptr d = compute_diff_for_types(first->get_underlying_type(), second->get_underlying_type(), ctxt); typedef_diff_sptr result(new typedef_diff(first, second, d, ctxt)); ctxt->initialize_canonical_diff(result); return result; } /// Return the leaf underlying diff node of a @ref typedef_diff node. /// /// If the underlying diff node of a @ref typedef_diff node is itself /// a @ref typedef_diff node, then recursively look at the underlying /// diff nodes to get the first one that is not a a @ref typedef_diff /// node. This is what a leaf underlying diff node means. /// /// Otherwise, if the underlying diff node of @ref typedef_diff is /// *NOT* a @ref typedef_diff node, then just return the underlying /// diff node. /// /// And if the diff node considered is not a @ref typedef_diff node, /// then just return it. /// /// @return the leaf underlying diff node of a @p diff. const diff* get_typedef_diff_underlying_type_diff(const diff* diff) { const typedef_diff* d = dynamic_cast(diff); if (!d) return diff; if (const typedef_diff* deef = dynamic_cast(d->underlying_type_diff().get())) return get_typedef_diff_underlying_type_diff(deef); return d->underlying_type_diff().get(); } // // /// Constructor for translation_unit_diff. /// /// @param first the first translation unit to consider for this diff. /// /// @param second the second translation unit to consider for this diff. /// /// @param ctxt the context of the diff. Note that this context /// object must stay alive at least during the life time of the /// current instance of @ref translation_unit_diff. Otherwise memory /// corruption issues occur. translation_unit_diff::translation_unit_diff(translation_unit_sptr first, translation_unit_sptr second, diff_context_sptr ctxt) : scope_diff(first->get_global_scope(), second->get_global_scope(), ctxt), priv_(new priv(first, second)) { } /// Getter for the first translation unit of this diff. /// /// @return the first translation unit of this diff. const translation_unit_sptr translation_unit_diff::first_translation_unit() const {return priv_->first_;} /// Getter for the second translation unit of this diff. /// /// @return the second translation unit of this diff. const translation_unit_sptr translation_unit_diff::second_translation_unit() const {return priv_->second_;} /// Return true iff the current diff node carries a change. /// /// @return true iff the current diff node carries a change. bool translation_unit_diff::has_changes() const {return scope_diff::has_changes();} /// @return the kind of local change carried by the current diff node. /// The value returned is zero if the current node carries no local /// change. enum change_kind translation_unit_diff::has_local_changes() const {return ir::NO_CHANGE_KIND;} /// Report the diff in a serialized form. /// /// @param out the output stream to serialize the report to. /// /// @param indent the prefix to use as indentation for the report. void translation_unit_diff::report(ostream& out, const string& indent) const {scope_diff::report(out, indent);} /// Compute the diff between two translation_units. /// /// Note that the two translation units must have been created in the /// same @ref environment, otherwise, this function aborts. /// /// @param first the first translation_unit to consider. /// /// @param second the second translation_unit to consider. /// /// @param ctxt the diff context to use. If null, this function will /// create a new context and set to the diff object returned. /// /// @return the newly created diff object. translation_unit_diff_sptr compute_diff(const translation_unit_sptr first, const translation_unit_sptr second, diff_context_sptr ctxt) { ABG_ASSERT(first && second); if (!ctxt) ctxt.reset(new diff_context); // TODO: handle first or second having empty contents. translation_unit_diff_sptr tu_diff(new translation_unit_diff(first, second, ctxt)); scope_diff_sptr sc_diff = dynamic_pointer_cast(tu_diff); compute_diff(static_pointer_cast(first->get_global_scope()), static_pointer_cast(second->get_global_scope()), sc_diff, ctxt); ctxt->initialize_canonical_diff(tu_diff); return tu_diff; } // // /// The private data of the @ref diff_maps type. struct diff_maps::priv { string_diff_ptr_map type_decl_diff_map_; string_diff_ptr_map enum_diff_map_; string_diff_ptr_map class_diff_map_; string_diff_ptr_map union_diff_map_; string_diff_ptr_map typedef_diff_map_; string_diff_ptr_map array_diff_map_; string_diff_ptr_map reference_diff_map_; string_diff_ptr_map function_type_diff_map_; string_diff_ptr_map function_decl_diff_map_; string_diff_ptr_map var_decl_diff_map_; string_diff_ptr_map distinct_diff_map_; string_diff_ptr_map fn_parm_diff_map_; diff_artifact_set_map_type impacted_artifacts_map_; }; // end struct diff_maps::priv /// Default constructor of the @ref diff_maps type. diff_maps::diff_maps() : priv_(new diff_maps::priv()) {} diff_maps::~diff_maps() = default; /// Getter of the map that contains basic type diffs. /// /// @return the map that contains basic type diffs. const string_diff_ptr_map& diff_maps::get_type_decl_diff_map() const {return priv_->type_decl_diff_map_;} /// Getter of the map that contains basic type diffs. /// /// @return the map that contains basic type diffs. string_diff_ptr_map& diff_maps::get_type_decl_diff_map() {return priv_->type_decl_diff_map_;} /// Getter of the map that contains enum type diffs. /// /// @return the map that contains enum type diffs. const string_diff_ptr_map& diff_maps::get_enum_diff_map() const {return priv_->enum_diff_map_;} /// Getter of the map that contains enum type diffs. /// /// @return the map that contains enum type diffs. string_diff_ptr_map& diff_maps::get_enum_diff_map() {return priv_->enum_diff_map_;} /// Getter of the map that contains class type diffs. /// /// @return the map that contains class type diffs. const string_diff_ptr_map& diff_maps::get_class_diff_map() const {return priv_->class_diff_map_;} /// Getter of the map that contains class type diffs. /// /// @return the map that contains class type diffs. string_diff_ptr_map& diff_maps::get_class_diff_map() {return priv_->class_diff_map_;} /// Getter of the map that contains union type diffs. /// /// @return the map that contains union type diffs. const string_diff_ptr_map& diff_maps::get_union_diff_map() const {return priv_->union_diff_map_;} /// Getter of the map that contains union type diffs. /// /// @return the map that contains union type diffs. string_diff_ptr_map& diff_maps::get_union_diff_map() {return priv_->union_diff_map_;} /// Getter of the map that contains typedef type diffs. /// /// @return the map that contains typedef type diffs. const string_diff_ptr_map& diff_maps::get_typedef_diff_map() const {return priv_->typedef_diff_map_;} /// Getter of the map that contains typedef type diffs. /// /// @return the map that contains typedef type diffs. string_diff_ptr_map& diff_maps::get_typedef_diff_map() {return priv_->typedef_diff_map_;} /// Getter of the map that contains array type diffs. /// /// @return the map that contains array type diffs. const string_diff_ptr_map& diff_maps::get_array_diff_map() const {return priv_->array_diff_map_;} /// Getter of the map that contains array type diffs. /// /// @return the map that contains array type diffs. string_diff_ptr_map& diff_maps::get_array_diff_map() {return priv_->array_diff_map_;} /// Getter of the map that contains reference type diffs. /// /// @return the map that contains reference type diffs. const string_diff_ptr_map& diff_maps::get_reference_diff_map() const {return priv_->reference_diff_map_;} /// Getter of the map that contains reference type diffs. /// /// @return the map that contains reference type diffs. string_diff_ptr_map& diff_maps::get_reference_diff_map() {{return priv_->reference_diff_map_;}} /// Getter of the map that contains function parameter diffs. /// /// @return the map that contains function parameter diffs. const string_diff_ptr_map& diff_maps::get_fn_parm_diff_map() const {return priv_->fn_parm_diff_map_;} /// Getter of the map that contains function parameter diffs. /// /// @return the map that contains function parameter diffs. string_diff_ptr_map& diff_maps::get_fn_parm_diff_map() {return priv_->fn_parm_diff_map_;} /// Getter of the map that contains function type diffs. /// /// @return the map that contains function type diffs. const string_diff_ptr_map& diff_maps::get_function_type_diff_map() const {return priv_->function_type_diff_map_;} /// Getter of the map that contains function type diffs. /// /// @return the map that contains function type diffs. string_diff_ptr_map& diff_maps::get_function_type_diff_map() {return priv_->function_type_diff_map_;} /// Getter of the map that contains function decl diffs. /// /// @return the map that contains function decl diffs. const string_diff_ptr_map& diff_maps::get_function_decl_diff_map() const {return priv_->function_decl_diff_map_;} /// Getter of the map that contains function decl diffs. /// /// @return the map that contains function decl diffs. string_diff_ptr_map& diff_maps::get_function_decl_diff_map() {return priv_->function_decl_diff_map_;} /// Getter of the map that contains var decl diffs. /// /// @return the map that contains var decl diffs. const string_diff_ptr_map& diff_maps::get_var_decl_diff_map() const {return priv_->var_decl_diff_map_;} /// Getter of the map that contains var decl diffs. /// /// @return the map that contains var decl diffs. string_diff_ptr_map& diff_maps::get_var_decl_diff_map() {return priv_->var_decl_diff_map_;} /// Getter of the map that contains distinct diffs. /// /// @return the map that contains distinct diffs. const string_diff_ptr_map& diff_maps::get_distinct_diff_map() const {return priv_->distinct_diff_map_;} /// Getter of the map that contains distinct diffs. /// /// @return the map that contains distinct diffs. string_diff_ptr_map& diff_maps::get_distinct_diff_map() {return priv_->distinct_diff_map_;} /// Insert a new diff node into the current instance of @ref diff_maps. /// /// @param dif the new diff node to insert into the @ref diff_maps. /// /// @param impacted_iface the interface (global function or variable) /// currently being analysed that led to analysing the diff node @p /// dif. In other words, this is the interface impacted by the diff /// node @p dif. Note that this can be nil in cases where we are /// directly analysing changes to a type that is not reachable from /// any global function or variable. /// /// @return true iff the diff node could be added to the current /// instance of @ref diff_maps. bool diff_maps::insert_diff_node(const diff *dif, const type_or_decl_base_sptr& impacted_iface) { string n = get_pretty_representation(dif->first_subject(), /*internal=*/true); if (const type_decl_diff *d = is_diff_of_basic_type(dif)) get_type_decl_diff_map()[n] = const_cast(d); else if (const enum_diff *d = is_enum_diff(dif)) get_enum_diff_map()[n] = const_cast(d); else if (const class_diff *d = is_class_diff(dif)) get_class_diff_map()[n] = const_cast(d); else if (const union_diff *d = is_union_diff(dif)) get_union_diff_map()[n] = const_cast(d); else if (const typedef_diff *d = is_typedef_diff(dif)) get_typedef_diff_map()[n] = const_cast(d); else if (const array_diff *d = is_array_diff(dif)) get_array_diff_map()[n] = const_cast(d); else if (const reference_diff *d = is_reference_diff(dif)) get_reference_diff_map()[n] = const_cast(d); else if (const fn_parm_diff *d = is_fn_parm_diff(dif)) get_fn_parm_diff_map()[n] = const_cast(d); else if (const function_type_diff *d = is_function_type_diff(dif)) get_function_type_diff_map()[n] = const_cast(d); else if (const var_diff *d = is_var_diff(dif)) get_var_decl_diff_map()[n] = const_cast(d); else if (const function_decl_diff *d = is_function_decl_diff(dif)) get_function_decl_diff_map()[n] = const_cast(d); else if (const distinct_diff *d = is_distinct_diff(dif)) get_distinct_diff_map()[n] = const_cast(d); else if (is_base_diff(dif)) // we silently drop this case. return true; else ABG_ASSERT_NOT_REACHED; // Update the map that associates this diff node to the set of // interfaces it impacts. if (impacted_iface) { diff_artifact_set_map_type::iterator i = priv_->impacted_artifacts_map_.find(dif); if (i == priv_->impacted_artifacts_map_.end()) { artifact_sptr_set_type set; set.insert(impacted_iface); priv_->impacted_artifacts_map_[dif] = set; } else i->second.insert(impacted_iface); } return true; } /// Lookup the interfaces that are impacted by a given leaf diff node. /// /// @param d the diff node to consider. /// /// @return the set of artifacts impacted by @p d. artifact_sptr_set_type* diff_maps::lookup_impacted_interfaces(const diff *d) const { diff_artifact_set_map_type::iterator i = priv_->impacted_artifacts_map_.find(d); if (i == priv_->impacted_artifacts_map_.end()) return 0; return &i->second; } // // /// Constructor for the @ref diff_stat type. /// /// @param ctxt the context of the corpus diff. Note that this /// context object must stay alive at least during the life time of /// the current instance of @ref corpus_diff::diff_stats. Otherwise /// memory corruption issues occur. corpus_diff::diff_stats::diff_stats(diff_context_sptr ctxt) : priv_(new priv(ctxt)) {} /// Getter for the number of functions removed. /// /// @return the number of functions removed. size_t corpus_diff::diff_stats::num_func_removed() const {return priv_->num_func_removed;} /// Setter for the number of functions removed. /// /// @param n the new number of functions removed. void corpus_diff::diff_stats::num_func_removed(size_t n) {priv_->num_func_removed = n;} /// Getter for the number of removed functions that have been filtered /// out. /// /// @return the number of removed functions that have been filtered /// out. size_t corpus_diff::diff_stats::num_removed_func_filtered_out() const { if (priv_->ctxt() && !priv_->ctxt()->show_deleted_fns()) return num_func_removed(); return priv_->num_removed_func_filtered_out; } /// Setter for the number of removed functions that have been filtered /// out. /// /// @param t the new value. void corpus_diff::diff_stats::num_removed_func_filtered_out(size_t t) {priv_->num_removed_func_filtered_out = t;} /// Getter for the net number of function removed. /// /// This is the difference between the number of functions removed and /// the number of functons removed that have been filtered out. /// /// @return the net number of function removed. size_t corpus_diff::diff_stats::net_num_func_removed() const { ABG_ASSERT(num_func_removed() >= num_removed_func_filtered_out()); return num_func_removed() - num_removed_func_filtered_out(); } /// Getter for the number of functions added. /// /// @return the number of functions added. size_t corpus_diff::diff_stats::num_func_added() const {return priv_->num_func_added;} /// Setter for the number of functions added. /// /// @param n the new number of functions added. void corpus_diff::diff_stats::num_func_added(size_t n) {priv_->num_func_added = n;} /// Getter for the number of added function that have been filtered out. /// /// @return the number of added function that have been filtered out. size_t corpus_diff::diff_stats::num_added_func_filtered_out() const { if (priv_->ctxt() && !priv_->ctxt()->show_added_fns()) return num_func_added(); return priv_->num_added_func_filtered_out; } /// Setter for the number of added function that have been filtered /// out. /// /// @param n the new value. void corpus_diff::diff_stats::num_added_func_filtered_out(size_t n) {priv_->num_added_func_filtered_out = n;} /// Getter for the net number of added functions. /// /// The net number of added functions is the difference between the /// number of added functions and the number of added functions that /// have been filtered out. /// /// @return the net number of added functions. size_t corpus_diff::diff_stats::net_num_func_added() const { ABG_ASSERT(num_func_added() >= num_added_func_filtered_out()); return num_func_added() - num_added_func_filtered_out(); } /// Getter for the number of functions that have a change in one of /// their sub-types. /// /// @return the number of functions that have a change in one of their /// sub-types. size_t corpus_diff::diff_stats::num_func_changed() const {return priv_->num_func_changed;} /// Setter for the number of functions that have a change in one of /// their sub-types. /// /// @@param n the new number of functions that have a change in one of /// their sub-types. void corpus_diff::diff_stats::num_func_changed(size_t n) {priv_->num_func_changed = n;} /// Getter for the number of functions that have a change in one of /// their sub-types, and that have been filtered out. /// /// @return the number of functions that have a change in one of their /// sub-types, and that have been filtered out. size_t corpus_diff::diff_stats::num_changed_func_filtered_out() const {return priv_->num_changed_func_filtered_out;} /// Setter for the number of functions that have a change in one of /// their sub-types, and that have been filtered out. /// /// @param n the new number of functions that have a change in one of their /// sub-types, and that have been filtered out. void corpus_diff::diff_stats::num_changed_func_filtered_out(size_t n) {priv_->num_changed_func_filtered_out = n;} /// Getter for the number of functions that carry virtual member /// offset changes. /// /// @return the number of functions that carry virtual member changes. size_t corpus_diff::diff_stats::num_func_with_virtual_offset_changes() const {return priv_->num_func_with_virt_offset_changes;} /// Setter for the number of functions that carry virtual member /// offset changes. /// /// @param n the new number of functions that carry virtual member /// offset. changes. void corpus_diff::diff_stats::num_func_with_virtual_offset_changes(size_t n) {priv_->num_func_with_virt_offset_changes = n;} /// Getter for the number of functions that have a change in their /// sub-types, minus the number of these functions that got filtered /// out from the diff. /// /// @return for the the number of functions that have a change in /// their sub-types, minus the number of these functions that got /// filtered out from the diff. size_t corpus_diff::diff_stats::net_num_func_changed() const {return num_func_changed() - num_changed_func_filtered_out();} /// Getter for the number of variables removed. /// /// @return the number of variables removed. size_t corpus_diff::diff_stats::num_vars_removed() const {return priv_->num_vars_removed;} /// Setter for the number of variables removed. /// /// @param n the new number of variables removed. void corpus_diff::diff_stats::num_vars_removed(size_t n) {priv_->num_vars_removed = n;} /// Getter for the number removed variables that have been filtered /// out. /// /// @return the number removed variables that have been filtered out. size_t corpus_diff::diff_stats::num_removed_vars_filtered_out() const { if (priv_->ctxt() && !priv_->ctxt()->show_deleted_vars()) return num_vars_removed(); return priv_->num_removed_vars_filtered_out; } /// Setter for the number of removed variables that have been filtered /// out. /// /// @param n the new value. void corpus_diff::diff_stats::num_removed_vars_filtered_out(size_t n) const {priv_->num_removed_vars_filtered_out = n;} /// Getter for the net number of removed variables. /// /// The net number of removed variables is the difference between the /// number of removed variables and the number of removed variables /// that have been filtered out. /// /// @return the net number of removed variables. size_t corpus_diff::diff_stats::net_num_vars_removed() const { ABG_ASSERT(num_vars_removed() >= num_removed_vars_filtered_out()); return num_vars_removed() - num_removed_vars_filtered_out(); } /// Getter for the number of variables added. /// /// @return the number of variables added. size_t corpus_diff::diff_stats::num_vars_added() const {return priv_->num_vars_added;} /// Setter for the number of variables added. /// /// @param n the new number of variables added. void corpus_diff::diff_stats::num_vars_added(size_t n) {priv_->num_vars_added = n;} /// Getter for the number of added variables that have been filtered /// out. /// /// @return the number of added variables that have been filtered out. size_t corpus_diff::diff_stats::num_added_vars_filtered_out() const { if (priv_->ctxt() && !priv_->ctxt()->show_added_vars()) return num_vars_added(); return priv_->num_added_vars_filtered_out; } /// Setter for the number of added variables that have been filtered /// out. /// /// @param n the new value. void corpus_diff::diff_stats::num_added_vars_filtered_out(size_t n) {priv_->num_added_vars_filtered_out = n;} /// Getter for the net number of added variables. /// /// The net number of added variables is the difference between the /// number of added variables and the number of added variables that /// have been filetered out. /// /// @return the net number of added variables. size_t corpus_diff::diff_stats::net_num_vars_added() const { ABG_ASSERT(num_vars_added() >= num_added_vars_filtered_out()); return num_vars_added() - num_added_vars_filtered_out(); } /// Getter for the number of variables that have a change in one of /// their sub-types. /// /// @return the number of variables that have a change in one of their /// sub-types. size_t corpus_diff::diff_stats::num_vars_changed() const {return priv_->num_vars_changed;} /// Setter for the number of variables that have a change in one of /// their sub-types. /// /// @param n the new number of variables that have a change in one of /// their sub-types. void corpus_diff::diff_stats::num_vars_changed(size_t n) {priv_->num_vars_changed = n;} /// Getter for the number of variables that have a change in one of /// their sub-types, and that have been filtered out. /// /// @return the number of functions that have a change in one of their /// sub-types, and that have been filtered out. size_t corpus_diff::diff_stats::num_changed_vars_filtered_out() const {return priv_->num_changed_vars_filtered_out;} /// Setter for the number of variables that have a change in one of /// their sub-types, and that have been filtered out. /// /// @param n the new number of variables that have a change in one of their /// sub-types, and that have been filtered out. void corpus_diff::diff_stats::num_changed_vars_filtered_out(size_t n) {priv_->num_changed_vars_filtered_out = n;} /// Getter for the number of variables that have a change in their /// sub-types, minus the number of these variables that got filtered /// out from the diff. /// /// @return for the the number of variables that have a change in /// their sub-types, minus the number of these variables that got /// filtered out from the diff. size_t corpus_diff::diff_stats::net_num_vars_changed() const {return num_vars_changed() - num_changed_vars_filtered_out();} /// Getter for the number of function symbols (not referenced by any /// debug info) that got removed. /// /// @return the number of function symbols (not referenced by any /// debug info) that got removed. size_t corpus_diff::diff_stats::num_func_syms_removed() const {return priv_->num_func_syms_removed;} /// Setter for the number of function symbols (not referenced by any /// debug info) that got removed. /// /// @param n the number of function symbols (not referenced by any /// debug info) that got removed. void corpus_diff::diff_stats::num_func_syms_removed(size_t n) {priv_->num_func_syms_removed = n;} /// Getter for the number of removed function symbols, not referenced /// by debug info, that have been filtered out. /// /// @return the number of removed function symbols, not referenced by /// debug info, that have been filtered out. size_t corpus_diff::diff_stats::num_removed_func_syms_filtered_out() const { if (priv_->ctxt() && !priv_->ctxt()->show_symbols_unreferenced_by_debug_info()) return num_func_syms_removed(); return priv_->num_removed_func_syms_filtered_out; } /// Setter for the number of removed function symbols, not referenced /// by debug info, that have been filtered out. /// /// @param n the new the number of removed function symbols, not /// referenced by debug info, that have been filtered out. void corpus_diff::diff_stats::num_removed_func_syms_filtered_out(size_t n) {priv_->num_removed_func_syms_filtered_out = n;} /// Getter of the net number of removed function symbols that are not /// referenced by any debug info. /// /// This is the difference between the total number of removed /// function symbols and the number of removed function symbols that /// have been filteted out. Both numbers are for symbols not /// referenced by debug info. /// /// return the net number of removed function symbols that are not /// referenced by any debug info. size_t corpus_diff::diff_stats::net_num_removed_func_syms() const { ABG_ASSERT(num_func_syms_removed() >= num_removed_func_syms_filtered_out()); return num_func_syms_removed() - num_removed_func_syms_filtered_out(); } /// Getter for the number of function symbols (not referenced by any /// debug info) that got added. /// /// @return the number of function symbols (not referenced by any /// debug info) that got added. size_t corpus_diff::diff_stats::num_func_syms_added() const {return priv_->num_func_syms_added;} /// Setter for the number of function symbols (not referenced by any /// debug info) that got added. /// /// @param n the new number of function symbols (not referenced by any /// debug info) that got added. void corpus_diff::diff_stats::num_func_syms_added(size_t n) {priv_->num_func_syms_added = n;} /// Getter for the number of added function symbols, not referenced by /// any debug info, that have been filtered out. /// /// @return the number of added function symbols, not referenced by /// any debug info, that have been filtered out. size_t corpus_diff::diff_stats::num_added_func_syms_filtered_out() const { if (priv_->ctxt() && !(priv_->ctxt()->show_added_symbols_unreferenced_by_debug_info() && priv_->ctxt()->show_symbols_unreferenced_by_debug_info())) return num_func_syms_added(); return priv_->num_added_func_syms_filtered_out; } /// Setter for the number of added function symbols, not referenced by /// any debug info, that have been filtered out. /// /// @param n the new number of added function symbols, not referenced /// by any debug info, that have been filtered out. void corpus_diff::diff_stats::num_added_func_syms_filtered_out(size_t n) {priv_->num_added_func_syms_filtered_out = n;} /// Getter of the net number of added function symbols that are not /// referenced by any debug info. /// /// This is the difference between the total number of added /// function symbols and the number of added function symbols that /// have been filteted out. Both numbers are for symbols not /// referenced by debug info. /// /// return the net number of added function symbols that are not /// referenced by any debug info. size_t corpus_diff::diff_stats::net_num_added_func_syms() const { ABG_ASSERT(num_func_syms_added() >= num_added_func_syms_filtered_out()); return num_func_syms_added()- num_added_func_syms_filtered_out(); } /// Getter for the number of variable symbols (not referenced by any /// debug info) that got removed. /// /// @return the number of variable symbols (not referenced by any /// debug info) that got removed. size_t corpus_diff::diff_stats::num_var_syms_removed() const {return priv_->num_var_syms_removed;} /// Setter for the number of variable symbols (not referenced by any /// debug info) that got removed. /// /// @param n the number of variable symbols (not referenced by any /// debug info) that got removed. void corpus_diff::diff_stats::num_var_syms_removed(size_t n) {priv_->num_var_syms_removed = n;} /// Getter for the number of removed variable symbols, not referenced /// by any debug info, that have been filtered out. /// /// @return the number of removed variable symbols, not referenced /// by any debug info, that have been filtered out. size_t corpus_diff::diff_stats::num_removed_var_syms_filtered_out() const { if (priv_->ctxt() && !priv_->ctxt()->show_symbols_unreferenced_by_debug_info()) return num_var_syms_removed(); return priv_->num_removed_var_syms_filtered_out; } /// Setter for the number of removed variable symbols, not referenced /// by any debug info, that have been filtered out. /// /// @param n the number of removed variable symbols, not referenced by /// any debug info, that have been filtered out. void corpus_diff::diff_stats::num_removed_var_syms_filtered_out(size_t n) {priv_->num_removed_var_syms_filtered_out = n;} /// Getter of the net number of removed variable symbols that are not /// referenced by any debug info. /// /// This is the difference between the total number of removed /// variable symbols and the number of removed variable symbols that /// have been filteted out. Both numbers are for symbols not /// referenced by debug info. /// /// return the net number of removed variable symbols that are not /// referenced by any debug info. size_t corpus_diff::diff_stats::net_num_removed_var_syms() const { ABG_ASSERT(num_var_syms_removed() >= num_removed_var_syms_filtered_out()); return num_var_syms_removed() - num_removed_var_syms_filtered_out(); } /// Getter for the number of variable symbols (not referenced by any /// debug info) that got added. /// /// @return the number of variable symbols (not referenced by any /// debug info) that got added. size_t corpus_diff::diff_stats::num_var_syms_added() const {return priv_->num_var_syms_added;} /// Setter for the number of variable symbols (not referenced by any /// debug info) that got added. /// /// @param n the new number of variable symbols (not referenced by any /// debug info) that got added. void corpus_diff::diff_stats::num_var_syms_added(size_t n) {priv_->num_var_syms_added = n;} /// Getter for the number of added variable symbols, not referenced by /// any debug info, that have been filtered out. /// /// @return the number of added variable symbols, not referenced by /// any debug info, that have been filtered out. size_t corpus_diff::diff_stats::num_added_var_syms_filtered_out() const { if (priv_->ctxt() && !(priv_->ctxt()->show_added_symbols_unreferenced_by_debug_info() && priv_->ctxt()->show_symbols_unreferenced_by_debug_info())) return num_var_syms_added(); return priv_->num_added_var_syms_filtered_out; } /// Setter for the number of added variable symbols, not referenced by /// any debug info, that have been filtered out. /// /// @param n the new number of added variable symbols, not referenced /// by any debug info, that have been filtered out. void corpus_diff::diff_stats::num_added_var_syms_filtered_out(size_t n) {priv_->num_added_var_syms_filtered_out = n;} /// Getter of the net number of added variable symbols that are not /// referenced by any debug info. /// /// This is the difference between the total number of added /// variable symbols and the number of added variable symbols that /// have been filteted out. Both numbers are for symbols not /// referenced by debug info. /// /// return the net number of added variable symbols that are not /// referenced by any debug info. size_t corpus_diff::diff_stats::net_num_added_var_syms() const { ABG_ASSERT(num_var_syms_added() >= num_added_var_syms_filtered_out()); return num_var_syms_added() - num_added_var_syms_filtered_out(); } /// Getter of the number of leaf type change diff nodes. /// /// @return the number of leaf type change diff nodes. size_t corpus_diff::diff_stats::num_leaf_changes() const {return priv_->num_leaf_changes;} /// Setter of the number of leaf type change diff nodes. /// /// @param n the new number of leaf type change diff nodes. void corpus_diff::diff_stats::num_leaf_changes(size_t n) {priv_->num_leaf_changes = n;} /// Getter of the number of leaf type change diff nodes that have been /// filtered out. /// /// @return the number of leaf type change diff nodes that have been size_t corpus_diff::diff_stats::num_leaf_changes_filtered_out() const {return priv_->num_leaf_changes_filtered_out;} /// Setter of the number of leaf type change diff nodes that have been /// filtered out. /// /// @param n the new number of leaf type change diff nodes that have /// been filtered out. void corpus_diff::diff_stats::num_leaf_changes_filtered_out(size_t n) {priv_->num_leaf_changes_filtered_out = n;} /// Getter of the net number of leaf change diff nodes. /// /// This is the difference between the total number of leaf change /// diff nodes, and the number of the leaf change diff nodes that have /// been filtered out. /// /// A leaf change is either a type change, a function change or a /// variable change. size_t corpus_diff::diff_stats::net_num_leaf_changes() const { ABG_ASSERT(num_leaf_changes() >= num_leaf_changes_filtered_out()); return num_leaf_changes() - num_leaf_changes_filtered_out(); } /// Getter for the number of leaf type change diff nodes. /// /// @return the number of leaf type changes diff nodes. size_t corpus_diff::diff_stats::num_leaf_type_changes() const {return priv_->num_leaf_type_changes;} /// Setter for the number of leaf type change diff nodes. /// /// @param n the new number of leaf type change diff nodes. void corpus_diff::diff_stats::num_leaf_type_changes(size_t n) {priv_->num_leaf_type_changes = n;} /// Getter for the number of filtered out leaf type change diff nodes. /// /// @return the number of filtered out leaf type change diff nodes. size_t corpus_diff::diff_stats::num_leaf_type_changes_filtered_out() const {return priv_->num_leaf_type_changes_filtered_out;} /// Setter for the number of filtered out leaf type change diff nodes. /// @param n the new number of filtered out leaf type change diff nodes. void corpus_diff::diff_stats::num_leaf_type_changes_filtered_out(size_t n) {priv_->num_leaf_type_changes_filtered_out = n;} /// Getter for the net number of leaf type change diff nodes. /// /// This is the difference between the number of leaf type changes and /// the number of filtered out leaf type changes. /// /// @return the net number of leaf type change diff nodes. size_t corpus_diff::diff_stats::net_num_leaf_type_changes() const {return num_leaf_type_changes() - num_leaf_type_changes_filtered_out();} /// Getter for the number of leaf function change diff nodes. /// /// @return the number of leaf function change diff nodes. size_t corpus_diff::diff_stats::num_leaf_func_changes() const {return priv_->num_leaf_func_changes;} /// Setter for the number of leaf function change diff nodes. /// /// @param n the new number of leaf function change diff nodes. void corpus_diff::diff_stats::num_leaf_func_changes(size_t n) {priv_->num_leaf_func_changes = n;} /// Getter for the number of leaf function change diff nodes that were /// filtered out. /// /// @return the number of leaf function change diff nodes that were /// filtered out. size_t corpus_diff::diff_stats::num_leaf_func_changes_filtered_out() const {return priv_->num_leaf_func_changes_filtered_out;} /// Setter for the number of leaf function change diff nodes that were /// filtered out. /// /// @param n the new number of leaf function change diff nodes that /// were filtered out. void corpus_diff::diff_stats::num_leaf_func_changes_filtered_out(size_t n) {priv_->num_leaf_func_changes_filtered_out = n;} /// Getter for the net number of leaf function change diff nodes. /// /// This is the difference between the number of leaf function change /// diff nodes and the number of filtered out leaf function change /// diff nodes. /// /// @return the net number of leaf function change diff nodes. size_t corpus_diff::diff_stats::net_num_leaf_func_changes() const {return num_leaf_func_changes() - num_leaf_func_changes_filtered_out();} /// Getter for the number of leaf variable change diff nodes. /// /// @return the number of leaf variable change diff nodes. size_t corpus_diff::diff_stats::num_leaf_var_changes() const {return priv_->num_leaf_var_changes;} /// Setter for the number of leaf variable change diff nodes. /// /// @param n the number of leaf variable change diff nodes. void corpus_diff::diff_stats::num_leaf_var_changes(size_t n) {priv_->num_leaf_var_changes = n;} /// Getter of the number of added types that are unreachable from the /// public interface of the ABI corpus. /// /// Public interface means the set of defined and publicly exported /// functions and variables of the ABI corpus. /// /// @return the number of added types that are unreachable from the /// public interface of the ABI corpus. size_t corpus_diff::diff_stats::num_added_unreachable_types() const {return priv_->num_added_unreachable_types;} /// Setter of the number of added types that are unreachable from the /// public interface (global functions or variables) of the ABI /// corpus. /// /// Public interface means the set of defined and publicly exported /// functions and variables of the ABI corpus. /// /// @param n the new number of added types that are unreachable from /// the public interface of the ABI corpus. void corpus_diff::diff_stats::num_added_unreachable_types(size_t n) {priv_->num_added_unreachable_types = n;} /// Getter of the number of added types that are unreachable from /// public interfaces and that are filtered out by suppression /// specifications. /// /// @return the number of added types that are unreachable from public /// interfaces and that are filtered out by suppression /// specifications. size_t corpus_diff::diff_stats::num_added_unreachable_types_filtered_out() const {return priv_->num_added_unreachable_types_filtered_out;} /// Setter of the number of added types that are unreachable from /// public interfaces and that are filtered out by suppression /// specifications. /// /// @param n the new number of added types that are unreachable from /// public interfaces and that are filtered out by suppression /// specifications. void corpus_diff::diff_stats::num_added_unreachable_types_filtered_out(size_t n) {priv_->num_added_unreachable_types_filtered_out = n;} /// Getter of the number of added types that are unreachable from /// public interfaces and that are *NOT* filtered out by suppression /// specifications. /// /// @return the number of added types that are unreachable from public /// interfaces and that are *NOT* filtered out by suppression /// specifications. size_t corpus_diff::diff_stats::net_num_added_unreachable_types() const { ABG_ASSERT(num_added_unreachable_types() >= num_added_unreachable_types_filtered_out()); return (num_added_unreachable_types() - num_added_unreachable_types_filtered_out()); } /// Getter of the number of removed types that are unreachable from /// the public interface of the ABI corpus. /// /// Public interface means the set of defined and publicly exported /// functions and variables of the ABI corpus. /// /// @return the number of removed types that are unreachable from /// the public interface of the ABI corpus. size_t corpus_diff::diff_stats::num_removed_unreachable_types() const {return priv_->num_removed_unreachable_types;} /// Setter of the number of removed types that are unreachable from /// the public interface of the ABI corpus. /// /// Public interface means the set of defined and publicly exported /// functions and variables of the ABI corpus. /// ///@param n the new number of removed types that are unreachable from /// the public interface of the ABI corpus. void corpus_diff::diff_stats::num_removed_unreachable_types(size_t n) {priv_->num_removed_unreachable_types = n;} /// Getter of the number of removed types that are not reachable from /// public interfaces and that have been filtered out by suppression /// specifications. /// /// @return the number of removed types that are not reachable from /// public interfaces and that have been filtered out by suppression /// specifications. size_t corpus_diff::diff_stats::num_removed_unreachable_types_filtered_out() const {return priv_->num_removed_unreachable_types_filtered_out;} /// Setter of the number of removed types that are not reachable from /// public interfaces and that have been filtered out by suppression /// specifications. /// /// @param n the new number of removed types that are not reachable /// from public interfaces and that have been filtered out by /// suppression specifications. void corpus_diff::diff_stats::num_removed_unreachable_types_filtered_out(size_t n) {priv_->num_removed_unreachable_types_filtered_out = n;} /// Getter of the number of removed types that are not reachable from /// public interfaces and that have *NOT* been filtered out by /// suppression specifications. /// /// @return the number of removed types that are not reachable from /// public interfaces and that have *NOT* been filtered out by /// suppression specifications. size_t corpus_diff::diff_stats::net_num_removed_unreachable_types() const { ABG_ASSERT(num_removed_unreachable_types() >= num_removed_unreachable_types_filtered_out()); return (num_removed_unreachable_types() - num_removed_unreachable_types_filtered_out()); } /// Getter of the number of changed types that are unreachable from /// the public interface of the ABI corpus. /// /// Public interface means the set of defined and publicly exported /// functions and variables of the ABI corpus. /// /// @return the number of changed types that are unreachable from the /// public interface of the ABI corpus. size_t corpus_diff::diff_stats::num_changed_unreachable_types() const {return priv_->num_changed_unreachable_types;} /// Setter of the number of changed types that are unreachable from /// the public interface of the ABI corpus. /// /// Public interface means the set of defined and publicly exported /// functions and variables of the ABI corpus. /// ///@param n the new number of changed types that are unreachable from /// the public interface of the ABI corpus. void corpus_diff::diff_stats::num_changed_unreachable_types(size_t n) {priv_->num_changed_unreachable_types = n;} /// Getter of the number of changed types that are unreachable from /// public interfaces and that have been filtered out by suppression /// specifications. /// /// @return the number of changed types that are unreachable from /// public interfaces and that have been filtered out by suppression /// specifications. size_t corpus_diff::diff_stats::num_changed_unreachable_types_filtered_out() const {return priv_->num_changed_unreachable_types_filtered_out;} /// Setter of the number of changed types that are unreachable from /// public interfaces and that have been filtered out by suppression /// specifications. /// /// @param n the new number of changed types that are unreachable from /// public interfaces and that have been filtered out by suppression /// specifications. void corpus_diff::diff_stats::num_changed_unreachable_types_filtered_out(size_t n) {priv_->num_changed_unreachable_types_filtered_out = n;} /// Getter of the number of changed types that are unreachable from /// public interfaces and that have *NOT* been filtered out by /// suppression specifications. /// /// @return the number of changed types that are unreachable from /// public interfaces and that have *NOT* been filtered out by /// suppression specifications. size_t corpus_diff::diff_stats::net_num_changed_unreachable_types() const { ABG_ASSERT(num_changed_unreachable_types() >= num_changed_unreachable_types_filtered_out()); return (num_changed_unreachable_types() - num_changed_unreachable_types_filtered_out()); } /// Getter for the number of leaf variable changes diff nodes that /// have been filtered out. /// /// @return the number of leaf variable changes diff nodes that have /// been filtered out. size_t corpus_diff::diff_stats::num_leaf_var_changes_filtered_out() const {return priv_->num_leaf_var_changes_filtered_out;} /// Setter for the number of leaf variable changes diff nodes that /// have been filtered out. /// /// @param n the number of leaf variable changes diff nodes that have /// been filtered out. void corpus_diff::diff_stats::num_leaf_var_changes_filtered_out(size_t n) {priv_->num_leaf_var_changes_filtered_out = n;} /// Getter for the net number of leaf variable change diff nodes. /// /// This the difference between the number of leaf variable change /// diff nodes and the number of filtered out leaf variable change /// diff nodes. /// /// @return the net number of leaf variable change diff nodes. size_t corpus_diff::diff_stats::net_num_leaf_var_changes() const {return num_leaf_var_changes() - num_leaf_var_changes_filtered_out();} // /// Getter of the context associated with this corpus. /// /// @return a smart pointer to the context associate with the corpus. diff_context_sptr corpus_diff::priv::get_context() {return ctxt_.lock();} /// Tests if the lookup tables are empty. /// /// @return true if the lookup tables are empty, false otherwise. bool corpus_diff::priv::lookup_tables_empty() const { return (deleted_fns_.empty() && added_fns_.empty() && changed_fns_map_.empty() && deleted_vars_.empty() && added_vars_.empty() && changed_vars_map_.empty()); } /// Clear the lookup tables useful for reporting an enum_diff. void corpus_diff::priv::clear_lookup_tables() { deleted_fns_.clear(); added_fns_.clear(); changed_fns_map_.clear(); deleted_vars_.clear(); added_vars_.clear(); changed_vars_map_.clear(); } /// If the lookup tables are not yet built, walk the differences and /// fill the lookup tables. void corpus_diff::priv::ensure_lookup_tables_populated() { if (!lookup_tables_empty()) return; diff_context_sptr ctxt = get_context(); { edit_script& e = fns_edit_script_; for (vector::const_iterator it = e.deletions().begin(); it != e.deletions().end(); ++it) { unsigned i = it->index(); ABG_ASSERT(i < first_->get_functions().size()); function_decl* deleted_fn = first_->get_functions()[i]; string n = deleted_fn->get_id(); ABG_ASSERT(!n.empty()); // The below is commented out because there can be several // functions with the same ID in the corpus. So several // functions with the same ID can be deleted. // ABG_ASSERT(deleted_fns_.find(n) == deleted_fns_.end()); deleted_fns_[n] = deleted_fn; } for (vector::const_iterator it = e.insertions().begin(); it != e.insertions().end(); ++it) { for (vector::const_iterator iit = it->inserted_indexes().begin(); iit != it->inserted_indexes().end(); ++iit) { unsigned i = *iit; function_decl* added_fn = second_->get_functions()[i]; string n = added_fn->get_id(); ABG_ASSERT(!n.empty()); // The below is commented out because there can be several // functions with the same ID in the corpus. So several // functions with the same ID can be added. // ABG_ASSERT(added_fns_.find(n) == added_fns_.end()); string_function_ptr_map::const_iterator j = deleted_fns_.find(n); if (j != deleted_fns_.end()) { function_decl_sptr f(j->second, noop_deleter()); function_decl_sptr s(added_fn, noop_deleter()); function_decl_diff_sptr d = compute_diff(f, s, ctxt); if (*j->second != *added_fn) changed_fns_map_[j->first] = d; deleted_fns_.erase(j); } else added_fns_[n] = added_fn; } } sort_string_function_decl_diff_sptr_map(changed_fns_map_, changed_fns_); // Now walk the allegedly deleted functions; check if their // underlying symbols are deleted as well; otherwise, consider // that the function in question hasn't been deleted. vector to_delete; for (string_function_ptr_map::const_iterator i = deleted_fns_.begin(); i != deleted_fns_.end(); ++i) if (second_->lookup_function_symbol(*i->second->get_symbol())) to_delete.push_back(i->first); for (vector::const_iterator i = to_delete.begin(); i != to_delete.end(); ++i) deleted_fns_.erase(*i); // Do something similar for added functions. to_delete.clear(); for (string_function_ptr_map::const_iterator i = added_fns_.begin(); i != added_fns_.end(); ++i) { if (first_->lookup_function_symbol(*i->second->get_symbol())) to_delete.push_back(i->first); else if (! i->second->get_symbol()->get_version().is_empty() && i->second->get_symbol()->get_version().is_default()) // We are looking for a symbol that has a default version, // and which seems to be newly added. Let's see if the same // symbol with *no* version was already present in the // former corpus. If yes, then the symbol shouldn't be // considered as 'added'. { elf_symbol::version empty_version; if (first_->lookup_function_symbol(i->second->get_symbol()->get_name(), empty_version)) to_delete.push_back(i->first); } } for (vector::const_iterator i = to_delete.begin(); i != to_delete.end(); ++i) added_fns_.erase(*i); } { edit_script& e = vars_edit_script_; for (vector::const_iterator it = e.deletions().begin(); it != e.deletions().end(); ++it) { unsigned i = it->index(); ABG_ASSERT(i < first_->get_variables().size()); var_decl* deleted_var = first_->get_variables()[i]; string n = deleted_var->get_id(); ABG_ASSERT(!n.empty()); ABG_ASSERT(deleted_vars_.find(n) == deleted_vars_.end()); deleted_vars_[n] = deleted_var; } for (vector::const_iterator it = e.insertions().begin(); it != e.insertions().end(); ++it) { for (vector::const_iterator iit = it->inserted_indexes().begin(); iit != it->inserted_indexes().end(); ++iit) { unsigned i = *iit; var_decl* added_var = second_->get_variables()[i]; string n = added_var->get_id(); ABG_ASSERT(!n.empty()); { string_var_ptr_map::const_iterator k = added_vars_.find(n); if ( k != added_vars_.end()) { ABG_ASSERT(is_member_decl(k->second) && get_member_is_static(k->second)); continue; } } string_var_ptr_map::const_iterator j = deleted_vars_.find(n); if (j != deleted_vars_.end()) { if (*j->second != *added_var) { var_decl_sptr f(j->second, noop_deleter()); var_decl_sptr s(added_var, noop_deleter()); changed_vars_map_[n] = compute_diff(f, s, ctxt); } deleted_vars_.erase(j); } else added_vars_[n] = added_var; } } sort_string_var_diff_sptr_map(changed_vars_map_, sorted_changed_vars_); // Now walk the allegedly deleted variables; check if their // underlying symbols are deleted as well; otherwise consider // that the variable in question hasn't been deleted. vector to_delete; for (string_var_ptr_map::const_iterator i = deleted_vars_.begin(); i != deleted_vars_.end(); ++i) if (second_->lookup_variable_symbol(*i->second->get_symbol())) to_delete.push_back(i->first); for (vector::const_iterator i = to_delete.begin(); i != to_delete.end(); ++i) deleted_vars_.erase(*i); // Do something similar for added variables. to_delete.clear(); for (string_var_ptr_map::const_iterator i = added_vars_.begin(); i != added_vars_.end(); ++i) if (first_->lookup_variable_symbol(*i->second->get_symbol())) to_delete.push_back(i->first); else if (! i->second->get_symbol()->get_version().is_empty() && i->second->get_symbol()->get_version().is_default()) // We are looking for a symbol that has a default version, // and which seems to be newly added. Let's see if the same // symbol with *no* version was already present in the // former corpus. If yes, then the symbol shouldn't be // considered as 'added'. { elf_symbol::version empty_version; if (first_->lookup_variable_symbol(i->second->get_symbol()->get_name(), empty_version)) to_delete.push_back(i->first); } for (vector::const_iterator i = to_delete.begin(); i != to_delete.end(); ++i) added_vars_.erase(*i); } // Massage the edit script for added/removed function symbols that // were not referenced by any debug info and turn them into maps of // {symbol_name, symbol}. { edit_script& e = unrefed_fn_syms_edit_script_; for (vector::const_iterator it = e.deletions().begin(); it != e.deletions().end(); ++it) { unsigned i = it->index(); ABG_ASSERT(i < first_->get_unreferenced_function_symbols().size()); elf_symbol_sptr deleted_sym = first_->get_unreferenced_function_symbols()[i]; if (!second_->lookup_function_symbol(*deleted_sym)) deleted_unrefed_fn_syms_[deleted_sym->get_id_string()] = deleted_sym; } for (vector::const_iterator it = e.insertions().begin(); it != e.insertions().end(); ++it) { for (vector::const_iterator iit = it->inserted_indexes().begin(); iit != it->inserted_indexes().end(); ++iit) { unsigned i = *iit; ABG_ASSERT(i < second_->get_unreferenced_function_symbols().size()); elf_symbol_sptr added_sym = second_->get_unreferenced_function_symbols()[i]; if ((deleted_unrefed_fn_syms_.find(added_sym->get_id_string()) == deleted_unrefed_fn_syms_.end())) { if (!first_->lookup_function_symbol(*added_sym)) { bool do_add = true; if (! added_sym->get_version().is_empty() && added_sym->get_version().is_default()) { // So added_seem has a default version. If // the former corpus had a symbol with the // same name as added_sym but with *no* // version, then added_sym shouldn't be // considered as a newly added symbol. elf_symbol::version empty_version; if (first_->lookup_function_symbol(added_sym->get_name(), empty_version)) do_add = false; } if (do_add) added_unrefed_fn_syms_[added_sym->get_id_string()] = added_sym; } } else deleted_unrefed_fn_syms_.erase(added_sym->get_id_string()); } } } // Massage the edit script for added/removed variable symbols that // were not referenced by any debug info and turn them into maps of // {symbol_name, symbol}. { edit_script& e = unrefed_var_syms_edit_script_; for (vector::const_iterator it = e.deletions().begin(); it != e.deletions().end(); ++it) { unsigned i = it->index(); ABG_ASSERT(i < first_->get_unreferenced_variable_symbols().size()); elf_symbol_sptr deleted_sym = first_->get_unreferenced_variable_symbols()[i]; if (!second_->lookup_variable_symbol(*deleted_sym)) deleted_unrefed_var_syms_[deleted_sym->get_id_string()] = deleted_sym; } for (vector::const_iterator it = e.insertions().begin(); it != e.insertions().end(); ++it) { for (vector::const_iterator iit = it->inserted_indexes().begin(); iit != it->inserted_indexes().end(); ++iit) { unsigned i = *iit; ABG_ASSERT(i < second_->get_unreferenced_variable_symbols().size()); elf_symbol_sptr added_sym = second_->get_unreferenced_variable_symbols()[i]; if (deleted_unrefed_var_syms_.find(added_sym->get_id_string()) == deleted_unrefed_var_syms_.end()) { if (!first_->lookup_variable_symbol(*added_sym)) { bool do_add = true; if (! added_sym->get_version().is_empty() && added_sym->get_version().is_default()) { // So added_seem has a default version. If // the former corpus had a symbol with the // same name as added_sym but with *no* // version, then added_sym shouldn't be // considered as a newly added symbol. elf_symbol::version empty_version; if (first_->lookup_variable_symbol(added_sym->get_name(), empty_version)) do_add = false; } if (do_add) added_unrefed_var_syms_[added_sym->get_id_string()] = added_sym; } } else deleted_unrefed_var_syms_.erase(added_sym->get_id_string()); } } } // Handle the unreachable_types_edit_script_ { edit_script& e = unreachable_types_edit_script_; // Populate the map of deleted unreachable types from the // deletions of the edit script. for (vector::const_iterator it = e.deletions().begin(); it != e.deletions().end(); ++it) { unsigned i = it->index(); type_base_sptr t (first_->get_types_not_reachable_from_public_interfaces()[i]); if (!is_user_defined_type(t)) continue; string repr = abigail::ir::get_pretty_representation(t, true); deleted_unreachable_types_[repr] = t; } // Populate the map of added and change unreachable types from the // insertions of the edit script. for (vector::const_iterator it = e.insertions().begin(); it != e.insertions().end(); ++it) { for (vector::const_iterator iit = it->inserted_indexes().begin(); iit != it->inserted_indexes().end(); ++iit) { unsigned i = *iit; type_base_sptr t (second_->get_types_not_reachable_from_public_interfaces()[i]); if (!is_user_defined_type(t)) continue; string repr = abigail::ir::get_pretty_representation(t, true); // Let's see if the inserted type we are looking at was // reported as deleted as well. // // If it's been deleted and a different version of it has // now been added, it means it's been *changed*. In that // case we'll compute the diff of that change and store it // in the map of changed unreachable types. // // Otherwise, it means the type's been added so we'll add // it to the set of added unreachable types. string_type_base_sptr_map::const_iterator j = deleted_unreachable_types_.find(repr); if (j != deleted_unreachable_types_.end()) { // So there was another type of the same pretty // representation which was reported as deleted. // Let's see if they are different or not ... decl_base_sptr old_type = is_decl(j->second); decl_base_sptr new_type = is_decl(t); if (old_type != new_type) { // The previously added type is different from this // one that is added. That means the initial type // was changed. Let's compute its diff and store it // as a changed type. diff_sptr d = compute_diff(old_type, new_type, ctxt); ABG_ASSERT(d->has_changes()); changed_unreachable_types_[repr]= d; } // In any case, the type was both deleted and added, // so we cannot have it marked as being deleted. So // let's remove it from the deleted types. deleted_unreachable_types_.erase(j); } else // The type wasn't previously reported as deleted, so // it's really added. added_unreachable_types_[repr] = t; } } } } /// Test if a change reports about a given @ref function_decl that is /// changed in a certain way is suppressed by a given suppression /// specifiation /// /// @param fn the @ref function_decl to consider. /// /// @param suppr the suppression specification to consider. /// /// @param k the kind of change that happened to @p fn. /// /// @param ctxt the context of the current diff. /// /// @return true iff the suppression specification @p suppr suppresses /// change reports about function @p fn, if that function changes in /// the way expressed by @p k. static bool function_is_suppressed(const function_decl* fn, const suppression_sptr suppr, function_suppression::change_kind k, const diff_context_sptr ctxt) { function_suppression_sptr fn_suppr = is_function_suppression(suppr); if (!fn_suppr) return false; return fn_suppr->suppresses_function(fn, k, ctxt); } /// Test if a change reports about a given @ref var_decl that is /// changed in a certain way is suppressed by a given suppression /// specifiation /// /// @param fn the @ref var_decl to consider. /// /// @param suppr the suppression specification to consider. /// /// @param k the kind of change that happened to @p fn. /// /// @param ctxt the context of the current diff. /// /// @return true iff the suppression specification @p suppr suppresses /// change reports about variable @p fn, if that variable changes in /// the way expressed by @p k. static bool variable_is_suppressed(const var_decl* var, const suppression_sptr suppr, variable_suppression::change_kind k, const diff_context_sptr ctxt) { variable_suppression_sptr var_suppr = is_variable_suppression(suppr); if (!var_suppr) return false; return var_suppr->suppresses_variable(var, k, ctxt); } /// Apply suppression specifications for this corpus diff to the set /// of added/removed functions/variables, as well as to types not /// reachable from global functions/variables. void corpus_diff::priv::apply_supprs_to_added_removed_fns_vars_unreachable_types() { diff_context_sptr ctxt = get_context(); const suppressions_type& suppressions = ctxt->suppressions(); for (suppressions_type::const_iterator i = suppressions.begin(); i != suppressions.end(); ++i) { // Added/Deleted functions. if (function_suppression_sptr fn_suppr = is_function_suppression(*i)) { // Added functions for (string_function_ptr_map::const_iterator e = added_fns_.begin(); e != added_fns_.end(); ++e) if (function_is_suppressed(e->second, fn_suppr, function_suppression::ADDED_FUNCTION_CHANGE_KIND, ctxt)) suppressed_added_fns_[e->first] = e->second; // Deleted functions. for (string_function_ptr_map::const_iterator e = deleted_fns_.begin(); e != deleted_fns_.end(); ++e) if (function_is_suppressed(e->second, fn_suppr, function_suppression::DELETED_FUNCTION_CHANGE_KIND, ctxt)) suppressed_deleted_fns_[e->first] = e->second; // Added function symbols not referenced by any debug info for (string_elf_symbol_map::const_iterator e = added_unrefed_fn_syms_.begin(); e != added_unrefed_fn_syms_.end(); ++e) if (fn_suppr->suppresses_function_symbol(e->second, function_suppression::ADDED_FUNCTION_CHANGE_KIND, ctxt)) suppressed_added_unrefed_fn_syms_[e->first] = e->second; // Removed function symbols not referenced by any debug info for (string_elf_symbol_map::const_iterator e = deleted_unrefed_fn_syms_.begin(); e != deleted_unrefed_fn_syms_.end(); ++e) if (fn_suppr->suppresses_function_symbol(e->second, function_suppression::DELETED_FUNCTION_CHANGE_KIND, ctxt)) suppressed_deleted_unrefed_fn_syms_[e->first] = e->second; } // Added/Delete virtual member functions changes that might be // suppressed by a type_suppression that matches the enclosing // class of the virtual member function. else if (type_suppression_sptr type_suppr = is_type_suppression(*i)) { // Added virtual functions for (string_function_ptr_map::const_iterator e = added_fns_.begin(); e != added_fns_.end(); ++e) if (is_member_function(e->second) && get_member_function_is_virtual(e->second)) { function_decl *f = e->second; class_decl_sptr c = is_class_type(is_method_type(f->get_type())->get_class_type()); ABG_ASSERT(c); if (type_suppr->suppresses_type(c, ctxt)) suppressed_added_fns_[e->first] = e->second; } // Deleted virtual functions for (string_function_ptr_map::const_iterator e = deleted_fns_.begin(); e != deleted_fns_.end(); ++e) if (is_member_function(e->second) && get_member_function_is_virtual(e->second)) { function_decl *f = e->second; class_decl_sptr c = is_class_type(is_method_type(f->get_type())->get_class_type()); ABG_ASSERT(c); if (type_suppr->suppresses_type(c, ctxt)) suppressed_deleted_fns_[e->first] = e->second; } // Apply this type suppression to deleted types // non-reachable from a public interface. for (string_type_base_sptr_map::const_iterator e = deleted_unreachable_types_.begin(); e != deleted_unreachable_types_.end(); ++e) if (type_suppr->suppresses_type(e->second, ctxt)) suppressed_deleted_unreachable_types_[e->first] = e->second; // Apply this type suppression to added types // non-reachable from a public interface. for (string_type_base_sptr_map::const_iterator e = added_unreachable_types_.begin(); e != added_unreachable_types_.end(); ++e) if (type_suppr->suppresses_type(e->second, ctxt)) suppressed_added_unreachable_types_[e->first] = e->second; } // Added/Deleted variables else if (variable_suppression_sptr var_suppr = is_variable_suppression(*i)) { // Added variables for (string_var_ptr_map::const_iterator e = added_vars_.begin(); e != added_vars_.end(); ++e) if (variable_is_suppressed(e->second, var_suppr, variable_suppression::ADDED_VARIABLE_CHANGE_KIND, ctxt)) suppressed_added_vars_[e->first] = e->second; //Deleted variables for (string_var_ptr_map::const_iterator e = deleted_vars_.begin(); e != deleted_vars_.end(); ++e) if (variable_is_suppressed(e->second, var_suppr, variable_suppression::DELETED_VARIABLE_CHANGE_KIND, ctxt)) suppressed_deleted_vars_[e->first] = e->second; // Added variable symbols not referenced by any debug info for (string_elf_symbol_map::const_iterator e = added_unrefed_var_syms_.begin(); e != added_unrefed_var_syms_.end(); ++e) if (var_suppr->suppresses_variable_symbol(e->second, variable_suppression::ADDED_VARIABLE_CHANGE_KIND, ctxt)) suppressed_added_unrefed_var_syms_[e->first] = e->second; // Removed variable symbols not referenced by any debug info for (string_elf_symbol_map::const_iterator e = deleted_unrefed_var_syms_.begin(); e != deleted_unrefed_var_syms_.end(); ++e) if (var_suppr->suppresses_variable_symbol(e->second, variable_suppression::DELETED_VARIABLE_CHANGE_KIND, ctxt)) suppressed_deleted_unrefed_var_syms_[e->first] = e->second; } } } /// Test if the change reports for a given deleted function have /// been deleted. /// /// @param fn the function to consider. /// /// @return true iff the change reports for a give given deleted /// function have been deleted. bool corpus_diff::priv::deleted_function_is_suppressed(const function_decl* fn) const { if (!fn) return false; string_function_ptr_map::const_iterator i = suppressed_deleted_fns_.find(fn->get_id()); return (i != suppressed_deleted_fns_.end()); } /// Test if an added type that is unreachable from public interface /// has been suppressed by a suppression specification. /// /// @param t the added unreachable type to be considered. /// /// @return true iff @p t has been suppressed by a suppression /// specification. bool corpus_diff::priv::added_unreachable_type_is_suppressed(const type_base *t)const { if (!t) return false; string repr = abigail::ir::get_pretty_representation(t, /*internal=*/true); string_type_base_sptr_map::const_iterator i = suppressed_added_unreachable_types_.find(repr); if (i == suppressed_added_unreachable_types_.end()) return false; return true; } /// Test if a deleted type that is unreachable from public interface /// has been suppressed by a suppression specification. /// /// @param t the deleted unreachable type to be considered. /// /// @return true iff @p t has been suppressed by a suppression /// specification. bool corpus_diff::priv::deleted_unreachable_type_is_suppressed(const type_base *t) const { if (!t) return false; string repr = abigail::ir::get_pretty_representation(t, /*internal=*/true); string_type_base_sptr_map::const_iterator i = suppressed_deleted_unreachable_types_.find(repr); if (i == suppressed_deleted_unreachable_types_.end()) return false; return true; } /// Test if the change reports for a give given added function has /// been deleted. /// /// @param fn the function to consider. /// /// @return true iff the change reports for a give given added /// function has been deleted. bool corpus_diff::priv::added_function_is_suppressed(const function_decl* fn) const { if (!fn) return false; string_function_ptr_map::const_iterator i = suppressed_added_fns_.find(fn->get_id()); return (i != suppressed_added_fns_.end()); } /// Test if the change reports for a give given deleted variable has /// been deleted. /// /// @param var the variable to consider. /// /// @return true iff the change reports for a give given deleted /// variable has been deleted. bool corpus_diff::priv::deleted_variable_is_suppressed(const var_decl* var) const { if (!var) return false; string_var_ptr_map::const_iterator i = suppressed_deleted_vars_.find(var->get_id()); return (i != suppressed_deleted_vars_.end()); } /// Test if the change reports for a given added variable have been /// suppressed. /// /// @param var the variable to consider. /// /// @return true iff the change reports for a given deleted /// variable has been deleted. bool corpus_diff::priv::added_variable_is_suppressed(const var_decl* var) const { if (!var) return false; string_var_ptr_map::const_iterator i = suppressed_added_vars_.find(var->get_id()); return (i != suppressed_added_vars_.end()); } /// Test if the change reports for a given deleted function symbol /// (that is not referenced by any debug info) has been suppressed. /// /// @param var the function to consider. /// /// @return true iff the change reports for a given deleted function /// symbol has been suppressed. bool corpus_diff::priv::deleted_unrefed_fn_sym_is_suppressed(const elf_symbol* s) const { if (!s) return false; string_elf_symbol_map::const_iterator i = suppressed_deleted_unrefed_fn_syms_.find(s->get_id_string()); return (i != suppressed_deleted_unrefed_fn_syms_.end()); } /// Test if the change reports for a given added function symbol /// (that is not referenced by any debug info) has been suppressed. /// /// @param var the function to consider. /// /// @return true iff the change reports for a given added function /// symbol has been suppressed. bool corpus_diff::priv::added_unrefed_fn_sym_is_suppressed(const elf_symbol* s) const { if (!s) return false; string_elf_symbol_map::const_iterator i = suppressed_added_unrefed_fn_syms_.find(s->get_id_string()); return (i != suppressed_added_unrefed_fn_syms_.end()); } /// Test if the change reports for a given deleted variable symbol /// (that is not referenced by any debug info) has been suppressed. /// /// @param var the variable to consider. /// /// @return true iff the change reports for a given deleted variable /// symbol has been suppressed. bool corpus_diff::priv::deleted_unrefed_var_sym_is_suppressed(const elf_symbol* s) const { if (!s) return false; string_elf_symbol_map::const_iterator i = suppressed_deleted_unrefed_var_syms_.find(s->get_id_string()); return (i != suppressed_deleted_unrefed_var_syms_.end()); } /// Test if the change reports for a given added variable symbol /// (that is not referenced by any debug info) has been suppressed. /// /// @param var the variable to consider. /// /// @return true iff the change reports for a given added variable /// symbol has been suppressed. bool corpus_diff::priv::added_unrefed_var_sym_is_suppressed(const elf_symbol* s) const { if (!s) return false; string_elf_symbol_map::const_iterator i = suppressed_added_unrefed_var_syms_.find(s->get_id_string()); return (i != suppressed_added_unrefed_var_syms_.end()); } #ifdef do_count_diff_map_changes #undef do_count_diff_map_changes #endif #define do_count_diff_map_changes(diff_map, n_changes, n_filtered) \ { \ string_diff_ptr_map::const_iterator i; \ for (i = diff_map.begin(); \ i != diff_map.end(); \ ++i) \ { \ if (const var_diff* d = is_var_diff(i->second)) \ if (is_data_member(d->first_var())) \ continue; \ \ if (i->second->has_local_changes()) \ ++n_changes; \ if (!i->second->get_canonical_diff()->to_be_reported()) \ ++n_filtered; \ } \ } /// Count the number of leaf changes as well as the number of the /// changes that have been filtered out. /// /// @param num_changes out parameter. This is set to the total number /// of leaf changes. /// /// @param num_filtered out parameter. This is set to the number of /// leaf changes that have been filtered out. void corpus_diff::priv::count_leaf_changes(size_t &num_changes, size_t &num_filtered) { count_leaf_type_changes(num_changes, num_filtered); // Now count the non-type changes. do_count_diff_map_changes(leaf_diffs_.get_function_decl_diff_map(), num_changes, num_filtered); do_count_diff_map_changes(leaf_diffs_.get_var_decl_diff_map(), num_changes, num_filtered); } /// Count the number of leaf *type* changes as well as the number of /// the leaf type changes that have been filtered out. /// /// @param num_changes out parameter. This is set to the total number /// of leaf type changes. /// /// @param num_filtered out parameter. This is set to the number of /// leaf type changes that have been filtered out. void corpus_diff::priv::count_leaf_type_changes(size_t &num_changes, size_t &num_filtered) { do_count_diff_map_changes(leaf_diffs_.get_type_decl_diff_map(), num_changes, num_filtered); do_count_diff_map_changes(leaf_diffs_.get_enum_diff_map(), num_changes, num_filtered); do_count_diff_map_changes(leaf_diffs_.get_class_diff_map(), num_changes, num_filtered); do_count_diff_map_changes(leaf_diffs_.get_union_diff_map(), num_changes, num_filtered); do_count_diff_map_changes(leaf_diffs_.get_typedef_diff_map(), num_changes, num_filtered); do_count_diff_map_changes(leaf_diffs_.get_array_diff_map(), num_changes, num_filtered); do_count_diff_map_changes(leaf_diffs_.get_distinct_diff_map(), num_changes, num_filtered); do_count_diff_map_changes(leaf_diffs_.get_fn_parm_diff_map(), num_changes, num_filtered); } /// Count the number of types not reachable from the interface (i.e, /// not reachable from global functions or variables). /// /// @param num_added this is set to the number of added types not /// reachable from the interface. /// /// @param num_deleted this is set to the number of deleted types not /// reachable from the interface. /// /// @param num_changed this is set to the number of changed types not /// reachable from the interface. /// /// @param num_filtered_added this is set to the number of added types /// not reachable from the interface and that have been filtered out /// by suppression specifications. /// /// @param num_filtered_deleted this is set to the number of deleted /// types not reachable from the interface and that have been filtered /// out by suppression specifications. /// /// @param num_filtered_changed this is set to the number of changed /// types not reachable from the interface and that have been filtered /// out by suppression specifications. void corpus_diff::priv::count_unreachable_types(size_t &num_added, size_t &num_deleted, size_t &num_changed, size_t &num_filtered_added, size_t &num_filtered_deleted, size_t &num_filtered_changed) { num_added = added_unreachable_types_.size(); num_deleted = deleted_unreachable_types_.size(); num_changed = changed_unreachable_types_.size(); num_filtered_added = suppressed_added_unreachable_types_.size(); num_filtered_deleted = suppressed_deleted_unreachable_types_.size(); for (vector::const_iterator i = changed_unreachable_types_sorted().begin(); i != changed_unreachable_types_sorted().end(); ++i) if (!(*i)->to_be_reported()) ++num_filtered_changed; } /// Get the sorted vector of diff nodes representing changed /// unreachable types. /// /// Upon the first invocation of this method, if the vector is empty, /// this function gets the diff nodes representing changed /// unreachable, sort them, and return the sorted vector. /// /// @return the sorted vector of diff nodes representing changed /// unreachable types. const vector& corpus_diff::priv::changed_unreachable_types_sorted() const { if (changed_unreachable_types_sorted_.empty()) if (!changed_unreachable_types_.empty()) sort_string_diff_sptr_map(changed_unreachable_types_, changed_unreachable_types_sorted_); return changed_unreachable_types_sorted_; } /// Compute the diff stats. /// /// To know the number of functions that got filtered out, this /// function applies the categorizing filters to the diff sub-trees of /// each function changes diff, prior to calculating the stats. /// /// @param num_removed the number of removed functions. /// /// @param num_added the number of added functions. /// /// @param num_changed the number of changed functions. /// /// @param num_filtered_out the number of changed functions that are /// got filtered out from the report void corpus_diff::priv::apply_filters_and_compute_diff_stats(diff_stats& stat) { stat.num_func_removed(deleted_fns_.size()); stat.num_removed_func_filtered_out(suppressed_deleted_fns_.size()); stat.num_func_added(added_fns_.size()); stat.num_added_func_filtered_out(suppressed_added_fns_.size()); stat.num_func_changed(changed_fns_map_.size()); stat.num_vars_removed(deleted_vars_.size()); stat.num_removed_vars_filtered_out(suppressed_deleted_vars_.size()); stat.num_vars_added(added_vars_.size()); stat.num_added_vars_filtered_out(suppressed_added_vars_.size()); stat.num_vars_changed(changed_vars_map_.size()); diff_context_sptr ctxt = get_context(); // Walk the changed function diff nodes to apply the categorization // filters. diff_sptr diff; for (function_decl_diff_sptrs_type::const_iterator i = changed_fns_.begin(); i != changed_fns_.end(); ++i) { diff_sptr diff = *i; ctxt->maybe_apply_filters(diff); } // Walk the changed variable diff nodes to apply the categorization // filters. for (var_diff_sptrs_type::const_iterator i = sorted_changed_vars_.begin(); i != sorted_changed_vars_.end(); ++i) { diff_sptr diff = *i; ctxt->maybe_apply_filters(diff); } // walk the changed unreachable types to apply categorization // filters for (diff_sptrs_type::const_iterator i = changed_unreachable_types_sorted().begin(); i != changed_unreachable_types_sorted().end(); ++i) { diff_sptr diff = *i; ctxt->maybe_apply_filters(diff); } categorize_redundant_changed_sub_nodes(); // Walk the changed function diff nodes to count the number of // filtered-out functions and the number of functions with virtual // offset changes. for (function_decl_diff_sptrs_type::const_iterator i = changed_fns_.begin(); i != changed_fns_.end(); ++i) { if ((*i)->is_filtered_out()) { stat.num_changed_func_filtered_out (stat.num_changed_func_filtered_out() + 1); if ((*i)->has_local_changes()) stat.num_leaf_func_changes_filtered_out (stat.num_leaf_func_changes_filtered_out() + 1); } else { if ((*i)->get_category() & VIRTUAL_MEMBER_CHANGE_CATEGORY) stat.num_func_with_virtual_offset_changes (stat.num_func_with_virtual_offset_changes() + 1); } if ((*i)->has_local_changes()) stat.num_leaf_func_changes (stat.num_leaf_func_changes() + 1); } // Walk the changed variables diff nodes to count the number of // filtered-out variables. for (var_diff_sptrs_type ::const_iterator i = sorted_changed_vars_.begin(); i != sorted_changed_vars_.end(); ++i) { if ((*i)->is_filtered_out()) { stat.num_changed_vars_filtered_out (stat.num_changed_vars_filtered_out() + 1); if ((*i)->has_local_changes()) stat.num_leaf_var_changes_filtered_out (stat.num_leaf_var_changes_filtered_out() + 1); } if ((*i)->has_local_changes()) stat.num_leaf_var_changes (stat.num_leaf_var_changes() + 1); } stat.num_func_syms_added(added_unrefed_fn_syms_.size()); stat.num_added_func_syms_filtered_out(suppressed_added_unrefed_fn_syms_.size()); stat.num_func_syms_removed(deleted_unrefed_fn_syms_.size()); stat.num_removed_func_syms_filtered_out(suppressed_deleted_unrefed_fn_syms_.size()); stat.num_var_syms_added(added_unrefed_var_syms_.size()); stat.num_added_var_syms_filtered_out(suppressed_added_unrefed_var_syms_.size()); stat.num_var_syms_removed(deleted_unrefed_var_syms_.size()); stat.num_removed_var_syms_filtered_out(suppressed_deleted_unrefed_var_syms_.size()); // Walk the general leaf type diff nodes to count them { size_t num_type_changes = 0, num_type_filtered = 0; count_leaf_type_changes(num_type_changes, num_type_filtered); stat.num_leaf_type_changes(num_type_changes); stat.num_leaf_type_changes_filtered_out(num_type_filtered); } // Walk the general leaf artefacts diff nodes to count them { size_t num_changes = 0, num_filtered = 0; count_leaf_changes(num_changes, num_filtered); stat.num_leaf_changes(num_changes); stat.num_leaf_changes_filtered_out(num_filtered); } // Walk the unreachable types to count them { size_t num_added_unreachable_types = 0, num_changed_unreachable_types = 0, num_deleted_unreachable_types = 0, num_added_unreachable_types_filtered = 0, num_changed_unreachable_types_filtered = 0, num_deleted_unreachable_types_filtered = 0; count_unreachable_types(num_added_unreachable_types, num_deleted_unreachable_types, num_changed_unreachable_types, num_added_unreachable_types_filtered, num_deleted_unreachable_types_filtered, num_changed_unreachable_types_filtered); stat.num_added_unreachable_types(num_added_unreachable_types); stat.num_removed_unreachable_types(num_deleted_unreachable_types); stat.num_changed_unreachable_types(num_changed_unreachable_types); stat.num_added_unreachable_types_filtered_out (num_added_unreachable_types_filtered); stat.num_removed_unreachable_types_filtered_out (num_deleted_unreachable_types_filtered); stat.num_changed_unreachable_types_filtered_out (num_changed_unreachable_types_filtered); } } /// Emit the summary of the functions & variables that got /// removed/changed/added. /// /// TODO: This should be handled by the reporters, just like what is /// done for reporter_base::diff_to_be_reported. /// /// @param out the output stream to emit the stats to. /// /// @param indent the indentation string to use in the summary. void corpus_diff::priv::emit_diff_stats(const diff_stats& s, ostream& out, const string& indent) { /// Report added/removed/changed functions. size_t net_num_leaf_changes = s.net_num_func_removed() + s.net_num_func_added() + s.net_num_leaf_func_changes() + s.net_num_vars_removed() + s.net_num_vars_added() + s.net_num_leaf_var_changes() + s.net_num_leaf_type_changes() + s.net_num_removed_func_syms() + s.net_num_added_func_syms() + s.net_num_removed_var_syms() + s.net_num_added_var_syms(); if (!sonames_equal_) out << indent << "ELF SONAME changed\n"; if (!architectures_equal_) out << indent << "ELF architecture changed\n"; diff_context_sptr ctxt = get_context(); if (ctxt->show_leaf_changes_only()) { out << "Leaf changes summary: "; out << net_num_leaf_changes << " artifact"; if (net_num_leaf_changes > 1) out << "s"; out << " changed"; if (size_t num_filtered = s.num_leaf_changes_filtered_out()) out << " (" << num_filtered << " filtered out)"; out << "\n"; out << indent << "Changed leaf types summary: " << s.net_num_leaf_type_changes(); if (s.num_leaf_type_changes_filtered_out()) out << " (" << s.num_leaf_type_changes_filtered_out() << " filtered out)"; out << " leaf type"; if (s.num_leaf_type_changes() > 1) out << "s"; out << " changed\n"; // function changes summary out << indent << "Removed/Changed/Added functions summary: "; out << s.net_num_func_removed() << " Removed"; if (s.num_removed_func_filtered_out()) out << " (" << s.num_removed_func_filtered_out() << " filtered out)"; out << ", "; out << s.net_num_leaf_func_changes() << " Changed"; if (s.num_leaf_func_changes_filtered_out()) out << " (" << s.num_leaf_func_changes_filtered_out() << " filtered out)"; out << ", "; out << s.net_num_func_added()<< " Added "; if (s.net_num_func_added() <= 1) out << "function"; else out << "functions"; if (s.num_added_func_filtered_out()) out << " (" << s.num_added_func_filtered_out() << " filtered out)"; out << "\n"; // variables changes summary out << indent << "Removed/Changed/Added variables summary: "; out << s.net_num_vars_removed() << " Removed"; if (s.num_removed_vars_filtered_out()) out << " (" << s.num_removed_vars_filtered_out() << " filtered out)"; out << ", "; out << s.net_num_leaf_var_changes() << " Changed"; if (s.num_leaf_var_changes_filtered_out()) out << " (" << s.num_leaf_var_changes_filtered_out() << " filtered out)"; out << ", "; out << s.net_num_vars_added() << " Added "; if (s.net_num_vars_added() <= 1) out << "variable"; else out << "variables"; if (s.num_added_vars_filtered_out()) out << " (" << s.num_added_vars_filtered_out() << " filtered out)"; out << "\n"; } else // if (ctxt->show_leaf_changes_only()) { size_t total_nb_function_changes = s.num_func_removed() + s.num_func_changed() + s.num_func_added(); // function changes summary out << indent << "Functions changes summary: "; out << s.net_num_func_removed() << " Removed"; if (s.num_removed_func_filtered_out()) out << " (" << s.num_removed_func_filtered_out() << " filtered out)"; out << ", "; out << s.net_num_func_changed() << " Changed"; if (s.num_changed_func_filtered_out()) out << " (" << s.num_changed_func_filtered_out() << " filtered out)"; out << ", "; out << s.net_num_func_added() << " Added"; if (s.num_added_func_filtered_out()) out << " (" << s.num_added_func_filtered_out() << " filtered out)"; if (total_nb_function_changes <= 1) out << " function"; else out << " functions"; out << "\n"; // variables changes summary size_t total_nb_variable_changes = s.num_vars_removed() + s.num_vars_changed() + s.num_vars_added(); out << indent << "Variables changes summary: "; out << s.net_num_vars_removed() << " Removed"; if (s.num_removed_vars_filtered_out()) out << " (" << s.num_removed_vars_filtered_out() << " filtered out)"; out << ", "; out << s.num_vars_changed() - s.num_changed_vars_filtered_out() << " Changed"; if (s.num_changed_vars_filtered_out()) out << " (" << s.num_changed_vars_filtered_out() << " filtered out)"; out << ", "; out << s.net_num_vars_added() << " Added"; if (s.num_added_vars_filtered_out()) out << " (" << s.num_added_vars_filtered_out() << " filtered out)"; if (total_nb_variable_changes <= 1) out << " variable"; else out << " variables"; out << "\n"; } // Show statistics about types not reachable from global // functions/variables. if (ctxt->show_unreachable_types()) { size_t total_nb_unreachable_type_changes = s.num_removed_unreachable_types() + s.num_changed_unreachable_types() + s.num_added_unreachable_types(); // Show summary of unreachable types out << indent << "Unreachable types summary: " << s.net_num_removed_unreachable_types() << " removed"; if (s.num_removed_unreachable_types_filtered_out()) out << " (" << s.num_removed_unreachable_types_filtered_out() << " filtered out)"; out << ", "; out << s.net_num_changed_unreachable_types() << " changed"; if (s.num_changed_unreachable_types_filtered_out()) out << " (" << s.num_changed_unreachable_types_filtered_out() << " filtered out)"; out << ", "; out << s.net_num_added_unreachable_types() << " added"; if (s.num_added_unreachable_types_filtered_out()) out << " (" << s.num_added_unreachable_types_filtered_out() << " filtered out)"; if (total_nb_unreachable_type_changes <= 1) out << " type"; else out << " types"; out << "\n"; } if (ctxt->show_symbols_unreferenced_by_debug_info() && (s.num_func_syms_removed() || s.num_func_syms_added() || s.num_var_syms_removed() || s.num_var_syms_added())) { // function symbols changes summary. if (!ctxt->show_added_symbols_unreferenced_by_debug_info() && s.num_func_syms_removed() == 0 && s.num_func_syms_added() != 0) // If the only unreferenced function symbol change is function // syms that got added, but we were forbidden to show function // syms being added, do nothing. ; else { out << indent << "Function symbols changes summary: " << s.net_num_removed_func_syms() << " Removed"; if (s.num_removed_func_syms_filtered_out()) out << " (" << s.num_removed_func_syms_filtered_out() << " filtered out)"; out << ", "; out << s.net_num_added_func_syms() << " Added"; if (s.num_added_func_syms_filtered_out()) out << " (" << s.num_added_func_syms_filtered_out() << " filtered out)"; out << " function symbol"; if (s.num_func_syms_added() + s.num_func_syms_removed() > 1) out << "s"; out << " not referenced by debug info\n"; } // variable symbol changes summary. if (!ctxt->show_added_symbols_unreferenced_by_debug_info() && s.num_var_syms_removed() == 0 && s.num_var_syms_added() != 0) // If the only unreferenced variable symbol change is variable // syms that got added, but we were forbidden to show variable // syms being added, do nothing. ; else { out << indent << "Variable symbols changes summary: " << s.net_num_removed_var_syms() << " Removed"; if (s.num_removed_var_syms_filtered_out()) out << " (" << s.num_removed_var_syms_filtered_out() << " filtered out)"; out << ", "; out << s.net_num_added_var_syms() << " Added"; if (s.num_added_var_syms_filtered_out()) out << " (" << s.num_added_var_syms_filtered_out() << " filtered out)"; out << " variable symbol"; if (s.num_var_syms_added() + s.num_var_syms_removed() > 1) out << "s"; out << " not referenced by debug info\n"; } } } /// Walk the changed functions and variables diff nodes to categorize /// redundant nodes. void corpus_diff::priv::categorize_redundant_changed_sub_nodes() { diff_sptr diff; diff_context_sptr ctxt = get_context(); ctxt->forget_visited_diffs(); for (function_decl_diff_sptrs_type::const_iterator i = changed_fns_.begin(); i!= changed_fns_.end(); ++i) { diff = *i; categorize_redundancy(diff); } for (var_diff_sptrs_type::const_iterator i = sorted_changed_vars_.begin(); i!= sorted_changed_vars_.end(); ++i) { diff_sptr diff = *i; categorize_redundancy(diff); } for (diff_sptrs_type::const_iterator i = changed_unreachable_types_sorted().begin(); i!= changed_unreachable_types_sorted().end(); ++i) { diff_sptr diff = *i; categorize_redundancy(diff); } } /// Walk the changed functions and variables diff nodes and clear the /// redundancy categorization they might carry. void corpus_diff::priv::clear_redundancy_categorization() { diff_sptr diff; for (function_decl_diff_sptrs_type::const_iterator i = changed_fns_.begin(); i!= changed_fns_.end(); ++i) { diff = *i; abigail::comparison::clear_redundancy_categorization(diff); } for (var_diff_sptrs_type::const_iterator i = sorted_changed_vars_.begin(); i!= sorted_changed_vars_.end(); ++i) { diff = *i; abigail::comparison::clear_redundancy_categorization(diff); } } /// If the user asked to dump the diff tree node (for changed /// variables and functions) on the error output stream, then just do /// that. /// /// This function is used for debugging purposes. void corpus_diff::priv::maybe_dump_diff_tree() { diff_context_sptr ctxt = get_context(); if (!ctxt->dump_diff_tree() || ctxt->error_output_stream() == 0) return; if (!changed_fns_.empty()) { *ctxt->error_output_stream() << "changed functions diff tree: \n\n"; for (function_decl_diff_sptrs_type::const_iterator i = changed_fns_.begin(); i != changed_fns_.end(); ++i) { diff_sptr d = *i; print_diff_tree(d, *ctxt->error_output_stream()); } } if (!sorted_changed_vars_.empty()) { *ctxt->error_output_stream() << "\nchanged variables diff tree: \n\n"; for (var_diff_sptrs_type::const_iterator i = sorted_changed_vars_.begin(); i != sorted_changed_vars_.end(); ++i) { diff_sptr d = *i; print_diff_tree(d, *ctxt->error_output_stream()); } } if (!changed_unreachable_types_sorted().empty()) { *ctxt->error_output_stream() << "\nchanged unreachable " "types diff tree: \n\n"; for (vector::const_iterator i = changed_unreachable_types_sorted().begin(); i != changed_unreachable_types_sorted().end(); ++i) { diff_sptr d = *i; print_diff_tree(d, *ctxt->error_output_stream()); } } } /// Populate the vector of children node of the @ref corpus_diff type. /// /// The children node can then later be retrieved using /// corpus_diff::children_node(). void corpus_diff::chain_into_hierarchy() { for (function_decl_diff_sptrs_type::const_iterator i = changed_functions_sorted().begin(); i != changed_functions_sorted().end(); ++i) if (diff_sptr d = *i) append_child_node(d); } /// Constructor for @ref corpus_diff. /// /// @param first the first corpus of the diff. /// /// @param second the second corpus of the diff. /// /// @param ctxt the diff context to use. Note that this context /// object must stay alive at least during the life time of the /// current instance of @ref corpus_diff. Otherwise memory corruption /// issues occur. corpus_diff::corpus_diff(corpus_sptr first, corpus_sptr second, diff_context_sptr ctxt) : priv_(new priv(first, second, ctxt)) {} corpus_diff::~corpus_diff() = default; /// Finish building the current instance of @ref corpus_diff. void corpus_diff::finish_diff_type() { if (priv_->finished_) return; chain_into_hierarchy(); priv_->finished_ = true; } /// @return the first corpus of the diff. corpus_sptr corpus_diff::first_corpus() const {return priv_->first_;} /// @return the second corpus of the diff. corpus_sptr corpus_diff::second_corpus() const {return priv_->second_;} /// @return the children nodes of the current instance of corpus_diff. const vector& corpus_diff::children_nodes() const {return priv_->children_;} /// Append a new child node to the vector of children nodes for the /// current instance of @ref corpus_diff node. /// /// Note that the vector of children nodes for the current instance of /// @ref corpus_diff node must remain sorted, using /// diff_less_than_functor. /// /// @param d the new child node. Note that the life time of the /// object held by @p d will thus equal the life time of the current /// instance of @ref corpus_diff. void corpus_diff::append_child_node(diff_sptr d) { ABG_ASSERT(d); diff_less_than_functor is_less_than; bool inserted = false; for (vector::iterator i = priv_->children_.begin(); i != priv_->children_.end(); ++i) // Look for the point where to insert the diff child node. if (!is_less_than(d.get(), *i)) { context()->keep_diff_alive(d); priv_->children_.insert(i, d.get()); // As we have just inserted 'd' into the vector, the iterator // 'i' is invalidated. We must *NOT* use it anymore. inserted = true; break; } if (!inserted) { context()->keep_diff_alive(d); // We didn't insert anything to the vector, presumably b/c it was // empty or had one element that was "less than" 'd'. We can thus // just append 'd' to the end of the vector. priv_->children_.push_back(d.get()); } } /// @return the bare edit script of the functions changed as recorded /// by the diff. edit_script& corpus_diff::function_changes() const {return priv_->fns_edit_script_;} /// @return the bare edit script of the variables changed as recorded /// by the diff. edit_script& corpus_diff::variable_changes() const {return priv_->vars_edit_script_;} /// Test if the soname of the underlying corpus has changed. /// /// @return true iff the soname has changed. bool corpus_diff::soname_changed() const {return !priv_->sonames_equal_;} /// Test if the architecture of the underlying corpus has changed. /// /// @return true iff the architecture has changed. bool corpus_diff::architecture_changed() const {return !priv_->architectures_equal_;} /// Getter for the deleted functions of the diff. /// /// @return the the deleted functions of the diff. const string_function_ptr_map& corpus_diff::deleted_functions() const {return priv_->deleted_fns_;} /// Getter for the added functions of the diff. /// /// @return the added functions of the diff. const string_function_ptr_map& corpus_diff::added_functions() {return priv_->added_fns_;} /// Getter for the functions which signature didn't change, but which /// do have some indirect changes in their parms. /// /// @return a non-sorted map of functions which signature didn't /// change, but which do have some indirect changes in their parms. /// The key of the map is a unique identifier for the function; it's /// usually made of the name and version of the underlying ELF symbol /// of the function for corpora that were built from ELF files. const string_function_decl_diff_sptr_map& corpus_diff::changed_functions() {return priv_->changed_fns_map_;} /// Getter for a sorted vector of functions which signature didn't /// change, but which do have some indirect changes in their parms. /// /// @return a sorted vector of functions which signature didn't /// change, but which do have some indirect changes in their parms. const function_decl_diff_sptrs_type& corpus_diff::changed_functions_sorted() {return priv_->changed_fns_;} /// Getter for the variables that got deleted from the first subject /// of the diff. /// /// @return the map of deleted variable. const string_var_ptr_map& corpus_diff::deleted_variables() const {return priv_->deleted_vars_;} /// Getter for the added variables of the diff. /// /// @return the map of added variable. const string_var_ptr_map& corpus_diff::added_variables() const {return priv_->added_vars_;} /// Getter for the non-sorted map of variables which signature didn't /// change but which do have some indirect changes in some sub-types. /// /// @return the non-sorted map of changed variables. const string_var_diff_sptr_map& corpus_diff::changed_variables() {return priv_->changed_vars_map_;} /// Getter for the sorted vector of variables which signature didn't /// change but which do have some indirect changes in some sub-types. /// /// @return a sorted vector of changed variables. const var_diff_sptrs_type& corpus_diff::changed_variables_sorted() {return priv_->sorted_changed_vars_;} /// Getter for function symbols not referenced by any debug info and /// that got deleted. /// /// @return a map of elf function symbols not referenced by any debug /// info and that got deleted. const string_elf_symbol_map& corpus_diff::deleted_unrefed_function_symbols() const {return priv_->deleted_unrefed_fn_syms_;} /// Getter for function symbols not referenced by any debug info and /// that got added. /// /// @return a map of elf function symbols not referenced by any debug /// info and that got added. const string_elf_symbol_map& corpus_diff::added_unrefed_function_symbols() const {return priv_->added_unrefed_fn_syms_;} /// Getter for variable symbols not referenced by any debug info and /// that got deleted. /// /// @return a map of elf variable symbols not referenced by any debug /// info and that got deleted. const string_elf_symbol_map& corpus_diff::deleted_unrefed_variable_symbols() const {return priv_->deleted_unrefed_var_syms_;} /// Getter for variable symbols not referenced by any debug info and /// that got added. /// /// @return a map of elf variable symbols not referenced by any debug /// info and that got added. const string_elf_symbol_map& corpus_diff::added_unrefed_variable_symbols() const {return priv_->added_unrefed_var_syms_;} /// Getter for a map of deleted types that are not reachable from /// global functions/variables. /// /// @return a map that associates pretty representation of deleted /// unreachable types and said types. const string_type_base_sptr_map& corpus_diff::deleted_unreachable_types() const {return priv_->deleted_unreachable_types_;} /// Getter of a sorted vector of deleted types that are not reachable /// from global functions/variables. /// /// @return a sorted vector of deleted types that are not reachable /// from global functions/variables. The types are lexicographically /// sorted by considering their pretty representation. const vector& corpus_diff::deleted_unreachable_types_sorted() const { if (priv_->deleted_unreachable_types_sorted_.empty()) if (!priv_->deleted_unreachable_types_.empty()) sort_string_type_base_sptr_map(priv_->deleted_unreachable_types_, priv_->deleted_unreachable_types_sorted_); return priv_->deleted_unreachable_types_sorted_; } /// Getter for a map of added types that are not reachable from global /// functions/variables. /// /// @return a map that associates pretty representation of added /// unreachable types and said types. const string_type_base_sptr_map& corpus_diff::added_unreachable_types() const {return priv_->added_unreachable_types_;} /// Getter of a sorted vector of added types that are not reachable /// from global functions/variables. /// /// @return a sorted vector of added types that are not reachable from /// global functions/variables. The types are lexicographically /// sorted by considering their pretty representation. const vector& corpus_diff::added_unreachable_types_sorted() const { if (priv_->added_unreachable_types_sorted_.empty()) if (!priv_->added_unreachable_types_.empty()) sort_string_type_base_sptr_map(priv_->added_unreachable_types_, priv_->added_unreachable_types_sorted_); return priv_->added_unreachable_types_sorted_; } /// Getter for a map of changed types that are not reachable from /// global functions/variables. /// /// @return a map that associates pretty representation of changed /// unreachable types and said types. const string_diff_sptr_map& corpus_diff::changed_unreachable_types() const {return priv_->changed_unreachable_types_;} /// Getter of a sorted vector of changed types that are not reachable /// from global functions/variables. /// /// @return a sorted vector of changed types that are not reachable /// from global functions/variables. The diffs are lexicographically /// sorted by considering their pretty representation. const vector& corpus_diff::changed_unreachable_types_sorted() const {return priv_->changed_unreachable_types_sorted();} /// Getter of the diff context of this diff /// /// @return the diff context for this diff. const diff_context_sptr corpus_diff::context() const {return priv_->get_context();} /// @return the pretty representation for the current instance of @ref /// corpus_diff const string& corpus_diff::get_pretty_representation() const { if (priv_->pretty_representation_.empty()) { std::ostringstream o; o << "corpus_diff[" << first_corpus()->get_path() << ", " << second_corpus()->get_path() << "]"; priv_->pretty_representation_ = o.str(); } return priv_->pretty_representation_; } /// Return true iff the current @ref corpus_diff node carries a /// change. /// /// @return true iff the current diff node carries a change. bool corpus_diff::has_changes() const { return (soname_changed() || architecture_changed() || !(priv_->deleted_fns_.empty() && priv_->added_fns_.empty() && priv_->changed_fns_map_.empty() && priv_->deleted_vars_.empty() && priv_->added_vars_.empty() && priv_->changed_vars_map_.empty() && priv_->added_unrefed_fn_syms_.empty() && priv_->deleted_unrefed_fn_syms_.empty() && priv_->added_unrefed_var_syms_.empty() && priv_->deleted_unrefed_var_syms_.empty() && priv_->deleted_unreachable_types_.empty() && priv_->added_unreachable_types_.empty() && priv_->changed_unreachable_types_.empty())); } /// Test if the current instance of @ref corpus_diff carries changes /// that we are sure are incompatible. By incompatible change we mean /// a change that "breaks" the ABI of the corpus we are looking at. /// /// In concrete terms, this function considers the following changes /// as being ABI incompatible for sure: /// /// - a soname change /// - if exported functions or variables got removed /// /// Note that subtype changes *can* represent changes that break ABI /// too. But they also can be changes that are OK, ABI-wise. /// /// It's up to the user to provide suppression specifications to say /// explicitely which subtype change is OK. The remaining sub-type /// changes are then considered to be ABI incompatible. But to test /// if such ABI incompatible subtype changes are present you need to /// use the function @ref corpus_diff::has_net_subtype_changes() /// /// @return true iff the current instance of @ref corpus_diff carries /// changes that we are sure are ABI incompatible. bool corpus_diff::has_incompatible_changes() const { const diff_stats& stats = const_cast(this)-> apply_filters_and_suppressions_before_reporting(); return (soname_changed() || architecture_changed() || stats.net_num_func_removed() != 0 || (stats.num_func_with_virtual_offset_changes() != 0 // If all reports about functions with sub-type changes // have been suppressed, then even those about functions // that are virtual don't matter anymore because the // user willingly requested to shut them down && stats.net_num_func_changed() != 0) || stats.net_num_vars_removed() != 0 || stats.net_num_removed_func_syms() != 0 || stats.net_num_removed_var_syms() != 0 || stats.net_num_removed_unreachable_types() != 0 || stats.net_num_changed_unreachable_types() != 0); } /// Test if the current instance of @ref corpus_diff carries subtype /// changes whose reports are not suppressed by any suppression /// specification. In effect, these are deemed incompatible ABI /// changes. /// /// @return true iff the the current instance of @ref corpus_diff /// carries subtype changes that are deemed incompatible ABI changes. bool corpus_diff::has_net_subtype_changes() const { const diff_stats& stats = const_cast(this)-> apply_filters_and_suppressions_before_reporting(); return (stats.net_num_func_changed() != 0 || stats.net_num_vars_changed() != 0 || stats.net_num_removed_unreachable_types() != 0 || stats.net_num_changed_unreachable_types() != 0); } /// Test if the current instance of @ref corpus_diff carries changes /// whose reports are not suppressed by any suppression specification. /// In effect, these are deemed incompatible ABI changes. /// /// @return true iff the the current instance of @ref corpus_diff /// carries subtype changes that are deemed incompatible ABI changes. bool corpus_diff::has_net_changes() const {return context()->get_reporter()->diff_has_net_changes(this);} /// Apply the different filters that are registered to be applied to /// the diff tree; that includes the categorization filters. Also, /// apply the suppression interpretation filters. /// /// After the filters are applied, this function calculates some /// statistics about the changes carried by the current instance of /// @ref corpus_diff. These statistics are represented by an instance /// of @ref corpus_diff::diff_stats. /// /// This member function is called by the reporting function /// corpus_diff::report(). /// /// Note that for a given instance of corpus_diff, this function /// applies the filters and suppressions only the first time it is /// invoked. Subsequent invocations just return the instance of /// corpus_diff::diff_stats that was cached after the first /// invocation. /// /// @return a reference to the statistics about the changes carried by /// the current instance of @ref corpus_diff. const corpus_diff::diff_stats& corpus_diff::apply_filters_and_suppressions_before_reporting() { if (priv_->diff_stats_) return *priv_->diff_stats_; apply_suppressions(this); priv_->diff_stats_.reset(new diff_stats(context())); mark_leaf_diff_nodes(); priv_->apply_filters_and_compute_diff_stats(*priv_->diff_stats_); return *priv_->diff_stats_; } /// A visitor that marks leaf diff nodes by storing them in the /// instance of @ref diff_maps returned by /// corpus_diff::get_leaf_diffs() invoked on the current instance of /// corpus_diff. struct leaf_diff_node_marker_visitor : public diff_node_visitor { /// This is called when the visitor visits a diff node. /// /// It basically tests if the diff node being visited is a leaf diff /// node - that is, it contains local changes. If it does, then the /// node is added to the set of maps that hold leaf diffs in the /// current corpus_diff. /// /// Note that only leaf nodes that are reachable from public /// interfaces (global functions or variables) are collected by this /// visitor. /// /// @param d the diff node being visited. virtual void visit_begin(diff *d) { if (d->has_local_changes() // A leaf basic (or class/union) type name change makes no // sense when showing just leaf changes. It only makes sense // when it can explain the details about a non-leaf change. // In other words, it doesn't make sense to say that an "int" // became "unsigned int". But it does make sense to say that // a typedef changed because its underlying type was 'int' and // is now an "unsigned int". && !filtering::has_basic_or_class_type_name_change(d) // Similarly, a *local* change describing a type that changed // its nature doesn't make sense. && !is_distinct_diff(d) // Similarly, a pointer (or reference or array), a typedef or // qualified type change in itself doesn't make sense. It // would rather make sense to show that pointer change as part // of the variable change whose pointer type changed, for // instance. && !is_pointer_diff(d) && !is_reference_diff(d) && !is_qualified_type_diff(d) && !is_typedef_diff(d) && !is_array_diff(d) // Similarly a parameter change in itself doesn't make sense. // It should have already been reported as part of the change // of the function it belongs to. && !is_fn_parm_diff(d) // An anonymous class or union diff doesn't make sense on its // own. It must have been described already by the diff of // the enclosing struct or union if 'd' is from an anonymous // data member, or from a typedef change if 'd' is from a // typedef change which underlying type is an anonymous // struct/union. && !is_anonymous_class_or_union_diff(d) // Don't show decl-only-ness changes either. && !filtering::has_decl_only_def_change(d) // Sometime, we can encounter artifacts of bogus DWARF that // yield a diff node for a decl-only class (and empty class // with the is_declaration flag set) that carries a non-zero // size! And of course at some point that non-zero size // changes. We need to be able to detect that. && !filtering::is_decl_only_class_with_size_change(d)) { diff_context_sptr ctxt = d->context(); const corpus_diff *corpus_diff_node = ctxt->get_corpus_diff().get(); ABG_ASSERT(corpus_diff_node); if (diff *iface_diff = get_current_topmost_iface_diff()) { type_or_decl_base_sptr iface = iface_diff->first_subject(); // So, this diff node that is reachable from a global // function or variable carries a leaf change. Let's add // it to the set of of leaf diffs of corpus_diff_node. const_cast(corpus_diff_node)-> get_leaf_diffs().insert_diff_node(d, iface); } } } }; // end struct leaf_diff_node_marker_visitor /// Walks the diff nodes associated to the current corpus diff and /// mark those that carry local changes. They are said to be leaf /// diff nodes. /// /// The marked nodes are available from the /// corpus_diff::get_leaf_diffs() function. void corpus_diff::mark_leaf_diff_nodes() { if (!has_changes()) return; if (!context()->show_leaf_changes_only()) return; leaf_diff_node_marker_visitor v; context()->forget_visited_diffs(); bool s = context()->visiting_a_node_twice_is_forbidden(); context()->forbid_visiting_a_node_twice(true); context()->forbid_visiting_a_node_twice_per_interface(true); traverse(v); context()->forbid_visiting_a_node_twice(s); context()->forbid_visiting_a_node_twice_per_interface(false); } /// Get the set of maps that contain leaf nodes. A leaf node being a /// node with a local change. /// /// @return the set of maps that contain leaf nodes. A leaf node /// being a node with a local change. diff_maps& corpus_diff::get_leaf_diffs() {return priv_->leaf_diffs_;} /// Get the set of maps that contain leaf nodes. A leaf node being a /// node with a local change. /// /// @return the set of maps that contain leaf nodes. A leaf node /// being a node with a local change. const diff_maps& corpus_diff::get_leaf_diffs() const {return priv_->leaf_diffs_;} /// Report the diff in a serialized form. /// /// @param out the stream to serialize the diff to. /// /// @param indent the prefix to use for the indentation of this /// serialization. void corpus_diff::report(ostream& out, const string& indent) const { context()->get_reporter()->report(*this, out, indent); } /// Traverse the diff sub-tree under the current instance corpus_diff. /// /// @param v the visitor to invoke on each diff node of the sub-tree. /// /// @return true if the traversing has to keep going on, false otherwise. bool corpus_diff::traverse(diff_node_visitor& v) { finish_diff_type(); v.visit_begin(this); if (!v.visit(this, true)) { v.visit_end(this); return false; } for (function_decl_diff_sptrs_type::const_iterator i = changed_functions_sorted().begin(); i != changed_functions_sorted().end(); ++i) { if (diff_sptr d = *i) { const diff_context_sptr &ctxt = context(); if (ctxt->visiting_a_node_twice_is_forbidden_per_interface()) ctxt->forget_visited_diffs(); v.set_current_topmost_iface_diff(d.get()); if (!d->traverse(v)) { v.visit_end(this); v.set_current_topmost_iface_diff(0); return false; } } } for (var_diff_sptrs_type::const_iterator i = changed_variables_sorted().begin(); i != changed_variables_sorted().end(); ++i) { if (diff_sptr d = *i) { const diff_context_sptr &ctxt = context(); if (ctxt->visiting_a_node_twice_is_forbidden_per_interface()) ctxt->forget_visited_diffs(); v.set_current_topmost_iface_diff(d.get()); if (!d->traverse(v)) { v.visit_end(this); v.set_current_topmost_iface_diff(0); return false; } } } v.set_current_topmost_iface_diff(0); // Traverse the changed unreachable type diffs. These diffs are on // types that are not reachable from global functions or variables. for (vector::const_iterator i = changed_unreachable_types_sorted().begin(); i != changed_unreachable_types_sorted().end(); ++i) { if (diff_sptr d = *i) { const diff_context_sptr &ctxt = context(); if (ctxt->visiting_a_node_twice_is_forbidden_per_interface()) ctxt->forget_visited_diffs(); if (!d->traverse(v)) { v.visit_end(this); return false; } } } v.visit_end(this); return true; } /// Compute the diff between two instances of @ref corpus. /// /// Note that the two corpora must have been created in the same @ref /// environment, otherwise, this function aborts. /// /// @param f the first @ref corpus to consider for the diff. /// /// @param s the second @ref corpus to consider for the diff. /// /// @param ctxt the diff context to use. /// /// @return the resulting diff between the two @ref corpus. corpus_diff_sptr compute_diff(const corpus_sptr f, const corpus_sptr s, diff_context_sptr ctxt) { typedef corpus::functions::const_iterator fns_it_type; typedef corpus::variables::const_iterator vars_it_type; typedef elf_symbols::const_iterator symbols_it_type; typedef diff_utils::deep_ptr_eq_functor eq_type; typedef vector::const_iterator type_base_wptr_it_type; ABG_ASSERT(f && s); if (!ctxt) ctxt.reset(new diff_context); corpus_diff_sptr r(new corpus_diff(f, s, ctxt)); ctxt->set_corpus_diff(r); if(ctxt->show_soname_change()) r->priv_->sonames_equal_ = f->get_soname() == s->get_soname(); else r->priv_->sonames_equal_ = true; r->priv_->architectures_equal_ = f->get_architecture_name() == s->get_architecture_name(); // Compute the diff of publicly defined and exported functions diff_utils::compute_diff(f->get_functions().begin(), f->get_functions().end(), s->get_functions().begin(), s->get_functions().end(), r->priv_->fns_edit_script_); // Compute the diff of publicly defined and exported variables. diff_utils::compute_diff (f->get_variables().begin(), f->get_variables().end(), s->get_variables().begin(), s->get_variables().end(), r->priv_->vars_edit_script_); // Compute the diff of function elf symbols not referenced by debug // info. diff_utils::compute_diff (f->get_unreferenced_function_symbols().begin(), f->get_unreferenced_function_symbols().end(), s->get_unreferenced_function_symbols().begin(), s->get_unreferenced_function_symbols().end(), r->priv_->unrefed_fn_syms_edit_script_); // Compute the diff of variable elf symbols not referenced by debug // info. diff_utils::compute_diff (f->get_unreferenced_variable_symbols().begin(), f->get_unreferenced_variable_symbols().end(), s->get_unreferenced_variable_symbols().begin(), s->get_unreferenced_variable_symbols().end(), r->priv_->unrefed_var_syms_edit_script_); if (ctxt->show_unreachable_types()) // Compute the diff of types not reachable from public functions // or global variables that are exported. diff_utils::compute_diff (f->get_types_not_reachable_from_public_interfaces().begin(), f->get_types_not_reachable_from_public_interfaces().end(), s->get_types_not_reachable_from_public_interfaces().begin(), s->get_types_not_reachable_from_public_interfaces().end(), r->priv_->unreachable_types_edit_script_); r->priv_->ensure_lookup_tables_populated(); return r; } // /// Compute the diff between two instances of @ref corpus_group. /// /// Note that the two corpus_diff must have been created in the same /// @ref environment, otherwise, this function aborts. /// /// @param f the first @ref corpus_group to consider for the diff. /// /// @param s the second @ref corpus_group to consider for the diff. /// /// @param ctxt the diff context to use. /// /// @return the resulting diff between the two @ref corpus_group. corpus_diff_sptr compute_diff(const corpus_group_sptr& f, const corpus_group_sptr& s, diff_context_sptr ctxt) { corpus_sptr c1 = f; corpus_sptr c2 = s; return compute_diff(c1, c2, ctxt); } // // // /// The private data of the @diff_node_visitor type. struct diff_node_visitor::priv { diff* topmost_interface_diff; visiting_kind kind; priv() : topmost_interface_diff(), kind() {} priv(visiting_kind k) : topmost_interface_diff(), kind(k) {} }; // end struct diff_node_visitor /// Default constructor of the @ref diff_node_visitor type. diff_node_visitor::diff_node_visitor() : priv_(new priv) {} diff_node_visitor::~diff_node_visitor() = default; /// Constructor of the @ref diff_node_visitor type. /// /// @param k how the visiting has to be performed. diff_node_visitor::diff_node_visitor(visiting_kind k) : priv_(new priv(k)) {} /// Getter for the visiting policy of the traversing code while /// invoking this visitor. /// /// @return the visiting policy used by the traversing code when /// invoking this visitor. visiting_kind diff_node_visitor::get_visiting_kind() const {return priv_->kind;} /// Setter for the visiting policy of the traversing code while /// invoking this visitor. /// /// @param v a bit map representing the new visiting policy used by /// the traversing code when invoking this visitor. void diff_node_visitor::set_visiting_kind(visiting_kind v) {priv_->kind = v;} /// Setter for the visiting policy of the traversing code while /// invoking this visitor. This one makes a logical or between the /// current policy and the bitmap given in argument and assigns the /// current policy to the result. /// /// @param v a bitmap representing the visiting policy to or with /// the current policy. void diff_node_visitor::or_visiting_kind(visiting_kind v) {priv_->kind = priv_->kind | v;} /// Setter of the diff current topmost interface which is impacted by /// the current diff node being visited. /// /// @param d the current topmost interface diff impacted. void diff_node_visitor::set_current_topmost_iface_diff(diff* d) {priv_->topmost_interface_diff = d;} /// Getter of the diff current topmost interface which is impacted by /// the current diff node being visited. /// /// @return the current topmost interface diff impacted. diff* diff_node_visitor::get_current_topmost_iface_diff() const {return priv_->topmost_interface_diff;} /// This is called by the traversing code on a @ref diff node just /// before visiting it. That is, before visiting it and its children /// node. /// /// @param d the diff node to visit. void diff_node_visitor::visit_begin(diff* /*p*/) {} /// This is called by the traversing code on a @ref diff node just /// after visiting it. That is after visiting it and its children /// nodes. /// /// @param d the diff node that got visited. void diff_node_visitor::visit_end(diff* /*p*/) {} /// This is called by the traversing code on a @ref corpus_diff node /// just before visiting it. That is, before visiting it and its /// children node. /// /// @param p the corpus_diff node to visit. /// void diff_node_visitor::visit_begin(corpus_diff* /*p*/) {} /// This is called by the traversing code on a @ref corpus_diff node /// just after visiting it. That is after visiting it and its children /// nodes. /// /// @param d the diff node that got visited. void diff_node_visitor::visit_end(corpus_diff* /*d*/) {} /// Default visitor implementation /// /// @return true bool diff_node_visitor::visit(diff*, bool) {return true;} /// Default visitor implementation. /// /// @return true bool diff_node_visitor::visit(distinct_diff* dif, bool pre) { diff* d = dif; visit(d, pre); return true; } /// Default visitor implementation. /// /// @return true bool diff_node_visitor::visit(var_diff* dif, bool pre) { diff* d = dif; visit(d, pre); return true; } /// Default visitor implementation. /// /// @return true bool diff_node_visitor::visit(pointer_diff* dif, bool pre) { diff* d = dif; visit(d, pre); return true; } /// Default visitor implementation. /// /// @return true bool diff_node_visitor::visit(reference_diff* dif, bool pre) { diff* d = dif; visit(d, pre); return true; } /// Default visitor implementation. /// /// @return true bool diff_node_visitor::visit(qualified_type_diff* dif, bool pre) { diff* d = dif; visit(d, pre); return true; } /// Default visitor implementation. /// /// @return true bool diff_node_visitor::visit(enum_diff* dif, bool pre) { diff* d = dif; visit(d, pre); return true; } /// Default visitor implementation. /// /// @return true bool diff_node_visitor::visit(class_diff* dif, bool pre) { diff* d = dif; visit(d, pre); return true; } /// Default visitor implementation. /// /// @return true bool diff_node_visitor::visit(base_diff* dif, bool pre) { diff* d = dif; visit(d, pre); return true; } /// Default visitor implementation. /// /// @return true bool diff_node_visitor::visit(scope_diff* dif, bool pre) { diff* d = dif; visit(d, pre); return true; } /// Default visitor implementation. /// /// @return true bool diff_node_visitor::visit(function_decl_diff* dif, bool pre) { diff* d = dif; visit(d, pre); return true; } /// Default visitor implementation. /// /// @return true bool diff_node_visitor::visit(type_decl_diff* dif, bool pre) { diff* d = dif; visit(d, pre); return true; } /// Default visitor implementation. /// /// @return true bool diff_node_visitor::visit(typedef_diff* dif, bool pre) { diff* d = dif; visit(d, pre); return true; } /// Default visitor implementation. /// /// @return true bool diff_node_visitor::visit(translation_unit_diff* dif, bool pre) { diff* d = dif; visit(d, pre); return true; } /// Default visitor implementation. /// /// @return true bool diff_node_visitor::visit(corpus_diff*, bool) {return true;} // // // // /// A visitor to propagate the category of a node up to its parent /// nodes. This visitor doesn't touch the REDUNDANT_CATEGORY or the /// SUPPRESSED_CATEGORY because those are propagated using other /// specific visitors. struct category_propagation_visitor : public diff_node_visitor { virtual void visit_end(diff* d) { // Has this diff node 'd' been already visited ? bool already_visited = d->context()->diff_has_been_visited(d); // The canonical diff node of the class of equivalence of the diff // node 'd'. diff* canonical = d->get_canonical_diff(); // If this class of equivalence of diff node is being visited for // the first time, then update its canonical node's category too. bool update_canonical = !already_visited && canonical; for (vector::const_iterator i = d->children_nodes().begin(); i != d->children_nodes().end(); ++i) { // If we are visiting the class of equivalence of 'd' for the // first time, then let's look at the children of 'd' and // propagate their categories to 'd'. // // If the class of equivalence of 'd' has already been // visited, then let's look at the canonical diff nodes of the // children of 'd' and propagate their categories to 'd'. diff* diff = already_visited ? (*i)->get_canonical_diff() : *i; ABG_ASSERT(diff); diff_category c = diff->get_category(); // Do not propagate redundant and suppressed categories. Those // are propagated in a specific pass elsewhere. c &= ~(REDUNDANT_CATEGORY | SUPPRESSED_CATEGORY | PRIVATE_TYPE_CATEGORY); // Also, if a (class) type has got a harmful name change, do not // propagate harmless name changes coming from its sub-types // (i.e, data members) to the class itself. if (filtering::has_harmful_name_change(d)) c &= ~HARMLESS_DECL_NAME_CHANGE_CATEGORY; d->add_to_category(c); if (!already_visited && canonical) if (update_canonical) canonical->add_to_category(c); } } };// end struct category_propagation_visitor /// Visit all the nodes of a given sub-tree. For each node that has a /// particular category set, propagate that category set up to its /// parent nodes. /// /// @param diff_tree the diff sub-tree to walk for categorization /// purpose; void propagate_categories(diff* diff_tree) { category_propagation_visitor v; bool s = diff_tree->context()->visiting_a_node_twice_is_forbidden(); diff_tree->context()->forbid_visiting_a_node_twice(true); diff_tree->context()->forget_visited_diffs(); diff_tree->traverse(v); diff_tree->context()->forbid_visiting_a_node_twice(s); } /// Visit all the nodes of a given sub-tree. For each node that has a /// particular category set, propagate that category set up to its /// parent nodes. /// /// @param diff_tree the diff sub-tree to walk for categorization /// purpose; void propagate_categories(diff_sptr diff_tree) {propagate_categories(diff_tree.get());} /// Visit all the nodes of a given corpus tree. For each node that /// has a particular category set, propagate that category set up to /// its parent nodes. /// /// @param diff_tree the corpus_diff tree to walk for categorization /// purpose; void propagate_categories(corpus_diff* diff_tree) { category_propagation_visitor v; bool s = diff_tree->context()->visiting_a_node_twice_is_forbidden(); diff_tree->context()->forbid_visiting_a_node_twice(false); diff_tree->traverse(v); diff_tree->context()->forbid_visiting_a_node_twice(s); } /// Visit all the nodes of a given corpus tree. For each node that /// has a particular category set, propagate that category set up to /// its parent nodes. /// /// @param diff_tree the corpus_diff tree to walk for categorization /// purpose; void propagate_categories(corpus_diff_sptr diff_tree) {propagate_categories(diff_tree.get());} /// A tree node visitor that knows how to categorizes a given diff /// node in the SUPPRESSED_CATEGORY category and how to propagate that /// categorization. struct suppression_categorization_visitor : public diff_node_visitor { /// Before visiting the children of the diff node, check if the node /// is suppressed by a suppression specification. If it is, mark /// the node as belonging to the SUPPRESSED_CATEGORY category. /// /// @param p the diff node to visit. virtual void visit_begin(diff* d) { bool is_private_type = false; if (d->is_suppressed(is_private_type)) { diff_category c = is_private_type ? PRIVATE_TYPE_CATEGORY : SUPPRESSED_CATEGORY; d->add_to_local_and_inherited_categories(c); // If a node was suppressed, all the other nodes of its class // of equivalence are suppressed too. diff *canonical_diff = d->get_canonical_diff(); if (canonical_diff != d) canonical_diff->add_to_category(c); } } /// After visiting the children nodes of a given diff node, /// propagate the SUPPRESSED_CATEGORY from the children nodes to the /// diff node, if need be. /// /// That is, if all children nodes carry a suppressed change the /// current node should be marked as suppressed as well. /// /// In practice, this might be too strong of a condition. If the /// current node carries a local change (i.e, a change not carried /// by any of its children node) and if that change is not /// suppressed, then the current node should *NOT* be suppressed. /// /// But right now, the IR doesn't let us know about local vs /// children-carried changes. So we cannot be that precise yet. virtual void visit_end(diff* d) { bool has_non_suppressed_child = false; bool has_non_empty_child = false; bool has_suppressed_child = false; bool has_non_private_child = false; bool has_private_child = false; if (// A node to which we can propagate the "SUPPRESSED_CATEGORY" // (or the PRIVATE_TYPE_CATEGORY for the same matter) // category from its children is a node which: // // 1/ hasn't been suppressed already // // 2/ and has no local change (unless it's a pointer, // reference or qualified diff node). // // Note that qualified type and typedef diff nodes are a bit // special. The local changes of the underlying type are // considered local for the qualified/typedef type, just like // for pointer/reference types. But then the qualified or // typedef type itself can have local changes of its own, and // those changes are of the kind LOCAL_NON_TYPE_CHANGE_KIND. // So a qualified type which have local changes that are // *NOT* of LOCAL_NON_TYPE_CHANGE_KIND (or that has no local // changes at all) and which is in the PRIVATE_TYPE_CATEGORY // or SUPPRESSED_CATEGORY can see these categories be // propagated. // // Note that all pointer/reference diff node changes are // potentially considered local, i.e, local changes of the // pointed-to-type are considered local to the pointer itself. // // Similarly, changes local to the type of function parameters, // variables (and data members) and classes (that are not of // LOCAL_NON_TYPE_CHANGE_KIND kind) and that have been // suppressed can propagate their SUPPRESSED_CATEGORY-ness to // those kinds of diff node. !(d->get_category() & SUPPRESSED_CATEGORY) && (!d->has_local_changes() || is_pointer_diff(d) || is_reference_diff(d) || (is_qualified_type_diff(d) && (!(d->has_local_changes() & LOCAL_NON_TYPE_CHANGE_KIND))) || (is_typedef_diff(d) && (!(d->has_local_changes() & LOCAL_NON_TYPE_CHANGE_KIND))) || (is_function_decl_diff(d) && (!(d->has_local_changes() & LOCAL_NON_TYPE_CHANGE_KIND))) || (is_fn_parm_diff(d) && (!(d->has_local_changes() & LOCAL_NON_TYPE_CHANGE_KIND))) || (is_function_type_diff(d) && (!(d->has_local_changes() & LOCAL_NON_TYPE_CHANGE_KIND))) || (is_var_diff(d) && (!(d->has_local_changes() & LOCAL_NON_TYPE_CHANGE_KIND))) || (is_class_diff(d) && (!(d->has_local_changes() & LOCAL_NON_TYPE_CHANGE_KIND))))) { // Note that we handle private diff nodes differently from // generally suppressed diff nodes. E.g, it's not because a // type is private (and suppressed because of that; i.e, in // the category PRIVATE_TYPE_CATEGORY) that a typedef to that // type should also be private and so suppressed. Private // diff nodes thus have different propagation rules than // generally suppressed rules. for (vector::const_iterator i = d->children_nodes().begin(); i != d->children_nodes().end(); ++i) { diff* child = *i; if (child->has_changes()) { has_non_empty_child = true; if (child->get_class_of_equiv_category() & SUPPRESSED_CATEGORY) has_suppressed_child = true; else if (child->get_class_of_equiv_category() & PRIVATE_TYPE_CATEGORY) // Propagation of the PRIVATE_TYPE_CATEGORY is going // to be handled later below. ; else has_non_suppressed_child = true; if (child->get_class_of_equiv_category() & PRIVATE_TYPE_CATEGORY) has_private_child = true; else if (child->get_class_of_equiv_category() & SUPPRESSED_CATEGORY) // Propagation of the SUPPRESSED_CATEGORY has been // handled above already. ; else has_non_private_child = true; } } if (has_non_empty_child && has_suppressed_child && !has_non_suppressed_child) { d->add_to_category(SUPPRESSED_CATEGORY); // If a node was suppressed, all the other nodes of its class // of equivalence are suppressed too. diff *canonical_diff = d->get_canonical_diff(); if (canonical_diff != d) canonical_diff->add_to_category(SUPPRESSED_CATEGORY); } // Note that the private-ness of a an underlying type won't be // propagated to its parent typedef, by virtue of the big "if" // clause at the beginning of this function. So we don't have // to handle that case here. So the idiom of defining // typedefs of private (opaque) types will be respected; // meaning that changes to opaque underlying type will be // flagged as private and the typedef will be flagged private // as well, unless the typedef itself has local non-type // changes. In the later case, changes to the typedef will be // emitted because the typedef won't inherit the privateness // of its underlying type. So in practise, the typedef // remains public for the purpose of change reporting. if (has_non_empty_child && has_private_child && !has_non_private_child) { d->add_to_category(PRIVATE_TYPE_CATEGORY); // If a node was suppressed, all the other nodes of its class // of equivalence are suppressed too. diff *canonical_diff = d->get_canonical_diff(); if (canonical_diff != d) canonical_diff->add_to_category(PRIVATE_TYPE_CATEGORY); } // If the underlying type of a typedef is private and carries // changes (that are implicitely suppressed because it's // private) then the typedef must be suppressed too, so that // those changes to the underlying type are not seen. if (is_typedef_diff(d) && !d->has_local_changes() && has_private_child && has_non_empty_child) { d->add_to_category(SUPPRESSED_CATEGORY|PRIVATE_TYPE_CATEGORY); // If a node was suppressed, all the other nodes of its class // of equivalence are suppressed too. diff *canonical_diff = d->get_canonical_diff(); if (canonical_diff != d) canonical_diff->add_to_category (SUPPRESSED_CATEGORY|PRIVATE_TYPE_CATEGORY); } if (const function_decl_diff *fn_diff = is_function_decl_diff(d)) if (!(d->has_local_changes() & LOCAL_NON_TYPE_CHANGE_KIND)) { // d is a function diff that carries a local *type* // change (that means it's a change to the function // type). Let's see if the child function type diff // node is suppressed. That would mean that we are // instructed to show details of a diff that is deemed // suppressed; this means the suppression conflicts with // a local type change. In that case, let's follow what // the user asked and suppress the function altogether, if (function_type_diff_sptr fn_type_diff = fn_diff->type_diff()) if (fn_type_diff->is_suppressed()) { d->add_to_category(SUPPRESSED_CATEGORY); // If a node was suppressed, all the other nodes // of its class of equivalence are suppressed too. diff *canonical_diff = d->get_canonical_diff(); if (canonical_diff != d) canonical_diff->add_to_category(SUPPRESSED_CATEGORY); } } } } }; //end struct suppression_categorization_visitor /// Walk a given diff-sub tree and appply the suppressions carried by /// the context. If the suppression applies to a given node than /// categorize the node into the SUPPRESSED_CATEGORY category and /// propagate that categorization. /// /// @param diff_tree the diff-sub tree to apply the suppressions to. void apply_suppressions(diff* diff_tree) { if (diff_tree && !diff_tree->context()->suppressions().empty()) { // Apply suppressions to functions and variables that have // changed sub-types. suppression_categorization_visitor v; diff_tree->context()->forget_visited_diffs(); bool s = diff_tree->context()->visiting_a_node_twice_is_forbidden(); diff_tree->context()->forbid_visiting_a_node_twice(true); diff_tree->traverse(v); diff_tree->context()->forbid_visiting_a_node_twice(s); } } /// Walk a given diff-sub tree and appply the suppressions carried by /// the context. If the suppression applies to a given node than /// categorize the node into the SUPPRESSED_CATEGORY category and /// propagate that categorization. /// /// @param diff_tree the diff-sub tree to apply the suppressions to. void apply_suppressions(diff_sptr diff_tree) {apply_suppressions(diff_tree.get());} /// Walk a @ref corpus_diff tree and appply the suppressions carried /// by the context. If the suppression applies to a given node then /// categorize the node into the SUPPRESSED_CATEGORY category and /// propagate that categorization. /// /// @param diff_tree the diff tree to apply the suppressions to. void apply_suppressions(const corpus_diff* diff_tree) { if (diff_tree && !diff_tree->context()->suppressions().empty()) { // First, visit the children trees of changed constructs: // changed functions, variables, as well as sub-types of these, // and apply suppression specifications to these ... suppression_categorization_visitor v; diff_tree->context()->forget_visited_diffs(); bool s = diff_tree->context()->visiting_a_node_twice_is_forbidden(); diff_tree->context()->forbid_visiting_a_node_twice(true); const_cast(diff_tree)->traverse(v); diff_tree->context()->forbid_visiting_a_node_twice(s); // ... then also visit the set of added and removed functions, // variables, symbols, and types not reachable from global // functions and variables. diff_tree->priv_-> apply_supprs_to_added_removed_fns_vars_unreachable_types(); } } /// Walk a diff tree and appply the suppressions carried by the /// context. If the suppression applies to a given node than /// categorize the node into the SUPPRESSED_CATEGORY category and /// propagate that categorization. /// /// @param diff_tree the diff tree to apply the suppressions to. void apply_suppressions(corpus_diff_sptr diff_tree) {apply_suppressions(diff_tree.get());} // // /// A visitor to print (to an output stream) a pretty representation /// of a @ref diff sub-tree or of a complete @ref corpus_diff tree. struct diff_node_printer : public diff_node_visitor { ostream& out_; unsigned level_; /// Emit a certain number of spaces to the output stream associated /// to this diff_node_printer. /// /// @param level half of the numver of spaces to emit. void do_indent(unsigned level) { for (unsigned i = 0; i < level; ++i) out_ << " "; } diff_node_printer(ostream& out) : diff_node_visitor(DO_NOT_MARK_VISITED_NODES_AS_VISITED), out_(out), level_(0) {} virtual void visit_begin(diff*) { ++level_; } virtual void visit_end(diff*) { --level_; } virtual void visit_begin(corpus_diff*) { ++level_; } virtual void visit_end(corpus_diff*) { --level_; } virtual bool visit(diff* d, bool pre) { if (!pre) // We are post-visiting the diff node D. Which means, we have // printed a pretty representation for it already. So do // nothing now. return true; do_indent(level_); out_ << d->get_pretty_representation(); out_ << "\n"; do_indent(level_); out_ << "{\n"; do_indent(level_ + 1); out_ << "category: "<< d->get_category() << "\n"; do_indent(level_ + 1); out_ << "@: " << std::hex << d << std::dec << "\n"; do_indent(level_ + 1); out_ << "@-canonical: " << std::hex << d->get_canonical_diff() << std::dec << "\n"; do_indent(level_); out_ << "}\n"; return true; } virtual bool visit(corpus_diff* d, bool pre) { if (!pre) // We are post-visiting the diff node D. Which means, we have // printed a pretty representation for it already. So do // nothing now. return true; // indent for (unsigned i = 0; i < level_; ++i) out_ << ' '; out_ << d->get_pretty_representation(); out_ << '\n'; return true; } }; // end struct diff_printer_visitor // /// Emit a textual representation of a @ref diff sub-tree to an /// output stream. /// /// @param diff_tree the sub-tree to emit the textual representation /// for. /// /// @param out the output stream to emit the textual representation /// for @p diff_tree to. void print_diff_tree(diff* diff_tree, ostream& out) { diff_node_printer p(out); bool s = diff_tree->context()->visiting_a_node_twice_is_forbidden(); diff_tree->context()->forbid_visiting_a_node_twice(false); diff_tree->traverse(p); diff_tree->context()->forbid_visiting_a_node_twice(s); } /// Emit a textual representation of a @ref corpus_diff tree to an /// output stream. /// /// @param diff_tree the @ref corpus_diff tree to emit the textual /// representation for. /// /// @param out the output stream to emit the textual representation /// for @p diff_tree to. void print_diff_tree(corpus_diff* diff_tree, std::ostream& out) { diff_node_printer p(out); bool s = diff_tree->context()->visiting_a_node_twice_is_forbidden(); diff_tree->context()->forbid_visiting_a_node_twice(false); diff_tree->traverse(p); diff_tree->context()->forbid_visiting_a_node_twice(s); } /// Emit a textual representation of a @ref diff sub-tree to an /// output stream. /// /// @param diff_tree the sub-tree to emit the textual representation /// for. /// /// @param out the output stream to emit the textual representation /// for @p diff_tree to. void print_diff_tree(diff_sptr diff_tree, std::ostream& o) {print_diff_tree(diff_tree.get(), o);} /// Emit a textual representation of a @ref corpus_diff tree to an /// output stream. /// /// @param diff_tree the @ref corpus_diff tree to emit the textual /// representation for. /// /// @param out the output stream to emit the textual representation /// for @p diff_tree to. void print_diff_tree(corpus_diff_sptr diff_tree, std::ostream& o) {print_diff_tree(diff_tree.get(), o);} // /// A tree visitor to categorize nodes with respect to the /// REDUNDANT_CATEGORY. That is, detect if a node is redundant (is /// present on several spots of the tree) and mark such nodes /// appropriatly. This visitor also takes care of propagating the /// REDUNDANT_CATEGORY of a given node to its parent nodes as /// appropriate. struct redundancy_marking_visitor : public diff_node_visitor { bool skip_children_nodes_; redundancy_marking_visitor() : skip_children_nodes_() {} virtual void visit_begin(diff* d) { if (d->to_be_reported()) { // A diff node that carries a change and that has been already // traversed elsewhere is considered redundant. So let's mark // it as such and let's not traverse it; that is, let's not // visit its children. if ((d->context()->diff_has_been_visited(d) || d->get_canonical_diff()->is_traversing()) && d->has_changes()) { // But if two diff nodes are redundant sibbling that carry // changes of base types, do not mark them as being // redundant. This is to avoid marking nodes as redundant // in this case: // // int foo(int a, int b); // compared with: // float foo(float a, float b); (in C). // // In this case, we want to report all the occurences of // the int->float change because logically, they are at // the same level in the diff tree. bool redundant_with_sibling_node = false; const diff* p = d->parent_node(); // If this is a child node of a fn_parm_diff, look through // the fn_parm_diff node to get the function diff node. if (p && dynamic_cast(p)) p = p->parent_node(); if (p) for (vector::const_iterator s = p->children_nodes().begin(); s != p->children_nodes().end(); ++s) { if (*s == d) continue; diff* sib = *s; // If this is a fn_parm_diff, look through the // fn_parm_diff node to get at the real type node. if (fn_parm_diff* f = dynamic_cast(*s)) sib = f->type_diff().get(); if (sib == d) continue; if (sib->get_canonical_diff() == d->get_canonical_diff() // Sibbling diff nodes that carry base type // changes ar to be marked as redundant. && (is_base_diff(sib) || is_distinct_diff(sib))) { redundant_with_sibling_node = true; break; } } if (!redundant_with_sibling_node // Changes to basic types should never be considered // redundant. For instance, if a member of integer // type is changed into a char type in both a struct A // and a struct B, we want to see both changes. && !has_basic_type_change_only(d) // The same goes for distinct type changes && !filtering::is_mostly_distinct_diff(d) // Functions with similar *local* changes are never marked // redundant because otherwise one could miss important // similar local changes that are applied to different // functions. && !is_function_type_diff_with_local_changes(d) // Changes involving variadic parameters of functions // should never be marked redundant because we want to see // them all. && !is_diff_of_variadic_parameter(d) && !is_diff_of_variadic_parameter_type(d) // If the canonical diff itself has been filtered out, // then this one is not marked redundant, unless the // canonical diff was already redundant. && (!d->get_canonical_diff()->is_filtered_out() || (d->get_canonical_diff()->get_category() & REDUNDANT_CATEGORY)) // If the *same* diff node (not one that is merely // equivalent to this one) has already been visited // the do not mark it as beind redundant. It's only // the other nodes that are equivalent to this one // that must be marked redundant. && d->context()->diff_has_been_visited(d) != d // If the diff node is a function parameter and is not // a reference/pointer (to a non basic or a non // distinct type diff) then do not mark it as // redundant. // // Children nodes of base class diff nodes are never // redundant either, we want to see them all. && (is_reference_or_ptr_diff_to_non_basic_nor_distinct_types(d) || (!is_child_node_of_function_parm_diff(d) && !is_child_node_of_base_diff(d)))) { d->add_to_category(REDUNDANT_CATEGORY); // As we said in preamble, as this node is marked as // being redundant, let's not visit its children. // This is not an optimization; it's needed for // correctness. In the case of a diff node involving // a class type that refers to himself, visiting the // children nodes might cause them to be wrongly // marked as redundant. set_visiting_kind(get_visiting_kind() | SKIP_CHILDREN_VISITING_KIND); skip_children_nodes_ = true; } } } else { // If the node is not to be reported, do not look at it children. set_visiting_kind(get_visiting_kind() | SKIP_CHILDREN_VISITING_KIND); skip_children_nodes_ = true; } } virtual void visit_begin(corpus_diff*) { } virtual void visit_end(diff* d) { if (skip_children_nodes_) // When visiting this node, we decided to skip its children // node. Now that we are done visiting the node, lets stop // avoiding the children nodes visiting for the other tree // nodes. { set_visiting_kind(get_visiting_kind() & (~SKIP_CHILDREN_VISITING_KIND)); skip_children_nodes_ = false; } else { // Propagate the redundancy categorization of the children nodes // to this node. But if this node has local changes, then it // doesn't inherit redundancy from its children nodes. if (!(d->get_category() & REDUNDANT_CATEGORY) && (!d->has_local_changes_to_be_reported() // By default, pointer, reference and qualified types // consider that a local changes to their underlying // type is always a local change for themselves. // // This is as if those types don't have local changes // in the same sense as other types. So we always // propagate redundancy to them, regardless of if they // have local changes or not. // // We also propagate redundancy to typedef types if // these /only/ carry changes to their underlying // type. // // Note that changes to the underlying type of a // typedef is considered local of // LOCAL_TYPE_CHANGE_KIND kind. The other changes to the // typedef itself are considered local of // LOCAL_NON_TYPE_CHANGE_KIND kind. || is_pointer_diff(d) || is_qualified_type_diff(d) // A typedef with local non-type changes should not // see redundancy propagation from its underlying // type, otherwise, the non-type change might be // "suppressed" away. || (is_typedef_diff(d) && (!(d->has_local_changes() & LOCAL_NON_TYPE_CHANGE_KIND))) // A (member) variable with non-type local changes // should not see redundacy propagation from its type. // If redundant local-type changes are carried by its // type however, then that redundancy is propagated to // the variable. This is key to keep the redundancy // consistency in the system; otherwise, a type change // would be rightfully considered redundant at some // places but not at others. || (is_var_diff(d) && (!(d->has_local_changes() & LOCAL_NON_TYPE_CHANGE_KIND))) )) { bool has_non_redundant_child = false; bool has_non_empty_child = false; for (vector::const_iterator i = d->children_nodes().begin(); i != d->children_nodes().end(); ++i) { if ((*i)->has_changes()) { has_non_empty_child = true; // Let's see if the current child node '*i' is // "non-redundant". // // A non-redundant node would be a node that // carries a change to be reported and has not // been marked as being redundant. if ((*i)->to_be_reported() && ((*i)->get_category() & REDUNDANT_CATEGORY) == 0) has_non_redundant_child = true; } if (has_non_redundant_child) break; } // A diff node for which at least a child node carries a // change, and for which all the children are redundant is // deemed redundant too, unless it has local changes. if (has_non_empty_child && !has_non_redundant_child) d->add_to_category(REDUNDANT_CATEGORY); } } } virtual void visit_end(corpus_diff*) { } virtual bool visit(diff*, bool) {return true;} virtual bool visit(corpus_diff*, bool) { return true; } };// end struct redundancy_marking_visitor /// A visitor of @ref diff nodes that clears the REDUNDANT_CATEGORY /// category out of the nodes. struct redundancy_clearing_visitor : public diff_node_visitor { bool visit(corpus_diff*, bool) {return true;} bool visit(diff* d, bool) { // clear the REDUNDANT_CATEGORY out of the current node. diff_category c = d->get_category(); c &= ~REDUNDANT_CATEGORY; d->set_category(c); return true; } }; // end struct redundancy_clearing_visitor /// Walk a given @ref diff sub-tree to categorize each of the nodes /// with respect to the REDUNDANT_CATEGORY. /// /// @param diff_tree the @ref diff sub-tree to walk. void categorize_redundancy(diff* diff_tree) { if (diff_tree->context()->show_redundant_changes()) return; redundancy_marking_visitor v; bool s = diff_tree->context()->visiting_a_node_twice_is_forbidden(); diff_tree->context()->forbid_visiting_a_node_twice(false); diff_tree->traverse(v); diff_tree->context()->forbid_visiting_a_node_twice(s); } /// Walk a given @ref diff sub-tree to categorize each of the nodes /// with respect to the REDUNDANT_CATEGORY. /// /// @param diff_tree the @ref diff sub-tree to walk. void categorize_redundancy(diff_sptr diff_tree) {categorize_redundancy(diff_tree.get());} /// Walk a given @ref corpus_diff tree to categorize each of the nodes /// with respect to the REDUNDANT_CATEGORY. /// /// @param diff_tree the @ref corpus_diff tree to walk. void categorize_redundancy(corpus_diff* diff_tree) { redundancy_marking_visitor v; diff_tree->context()->forget_visited_diffs(); bool s = diff_tree->context()->visiting_a_node_twice_is_forbidden(); diff_tree->context()->forbid_visiting_a_node_twice(false); diff_tree->traverse(v); diff_tree->context()->forbid_visiting_a_node_twice(s); } /// Walk a given @ref corpus_diff tree to categorize each of the nodes /// with respect to the REDUNDANT_CATEGORY. /// /// @param diff_tree the @ref corpus_diff tree to walk. void categorize_redundancy(corpus_diff_sptr diff_tree) {categorize_redundancy(diff_tree.get());} // /// Walk a given @ref diff sub-tree to clear the REDUNDANT_CATEGORY /// out of the category of the nodes. /// /// @param diff_tree the @ref diff sub-tree to walk. void clear_redundancy_categorization(diff* diff_tree) { redundancy_clearing_visitor v; bool s = diff_tree->context()->visiting_a_node_twice_is_forbidden(); diff_tree->context()->forbid_visiting_a_node_twice(false); diff_tree->traverse(v); diff_tree->context()->forbid_visiting_a_node_twice(s); diff_tree->context()->forget_visited_diffs(); } /// Walk a given @ref diff sub-tree to clear the REDUNDANT_CATEGORY /// out of the category of the nodes. /// /// @param diff_tree the @ref diff sub-tree to walk. void clear_redundancy_categorization(diff_sptr diff_tree) {clear_redundancy_categorization(diff_tree.get());} /// Walk a given @ref corpus_diff tree to clear the REDUNDANT_CATEGORY /// out of the category of the nodes. /// /// @param diff_tree the @ref corpus_diff tree to walk. void clear_redundancy_categorization(corpus_diff* diff_tree) { redundancy_clearing_visitor v; bool s = diff_tree->context()->visiting_a_node_twice_is_forbidden(); diff_tree->context()->forbid_visiting_a_node_twice(false); diff_tree->traverse(v); diff_tree->context()->forbid_visiting_a_node_twice(s); diff_tree->context()->forget_visited_diffs(); } /// Walk a given @ref corpus_diff tree to clear the REDUNDANT_CATEGORY /// out of the category of the nodes. /// /// @param diff_tree the @ref corpus_diff tree to walk. void clear_redundancy_categorization(corpus_diff_sptr diff_tree) {clear_redundancy_categorization(diff_tree.get());} /// Apply the @ref diff tree filters that have been associated to the /// context of the a given @ref corpus_diff tree. As a result, the /// nodes of the @diff tree are going to be categorized into one of /// several of the categories of @ref diff_category. /// /// @param diff_tree the @ref corpus_diff instance which @ref diff are /// to be categorized. void apply_filters(corpus_diff_sptr diff_tree) { diff_tree->context()->maybe_apply_filters(diff_tree); propagate_categories(diff_tree); } /// Test if a diff node represents the difference between a variadic /// parameter type and something else. /// /// @param d the diff node to consider. /// /// @return true iff @p d is a diff node that represents the /// difference between a variadic parameter type and something else. bool is_diff_of_variadic_parameter_type(const diff* d) { if (!d) return false; type_base_sptr t = is_type(d->first_subject()); if (t && t->get_environment().is_variadic_parameter_type(t)) return true; t = is_type(d->second_subject()); if (t && t->get_environment().is_variadic_parameter_type(t)) return true; return false; } /// Test if a diff node represents the difference between a variadic /// parameter type and something else. /// /// @param d the diff node to consider. /// /// @return true iff @p d is a diff node that represents the /// difference between a variadic parameter type and something else. bool is_diff_of_variadic_parameter_type(const diff_sptr& d) {return is_diff_of_variadic_parameter_type(d.get());} /// Test if a diff node represents the difference between a variadic /// parameter and something else. /// /// @param d the diff node to consider. /// /// @return true iff @p d is a diff node that represents the /// difference between a variadic parameter and something else. bool is_diff_of_variadic_parameter(const diff* d) { fn_parm_diff* diff = dynamic_cast(const_cast(d)); return (diff && is_diff_of_variadic_parameter_type(diff->type_diff())); } /// Test if a diff node represents the difference between a variadic /// parameter and something else. /// /// @param d the diff node to consider. /// /// @return true iff @p d is a diff node that represents the /// difference between a variadic parameter and something else. bool is_diff_of_variadic_parameter(const diff_sptr& d) {return is_diff_of_variadic_parameter(d.get());} /// Test if a diff node represents a diff between two basic types. /// /// @param d the diff node to consider. /// /// @return true iff @p d is a diff between two basic types. const type_decl_diff* is_diff_of_basic_type(const diff *d) {return dynamic_cast(d);} /// Test if a diff node represents a diff between two basic types, or /// between pointers, references or qualified type to basic types. /// /// @param diff the diff node to consider. /// /// @param allow_indirect_type if true, then this function looks into /// pointer, reference or qualified diff types to see if they "point /// to" basic types. /// /// @return true iff @p d is a diff between two basic types. const type_decl_diff* is_diff_of_basic_type(const diff* diff, bool allow_indirect_type) { if (allow_indirect_type) diff = peel_pointer_or_qualified_type_diff(diff); return is_diff_of_basic_type(diff); } /// If a diff node is about changes between two typedef types, get the /// diff node about changes between the underlying types. /// /// Note that this function walks the tree of underlying diff nodes /// returns the first diff node about types that are not typedefs. /// /// @param dif the dif node to consider. /// /// @return the underlying diff node of @p dif, or just return @p dif /// if it's not a typedef diff node. const diff* peel_typedef_diff(const diff* dif) { const typedef_diff *d = 0; while ((d = is_typedef_diff(dif))) dif = d->underlying_type_diff().get(); return dif; } /// If a diff node is about changes between two pointer types, get the /// diff node about changes between the underlying (pointed-to) types. /// /// Note that this function walks the tree of underlying diff nodes /// returns the first diff node about types that are not pointers. /// /// @param dif the dif node to consider. /// /// @return the underlying diff node of @p dif, or just return @p dif /// if it's not a pointer diff node. const diff* peel_pointer_diff(const diff* dif) { const pointer_diff *d = 0; while ((d = is_pointer_diff(dif))) dif = d->underlying_type_diff().get(); return dif; } /// If a diff node is about changes between two reference types, get /// the diff node about changes between the underlying (pointed-to) /// types. /// /// Note that this function walks the tree of underlying diff nodes /// returns the first diff node about types that are not references. /// /// @param dif the dif node to consider. /// /// @return the underlying diff node of @p dif, or just return @p dif /// if it's not a reference diff node. const diff* peel_reference_diff(const diff* dif) { const reference_diff *d = 0; while ((d = is_reference_diff(dif))) dif = d->underlying_type_diff().get(); return dif; } /// If a diff node is about changes between two qualified types, get /// the diff node about changes between the underlying (non-qualified) /// types. /// /// Note that this function walks the tree of underlying diff nodes /// returns the first diff node about types that are not qualified. /// /// @param dif the dif node to consider. /// /// @return the underlying diff node of @p dif, or just return @p dif /// if it's not a qualified diff node. const diff* peel_qualified_diff(const diff* dif) { const qualified_type_diff *d = 0; while ((d = is_qualified_type_diff(dif))) dif = d->underlying_type_diff().get(); return dif; } /// If a diff node is about changes between two function parameters /// get the diff node about changes between the types of the parameters. /// /// @param dif the dif node to consider. /// /// @return the diff of the types of the parameters. const diff* peel_fn_parm_diff(const diff* dif) { const fn_parm_diff *d = 0; while ((d = is_fn_parm_diff(dif))) dif = d->type_diff().get(); return dif; } /// If a diff node is about changes between two pointer, reference or /// qualified types, get the diff node about changes between the /// underlying types. /// /// Note that this function walks the tree of underlying diff nodes /// returns the first diff node about types that are not pointer, /// reference or qualified. /// /// @param dif the dif node to consider. /// /// @return the underlying diff node of @p dif, or just return @p dif /// if it's not a pointer, reference or qualified diff node. const diff* peel_pointer_or_qualified_type_diff(const diff*dif) { while (true) { if (const pointer_diff *d = is_pointer_diff(dif)) dif = peel_pointer_diff(d); else if (const reference_diff *d = is_reference_diff(dif)) dif = peel_reference_diff(d); else if (const qualified_type_diff *d = is_qualified_type_diff(dif)) dif = peel_qualified_diff(d); else break; } return dif; } /// If a diff node is about changes between two typedefs or qualified /// types, get the diff node about changes between the underlying /// types. /// /// Note that this function walks the tree of underlying diff nodes /// returns the first diff node about types that are not typedef or /// qualified types. /// /// @param dif the dif node to consider. /// /// @return the underlying diff node of @p dif, or just return @p dif /// if it's not typedef or qualified diff node. const diff* peel_typedef_or_qualified_type_diff(const diff *dif) { while (true) { if (const typedef_diff *d = is_typedef_diff(dif)) dif = peel_typedef_diff(d); else if (const qualified_type_diff *d = is_qualified_type_diff(dif)) dif = peel_qualified_diff(d); else break; } return dif; } /// If a diff node is about changes between two typedefs or qualified /// types, get the diff node about changes between the underlying /// types. /// /// Note that this function walks the tree of underlying diff nodes /// returns the first diff node about types that are neither typedef, /// qualified type nor parameters. /// /// @param dif the dif node to consider. /// /// @return the diff node about changes between the underlying types. const diff* peel_typedef_qualified_type_or_parameter_diff(const diff *dif) { while (true) { if (const typedef_diff *d = is_typedef_diff(dif)) dif = peel_typedef_diff(d); else if (const qualified_type_diff *d = is_qualified_type_diff(dif)) dif = peel_qualified_diff(d); else if (const fn_parm_diff *d = is_fn_parm_diff(dif)) dif = peel_fn_parm_diff(d); else break; } return dif; } /// Test if a diff node represents a diff between two class or union /// types. /// /// @param d the diff node to consider. /// /// @return iff @p is a diff between two class or union types then /// return the instance of @ref class_or_union_diff that @p derives /// from. Otherwise, return nil. const class_or_union_diff* is_diff_of_class_or_union_type(const diff *d) {return dynamic_cast(d);} /// Test if a given diff node carries *only* a local type change. /// /// @param d the diff node to consider. /// /// @return true iff @p has a change and that change is a local type /// change. static bool has_local_type_change_only(const diff *d) { if (enum change_kind k = d->has_local_changes()) if ((k & LOCAL_NON_TYPE_CHANGE_KIND) == 0 && (k & LOCAL_TYPE_CHANGE_KIND) != 0) return true; return false; } /// Test if a diff node is a decl diff that only carries a basic type /// change on its type diff sub-node. /// ///Note that that pointers/references/qualified types diffs to basic /// type diffs are considered as having basic type change only. /// /// @param d the diff node to consider. /// /// @return true iff @p d is a decl diff that only carries a basic /// type change on its type diff sub-node. bool has_basic_type_change_only(const diff *d) { d = peel_typedef_qualified_type_or_parameter_diff(d); if (is_diff_of_basic_type(d, true) && d->has_changes()) return true; else if (const var_diff * v = dynamic_cast(d)) return (has_local_type_change_only(v) && is_diff_of_basic_type(v->type_diff().get(), true)); else if (const fn_parm_diff * p = dynamic_cast(d)) return (has_local_type_change_only(p) && is_diff_of_basic_type(p->type_diff().get(), true)); else if (const function_decl_diff* f = dynamic_cast(d)) return (has_local_type_change_only(f) && f->type_diff() && is_diff_of_basic_type(f->type_diff()->return_type_diff().get(), true)); return false; } }// end namespace comparison } // end namespace abigail