// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // -*- Mode: C++ -*- // // Copyright (C) 2017-2022 Red Hat, Inc. // // Author: Dodji Seketeli #include #include #include "abg-comparison-priv.h" #include "abg-reporter-priv.h" namespace abigail { namespace comparison { /// Convert a number in bits into a number in bytes. /// /// @param bits the number in bits to convert. /// /// @return the number @p bits converted into bytes. uint64_t convert_bits_to_bytes(size_t bits) {return bits / 8;} /// Emit a numerical value to an output stream. /// /// Depending on the current @ref diff_context, the number is going to /// be emitted either in decimal or hexadecimal base. /// /// @param value the value to emit. /// /// @param ctxt the current diff context. /// /// @param out the output stream to emit the numerical value to. void emit_num_value(uint64_t value, const diff_context& ctxt, ostream& out) { if (ctxt.show_hex_values()) out << std::hex << std::showbase ; else out << std::dec; out << value << std::dec << std::noshowbase; } /// Convert a bits value into a byte value if the current diff context /// instructs us to do so. /// /// @param bits the bits value to convert. /// /// @param ctxt the current diff context to consider. /// /// @return the resulting bits or bytes value, depending on what the /// diff context instructs us to do. uint64_t maybe_convert_bits_to_bytes(uint64_t bits, const diff_context& ctxt) { if (ctxt.show_offsets_sizes_in_bits()) return bits; return convert_bits_to_bytes(bits); } /// Emit a message showing the numerical change between two values, to /// a given output stream. /// /// The function emits a message like /// /// "XXX changes from old_bits to new_bits (in bits)" /// /// or /// /// "XXX changes from old_bits to new_bits (in bytes)" /// /// Depending on if the current diff context instructs us to emit the /// change in bits or bytes. XXX is the string content of the @p what /// parameter. /// /// @param what the string that tells us what the change represents. /// This is the "XXX" we refer to in the explanation above. /// /// @param old_bits the initial value (which changed) in bits. /// /// @param new_bits the final value (resulting from the change or @p /// old_bits) in bits. /// /// @param ctxt the current diff context to consider. /// /// @param out the output stream to send the change message to. /// /// @param show_bits_or_byte if this is true, then the message is /// going to precise if the changed value is in bits or bytes. /// Otherwise, no mention of that is made. void show_numerical_change(const string& what, uint64_t old_bits, uint64_t new_bits, const diff_context& ctxt, ostream& out, bool show_bits_or_byte) { bool can_convert_bits_to_bytes = (old_bits % 8 == 0 && new_bits % 8 == 0); uint64_t o = can_convert_bits_to_bytes ? maybe_convert_bits_to_bytes(old_bits, ctxt) : old_bits; uint64_t n = can_convert_bits_to_bytes ? maybe_convert_bits_to_bytes(new_bits, ctxt) : new_bits; string bits_or_bytes = (!can_convert_bits_to_bytes || ctxt.show_offsets_sizes_in_bits ()) ? "bits" : "bytes"; out << what << " changed from "; emit_num_value(o, ctxt, out); out << " to "; emit_num_value(n, ctxt, out); if (show_bits_or_byte) { out << " (in "; out << bits_or_bytes; out << ")"; } } /// Emit a message showing the value of a numerical value representing /// a size or an offset, preceded by a string. The message is ended /// by a part which says if the value is in bits or bytes. /// /// @param what the string prefix of the message to emit. /// /// @param value the numerical value to emit. /// /// @param ctxt the diff context to take into account. /// /// @param out the output stream to emit the message to. void show_offset_or_size(const string& what, uint64_t value, const diff_context& ctxt, ostream& out) { uint64_t v = value; bool can_convert_bits_to_bytes = (value % 8 == 0); if (can_convert_bits_to_bytes) v = maybe_convert_bits_to_bytes(v, ctxt); string bits_or_bytes = (!can_convert_bits_to_bytes || ctxt.show_offsets_sizes_in_bits()) ? "bits" : "bytes"; if (!what.empty()) out << what << " "; emit_num_value(v, ctxt, out); out << " (in " << bits_or_bytes << ")"; } /// Emit a message showing the value of a numerical value representing /// a size or an offset. The message is ended by a part which says if /// the value is in bits or bytes. /// /// @param value the numerical value to emit. /// /// @param ctxt the diff context to take into account. /// /// @param out the output stream to emit the message to. void show_offset_or_size(uint64_t value, const diff_context& ctxt, ostream& out) {show_offset_or_size("", value, ctxt, out);} /// Stream a string representation for a member function. /// /// @param ctxt the current diff context. /// /// @param mem_fn the member function to stream /// /// @param out the output stream to send the representation to void represent(const diff_context& ctxt, method_decl_sptr mem_fn, ostream& out) { if (!mem_fn || !is_member_function(mem_fn)) return; method_decl_sptr meth = dynamic_pointer_cast(mem_fn); ABG_ASSERT(meth); out << "'" << mem_fn->get_pretty_representation() << "'"; report_loc_info(meth, ctxt, out); if (get_member_function_is_virtual(mem_fn)) { ssize_t voffset = get_member_function_vtable_offset(mem_fn); ssize_t biggest_voffset = is_class_type(meth->get_type()->get_class_type())-> get_biggest_vtable_offset(); if (voffset > -1) { out << ", virtual at voffset "; emit_num_value(get_member_function_vtable_offset(mem_fn), ctxt, out); out << "/"; emit_num_value(biggest_voffset, ctxt, out); } } if (ctxt.show_linkage_names() && (mem_fn->get_symbol())) { out << " {" << mem_fn->get_symbol()->get_id_string() << "}"; } out << "\n"; } /// Stream a string representation for a data member. /// /// @param d the data member to stream /// /// @param ctxt the current diff context. /// /// @param out the output stream to send the representation to /// /// @param indent the indentation string to use for the change report. void represent_data_member(var_decl_sptr d, const diff_context_sptr& ctxt, ostream& out, const string& indent) { if (!is_data_member(d) || (!get_member_is_static(d) && !get_data_member_is_laid_out(d))) return; out << indent << "'" << d->get_pretty_representation(/*internal=*/false, /*qualified_name=*/false) << "'"; if (!get_member_is_static(d)) { // Do not emit offset information for data member of a union // type because all data members of a union are supposed to be // at offset 0. if (!is_union_type(d->get_scope())) show_offset_or_size(", at offset", get_data_member_offset(d), *ctxt, out); report_loc_info(d, *ctxt, out); } out << "\n"; } /// If a given @ref var_diff node carries a data member change in /// which the offset of the data member actually changed, then emit a /// string (to an output stream) that represents that offset change. /// /// For instance, if the offset of the data member increased by 32 /// bits then the string emitted is going to be "by +32 bits". /// /// If, on the other hand, the offset of the data member decreased by /// 64 bits then the string emitted is going to be "by -64 bits". /// /// This function is a sub-routine used by the reporting system. /// /// @param diff the diff node that potentially carries the data member /// change. /// /// @param ctxt the context in which the diff is being reported. /// /// @param out the output stream to emit the string to. void maybe_show_relative_offset_change(const var_diff_sptr &diff, diff_context& ctxt, ostream& out) { if (!ctxt.show_relative_offset_changes()) return; var_decl_sptr o = diff->first_var(); var_decl_sptr n = diff->second_var(); uint64_t first_offset = get_data_member_offset(o), second_offset = get_data_member_offset(n); string sign; uint64_t change = 0; if (first_offset < second_offset) { sign = "+"; change = second_offset - first_offset; } else if (first_offset > second_offset) { sign = "-"; change = first_offset - second_offset; } else return; if (!ctxt.show_offsets_sizes_in_bits()) change = convert_bits_to_bytes(change); string bits_or_bytes = ctxt.show_offsets_sizes_in_bits() ? "bits" : "bytes"; out << " (by " << sign; emit_num_value(change, ctxt, out); out << " " << bits_or_bytes << ")"; } /// If a given @ref var_diff node carries a hange in which the size of /// the variable actually changed, then emit a string (to an output /// stream) that represents that size change. /// /// For instance, if the size of the variable increased by 32 bits /// then the string emitted is going to be "by +32 bits". /// /// If, on the other hand, the size of the variable decreased by 64 /// bits then the string emitted is going to be "by -64 bits". /// /// This function is a sub-routine used by the reporting system. /// /// @param diff the diff node that potentially carries the variable /// change. /// /// @param ctxt the context in which the diff is being reported. /// /// @param out the output stream to emit the string to. void maybe_show_relative_size_change(const var_diff_sptr &diff, diff_context& ctxt, ostream& out) { if (!ctxt.show_relative_offset_changes()) return; var_decl_sptr o = diff->first_var(); var_decl_sptr n = diff->second_var(); uint64_t first_size = get_var_size_in_bits(o), second_size = get_var_size_in_bits(n); string sign; uint64_t change = 0; if (first_size < second_size) { sign = "+"; change = second_size - first_size; } else if (first_size > second_size) { sign = "-"; change = first_size - second_size; } else return; if (!ctxt.show_offsets_sizes_in_bits()) change = convert_bits_to_bytes(change); string bits_or_bytes = ctxt.show_offsets_sizes_in_bits() ? "bits" : "bytes"; out << " (by " << sign; emit_num_value(change, ctxt, out); out << " " << bits_or_bytes << ")"; } /// Represent the changes carried by an instance of @ref var_diff that /// represent a difference between two class data members. /// /// @param diff diff the diff node to represent. /// /// @param ctxt the diff context to use. /// /// @param local_only if true, only display local changes. /// /// @param out the output stream to send the representation to. /// /// @param indent the indentation string to use for the change report. void represent(const var_diff_sptr &diff, diff_context_sptr ctxt, ostream& out, const string& indent, bool local_only) { if (!ctxt->get_reporter()->diff_to_be_reported(diff.get())) return; const var_decl_sptr o = diff->first_var(); const var_decl_sptr n = diff->second_var(); const bool o_anon = !!is_anonymous_data_member(o); const bool n_anon = !!is_anonymous_data_member(n); const bool is_strict_anonymous_data_member_change = o_anon && n_anon; const string o_name = o->get_qualified_name(); const string n_name = n->get_qualified_name(); const uint64_t o_size = get_var_size_in_bits(o); const uint64_t n_size = get_var_size_in_bits(n); const uint64_t o_offset = get_data_member_offset(o); const uint64_t n_offset = get_data_member_offset(n); const string o_pretty_representation = o->get_pretty_representation(/*internal=*/false, /*qualified_name=*/false); // no n_pretty_representation here as it's only needed in a couple of places const bool show_size_offset_changes = ctxt->get_allowed_category() & SIZE_OR_OFFSET_CHANGE_CATEGORY; // Has the main diff text been output? bool emitted = false; // Are we continuing on a new line? (implies emitted) bool begin_with_and = false; // Have we reported a size change already? bool size_reported = false; //---------------------------------------------------------------- // First we'll try to emit a report about the type change of this // var_decl_diff. // // In the context of that type change report, we need to keep in // mind that because we want to emit specific (useful) reports about // anonymous data member changes, we'll try to detect the various // scenarii that involve anonymous data member changes. // // Then, as a fallback method, we'll emit a more generic type change // report for the other generic type changes. //---------------------------------------------------------------- if (is_strict_anonymous_data_member_change) { const string n_pretty_representation = n->get_pretty_representation(/*internal=*/false, /*qualified_name=*/false); const type_base_sptr o_type = o->get_type(), n_type = n->get_type(); if (o_pretty_representation != n_pretty_representation) { show_offset_or_size(indent + "anonymous data member at offset", o_offset, *ctxt, out); out << " changed from:\n" << indent << " " << o_pretty_representation << "\n" << indent << "to:\n" << indent << " " << n_pretty_representation << "\n"; begin_with_and = true; emitted = true; } else if (get_type_name(o_type) != get_type_name(n_type) && is_decl(o_type) && is_decl(n_type) && is_decl(o_type)->get_is_anonymous() && is_decl(n_type)->get_is_anonymous()) { out << indent << "while looking at anonymous data member '" << o_pretty_representation << "':\n" << indent << "the internal name of that anonymous data member" " changed from:\n" << indent << " " << get_type_name(o_type) << "\n" << indent << "to:\n" << indent << " " << get_type_name(n_type) << "\n" << indent << " This is usually due to " << "an anonymous member type being added or removed from " << "the containing type\n"; begin_with_and = true; emitted = true; } } else if (filtering::has_anonymous_data_member_change(diff)) { ABG_ASSERT(o_anon != n_anon); // So we are looking at a non-anonymous data member change from // or to an anonymous data member. const string n_pretty_representation = n->get_pretty_representation(/*internal=*/false, /*qualified_name=*/false); out << indent << (o_anon ? "anonymous " : "") << "data member " << o_pretty_representation; show_offset_or_size(" at offset", o_offset, *ctxt, out); out << " became " << (n_anon ? "anonymous " : "") << "data member '" << n_pretty_representation << "'\n"; begin_with_and = true; emitted = true; } // // If we haven't succeeded in emitting a specific type change report // (mainly related to anonymous data data member changes) then let's // try to emit a more generic report about the type change. // // This is the fallback method outlined in the comment at the // beginning of this section. // if (!emitted) if (const diff_sptr d = diff->type_diff()) { if (ctxt->get_reporter()->diff_to_be_reported(d.get())) { if (local_only) out << indent << "type '" << get_pretty_representation(o->get_type()) << "' of '" << (o_anon ? string("anonymous data member") : o->get_qualified_name()) << "' changed"; else out << indent << "type of '"<< (o_anon ? "anonymous data member ": "") << o_pretty_representation << "' changed"; if (d->currently_reporting()) out << ", as being reported\n"; else if (d->reported_once()) out << ", as reported earlier\n"; else { out << ":\n"; d->report(out, indent + " "); } begin_with_and = true; emitted = true; size_reported = true; } } // // Okay, now we are done with report type changes. Let's report the // other potential kinds of changes. // if (!filtering::has_anonymous_data_member_change(diff) && o_name != n_name) { if (filtering::has_harmless_name_change(o, n) && !(ctxt->get_allowed_category() & HARMLESS_DECL_NAME_CHANGE_CATEGORY)) ; else { if (begin_with_and) { out << indent << "and "; begin_with_and = false; } else if (!emitted) out << indent; else out << ", "; out << "name of '" << o_name << "' changed to '" << n_name << "'"; report_loc_info(n, *ctxt, out); emitted = true; } } if (get_data_member_is_laid_out(o) != get_data_member_is_laid_out(n)) { if (begin_with_and) { out << indent << "and "; begin_with_and = false; } else if (!emitted) out << indent << "'" << o_pretty_representation << "' "; else out << ", "; if (get_data_member_is_laid_out(o)) out << "is no more laid out"; else out << "now becomes laid out"; emitted = true; } if (show_size_offset_changes) { if (o_offset != n_offset) { if (begin_with_and) { out << indent << "and "; begin_with_and = false; } else if (!emitted) { out << indent; if (is_strict_anonymous_data_member_change) out << "anonymous data member "; out << "'" << o_pretty_representation << "' "; } else out << ", "; show_numerical_change("offset", o_offset, n_offset, *ctxt, out); maybe_show_relative_offset_change(diff, *ctxt, out); emitted = true; } if (!size_reported && o_size != n_size) { if (begin_with_and) { out << indent << "and "; begin_with_and = false; } else if (!emitted) { out << indent; if (is_strict_anonymous_data_member_change) out << "anonymous data member "; out << "'" << o_pretty_representation << "' "; } else out << ", "; show_numerical_change("size", o_size, n_size, *ctxt, out); maybe_show_relative_size_change(diff, *ctxt, out); emitted = true; } } if (o->get_binding() != n->get_binding()) { if (begin_with_and) { out << indent << "and "; begin_with_and = false; } else if (!emitted) out << indent << "'" << o_pretty_representation << "' "; else out << ", "; out << "elf binding changed from " << o->get_binding() << " to " << n->get_binding(); emitted = true; } if (o->get_visibility() != n->get_visibility()) { if (begin_with_and) { out << indent << "and "; begin_with_and = false; } else if (!emitted) out << indent << "'" << o_pretty_representation << "' "; else out << ", "; out << "visibility changed from " << o->get_visibility() << " to " << n->get_visibility(); emitted = true; } if ((ctxt->get_allowed_category() & ACCESS_CHANGE_CATEGORY) && (get_member_access_specifier(o) != get_member_access_specifier(n))) { if (begin_with_and) { out << indent << "and "; begin_with_and = false; } else if (!emitted) out << indent << "'" << o_pretty_representation << "' "; else out << ", "; out << "access changed from '" << get_member_access_specifier(o) << "' to '" << get_member_access_specifier(n) << "'"; emitted = true; } if (get_member_is_static(o) != get_member_is_static(n)) { if (begin_with_and) { out << indent << "and "; begin_with_and = false; } else if (!emitted) out << indent << "'" << o_pretty_representation << "' "; else out << ", "; if (get_member_is_static(o)) out << "is no more static"; else out << "now becomes static"; emitted = true; } if (begin_with_and) // do nothing as begin_with_and implies emitted ; else if (!emitted) // We appear to have fallen off the edge of the map. out << indent << "'" << o_pretty_representation << "' has *some* difference - please report as a bug"; else { ;// do nothing } emitted = true; if (!begin_with_and) out << "\n"; } /// Report the size and alignment changes of a type. /// /// @param first the first type to consider. /// /// @param second the second type to consider. /// /// @param ctxt the content of the current diff. /// /// @param out the output stream to report the change to. /// /// @param indent the string to use for indentation. void report_size_and_alignment_changes(type_or_decl_base_sptr first, type_or_decl_base_sptr second, diff_context_sptr ctxt, ostream& out, const string& indent) { type_base_sptr f = dynamic_pointer_cast(first), s = dynamic_pointer_cast(second); if (!s || !f) return; class_or_union_sptr first_class = is_class_or_union_type(first), second_class = is_class_or_union_type(second); if (filtering::has_class_decl_only_def_change(first_class, second_class)) // So these two classes differ only by the fact that one is the // declaration-only form of the second. The declaration-only class // is of unknown size (recorded as 0) and it is not meaningful to // report a size change. return; unsigned fs = f->get_size_in_bits(), ss = s->get_size_in_bits(), fa = f->get_alignment_in_bits(), sa = s->get_alignment_in_bits(); array_type_def_sptr first_array = is_array_type(is_type(first)), second_array = is_array_type(is_type(second)); unsigned fdc = first_array ? first_array->get_dimension_count(): 0, sdc = second_array ? second_array->get_dimension_count(): 0; if (ctxt->get_allowed_category() & SIZE_OR_OFFSET_CHANGE_CATEGORY) { if (fs != ss || fdc != sdc) { if (first_array && second_array) { // We are looking at size or alignment changes between two // arrays ... out << indent << "array type size changed from "; if (first_array->is_infinite()) out << "infinity"; else emit_num_value(first_array->get_size_in_bits(), *ctxt, out); out << " to "; if (second_array->is_infinite()) out << "infinity"; else emit_num_value(second_array->get_size_in_bits(), *ctxt, out); out << "\n"; if (sdc != fdc) { out << indent + " " << "number of dimensions changed from " << fdc << " to " << sdc << "\n"; } array_type_def::subranges_type::const_iterator i, j; for (i = first_array->get_subranges().begin(), j = second_array->get_subranges().begin(); (i != first_array->get_subranges().end() && j != second_array->get_subranges().end()); ++i, ++j) { if ((*i)->get_length() != (*j)->get_length()) { out << indent << "array type subrange " << i - first_array->get_subranges().begin() + 1 << " changed length from "; if ((*i)->is_infinite()) out << "infinity"; else out << (*i)->get_length(); out << " to "; if ((*j)->is_infinite()) out << "infinity"; else out << (*j)->get_length(); out << "\n"; } } } // end if (first_array && second_array) else if (fs != ss) { out << indent; show_numerical_change("type size", fs, ss, *ctxt, out); out << "\n"; } } // end if (fs != ss || fdc != sdc) else if (ctxt->show_relative_offset_changes()) { out << indent << "type size hasn't changed\n"; } } if ((ctxt->get_allowed_category() & SIZE_OR_OFFSET_CHANGE_CATEGORY) && (fa != sa)) { out << indent; show_numerical_change("type alignment", fa, sa, *ctxt, out, /*show_bits_or_bytes=*/false); out << "\n"; } } /// @param tod the type or declaration to emit loc info about /// /// @param ctxt the content of the current diff. /// /// @param out the output stream to report the change to. /// /// @return true iff something was reported. bool report_loc_info(const type_or_decl_base_sptr& tod, const diff_context& ctxt, ostream &out) { if (!ctxt.show_locs()) return false; decl_base_sptr decl = is_decl(tod); if (!decl) return false; location loc; translation_unit* tu = get_translation_unit(decl); if (tu && (loc = decl->get_location())) { string path; unsigned line, column; loc.expand(path, line, column); //tu->get_loc_mgr().expand_location(loc, path, line, column); path = basename(const_cast(path.c_str())); out << " at " << path << ":" << line << ":" << column; return true; } return false; } /// Report the name, size and alignment changes of a type. /// /// @param first the first type to consider. /// /// @param second the second type to consider. /// /// @param ctxt the content of the current diff. /// /// @param out the output stream to report the change to. /// /// @param indent the string to use for indentation. void report_name_size_and_alignment_changes(decl_base_sptr first, decl_base_sptr second, diff_context_sptr ctxt, ostream& out, const string& indent) { string fn = first->get_qualified_name(), sn = second->get_qualified_name(); if (fn != sn) { if (!(ctxt->get_allowed_category() & HARMLESS_DECL_NAME_CHANGE_CATEGORY) && filtering::has_harmless_name_change(first, second)) // This is a harmless name change. but then // HARMLESS_DECL_NAME_CHANGE_CATEGORY doesn't seem allowed. ; else { out << indent; if (is_type(first)) out << "type"; else out << "declaration"; out << " name changed from '" << fn << "' to '" << sn << "'"; out << "\n"; } } report_size_and_alignment_changes(first, second, ctxt, out, indent); } /// Output the header preceding the the report for /// insertion/deletion/change of a part of a class. This is a /// subroutine of class_diff::report. /// /// @param out the output stream to output the report to. /// /// @param number the number of insertion/deletion to refer to in the /// header. /// /// @param num_filtered the number of filtered changes. /// /// @param k the kind of diff (insertion/deletion/change) we want the /// head to introduce. /// /// @param section_name the name of the sub-part of the class to /// report about. /// /// @param indent the string to use as indentation prefix in the /// header. void report_mem_header(ostream& out, size_t number, size_t num_filtered, diff_kind k, const string& section_name, const string& indent) { size_t net_number = number - num_filtered; string change; char colon_or_semi_colon = ':'; switch (k) { case del_kind: change = (number > 1) ? "deletions" : "deletion"; break; case ins_kind: change = (number > 1) ? "insertions" : "insertion"; break; case subtype_change_kind: case change_kind: change = (number > 1) ? "changes" : "change"; break; } if (net_number == 0) { out << indent << "no " << section_name << " " << change; colon_or_semi_colon = ';'; } else if (net_number == 1) out << indent << "1 " << section_name << " " << change; else out << indent << net_number << " " << section_name << " " << change; if (num_filtered) out << " (" << num_filtered << " filtered)"; out << colon_or_semi_colon << "\n"; } /// Output the header preceding the the report for /// insertion/deletion/change of a part of a class. This is a /// subroutine of class_diff::report. /// /// @param out the output stream to output the report to. /// /// @param k the kind of diff (insertion/deletion/change) we want the /// head to introduce. /// /// @param section_name the name of the sub-part of the class to /// report about. /// /// @param indent the string to use as indentation prefix in the /// header. void report_mem_header(ostream& out, diff_kind k, const string& section_name, const string& indent) { string change; switch (k) { case del_kind: change = "deletions"; break; case ins_kind: change = "insertions"; break; case subtype_change_kind: case change_kind: change = "changes"; break; } out << indent << "there are " << section_name << " " << change << ":\n"; } /// Report the differences in access specifiers and static-ness for /// class members. /// /// @param decl1 the first class member to consider. /// /// @param decl2 the second class member to consider. /// /// @param out the output stream to send the report to. /// /// @param indent the indentation string to use for the report. /// /// @return true if something was reported, false otherwise. bool maybe_report_diff_for_member(const decl_base_sptr& decl1, const decl_base_sptr& decl2, const diff_context_sptr& ctxt, ostream& out, const string& indent) { bool reported = false; if (!is_member_decl(decl1) || !is_member_decl(decl2)) return reported; string decl1_repr = decl1->get_pretty_representation(), decl2_repr = decl2->get_pretty_representation(); if (get_member_is_static(decl1) != get_member_is_static(decl2)) { bool lost = get_member_is_static(decl1); out << indent << "'" << decl1_repr << "' "; if (report_loc_info(decl2, *ctxt, out)) out << " "; if (lost) out << "became non-static"; else out << "became static"; out << "\n"; reported = true; } if ((ctxt->get_allowed_category() & ACCESS_CHANGE_CATEGORY) && (get_member_access_specifier(decl1) != get_member_access_specifier(decl2))) { out << indent << "'" << decl1_repr << "' access changed from '" << get_member_access_specifier(decl1) << "' to '" << get_member_access_specifier(decl2) << "'\n"; reported = true; } return reported; } /// Report the difference between two ELF symbols, if there is any. /// /// @param symbol1 the first symbol to consider. /// /// @param symbol2 the second symbol to consider. /// /// @param ctxt the diff context. /// /// @param the output stream to emit the report to. /// /// @param indent the indentation string to use. void maybe_report_diff_for_symbol(const elf_symbol_sptr& symbol1, const elf_symbol_sptr& symbol2, const diff_context_sptr& ctxt, ostream& out, const string& indent) { if (!symbol1 || !symbol2 || symbol1 == symbol2) return; if (symbol1->get_size() != symbol2->get_size()) { out << indent; show_numerical_change("size of symbol", symbol1->get_size(), symbol2->get_size(), *ctxt, out, /*show_bits_or_bytes=*/false); out << "\n"; } if (symbol1->get_name() != symbol2->get_name()) { out << indent << "symbol name changed from " << symbol1->get_name() << " to " << symbol2->get_name() << "\n"; } if (symbol1->get_type() != symbol2->get_type()) { out << indent << "symbol type changed from '" << symbol1->get_type() << "' to '" << symbol2->get_type() << "'\n"; } if (symbol1->is_public() != symbol2->is_public()) { out << indent << "symbol became "; if (symbol2->is_public()) out << "exported"; else out << "non-exported"; out << "\n"; } if (symbol1->is_defined() != symbol2->is_defined()) { out << indent << "symbol became "; if (symbol2->is_defined()) out << "defined"; else out << "undefined"; out << "\n"; } if (symbol1->get_version() != symbol2->get_version()) { out << indent << "symbol version changed from " << symbol1->get_version().str() << " to " << symbol2->get_version().str() << "\n"; } const abg_compat::optional& crc1 = symbol1->get_crc(); const abg_compat::optional& crc2 = symbol2->get_crc(); if (crc1 != crc2) { const std::string none = "(none)"; out << indent << "CRC (modversions) changed from " << std::showbase << std::hex; if (crc1.has_value()) out << crc1.value(); else out << none; out << " to "; if (crc2.has_value()) out << crc2.value(); else out << none; out << std::noshowbase << std::dec << "\n"; } const abg_compat::optional& ns1 = symbol1->get_namespace(); const abg_compat::optional& ns2 = symbol2->get_namespace(); if (ns1 != ns2) { const std::string none = "(none)"; out << indent << "namespace changed from "; if (ns1.has_value()) out << "'" << ns1.value() << "'"; else out << none; out << " to "; if (ns2.has_value()) out << "'" << ns2.value() << "'"; else out << none; out << "\n"; } } /// For a given symbol, emit a string made of its name and version. /// The string also contains the list of symbols that alias this one. /// /// @param out the output string to emit the resulting string to. /// /// @param indent the indentation string to use before emitting the /// resulting string. /// /// @param symbol the symbol to emit the representation string for. /// /// @param sym_map the symbol map to consider to look for aliases of /// @p symbol. void show_linkage_name_and_aliases(ostream& out, const string& indent, const elf_symbol& symbol, const string_elf_symbols_map_type& sym_map) { out << indent << symbol.get_id_string(); string aliases = symbol.get_aliases_id_string(sym_map, /*include_symbol_itself=*/false); if (!aliases.empty()) out << ", aliases " << aliases; } /// Report changes about types that are not reachable from global /// functions and variables, in a given @param corpus_diff. /// /// @param d the corpus_diff to consider. /// /// @param s the statistics of the changes, after filters and /// suppressions are reported. This is typically what is returned by /// corpus_diff::apply_filters_and_suppressions_before_reporting(). /// /// @param indent the indendation string (usually a string of white /// spaces) to use for indentation during the reporting. /// /// @param out the output stream to emit the report to. void maybe_report_unreachable_type_changes(const corpus_diff& d, const corpus_diff::diff_stats &s, const string& indent, ostream& out) { const diff_context_sptr& ctxt = d.context(); if (!(ctxt->show_unreachable_types() && (!d.priv_->deleted_unreachable_types_.empty() || !d.priv_->added_unreachable_types_.empty() || !d.priv_->changed_unreachable_types_.empty()))) // The user either doesn't want us to show changes about // unreachable types or there are not such changes. return; // Handle removed unreachable types. if (s.net_num_removed_unreachable_types() == 1) out << indent << "1 removed type unreachable from any public interface:\n\n"; else if (s.net_num_removed_unreachable_types() > 1) out << indent << s.net_num_removed_unreachable_types() << " removed types unreachable from any public interface:\n\n"; vector sorted_removed_unreachable_types; sort_string_type_base_sptr_map(d.priv_->deleted_unreachable_types_, sorted_removed_unreachable_types); bool emitted = false; for (vector::const_iterator i = sorted_removed_unreachable_types.begin(); i != sorted_removed_unreachable_types.end(); ++i) { if (d.priv_->deleted_unreachable_type_is_suppressed((*i).get())) continue; out << indent << " [D] '" << get_pretty_representation(*i) << "'"; report_loc_info(*i, *ctxt, out); out << "\n"; emitted = true; } if (emitted) out << "\n"; // Handle changed unreachable types! if (s.net_num_changed_unreachable_types() == 1) out << indent << "1 changed type unreachable from any public interface:\n\n"; else if (s.net_num_changed_unreachable_types() > 1) out << indent << s.net_num_changed_unreachable_types() << " changed types unreachable from any public interface:\n\n"; diff_sptrs_type sorted_diff_sptrs; sort_string_diff_sptr_map(d.priv_->changed_unreachable_types_, sorted_diff_sptrs); for (diff_sptrs_type::const_iterator i = sorted_diff_sptrs.begin(); i != sorted_diff_sptrs.end(); ++i) { diff_sptr diff = *i; if (!diff || !diff->to_be_reported()) continue; string repr = diff->first_subject()->get_pretty_representation(); out << indent << " [C] '" << repr << "' changed:\n"; diff->report(out, indent + " "); // Extra spacing. out << "\n"; } // Changed types have extra spacing already. No new line here. // Handle added unreachable types. if (s.net_num_added_unreachable_types() == 1) out << indent << "1 added type unreachable from any public interface:\n\n"; else if (s.net_num_added_unreachable_types() > 1) out << indent << s.net_num_added_unreachable_types() << " added types unreachable from any public interface:\n\n"; vector sorted_added_unreachable_types; sort_string_type_base_sptr_map(d.priv_->added_unreachable_types_, sorted_added_unreachable_types); emitted = false; for (vector::const_iterator i = sorted_added_unreachable_types.begin(); i != sorted_added_unreachable_types.end(); ++i) { if (d.priv_->added_unreachable_type_is_suppressed((*i).get())) continue; out << indent << " [A] '" << get_pretty_representation(*i) << "'"; report_loc_info(*i, *ctxt, out); out << "\n"; emitted = true; } if (emitted) out << "\n"; } /// If a given diff node impacts some public interfaces, then report /// about those impacted interfaces on a given output stream. /// /// @param d the diff node to get the impacted interfaces for. /// /// @param out the output stream to report to. /// /// @param indent the white space string to use for indentation. void maybe_report_interfaces_impacted_by_diff(const diff *d, ostream &out, const string &indent) { const diff_context_sptr &ctxt = d->context(); const corpus_diff_sptr &corp_diff = ctxt->get_corpus_diff(); if (!corp_diff) return; if (!ctxt->show_impacted_interfaces()) return; const diff_maps &maps = corp_diff->get_leaf_diffs(); artifact_sptr_set_type* impacted_artifacts = maps.lookup_impacted_interfaces(d); if (impacted_artifacts == 0) return; if (impacted_artifacts->empty()) return; vector sorted_impacted_interfaces; sort_artifacts_set(*impacted_artifacts, sorted_impacted_interfaces); size_t num_impacted_interfaces = impacted_artifacts->size(); if (num_impacted_interfaces == 1) out << indent << "one impacted interface:\n"; else out << indent << num_impacted_interfaces << " impacted interfaces:\n"; string cur_indent = indent + " "; vector::const_iterator it; for (it = sorted_impacted_interfaces.begin(); it != sorted_impacted_interfaces.end(); ++it) { out << cur_indent << get_pretty_representation(*it) << "\n"; } } /// If a given diff node impacts some public interfaces, then report /// about those impacted interfaces on standard output. /// /// @param d the diff node to get the impacted interfaces for. /// /// @param out the output stream to report to. /// /// @param indent the white space string to use for indentation. void maybe_report_interfaces_impacted_by_diff(const diff_sptr &d, ostream &out, const string &indent) {return maybe_report_interfaces_impacted_by_diff(d.get(), out, indent);} /// Tests if the diff node is to be reported. /// /// @param p the diff to consider. /// /// @return true iff the diff is to be reported. bool reporter_base::diff_to_be_reported(const diff *d) const {return d && d->to_be_reported();} /// Report about data members replaced by an anonymous data member /// without changing the overall bit-layout of the class or union in /// an ABI-meaningful way. /// /// @param d the diff to consider. /// /// @param out the output stream to emit the change report to. /// /// @param indent the indentation string to use. void maybe_report_data_members_replaced_by_anon_dm(const class_or_union_diff &d, ostream &out, const string &indent) { const diff_context_sptr& ctxt = d.context(); if ((ctxt->get_allowed_category() & HARMLESS_DATA_MEMBER_CHANGE_CATEGORY) && !d.data_members_replaced_by_adms().empty()) { // Let's detect all the data members that are replaced by // members of the same anonymous data member and report them // in one go. for (changed_var_sptrs_type::const_iterator i = d.ordered_data_members_replaced_by_adms().begin(); i != d.ordered_data_members_replaced_by_adms().end();) { // This contains the data members replaced by the same // anonymous data member. vector dms_replaced_by_same_anon_dm; dms_replaced_by_same_anon_dm.push_back(i->first); // This contains the anonymous data member that replaced the // data members in the variable above. var_decl_sptr anonymous_data_member = i->second; // Let's look forward to see if the subsequent data // members were replaced by members of // anonymous_data_member. for (++i; i != d.ordered_data_members_replaced_by_adms().end() && *i->second == *anonymous_data_member; ++i) dms_replaced_by_same_anon_dm.push_back(i->first); bool several_data_members_replaced = dms_replaced_by_same_anon_dm.size() > 1; out << indent << "data member"; if (several_data_members_replaced) out << "s"; bool first_data_member = true; for (vector::const_iterator it = dms_replaced_by_same_anon_dm.begin(); it != dms_replaced_by_same_anon_dm.end(); ++it) { string name = (*it)->get_qualified_name(); if (!first_data_member) out << ","; out << " '" << name << "'"; first_data_member = false; } if (several_data_members_replaced) out << " were "; else out << " was "; out << "replaced by anonymous data member:\n" << indent + " " << "'" << anonymous_data_member->get_pretty_representation() << "'\n"; } } } /// Report about the base classes of a class having been re-ordered. /// /// @param d the class diff to consider. /// /// @param out the output stream to report the change to. /// /// @param indent the indentation string to use. void maybe_report_base_class_reordering(const class_diff &d, ostream &out, const string &indent) { if (d.moved_bases().empty()) return; class_decl_sptr first = d.first_class_decl(), second = d.second_class_decl(); ABG_ASSERT(!first->get_base_specifiers().empty()); ABG_ASSERT(!second->get_base_specifiers().empty()); out << indent << "base classes of '" << first->get_pretty_representation() << "' are re-ordered from: "; vector classes = {first, second}; unsigned nb_classes_seen = 0; for (auto &klass : classes) { if (nb_classes_seen >= 1) out << " to: "; out << "'"; bool needs_comma = false; for (auto &b : klass->get_base_specifiers()) { if (needs_comma) out << ", "; if (b->get_is_virtual()) out << "virtual "; out << b->get_base_class()->get_qualified_name(); needs_comma = true; } out << "'"; nb_classes_seen++; } if (nb_classes_seen) out << "\n"; } } // Namespace comparison } // end namespace abigail