// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// -*- Mode: C++ -*-
//
// Copyright (C) 2013-2025 Red Hat, Inc.
//
// Author: Dodji Seketeli

/// @file
///
/// This program reads an elf file, try to load its debug info (in
/// DWARF format) and emit it back in a set of "text sections" in native
/// libabigail XML format.

#include "config.h"
#include <unistd.h>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <set>
#include "abg-config.h"
#include "abg-tools-utils.h"
#include "abg-corpus.h"
#include "abg-dwarf-reader.h"
#ifdef WITH_CTF
#include "abg-ctf-reader.h"
#endif
#ifdef WITH_BTF
#include "abg-btf-reader.h"
#endif
#include "abg-writer.h"
#include "abg-reader.h"
#include "abg-comparison.h"
#include "abg-suppression.h"

using std::string;
using std::cerr;
using std::cout;
using std::ostream;
using std::ofstream;
using std::vector;
using std::set;
using std::shared_ptr;
using std::static_pointer_cast;
using abg_compat::optional;
using abigail::tools_utils::emit_prefix;
using abigail::tools_utils::temp_file;
using abigail::tools_utils::temp_file_sptr;
using abigail::tools_utils::check_file;
using abigail::tools_utils::build_corpus_group_from_kernel_dist_under;
using abigail::tools_utils::timer;
using abigail::tools_utils::create_best_elf_based_reader;
using abigail::tools_utils::stick_corpus_and_dependencies_into_corpus_group;
using abigail::tools_utils::stick_corpus_and_binaries_into_corpus_group;
using abigail::tools_utils::add_dependencies_into_corpus_group;
using abigail::ir::environment_sptr;
using abigail::ir::environment;
using abigail::corpus;
using abigail::corpus_sptr;
using abigail::translation_units;
using abigail::suppr::suppression_sptr;
using abigail::suppr::suppressions_type;
using abigail::suppr::read_suppressions;
using abigail::comparison::corpus_diff;
using abigail::comparison::corpus_diff_sptr;
using abigail::comparison::compute_diff;
using abigail::comparison::diff_context_sptr;
using abigail::comparison::diff_context;
using abigail::xml_writer::SEQUENCE_TYPE_ID_STYLE;
using abigail::xml_writer::HASH_TYPE_ID_STYLE;
using abigail::xml_writer::create_write_context;
using abigail::xml_writer::type_id_style_kind;
using abigail::xml_writer::write_context_sptr;
using abigail::xml_writer::write_corpus;
using abigail::xml_writer::write_corpus_group;
using abigail::abixml::read_corpus_from_abixml_file;

using namespace abigail;

struct options
{
  string		wrong_option;
  string		in_file_path;
  string		out_file_path;
  vector<string>	di_root_paths;
  vector<string>	headers_dirs;
  vector<string>	header_files;
  vector<string>	added_bins_dirs;
  vector<string>	added_bins;
  string		vmlinux;
  vector<string>	suppression_paths;
  vector<string>	kabi_whitelist_paths;
  suppressions_type	kabi_whitelist_supprs;
  bool			display_version;
  bool			display_abixml_version;
  bool			fail_no_debug_info;
  bool			check_alt_debug_info_path;
  bool			show_base_name_alt_debug_info_path;
  bool			write_architecture;
  bool			write_corpus_path;
  bool			write_comp_dir;
  bool			write_elf_needed;
  bool			write_parameter_names;
  bool			short_locs;
  bool			default_sizes;
  bool			load_all_types;
  bool			load_undefined_interfaces;
  bool			linux_kernel_mode;
  bool			corpus_group_for_linux;
  bool			show_stats;
  bool			noout;
  bool			follow_dependencies;
  bool			list_dependencies;
#ifdef WITH_CTF
  bool			use_ctf;
#endif
#ifdef WITH_BTF
  bool			use_btf;
#endif
  bool			show_locs;
  bool			abidiff;
#ifdef WITH_DEBUG_SELF_COMPARISON
  bool			debug_abidiff;
#endif
#ifdef WITH_DEBUG_TYPE_CANONICALIZATION
  bool			debug_type_canonicalization;
  bool			debug_die_canonicalization;
#endif
  bool			annotate;
  bool			do_log;
  bool			drop_private_types;
  bool			force_early_suppression;
  bool			drop_undefined_syms;
  bool			assume_odr_for_cplusplus;
  bool			leverage_dwarf_factorization;
  optional<bool>	exported_interfaces_only;
  type_id_style_kind	type_id_style;
#ifdef WITH_DEBUG_SELF_COMPARISON
  string		type_id_file_path;
#endif

