libabigail/tools/abidiff.cc
Dodji Seketeli c32b8ec9f3 Bug 24690 - Support comparing non-reachable types of a binary
This patch adds the ability to compare all types of a binary,
including those types that are not reachable from global functions and
variables.

This implies that for types that are not reachable from public
interfaces, we want compare them against each others directly, without
first comparing global functions/variables and walking the graph of
reachable types from there.

The patch adds the --non-reachable-types option to abidiff and
abipkgdiff, instructing them to also compare types that are
non-reachable from global variables and functions.

Using that option, for instance, here is what the summary of
abipkgdiff now looks like, in the test case attached added by this
patch:

================ changes of 'libflatpak.so.0.10204.0'===============
  Functions changes summary: 0 Removed, 0 Changed (16 filtered out), 16 Added functions
  Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
  Unreachable types summary: 3 removed (2 filtered out), 1 changed (15 filtered out), 3 added (1 filtered out) types

You can see that there is a new summary line which starts with the
string: "Unreachable types summary:"

Then in the body of the report, those unreachable types are reported
separately.

In practise, we want to limit the unreachable types to compare
somehow, otherwise we'll end up comparing all the types of the types
of the binary and that can be huge.  So we want to limit the
unreachable type analysis to types that are defined in public headers.

So, for abipkgdiff, one can limit the analysis of non-reachable types
to those defined in public headers by supplying the --devel{1,2}
options that specifies the development packages that contain said
public headers.  For abidiff however, you'll want to use the
--headers-dir{1,2} options for that.

