blob: 8f4e78c67d60e51582685a757950b4f8ddbaae5e [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// -*- Mode: C++ -*-
//
// Copyright (C) 2017-2020 Red Hat, Inc.
//
// Author: Dodji Seketeli
/// @file
///
/// This is the implementation of the
/// abigail::comparison::default_reporter type.
#include "abg-comparison-priv.h"
#include "abg-reporter.h"
#include "abg-reporter-priv.h"
namespace abigail
{
namespace comparison
{
/// Test if a diff node is to be reported by the current instance of
/// @ref leaf_reporter.
///
/// A node is said to be reported by the current instance of @ref
/// leaf_reporter if the node carries local changes and if the node's
/// reporting hasn't been suppressed.
bool
leaf_reporter::diff_to_be_reported(const diff *d) const
{return d && d->to_be_reported() && d->has_local_changes();}
/// Test if a given instance of @ref corpus_diff carries changes whose
/// reports are not suppressed by any suppression specification. In
/// effect, these are deemed incompatible ABI changes.
///
/// @param d the @ref corpus_diff to consider
///
/// @return true iff @p d carries subtype changes that are deemed
/// incompatible ABI changes.
bool
leaf_reporter::diff_has_net_changes(const corpus_diff *d) const
{
if (!d)
return false;
const corpus_diff::diff_stats& stats = const_cast<corpus_diff*>(d)->
apply_filters_and_suppressions_before_reporting();
// Logic here should match emit_diff_stats.
return (d->architecture_changed()
|| d->soname_changed()
|| stats.net_num_func_removed()
|| stats.net_num_leaf_type_changes()
|| stats.net_num_leaf_func_changes()
|| stats.net_num_func_added()
|| stats.net_num_vars_removed()
|| stats.net_num_leaf_var_changes()
|| stats.net_num_vars_added()
|| stats.net_num_removed_unreachable_types()
|| stats.net_num_changed_unreachable_types()
|| stats.net_num_added_unreachable_types()
|| stats.net_num_removed_func_syms()
|| stats.net_num_added_func_syms()
|| stats.net_num_removed_var_syms()
|| stats.net_num_added_var_syms());
}
/// See if a diff is important.
///
/// All changes to non-class/unions are important.
/// Changes to class/unions are important if there are
/// new or removed members
/// a non-boring change to a member
/// A non-boring change is one where the type decl has changed.
/// So an unimportant change is one where the class/struct
/// may have changed size but its declaration is unchanged.
static bool
is_important(const diff *d)
{
const class_or_union_diff* cou_dif = dynamic_cast<const class_or_union_diff*>(d);
if (cou_dif) {
if (cou_dif->member_fns_changes())
return true;
if (cou_dif->data_members_changes()) {
if (cou_dif->class_or_union_diff::get_priv()->
get_deleted_non_static_data_members_number())
return true;
if (cou_dif->class_or_union_diff::get_priv()->inserted_data_members_.size())
return true;
auto& changed_dm = cou_dif->class_or_union_diff::get_priv()->sorted_changed_dm_;
for (const auto& sub_dif : changed_dm) {
auto n1 = sub_dif->first_var()->get_pretty_representation();
auto n2 = sub_dif->second_var()->get_pretty_representation();
if (n1 != n2)
return true;
}
auto& subtype_changed_dm = cou_dif->class_or_union_diff::get_priv()->sorted_subtype_changed_dm_;
for (const auto& sub_dif : subtype_changed_dm) {
auto n1 = sub_dif->first_var()->get_pretty_representation();
auto n2 = sub_dif->second_var()->get_pretty_representation();
if (n1 != n2)
return true;
}
}
return false;
}
return true;
}
/// Report the changes carried by the diffs contained in an instance
/// of @ref string_diff_ptr_map.
///
/// @param mapp the set of diffs to report for.
///
/// @param out the output stream to report the diffs to.
///
/// @param indent the string to use for indentation.
static void
report_diffs(const reporter_base& r,
const string_diff_ptr_map& mapp,
ostream& out,
const string& indent)
{
diff_ptrs_type sorted_diffs;
sort_string_diff_ptr_map(mapp, sorted_diffs);
bool started_to_emit = false;
for (diff_ptrs_type::const_iterator i = sorted_diffs.begin();
i != sorted_diffs.end();
++i)
{
if (const var_diff *d = is_var_diff(*i))
if (is_data_member(d->first_var()))
continue;
if (r.diff_to_be_reported((*i)->get_canonical_diff()))
{
if (started_to_emit)
out << "\n";
string n = (*i)->first_subject()->get_pretty_representation();
out << indent << "'" << n;
report_loc_info((*i)->first_subject(),
*(*i)->context(), out);
diff* canon_diff = (*i)->get_canonical_diff();
out << "' changed";
// Work out whether the diff has only indirect changes.
if ((*i)->context()->flag_indirect_changes()
&& !is_important(canon_diff))
out << " (indirectly)";
out << ":\n";
canon_diff->report(out, indent + " ");
started_to_emit = true;
}
}
}
/// Report the type changes carried by an instance of @ref diff_maps.
///
/// @param maps the set of diffs to report.
///
/// @param out the output stream to report the diffs to.
///
/// @param indent the string to use for indentation.
static void
report_type_changes_from_diff_maps(const leaf_reporter& reporter,
const diff_maps& maps,
ostream& out,
const string& indent)
{
// basic types
report_diffs(reporter, maps.get_type_decl_diff_map(), out, indent);
// enums
report_diffs(reporter, maps.get_enum_diff_map(), out, indent);
// classes
report_diffs(reporter, maps.get_class_diff_map(), out, indent);
// unions
report_diffs(reporter, maps.get_union_diff_map(), out, indent);
// typedefs
report_diffs(reporter, maps.get_typedef_diff_map(), out, indent);
// arrays
report_diffs(reporter, maps.get_array_diff_map(), out, indent);
// It doesn't make sense to report function type changes, does it?
// report_diffs(reporter, maps.get_function_type_diff_map(), out, indent);
// distinct diffs
report_diffs(reporter, maps.get_distinct_diff_map(), out, indent);
// function parameter diffs
report_diffs(reporter, maps.get_fn_parm_diff_map(), out, indent);
}
/// Report the changes carried by an instance of @ref diff_maps.
///
/// @param maps the set of diffs to report.
///
/// @param out the output stream to report the diffs to.
///
/// @param indent the string to use for indentation.
void
leaf_reporter::report_changes_from_diff_maps(const diff_maps& maps,
ostream& out,
const string& indent) const
{
report_type_changes_from_diff_maps(*this, maps, out, indent);
// function decls
report_diffs(*this, maps.get_function_decl_diff_map(), out, indent);
// var decl
report_diffs(*this, maps.get_var_decl_diff_map(), out, indent);
}
/// Report the changes carried by a @ref typedef_diff node.
///
/// @param out the output stream to report to.
///
/// @param indent the white space string to use for indentation.
void
leaf_reporter::report(const typedef_diff& d,
ostream& out,
const string& indent) const
{
if (!diff_to_be_reported(&d))
return;
// all changes carried by a typedef_diff are considered local, so
// let's just call the default reporter here.
default_reporter::report(d, out, indent);
maybe_report_interfaces_impacted_by_diff(&d, out, indent);
}
/// Report the changes carried by a @ref qualified_type_diff node.
///
/// @param out the output stream to report to.
///
/// @param indent the white space string to use for indentation.
void
leaf_reporter::report(const qualified_type_diff& d, ostream& out,
const string& indent) const
{
if (!diff_to_be_reported(&d))
return;
report_local_qualified_type_changes(d, out, indent);
}
/// Report the changes carried by a @ref pointer_diff node.
///
/// Note that this function does nothing because a @ref pointer_diff
/// node never carries local changes.
void
leaf_reporter::report(const pointer_diff &d,
ostream& out,
const string& indent) const
{
// Changes that modify the representation of a pointed-to type is
// considered local to the pointer type.
if (!diff_to_be_reported(&d))
return;
out << indent
<< "pointer type changed from: '"
<< d.first_pointer()->get_pretty_representation()
<< "' to: '"
<< d.second_pointer()->get_pretty_representation()
<< "'\n";
}
/// Report the changes carried by a @ref reference_diff node.
///
/// @param out the output stream to report to.
///
/// @param indent the white space string to use for indentation.
void
leaf_reporter::report(const reference_diff& d,
ostream& out,
const string& indent) const
{
if (!diff_to_be_reported(&d))
return;
report_local_reference_type_changes(d, out, indent);
}
/// Report the changes carried by a @ref fn_parm_diff node.
///
/// @param out the output stream to report to.
///
/// @param indent the white space string to use for indentation.
void
leaf_reporter::report(const fn_parm_diff& d,
ostream& out,
const string& indent) const
{
if (!diff_to_be_reported(&d))
return;
ABG_ASSERT(diff_to_be_reported(d.type_diff().get()));
function_decl::parameter_sptr f = d.first_parameter();
out << indent
<< "parameter " << f->get_index();
report_loc_info(f, *d.context(), out);
out << " of type '"
<< f->get_type_pretty_representation()
<< "' changed:\n";
d.type_diff()->report(out, indent + " ");
}
/// Report the changes carried by a @ref function_type_diff node.
///
/// @param out the output stream to report to.
///
/// @param indent the white space string to use for indentation.
void
leaf_reporter::report(const function_type_diff& d,
ostream& out,
const string& indent) const
{
if (!diff_to_be_reported(&d))
return;
report_local_function_type_changes(d, out, indent);
if (diff_to_be_reported(d.priv_->return_type_diff_.get()))
{
out << indent << "return type changed:\n";
d.priv_->return_type_diff_->report(out, indent + " ");
}
// Hmmh, the above was quick. Now report about function parameters;
//
// Report about the parameter types that have changed sub-types.
for (vector<fn_parm_diff_sptr>::const_iterator i =
d.priv_->sorted_subtype_changed_parms_.begin();
i != d.priv_->sorted_subtype_changed_parms_.end();
++i)
{
diff_sptr dif = *i;
if (diff_to_be_reported(dif.get()))
dif->report(out, indent);
}
}
/// Report the changes carried by a @ref scope_diff node.
///
/// @param out the output stream to report to.
///
/// @param indent the white space string to use for indentation.
void
leaf_reporter::report(const scope_diff& d,
ostream& out,
const string& indent) const
{
if (!d.to_be_reported())
return;
// Report changed types.
unsigned num_changed_types = d.changed_types().size();
if (num_changed_types)
out << indent << "changed types:\n";
for (diff_sptrs_type::const_iterator dif = d.changed_types().begin();
dif != d.changed_types().end();
++dif)
{
if (!*dif || !diff_to_be_reported((*dif).get()))
continue;
out << indent << " '"
<< (*dif)->first_subject()->get_pretty_representation()
<< "' changed:\n";
(*dif)->report(out, indent + " ");
}
// Report changed decls
unsigned num_changed_decls = d.changed_decls().size();
if (num_changed_decls)
out << indent << "changed declarations:\n";
for (diff_sptrs_type::const_iterator dif= d.changed_decls().begin();
dif != d.changed_decls().end ();
++dif)
{
if (!*dif || !diff_to_be_reported((*dif).get()))
continue;
out << indent << " '"
<< (*dif)->first_subject()->get_pretty_representation()
<< "' was changed to '"
<< (*dif)->second_subject()->get_pretty_representation() << "'";
report_loc_info((*dif)->second_subject(), *d.context(), out);
out << ":\n";
(*dif)->report(out, indent + " ");
}
// Report removed types/decls
for (string_decl_base_sptr_map::const_iterator i =
d.priv_->deleted_types_.begin();
i != d.priv_->deleted_types_.end();
++i)
out << indent
<< " '"
<< i->second->get_pretty_representation()
<< "' was removed\n";
if (d.priv_->deleted_types_.size())
out << "\n";
for (string_decl_base_sptr_map::const_iterator i =
d.priv_->deleted_decls_.begin();
i != d.priv_->deleted_decls_.end();
++i)
out << indent
<< " '"
<< i->second->get_pretty_representation()
<< "' was removed\n";
if (d.priv_->deleted_decls_.size())
out << "\n";
// Report added types/decls
bool emitted = false;
for (string_decl_base_sptr_map::const_iterator i =
d.priv_->inserted_types_.begin();
i != d.priv_->inserted_types_.end();
++i)
{
// Do not report about type_decl as these are usually built-in
// types.
if (dynamic_pointer_cast<type_decl>(i->second))
continue;
out << indent
<< " '"
<< i->second->get_pretty_representation()
<< "' was added\n";
emitted = true;
}
if (emitted)
out << "\n";
emitted = false;
for (string_decl_base_sptr_map::const_iterator i =
d.priv_->inserted_decls_.begin();
i != d.priv_->inserted_decls_.end();
++i)
{
// Do not report about type_decl as these are usually built-in
// types.
if (dynamic_pointer_cast<type_decl>(i->second))
continue;
out << indent
<< " '"
<< i->second->get_pretty_representation()
<< "' was added\n";
emitted = true;
}
if (emitted)
out << "\n";
}
/// Report the changes carried by a @ref array_diff node.
///
/// @param out the output stream to report to.
///
/// @param indent the white space string to use for indentation.
void
leaf_reporter::report(const array_diff& d,
ostream& out,
const string& indent) const
{
if (!diff_to_be_reported(&d))
return;
RETURN_IF_BEING_REPORTED_OR_WAS_REPORTED_EARLIER3(d.first_array(),
d.second_array(),
"array type");
report_name_size_and_alignment_changes(d.first_array(),
d.second_array(),
d.context(),
out, indent);
diff_sptr dif = d.element_type_diff();
if (diff_to_be_reported(dif.get()))
{
string fn = ir::get_pretty_representation(is_type(dif->first_subject()));
// report array element type changes
out << indent << "array element type '"
<< fn << "' changed: \n";
dif->report(out, indent + " ");
}
maybe_report_interfaces_impacted_by_diff(&d, out, indent);
}
/// Report the changes carried by a @ref class_or_union_diff node.
///
/// @param out the output stream to report to.
///
/// @param indent the white space string to use for indentation.
void
leaf_reporter::report(const class_or_union_diff& d,
ostream& out,
const string& indent) const
{
if (!diff_to_be_reported(&d))
return;
class_or_union_sptr first = d.first_class_or_union(),
second = d.second_class_or_union();
const diff_context_sptr& ctxt = d.context();
// Report class decl-only -> definition change.
if (ctxt->get_allowed_category() & TYPE_DECL_ONLY_DEF_CHANGE_CATEGORY)
if (filtering::has_class_decl_only_def_change(first, second))
{
string was =
first->get_is_declaration_only()
? " was a declaration-only type"
: " was a defined type";
string is_now =
second->get_is_declaration_only()
? " and is now a declaration-only type"
: " and is now a defined type";
out << indent << "type " << first->get_pretty_representation()
<< was << is_now << "\n";
return;
}
// member functions
if (d.member_fns_changes())
{
// report deletions
int numdels = d.get_priv()->deleted_member_functions_.size();
size_t num_filtered =
d.get_priv()->count_filtered_deleted_mem_fns(ctxt);
if (numdels)
report_mem_header(out, numdels, num_filtered, del_kind,
"member function", indent);
for (string_member_function_sptr_map::const_iterator i =
d.get_priv()->deleted_member_functions_.begin();
i != d.get_priv()->deleted_member_functions_.end();
++i)
{
if (!(ctxt->get_allowed_category()
& NON_VIRT_MEM_FUN_CHANGE_CATEGORY)
&& !get_member_function_is_virtual(i->second))
continue;
method_decl_sptr mem_fun = i->second;
out << indent << " ";
represent(*ctxt, mem_fun, out);
}
// report insertions;
int numins = d.get_priv()->inserted_member_functions_.size();
num_filtered = d.get_priv()->count_filtered_inserted_mem_fns(ctxt);
if (numins)
report_mem_header(out, numins, num_filtered, ins_kind,
"member function", indent);
for (string_member_function_sptr_map::const_iterator i =
d.get_priv()->inserted_member_functions_.begin();
i != d.get_priv()->inserted_member_functions_.end();
++i)
{
if (!(ctxt->get_allowed_category()
& NON_VIRT_MEM_FUN_CHANGE_CATEGORY)
&& !get_member_function_is_virtual(i->second))
continue;
method_decl_sptr mem_fun = i->second;
out << indent << " ";
represent(*ctxt, mem_fun, out);
}
// report member function with sub-types changes
int numchanges = d.get_priv()->sorted_changed_member_functions_.size();
if (numchanges)
report_mem_header(out, change_kind, "member function", indent);
for (function_decl_diff_sptrs_type::const_iterator i =
d.get_priv()->sorted_changed_member_functions_.begin();
i != d.get_priv()->sorted_changed_member_functions_.end();
++i)
{
if (!(ctxt->get_allowed_category()
& NON_VIRT_MEM_FUN_CHANGE_CATEGORY)
&& !(get_member_function_is_virtual
((*i)->first_function_decl()))
&& !(get_member_function_is_virtual
((*i)->second_function_decl())))
continue;
diff_sptr diff = *i;
if (!diff_to_be_reported(diff.get()))
continue;
string repr =
(*i)->first_function_decl()->get_pretty_representation();
out << indent << " '" << repr << "' has some changes:\n";
diff->report(out, indent + " ");
}
}
// data members
if (d.data_members_changes())
{
// report deletions
int numdels = d.class_or_union_diff::get_priv()->
get_deleted_non_static_data_members_number();
if (numdels)
{
report_mem_header(out, numdels, 0, del_kind,
"data member", indent);
vector<decl_base_sptr> sorted_dms;
sort_data_members
(d.class_or_union_diff::get_priv()->deleted_data_members_,
sorted_dms);
for (vector<decl_base_sptr>::const_iterator i = sorted_dms.begin();
i != sorted_dms.end();
++i)
{
var_decl_sptr data_mem =
dynamic_pointer_cast<var_decl>(*i);
ABG_ASSERT(data_mem);
if (get_member_is_static(data_mem))
continue;
represent_data_member(data_mem, ctxt, out, indent + " ");
}
}
//report insertions
int numins =
d.class_or_union_diff::get_priv()->inserted_data_members_.size();
if (numins)
{
report_mem_header(out, numins, 0, ins_kind,
"data member", indent);
vector<decl_base_sptr> sorted_dms;
sort_data_members
(d.class_or_union_diff::get_priv()->inserted_data_members_,
sorted_dms);
for (vector<decl_base_sptr>::const_iterator i = sorted_dms.begin();
i != sorted_dms.end();
++i)
{
var_decl_sptr data_mem =
dynamic_pointer_cast<var_decl>(*i);
ABG_ASSERT(data_mem);
represent_data_member(data_mem, ctxt, out, indent + " ");
}
}
// report changes
size_t numchanges = (d.sorted_changed_data_members().size()
+ d.sorted_subtype_changed_data_members().size());
size_t num_filtered =
(d.count_filtered_changed_data_members(/*local_only=*/true)
+ d.count_filtered_subtype_changed_data_members(/*local_only=*/true));
ABG_ASSERT(numchanges >= num_filtered);
size_t net_numchanges = numchanges - num_filtered;
if (net_numchanges)
{
report_mem_header(out, change_kind, "data member", indent);
for (var_diff_sptrs_type::const_iterator it =
d.sorted_changed_data_members().begin();
it != d.sorted_changed_data_members().end();
++it)
if (diff_to_be_reported((*it).get()))
represent(*it, ctxt, out, indent + " ", /*local_only=*/true);
for (var_diff_sptrs_type::const_iterator it =
d.sorted_subtype_changed_data_members().begin();
it != d.sorted_subtype_changed_data_members().end();
++it)
if (diff_to_be_reported((*it).get()))
represent(*it, ctxt, out, indent + " ", /*local_only=*/true);
}
// Report about data members replaced by an anonymous union data
// member.
maybe_report_data_members_replaced_by_anon_dm(d, out, indent);
}
}
/// Report the changes carried by a @ref class_diff node.
///
/// @param out the output stream to report to.
///
/// @param indent the white space string to use for indentation.
void
leaf_reporter::report(const class_diff& d,
ostream& out,
const string& indent) const
{
if (!diff_to_be_reported(&d))
return;
RETURN_IF_BEING_REPORTED_OR_WAS_REPORTED_EARLIER(d.first_subject(),
d.second_subject());
string name = d.first_subject()->get_pretty_representation();
// Now report the changes about the differents parts of the type.
class_decl_sptr first = d.first_class_decl(),
second = d.second_class_decl();
report_name_size_and_alignment_changes(first, second, d.context(),
out, indent);
const diff_context_sptr& ctxt = d.context();
maybe_report_diff_for_member(first, second, ctxt, out, indent);
d.class_or_union_diff::report(out, indent);
maybe_report_interfaces_impacted_by_diff(&d, out, indent);
d.reported_once(true);
}
/// Report the changes carried by a @ref union_diff node.
///
/// @param out the output stream to report to.
///
/// @param indent the white space string to use for indentation.
void
leaf_reporter::report(const union_diff& d,
ostream& out,
const string& indent) const
{
if (!diff_to_be_reported(&d))
return;
RETURN_IF_BEING_REPORTED_OR_WAS_REPORTED_EARLIER(d.first_subject(),
d.second_subject());
// Now report the changes about the differents parts of the type.
union_decl_sptr first = d.first_union_decl(), second = d.second_union_decl();
report_name_size_and_alignment_changes(first, second, d.context(),
out, indent);
maybe_report_diff_for_member(first, second,d. context(), out, indent);
d.class_or_union_diff::report(out, indent);
maybe_report_interfaces_impacted_by_diff(&d, out, indent);
}
/// Report the changes carried by a @ref distinct_diff node.
///
/// @param out the output stream to report to.
///
/// @param indent the white space string to use for indentation.
void
leaf_reporter::report(const distinct_diff& d,
ostream& out,
const string& indent) const
{
if (!diff_to_be_reported(&d))
return;
type_or_decl_base_sptr f = d.first(), s = d.second();
string f_repr = f ? f->get_pretty_representation() : "'void'";
string s_repr = s ? s->get_pretty_representation() : "'void'";
diff_sptr diff = d.compatible_child_diff();
string compatible = diff ? " to compatible type '": " to '";
out << indent << "entity changed from '" << f_repr << "'"
<< compatible << s_repr << "'";
report_loc_info(s, *d.context(), out);
out << "\n";
report_size_and_alignment_changes(f, s, d.context(), out, indent);
maybe_report_interfaces_impacted_by_diff(&d, out, indent);
}
/// Report the changes carried by a @ref function_decl_diff node.
///
/// @param out the output stream to report to.
///
/// @param indent the white space string to use for indentation.
void
leaf_reporter::report(const function_decl_diff& d,
ostream& out,
const string& indent) const
{
if (!diff_to_be_reported(&d))
return;
maybe_report_diff_for_member(d.first_function_decl(),
d.second_function_decl(),
d.context(), out, indent);
function_decl_sptr ff = d.first_function_decl();
function_decl_sptr sf = d.second_function_decl();
diff_context_sptr ctxt = d.context();
corpus_sptr fc = ctxt->get_corpus_diff()->first_corpus();
corpus_sptr sc = ctxt->get_corpus_diff()->second_corpus();
string qn1 = ff->get_qualified_name(), qn2 = sf->get_qualified_name(),
linkage_names1, linkage_names2;
elf_symbol_sptr s1 = ff->get_symbol(), s2 = sf->get_symbol();
if (s1)
linkage_names1 = s1->get_id_string();
if (s2)
linkage_names2 = s2->get_id_string();
// If the symbols for ff and sf have aliases, get all the names of
// the aliases;
if (fc && s1)
linkage_names1 =
s1->get_aliases_id_string(fc->get_fun_symbol_map());
if (sc && s2)
linkage_names2 =
s2->get_aliases_id_string(sc->get_fun_symbol_map());
/// If the set of linkage names of the function have changed, report
/// it.
if (linkage_names1 != linkage_names2)
{
if (linkage_names1.empty())
{
out << indent << ff->get_pretty_representation()
<< " didn't have any linkage name, and it now has: '"
<< linkage_names2 << "'\n";
}
else if (linkage_names2.empty())
{
out << indent << ff->get_pretty_representation()
<< " did have linkage names '" << linkage_names1
<< "'\n"
<< indent << "but it doesn't have any linkage name anymore\n";
}
else
out << indent << "linkage names of "
<< ff->get_pretty_representation()
<< "\n" << indent << "changed from '"
<< linkage_names1 << "' to '" << linkage_names2 << "'\n";
}
if (qn1 != qn2
&& diff_to_be_reported(d.type_diff().get()))
{
// So the function has sub-type changes that are to be
// reported. Let's see if the function name changed too; if it
// did, then we'd report that change right before reporting the
// sub-type changes.
string frep1 = d.first_function_decl()->get_pretty_representation(),
frep2 = d.second_function_decl()->get_pretty_representation();
out << indent << "'" << frep1 << " {" << linkage_names1<< "}"
<< "' now becomes '"
<< frep2 << " {" << linkage_names2 << "}" << "'\n";
}
maybe_report_diff_for_symbol(ff->get_symbol(),
sf->get_symbol(),
ctxt, out, indent);
// Now report about inline-ness changes
if (ff->is_declared_inline() != sf->is_declared_inline())
{
out << indent;
if (ff->is_declared_inline())
out << sf->get_pretty_representation()
<< " is not declared inline anymore\n";
else
out << sf->get_pretty_representation()
<< " is now declared inline\n";
}
// Report about vtable offset changes.
if (is_member_function(ff) && is_member_function(sf))
{
bool ff_is_virtual = get_member_function_is_virtual(ff),
sf_is_virtual = get_member_function_is_virtual(sf);
if (ff_is_virtual != sf_is_virtual)
{
out << indent;
if (ff_is_virtual)
out << ff->get_pretty_representation()
<< " is no more declared virtual\n";
else
out << ff->get_pretty_representation()
<< " is now declared virtual\n";
}
size_t ff_vtable_offset = get_member_function_vtable_offset(ff),
sf_vtable_offset = get_member_function_vtable_offset(sf);
if (ff_is_virtual && sf_is_virtual
&& (ff_vtable_offset != sf_vtable_offset))
{
out << indent
<< "the vtable offset of " << ff->get_pretty_representation()
<< " changed from " << ff_vtable_offset
<< " to " << sf_vtable_offset << "\n";
}
// the classes of the two member functions.
class_decl_sptr fc =
is_class_type(is_method_type(ff->get_type())->get_class_type());
class_decl_sptr sc =
is_class_type(is_method_type(sf->get_type())->get_class_type());
// Detect if the virtual member function changes above
// introduced a vtable change or not.
bool vtable_added = false, vtable_removed = false;
if (!fc->get_is_declaration_only() && !sc->get_is_declaration_only())
{
vtable_added = !fc->has_vtable() && sc->has_vtable();
vtable_removed = fc->has_vtable() && !sc->has_vtable();
}
bool vtable_changed = ((ff_is_virtual != sf_is_virtual)
|| (ff_vtable_offset != sf_vtable_offset));
bool incompatible_change = (ff_vtable_offset != sf_vtable_offset);
if (vtable_added)
out << indent
<< " note that a vtable was added to "
<< fc->get_pretty_representation()
<< "\n";
else if (vtable_removed)
out << indent
<< " note that the vtable was removed from "
<< fc->get_pretty_representation()
<< "\n";
else if (vtable_changed)
{
out << indent;
if (incompatible_change)
out << " note that this is an ABI incompatible "
"change to the vtable of ";
else
out << " note that this induces a change to the vtable of ";
out << fc->get_pretty_representation()
<< "\n";
}
}
// Report about function type differences.
if (diff_to_be_reported(d.type_diff().get()))
d.type_diff()->report(out, indent);
}
/// Report the changes carried by a @ref var_diff node.
///
/// @param out the output stream to report to.
///
/// @param indent the white space string to use for indentation.
void
leaf_reporter::report(const var_diff& d,
ostream& out,
const string& indent) const
{
if (!diff_to_be_reported(&d))
return;
decl_base_sptr first = d.first_var(), second = d.second_var();
string n = first->get_pretty_representation();
report_name_size_and_alignment_changes(first, second,
d.context(),
out, indent);
maybe_report_diff_for_symbol(d.first_var()->get_symbol(),
d.second_var()->get_symbol(),
d.context(), out, indent);
maybe_report_diff_for_member(first, second, d.context(), out, indent);
if (diff_sptr dif = d.type_diff())
{
if (diff_to_be_reported(dif.get()))
{
RETURN_IF_BEING_REPORTED_OR_WAS_REPORTED_EARLIER2(dif, "type");
out << indent << "type of variable changed:\n";
dif->report(out, indent + " ");
}
}
}
/// Report the changes carried by a @ref translation_unit_diff node.
///
/// @param out the output stream to report to.
///
/// @param indent the white space string to use for indentation.
void
leaf_reporter::report(const translation_unit_diff& d,
ostream& out,
const string& indent) const
{
if (!d.to_be_reported())
return;
static_cast<const scope_diff&>(d).report(out, indent);
}
/// Report the changes carried by a @ref corpus_diff node.
///
/// @param out the output stream to report to.
///
/// @param indent the white space string to use for indentation.
void
leaf_reporter::report(const corpus_diff& d,
ostream& out,
const string& indent) const
{
if (!d.has_changes())
return;
const corpus_diff::diff_stats &s =
const_cast<corpus_diff&>(d).
apply_filters_and_suppressions_before_reporting();
const diff_context_sptr& ctxt = d.context();
d.priv_->emit_diff_stats(s, out, indent);
if (ctxt->show_stats_only())
return;
out << "\n";
if (ctxt->show_soname_change()
&& !d.priv_->sonames_equal_)
out << indent << "SONAME changed from '"
<< d.first_corpus()->get_soname() << "' to '"
<< d.second_corpus()->get_soname() << "'\n\n";
if (ctxt->show_architecture_change()
&& !d.priv_->architectures_equal_)
out << indent << "architecture changed from '"
<< d.first_corpus()->get_architecture_name() << "' to '"
<< d.second_corpus()->get_architecture_name() << "'\n\n";
/// Report removed/added/changed functions.
if (ctxt->show_deleted_fns())
{
if (s.net_num_func_removed() == 1)
out << indent << "1 Removed function:\n\n";
else if (s.net_num_func_removed() > 1)
out << indent << s.net_num_func_removed() << " Removed functions:\n\n";
bool emitted = false;
vector<function_decl*>sorted_deleted_fns;
sort_string_function_ptr_map(d.priv_->deleted_fns_, sorted_deleted_fns);
for (vector<function_decl*>::const_iterator i =
sorted_deleted_fns.begin();
i != sorted_deleted_fns.end();
++i)
{
if (d.priv_->deleted_function_is_suppressed(*i))
continue;
out << indent
<< " ";
out << "[D] ";
out << "'" << (*i)->get_pretty_representation() << "'";
if (ctxt->show_linkage_names())
{
out << " {";
show_linkage_name_and_aliases(out, "", *(*i)->get_symbol(),
d.first_corpus()->get_fun_symbol_map());
out << "}";
}
out << "\n";
if (is_member_function(*i) && get_member_function_is_virtual(*i))
{
class_decl_sptr c =
is_class_type(is_method_type((*i)->get_type())->get_class_type());
out << indent
<< " "
<< "note that this removes an entry from the vtable of "
<< c->get_pretty_representation()
<< "\n";
}
emitted = true;
}
if (emitted)
out << "\n";
}
if (ctxt->show_added_fns())
{
if (s.net_num_func_added() == 1)
out << indent << "1 Added function:\n\n";
else if (s.net_num_func_added() > 1)
out << indent << s.net_num_func_added()
<< " Added functions:\n\n";
bool emitted = false;
vector<function_decl*> sorted_added_fns;
sort_string_function_ptr_map(d.priv_->added_fns_, sorted_added_fns);
for (vector<function_decl*>::const_iterator i = sorted_added_fns.begin();
i != sorted_added_fns.end();
++i)
{
if (d.priv_->added_function_is_suppressed(*i))
continue;
out
<< indent
<< " ";
out << "[A] ";
out << "'"
<< (*i)->get_pretty_representation()
<< "'";
if (ctxt->show_linkage_names())
{
out << " {";
show_linkage_name_and_aliases
(out, "", *(*i)->get_symbol(),
d.second_corpus()->get_fun_symbol_map());
out << "}";
}
out << "\n";
if (is_member_function(*i) && get_member_function_is_virtual(*i))
{
class_decl_sptr c =
is_class_type(is_method_type((*i)->get_type())->get_class_type());
out << indent
<< " "
<< "note that this adds a new entry to the vtable of "
<< c->get_pretty_representation()
<< "\n";
}
emitted = true;
}
if (emitted)
out << "\n";
}
if (ctxt->show_changed_fns())
{
// Show changed functions.
size_t num_changed = s.net_num_leaf_func_changes();
if (num_changed == 1)
out << indent << "1 function with some sub-type change:\n\n";
else if (num_changed > 1)
out << indent << num_changed
<< " functions with some sub-type change:\n\n";
vector<function_decl_diff_sptr> sorted_changed_fns;
sort_string_function_decl_diff_sptr_map(d.priv_->changed_fns_map_,
sorted_changed_fns);
for (vector<function_decl_diff_sptr>::const_iterator i =
sorted_changed_fns.begin();
i != sorted_changed_fns.end();
++i)
{
diff_sptr diff = *i;
if (!diff)
continue;
if (diff_to_be_reported(diff.get()))
{
function_decl_sptr fn = (*i)->first_function_decl();
out << indent << " [C] '"
<< fn->get_pretty_representation() << "'";
report_loc_info((*i)->second_function_decl(), *ctxt, out);
out << " has some sub-type changes:\n";
if ((fn->get_symbol()->has_aliases()
&& !(is_member_function(fn)
&& get_member_function_is_ctor(fn))
&& !(is_member_function(fn)
&& get_member_function_is_dtor(fn)))
|| (is_c_language(get_translation_unit(fn)->get_language())
&& fn->get_name() != fn->get_linkage_name()))
{
int number_of_aliases =
fn->get_symbol()->get_number_of_aliases();
if (number_of_aliases == 0)
{
out << indent << " "
<< "Please note that the exported symbol of "
"this function is "
<< fn->get_symbol()->get_id_string()
<< "\n";
}
else
{
out << indent << " "
<< "Please note that the symbol of this function is "
<< fn->get_symbol()->get_id_string()
<< "\n and it aliases symbol";
if (number_of_aliases > 1)
out << "s";
out << ": "
<< fn->get_symbol()->get_aliases_id_string(false)
<< "\n";
}
}
diff->report(out, indent + " ");
// Extra spacing.
out << "\n";
}
}
// Changed functions have extra spacing already. No new line here.
}
// Report removed/added/changed variables.
if (ctxt->show_deleted_vars())
{
if (s.net_num_vars_removed() == 1)
out << indent << "1 Removed variable:\n\n";
else if (s.net_num_vars_removed() > 1)
out << indent << s.net_num_vars_removed()
<< " Removed variables:\n\n";
string n;
bool emitted = false;
vector<var_decl*> sorted_deleted_vars;
sort_string_var_ptr_map(d.priv_->deleted_vars_, sorted_deleted_vars);
for (vector<var_decl*>::const_iterator i =
sorted_deleted_vars.begin();
i != sorted_deleted_vars.end();
++i)
{
if (d.priv_->deleted_variable_is_suppressed(*i))
continue;
n = (*i)->get_pretty_representation();
out << indent
<< " ";
out << "[D] ";
out << "'"
<< n
<< "'";
if (ctxt->show_linkage_names())
{
out << " {";
show_linkage_name_and_aliases(out, "", *(*i)->get_symbol(),
d.first_corpus()->get_var_symbol_map());
out << "}";
}
out << "\n";
emitted = true;
}
if (emitted)
out << "\n";
}
if (ctxt->show_added_vars())
{
if (s.net_num_vars_added() == 1)
out << indent << "1 Added variable:\n\n";
else if (s.net_num_vars_added() > 1)
out << indent << s.net_num_vars_added()
<< " Added variables:\n\n";
string n;
bool emitted = false;
vector<var_decl*> sorted_added_vars;
sort_string_var_ptr_map(d.priv_->added_vars_, sorted_added_vars);
for (vector<var_decl*>::const_iterator i =
sorted_added_vars.begin();
i != sorted_added_vars.end();
++i)
{
if (d.priv_->added_variable_is_suppressed(*i))
continue;
n = (*i)->get_pretty_representation();
out << indent
<< " ";
out << "[A] ";
out << "'" << n << "'";
if (ctxt->show_linkage_names())
{
out << " {";
show_linkage_name_and_aliases(out, "", *(*i)->get_symbol(),
d.second_corpus()->get_var_symbol_map());
out << "}";
}
out << "\n";
emitted = true;
}
if (emitted)
out << "\n";
}
if (ctxt->show_changed_vars())
{
size_t num_changed = s.net_num_leaf_var_changes();
if (num_changed == 1)
out << indent << "1 Changed variable:\n\n";
else if (num_changed > 1)
out << indent << num_changed
<< " Changed variables:\n\n";
string n1, n2;
for (var_diff_sptrs_type::const_iterator i =
d.priv_->sorted_changed_vars_.begin();
i != d.priv_->sorted_changed_vars_.end();
++i)
{
diff_sptr diff = *i;
if (!diff)
continue;
if (!diff_to_be_reported(diff.get()))
continue;
n1 = diff->first_subject()->get_pretty_representation();
n2 = diff->second_subject()->get_pretty_representation();
out << indent << " [C] '" << n1 << "' was changed";
if (n1 != n2)
out << " to '" << n2 << "'";
report_loc_info(diff->second_subject(), *ctxt, out);
out << ":\n";
diff->report(out, indent + " ");
// Extra spacing.
out << "\n";
}
// Changed variables have extra spacing already. No new line here.
}
// Report removed function symbols not referenced by any debug info.
if (ctxt->show_symbols_unreferenced_by_debug_info()
&& d.priv_->deleted_unrefed_fn_syms_.size())
{
if (s.net_num_removed_func_syms() == 1)
out << indent
<< "1 Removed function symbol not referenced by debug info:\n\n";
else if (s.net_num_removed_func_syms() > 0)
out << indent
<< s.net_num_removed_func_syms()
<< " Removed function symbols not referenced by debug info:\n\n";
bool emitted = false;
vector<elf_symbol_sptr> sorted_deleted_unrefed_fn_syms;
sort_string_elf_symbol_map(d.priv_->deleted_unrefed_fn_syms_,
sorted_deleted_unrefed_fn_syms);
for (vector<elf_symbol_sptr>::const_iterator i =
sorted_deleted_unrefed_fn_syms.begin();
i != sorted_deleted_unrefed_fn_syms.end();
++i)
{
if (d.priv_->deleted_unrefed_fn_sym_is_suppressed((*i).get()))
continue;
out << indent << " ";
out << "[D] ";
show_linkage_name_and_aliases(out, "", **i,
d.first_corpus()->get_fun_symbol_map());
out << "\n";
emitted = true;
}
if (emitted)
out << "\n";
}
// Report added function symbols not referenced by any debug info.
if (ctxt->show_symbols_unreferenced_by_debug_info()
&& ctxt->show_added_symbols_unreferenced_by_debug_info()
&& d.priv_->added_unrefed_fn_syms_.size())
{
if (s.net_num_added_func_syms() == 1)
out << indent
<< "1 Added function symbol not referenced by debug info:\n\n";
else if (s.net_num_added_func_syms() > 0)
out << indent
<< s.net_num_added_func_syms()
<< " Added function symbols not referenced by debug info:\n\n";
bool emitted = false;
vector<elf_symbol_sptr> sorted_added_unrefed_fn_syms;
sort_string_elf_symbol_map(d.priv_->added_unrefed_fn_syms_,
sorted_added_unrefed_fn_syms);
for (vector<elf_symbol_sptr>::const_iterator i =
sorted_added_unrefed_fn_syms.begin();
i != sorted_added_unrefed_fn_syms.end();
++i)
{
if (d.priv_->added_unrefed_fn_sym_is_suppressed((*i).get()))
continue;
out << indent << " ";
out << "[A] ";
show_linkage_name_and_aliases(out, "",
**i,
d.second_corpus()->get_fun_symbol_map());
out << "\n";
emitted = true;
}
if (emitted)
out << "\n";
}
// Report removed variable symbols not referenced by any debug info.
if (ctxt->show_symbols_unreferenced_by_debug_info()
&& d.priv_->deleted_unrefed_var_syms_.size())
{
if (s.net_num_removed_var_syms() == 1)
out << indent
<< "1 Removed variable symbol not referenced by debug info:\n\n";
else if (s.net_num_removed_var_syms() > 0)
out << indent
<< s.net_num_removed_var_syms()
<< " Removed variable symbols not referenced by debug info:\n\n";
bool emitted = false;
vector<elf_symbol_sptr> sorted_deleted_unrefed_var_syms;
sort_string_elf_symbol_map(d.priv_->deleted_unrefed_var_syms_,
sorted_deleted_unrefed_var_syms);
for (vector<elf_symbol_sptr>::const_iterator i =
sorted_deleted_unrefed_var_syms.begin();
i != sorted_deleted_unrefed_var_syms.end();
++i)
{
if (d.priv_->deleted_unrefed_var_sym_is_suppressed((*i).get()))
continue;
out << indent << " ";
out << "[D] ";
show_linkage_name_and_aliases
(out, "", **i,
d.first_corpus()->get_fun_symbol_map());
out << "\n";
emitted = true;
}
if (emitted)
out << "\n";
}
// Report added variable symbols not referenced by any debug info.
if (ctxt->show_symbols_unreferenced_by_debug_info()
&& ctxt->show_added_symbols_unreferenced_by_debug_info()
&& d.priv_->added_unrefed_var_syms_.size())
{
if (s.net_num_added_var_syms() == 1)
out << indent
<< "1 Added variable symbol not referenced by debug info:\n\n";
else if (s.net_num_added_var_syms() > 0)
out << indent
<< s.net_num_added_var_syms()
<< " Added variable symbols not referenced by debug info:\n\n";
bool emitted = false;
vector<elf_symbol_sptr> sorted_added_unrefed_var_syms;
sort_string_elf_symbol_map(d.priv_->added_unrefed_var_syms_,
sorted_added_unrefed_var_syms);
for (vector<elf_symbol_sptr>::const_iterator i =
sorted_added_unrefed_var_syms.begin();
i != sorted_added_unrefed_var_syms.end();
++i)
{
if (d.priv_->added_unrefed_var_sym_is_suppressed((*i).get()))
continue;
out << indent << " ";
out << "[A] ";
show_linkage_name_and_aliases(out, "", **i,
d.second_corpus()->get_fun_symbol_map());
out << "\n";
emitted = true;
}
if (emitted)
out << "\n";
}
// Now show the changed types.
const diff_maps& leaf_diffs = d.get_leaf_diffs();
report_type_changes_from_diff_maps(*this, leaf_diffs, out, indent);
// Report added/removed/changed types not reacheable from public
// interfaces.
maybe_report_unreachable_type_changes(d, s, indent, out);
d.priv_->maybe_dump_diff_tree();
}
} // end namespace comparison
} // end namespace abigail