  options()
    : display_version(),
      display_abixml_version(),
      fail_no_debug_info(),
      check_alt_debug_info_path(),
      show_base_name_alt_debug_info_path(),
      write_architecture(true),
      write_corpus_path(true),
      write_comp_dir(true),
      write_elf_needed(true),
      write_parameter_names(true),
      short_locs(false),
      default_sizes(true),
      load_all_types(),
      load_undefined_interfaces(true),
      linux_kernel_mode(true),
      corpus_group_for_linux(false),
      show_stats(),
      noout(),
      follow_dependencies(),
      list_dependencies(),
#ifdef WITH_CTF
      use_ctf(false),
#endif
#ifdef WITH_BTF
      use_btf(false),
#endif
      show_locs(true),
      abidiff(),
#ifdef WITH_DEBUG_SELF_COMPARISON
      debug_abidiff(),
#endif
#ifdef WITH_DEBUG_TYPE_CANONICALIZATION
      debug_type_canonicalization(),
      debug_die_canonicalization(),
#endif
      annotate(),
      do_log(),
      drop_private_types(false),
      force_early_suppression(false),
      drop_undefined_syms(false),
      assume_odr_for_cplusplus(true),
      leverage_dwarf_factorization(true),
      type_id_style(SEQUENCE_TYPE_ID_STYLE)
  {}

  ~options()
  {
  }
};

static void
display_usage(const string& prog_name, ostream& out)
{
  emit_prefix(prog_name, out)
    << "usage: " << prog_name << " [options] [<path-to-elf-file>]\n"
    << " where options can be: \n"
    << "  --abidiff  compare the loaded ABI against itself\n"
    << "  --abixml-version  display the version of the ABIXML ABI format\n"
    << "  --add-binaries <bin1,bin2,...>  build a corpus group with "
    "the added inaries\n"
    << "  --allow-non-exported-interfaces analyze interfaces that"
    "might not be exported\n"
    << "  --annotate  annotate the ABI artifacts emitted in the output\n"
#ifdef WITH_BTF
    << "  --btf use BTF instead of DWARF in ELF files\n"
#endif
    << "  --check-alternate-debug-info <elf-path>  check alternate debug info "
    "of <elf-path>\n"
    << "  --check-alternate-debug-info-base-name <elf-path>  check alternate "
    "debug info of <elf-path>, and show its base name\n"
#ifdef WITH_CTF
    << "  --ctf use CTF instead of DWARF in ELF files\n"
#endif
#ifdef WITH_DEBUG_SELF_COMPARISON
    << "  --debug-abidiff  debug the process of comparing the loaded ABI against itself\n"
#endif
    << "  --debug-info-dir|-d <dir-path>  look for debug info under 'dir-path'\n"
#ifdef WITH_DEBUG_TYPE_CANONICALIZATION
    << "  --debug-tc  debug the type canonicalization process\n"
    << "  --debug-dc  debug the DIE canonicalization process\n"
#endif
    << "  --drop-undefined-syms  drop undefined symbols from representation\n"
    << "  --exported-interfaces-only  analyze exported interfaces only\n"
    << "  --follow-dependencies  build a corpus group with the dependencies\n"
    << "  --follow-dependencies  build a corpus group with the dependencies\n"
    << "  --force-early-suppression  drop IR nodes that match suppression specifications\n"
    << "  --headers-dir|--hd <path> the path to headers of the elf file\n"
    << "  --header-file|--hf <path> the path one header of the elf file\n"
    << "  --help|-h  display this message\n"
    << "  --kmi-whitelist|--kmi-stablelist|-w  path to a linux kernel "
    "abi whitelist\n"
    << "  --list-dependencies  list the dependencies of a given binary\n"
    << "  --added-binaries-dir|--abd <dir-of-deps>  where to look for dependencies "
    "or added binaries\n"
    << "  --linux-tree|--lt  emit the ABI for the union of a "
    "vmlinux and its modules\n"
    << "  --load-all-types  read all types including those not reachable from "
    "exported declarations\n"
    << "  --no-architecture  do not emit architecture info in the output\n"
    << "  --no-assume-odr-for-cplusplus  do not assume the ODR to speed-up the "
    "analysis of the binary\n"
    << "  --no-comp-dir-path  do not show compilation path information\n"
    << "  --no-corpus-path  do not take the path to the corpora into account\n"
    << "  --no-elf-needed  do not show the DT_NEEDED information\n"
    << "  --no-leverage-dwarf-factorization  do not use DWZ optimisations to "
    "speed-up the analysis of the binary\n"
    << "  --no-linux-kernel-mode  don't consider the input binary as "
       "a Linux Kernel binary\n"
    << "  --no-load-undefined-interfaces  do not consider undefined "
    "interfaces from the binary"
    << "  --no-parameter-names  do not show names of function parameters\n"
    << "  --no-show-locs  do not show location information\n"
    << "  --no-write-default-sizes  do not emit pointer size when it equals"
    " the default address size of the translation unit\n"
    << "  --noout  do not emit anything after reading the binary\n"
    << "  --out-file|-o  <file-path>  write the output to 'file-path'\n"
    << "  --short-locs  only print filenames rather than paths\n"
    << "  --suppressions|--suppr <path> specify a suppression file\n"
    << "  --type-id-style <sequence|hash>  type id style (sequence(default): "
       "\"type-id-\" + number; hash: hex-digits)\n"
    << "  --stats  show statistics about various internal stuff\n"
    << "  --verbose show verbose messages about internal stuff\n"
    << "  --version|-v  display program version information and exit\n"
    << "  --vmlinux <path>  the path to the vmlinux binary to consider to emit "
       "the ABI of the union of vmlinux and its modules\n" ;
}