The patch comes with appropriate regression tests.

	* include/abg-comparison.h (string_type_base_sptr_map): Define new
	typedef.
	(diff_context::show_unreachable_types): Declare new member
	functions.
	(corpus_diff::{deleted_unreachable_types,
	deleted_unreachable_types_sorted, added_unreachable_types,
	added_unreachable_types_sorted, changed_unreachable_types,
	changed_unreachable_types_sorted}): Likewise.
	(maybe_report_unreachable_type_changes): Declare this function a
	friend of class corpus_diff.
	(corpus_diff::diff_stats::{num_added_unreachable_types,
	num_added_unreachable_types_filtered_out,
	net_num_added_unreachable_types, num_removed_unreachable_types,
	num_removed_unreachable_types_filtered_out,
	net_num_removed_unreachable_types, num_changed_unreachable_types,
	num_changed_unreachable_types_filtered_out,
	net_num_changed_unreachable_types}): Likewise.
	* src/abg-comparison-priv.h
	(diff_context::priv::show_unreachable_types_): Define new data
	member.
	(diff_context::priv::priv): Initialize the new data member.
	(diff_comp::operator()): Use pretty representation of diff
	subjects to sort them, rather than just their name.  Also, add
	comment to the other member functions of diff_comp.
	(corpus_diff::{unreachable_types_edit_script_,
	deleted_unreachable_types_, deleted_unreachable_types_sorted_,
	suppressed_deleted_unreachable_types_, added_unreachable_types_,
	added_unreachable_types_sorted_,
	suppressed_added_unreachable_types_, changed_unreachable_types_,
	changed_unreachable_types_sorted_}): Define new data members.
	(corpus_diff::priv::apply_supprs_to_added_removed_fns_vars_unreachable_types):
	Changed the name of
	corpus_diff::priv::apply_suppressions_to_added_removed_fns_vars into
	this.
	(corpus_diff::priv::{added_unreachable_type_is_suppressed,
	deleted_unreachable_type_is_suppressed,
	changed_unreachable_types_sorted, count_unreachable_types}):
	Declare new member functions.
	(corpus_diff::diff_stats::priv::{num_added_unreachable_types,
	num_added_unreachable_types_filtered_out,
	num_removed_unreachable_types,
	num_removed_unreachable_types_filtered_out,
	num_changed_unreachable_types,
	num_changed_unreachable_types_filtered_out}): Define new data
	members.
	(sort_string_type_base_sptr_map): Declare new function.
	* src/abg-comparison.cc (sort_string_type_base_sptr_map)
	(diff_context::show_unreachable_types): Define new functions.
	(corpus_diff::diff_stats::{num_added_unreachable_types,
	num_added_unreachable_types_filtered_out,
	net_num_added_unreachable_types,
	net_num_removed_unreachable_types,
	num_removed_unreachable_types_filtered_out,
	num_removed_unreachable_types}): Define new member functions.
	(diff_maps::insert_diff_node): Do not update the map "diff ->
	impacted interfaces" if the current impacted interface is nil.
	This happens if we are looking at a diff node for a change on a
	type that is not reachable from any interfaces.
	(corpus_diff::priv::ensure_lookup_tables_populated): Handle the
	edit script for unreachable types.
	(corpus_diff::priv::apply_supprs_to_added_removed_fns_vars_unreachable_types):
	Rename
	corpus_diff::priv::apply_suppressions_to_added_removed_fns_vars
	into this.  Apply suppression specifications to added and removed
	unreachable types as well.
	(corpus_diff::priv::{added,deleted}_unreachable_type_is_suppressed):
	Define new member functions.
	(corpus_diff::priv::{count_unreachable_types,
	changed_unreachable_types_sorted}): Likewise.
	(corpus_diff::priv::apply_filters_and_compute_diff_stats): Update
	statistics (including walking changed unreachable types to apply
	categorization and redundancy filters to them) related to
	unreachable types.
	(corpus_diff::priv::emit_diff_stats): Emit diff stats related to
	unreachable types.
	(corpus_diff::priv::maybe_dump_diff_tree): Dump diff tree nodes
	related to unreachable types.
	(corpus_diff::{deleted_unreachable_types,
	deleted_unreachable_types_sorted, added_unreachable_types,
	added_unreachable_types_sorted, changed_unreachable_types,
	changed_unreachable_types_sorted): Define new member functions.
	(corpus_diff::has_changes): Take deleted/added/changed unreachable
	types into account.
	(corpus_diff::has_incompatible_changes): Take net removed/changed
	unreachable types into account.
	(corpus_diff::has_net_subtype_changes): Take net removed and
	changed unreachable types into account.
	(corpus_diff::has_net_changes): Take net removed/added/changed
	unreachable types into account.
	(corpus_diff::traverse): When traversing the components of a
	corpus_diff node, make sure to traverse the changed unreachable
	types of the corpus.
	(leaf_diff_node_marker_visitor::visit_begin): Arrange for the fact
	that the current topmost interface can be nil if we are looking at
	types not reachable from global functions/variables.  Also, make
	sure that only leaf nodes that are reachable from a global
	function/variable are recorded as leaf nodes.
	(compute_diff): In the overload for corpus_sptr, compute the
	changes between types not reachable from global functions and
	variables, if the user wishes that we do so.  Also, add more
	comments.
	(apply_suppressions): Update for the name change of the function
	apply_suppressions_to_added_removed_fns_vars to
	apply_supprs_to_added_removed_fns_vars_unreachable_types.
	* include/abg-corpus.h
	(corpus::{record_type_as_reachable_from_public_interfaces,
	type_is_reachable_from_public_interfaces,
	get_types_not_reachable_from_public_interfaces}): Declare new
	member functions.
	(corpus::recording_types_reachable_from_public_interface_supported):
	Declare new virtual member function.
	(corpus_group::get_public_types_pretty_representations): Declare
	new member functons.
	(corpus_group::recording_types_reachable_from_public_interface_supported):
	Declare new virtual member function.
	* src/abg-corpus-priv.h
	(corpus::priv::{types_not_reachable_from_pub_ifaces_,
	pub_type_pretty_reprs_}): Define new data members.
	(corpus::priv::priv): Initialize the pub_type_pretty_reprs_ data
	member because it's a pointer.
	(corpus::priv::get_public_types_pretty_representations): Declare
	new member function.
	(corpus::priv::~priv): Declare a destructor.
	* src/abg-corpus.cc
	(corpus::priv::get_public_types_pretty_representations): Define
	new member function.
	(corpus::priv::~priv): Define new destructor to delete the new
	pub_type_pretty_reprs_ member pointer.
	(corpus::{record_type_as_reachable_from_public_interfaces,
	type_is_reachable_from_public_interfaces,
	get_types_not_reachable_from_public_interfaces,
	recording_types_reachable_from_public_interface_supported}):
	Define new member functions
	(corpus_group::get_public_types_pretty_representations): Likewise.
	* include/abg-diff-utils.h (struct deep_ptr_eq_functor): Document
	the equality operator.  Also, add an overload to the equality
	operator, for weak_ptr<T>.  The existing equality operator
	overload was just for shared_ptr<T>.
	* include/abg-fwd.h (is_user_defined_type): Declare function.
	* include/abg-ir.h (operator!=(const decl_base_sptr&, const
	decl_base_sptr&)): Declare new operator.
	(type_maps::get_types_sorted_by_name): Declare
	new member function.
	(decl_base::{g,s}et_is_artificial): Declare new member function.
	(function_decl::parameter::{g,s}et_artificial): Remove these
	member functions.
	* src/abg-ir.cc (operator!=(const decl_base_sptr&, const
	decl_base_sptr&)): Define new operator.
	(decl_base::priv::is_artificial_): Define new data
	member.
	(type_maps::priv::sorted_types_): Define new data member.
	(struct type_name_comp): Define new comparison functor to sort
	types based on their pretty representations.
	(decl_base::priv::priv): Initialize it.
	(decl_base::{g,s}et_is_artificial): Define new member functions.
	(type_maps::get_types_sorted_by_name): Define new member function.
	(is_user_defined_type): Define new function overloads.
	(strip_typedef, function_type::{function_type, set_parameters}):
	Adjust using decl_base::get_is_artificial rather than
	function_decl::parameter::get_artificial.
	(function_decl::parameter::priv::artificial_): Remove this data
	member.
	(function_decl::parameter::priv::priv): Adjust to the removal of
	function_decl::parameter::priv::artificial_.  This constructor
	does not take an "is_artificial" flag anymore.
	(function_decl::parameter::parameter): Adjust to the removal of
	the is_artificial flag from the arguments of the constructor of
	function_decl::parameter::parameter::priv.
	(function_decl::parameter::get_artificial): Remove this member
	function.
	* src/abg-reporter-priv.h (maybe_report_unreachable_type_changes):
	Declare new function.
	* src/abg-reporter-priv.cc
	(maybe_report_unreachable_type_changes): Define new function.
	* src/abg-default-reporter.cc (default_reporter::report): In the
	overload for corpus_diff&, report added/removed/changed types that
	are not reachable from global functions and variables using the
	new function maybe_report_unreachable_type_changes.
	* src/abg-leaf-reporter.cc (leaf_reporter::report): In the
	overload for corpus_diff, report changes to types unreachable from
	global functions or variables, using the new function
	maybe_report_unreachable_type_changes.
	* src/abg-dwarf-reader.cc (build_ir_node_from_die): When the user
	requests that all types be loaded, record relevant types as
	reachable from global functions and variables.
	(build_enum_type, add_or_update_class_type)
	(add_or_update_union_type): Read the 'is-artificial' DWARF
	attribute and set the corresponding decl_base property
	accordingly.
	(finish_member_function_reading, strip_typedef)
	(function_type::function_type): Adjust using
	decl_base::get_is_artificial, rather than
	function_decl::parameter::get_artificial.
	* include/abg-reader.h
	(consider_types_not_reachable_from_public_interfaces): Declare new
	function.
	* src/abg-reader.cc
	(read_context::m_tracking_non_reachable_types): Add new data
	member.
	(read_context::read_context): Initialize it.
	(read_context::tracking_non_reachable_types): Define accessors for
	the new data member above.
	(read_is_declaration_only): Re-indent.
	(read_is_artificial): Define new helper function.
	(build_function_parameter): Use the new read_is_artificial
	function here, rather than open-coding it.
	(build_enum_type_decl, build_class_decl, build_union_decl):
	Support reading the 'is-artificial' property by using the new
	read_is_artificial function.
	(read_corpus_from_input): If the user wants us to take
	non-reachable types into account, then make sure we do so.
	(read_tracking_non_reachable_types, read_is_non_reachable_type):
	Define new static functions.
	(handle_element_node, build_type): Read the "is-non-reachable"
	attribute on type element nodes if the user wants us to track
	non-reachable types.
	(consider_types_not_reachable_from_public_interfaces): Define new
	function.
	* src/abg-writer.cc (write_is_artificial): Define new static
	helper function.
	(annotate): Adjust using decl_base::get_is_artificial rather than
	function_decl::parameter::get_artificial.
	(write_enum_type_decl, write_class_decl_opening_tag)
	(write_union_decl_opening_tag): Support writing the
	"is-artificial" property, using the new write_is_artificial
	function.
	(write_function_type): Adjust this to use the new
	write_is_artificial rather than open-coding writing the
	'is-artificial' attribute.
	(write_is_non_reachable)
	(write_tracking_non_reachable_types): Define new static functions.
	(write_enum_type_decl, write_class_decl_opening_tag)
	(write_union_decl_opening_tag): Write the 'is-no-reachable'
	attribute when applicable.
	(write_corpus, write_corpus_group): Write the
	'tracking-non-reachable-types' attribute when applicable.
	* tools/abidiff.cc (options::options): Initialize ...
	(options::show_all_types): ... new data member.
	(display_usage): Add help string from the new
	--non-reachable-types option.
	(parse_command_line): Parse the new --non-reachable-types option.
	(set_diff_context_from_opts): Set the
	dwarf_reader::read_context::show_unreachable_types property.
	(set_native_xml_reader_options): Define new
	static function.
	(main): Load all types when analyzing the DWARF or the ABIXML
	files, if the user wants us to do so.
	* tools/abipkgdiff.cc (options::show_all_types): Define new data
	member.
	(options::options): Initialize it.
	(parse_command_line): Parse the --non-reachable-types option to
	set the options::show_all_types data member.
	(display_usage): Add a help string for the new
	--non-reachable-types option.
	(set_diff_context_from_opts): Set the
	dwarf_reader::read_context::show_unreachable_types property based
	on the options::show_all_type data member.
	(compare): Configure the read context to load all types while
	analyzing the DWARF info, depending on the options::show_all_type
	data member.
	* doc/manuals/abidiff.rst: Document the new --non-reachable-types
	option added to abidiff above.
	* doc/manuals/abipkgdiff.rst: Add documentation for the
	--non-reachable-types option.
	* tests/data/test-diff-suppr/test47-non-reachable-types-v{0,1}.c:
	Source code files of test binary input.
	* tests/data/test-diff-suppr/test47-non-reachable-types-suppr-{1,2,3,4,5}.txt:
	New test input files.
	* tests/data/test-diff-suppr/test47-non-reachable-types-report-{1,2,3,4,5,6,7,8,9,10}.txt:
	New test reference output files.
	* tests/data/test-diff-suppr/test47-non-reachable-types-v{0,1}.o.alltypes.abixml:
	New test input abixml.
	* tests/data/Makefile.am: Add the new test material to source
	distribution.
	* tests/test-diff-suppr.cc (in_out_specs): Add the new tests above
	to this test harness.
	* tests/data/test-abidiff/test-struct1-report.txt: Adjust.
	* tests/data/test-diff-pkg/PR24690/flatpak-debuginfo-1.2.4-3.fc30.x86_64.rpm:
	New input binary RPM.
	* tests/data/test-diff-pkg/PR24690/flatpak-debuginfo-1.4.0-1.fc30.x86_64.rpm:
	Likewise.
	* tests/data/test-diff-pkg/PR24690/flatpak-devel-1.2.4-3.fc30.x86_64.rpm:
	Likewise.
	* tests/data/test-diff-pkg/PR24690/flatpak-devel-1.4.0-1.fc30.x86_64.rpm:
	Likewise.
	* tests/data/test-diff-pkg/PR24690/flatpak-libs-1.2.4-3.fc30.x86_64.rpm:
	Likewise.
	* tests/data/test-diff-pkg/PR24690/flatpak-libs-1.4.0-1.fc30.x86_64.rpm:
	Likewise.
	* tests/data/test-diff-pkg/PR24690/flatpak-libs-debuginfo-1.2.4-3.fc30.x86_64.rpm:
	Likewise.
	* tests/data/test-diff-pkg/PR24690/flatpak-libs-debuginfo-1.4.0-1.fc30.x86_64.rpm:
	Likewise.
	* tests/data/test-diff-pkg/PR24690/PR24690-report-0.txt: New test
	reference output.
	* tests/data/Makefile.am: Add the new test material above to
	source distribution.
	* tests/test-diff-pkg.cc (in_out_specs): Add the new test material
	above to this test harness.

Signed-off-by: Dodji Seketeli <dodji@redhat.com>
2020-01-06 14:26:00 +01:00

1359 lines
41 KiB
C++

// -*- Mode: C++ -*-
//
// Copyright (C) 2013-2019 Red Hat, Inc.
//
// This file is part of the GNU Application Binary Interface Generic
// Analysis and Instrumentation Library (libabigail). This library is
// free software; you can redistribute it and/or modify it under the
// terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 3, or (at your option) any
// later version.
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Lesser Public License for more details.
// You should have received a copy of the GNU Lesser General Public
// License along with this program; see the file COPYING-LGPLV3. If
// not, see <http://www.gnu.org/licenses/>.
//
// Author: Dodji Seketeli
/// @file
#include <cstring>
#include <vector>
#include <string>
#include <iostream>
#include "abg-config.h"
#include "abg-comp-filter.h"
#include "abg-suppression.h"
#include "abg-tools-utils.h"
#include "abg-reader.h"
#include "abg-dwarf-reader.h"
using std::vector;
using std::string;
using std::ostream;
using std::cout;
using std::cerr;
using abg_compat::shared_ptr;
using abigail::ir::environment;
using abigail::ir::environment_sptr;
using abigail::translation_unit;
using abigail::translation_unit_sptr;
using abigail::corpus_sptr;
using abigail::corpus_group_sptr;
using abigail::comparison::translation_unit_diff_sptr;
using abigail::comparison::corpus_diff;
using abigail::comparison::corpus_diff_sptr;
using abigail::comparison::compute_diff;
using abigail::comparison::get_default_harmless_categories_bitmap;
using abigail::comparison::get_default_harmful_categories_bitmap;
using abigail::suppr::suppression_sptr;
using abigail::suppr::suppressions_type;
using abigail::suppr::read_suppressions;
using namespace abigail::dwarf_reader;
using abigail::tools_utils::emit_prefix;
using abigail::tools_utils::check_file;
using abigail::tools_utils::guess_file_type;
using abigail::tools_utils::gen_suppr_spec_from_headers;
using abigail::tools_utils::gen_suppr_spec_from_kernel_abi_whitelist;
using abigail::tools_utils::load_default_system_suppressions;
using abigail::tools_utils::load_default_user_suppressions;
using abigail::tools_utils::abidiff_status;
struct options
{
bool display_usage;
bool display_version;
bool missing_operand;
string wrong_option;
string file1;
string file2;
vector<string> suppression_paths;
vector<string> kernel_abi_whitelist_paths;
vector<string> drop_fn_regex_patterns;
vector<string> drop_var_regex_patterns;
vector<string> keep_fn_regex_patterns;
vector<string> keep_var_regex_patterns;
string headers_dir1;
string headers_dir2;
bool drop_private_types;
bool linux_kernel_mode;
bool no_default_supprs;
bool no_arch;
bool no_corpus;
bool leaf_changes_only;
bool fail_no_debug_info;
bool show_hexadecimal_values;
bool show_offsets_sizes_in_bits;
bool show_relative_offset_changes;
bool show_stats_only;
bool show_symtabs;
bool show_deleted_fns;
bool show_changed_fns;
bool show_added_fns;
bool show_added_syms;
bool show_all_fns;
bool show_deleted_vars;
bool show_changed_vars;
bool show_added_vars;
bool show_all_vars;
bool show_all_types;
bool show_linkage_names;
bool show_locs;
bool show_harmful_changes;
bool show_harmless_changes;
bool show_redundant_changes;
bool show_symbols_not_referenced_by_debug_info;
bool show_impacted_interfaces;
bool dump_diff_tree;
bool show_stats;
bool do_log;
vector<char*> di_root_paths1;
vector<char*> di_root_paths2;
vector<char**> prepared_di_root_paths1;
vector<char**> prepared_di_root_paths2;
options()
: display_usage(),
display_version(),
missing_operand(),
drop_private_types(false),
linux_kernel_mode(true),
no_default_supprs(),
no_arch(),
no_corpus(),
leaf_changes_only(),
fail_no_debug_info(),
show_hexadecimal_values(),
show_offsets_sizes_in_bits(true),
show_relative_offset_changes(true),
show_stats_only(),
show_symtabs(),
show_deleted_fns(),
show_changed_fns(),
show_added_fns(),
show_added_syms(true),
show_all_fns(true),
show_deleted_vars(),
show_changed_vars(),
show_added_vars(),
show_all_vars(true),
show_all_types(false),
show_linkage_names(true),
show_locs(true),
show_harmful_changes(true),
show_harmless_changes(),
show_redundant_changes(),
show_symbols_not_referenced_by_debug_info(true),
show_impacted_interfaces(),
dump_diff_tree(),
show_stats(),
do_log()
{}
~options()
{
for (vector<char*>::iterator i = di_root_paths1.begin();
i != di_root_paths1.end();
++i)
free(*i);
for (vector<char*>::iterator i = di_root_paths2.begin();
i != di_root_paths2.end();
++i)
free(*i);
prepared_di_root_paths1.clear();
prepared_di_root_paths2.clear();
}
};//end struct options;
static void
display_usage(const string& prog_name, ostream& out)
{
emit_prefix(prog_name, out)
<< "usage: " << prog_name << " [options] [<file1> <file2>]\n"
<< " where options can be:\n"
<< " --help|-h display this message\n "
<< " --version|-v display program version information and exit\n"
<< " --debug-info-dir1|--d1 <path> the root for the debug info of file1\n"
<< " --debug-info-dir2|--d2 <path> the root for the debug info of file2\n"
<< " --headers-dir1|--hd1 <path> the path to headers of file1\n"
<< " --headers-dir2|--hd2 <path> the path to headers of file2\n"
<< " --drop-private-types drop private types from "
"internal representation\n"
<< " --no-linux-kernel-mode don't consider the input binaries as "
"linux kernel binaries\n"
<< " --kmi-whitelist|-w path to a "
"linux kernel abi whitelist\n"
<< " --stat only display the diff stats\n"
<< " --symtabs only display the symbol tables of the corpora\n"
<< " --no-default-suppression don't load any "
"default suppression specification\n"
<< " --no-architecture do not take architecture in account\n"
<< " --no-corpus-path do not take the path to the corpora into account\n"
<< " --fail-no-debug-info bail out if no debug info was found\n"
<< " --leaf-changes-only|-l only show leaf changes, "
"so no change impact analysis\n"
<< " --deleted-fns display deleted public functions\n"
<< " --changed-fns display changed public functions\n"
<< " --added-fns display added public functions\n"
<< " --deleted-vars display deleted global public variables\n"
<< " --changed-vars display changed global public variables\n"
<< " --added-vars display added global public variables\n"
<< " --non-reachable-types|-t consider types non reachable"
" from public interfaces\n"
<< " --no-added-syms do not display added functions or variables\n"
<< " --no-linkage-name do not display linkage names of "
"added/removed/changed\n"
<< " --no-unreferenced-symbols do not display changes "
"about symbols not referenced by debug info\n"
<< " --no-show-locs do now show location information\n"
<< " --show-bytes show size and offsets in bytes\n"
<< " --show-bits show size and offsets in bits\n"
<< " --show-hex show size and offset in hexadecimal\n"
<< " --show-dec show size and offset in decimal\n"
<< " --no-show-relative-offset-changes do not show relative"
" offset changes\n"
<< " --suppressions|--suppr <path> specify a suppression file\n"
<< " --drop <regex> drop functions and variables matching a regexp\n"
<< " --drop-fn <regex> drop functions matching a regexp\n"
<< " --drop-var <regex> drop variables matching a regexp\n"
<< " --keep <regex> keep only functions and variables matching a regex\n"
<< " --keep-fn <regex> keep only functions matching a regex\n"
<< " --keep-var <regex> keep only variables matching a regex\n"
<< " --harmless display the harmless changes\n"
<< " --no-harmful do not display the harmful changes\n"
<< " --redundant display redundant changes\n"
<< " --no-redundant do not display redundant changes "
"(this is the default)\n"
<< " --impacted-interfaces do not display interfaces impacted"
" by leaf changes\n"
<< " --dump-diff-tree emit a debug dump of the internal diff tree to "
"the error output stream\n"
<< " --stats show statistics about various internal stuff\n"
<< " --verbose show verbose messages about internal stuff\n";
}
/// Parse the command line and set the options accordingly.
///
/// @param argc the number of words on the command line
///
/// @param argv the command line, which is an array of words.
///
/// @param opts the options data structure. This is set by the
/// function iff it returns true.
///
/// @return true if the command line could be parsed and opts filed,
/// false otherwise.
bool
parse_command_line(int argc, char* argv[], options& opts)
{
if (argc < 2)
return false;
for (int i = 1; i < argc; ++i)
{
if (argv[i][0] != '-')
{
if (opts.file1.empty())
opts.file1 = argv[i];
else if (opts.file2.empty())
opts.file2 = argv[i];
else
return false;
}
else if (!strcmp(argv[i], "--version")
|| !strcmp(argv[i], "-v"))
{
opts.display_version = true;
return true;
}
else if (!strcmp(argv[i], "--debug-info-dir1")
|| !strcmp(argv[i], "--d1"))
{
int j = i + 1;
if (j >= argc)
{
opts.missing_operand = true;
opts.wrong_option = argv[i];
return true;
}
// elfutils wants the root path to the debug info to be
// absolute.
opts.di_root_paths1.push_back
(abigail::tools_utils::make_path_absolute_to_be_freed(argv[j]));
++i;
}
else if (!strcmp(argv[i], "--debug-info-dir2")
|| !strcmp(argv[i], "--d2"))
{
int j = i + 1;
if (j >= argc)
{
opts.missing_operand = true;
opts.wrong_option = argv[i];
return true;
}
// elfutils wants the root path to the debug info to be
// absolute.
opts.di_root_paths2.push_back
(abigail::tools_utils::make_path_absolute_to_be_freed(argv[j]));
++i;
}
else if (!strcmp(argv[i], "--headers-dir1")
|| !strcmp(argv[i], "--hd1"))
{
int j = i + 1;
if (j >= argc)
{
opts.missing_operand = true;
opts.wrong_option = argv[i];
return true;
}
opts.headers_dir1 = argv[j];
++i;
}
else if (!strcmp(argv[i], "--headers-dir2")
|| !strcmp(argv[i], "--hd2"))
{
int j = i + 1;
if (j >= argc)
{
opts.missing_operand = true;
opts.wrong_option = argv[i];
return true;
}
opts.headers_dir2 = argv[j];
++i;
}
else if (!strcmp(argv[i], "--kmi-whitelist")
|| !strcmp(argv[i], "-w"))
{
int j = i + 1;
if (j >= argc)
{
opts.missing_operand = true;
opts.wrong_option = argv[i];
return true;
}
opts.kernel_abi_whitelist_paths.push_back(argv[j]);
++i;
}
else if (!strcmp(argv[i], "--stat"))
opts.show_stats_only = true;
else if (!strcmp(argv[i], "--symtabs"))
opts.show_symtabs = true;
else if (!strcmp(argv[i], "--help")
|| !strcmp(argv[i], "-h"))
{
opts.display_usage = true;
return true;
}
else if (!strcmp(argv[i], "--drop-private-types"))
opts.drop_private_types = true;
else if (!strcmp(argv[i], "--no-default-suppression"))
opts.no_default_supprs = true;
else if (!strcmp(argv[i], "--no-architecture"))
opts.no_arch = true;
else if (!strcmp(argv[i], "--no-corpus-path"))
opts.no_corpus = true;
else if (!strcmp(argv[i], "--fail-no-debug-info"))
opts.fail_no_debug_info = true;
else if (!strcmp(argv[i], "--leaf-changes-only")
||!strcmp(argv[i], "-l"))
opts.leaf_changes_only = true;
else if (!strcmp(argv[i], "--deleted-fns"))
{
opts.show_deleted_fns = true;
opts.show_all_fns = false;
opts.show_all_vars = false;
}
else if (!strcmp(argv[i], "--changed-fns"))
{
opts.show_changed_fns = true;
opts.show_all_fns = false;
opts.show_all_vars = false;
}
else if (!strcmp(argv[i], "--added-fns"))
{
opts.show_added_fns = true;
opts.show_all_fns = false;
opts.show_all_vars = false;
}
else if (!strcmp(argv[i], "--deleted-vars"))
{
opts.show_deleted_vars = true;
opts.show_all_fns = false;
opts.show_all_vars = false;
}
else if (!strcmp(argv[i], "--changed-vars"))
{
opts.show_changed_vars = true;
opts.show_all_fns = false;
opts.show_all_vars = false;
}
else if (!strcmp(argv[i], "--added-vars"))
{
opts.show_added_vars = true;
opts.show_all_fns = false;
opts.show_all_vars = false;
}
else if (!strcmp(argv[i], "--non-reachable-types")
|| !strcmp(argv[i], "-t"))
opts.show_all_types = true;
else if (!strcmp(argv[i], "--no-added-syms"))
{
opts.show_added_syms = false;
opts.show_added_vars = false;
opts.show_added_fns = false;
// If any of the {changed,deleted}_{vars,fns} is already
// specified, --no-added-syms has no further effect. If it
// is the only option specified (as of the time of parsing
// it), it shall mean "show everything, except added vars,
// fns and unreferenced symbols.
if (!(opts.show_changed_fns
|| opts.show_changed_vars
|| opts.show_deleted_fns
|| opts.show_deleted_vars))
{
opts.show_changed_fns = true;
opts.show_changed_vars = true;
opts.show_deleted_vars = true;
opts.show_deleted_fns = true;
}
opts.show_all_fns = false;
opts.show_all_vars = false;
}
else if (!strcmp(argv[i], "--no-linkage-name"))
opts.show_linkage_names = false;
else if (!strcmp(argv[i], "--no-unreferenced-symbols"))
opts.show_symbols_not_referenced_by_debug_info = false;
else if (!strcmp(argv[i], "--no-show-locs"))
opts.show_locs = false;
else if (!strcmp(argv[i], "--show-bytes"))
opts.show_offsets_sizes_in_bits = false;
else if (!strcmp(argv[i], "--show-bits"))
opts.show_offsets_sizes_in_bits = true;
else if (!strcmp(argv[i], "--show-hex"))
opts.show_hexadecimal_values = true;
else if (!strcmp(argv[i], "--show-dec"))
opts.show_hexadecimal_values = false;
else if (!strcmp(argv[i], "--no-show-relative-offset-changes"))
opts.show_relative_offset_changes = false;
else if (!strcmp(argv[i], "--suppressions")
|| !strcmp(argv[i], "--suppr"))
{
int j = i + 1;
if (j >= argc)
{
opts.missing_operand = true;
opts.wrong_option = argv[i];
return true;
}
opts.suppression_paths.push_back(argv[j]);
++i;
}
else if (!strcmp(argv[i], "--drop"))
{
int j = i + 1;
if (j >= argc)
{
opts.missing_operand = true;
opts.wrong_option = argv[i];
return true;
}
opts.drop_fn_regex_patterns.push_back(argv[j]);
opts.drop_var_regex_patterns.push_back(argv[j]);
++i;
}
else if (!strcmp(argv[i], "--drop-fn"))
{
int j = i + 1;
if (j >= argc)
{
opts.missing_operand = true;
opts.wrong_option = argv[i];
return true;
}
opts.drop_fn_regex_patterns.push_back(argv[j]);
++i;
}
else if (!strcmp(argv[i], "--drop-var"))
{
int j = i + 1;
if (j >= argc)
{
opts.missing_operand = true;
opts.wrong_option = argv[i];
return true;
}
opts.drop_var_regex_patterns.push_back(argv[j]);
++i;
}
else if (!strcmp(argv[i], "--keep"))
{
int j = i + 1;
if (j >= argc)
{
opts.missing_operand = true;
opts.wrong_option = argv[i];
return true;
}
opts.keep_fn_regex_patterns.push_back(argv[j]);
opts.keep_var_regex_patterns.push_back(argv[j]);
++i;
}
else if (!strcmp(argv[i], "--keep-fn"))
{
int j = i + 1;
if (j >= argc)
{
opts.missing_operand = true;
opts.wrong_option = argv[i];
return true;
}
opts.keep_fn_regex_patterns.push_back(argv[j]);
}
else if (!strcmp(argv[i], "--keep-var"))
{
int j = i + 1;
if (j >= argc)
{
opts.missing_operand = true;
opts.wrong_option = argv[i];
return true;
}
opts.keep_var_regex_patterns.push_back(argv[j]);
}
else if (!strcmp(argv[i], "--harmless"))
opts.show_harmless_changes = true;
else if (!strcmp(argv[i], "--no-harmful"))
opts.show_harmful_changes = false;
else if (!strcmp(argv[i], "--redundant"))
opts.show_redundant_changes = true;
else if (!strcmp(argv[i], "--no-redundant"))
opts.show_redundant_changes = false;
else if (!strcmp(argv[i], "--impacted-interfaces"))
opts.show_impacted_interfaces = true;
else if (!strcmp(argv[i], "--dump-diff-tree"))
opts.dump_diff_tree = true;
else if (!strcmp(argv[i], "--stats"))
opts.show_stats = true;
else if (!strcmp(argv[i], "--verbose"))
opts.do_log = true;
else
{
if (strlen(argv[i]) >= 2 && argv[i][0] == '-' && argv[i][1] == '-')
opts.wrong_option = argv[i];
return false;
}
}
return true;
}
/// Display the function symbol tables for the two corpora.
///
/// @param c1 the first corpus to display the symbol table for.
///
/// @param c2 the second corpus to display the symbol table for.
///
/// @param o the output stream to emit the symbol tables to.
static void
display_symtabs(const corpus_sptr c1, const corpus_sptr c2, ostream& o)
{
o << "size of the functions symtabs: "
<< c1->get_functions().size()
<< " and "
<< c2->get_functions().size()
<< "\n\n";
if (c1->get_functions().size())
o << "First functions symbol table\n\n";
for (abigail::corpus::functions::const_iterator i =
c1->get_functions().begin();
i != c1->get_functions().end();
++i)
o << (*i)->get_pretty_representation() << std::endl;
if (c1->get_functions().size() != 0)
o << "\n";
if (c2->get_functions().size())
o << "Second functions symbol table\n\n";
for (abigail::corpus::functions::const_iterator i =
c2->get_functions().begin();
i != c2->get_functions().end();
++i)
o << (*i)->get_pretty_representation() << std::endl;
}
using abigail::comparison::diff_context_sptr;
using abigail::comparison::diff_context;
/// Check that the suppression specification files supplied are
/// present. If not, emit an error on stderr.
///
/// @param opts the options instance to use.
///
/// @return true if all suppression specification files are present,
/// false otherwise.
static bool
maybe_check_suppression_files(const options& opts)
{
for (vector<string>::const_iterator i = opts.suppression_paths.begin();
i != opts.suppression_paths.end();
++i)
if (!check_file(*i, cerr, "abidiff"))
return false;
for (vector<string>::const_iterator i =
opts.kernel_abi_whitelist_paths.begin();
i != opts.kernel_abi_whitelist_paths.end();
++i)
if (!check_file(*i, cerr, "abidiff"))
return false;
return true;
}
/// Update the diff context from the @ref options data structure.
///
/// @param ctxt the diff context to update.
///
/// @param opts the instance of @ref options to consider.
static void
set_diff_context_from_opts(diff_context_sptr ctxt,
options& opts)
{
ctxt->default_output_stream(&cout);
ctxt->error_output_stream(&cerr);
ctxt->show_leaf_changes_only(opts.leaf_changes_only);
ctxt->show_hex_values(opts.show_hexadecimal_values);
ctxt->show_offsets_sizes_in_bits(opts.show_offsets_sizes_in_bits);
ctxt->show_relative_offset_changes(opts.show_relative_offset_changes);
ctxt->show_stats_only(opts.show_stats_only);
ctxt->show_deleted_fns(opts.show_all_fns || opts.show_deleted_fns);
ctxt->show_changed_fns(opts.show_all_fns || opts.show_changed_fns);
ctxt->show_added_fns(opts.show_all_fns || opts.show_added_fns);
ctxt->show_deleted_vars(opts.show_all_vars || opts.show_deleted_vars);
ctxt->show_changed_vars(opts.show_all_vars || opts.show_changed_vars);
ctxt->show_added_vars(opts.show_all_vars || opts.show_added_vars);
ctxt->show_linkage_names(opts.show_linkage_names);
ctxt->show_locs(opts.show_locs);
ctxt->show_redundant_changes(opts.show_redundant_changes);
ctxt->show_symbols_unreferenced_by_debug_info
(opts.show_symbols_not_referenced_by_debug_info);
ctxt->show_added_symbols_unreferenced_by_debug_info
(opts.show_symbols_not_referenced_by_debug_info && opts.show_added_syms);
ctxt->show_unreachable_types(opts.show_all_types);
ctxt->show_impacted_interfaces(opts.show_impacted_interfaces);
if (!opts.show_harmless_changes)
ctxt->switch_categories_off(get_default_harmless_categories_bitmap());
if (!opts.show_harmful_changes)
ctxt->switch_categories_off(get_default_harmful_categories_bitmap());
suppressions_type supprs;
for (vector<string>::const_iterator i = opts.suppression_paths.begin();
i != opts.suppression_paths.end();
++i)
read_suppressions(*i, supprs);
ctxt->add_suppressions(supprs);
if (!opts.no_default_supprs && opts.suppression_paths.empty())
{
// Load the default system and user suppressions.
suppressions_type& supprs = ctxt->suppressions();
load_default_system_suppressions(supprs);
load_default_user_suppressions(supprs);
}
if (!opts.headers_dir1.empty())
{
// Generate suppression specification to avoid showing ABI
// changes on types that are not defined in public headers.
suppression_sptr suppr =
gen_suppr_spec_from_headers(opts.headers_dir1);
if (suppr)
ctxt->add_suppression(suppr);
}
if (!opts.headers_dir2.empty())
{
// Generate suppression specification to avoid showing ABI
// changes on types that are not defined in public headers.
suppression_sptr suppr =
gen_suppr_spec_from_headers(opts.headers_dir2);
if (suppr)
ctxt->add_suppression(suppr);
}
ctxt->dump_diff_tree(opts.dump_diff_tree);
}
/// Set suppression specifications to the @p read_context used to load
/// the ABI corpus from the ELF/DWARF file.
///
/// These suppression specifications are going to be applied to drop
/// some ABI artifacts on the floor (while reading the ELF/DWARF file
/// or the native XML ABI file) and thus minimize the size of the
/// resulting ABI corpus.
///
/// @param read_ctxt the read context to apply the suppression
/// specifications to. Note that the type of this parameter is
/// generic (class template) because in practise, it can be either an
/// abigail::dwarf_reader::read_context type or an
/// abigail::xml_reader::read_context type.
///
/// @param opts the options where to get the suppression
/// specifications from.
template<class ReadContextType>
static void
set_suppressions(ReadContextType& read_ctxt, const options& opts)
{
suppressions_type supprs;
for (vector<string>::const_iterator i = opts.suppression_paths.begin();
i != opts.suppression_paths.end();
++i)
read_suppressions(*i, supprs);
if (read_context_get_path(read_ctxt) == opts.file1
&& !opts.headers_dir1.empty())
{
// Generate suppression specification to avoid showing ABI
// changes on types that are not defined in public headers for
// the first binary.
//
// As these suppression specifications are applied during the
// corpus loading, they are going to be dropped from the
// internal representation altogether.
suppression_sptr suppr =
gen_suppr_spec_from_headers(opts.headers_dir1);
if (suppr)
{
if (opts.drop_private_types)
suppr->set_drops_artifact_from_ir(true);
supprs.push_back(suppr);
}
}
if (read_context_get_path(read_ctxt) == opts.file2
&& !opts.headers_dir2.empty())
{
// Generate suppression specification to avoid showing ABI
// changes on types that are not defined in public headers for
// the second binary.
//
// As these suppression specifications are applied during the
// corpus loading, they are going to be dropped from the
// internal representation altogether.
suppression_sptr suppr =
gen_suppr_spec_from_headers(opts.headers_dir2);
if (suppr)
{
if (opts.drop_private_types)
suppr->set_drops_artifact_from_ir(true);
supprs.push_back(suppr);
}
}
for (vector<string>::const_iterator i =
opts.kernel_abi_whitelist_paths.begin();
i != opts.kernel_abi_whitelist_paths.end();
++i)
gen_suppr_spec_from_kernel_abi_whitelist(*i, supprs);
add_read_context_suppressions(read_ctxt, supprs);
}
/// Configure the abigail::xml_reacher::read_context based on the
/// relevant command-line options.
///
/// @param ctxt the read context to configure.
///
/// @param opts the command-line options to configure @p ctxt from.
static void
set_native_xml_reader_options(abigail::xml_reader::read_context& ctxt,
const options& opts)
{
consider_types_not_reachable_from_public_interfaces(ctxt,
opts.show_all_types);
}
/// Set the regex patterns describing the functions to drop from the
/// symbol table of a given corpus.
///
/// @param opts the options to the regex patterns from.
///
/// @param c the corpus to set the regex patterns into.
static void
set_corpus_keep_drop_regex_patterns(options& opts, corpus_sptr c)
{
if (!opts.drop_fn_regex_patterns.empty())
{
vector<string>& v = opts.drop_fn_regex_patterns;
vector<string>& p = c->get_regex_patterns_of_fns_to_suppress();
p.assign(v.begin(), v.end());
}
if (!opts.keep_fn_regex_patterns.empty())
{
vector<string>& v = opts.keep_fn_regex_patterns;
vector<string>& p = c->get_regex_patterns_of_fns_to_keep();
p.assign(v.begin(), v.end());
}
if (!opts.drop_var_regex_patterns.empty())
{
vector<string>& v = opts.drop_var_regex_patterns;
vector<string>& p = c->get_regex_patterns_of_vars_to_suppress();
p.assign(v.begin(), v.end());
}
if (!opts.keep_var_regex_patterns.empty())
{
vector<string>& v = opts.keep_var_regex_patterns;
vector<string>& p = c->get_regex_patterns_of_vars_to_keep();
p.assign(v.begin(), v.end());
}
}
/// This function sets diff context options that are specific to
/// kernel module interface comparison.
///
/// @param ctxt the diff context to consider.
static void
adjust_diff_context_for_kmidiff(diff_context &ctxt)
{
ctxt.show_linkage_names(false);
ctxt.show_added_fns(false);
ctxt.show_added_vars(false);
ctxt.show_added_symbols_unreferenced_by_debug_info
(false);
}
/// Convert options::di_root_paths{1,2} into
/// options::prepared_di_root_paths{1,2} which is the suitable type
/// format that the dwarf_reader expects.
///
/// @param o the options to consider.
static void
prepare_di_root_paths(options& o)
{
abigail::tools_utils::convert_char_stars_to_char_star_stars
(o.di_root_paths1, o.prepared_di_root_paths1);
abigail::tools_utils::convert_char_stars_to_char_star_stars
(o.di_root_paths2, o.prepared_di_root_paths2);
}
/// Emit an appropriate error message if necessary, given an error
/// code.
///
/// To emit the appropriate error message the function might need to
/// access the context in which the (ELF) input file was being loaded,
/// if it's present.
///
/// @param status_code the status code returned after trying to load
/// the input file.
///
/// @param ctxt the context used to load the ELF file, if we still
/// have it. If this is nil, then it's ignored.
///
/// @param prog_name the name of the current program. This is
/// important as it's used in the error message.
///
/// @param input_file_name the name of the input file that we are
/// tryin to load.
///
/// @param debug_info_dir1 if non nil, then this points to the path of
/// the root debug info directory of the first binary that we are
/// trying to load.. If nil, then it's ignored.
///
/// @param debug_info_dir2 if non nil, then this points to the path of
/// the root debug info directory of the second binary that we are
/// trying to load.. If nil, then it's ignored.
///
/// @return abigail::tools_utils::ABIDIFF_ERROR if an error was
/// detected, abigail::tools_utils::ABIDIFF_OK otherwise.
static abigail::tools_utils::abidiff_status
handle_error(abigail::dwarf_reader::status status_code,
const abigail::dwarf_reader::read_context* ctxt,
const string& prog_name,
const options& opts)
{
if (!(status_code & abigail::dwarf_reader::STATUS_OK))
{
emit_prefix(prog_name, cerr)
<< "failed to read input file " << opts.file1 << "\n";
if (status_code & abigail::dwarf_reader::STATUS_DEBUG_INFO_NOT_FOUND)
{
emit_prefix(prog_name, cerr) <<
"could not find the debug info\n";
{
if (opts.prepared_di_root_paths1.empty() == 0)
emit_prefix(prog_name, cerr)
<< "Maybe you should consider using the "
"--debug-info-dir1 option to tell me about the "
"root directory of the debuginfo? "
"(e.g, --debug-info-dir1 /usr/lib/debug)\n";
else
{
emit_prefix(prog_name, cerr)
<< "Maybe the root path to the debug information '";
for (vector<char**>::const_iterator i
= opts.prepared_di_root_paths1.begin();
i != opts.prepared_di_root_paths1.end();
++i)
{
if (i != opts.prepared_di_root_paths1.end())
cerr << ", ";
cerr << **i;
}
cerr << "' is wrong?\n";
}
}
{
if (opts.prepared_di_root_paths2.empty())
emit_prefix(prog_name, cerr)
<< "Maybe you should consider using the "
"--debug-info-dir2 option to tell me about the "
"root directory of the debuginfo? "
"(e.g, --debug-info-dir2 /usr/lib/debug)\n";
else
{
emit_prefix(prog_name, cerr)
<< "Maybe the root path to the debug information '";
for (vector<char**>::const_iterator i
= opts.prepared_di_root_paths2.begin();
i != opts.prepared_di_root_paths2.end();
++i)
{
if (i != opts.prepared_di_root_paths2.end())
cerr << ", ";
cerr << **i;
}
cerr << "' is wrong?\n";
}
}
}
if (status_code & abigail::dwarf_reader::STATUS_ALT_DEBUG_INFO_NOT_FOUND)
{
emit_prefix(prog_name, cerr)
<< "could not find the alternate debug info file";
if (ctxt)
{
string alt_di_path;
abigail::dwarf_reader::refers_to_alt_debug_info(*ctxt,
alt_di_path);
if (!alt_di_path.empty())
cerr << " at: " << alt_di_path;
}
cerr << "\n";
}
if (status_code & abigail::dwarf_reader::STATUS_NO_SYMBOLS_FOUND)
emit_prefix(prog_name, cerr)
<< "could not find the ELF symbols in the file '"
<< opts.file1
<< "'\n";
return abigail::tools_utils::ABIDIFF_ERROR;
}
return abigail::tools_utils::ABIDIFF_OK;
}
int
main(int argc, char* argv[])
{
options opts;
if (!parse_command_line(argc, argv, opts))
{
emit_prefix(argv[0], cerr)
<< "unrecognized option: "
<< opts.wrong_option << "\n"
<< "try the --help option for more information\n";
return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
| abigail::tools_utils::ABIDIFF_ERROR);
}
if (opts.missing_operand)
{
emit_prefix(argv[0], cerr)
<< "missing operand to option: " << opts.wrong_option <<"\n"
<< "try the --help option for more information\n";
return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
| abigail::tools_utils::ABIDIFF_ERROR);
}
if (opts.display_usage)
{
display_usage(argv[0], cout);
return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
| abigail::tools_utils::ABIDIFF_ERROR);
}
if (opts.display_version)
{
emit_prefix(argv[0], cout)
<< abigail::tools_utils::get_library_version_string()
<< "\n";
return 0;
}
prepare_di_root_paths(opts);
if (!maybe_check_suppression_files(opts))
return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
| abigail::tools_utils::ABIDIFF_ERROR);
abidiff_status status = abigail::tools_utils::ABIDIFF_OK;
if (!opts.file1.empty() && !opts.file2.empty())
{
if (!check_file(opts.file1, cerr))
return abigail::tools_utils::ABIDIFF_ERROR;
if (!check_file(opts.file2, cerr))
return abigail::tools_utils::ABIDIFF_ERROR;
abigail::tools_utils::file_type t1_type, t2_type;
t1_type = guess_file_type(opts.file1);
if (t1_type == abigail::tools_utils::FILE_TYPE_UNKNOWN)
{
emit_prefix(argv[0], cerr)
<< "Unknown content type for file " << opts.file1 << "\n";
return abigail::tools_utils::ABIDIFF_ERROR;
}
t2_type = guess_file_type(opts.file2);
if (t2_type == abigail::tools_utils::FILE_TYPE_UNKNOWN)
{
emit_prefix(argv[0], cerr)
<< "Unknown content type for file " << opts.file2 << "\n";
return abigail::tools_utils::ABIDIFF_ERROR;
}
environment_sptr env(new environment);
translation_unit_sptr t1, t2;
abigail::dwarf_reader::status c1_status =
abigail::dwarf_reader::STATUS_OK,
c2_status = abigail::dwarf_reader::STATUS_OK;
corpus_sptr c1, c2;
corpus_group_sptr g1, g2;
bool files_suppressed = false;
diff_context_sptr ctxt(new diff_context);
set_diff_context_from_opts(ctxt, opts);
suppressions_type& supprs = ctxt->suppressions();
files_suppressed = (file_is_suppressed(opts.file1, supprs)
|| file_is_suppressed(opts.file2, supprs));
if (files_suppressed)
// We don't have to compare anything because a user
// suppression specification file instructs us to avoid
// loading either one of the input files.
return abigail::tools_utils::ABIDIFF_OK;
switch (t1_type)
{
case abigail::tools_utils::FILE_TYPE_UNKNOWN:
emit_prefix(argv[0], cerr)
<< "Unknown content type for file " << opts.file1 << "\n";
return abigail::tools_utils::ABIDIFF_ERROR;
break;
case abigail::tools_utils::FILE_TYPE_NATIVE_BI:
t1 = abigail::xml_reader::read_translation_unit_from_file(opts.file1,
env.get());
break;
case abigail::tools_utils::FILE_TYPE_ELF: // fall through
case abigail::tools_utils::FILE_TYPE_AR:
{
abigail::dwarf_reader::read_context_sptr ctxt =
abigail::dwarf_reader::create_read_context
(opts.file1, opts.prepared_di_root_paths1,
env.get(), /*readalltypes=*/opts.show_all_types,
opts.linux_kernel_mode);
assert(ctxt);
abigail::dwarf_reader::set_show_stats(*ctxt, opts.show_stats);
set_suppressions(*ctxt, opts);
abigail::dwarf_reader::set_do_log(*ctxt, opts.do_log);
c1 = abigail::dwarf_reader::read_corpus_from_elf(*ctxt, c1_status);
if (!c1
|| (opts.fail_no_debug_info
&& (c1_status & STATUS_ALT_DEBUG_INFO_NOT_FOUND)
&& (c1_status & STATUS_DEBUG_INFO_NOT_FOUND)))
return handle_error(c1_status, ctxt.get(),
argv[0], opts);
}
break;
case abigail::tools_utils::FILE_TYPE_XML_CORPUS:
{
abigail::xml_reader::read_context_sptr ctxt =
abigail::xml_reader::create_native_xml_read_context(opts.file1,
env.get());
assert(ctxt);
set_suppressions(*ctxt, opts);
set_native_xml_reader_options(*ctxt, opts);
c1 = abigail::xml_reader::read_corpus_from_input(*ctxt);
if (!c1)
return handle_error(c1_status, /*ctxt=*/0,
argv[0], opts);
}
break;
case abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP:
{
abigail::xml_reader::read_context_sptr ctxt =
abigail::xml_reader::create_native_xml_read_context(opts.file1,
env.get());
assert(ctxt);
set_suppressions(*ctxt, opts);
set_native_xml_reader_options(*ctxt, opts);
g1 = abigail::xml_reader::read_corpus_group_from_input(*ctxt);
if (!g1)
return handle_error(c1_status, /*ctxt=*/0,
argv[0], opts);
}
break;
case abigail::tools_utils::FILE_TYPE_ZIP_CORPUS:
#ifdef WITH_ZIP_ARCHIVE
c1 = abigail::xml_reader::read_corpus_from_file(opts.file1);
if (!c1)
return handle_error(c1_status, /*ctxt=*/0, argv[0], opts);
#endif //WITH_ZIP_ARCHIVE
break;
case abigail::tools_utils::FILE_TYPE_RPM:
case abigail::tools_utils::FILE_TYPE_SRPM:
case abigail::tools_utils::FILE_TYPE_DEB:
case abigail::tools_utils::FILE_TYPE_DIR:
case abigail::tools_utils::FILE_TYPE_TAR:
break;
}
switch (t2_type)
{
case abigail::tools_utils::FILE_TYPE_UNKNOWN:
emit_prefix(argv[0], cerr)
<< "Unknown content type for file " << opts.file2 << "\n";
return abigail::tools_utils::ABIDIFF_ERROR;
break;
case abigail::tools_utils::FILE_TYPE_NATIVE_BI:
t2 = abigail::xml_reader::read_translation_unit_from_file(opts.file2,
env.get());
break;
case abigail::tools_utils::FILE_TYPE_ELF: // Fall through
case abigail::tools_utils::FILE_TYPE_AR:
{
abigail::dwarf_reader::read_context_sptr ctxt =
abigail::dwarf_reader::create_read_context
(opts.file2, opts.prepared_di_root_paths2,
env.get(), /*readalltypes=*/opts.show_all_types,
opts.linux_kernel_mode);
assert(ctxt);
abigail::dwarf_reader::set_show_stats(*ctxt, opts.show_stats);
abigail::dwarf_reader::set_do_log(*ctxt, opts.do_log);
set_suppressions(*ctxt, opts);
c2 = abigail::dwarf_reader::read_corpus_from_elf(*ctxt, c2_status);
if (!c2
|| (opts.fail_no_debug_info
&& (c2_status & STATUS_ALT_DEBUG_INFO_NOT_FOUND)
&& (c2_status & STATUS_DEBUG_INFO_NOT_FOUND)))
return handle_error(c2_status, ctxt.get(), argv[0], opts);
}
break;
case abigail::tools_utils::FILE_TYPE_XML_CORPUS:
{
abigail::xml_reader::read_context_sptr ctxt =
abigail::xml_reader::create_native_xml_read_context(opts.file2,
env.get());
assert(ctxt);
set_suppressions(*ctxt, opts);
set_native_xml_reader_options(*ctxt, opts);
c2 = abigail::xml_reader::read_corpus_from_input(*ctxt);
if (!c2)
return handle_error(c2_status, /*ctxt=*/0, argv[0], opts);
}
break;
case abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP:
{
abigail::xml_reader::read_context_sptr ctxt =
abigail::xml_reader::create_native_xml_read_context(opts.file2,
env.get());
assert(ctxt);
set_suppressions(*ctxt, opts);
set_native_xml_reader_options(*ctxt, opts);
g2 = abigail::xml_reader::read_corpus_group_from_input(*ctxt);
if (!g2)
return handle_error(c2_status, /*ctxt=*/0, argv[0], opts);
}
break;
case abigail::tools_utils::FILE_TYPE_ZIP_CORPUS:
#ifdef WITH_ZIP_ARCHIVE
c2 = abigail::xml_reader::read_corpus_from_file(opts.file2);
if (!c2)
return handle_error(c2_status, /*ctxt=*/0, argv[0], opts);
#endif //WITH_ZIP_ARCHIVE
break;
case abigail::tools_utils::FILE_TYPE_RPM:
case abigail::tools_utils::FILE_TYPE_SRPM:
case abigail::tools_utils::FILE_TYPE_DEB:
case abigail::tools_utils::FILE_TYPE_DIR:
case abigail::tools_utils::FILE_TYPE_TAR:
break;
}
if (!!c1 != !!c2
|| !!t1 != !!t2
|| !!g1 != !!g2)
{
emit_prefix(argv[0], cerr)
<< "the two input should be of the same kind\n";
return abigail::tools_utils::ABIDIFF_ERROR;
}
if (opts.no_arch)
{
if (c1)
c1->set_architecture_name("");
if (c2)
c2->set_architecture_name("");
}
if (opts.no_corpus)
{
if (c1)
c1->set_path("");
if (c2)
c2->set_path("");
}
if (t1)
{
diff_context_sptr diff_ctxt(new diff_context);
translation_unit_diff_sptr diff = compute_diff(t1, t2, diff_ctxt);
if (diff->has_changes())
diff->report(cout);
}
else if (c1)
{
if (opts.show_symtabs)
{
display_symtabs(c1, c2, cout);
return abigail::tools_utils::ABIDIFF_OK;
}
set_corpus_keep_drop_regex_patterns(opts, c1);
set_corpus_keep_drop_regex_patterns(opts, c2);
corpus_diff_sptr diff = compute_diff(c1, c2, ctxt);
if (diff->has_net_changes())
status = abigail::tools_utils::ABIDIFF_ABI_CHANGE;
if (diff->has_incompatible_changes())
status |= abigail::tools_utils::ABIDIFF_ABI_INCOMPATIBLE_CHANGE;
if (diff->has_changes())
diff->report(cout);
}
else if (g1)
{
if (opts.show_symtabs)
{
display_symtabs(c1, c2, cout);
return abigail::tools_utils::ABIDIFF_OK;
}
adjust_diff_context_for_kmidiff(*ctxt);
corpus_diff_sptr diff = compute_diff(g1, g2, ctxt);
if (diff->has_net_changes())
status = abigail::tools_utils::ABIDIFF_ABI_CHANGE;
if (diff->has_incompatible_changes())
status |= abigail::tools_utils::ABIDIFF_ABI_INCOMPATIBLE_CHANGE;
if (diff->has_changes())
diff->report(cout);
}
else
status = abigail::tools_utils::ABIDIFF_ERROR;
}
return status;
}
#ifdef __ABIGAIL_IN_THE_DEBUGGER__
/// Emit a textual representation of a given @ref corpus_diff tree to
/// stdout.
///
/// This is useful when debugging this program.
///
/// @param diff_tree the diff tree to emit a textual representation
/// for.
void
print_diff_tree(abigail::comparison::corpus_diff* diff_tree)
{
print_diff_tree(diff_tree, std::cout);
}
/// Emit a textual representation of a given @ref corpus_diff tree to
/// stdout.
///
/// This is useful when debugging this program.
///
/// @param diff_tree the diff tree to emit a textual representation
/// for.
void
print_diff_tree(abigail::comparison::corpus_diff_sptr diff_tree)
{
print_diff_tree(diff_tree, std::cout);
}
/// Emit a textual representation of a given @ref corpus_diff tree to
/// stdout.
///
/// This is useful when debugging this program.
///
/// @param diff_tree the diff tree to emit a textual representation
/// for.
void
print_diff_tree(abigail::comparison::diff_sptr diff_tree)
{
print_diff_tree(diff_tree.get(), std::cout);
}
/// Emit a textual representation of a given @ref diff tree to
/// stdout.
///
/// This is useful when debugging this program.
///
/// @param diff_tree the diff tree to emit a textual representation
/// for.
void
print_diff_tree(abigail::comparison::diff* diff_tree)
{
print_diff_tree(diff_tree, std::cout);
}
#endif // __ABIGAIL_IN_THE_DEBUGGER__