static 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.in_file_path.empty())
	    opts.in_file_path = argv[i];
	  else
	    return false;
	}
      else if (!strcmp(argv[i], "--version")
	       || !strcmp(argv[i], "-v"))
	opts.display_version = true;
      else if (!strcmp(argv[i], "--abixml-version")
	       || !strcmp(argv[i], "-v"))
	opts.display_abixml_version = true;
      else if (!strcmp(argv[i], "--fail-no-debug-info"))
	opts.fail_no_debug_info = true;
      else if (!strcmp(argv[i], "--debug-info-dir")
	       || !strcmp(argv[i], "-d"))
	{
	  if (argc <= i + 1
	      || argv[i + 1][0] == '-')
	    return false;
	  // elfutils wants the root path to the debug info to be
	  // absolute.
	  opts.di_root_paths.push_back
	    (abigail::tools_utils::make_path_absolute(string(argv[i + 1])));
	  ++i;
	}
      else if (!strcmp(argv[i], "--headers-dir")
	       || !strcmp(argv[i], "--hd"))
	{
	  int j = i + 1;
	  if (j >= argc)
	    return false;
	  opts.headers_dirs.push_back(argv[j]);
	  // The user is indirectly defining private types so she
	  // really wants those types to be removed from the ABIXML.
	  // So let's drop them from the IR.
	  opts.drop_private_types = true;
	  ++i;
	}
      else if (!strcmp(argv[i], "--added-binaries-dir")
	       || !strcmp(argv[i], "--abd"))
	{
	  int j = i + 1;
	  if (j >= argc)
	    return false;
	  opts.added_bins_dirs.push_back(argv[j]);
	  ++i;
	}
      else if (!strcmp(argv[i], "--header-file")
	       || !strcmp(argv[i], "--hf"))
	{
	  int j = i + 1;
	  if (j >= argc)
	    return false;
	  opts.header_files.push_back(argv[j]);
	  // The user is indirectly defining private types so she
	  // really wants those types to be removed from the ABIXML.
	  // So let's drop them from the IR.
	  opts.drop_private_types = true;
	  ++i;
	}
      else if (!strcmp(argv[i], "--out-file")
	       || !strcmp(argv[i], "-o"))
	{
	  if (argc <= i + 1
	      || argv[i + 1][0] == '-'
	      || !opts.out_file_path.empty())
	    return false;

	  opts.out_file_path = argv[i + 1];
	  ++i;
	}
      else if (!strcmp(argv[i], "--suppressions")
	       || !strcmp(argv[i], "--suppr"))
	{
	  int j = i + 1;
	  if (j >= argc)
	    return false;
	  opts.suppression_paths.push_back(argv[j]);
	  ++i;
	}
      else if (!strcmp(argv[i], "--kmi-whitelist")
	       || !strcmp(argv[i], "--kmi-stablelist")
	       || !strcmp(argv[i], "-w"))
	{
	  int j = i + 1;
	  if (j >= argc)
	    return false;
	  opts.kabi_whitelist_paths.push_back(argv[j]);
	  ++i;
	}
      else if (!strcmp(argv[i], "--linux-tree")
	       || !strcmp(argv[i], "--lt"))
	opts.corpus_group_for_linux = true;
      else if (!strcmp(argv[i], "--vmlinux"))
	{
	  int j = i + 1;
	  if (j >= argc)
	    return false;
	  opts.vmlinux = argv[j];
	  ++i;
	}
      else if (!strcmp(argv[i], "--noout"))
	opts.noout = true;
      else if (!strcmp(argv[i], "--follow-dependencies"))
	opts.follow_dependencies = true;
      else if (!strcmp(argv[i], "--list-dependencies"))
	opts.list_dependencies = true;
      else if (!strncmp(argv[i], "--add-binaries=",
			strlen("--add-binaries=")))
	tools_utils::get_comma_separated_args_of_option(argv[i],
							"--add-binaries=",
							opts.added_bins);
      else if (!strcmp(argv[i], "--add-binaries"))
	{
	  int j = i + 1;
	  if (j >= argc)
	    return false;

	  string s = argv[j];
	  if (s.find(','))
	    tools_utils::split_string(s, ",", opts.added_bins);
	  else
	    opts.added_bins.push_back(s);
	  ++i;
	}
#ifdef WITH_CTF
        else if (!strcmp(argv[i], "--ctf"))
          opts.use_ctf = true;
#endif
#ifdef WITH_BTF
        else if (!strcmp(argv[i], "--btf"))
          opts.use_btf = true;
#endif
      else if (!strcmp(argv[i], "--no-architecture"))
	opts.write_architecture = false;
      else if (!strcmp(argv[i], "--no-corpus-path"))
	opts.write_corpus_path = false;
      else if (!strcmp(argv[i], "--no-show-locs"))
	opts.show_locs = false;
      else if (!strcmp(argv[i], "--short-locs"))
	opts.short_locs = true;
      else if (!strcmp(argv[i], "--no-comp-dir-path"))
	opts.write_comp_dir = false;
      else if (!strcmp(argv[i], "--no-elf-needed"))
	opts.write_elf_needed = false;
      else if (!strcmp(argv[i], "--no-write-default-sizes"))
	opts.default_sizes = false;
      else if (!strcmp(argv[i], "--no-parameter-names"))
	opts.write_parameter_names = false;
      else if (!strcmp(argv[i], "--type-id-style"))
        {
          ++i;
          if (i >= argc)
            return false;
          if (!strcmp(argv[i], "sequence"))
            opts.type_id_style = SEQUENCE_TYPE_ID_STYLE;
          else if (!strcmp(argv[i], "hash"))
            opts.type_id_style = HASH_TYPE_ID_STYLE;
          else
            return false;
        }
      else if (!strcmp(argv[i], "--check-alternate-debug-info")
	       || !strcmp(argv[i], "--check-alternate-debug-info-base-name"))
	{
	  if (argc <= i + 1
	      || argv[i + 1][0] == '-'
	      || !opts.in_file_path.empty())
	    return false;
	  if (!strcmp(argv[i], "--check-alternate-debug-info-base-name"))
	    opts.show_base_name_alt_debug_info_path = true;
	  opts.check_alt_debug_info_path = true;
	  opts.in_file_path = argv[i + 1];
	  ++i;
	}
      else if (!strcmp(argv[i], "--load-all-types"))
	opts.load_all_types = true;
      else if (!strcmp(argv[i], "--no-load-undefined-interfaces"))
	opts.load_undefined_interfaces = false;
      else if (!strcmp(argv[i], "--drop-private-types"))
	opts.drop_private_types = true;
      else if (!strcmp(argv[i], "--force-early-suppression"))
	opts.force_early_suppression = true;
      else if (!strcmp(argv[i], "--drop-undefined-syms"))
	opts.drop_undefined_syms = true;
      else if (!strcmp(argv[i], "--exported-interfaces-only"))
	opts.exported_interfaces_only = true;
      else if (!strcmp(argv[i], "--allow-non-exported-interfaces"))
	opts.exported_interfaces_only = false;
      else if (!strcmp(argv[i], "--no-linux-kernel-mode"))
	opts.linux_kernel_mode = false;
      else if (!strcmp(argv[i], "--abidiff"))
	opts.abidiff = true;
#ifdef WITH_DEBUG_SELF_COMPARISON
      else if (!strcmp(argv[i], "--debug-abidiff"))
	{
	  opts.abidiff = true;
	  opts.debug_abidiff = true;
	}
#endif
#ifdef WITH_DEBUG_TYPE_CANONICALIZATION
      else if (!strcmp(argv[i], "--debug-tc")
	       || !strcmp(argv[i], "debug-type-canonicalization"))
	opts.debug_type_canonicalization = true;
      else if (!strcmp(argv[i], "--debug-dc")
	       || !strcmp(argv[i], "debug-die-canonicalization"))
	opts.debug_die_canonicalization = true;
#endif
      else if (!strcmp (argv[i], "--no-assume-odr-for-cplusplus"))
	opts.assume_odr_for_cplusplus = false;
      else if (!strcmp (argv[i], "--no-leverage-dwarf-factorization"))
	opts.leverage_dwarf_factorization = false;
      else if (!strcmp(argv[i], "--annotate"))
	opts.annotate = true;
      else if (!strcmp(argv[i], "--stats"))
	opts.show_stats = true;
      else if (!strcmp(argv[i], "--verbose"))
	opts.do_log = true;
      else if (!strcmp(argv[i], "--help")
	       || !strcmp(argv[i], "--h"))
	return false;
      else
	{
	  if (strlen(argv[i]) >= 2 && argv[i][0] == '-' && argv[i][1] == '-')
	    opts.wrong_option = argv[i];
	  return false;
	}
    }

  return true;
}

/// Initialize the context use for driving ABI comparison.
///
/// @param ctxt the context to initialize.
static void
set_diff_context(diff_context_sptr& ctxt)
{
  ctxt->default_output_stream(&cerr);
  ctxt->error_output_stream(&cerr);
  // Filter out changes that are not meaningful from an ABI
  // standpoint, from the diff output.
  ctxt->switch_categories_off
    (abigail::comparison::ACCESS_CHANGE_CATEGORY
     | abigail::comparison::COMPATIBLE_TYPE_CHANGE_CATEGORY
     | abigail::comparison::HARMLESS_DECL_NAME_CHANGE_CATEGORY);
}

/// 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, "abidw"))
      return false;

  for (vector<string>::const_iterator i =
	 opts.kabi_whitelist_paths.begin();
       i != opts.kabi_whitelist_paths.end();
       ++i)
    if (!check_file(*i, cerr, "abidw"))
      return false;

  return true;
}

/// Check that the header files supplied are present.
/// If not, emit an error on stderr.
///
/// @param opts the options instance to use.
///
/// @return true if all header files are present, false otherwise.
static bool
maybe_check_header_files(const options& opts)
{
  for (vector<string>::const_iterator file = opts.header_files.begin();
       file != opts.header_files.end();
       ++file)
    if (!check_file(*file, cerr, "abidw"))
      return false;

  return true;
}

/// 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)
/// and thus minimize the size of the resulting ABI corpus.
///
/// @param read_ctxt the read context to apply the suppression
/// specifications to.
///
/// @param opts the options where to get the suppression
/// specifications from.
static void
set_suppressions(abigail::elf_based_reader& rdr, 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 (opts.force_early_suppression)
    // User asked to unconditionally drop suppressed artifacts from
    // the IR.  Let's drop all nodes matched by suppression
    // specifications from the IR.
    for (auto& s : supprs)
      s->set_drops_artifact_from_ir(true);

  suppression_sptr suppr =
    abigail::tools_utils::gen_suppr_spec_from_headers(opts.headers_dirs,
						      opts.header_files);
  if (suppr)
    {
      if (opts.drop_private_types)
	suppr->set_drops_artifact_from_ir(true);
      supprs.push_back(suppr);
    }

  using abigail::tools_utils::gen_suppr_spec_from_kernel_abi_whitelists;
  const suppressions_type& wl_suppr =
      gen_suppr_spec_from_kernel_abi_whitelists(opts.kabi_whitelist_paths);

  opts.kabi_whitelist_supprs.insert(opts.kabi_whitelist_supprs.end(),
				    wl_suppr.begin(), wl_suppr.end());

  rdr.add_suppressions(supprs);
  rdr.add_suppressions(opts.kabi_whitelist_supprs);
}

/// Set a bunch of tunable buttons on the ELF-based reader from the
/// command-line options.
///
/// @param rdr the reader to tune.
///
/// @param opts the command line options.
static void
set_generic_options(abigail::fe_iface& rdr, options& opts)
{
  rdr.options().drop_undefined_syms = opts.drop_undefined_syms;
  rdr.options().show_stats = opts.show_stats;
  rdr.options().do_log = opts.do_log;
  rdr.options().leverage_dwarf_factorization =
    opts.leverage_dwarf_factorization;
  rdr.options().assume_odr_for_cplusplus =
    opts.assume_odr_for_cplusplus;
  rdr.options().load_undefined_interfaces = opts.load_undefined_interfaces;
}

/// Given a corpus (or a corpus group), write it as ABIXML, read it
/// back into another corpus and compare the resulting two corpora.
///
/// The result of the comparison should be the empty set.
///
/// @param write_ctxt the write context to use for writing the corpus
/// to ABIXML.
///
/// @param corp the input corpus (or corpus group) to serialize to
/// ABIXML.
///
/// @param env the environment used for computing.
///
/// @param t the timer to be used for the logs.
///
/// @param opts the options passed to the main program.
///
/// @param argv the vector of arguments of the main program.
///
/// @return 0 if the self comparison did yield the empty set, 1
/// otherwise.  If the comparison does (wronly) yield a result, that
/// result if emitted on std::cerr.
static int
perform_self_comparison(const write_context_sptr& write_ctxt,
			const corpus_sptr& corp,
			environment& env,
			timer& t,
			options& opts,
			char* argv[])
{
  // Save the abi in abixml format in a temporary file, read
  // it back, and compare the ABI of what we've read back
  // against the ABI of the input ELF file.
  temp_file_sptr tmp_file = temp_file::create();
  set_ostream(*write_ctxt, tmp_file->get_stream());
  corpus_group_sptr corp_group = is_corpus_group(corp);

  if (corp_group)
    write_corpus_group(*write_ctxt, corp_group, 0);
  else
    write_corpus(*write_ctxt, corp, 0);
  tmp_file->get_stream().flush();

#ifdef WITH_DEBUG_SELF_COMPARISON
  if (opts.debug_abidiff)
    {
      opts.type_id_file_path = tmp_file->get_path() + string(".typeid");
      write_canonical_type_ids(*write_ctxt, opts.type_id_file_path);
    }
#endif
  fe_iface_sptr rdr = abixml::create_reader(tmp_file->get_path(), env);
  set_generic_options(*rdr, opts);

#ifdef WITH_DEBUG_SELF_COMPARISON
  if (opts.debug_abidiff
      && !opts.type_id_file_path.empty())
    load_canonical_type_ids(*rdr, opts.type_id_file_path);
#endif

  t.start();
  fe_iface::status sts;
  corpus_sptr corp2;
  corpus_group_sptr corp_group2;

  if (corp_group)
    corp_group2 = abixml::read_corpus_group_from_input(*rdr);
  else
    corp2 = rdr->read_corpus(sts);

  t.stop();
  if (opts.do_log)
    emit_prefix(argv[0], cerr)
      << "Read corpus in: " << t << "\n";

#ifdef WITH_DEBUG_SELF_COMPARISON
  if (opts.debug_abidiff
      && !opts.type_id_file_path.empty())
    remove(opts.type_id_file_path.c_str());
#endif

  if (!corp2 && !corp_group2)
    {
      emit_prefix(argv[0], cerr)
	<< "Could not read temporary XML representation of "
	"elf file back\n";
      return 1;
    }

  diff_context_sptr ctxt(new diff_context);
  set_diff_context(ctxt);
  ctxt->show_locs(opts.show_locs);
  t.start();
  corpus_diff_sptr diff =
    corp_group2
    ? compute_diff(corp_group, corp_group2, ctxt)
    : compute_diff(corp, corp2, ctxt);

  t.stop();
  if (opts.do_log)
    emit_prefix(argv[0], cerr)
      << "computed diff in: " << t << "\n";

  bool has_error = diff->has_changes();
  if (has_error)
    {
      t.start();
      diff->report(cerr);
      t.stop();
      if (opts.do_log)
	emit_prefix(argv[0], cerr)
	  << "emitted report in: " << t << "\n";
      return 1;
    }
  return 0;
}

/// Load an ABI @ref corpus (the internal representation of the ABI of
/// a binary) and write it out as an abixml.
///
/// @param argv the arguments the program was called with.
///
/// @param env the environment the ABI artifacts are being created in.
///
/// @param opts the options of the program.
///
/// @return the exit code: 0 if everything went fine, non-zero
/// otherwise.
static int
load_corpus_and_write_abixml(char* argv[],
			     environment& env,
			     options& opts)
{
  int exit_code = 0;
  timer t;

#ifdef WITH_DEBUG_SELF_COMPARISON
  if (opts.debug_abidiff)
    env.self_comparison_debug_is_on(true);
#endif

#ifdef WITH_DEBUG_TYPE_CANONICALIZATION
  if (opts.debug_type_canonicalization)
    env.debug_type_canonicalization_is_on(true);
  if (opts.debug_die_canonicalization)
    env.debug_die_canonicalization_is_on(true);
#endif

  corpus_sptr corp;
  corpus_group_sptr corp_group;
  fe_iface::status s = fe_iface::STATUS_UNKNOWN;
  corpus::origin requested_fe_kind = corpus::DWARF_ORIGIN;
#ifdef WITH_CTF
  if (opts.use_ctf)
    requested_fe_kind = corpus::CTF_ORIGIN;
#endif
#ifdef WITH_BTF
  if (opts.use_btf)
    requested_fe_kind = corpus::BTF_ORIGIN;
#endif

  // First of all, create a reader to read the ABI from the file
  // specfied in opts ...
  abigail::elf_based_reader_sptr reader =
    create_best_elf_based_reader(opts.in_file_path,
				 opts.di_root_paths,
				 env, requested_fe_kind,
				 opts.load_all_types,
				 opts.linux_kernel_mode);
  ABG_ASSERT(reader);

  // ... then tune a bunch of "buttons" on the newly created reader
  // ...
  set_generic_options(*reader, opts);
  set_suppressions(*reader, opts);

  // If the user asked us to check if we found the "alternate debug
  // info file" associated to the input binary, then proceed to do so
  // ...
  if (opts.check_alt_debug_info_path)
    {
      string alt_di_path = reader->alternate_dwarf_debug_info_path();
      if (!alt_di_path.empty())
	{
	  cout << "found the alternate debug info file";
	  if (opts.show_base_name_alt_debug_info_path)
	    {
	      tools_utils::base_name(alt_di_path, alt_di_path);
	      cout << " '" << alt_di_path << "'";
	    }
	  cout << "\n";
	  return 0;
	}
      else
	{
	  emit_prefix(argv[0], cerr)
	    << "could not find alternate debug info file\n";
	  return 1;
	}
    }

  // ... ff we are asked to only analyze exported interfaces (to stay
  // concise), then take that into account ...
  if (opts.exported_interfaces_only.has_value())
    env.analyze_exported_interfaces_only(*opts.exported_interfaces_only);

  // And now, really read/analyze the ABI of the input file.
  t.start();
  corp = reader->read_corpus(s);
  t.stop();
  if (opts.do_log)
    emit_prefix(argv[0], cerr)
      << "read corpus from elf file in: " << t << "\n";

  if (opts.do_log)
    emit_prefix(argv[0], cerr)
      << "reset reader ELF in: " << t << "\n";

  // If we couldn't create a corpus, emit some (hopefully) useful
  // diagnostics and return an error.
  if (!corp)
    {
      if (s == fe_iface::STATUS_DEBUG_INFO_NOT_FOUND)
	{
	  if (opts.di_root_paths.empty())
	    {
	      emit_prefix(argv[0], cerr)
		<< "Could not read debug info from "
		<< opts.in_file_path << "\n";

	      emit_prefix(argv[0], cerr)
		<< "You might want to supply the root directory where "
		"to search debug info from, using the "
		"--debug-info-dir option "
		"(e.g --debug-info-dir /usr/lib/debug)\n";
	    }
	  else
	    {
	      emit_prefix(argv[0], cerr)
		<< "Could not read debug info for '" << opts.in_file_path
		<< "' from debug info root directory '";
	      for (vector<string>::const_iterator i =
		     opts.di_root_paths.begin();
		   i != opts.di_root_paths.end();
		   ++i)
		{
		  if (i != opts.di_root_paths.begin())
		    cerr << ", ";
		  cerr << *i;
		}
	    }
	}
      else if (s == fe_iface::STATUS_NO_SYMBOLS_FOUND)
	emit_prefix(argv[0], cerr)
	  << "Could not read ELF symbol information from "
	  << opts.in_file_path << "\n";
      else if (s & fe_iface::STATUS_ALT_DEBUG_INFO_NOT_FOUND)
	{
	  emit_prefix(argv[0], cerr)
	    << "Could not read alternate debug info file";
	  if (!reader->alternate_dwarf_debug_info_path().empty())
	    cerr << " '" << reader->alternate_dwarf_debug_info_path() << "'";
	  cerr << " for '"
	    << opts.in_file_path << "'.\n";
	  emit_prefix(argv[0], cerr)
	    << "You might have forgotten to install some "
	    "additional needed debug info\n";
	}

      return 1;
    }

  if (opts.fail_no_debug_info
      && s & fe_iface::STATUS_DEBUG_INFO_NOT_FOUND)
    {
      emit_prefix(argv[0], cerr)
	<< "Could not read debug info from "
	<< opts.in_file_path << "\n";

      emit_prefix(argv[0], cerr)
	<< "You might want to either recompile the binary with "
	"debug info support or supply the root directory where "
	"to search debug info from, using the "
	"--debug-info-dir option "
	"(e.g --debug-info-dir /usr/lib/debug)\n";
      return 1;
    }

  if (opts.list_dependencies)
    {
      // Show the dependencies of the corpus and display them.
      set<string> dependencies;
      if (tools_utils::get_dependencies(*corp, opts.added_bins_dirs,
					dependencies))
	{
	  cout << "Dependencies of '" << corp->get_path()
	       << "':\n\t";
	  int n = 0;
	  for (const auto& dep : dependencies)
	    {
	      if (n)
		cout << ", ";
	      cout << dep;
	      ++n;
	    }
	  cout << "\n";
	}
    }

  if (!opts.added_bins.empty())
    corp_group =
      stick_corpus_and_binaries_into_corpus_group(reader, corp,
						  opts.added_bins,
						  opts.added_bins_dirs);

  if (opts.follow_dependencies)
    {
      // load the dependencies of the corpus and put them all into a
      // corpus group.

      // If a corpus_group already exists, use that one ...
      if (corp_group && !corp_group->is_empty())
	add_dependencies_into_corpus_group(reader, *corp,
					   opts.added_bins_dirs,
					   *corp_group);
      else
	// .. otherwise, create a new corpus group.
	corp_group =
	  stick_corpus_and_dependencies_into_corpus_group(reader, corp,
							  opts.added_bins_dirs);
    }

  // Clear some resources to gain back some space.
  t.start();
  reader.reset();
  t.stop();

  // Now create a write context and write out an ABI XML description
  // of the read corpus.
  t.start();
  const write_context_sptr& write_ctxt = create_write_context(env, cout);
  set_common_options(*write_ctxt, opts);
  t.stop();

  if (opts.do_log)
    emit_prefix(argv[0], cerr)
      << "created & initialized write context in: "
      << t << "\n";

  if (opts.abidiff)
    return perform_self_comparison(write_ctxt,
				   corp_group ? corp_group : corp,
				   env, t, opts, argv);

  if (opts.noout)
    return 0;

  if (!opts.out_file_path.empty())
    {
      ofstream of(opts.out_file_path.c_str(), std::ios_base::trunc);
      if (!of.is_open())
        {
          emit_prefix(argv[0], cerr)
            << "could not open output file '"
            << opts.out_file_path << "'\n";
          return 1;
        }
      set_ostream(*write_ctxt, of);
      t.start();
      if (corp_group)
	write_corpus_group(*write_ctxt, corp_group, 0);
      else
	write_corpus(*write_ctxt, corp, 0);
      t.stop();
      if (opts.do_log)
        emit_prefix(argv[0], cerr)
          << "emitted abixml output in: " << t << "\n";
      of.close();
      return 0;
    }
  else
    {
      t.start();
      exit_code =
	corp_group
	? !write_corpus_group(*write_ctxt, corp_group, 0)
	: !write_corpus(*write_ctxt, corp, 0);
      t.stop();
      if (opts.do_log)
        emit_prefix(argv[0], cerr)
          << "emitted abixml out in: " << t << "\n";
    }

  return exit_code;
}

/// Load a corpus group representing the union of a Linux Kernel
/// vmlinux binary and its modules, and emit an abixml representation
/// for it.
///
/// @param argv the arguments this program was called with.
///
/// @param env the environment the ABI artifacts are created in.
///
/// @param opts the options this program was created with.
///
/// @return the exit code.  Zero if everything went well, non-zero
/// otherwise.
static int
load_kernel_corpus_group_and_write_abixml(char* argv[],
					  environment& env,
					  options& opts)
{
  if (!(tools_utils::is_dir(opts.in_file_path) && opts.corpus_group_for_linux))
    return 1;

  int exit_code = 0;

  if (!opts.vmlinux.empty())
    if (!abigail::tools_utils::check_file(opts.vmlinux, cerr, argv[0]))
      return 1;

#ifdef WITH_DEBUG_SELF_COMPARISON
  if (opts.debug_abidiff)
    env.self_comparison_debug_is_on(true);
#endif

  timer t, global_timer;
  suppressions_type supprs;

  if (opts.exported_interfaces_only.has_value())
    env.analyze_exported_interfaces_only(*opts.exported_interfaces_only);

  if (opts.do_log)
    emit_prefix(argv[0], cerr)
      << "going to build ABI representation of the Linux Kernel ...\n";

  global_timer.start();
  t.start();
  corpus::origin requested_fe_kind =
#ifdef WITH_CTF
    opts.use_ctf ? corpus::CTF_ORIGIN :
#endif
#ifdef WITH_BTF
    opts.use_btf ? corpus::BTF_ORIGIN :
#endif
    corpus::DWARF_ORIGIN;
  corpus_group_sptr group =
    build_corpus_group_from_kernel_dist_under(opts.in_file_path,
					      /*debug_info_root=*/"",
					      opts.vmlinux,
					      opts.suppression_paths,
					      opts.kabi_whitelist_paths,
					      supprs, opts.do_log, env,
					      requested_fe_kind);
  t.stop();

  if (opts.do_log)
    {
      emit_prefix(argv[0], cerr)
	<< "built ABI representation of the Linux Kernel in: "
	<< t << "\n";
    }

  if (!group)
    return 1;

  if (!opts.noout)
    {
      const xml_writer::write_context_sptr& ctxt
	  = xml_writer::create_write_context(env, cout);
      set_common_options(*ctxt, opts);

      if (opts.abidiff)
	return perform_self_comparison(ctxt, group, env, t, opts, argv);

      if (!opts.out_file_path.empty())
	{
	  ofstream of(opts.out_file_path.c_str(), std::ios_base::trunc);
	  if (!of.is_open())
	    {
	      emit_prefix(argv[0], cerr)
		<< "could not open output file '"
		<< opts.out_file_path << "'\n";
	      return 1;
	    }

	  if (opts.do_log)
	    emit_prefix(argv[0], cerr)
	      << "emitting the abixml output ...\n";
	  set_ostream(*ctxt, of);
	  t.start();
	  exit_code = !write_corpus_group(*ctxt, group, 0);
	  t.stop();
	  if (opts.do_log)
	    emit_prefix(argv[0], cerr)
	      << "emitted abixml output in: " << t << "\n";
	}
      else
	{
	  if (opts.do_log)
	    emit_prefix(argv[0], cerr)
	      << "emitting the abixml output ...\n";
	  t.start();
	  exit_code = !write_corpus_group(*ctxt, group, 0);
	  t.stop();
	  if (opts.do_log)
	    emit_prefix(argv[0], cerr)
	      << "emitted abixml output in: " << t << "\n";
	}
    }

  global_timer.stop();
  if (opts.do_log)
    emit_prefix(argv[0], cerr)
      << "total processing done in " << global_timer << "\n";
  return exit_code;
}

int
main(int argc, char* argv[])
{
  options opts;

  abigail::tools_utils::initialize();

  if (!parse_command_line(argc, argv, opts)
      || (opts.in_file_path.empty()
	  && !opts.display_version
	  && !opts.display_abixml_version))
    {
      if (!opts.wrong_option.empty())
	emit_prefix(argv[0], cerr)
	  << "unrecognized option: " << opts.wrong_option << "\n";
      display_usage(argv[0], cerr);
      return 1;
    }

  if (opts.display_version)
    {
      emit_prefix(argv[0], cout)
	<< abigail::tools_utils::get_library_version_string()
	<< "\n";
      return 0;
    }

    if (opts.display_abixml_version)
      {
	emit_prefix(argv[0], cout)
	  << abigail::tools_utils::get_abixml_version_string()
	  << "\n";
	return 0;
      }

  ABG_ASSERT(!opts.in_file_path.empty());
  if (opts.corpus_group_for_linux)
    {
      if (!abigail::tools_utils::check_dir(opts.in_file_path, cerr, argv[0]))
	return 1;
    }
  else
    {
      if (!abigail::tools_utils::check_file(opts.in_file_path, cerr, argv[0]))
	return 1;
    }

  if (!maybe_check_suppression_files(opts))
    return 1;

  if (!maybe_check_header_files(opts))
    return 1;

  abigail::tools_utils::file_type type =
    abigail::tools_utils::guess_file_type(opts.in_file_path);
  if (type != abigail::tools_utils::FILE_TYPE_ELF
      && type != abigail::tools_utils::FILE_TYPE_AR
      && type != abigail::tools_utils::FILE_TYPE_DIR)
    {
      emit_prefix(argv[0], cerr)
	<< "files of the kind of "<< opts.in_file_path << " are not handled\n";
      return 1;
    }

  environment env;
  int exit_code = 0;

  if (tools_utils::is_regular_file(opts.in_file_path))
    exit_code = load_corpus_and_write_abixml(argv, env, opts);
  else
    exit_code = load_kernel_corpus_group_and_write_abixml(argv, env, opts);

  return exit_code;
}
